2 * $Id: uams_gss.c,v 1.4 2009-09-14 00:02:21 didg 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
70 #define MIN(a, b) ((a > b) ? b : a)
72 static void log_status( char *s, OM_uint32 major_status,
73 OM_uint32 minor_status )
75 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
76 OM_uint32 min_status, maj_status;
77 OM_uint32 maj_ctx = 0, min_ctx = 0;
80 maj_status = gss_display_status( &min_status, major_status,
81 GSS_C_GSS_CODE, GSS_C_NULL_OID,
83 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
84 (int)msg.length, msg.value, strerror(errno));
85 gss_release_buffer(&min_status, &msg);
91 maj_status = gss_display_status( &min_status, minor_status,
92 GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
94 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
95 (int)msg.length, msg.value, strerror(errno));
96 gss_release_buffer(&min_status, &msg);
104 static void log_ctx_flags( OM_uint32 flags )
107 if (flags & GSS_C_DELEG_FLAG)
108 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
109 if (flags & GSS_C_MUTUAL_FLAG)
110 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
111 if (flags & GSS_C_REPLAY_FLAG)
112 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
113 if (flags & GSS_C_SEQUENCE_FLAG)
114 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
115 if (flags & GSS_C_CONF_FLAG)
116 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
117 if (flags & GSS_C_INTEG_FLAG)
118 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
122 static void log_principal(gss_name_t server_name)
125 OM_uint32 major_status = 0, minor_status = 0;
126 gss_buffer_desc exported_name;
128 /* Only for debugging purposes, check the gssapi internal representation */
129 major_status = gss_export_name(&minor_status, server_name, &exported_name);
130 LOG(log_debug, logtype_uams, "log_principal: exported server name is %s", (char*) exported_name.value);
131 gss_release_buffer( &minor_status, &exported_name );
135 /* get the principal from afpd and import it into server_name */
136 static int get_afpd_principal(void *obj, gss_name_t *server_name)
138 OM_uint32 major_status = 0, minor_status = 0;
139 char *realm, *fqdn, *service, *principal, *p;
140 int realmlen=0, fqdnlen=0, servicelen=0;
141 size_t principal_length;
142 gss_buffer_desc s_princ_buffer;
144 /* get all the required information from afpd */
145 if (uam_afpserver_option(obj, UAM_OPTION_KRB5REALM, (void*) &realm, &realmlen) < 0)
147 if (uam_afpserver_option(obj, UAM_OPTION_FQDN, (void*) &fqdn, &fqdnlen) < 0)
149 if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
152 /* we need all the info, log error and return if one's missing */
153 if (!service || !servicelen || !fqdn || !fqdnlen || !realm || !realmlen) {
154 LOG(log_error, logtype_uams,
155 "get_afpd_principal: could not retrieve required information from afpd.");
159 /* allocate memory to hold the temporary principal string */
160 principal_length = servicelen + 1 + fqdnlen + 1 + realmlen + 1;
161 if ( NULL == (principal = (char*) malloc( principal_length)) ) {
162 LOG(log_error, logtype_uams,
163 "get_afpd_principal: out of memory allocating %u bytes",
169 * Build the principal string.
170 * Format: 'service/fqdn@realm'
172 strlcpy( principal, service, principal_length);
173 strlcat( principal, "/", principal_length);
176 * The fqdn we get from afpd may contain a port.
177 * We need to strip the port from fqdn for principal.
179 p = strchr(fqdn, ':');
182 strlcat( principal, fqdn, principal_length);
185 strlcat( principal, "@", principal_length);
186 strlcat( principal, realm, principal_length);
189 * Import our principal into the gssapi internal representation
190 * stored in server_name.
192 s_princ_buffer.value = principal;
193 s_princ_buffer.length = strlen( principal ) + 1;
195 LOG(log_debug, logtype_uams, "get_afpd_principal: importing principal `%s'", principal);
196 major_status = gss_import_name( &minor_status,
202 * Get rid of malloc'ed memmory.
203 * Don't release the s_princ_buffer, we free principal instead.
207 if (major_status != GSS_S_COMPLETE) {
208 /* Importing our service principal failed, bail out. */
209 log_status( "import_principal", major_status, minor_status );
216 /* get the username */
217 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
219 OM_uint32 major_status = 0, minor_status = 0;
220 gss_buffer_desc client_name_buffer;
225 * To extract the unix username, use gss_display_name on client_name.
226 * We do rely on gss_display_name returning a zero terminated string.
227 * The username returned contains the realm and possibly an instance.
228 * We only want the username for afpd, so we have to strip those from
229 * the username before copying it to afpd's buffer.
232 major_status = gss_display_name( &minor_status, *client_name,
233 &client_name_buffer, (gss_OID *)NULL );
234 if (major_status != GSS_S_COMPLETE) {
235 log_status( "display_name", major_status, minor_status );
239 LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
242 p = strchr( client_name_buffer.value, '@' );
245 /* FIXME: chop off instance? */
246 p = strchr( client_name_buffer.value, '/' );
250 /* check if this username fits into afpd's username buffer */
251 namelen = strlen(client_name_buffer.value);
252 if ( namelen >= ulen ) {
253 /* The username is too long for afpd's buffer, bail out */
254 LOG(log_error, logtype_uams,
255 "get_client_username: username `%s' too long", client_name_buffer.value);
259 /* copy stripped username to afpd's buffer */
260 strlcpy(username, client_name_buffer.value, ulen);
263 /* we're done with client_name_buffer, release it */
264 gss_release_buffer(&minor_status, &client_name_buffer );
269 /* wrap afpd's sessionkey */
270 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
272 OM_uint32 status = 0;
274 gss_buffer_desc sesskey_buff, wrap_buff;
277 * gss_wrap afpd's session_key.
278 * This is needed fo OS X 10.3 clients. They request this information
279 * with type 8 (kGetKerberosSessionKey) on FPGetSession.
280 * See AFP 3.1 specs, page 77.
283 sesskey_buff.value = sinfo->sessionkey;
284 sesskey_buff.length = sinfo->sessionkey_len;
286 /* gss_wrap the session key with the default machanism.
287 Require both confidentiality and integrity services */
288 gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
290 if ( status != GSS_S_COMPLETE) {
291 LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey");
292 log_status( "GSS wrap", 0, status );
296 /* store the wrapped session key in afpd's session_info struct */
297 if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) {
298 LOG(log_error, logtype_uams,
299 "wrap_sessionkey: out of memory tyring to allocate %u bytes",
303 /* cryptedkey is binary data */
304 memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
305 sinfo->cryptedkey_len = wrap_buff.length;
308 /* we're done with buffer, release */
309 gss_release_buffer( &status, &wrap_buff );
315 static int acquire_credentials (gss_name_t *server_name, gss_cred_id_t *server_creds)
317 OM_uint32 major_status = 0, minor_status = 0;
319 LOG(log_debug, logtype_uams,
320 "acquire credentials: acquiring credentials (uid = %d, keytab = %s)",
321 (int)geteuid(), getenv( "KRB5_KTNAME") );
323 * Acquire credentials usable for accepting context negotiations.
324 * Credentials are for server_name, have an indefinite lifetime,
325 * have no specific mechanisms, are to be used for accepting context
326 * negotiations and are to be placed in server_creds.
327 * We don't care about the mechanisms or about the time for which they are valid.
329 major_status = gss_acquire_cred( &minor_status, *server_name,
330 GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
331 server_creds, NULL, NULL );
333 if (major_status != GSS_S_COMPLETE) {
334 log_status( "acquire_cred", major_status, minor_status );
342 static int accept_sec_context (gss_ctx_id_t *context, gss_cred_id_t server_creds,
343 gss_buffer_desc *ticket_buffer, gss_name_t *client_name,
344 gss_buffer_desc *authenticator_buff)
346 OM_uint32 major_status = 0, minor_status = 0, ret_flags;
348 /* Initialize autheticator buffer. */
349 authenticator_buff->length = 0;
350 authenticator_buff->value = NULL;
352 LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
353 ticket_buffer->length);
356 * Try to accept the secondary context using the tocken in ticket_buffer.
357 * We don't care about the mechanisms used, nor for the time.
358 * We don't act as a proxy either.
360 major_status = gss_accept_sec_context( &minor_status, context,
361 server_creds, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
362 client_name, NULL, authenticator_buff,
363 &ret_flags, NULL, NULL );
365 if (major_status != GSS_S_COMPLETE) {
366 log_status( "accept_sec_context", major_status, minor_status );
369 log_ctx_flags( ret_flags );
374 /* return 0 on success */
375 static int do_gss_auth(void *obj, char *ibuf, int ticket_len,
376 char *rbuf, int *rbuflen, char *username, int ulen,
377 struct session_info *sinfo )
379 OM_uint32 status = 0;
380 gss_name_t server_name, client_name;
381 gss_cred_id_t server_creds;
382 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
383 gss_buffer_desc ticket_buffer, authenticator_buff;
386 /* import our principal name from afpd */
387 if (get_afpd_principal(obj, &server_name) != 0) {
390 log_principal(server_name);
392 /* Now we have to acquire our credentials */
393 if ((ret = acquire_credentials (&server_name, &server_creds)))
397 * Try to accept the secondary context, using the ticket/token the
398 * client sent us. Ticket is stored at current ibuf position.
399 * Don't try to release ticket_buffer later, it points into ibuf!
401 ticket_buffer.length = ticket_len;
402 ticket_buffer.value = ibuf;
404 ret = accept_sec_context (&context_handle, server_creds, &ticket_buffer,
405 &client_name, &authenticator_buff);
408 /* We succesfully acquired the secondary context, now get the
409 username for afpd and gss_wrap the sessionkey */
410 if ( 0 == (ret = get_client_username(username, ulen, &client_name)) ) {
411 ret = wrap_sessionkey(context_handle, sinfo);
415 /* FIXME: Is copying the authenticator really necessary?
416 Where is this documented? */
417 u_int16_t auth_len = htons( authenticator_buff.length );
419 /* copy the authenticator length into the reply buffer */
420 memcpy( rbuf, &auth_len, sizeof(auth_len) );
421 *rbuflen += sizeof(auth_len);
422 rbuf += sizeof(auth_len);
424 /* copy the authenticator value into the reply buffer */
425 memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
426 *rbuflen += authenticator_buff.length;
429 /* Clean up after ourselves */
430 gss_release_name( &status, &client_name );
431 if ( authenticator_buff.value)
432 gss_release_buffer( &status, &authenticator_buff );
434 gss_delete_sec_context( &status, &context_handle, NULL );
436 gss_release_cred( &status, &server_creds );
439 gss_release_name( &status, &server_name );
444 /* -------------------------- */
445 static int gss_login(void *obj, struct passwd **uam_pwd,
446 char *ibuf, int ibuflen,
447 char *rbuf, int *rbuflen)
454 /* The reply contains a two-byte ID value - note
455 * that Apple's implementation seems to always return 1 as well
458 memcpy(rbuf, &temp16, sizeof(temp16));
459 *rbuflen += sizeof(temp16);
460 return AFPERR_AUTHCONT;
463 static int gss_logincont(void *obj, struct passwd **uam_pwd,
464 char *ibuf, int ibuflen,
465 char *rbuf, int *rbuflen)
467 struct passwd *pwd = NULL;
470 u_int16_t ticket_len;
474 struct session_info *sinfo;
476 /* Apple's AFP 3.1 documentation specifies that this command
477 * takes the following format:
479 * id returned in LoginExt response (u_int16_t)
480 * username (format unspecified) padded, when necessary, to end on an even boundary
481 * ticket length (u_int16_t)
485 /* Observation of AFP clients in the wild indicate that the actual
486 * format of this request is as follows:
487 * pad (byte) [consumed before login_ext is called]
488 * ?? (byte) - always observed to be 0
489 * id returned in LoginExt response (u_int16_t)
490 * username, encoding unspecified, null terminated C string,
491 * padded when the terminating null is an even numbered byte.
492 * The packet is formated such that the username begins on an
493 * odd numbered byte. Eg if the username is 3 characters and the
494 * terminating null makes 4, expect to pad the the result.
495 * The encoding of this string is unknown.
496 * ticket length (u_int16_t)
500 rblen = *rbuflen = 0;
503 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
506 ibuf++, ibuflen--; /* ?? */
508 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
509 memcpy( &login_id, ibuf, sizeof(login_id) );
510 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
511 login_id = ntohs( login_id );
513 /* get the username buffer from apfd */
514 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
517 /* get the session_info structure from afpd. We need the session key */
518 if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
521 if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
522 /* Should never happen. Most likely way too old afpd version */
523 LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set");
527 /* We skip past the 'username' parameter because all that matters is the ticket */
529 while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
531 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
535 ibuf++, ibuflen--; /* null termination */
537 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
539 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
541 /* get the length of the ticket the client sends us */
542 memcpy(&ticket_len, ibuf, sizeof(ticket_len));
543 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
544 ticket_len = ntohs( ticket_len );
546 /* a little bounds checking */
547 if (ticket_len > ibuflen) {
548 LOG(log_info, logtype_uams,
549 "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
553 /* now try to authenticate */
554 if (!do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
555 /* We use the username we got back from the gssapi client_name.
556 Should we compare this to the username the client sent in the clear?
557 We know the character encoding of the cleartext username (UTF8), what
558 encoding is the gssapi name in? */
559 if((pwd = uam_getname( obj, username, userlen )) == NULL) {
560 LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
563 if (uam_checkuser(pwd) < 0) {
564 LOG(log_info, logtype_uams, "%s not a valid user", username);
565 return AFPERR_NOTAUTH;
571 LOG(log_info, logtype_uams, "do_gss_auth failed" );
578 * For the krb5 uam, this function only needs to return a two-byte
579 * login-session id. None of the data provided by the client up to this
580 * point is trustworthy as we'll have a signed ticket to parse in logincont.
582 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
583 char *ibuf, int ibuflen,
584 char *rbuf, int *rbuflen)
590 /* The reply contains a two-byte ID value - note
591 * that Apple's implementation seems to always return 1 as well
594 memcpy(rbuf, &temp16, sizeof(temp16));
595 *rbuflen += sizeof(temp16);
596 return AFPERR_AUTHCONT;
600 static void gss_logout() {
603 int uam_setup(const char *path)
605 if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
606 gss_login, gss_logincont, gss_logout, gss_login_ext) < 0)
607 if (uam_register(UAM_SERVER_LOGIN, path, "Client Krb v2",
608 gss_login, gss_logincont, gss_logout) < 0)
614 static void uam_cleanup(void)
616 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
619 UAM_MODULE_EXPORT struct uam_export uams_gss = {
622 uam_setup, uam_cleanup