2 * $Id: uams_gss.c,v 1.12 2010-03-30 10:25:49 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 /* FIXME: must call gss_canonicalize_name before gss_export_name */
130 OM_uint32 major_status = 0, minor_status = 0;
131 gss_buffer_desc exported_name;
133 /* Only for debugging purposes, check the gssapi internal representation */
134 major_status = gss_export_name(&minor_status, server_name, &exported_name);
135 LOG(log_debug, logtype_uams, "log_principal: exported server name is %s", (char*) exported_name.value);
136 gss_release_buffer( &minor_status, &exported_name );
140 /* get the principal from afpd and import it into server_name */
141 static int get_afpd_principal(void *obj, gss_name_t *server_name)
143 OM_uint32 major_status = 0, minor_status = 0;
144 char *fqdn, *service, *principal, *p;
145 size_t fqdnlen=0, servicelen=0;
146 size_t principal_length;
147 gss_buffer_desc s_princ_buffer;
149 /* get information from afpd */
150 if (uam_afpserver_option(obj, UAM_OPTION_FQDN, (void*) &fqdn, &fqdnlen) < 0)
152 LOG(log_debug, logtype_uams, "get_afpd_principal: fqdn: %s", fqdn);
154 if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
156 LOG(log_debug, logtype_uams, "get_afpd_principal: service: %s", service);
158 /* if we don't have all the info, log that and return GSS_C_NO_NAME */
159 if (!service || !servicelen || !fqdn || !fqdnlen) {
160 LOG(log_note, logtype_uams,
161 "get_afpd_principal: could not retrieve information from afpd, using default service principal(s)");
163 *server_name = GSS_C_NO_NAME;
167 /* allocate memory to hold the temporary principal string */
168 principal_length = servicelen + 1 + fqdnlen + 1;
169 if ( NULL == (principal = (char*) malloc( principal_length)) ) {
170 LOG(log_error, logtype_uams,
171 "get_afpd_principal: out of memory allocating %u bytes",
177 * Build the principal string.
178 * Format: 'service@fqdn'
180 strlcpy( principal, service, principal_length);
181 strlcat( principal, "@", principal_length);
184 * The fqdn we get from afpd may contain a port.
185 * We need to strip the port from fqdn for principal.
187 if ((p = strchr(fqdn, ':')))
190 strlcat( principal, fqdn, principal_length);
194 * Import our principal into the gssapi internal representation
195 * stored in server_name.
197 s_princ_buffer.value = principal;
198 s_princ_buffer.length = strlen( principal ) + 1;
200 LOG(log_debug, logtype_uams, "get_afpd_principal: importing principal `%s'", principal);
201 major_status = gss_import_name( &minor_status,
203 GSS_C_NT_HOSTBASED_SERVICE,
207 * Get rid of malloc'ed memmory.
208 * Don't release the s_princ_buffer, we free principal instead.
212 if (major_status != GSS_S_COMPLETE) {
213 /* Importing our service principal failed, bail out. */
214 log_status( "import_principal", major_status, minor_status );
221 /* get the username */
222 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
224 OM_uint32 major_status = 0, minor_status = 0;
225 gss_buffer_desc client_name_buffer;
230 * To extract the unix username, use gss_display_name on client_name.
231 * We do rely on gss_display_name returning a zero terminated string.
232 * The username returned contains the realm and possibly an instance.
233 * We only want the username for afpd, so we have to strip those from
234 * the username before copying it to afpd's buffer.
237 major_status = gss_display_name( &minor_status, *client_name,
238 &client_name_buffer, (gss_OID *)NULL );
239 if (major_status != GSS_S_COMPLETE) {
240 log_status( "display_name", major_status, minor_status );
244 LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
247 p = strchr( client_name_buffer.value, '@' );
250 /* FIXME: chop off instance? */
251 p = strchr( client_name_buffer.value, '/' );
255 /* check if this username fits into afpd's username buffer */
256 namelen = strlen(client_name_buffer.value);
257 if ( namelen >= ulen ) {
258 /* The username is too long for afpd's buffer, bail out */
259 LOG(log_error, logtype_uams,
260 "get_client_username: username `%s' too long", client_name_buffer.value);
264 /* copy stripped username to afpd's buffer */
265 strlcpy(username, client_name_buffer.value, ulen);
268 /* we're done with client_name_buffer, release it */
269 gss_release_buffer(&minor_status, &client_name_buffer );
274 /* wrap afpd's sessionkey */
275 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
277 OM_uint32 status = 0;
279 gss_buffer_desc sesskey_buff, wrap_buff;
282 * gss_wrap afpd's session_key.
283 * This is needed fo OS X 10.3 clients. They request this information
284 * with type 8 (kGetKerberosSessionKey) on FPGetSession.
285 * See AFP 3.1 specs, page 77.
288 sesskey_buff.value = sinfo->sessionkey;
289 sesskey_buff.length = sinfo->sessionkey_len;
291 /* gss_wrap the session key with the default machanism.
292 Require both confidentiality and integrity services */
293 gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
295 if ( status != GSS_S_COMPLETE) {
296 LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey");
297 log_status( "GSS wrap", 0, status );
301 /* store the wrapped session key in afpd's session_info struct */
302 if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) {
303 LOG(log_error, logtype_uams,
304 "wrap_sessionkey: out of memory tyring to allocate %u bytes",
308 /* cryptedkey is binary data */
309 memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
310 sinfo->cryptedkey_len = wrap_buff.length;
313 /* we're done with buffer, release */
314 gss_release_buffer( &status, &wrap_buff );
320 static int acquire_credentials (gss_name_t *server_name, gss_cred_id_t *server_creds)
322 OM_uint32 major_status = 0, minor_status = 0;
325 if ((envp = getenv("KRB5_KTNAME")))
326 LOG(log_debug, logtype_uams,
327 "acquire credentials: acquiring credentials (uid = %d, keytab = %s)",
328 (int)geteuid(), envp);
330 LOG(log_debug, logtype_uams,
331 "acquire credentials: acquiring credentials (uid = %d) - $KRB5_KTNAME not found in env",
335 * Acquire credentials usable for accepting context negotiations.
336 * Credentials are for server_name, have an indefinite lifetime,
337 * have no specific mechanisms, are to be used for accepting context
338 * negotiations and are to be placed in server_creds.
339 * We don't care about the mechanisms or about the time for which they are valid.
341 major_status = gss_acquire_cred( &minor_status, *server_name,
342 GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
343 server_creds, NULL, NULL );
345 if (major_status != GSS_S_COMPLETE) {
346 log_status( "acquire_cred", major_status, minor_status );
354 static int accept_sec_context (gss_ctx_id_t *context, gss_cred_id_t server_creds,
355 gss_buffer_desc *ticket_buffer, gss_name_t *client_name,
356 gss_buffer_desc *authenticator_buff)
358 OM_uint32 major_status = 0, minor_status = 0, ret_flags;
360 /* Initialize autheticator buffer. */
361 authenticator_buff->length = 0;
362 authenticator_buff->value = NULL;
364 LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
365 ticket_buffer->length);
368 * Try to accept the secondary context using the tocken in ticket_buffer.
369 * We don't care about the mechanisms used, nor for the time.
370 * We don't act as a proxy either.
372 major_status = gss_accept_sec_context( &minor_status, context,
373 server_creds, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
374 client_name, NULL, authenticator_buff,
375 &ret_flags, NULL, NULL );
377 if (major_status != GSS_S_COMPLETE) {
378 log_status( "accept_sec_context", major_status, minor_status );
381 log_ctx_flags( ret_flags );
386 /* return 0 on success */
387 static int do_gss_auth(void *obj, char *ibuf, int ticket_len,
388 char *rbuf, int *rbuflen, char *username, int ulen,
389 struct session_info *sinfo )
391 OM_uint32 status = 0;
392 gss_name_t server_name, client_name;
393 gss_cred_id_t server_creds;
394 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
395 gss_buffer_desc ticket_buffer, authenticator_buff;
398 /* import our principal name from afpd */
399 if (get_afpd_principal(obj, &server_name) != 0) {
402 log_principal(server_name);
404 /* Now we have to acquire our credentials */
405 if ((ret = acquire_credentials (&server_name, &server_creds)))
409 * Try to accept the secondary context, using the ticket/token the
410 * client sent us. Ticket is stored at current ibuf position.
411 * Don't try to release ticket_buffer later, it points into ibuf!
413 ticket_buffer.length = ticket_len;
414 ticket_buffer.value = ibuf;
416 ret = accept_sec_context (&context_handle, server_creds, &ticket_buffer,
417 &client_name, &authenticator_buff);
420 /* We succesfully acquired the secondary context, now get the
421 username for afpd and gss_wrap the sessionkey */
422 if ( 0 == (ret = get_client_username(username, ulen, &client_name)) ) {
423 ret = wrap_sessionkey(context_handle, sinfo);
427 /* FIXME: Is copying the authenticator really necessary?
428 Where is this documented? */
429 u_int16_t auth_len = htons( authenticator_buff.length );
431 /* copy the authenticator length into the reply buffer */
432 memcpy( rbuf, &auth_len, sizeof(auth_len) );
433 *rbuflen += sizeof(auth_len);
434 rbuf += sizeof(auth_len);
436 /* copy the authenticator value into the reply buffer */
437 memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
438 *rbuflen += authenticator_buff.length;
441 /* Clean up after ourselves */
442 gss_release_name( &status, &client_name );
443 if ( authenticator_buff.value)
444 gss_release_buffer( &status, &authenticator_buff );
446 gss_delete_sec_context( &status, &context_handle, NULL );
448 gss_release_cred( &status, &server_creds );
451 gss_release_name( &status, &server_name );
456 /* -------------------------- */
457 static int gss_login(void *obj, struct passwd **uam_pwd,
458 char *ibuf, size_t ibuflen,
459 char *rbuf, size_t *rbuflen)
466 /* The reply contains a two-byte ID value - note
467 * that Apple's implementation seems to always return 1 as well
470 memcpy(rbuf, &temp16, sizeof(temp16));
471 *rbuflen += sizeof(temp16);
472 return AFPERR_AUTHCONT;
475 static int gss_logincont(void *obj, struct passwd **uam_pwd,
476 char *ibuf, size_t ibuflen,
477 char *rbuf, size_t *rbuflen)
479 struct passwd *pwd = NULL;
482 u_int16_t ticket_len;
486 struct session_info *sinfo;
488 /* Apple's AFP 3.1 documentation specifies that this command
489 * takes the following format:
491 * id returned in LoginExt response (u_int16_t)
492 * username (format unspecified) padded, when necessary, to end on an even boundary
493 * ticket length (u_int16_t)
497 /* Observation of AFP clients in the wild indicate that the actual
498 * format of this request is as follows:
499 * pad (byte) [consumed before login_ext is called]
500 * ?? (byte) - always observed to be 0
501 * id returned in LoginExt response (u_int16_t)
502 * username, encoding unspecified, null terminated C string,
503 * padded when the terminating null is an even numbered byte.
504 * The packet is formated such that the username begins on an
505 * odd numbered byte. Eg if the username is 3 characters and the
506 * terminating null makes 4, expect to pad the the result.
507 * The encoding of this string is unknown.
508 * ticket length (u_int16_t)
512 rblen = *rbuflen = 0;
514 if (ibuflen < 1 +sizeof(login_id)) {
515 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
518 ibuf++, ibuflen--; /* ?? */
520 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
521 memcpy( &login_id, ibuf, sizeof(login_id) );
522 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
523 login_id = ntohs( login_id );
525 /* get the username buffer from apfd */
526 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
529 /* get the session_info structure from afpd. We need the session key */
530 if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
533 if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
534 /* Should never happen. Most likely way too old afpd version */
535 LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set");
539 /* We skip past the 'username' parameter because all that matters is the ticket */
541 while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
543 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
547 ibuf++, ibuflen--; /* null termination */
549 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
551 LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
553 /* get the length of the ticket the client sends us */
554 memcpy(&ticket_len, ibuf, sizeof(ticket_len));
555 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
556 ticket_len = ntohs( ticket_len );
558 /* a little bounds checking */
559 if (ticket_len > ibuflen) {
560 LOG(log_info, logtype_uams,
561 "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
565 /* now try to authenticate */
566 if (!do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
567 /* We use the username we got back from the gssapi client_name.
568 Should we compare this to the username the client sent in the clear?
569 We know the character encoding of the cleartext username (UTF8), what
570 encoding is the gssapi name in? */
571 if((pwd = uam_getname( obj, username, userlen )) == NULL) {
572 LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
573 return AFPERR_NOTAUTH;
575 if (uam_checkuser(pwd) < 0) {
576 LOG(log_info, logtype_uams, "%s not a valid user", username);
577 return AFPERR_NOTAUTH;
583 LOG(log_info, logtype_uams, "do_gss_auth failed" );
590 * For the krb5 uam, this function only needs to return a two-byte
591 * login-session id. None of the data provided by the client up to this
592 * point is trustworthy as we'll have a signed ticket to parse in logincont.
594 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
595 char *ibuf, size_t ibuflen,
596 char *rbuf, size_t *rbuflen)
602 /* The reply contains a two-byte ID value - note
603 * that Apple's implementation seems to always return 1 as well
606 memcpy(rbuf, &temp16, sizeof(temp16));
607 *rbuflen += sizeof(temp16);
608 return AFPERR_AUTHCONT;
612 static void gss_logout() {
615 int uam_setup(const char *path)
617 if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
618 gss_login, gss_logincont, gss_logout, gss_login_ext) < 0)
619 if (uam_register(UAM_SERVER_LOGIN, path, "Client Krb v2",
620 gss_login, gss_logincont, gss_logout) < 0)
626 static void uam_cleanup(void)
628 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
631 UAM_MODULE_EXPORT struct uam_export uams_gss = {
634 uam_setup, uam_cleanup