2 * $Id: uams_gss.c,v 1.9 2009-10-15 14:54:43 franklahm 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 * Copyright (c) 2004 Bjoern Fernhomberg
8 * All Rights Reserved. See COPYRIGHT.
13 #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 */
37 #include <atalk/logger.h>
38 #include <atalk/afp.h>
39 #include <atalk/uam.h>
40 #include <atalk/util.h>
42 /* Kerberos includes */
48 #if HAVE_GSSAPI_GSSAPI_H
49 #include <gssapi/gssapi.h>
52 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
53 #include <gssapi/gssapi_generic.h>
56 #if HAVE_GSSAPI_GSSAPI_KRB5_H
57 #include <gssapi/gssapi_krb5.h>
64 /* We work around something I don't entirely understand... */
65 /* BF: This is a Heimdal/MIT compatibility fix */
66 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
67 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
74 #define MIN(a, b) ((a > b) ? b : a)
76 static void log_status( char *s, OM_uint32 major_status,
77 OM_uint32 minor_status )
79 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
80 OM_uint32 min_status, maj_status;
81 OM_uint32 maj_ctx = 0, min_ctx = 0;
84 maj_status = gss_display_status( &min_status, major_status,
85 GSS_C_GSS_CODE, GSS_C_NULL_OID,
87 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
88 (int)msg.length, msg.value, strerror(errno));
89 gss_release_buffer(&min_status, &msg);
95 maj_status = gss_display_status( &min_status, minor_status,
96 GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
98 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
99 (int)msg.length, msg.value, strerror(errno));
100 gss_release_buffer(&min_status, &msg);
108 static void log_ctx_flags( OM_uint32 flags )
111 if (flags & GSS_C_DELEG_FLAG)
112 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
113 if (flags & GSS_C_MUTUAL_FLAG)
114 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
115 if (flags & GSS_C_REPLAY_FLAG)
116 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
117 if (flags & GSS_C_SEQUENCE_FLAG)
118 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
119 if (flags & GSS_C_CONF_FLAG)
120 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
121 if (flags & GSS_C_INTEG_FLAG)
122 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
126 static void log_principal(gss_name_t server_name)
129 OM_uint32 major_status = 0, minor_status = 0;
130 gss_buffer_desc exported_name;
132 /* Only for debugging purposes, check the gssapi internal representation */
133 major_status = gss_export_name(&minor_status, server_name, &exported_name);
134 LOG(log_debug, logtype_uams, "log_principal: exported server name is %s", (char*) exported_name.value);
135 gss_release_buffer( &minor_status, &exported_name );
139 /* get the principal from afpd and import it into server_name */
140 static int get_afpd_principal(void *obj, gss_name_t *server_name)
142 OM_uint32 major_status = 0, minor_status = 0;
143 char *realm, *fqdn, *service, *principal, *p;
144 size_t realmlen=0, fqdnlen=0, servicelen=0;
145 size_t principal_length;
146 gss_buffer_desc s_princ_buffer;
148 /* get all the required information from afpd */
149 if (uam_afpserver_option(obj, UAM_OPTION_KRB5REALM, (void*) &realm, &realmlen) < 0)
151 LOG(log_debug, logtype_uams, "get_afpd_principal: REALM: %s", realm);
153 if (uam_afpserver_option(obj, UAM_OPTION_FQDN, (void*) &fqdn, &fqdnlen) < 0)
155 LOG(log_debug, logtype_uams, "get_afpd_principal: fqdn: %s", fqdn);
157 if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
159 LOG(log_debug, logtype_uams, "get_afpd_principal: service: %s", service);
161 /* we need all the info, log error and return if one's missing */
162 if (!service || !servicelen || !fqdn || !fqdnlen || !realm || !realmlen) {
163 LOG(log_error, logtype_uams,
164 "get_afpd_principal: could not retrieve required information from afpd.");
168 /* allocate memory to hold the temporary principal string */
169 principal_length = servicelen + 1 + fqdnlen + 1 + realmlen + 1;
170 if ( NULL == (principal = (char*) malloc( principal_length)) ) {
171 LOG(log_error, logtype_uams,
172 "get_afpd_principal: out of memory allocating %u bytes",
178 * Build the principal string.
179 * Format: 'service/fqdn@realm'
181 strlcpy( principal, service, principal_length);
182 strlcat( principal, "/", principal_length);
185 * The fqdn we get from afpd may contain a port.
186 * We need to strip the port from fqdn for principal.
188 p = strchr(fqdn, ':');
191 strlcat( principal, fqdn, principal_length);
194 strlcat( principal, "@", principal_length);
195 strlcat( principal, realm, principal_length);
198 * Import our principal into the gssapi internal representation
199 * stored in server_name.
201 s_princ_buffer.value = principal;
202 s_princ_buffer.length = strlen( principal ) + 1;
204 LOG(log_debug, logtype_uams, "get_afpd_principal: importing principal `%s'", principal);
205 major_status = gss_import_name( &minor_status,
211 * Get rid of malloc'ed memmory.
212 * Don't release the s_princ_buffer, we free principal instead.
216 if (major_status != GSS_S_COMPLETE) {
217 /* Importing our service principal failed, bail out. */
218 log_status( "import_principal", major_status, minor_status );
225 /* get the username */
226 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
228 OM_uint32 major_status = 0, minor_status = 0;
229 gss_buffer_desc client_name_buffer;
234 * To extract the unix username, use gss_display_name on client_name.
235 * We do rely on gss_display_name returning a zero terminated string.
236 * The username returned contains the realm and possibly an instance.
237 * We only want the username for afpd, so we have to strip those from
238 * the username before copying it to afpd's buffer.
241 major_status = gss_display_name( &minor_status, *client_name,
242 &client_name_buffer, (gss_OID *)NULL );
243 if (major_status != GSS_S_COMPLETE) {
244 log_status( "display_name", major_status, minor_status );
248 LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
251 p = strchr( client_name_buffer.value, '@' );
254 /* FIXME: chop off instance? */
255 p = strchr( client_name_buffer.value, '/' );
259 /* check if this username fits into afpd's username buffer */
260 namelen = strlen(client_name_buffer.value);
261 if ( namelen >= ulen ) {
262 /* The username is too long for afpd's buffer, bail out */
263 LOG(log_error, logtype_uams,
264 "get_client_username: username `%s' too long", client_name_buffer.value);
268 /* copy stripped username to afpd's buffer */
269 strlcpy(username, client_name_buffer.value, ulen);
272 /* we're done with client_name_buffer, release it */
273 gss_release_buffer(&minor_status, &client_name_buffer );
278 /* wrap afpd's sessionkey */
279 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
281 OM_uint32 status = 0;
283 gss_buffer_desc sesskey_buff, wrap_buff;
286 * gss_wrap afpd's session_key.
287 * This is needed fo OS X 10.3 clients. They request this information
288 * with type 8 (kGetKerberosSessionKey) on FPGetSession.
289 * See AFP 3.1 specs, page 77.
292 sesskey_buff.value = sinfo->sessionkey;
293 sesskey_buff.length = sinfo->sessionkey_len;
295 /* gss_wrap the session key with the default machanism.
296 Require both confidentiality and integrity services */
297 gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
299 if ( status != GSS_S_COMPLETE) {
300 LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey");
301 log_status( "GSS wrap", 0, status );
305 /* store the wrapped session key in afpd's session_info struct */
306 if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) {
307 LOG(log_error, logtype_uams,
308 "wrap_sessionkey: out of memory tyring to allocate %u bytes",
312 /* cryptedkey is binary data */
313 memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
314 sinfo->cryptedkey_len = wrap_buff.length;
317 /* we're done with buffer, release */
318 gss_release_buffer( &status, &wrap_buff );
324 static int acquire_credentials (gss_name_t *server_name, gss_cred_id_t *server_creds)
326 OM_uint32 major_status = 0, minor_status = 0;
329 if (envp = getenv("KRB5_KTNAME"))
330 LOG(log_debug, logtype_uams,
331 "acquire credentials: acquiring credentials (uid = %d, keytab = %s)",
332 (int)geteuid(), envp);
334 LOG(log_debug, logtype_uams,
335 "acquire credentials: acquiring credentials (uid = %d) - $KRB5_KTNAME not found in env",
339 * Acquire credentials usable for accepting context negotiations.
340 * Credentials are for server_name, have an indefinite lifetime,
341 * have no specific mechanisms, are to be used for accepting context
342 * negotiations and are to be placed in server_creds.
343 * We don't care about the mechanisms or about the time for which they are valid.
345 major_status = gss_acquire_cred( &minor_status, *server_name,
346 GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
347 server_creds, NULL, NULL );
349 if (major_status != GSS_S_COMPLETE) {
350 log_status( "acquire_cred", major_status, minor_status );
358 static int accept_sec_context (gss_ctx_id_t *context, gss_cred_id_t server_creds,
359 gss_buffer_desc *ticket_buffer, gss_name_t *client_name,
360 gss_buffer_desc *authenticator_buff)
362 OM_uint32 major_status = 0, minor_status = 0, ret_flags;
364 /* Initialize autheticator buffer. */
365 authenticator_buff->length = 0;
366 authenticator_buff->value = NULL;
368 LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
369 ticket_buffer->length);
372 * Try to accept the secondary context using the tocken in ticket_buffer.
373 * We don't care about the mechanisms used, nor for the time.
374 * We don't act as a proxy either.
376 major_status = gss_accept_sec_context( &minor_status, context,
377 server_creds, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
378 client_name, NULL, authenticator_buff,
379 &ret_flags, NULL, NULL );
381 if (major_status != GSS_S_COMPLETE) {
382 log_status( "accept_sec_context", major_status, minor_status );
385 log_ctx_flags( ret_flags );
390 /* return 0 on success */
391 static int do_gss_auth(void *obj, char *ibuf, int ticket_len,
392 char *rbuf, int *rbuflen, char *username, int ulen,
393 struct session_info *sinfo )
395 OM_uint32 status = 0;
396 gss_name_t server_name, client_name;
397 gss_cred_id_t server_creds;
398 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
399 gss_buffer_desc ticket_buffer, authenticator_buff;
402 /* import our principal name from afpd */
403 if (get_afpd_principal(obj, &server_name) != 0) {
406 log_principal(server_name);
408 /* Now we have to acquire our credentials */
409 if ((ret = acquire_credentials (&server_name, &server_creds)))
413 * Try to accept the secondary context, using the ticket/token the
414 * client sent us. Ticket is stored at current ibuf position.
415 * Don't try to release ticket_buffer later, it points into ibuf!
417 ticket_buffer.length = ticket_len;
418 ticket_buffer.value = ibuf;
420 ret = accept_sec_context (&context_handle, server_creds, &ticket_buffer,
421 &client_name, &authenticator_buff);
424 /* We succesfully acquired the secondary context, now get the
425 username for afpd and gss_wrap the sessionkey */
426 if ( 0 == (ret = get_client_username(username, ulen, &client_name)) ) {
427 ret = wrap_sessionkey(context_handle, sinfo);
431 /* FIXME: Is copying the authenticator really necessary?
432 Where is this documented? */
433 u_int16_t auth_len = htons( authenticator_buff.length );
435 /* copy the authenticator length into the reply buffer */
436 memcpy( rbuf, &auth_len, sizeof(auth_len) );
437 *rbuflen += sizeof(auth_len);
438 rbuf += sizeof(auth_len);
440 /* copy the authenticator value into the reply buffer */
441 memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
442 *rbuflen += authenticator_buff.length;
445 /* Clean up after ourselves */
446 gss_release_name( &status, &client_name );
447 if ( authenticator_buff.value)
448 gss_release_buffer( &status, &authenticator_buff );
450 gss_delete_sec_context( &status, &context_handle, NULL );
452 gss_release_cred( &status, &server_creds );
455 gss_release_name( &status, &server_name );
460 /* -------------------------- */
461 static int gss_login(void *obj, struct passwd **uam_pwd,
462 char *ibuf, size_t ibuflen,
463 char *rbuf, size_t *rbuflen)
470 /* The reply contains a two-byte ID value - note
471 * that Apple's implementation seems to always return 1 as well
474 memcpy(rbuf, &temp16, sizeof(temp16));
475 *rbuflen += sizeof(temp16);
476 return AFPERR_AUTHCONT;
479 static int gss_logincont(void *obj, struct passwd **uam_pwd,
480 char *ibuf, size_t ibuflen,
481 char *rbuf, size_t *rbuflen)
483 struct passwd *pwd = NULL;
486 u_int16_t ticket_len;
490 struct session_info *sinfo;
492 /* Apple's AFP 3.1 documentation specifies that this command
493 * takes the following format:
495 * id returned in LoginExt response (u_int16_t)
496 * username (format unspecified) padded, when necessary, to end on an even boundary
497 * ticket length (u_int16_t)
501 /* Observation of AFP clients in the wild indicate that the actual
502 * format of this request is as follows:
503 * pad (byte) [consumed before login_ext is called]
504 * ?? (byte) - always observed to be 0
505 * id returned in LoginExt response (u_int16_t)
506 * username, encoding unspecified, null terminated C string,
507 * padded when the terminating null is an even numbered byte.
508 * The packet is formated such that the username begins on an
509 * odd numbered byte. Eg if the username is 3 characters and the
510 * terminating null makes 4, expect to pad the the result.
511 * The encoding of this string is unknown.
512 * ticket length (u_int16_t)
516 rblen = *rbuflen = 0;
518 if (ibuflen < 1 +sizeof(login_id)) {
519 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
522 ibuf++, ibuflen--; /* ?? */
524 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
525 memcpy( &login_id, ibuf, sizeof(login_id) );
526 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
527 login_id = ntohs( login_id );
529 /* get the username buffer from apfd */
530 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
533 /* get the session_info structure from afpd. We need the session key */
534 if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
537 if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
538 /* Should never happen. Most likely way too old afpd version */
539 LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set");
543 /* We skip past the 'username' parameter because all that matters is the ticket */
545 while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
547 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
551 ibuf++, ibuflen--; /* null termination */
553 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
555 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
557 /* get the length of the ticket the client sends us */
558 memcpy(&ticket_len, ibuf, sizeof(ticket_len));
559 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
560 ticket_len = ntohs( ticket_len );
562 /* a little bounds checking */
563 if (ticket_len > ibuflen) {
564 LOG(log_info, logtype_uams,
565 "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
569 /* now try to authenticate */
570 if (!do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
571 /* We use the username we got back from the gssapi client_name.
572 Should we compare this to the username the client sent in the clear?
573 We know the character encoding of the cleartext username (UTF8), what
574 encoding is the gssapi name in? */
575 if((pwd = uam_getname( obj, username, userlen )) == NULL) {
576 LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
579 if (uam_checkuser(pwd) < 0) {
580 LOG(log_info, logtype_uams, "%s not a valid user", username);
581 return AFPERR_NOTAUTH;
587 LOG(log_info, logtype_uams, "do_gss_auth failed" );
594 * For the krb5 uam, this function only needs to return a two-byte
595 * login-session id. None of the data provided by the client up to this
596 * point is trustworthy as we'll have a signed ticket to parse in logincont.
598 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
599 char *ibuf, size_t ibuflen,
600 char *rbuf, size_t *rbuflen)
606 /* The reply contains a two-byte ID value - note
607 * that Apple's implementation seems to always return 1 as well
610 memcpy(rbuf, &temp16, sizeof(temp16));
611 *rbuflen += sizeof(temp16);
612 return AFPERR_AUTHCONT;
616 static void gss_logout() {
619 int uam_setup(const char *path)
621 if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
622 gss_login, gss_logincont, gss_logout, gss_login_ext) < 0)
623 if (uam_register(UAM_SERVER_LOGIN, path, "Client Krb v2",
624 gss_login, gss_logincont, gss_logout) < 0)
630 static void uam_cleanup(void)
632 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
635 UAM_MODULE_EXPORT struct uam_export uams_gss = {
638 uam_setup, uam_cleanup