From 00f87f749db99b604ec1022e2fe80e43032c4ab0 Mon Sep 17 00:00:00 2001 From: bfernhomberg Date: Tue, 7 Dec 2004 18:41:08 +0000 Subject: [PATCH] -cleanup uams_gss.c -build the principal used by uams_gss.so from afpd's configuration, don't use GSS_C_NT_HOSTBASED_SERVICE --- etc/afpd/uam.c | 12 +- etc/uams/uams_gss.c | 462 ++++++++++++++++++++++++++++++-------------- include/atalk/uam.h | 2 + 3 files changed, 335 insertions(+), 141 deletions(-) diff --git a/etc/afpd/uam.c b/etc/afpd/uam.c index 2d752531..f01fda7a 100644 --- a/etc/afpd/uam.c +++ b/etc/afpd/uam.c @@ -1,5 +1,5 @@ /* - * $Id: uam.c,v 1.24.6.7.2.1 2004-12-07 18:34:15 bfernhomberg Exp $ + * $Id: uam.c,v 1.24.6.7.2.2 2004-12-07 18:41:08 bfernhomberg Exp $ * * Copyright (c) 1999 Adrian Sun (asun@zoology.washington.edu) * All Rights Reserved. See COPYRIGHT. @@ -499,6 +499,16 @@ AFPObj *obj = private; if (len) *len = (*buf)?strlen(*buf):0; break; + case UAM_OPTION_KRB5REALM: + *buf = obj->options.k5realm; + if (len) + *len = (*buf)?strlen(*buf):0; + break; + case UAM_OPTION_FQDN: + *buf = obj->options.fqdn; + if (len) + *len = (*buf)?strlen(*buf):0; + break; case UAM_OPTION_MACCHARSET: *((int *) option) = obj->options.maccharset; *len = sizeof(obj->options.maccharset); diff --git a/etc/uams/uams_gss.c b/etc/uams/uams_gss.c index 05a1cfbe..965975b8 100644 --- a/etc/uams/uams_gss.c +++ b/etc/uams/uams_gss.c @@ -1,9 +1,10 @@ /* - * $Id: uams_gss.c,v 1.2.2.4 2004-06-20 15:51:46 bfernhomberg Exp $ + * $Id: uams_gss.c,v 1.2.2.4.2.1 2004-12-07 18:41:10 bfernhomberg Exp $ * * Copyright (c) 1990,1993 Regents of The University of Michigan. * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu) * Copyright (c) 2003 The Reed Institute + * Copyright (c) 2004 Bjoern Fernhomberg * All Rights Reserved. See COPYRIGHT. */ @@ -37,6 +38,7 @@ char *strchr (), *strrchr (); #include #include #include +#include /* Kerberos includes */ @@ -100,161 +102,342 @@ static void log_status( char *s, OM_uint32 major_status, } -void log_ctx_flags( OM_uint32 flags ) +static void log_ctx_flags( OM_uint32 flags ) { +#ifdef DEBUG1 if (flags & GSS_C_DELEG_FLAG) - LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" ); + LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" ); if (flags & GSS_C_MUTUAL_FLAG) - LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" ); + LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" ); if (flags & GSS_C_REPLAY_FLAG) - LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" ); + LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" ); if (flags & GSS_C_SEQUENCE_FLAG) - LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" ); + LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" ); if (flags & GSS_C_CONF_FLAG) - LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" ); + LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" ); if (flags & GSS_C_INTEG_FLAG) - LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" ); + LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" ); +#endif } -/* return 0 on success */ -static int do_gss_auth( char *service, char *ibuf, int ticket_len, - char *rbuf, int *rbuflen, char *username, int ulen, - struct session_info *sinfo ) +static void log_principal(gss_name_t server_name) { +#ifdef DEBUG1 OM_uint32 major_status = 0, minor_status = 0; - gss_name_t server_name; - gss_cred_id_t server_creds; - gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT; - gss_buffer_desc ticket_buffer, authenticator_buff, sesskey_buff, wrap_buff; - gss_name_t client_name; - OM_uint32 ret_flags; - int ret = 0; - /* FIXME - * princ specifies the principal to be used. - * The user specifies it when configuring afpd anyway, - * we should be able to detect it. - */ + gss_buffer_desc exported_name; + + /* Only for debugging purposes, check the gssapi internal representation */ + major_status = gss_export_name(&minor_status, server_name, &exported_name); + LOG(log_debug, logtype_uams, "log_principal: exported server name is %s", (char*) exported_name.value); + gss_release_buffer( &minor_status, &exported_name ); +#endif +} + +/* get the principal from afpd and import it into server_name */ +static int get_afpd_principal(void *obj, gss_name_t *server_name) +{ + OM_uint32 major_status = 0, minor_status = 0; + char *realm, *fqdn, *service, *principal, *p; + int realmlen=0, fqdnlen=0, servicelen=0; + size_t principal_length; gss_buffer_desc s_princ_buffer; - s_princ_buffer.value = service; - s_princ_buffer.length = strlen( service ) + 1; - - /* outline: - * gss_import_name (need a way to get this from afpd) - * gss_acquire_credential - * gss_accept_sec_context - * ... + /* get all the required information from afpd */ + if (uam_afpserver_option(obj, UAM_OPTION_KRB5REALM, (void*) &realm, &realmlen) < 0) + return 1; + if (uam_afpserver_option(obj, UAM_OPTION_FQDN, (void*) &fqdn, &fqdnlen) < 0) + return 1; + if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0) + return 1; + + /* we need all the info, log error and return if one's missing */ + if (!service || !servicelen || !fqdn || !fqdnlen || !realm || !realmlen) { + LOG(log_error, logtype_uams, + "get_afpd_principal: could not retrieve required information from afpd."); + return 1; + } + + /* allocate memory to hold the temporary principal string */ + principal_length = servicelen + 1 + fqdnlen + 1 + realmlen + 1; + if ( NULL == (principal = (char*) malloc( principal_length)) ) { + LOG(log_error, logtype_uams, + "get_afpd_principal: out of memory allocating %u bytes", + principal_length); + return 1; + } + + /* + * Build the principal string. + * Format: 'service/fqdn@realm' + */ + strlcpy( principal, service, principal_length); + strlcat( principal, "/", principal_length); + + /* + * The fqdn we get from afpd may contain a port. + * We need to strip the port from fqdn for principal. + */ + p = strchr(fqdn, ':'); + if (p) + *p = '\0'; + strlcat( principal, fqdn, principal_length); + if (p) + *p = ':'; + strlcat( principal, "@", principal_length); + strlcat( principal, realm, principal_length); + + /* + * Import our principal into the gssapi internal representation + * stored in server_name. + */ + s_princ_buffer.value = principal; + s_princ_buffer.length = strlen( principal ) + 1; + + LOG(log_debug, logtype_uams, "get_afpd_principal: importing principal `%s'", principal); + major_status = gss_import_name( &minor_status, + &s_princ_buffer, + GSS_C_NO_OID, + server_name ); + + /* + * Get rid of malloc'ed memmory. + * Don't release the s_princ_buffer, we free principal instead. */ - LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: importing name" ); - major_status = gss_import_name( &minor_status, - &s_princ_buffer, - GSS_C_NT_HOSTBASED_SERVICE, - &server_name ); + free(principal); + if (major_status != GSS_S_COMPLETE) { - log_status( "import_name", major_status, minor_status ); - ret = 1; - goto cleanup_vars; + /* Importing our service principal failed, bail out. */ + log_status( "import_principal", major_status, minor_status ); + return 1; } - - LOG(log_debug, logtype_uams, - "uams_gss.c :do_gss_auth: acquiring credentials (uid = %d, keytab = %s)", + return 0; +} + + +/* get the username */ +static int get_client_username(char *username, int ulen, gss_name_t *client_name) +{ + OM_uint32 major_status = 0, minor_status = 0; + gss_buffer_desc client_name_buffer; + char *p; + int namelen, ret=0; + + /* + * To extract the unix username, use gss_display_name on client_name. + * We do rely on gss_display_name returning a zero terminated string. + * The username returned contains the realm and possibly an instance. + * We only want the username for afpd, so we have to strip those from + * the username before copying it to afpd's buffer. + */ + + major_status = gss_display_name( &minor_status, *client_name, + &client_name_buffer, (gss_OID *)NULL ); + if (major_status != GSS_S_COMPLETE) { + log_status( "display_name", major_status, minor_status ); + return 1; + } + + LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value); + + /* chop off realm */ + p = strchr( client_name_buffer.value, '@' ); + if (p) + *p = 0; + /* FIXME: chop off instance? */ + p = strchr( client_name_buffer.value, '/' ); + if (p) + *p = 0; + + /* check if this username fits into afpd's username buffer */ + namelen = strlen(client_name_buffer.value); + if ( namelen >= ulen ) { + /* The username is too long for afpd's buffer, bail out */ + LOG(log_error, logtype_uams, + "get_client_username: username `%s' too long", client_name_buffer.value); + ret = 1; + } + else { + /* copy stripped username to afpd's buffer */ + strlcpy(username, client_name_buffer.value, ulen); + } + + /* we're done with client_name_buffer, release it */ + gss_release_buffer(&minor_status, &client_name_buffer ); + + return ret; +} + +/* wrap afpd's sessionkey */ +static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo) +{ + OM_uint32 status = 0; + int ret=0; + gss_buffer_desc sesskey_buff, wrap_buff; + + /* + * gss_wrap afpd's session_key. + * This is needed fo OS X 10.3 clients. They request this information + * with type 8 (kGetKerberosSessionKey) on FPGetSession. + * See AFP 3.1 specs, page 77. + */ + + sesskey_buff.value = sinfo->sessionkey; + sesskey_buff.length = sinfo->sessionkey_len; + + /* gss_wrap the session key with the default mechanism. + Require both confidentiality and integrity services */ + gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff); + + if ( status != GSS_S_COMPLETE) { + LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey"); + log_status( "GSS wrap", 0, status ); + return 1; + } + + /* store the wrapped session key in afpd's session_info struct */ + if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) { + LOG(log_error, logtype_uams, + "wrap_sessionkey: out of memory tyring to allocate %u bytes", + wrap_buff.length); + ret = 1; + } else { + /* cryptedkey is binary data */ + memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length); + sinfo->cryptedkey_len = wrap_buff.length; + } + + /* we're done with buffer, release */ + gss_release_buffer( &status, &wrap_buff ); + + return ret; +} + +/*-------------*/ +static int acquire_credentials (gss_name_t *server_name, gss_cred_id_t *server_creds) +{ + OM_uint32 major_status = 0, minor_status = 0; + + LOG(log_debug, logtype_uams, + "acquire credentials: acquiring credentials (uid = %d, keytab = %s)", (int)geteuid(), getenv( "KRB5_KTNAME") ); + /* + * Acquire credentials usable for accepting context negotiations. + * Credentials are for server_name, have an indefinite lifetime, + * have no specific mechanisms, are to be used for accepting context + * negotiations and are to be placed in server_creds. + * We don't care about the mechanisms or about the time for which they are valid. + */ + major_status = gss_acquire_cred( &minor_status, *server_name, + GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT, + server_creds, NULL, NULL ); - major_status = gss_acquire_cred( &minor_status, server_name, - GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT, - &server_creds, NULL, NULL ); if (major_status != GSS_S_COMPLETE) { - log_status( "acquire_cred", major_status, minor_status ); - ret = 1; - goto cleanup_vars; + log_status( "acquire_cred", major_status, minor_status ); + return 1; } + + return 0; +} + +/*-------------*/ +static int accept_sec_context (gss_ctx_id_t *context, gss_cred_id_t server_creds, + gss_buffer_desc *ticket_buffer, gss_name_t *client_name, + gss_buffer_desc *authenticator_buff) +{ + OM_uint32 major_status = 0, minor_status = 0, ret_flags; + + /* Initialize autheticator buffer. */ + authenticator_buff->length = 0; + authenticator_buff->value = NULL; - /* The GSSAPI docs say that this should be done in a big "do" loop, - * but Apple's implementation doesn't seem to support this behavior. + LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)", + ticket_buffer->length); + + /* + * Try to accept the secondary context using the tocken in ticket_buffer. + * We don't care about the mechanisms used, nor for the time. + * We don't act as a proxy either. + */ + major_status = gss_accept_sec_context( &minor_status, context, + server_creds, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS, + client_name, NULL, authenticator_buff, + &ret_flags, NULL, NULL ); + + if (major_status != GSS_S_COMPLETE) { + log_status( "accept_sec_context", major_status, minor_status ); + return 1; + } + log_ctx_flags( ret_flags ); + return 0; +} + + +/* return 0 on success */ +static int do_gss_auth(void *obj, char *ibuf, int ticket_len, + char *rbuf, int *rbuflen, char *username, int ulen, + struct session_info *sinfo ) +{ + OM_uint32 status = 0; + gss_name_t server_name, client_name; + gss_cred_id_t server_creds; + gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT; + gss_buffer_desc ticket_buffer, authenticator_buff; + int ret = 0; + + /* import our principal name from afpd */ + if (get_afpd_principal(obj, &server_name) != 0) { + return 1; + } + log_principal(server_name); + + /* Now we have to acquire our credentials */ + if ((ret = acquire_credentials (&server_name, &server_creds))) + goto cleanup_vars; + + /* + * Try to accept the secondary context, using the ticket/token the + * client sent us. Ticket is stored at current ibuf position. + * Don't try to release ticket_buffer later, it points into ibuf! */ ticket_buffer.length = ticket_len; ticket_buffer.value = ibuf; - authenticator_buff.length = 0; - authenticator_buff.value = NULL; - LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: accepting context (ticketlen: %u)", ticket_buffer.length); - major_status = gss_accept_sec_context( &minor_status, &context_handle, - server_creds, &ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS, - &client_name, NULL, &authenticator_buff, - &ret_flags, NULL, NULL ); - if (major_status == GSS_S_COMPLETE) { - gss_buffer_desc client_name_buffer; - -#ifdef DEBUG1 - log_ctx_flags( ret_flags ); -#endif - /* use gss_display_name on client_name */ - major_status = gss_display_name( &minor_status, client_name, - &client_name_buffer, (gss_OID *)NULL ); - if (major_status == GSS_S_COMPLETE) { - - sesskey_buff.value = sinfo->sessionkey; - sesskey_buff.length = sinfo->sessionkey_len; - - gss_wrap (&minor_status, context_handle, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff); - if ( minor_status == GSS_S_COMPLETE) { - sinfo->cryptedkey = malloc ( wrap_buff.length ); - memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length); - sinfo->cryptedkey_len = wrap_buff.length; - } - else { - log_status( "GSS wrap", major_status, minor_status ); - } - - if (wrap_buff.value) - gss_release_buffer( &minor_status, &wrap_buff ); - - u_int16_t auth_len = htons( authenticator_buff.length ); - /* save the username... note that doing it this way is - * not the best idea: if a principal is truncated, a user could be - * impersonated - */ - memcpy( username, client_name_buffer.value, - MIN(client_name_buffer.length, ulen - 1)); - username[MIN(client_name_buffer.length, ulen - 1)] = 0; - - LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: user is %s!", username ); - /* copy the authenticator length into the reply buffer */ - memcpy( rbuf, &auth_len, sizeof(auth_len) ); - *rbuflen += sizeof(auth_len), rbuf += sizeof(auth_len); - - /* copy the authenticator value into the reply buffer */ - memcpy( rbuf, authenticator_buff.value, authenticator_buff.length ); - *rbuflen += authenticator_buff.length; - - gss_release_buffer( &minor_status, &client_name_buffer ); - } else { - log_status( "display_name", major_status, minor_status ); - ret = 1; - } + ret = accept_sec_context (&context_handle, server_creds, &ticket_buffer, + &client_name, &authenticator_buff); - + if (!ret) { + /* We succesfully acquired the secondary context, now get the + username for afpd and gss_wrap the sessionkey */ + if ( 0 == (ret = get_client_username(username, ulen, &client_name)) ) { + ret = wrap_sessionkey(context_handle, sinfo); + } + if (!ret) { + /* FIXME: Is copying the authenticator really necessary? + Where is this documented? */ + u_int16_t auth_len = htons( authenticator_buff.length ); - /* Clean up after ourselves */ - gss_release_name( &minor_status, &client_name ); + /* copy the authenticator length into the reply buffer */ + memcpy( rbuf, &auth_len, sizeof(auth_len) ); + *rbuflen += sizeof(auth_len); + rbuf += sizeof(auth_len); - /* This will SIGSEGV, as it would free ibuf */ - /*gss_release_buffer( &minor_status, &ticket_buffer );*/ + /* copy the authenticator value into the reply buffer */ + memcpy( rbuf, authenticator_buff.value, authenticator_buff.length ); + *rbuflen += authenticator_buff.length; + } + /* Clean up after ourselves */ + gss_release_name( &status, &client_name ); if ( authenticator_buff.value) - gss_release_buffer( &minor_status, &authenticator_buff ); + gss_release_buffer( &status, &authenticator_buff ); - gss_delete_sec_context( &minor_status, - &context_handle, NULL ); - } else { - log_status( "accept_sec_context", major_status, minor_status ); - ret = 1; - } - gss_release_cred( &minor_status, &server_creds ); + gss_delete_sec_context( &status, &context_handle, NULL ); + } + gss_release_cred( &status, &server_creds ); cleanup_vars: - gss_release_name( &minor_status, &server_name ); + gss_release_name( &status, &server_name ); return ret; } @@ -288,8 +471,7 @@ static int gss_logincont(void *obj, struct passwd **uam_pwd, u_int16_t ticket_len; char *p; int rblen; - char *service; - int userlen, servicelen; + int userlen; struct session_info *sinfo; /* Apple's AFP 3.1 documentation specifies that this command @@ -319,7 +501,7 @@ static int gss_logincont(void *obj, struct passwd **uam_pwd, rblen = *rbuflen = 0; if (ibuflen < 3) { - LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet", p); + LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet"); return AFPERR_PARAM; } ibuf++, ibuflen--; /* ?? */ @@ -329,21 +511,18 @@ static int gss_logincont(void *obj, struct passwd **uam_pwd, ibuf += sizeof(login_id), ibuflen -= sizeof(login_id); login_id = ntohs( login_id ); + /* get the username buffer from apfd */ if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0) return AFPERR_MISC; - if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0) - return AFPERR_MISC; - - if (service == NULL) - return AFPERR_MISC; - + /* get the session_info structure from afpd. We need the session key */ if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0) return AFPERR_MISC; if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) { - LOG(log_info, logtype_uams, "internal error: sessionkey not set"); - return AFPERR_PARAM; + /* Should never happen. Most likely way too old afpd version */ + LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set"); + return AFPERR_MISC; } /* We skip past the 'username' parameter because all that matters is the ticket */ @@ -360,21 +539,24 @@ static int gss_logincont(void *obj, struct passwd **uam_pwd, LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p); + /* get the length of the ticket the client sends us */ memcpy(&ticket_len, ibuf, sizeof(ticket_len)); ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len); ticket_len = ntohs( ticket_len ); + /* a little bounds checking */ if (ticket_len > ibuflen) { - LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen); + LOG(log_info, logtype_uams, + "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen); return AFPERR_PARAM; } - if (!do_gss_auth(service, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) { - char *at = strchr( username, '@' ); - - // Chop off the realm name - if (at) - *at = '\0'; + /* now try to authenticate */ + if (!do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) { + /* We use the username we got back from the gssapi client_name. + Should we compare this to the username the client sent in the clear? + We know the character encoding of the cleartext username (UTF8), what + encoding is the gssapi name in? */ if((pwd = uam_getname( obj, username, userlen )) == NULL) { LOG(log_info, logtype_uams, "uam_getname() failed for %s", username); return AFPERR_PARAM; diff --git a/include/atalk/uam.h b/include/atalk/uam.h index b4c10d57..e161b685 100644 --- a/include/atalk/uam.h +++ b/include/atalk/uam.h @@ -44,6 +44,8 @@ #define UAM_OPTION_MACCHARSET (1 << 10) /* mac charset handle */ #define UAM_OPTION_UNIXCHARSET (1 << 11) /* unix charset handle */ #define UAM_OPTION_SESSIONINFO (1 << 12) /* unix charset handle */ +#define UAM_OPTION_KRB5REALM (1 << 13) /* krb realm */ +#define UAM_OPTION_FQDN (1 << 14) /* fully qualified name */ /* some password options. you pass these in the length parameter and * get back the corresponding option. not all of these are implemented. */ -- 2.39.2