2 * $Id: uams_gss.c,v 1.2.2.4.2.1 2004-12-07 18:41:10 bfernhomberg 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 */
20 #endif /* HAVE_UNISTD_H */
25 #else /* STDC_HEADERS */
29 #endif /* HAVE_STRCHR */
30 char *strchr (), *strrchr ();
32 #define memcpy(d,s,n) bcopy ((s), (d), (n))
33 #define memmove(d,s,n) bcopy ((s), (d), (n))
34 #endif /* ! HAVE_MEMCPY */
35 #endif /* STDC_HEADERS */
38 #include <atalk/logger.h>
39 #include <atalk/afp.h>
40 #include <atalk/uam.h>
41 #include <atalk/util.h>
43 /* Kerberos includes */
49 #if HAVE_GSSAPI_GSSAPI_H
50 #include <gssapi/gssapi.h>
53 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
54 #include <gssapi/gssapi_generic.h>
57 #if HAVE_GSSAPI_GSSAPI_KRB5_H
58 #include <gssapi/gssapi_krb5.h>
65 /* We work around something I don't entirely understand... */
66 /* BF: This is a Heimdal/MIT compatibility fix */
67 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
68 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
71 #define MIN(a, b) ((a > b) ? b : a)
73 static void log_status( char *s, OM_uint32 major_status,
74 OM_uint32 minor_status )
76 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
77 OM_uint32 min_status, maj_status;
78 OM_uint32 maj_ctx = 0, min_ctx = 0;
81 maj_status = gss_display_status( &min_status, major_status,
82 GSS_C_GSS_CODE, GSS_C_NULL_OID,
84 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
85 (int)msg.length, msg.value, strerror(errno));
86 gss_release_buffer(&min_status, &msg);
92 maj_status = gss_display_status( &min_status, minor_status,
93 GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
95 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
96 (int)msg.length, msg.value, strerror(errno));
97 gss_release_buffer(&min_status, &msg);
105 static void log_ctx_flags( OM_uint32 flags )
108 if (flags & GSS_C_DELEG_FLAG)
109 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
110 if (flags & GSS_C_MUTUAL_FLAG)
111 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
112 if (flags & GSS_C_REPLAY_FLAG)
113 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
114 if (flags & GSS_C_SEQUENCE_FLAG)
115 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
116 if (flags & GSS_C_CONF_FLAG)
117 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
118 if (flags & GSS_C_INTEG_FLAG)
119 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
123 static void log_principal(gss_name_t server_name)
126 OM_uint32 major_status = 0, minor_status = 0;
127 gss_buffer_desc exported_name;
129 /* Only for debugging purposes, check the gssapi internal representation */
130 major_status = gss_export_name(&minor_status, server_name, &exported_name);
131 LOG(log_debug, logtype_uams, "log_principal: exported server name is %s", (char*) exported_name.value);
132 gss_release_buffer( &minor_status, &exported_name );
136 /* get the principal from afpd and import it into server_name */
137 static int get_afpd_principal(void *obj, gss_name_t *server_name)
139 OM_uint32 major_status = 0, minor_status = 0;
140 char *realm, *fqdn, *service, *principal, *p;
141 int realmlen=0, fqdnlen=0, servicelen=0;
142 size_t principal_length;
143 gss_buffer_desc s_princ_buffer;
145 /* get all the required information from afpd */
146 if (uam_afpserver_option(obj, UAM_OPTION_KRB5REALM, (void*) &realm, &realmlen) < 0)
148 if (uam_afpserver_option(obj, UAM_OPTION_FQDN, (void*) &fqdn, &fqdnlen) < 0)
150 if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
153 /* we need all the info, log error and return if one's missing */
154 if (!service || !servicelen || !fqdn || !fqdnlen || !realm || !realmlen) {
155 LOG(log_error, logtype_uams,
156 "get_afpd_principal: could not retrieve required information from afpd.");
160 /* allocate memory to hold the temporary principal string */
161 principal_length = servicelen + 1 + fqdnlen + 1 + realmlen + 1;
162 if ( NULL == (principal = (char*) malloc( principal_length)) ) {
163 LOG(log_error, logtype_uams,
164 "get_afpd_principal: out of memory allocating %u bytes",
170 * Build the principal string.
171 * Format: 'service/fqdn@realm'
173 strlcpy( principal, service, principal_length);
174 strlcat( principal, "/", principal_length);
177 * The fqdn we get from afpd may contain a port.
178 * We need to strip the port from fqdn for principal.
180 p = strchr(fqdn, ':');
183 strlcat( principal, fqdn, principal_length);
186 strlcat( principal, "@", principal_length);
187 strlcat( principal, realm, principal_length);
190 * Import our principal into the gssapi internal representation
191 * stored in server_name.
193 s_princ_buffer.value = principal;
194 s_princ_buffer.length = strlen( principal ) + 1;
196 LOG(log_debug, logtype_uams, "get_afpd_principal: importing principal `%s'", principal);
197 major_status = gss_import_name( &minor_status,
203 * Get rid of malloc'ed memmory.
204 * Don't release the s_princ_buffer, we free principal instead.
208 if (major_status != GSS_S_COMPLETE) {
209 /* Importing our service principal failed, bail out. */
210 log_status( "import_principal", major_status, minor_status );
217 /* get the username */
218 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
220 OM_uint32 major_status = 0, minor_status = 0;
221 gss_buffer_desc client_name_buffer;
226 * To extract the unix username, use gss_display_name on client_name.
227 * We do rely on gss_display_name returning a zero terminated string.
228 * The username returned contains the realm and possibly an instance.
229 * We only want the username for afpd, so we have to strip those from
230 * the username before copying it to afpd's buffer.
233 major_status = gss_display_name( &minor_status, *client_name,
234 &client_name_buffer, (gss_OID *)NULL );
235 if (major_status != GSS_S_COMPLETE) {
236 log_status( "display_name", major_status, minor_status );
240 LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
243 p = strchr( client_name_buffer.value, '@' );
246 /* FIXME: chop off instance? */
247 p = strchr( client_name_buffer.value, '/' );
251 /* check if this username fits into afpd's username buffer */
252 namelen = strlen(client_name_buffer.value);
253 if ( namelen >= ulen ) {
254 /* The username is too long for afpd's buffer, bail out */
255 LOG(log_error, logtype_uams,
256 "get_client_username: username `%s' too long", client_name_buffer.value);
260 /* copy stripped username to afpd's buffer */
261 strlcpy(username, client_name_buffer.value, ulen);
264 /* we're done with client_name_buffer, release it */
265 gss_release_buffer(&minor_status, &client_name_buffer );
270 /* wrap afpd's sessionkey */
271 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
273 OM_uint32 status = 0;
275 gss_buffer_desc sesskey_buff, wrap_buff;
278 * gss_wrap afpd's session_key.
279 * This is needed fo OS X 10.3 clients. They request this information
280 * with type 8 (kGetKerberosSessionKey) on FPGetSession.
281 * See AFP 3.1 specs, page 77.
284 sesskey_buff.value = sinfo->sessionkey;
285 sesskey_buff.length = sinfo->sessionkey_len;
287 /* gss_wrap the session key with the default mechanism.
288 Require both confidentiality and integrity services */
289 gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
291 if ( status != GSS_S_COMPLETE) {
292 LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey");
293 log_status( "GSS wrap", 0, status );
297 /* store the wrapped session key in afpd's session_info struct */
298 if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) {
299 LOG(log_error, logtype_uams,
300 "wrap_sessionkey: out of memory tyring to allocate %u bytes",
304 /* cryptedkey is binary data */
305 memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
306 sinfo->cryptedkey_len = wrap_buff.length;
309 /* we're done with buffer, release */
310 gss_release_buffer( &status, &wrap_buff );
316 static int acquire_credentials (gss_name_t *server_name, gss_cred_id_t *server_creds)
318 OM_uint32 major_status = 0, minor_status = 0;
320 LOG(log_debug, logtype_uams,
321 "acquire credentials: acquiring credentials (uid = %d, keytab = %s)",
322 (int)geteuid(), getenv( "KRB5_KTNAME") );
324 * Acquire credentials usable for accepting context negotiations.
325 * Credentials are for server_name, have an indefinite lifetime,
326 * have no specific mechanisms, are to be used for accepting context
327 * negotiations and are to be placed in server_creds.
328 * We don't care about the mechanisms or about the time for which they are valid.
330 major_status = gss_acquire_cred( &minor_status, *server_name,
331 GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
332 server_creds, NULL, NULL );
334 if (major_status != GSS_S_COMPLETE) {
335 log_status( "acquire_cred", major_status, minor_status );
343 static int accept_sec_context (gss_ctx_id_t *context, gss_cred_id_t server_creds,
344 gss_buffer_desc *ticket_buffer, gss_name_t *client_name,
345 gss_buffer_desc *authenticator_buff)
347 OM_uint32 major_status = 0, minor_status = 0, ret_flags;
349 /* Initialize autheticator buffer. */
350 authenticator_buff->length = 0;
351 authenticator_buff->value = NULL;
353 LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
354 ticket_buffer->length);
357 * Try to accept the secondary context using the tocken in ticket_buffer.
358 * We don't care about the mechanisms used, nor for the time.
359 * We don't act as a proxy either.
361 major_status = gss_accept_sec_context( &minor_status, context,
362 server_creds, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
363 client_name, NULL, authenticator_buff,
364 &ret_flags, NULL, NULL );
366 if (major_status != GSS_S_COMPLETE) {
367 log_status( "accept_sec_context", major_status, minor_status );
370 log_ctx_flags( ret_flags );
375 /* return 0 on success */
376 static int do_gss_auth(void *obj, char *ibuf, int ticket_len,
377 char *rbuf, int *rbuflen, char *username, int ulen,
378 struct session_info *sinfo )
380 OM_uint32 status = 0;
381 gss_name_t server_name, client_name;
382 gss_cred_id_t server_creds;
383 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
384 gss_buffer_desc ticket_buffer, authenticator_buff;
387 /* import our principal name from afpd */
388 if (get_afpd_principal(obj, &server_name) != 0) {
391 log_principal(server_name);
393 /* Now we have to acquire our credentials */
394 if ((ret = acquire_credentials (&server_name, &server_creds)))
398 * Try to accept the secondary context, using the ticket/token the
399 * client sent us. Ticket is stored at current ibuf position.
400 * Don't try to release ticket_buffer later, it points into ibuf!
402 ticket_buffer.length = ticket_len;
403 ticket_buffer.value = ibuf;
405 ret = accept_sec_context (&context_handle, server_creds, &ticket_buffer,
406 &client_name, &authenticator_buff);
409 /* We succesfully acquired the secondary context, now get the
410 username for afpd and gss_wrap the sessionkey */
411 if ( 0 == (ret = get_client_username(username, ulen, &client_name)) ) {
412 ret = wrap_sessionkey(context_handle, sinfo);
416 /* FIXME: Is copying the authenticator really necessary?
417 Where is this documented? */
418 u_int16_t auth_len = htons( authenticator_buff.length );
420 /* copy the authenticator length into the reply buffer */
421 memcpy( rbuf, &auth_len, sizeof(auth_len) );
422 *rbuflen += sizeof(auth_len);
423 rbuf += sizeof(auth_len);
425 /* copy the authenticator value into the reply buffer */
426 memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
427 *rbuflen += authenticator_buff.length;
430 /* Clean up after ourselves */
431 gss_release_name( &status, &client_name );
432 if ( authenticator_buff.value)
433 gss_release_buffer( &status, &authenticator_buff );
435 gss_delete_sec_context( &status, &context_handle, NULL );
437 gss_release_cred( &status, &server_creds );
440 gss_release_name( &status, &server_name );
445 /* -------------------------- */
446 static int gss_login(void *obj, struct passwd **uam_pwd,
447 char *ibuf, int ibuflen,
448 char *rbuf, int *rbuflen)
455 /* The reply contains a two-byte ID value - note
456 * that Apple's implementation seems to always return 1 as well
459 memcpy(rbuf, &temp16, sizeof(temp16));
460 *rbuflen += sizeof(temp16);
461 return AFPERR_AUTHCONT;
464 static int gss_logincont(void *obj, struct passwd **uam_pwd,
465 char *ibuf, int ibuflen,
466 char *rbuf, int *rbuflen)
468 struct passwd *pwd = NULL;
471 u_int16_t ticket_len;
475 struct session_info *sinfo;
477 /* Apple's AFP 3.1 documentation specifies that this command
478 * takes the following format:
480 * id returned in LoginExt response (u_int16_t)
481 * username (format unspecified) padded, when necessary, to end on an even boundary
482 * ticket length (u_int16_t)
486 /* Observation of AFP clients in the wild indicate that the actual
487 * format of this request is as follows:
488 * pad (byte) [consumed before login_ext is called]
489 * ?? (byte) - always observed to be 0
490 * id returned in LoginExt response (u_int16_t)
491 * username, encoding unspecified, null terminated C string,
492 * padded when the terminating null is an even numbered byte.
493 * The packet is formated such that the username begins on an
494 * odd numbered byte. Eg if the username is 3 characters and the
495 * terminating null makes 4, expect to pad the the result.
496 * The encoding of this string is unknown.
497 * ticket length (u_int16_t)
501 rblen = *rbuflen = 0;
504 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
507 ibuf++, ibuflen--; /* ?? */
509 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
510 memcpy( &login_id, ibuf, sizeof(login_id) );
511 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
512 login_id = ntohs( login_id );
514 /* get the username buffer from apfd */
515 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
518 /* get the session_info structure from afpd. We need the session key */
519 if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
522 if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
523 /* Should never happen. Most likely way too old afpd version */
524 LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set");
528 /* We skip past the 'username' parameter because all that matters is the ticket */
530 while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
532 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
536 ibuf++, ibuflen--; /* null termination */
538 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
540 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
542 /* get the length of the ticket the client sends us */
543 memcpy(&ticket_len, ibuf, sizeof(ticket_len));
544 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
545 ticket_len = ntohs( ticket_len );
547 /* a little bounds checking */
548 if (ticket_len > ibuflen) {
549 LOG(log_info, logtype_uams,
550 "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
554 /* now try to authenticate */
555 if (!do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
556 /* We use the username we got back from the gssapi client_name.
557 Should we compare this to the username the client sent in the clear?
558 We know the character encoding of the cleartext username (UTF8), what
559 encoding is the gssapi name in? */
560 if((pwd = uam_getname( obj, username, userlen )) == NULL) {
561 LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
564 if (uam_checkuser(pwd) < 0) {
565 LOG(log_info, logtype_uams, "%s not a valid user", username);
566 return AFPERR_NOTAUTH;
572 LOG(log_info, logtype_uams, "do_gss_auth failed" );
579 * For the krb5 uam, this function only needs to return a two-byte
580 * login-session id. None of the data provided by the client up to this
581 * point is trustworthy as we'll have a signed ticket to parse in logincont.
583 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
584 char *ibuf, int ibuflen,
585 char *rbuf, int *rbuflen)
591 /* The reply contains a two-byte ID value - note
592 * that Apple's implementation seems to always return 1 as well
595 memcpy(rbuf, &temp16, sizeof(temp16));
596 *rbuflen += sizeof(temp16);
597 return AFPERR_AUTHCONT;
601 static void gss_logout() {
604 int uam_setup(const char *path)
606 if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
607 gss_login, gss_logincont, gss_logout, gss_login_ext) < 0)
608 if (uam_register(UAM_SERVER_LOGIN, path, "Client Krb v2",
609 gss_login, gss_logincont, gss_logout) < 0)
615 static void uam_cleanup(void)
617 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
620 UAM_MODULE_EXPORT struct uam_export uams_gss = {
623 uam_setup, uam_cleanup