2 * $Id: uams_gss.c,v 1.1 2003-08-22 17:12:45 samnoble Exp $
4 * Copyright (c) 1990,1993 Regents of The University of Michigan.
5 * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu)
6 * Copyright (c) 2003 The Reed Institute
7 * All Rights Reserved. See COPYRIGHT.
12 #endif /* HAVE_CONFIG_H */
19 #endif /* HAVE_UNISTD_H */
24 #else /* STDC_HEADERS */
28 #endif /* HAVE_STRCHR */
29 char *strchr (), *strrchr ();
31 #define memcpy(d,s,n) bcopy ((s), (d), (n))
32 #define memmove(d,s,n) bcopy ((s), (d), (n))
33 #endif /* ! HAVE_MEMCPY */
34 #endif /* STDC_HEADERS */
36 #include <atalk/logger.h>
38 // #include <security/pam_appl.h>
40 #include <atalk/afp.h>
41 #include <atalk/uam.h>
43 #include <gssapi/gssapi.h>
44 #include <gssapi/gssapi_generic.h>
45 #include <gssapi/gssapi_krb5.h>
47 /* The following routine is derived from code found in some GSS
48 * documentation from SUN.
50 static void log_status( char *s, OM_uint32 major_status,
51 OM_uint32 minor_status )
53 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
54 OM_uint32 min_status, maj_status;
55 OM_uint32 maj_ctx = 0, min_ctx = 0;
58 maj_status = gss_display_status( &min_status, major_status,
59 GSS_C_GSS_CODE, GSS_C_NULL_OID,
61 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
62 (int)msg.length, msg.value, strerror(errno));
63 gss_release_buffer(&min_status, &msg);
69 maj_status = gss_display_status( &min_status, minor_status,
70 GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
72 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
73 (int)msg.length, msg.value, strerror(errno));
74 gss_release_buffer(&min_status, &msg);
82 void log_ctx_flags( OM_uint32 flags )
84 if (flags & GSS_C_DELEG_FLAG)
85 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
86 if (flags & GSS_C_MUTUAL_FLAG)
87 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
88 if (flags & GSS_C_REPLAY_FLAG)
89 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
90 if (flags & GSS_C_SEQUENCE_FLAG)
91 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
92 if (flags & GSS_C_CONF_FLAG)
93 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
94 if (flags & GSS_C_INTEG_FLAG)
95 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
97 /* We work around something I don't entirely understand... */
98 #if !defined (GSS_C_NT_HOSTBASED_SERVICE)
99 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
102 #define MIN(a, b) ((a > b) ? b : a)
103 /* return 0 on success */
104 static int do_gss_auth( char *service, char *ibuf, int ticket_len,
105 char *rbuf, int *rbuflen, char *username, int ulen )
107 OM_uint32 major_status = 0, minor_status = 0;
108 gss_name_t server_name;
109 gss_cred_id_t server_creds;
110 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
111 gss_buffer_desc ticket_buffer, authenticator_buff;
112 gss_name_t client_name;
115 gss_buffer_desc s_princ_buffer;
117 s_princ_buffer.value = service;
118 s_princ_buffer.length = strlen( service ) + 1;
120 LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: importing name" );
121 major_status = gss_import_name( &minor_status,
123 GSS_C_NT_HOSTBASED_SERVICE,
125 if (major_status != GSS_S_COMPLETE) {
126 log_status( "import_name", major_status, minor_status );
131 LOG(log_debug, logtype_uams,
132 "uams_gss.c :do_gss_auth: acquiring credentials (uid = %d, keytab = %s)",
133 (int)geteuid(), getenv( "KRB5_KTNAME") );
134 major_status = gss_acquire_cred( &minor_status, server_name,
135 GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
136 &server_creds, NULL, NULL );
137 if (major_status != GSS_S_COMPLETE) {
138 log_status( "acquire_cred", major_status, minor_status );
143 /* The GSSAPI docs say that this should be done in a big "do" loop,
144 * but Apple's implementation doesn't seem to support this behavior.
146 ticket_buffer.length = ticket_len;
147 ticket_buffer.value = ibuf;
148 authenticator_buff.length = 0;
149 authenticator_buff.value = NULL;
150 LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: accepting context" );
151 major_status = gss_accept_sec_context( &minor_status, &context_handle,
152 server_creds, &ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
153 &client_name, NULL, &authenticator_buff,
154 &ret_flags, NULL, NULL );
156 if (major_status == GSS_S_COMPLETE) {
157 gss_buffer_desc client_name_buffer;
159 log_ctx_flags( ret_flags );
160 /* use gss_display_name on client_name */
161 major_status = gss_display_name( &minor_status, client_name,
162 &client_name_buffer, (gss_OID *)NULL );
163 if (major_status == GSS_S_COMPLETE) {
164 u_int16_t auth_len = htons( authenticator_buff.length );
165 /* save the username... note that doing it this way is
166 * not the best idea: if a principal is truncated, a user could be
169 memcpy( username, client_name_buffer.value,
170 MIN(client_name_buffer.length, ulen - 1));
171 username[MIN(client_name_buffer.length, ulen - 1)] = 0;
173 LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: user is %s!", username );
174 /* copy the authenticator length into the reply buffer */
175 memcpy( rbuf, &auth_len, sizeof(auth_len) );
176 *rbuflen += sizeof(auth_len), rbuf += sizeof(auth_len);
178 /* copy the authenticator value into the reply buffer */
179 memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
180 *rbuflen += authenticator_buff.length;
182 gss_release_buffer( &minor_status, &client_name_buffer );
184 log_status( "display_name", major_status, minor_status );
189 /* Clean up after ourselves */
190 gss_release_name( &minor_status, &client_name );
191 gss_release_buffer( &minor_status, &authenticator_buff );
192 gss_delete_sec_context( &minor_status,
193 &context_handle, NULL );
195 log_status( "accept_sec_context", major_status, minor_status );
198 gss_release_cred( &minor_status, &server_creds );
201 gss_release_name( &minor_status, &server_name );
206 /* -------------------------- */
207 static int gss_login(void *obj, struct passwd **uam_pwd,
208 char *ibuf, int ibuflen,
209 char *rbuf, int *rbuflen)
216 /* The reply contains a two-byte ID value - note
217 * that Apple's implementation seems to always return 1 as well
220 memcpy(rbuf, &temp16, sizeof(temp16));
221 *rbuflen += sizeof(temp16);
222 return AFPERR_AUTHCONT;
225 static int gss_logincont(void *obj, struct passwd **uam_pwd,
226 char *ibuf, int ibuflen,
227 char *rbuf, int *rbuflen)
229 struct passwd *pwd = NULL;
232 u_int16_t ticket_len;
236 int userlen, servicelen;
238 /* Apple's AFP 3.1 documentation specifies that this command
239 * takes the following format:
241 * id returned in LoginExt response (u_int16_t)
242 * username (format unspecified) padded, when necessary, to end on an even boundary
243 * ticket length (u_int16_t)
247 /* Observation of AFP clients in the wild indicate that the actual
248 * format of this request is as follows:
249 * pad (byte) [consumed before login_ext is called]
250 * ?? (byte) - always observed to be 0
251 * id returned in LoginExt response (u_int16_t)
252 * username, encoding unspecified, null terminated C string,
253 * padded when the terminating null is an even numbered byte.
254 * The packet is formated such that the username begins on an
255 * odd numbered byte. Eg if the username is 3 characters and the
256 * terminating null makes 4, expect to pad the the result.
257 * The encoding of this string is unknown.
258 * ticket length (u_int16_t)
262 rblen = *rbuflen = 0;
264 ibuf++, ibuflen--; /* ?? */
266 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
267 memcpy( &login_id, ibuf, sizeof(login_id) );
268 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
269 login_id = ntohs( login_id );
271 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
274 if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
280 /* We skip past the 'username' parameter because all that matters is the ticket */
282 while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
284 LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
288 ibuf++, ibuflen--; /* null termination */
290 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
292 LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
294 memcpy(&ticket_len, ibuf, sizeof(ticket_len));
295 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
296 ticket_len = ntohs( ticket_len );
298 if (ticket_len > ibuflen) {
299 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: invalid ticket length");
303 if (!do_gss_auth(service, ibuf, ticket_len, rbuf, &rblen, username, userlen)) {
304 char *at = strchr( username, '@' );
306 // Chop off the realm name
309 if((pwd = uam_getname( username, userlen )) == NULL) {
310 LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
313 if (uam_checkuser(pwd) < 0) {
314 LOG(log_info, logtype_uams, "%s not a valid user", username);
315 return AFPERR_NOTAUTH;
321 LOG(log_info, logtype_uams, "do_gss_auth failed" );
328 * For the krb5 uam, this function only needs to return a two-byte
329 * login-session id. None of the data provided by the client up to this
330 * point is trustworthy as we'll have a signed ticket to parse in logincont.
332 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
333 char *ibuf, int ibuflen,
334 char *rbuf, int *rbuflen)
340 /* The reply contains a two-byte ID value - note
341 * that Apple's implementation seems to always return 1 as well
344 memcpy(rbuf, &temp16, sizeof(temp16));
345 *rbuflen += sizeof(temp16);
346 return AFPERR_AUTHCONT;
350 static void gss_logout() {
353 static int uam_setup(const char *path)
355 if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
356 gss_login, gss_logincont, gss_logout, gss_login_ext) < 0)
362 static void uam_cleanup(void)
364 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
367 UAM_MODULE_EXPORT struct uam_export uams_gss = {
370 uam_setup, uam_cleanup