X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=etc%2Fuams%2Fuams_gss.c;h=325d1c13bb1e4c54ce2fe0cadfa95899cb7864fb;hb=3a84db87064922ad10ac10cc1d6833380e575995;hp=76714e1522addfd13b7573c751d71494b7392e34;hpb=cb8de5b604041071f3454dd8df97295544caba59;p=netatalk.git diff --git a/etc/uams/uams_gss.c b/etc/uams/uams_gss.c index 76714e15..325d1c13 100644 --- a/etc/uams/uams_gss.c +++ b/etc/uams/uams_gss.c @@ -1,6 +1,4 @@ /* - * $Id: uams_gss.c,v 1.12 2010-03-30 10:25:49 franklahm 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 @@ -12,55 +10,43 @@ #include "config.h" #endif /* HAVE_CONFIG_H */ -#include +#include +#include #include -#ifdef HAVE_UNISTD_H -#include -#endif /* HAVE_UNISTD_H */ #include -#include +#include #include #include #include #include +#include +#include /* Kerberos includes */ - -#if HAVE_GSSAPI_H -#include -#endif - -#if HAVE_GSSAPI_GSSAPI_H +#ifdef HAVE_GSSAPI_GSSAPI_H #include -#endif - -#if HAVE_GSSAPI_GSSAPI_GENERIC_H -#include -#endif - -#if HAVE_GSSAPI_GSSAPI_KRB5_H -#include -#endif +#else +#include +#endif // HAVE_GSSAPI_GSSAPI_H -#if HAVE_COM_ERR_H -#include -#endif +#ifdef HAVE_KERBEROS +#ifdef HAVE_KRB5_KRB5_H +#include +#else +#include +#endif /* HAVE_KRB5_KRB5_H */ +#endif /* HAVE_KERBEROS */ -/* We work around something I don't entirely understand... */ -/* BF: This is a Heimdal/MIT compatibility fix */ -#ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE -#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name -#endif - -#ifdef MIN -#undef MIN -#endif +#define LOG_UAMS(log_level, ...) \ + LOG(log_level, logtype_uams, __VA_ARGS__) -#define MIN(a, b) ((a > b) ? b : a) +#define LOG_LOGINCONT(log_level, ...) \ + LOG_UAMS(log_level, "FPLoginCont: " __VA_ARGS__) -static void log_status( char *s, OM_uint32 major_status, - OM_uint32 minor_status ) +static void log_status(char *s, + OM_uint32 major_status, + OM_uint32 minor_status) { gss_buffer_desc msg = GSS_C_EMPTY_BUFFER; OM_uint32 min_status, maj_status; @@ -70,19 +56,20 @@ static void log_status( char *s, OM_uint32 major_status, maj_status = gss_display_status( &min_status, major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &maj_ctx, &msg ); - LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s, - (int)msg.length, msg.value, strerror(errno)); + LOG_UAMS(log_error, "%s %.*s (error %s)", + s, msg.length, msg.value, strerror(errno)); gss_release_buffer(&min_status, &msg); if (!maj_ctx) break; } + while (1) { maj_status = gss_display_status( &min_status, minor_status, - GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5, + GSS_C_MECH_CODE, GSS_C_NULL_OID, &min_ctx, &msg ); - LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s, - (int)msg.length, msg.value, strerror(errno)); + LOG_UAMS(log_error, "%s %.*s (error %s)", + s, msg.length, msg.value, strerror(errno)); gss_release_buffer(&min_status, &msg); if (!min_ctx) @@ -90,125 +77,66 @@ static void log_status( char *s, OM_uint32 major_status, } } - -static void log_ctx_flags( OM_uint32 flags ) +static void log_ctx_flags(OM_uint32 flags) { -#ifdef DEBUG if (flags & GSS_C_DELEG_FLAG) - LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" ); + LOG_LOGINCONT(log_debug, "context flag: GSS_C_DELEG_FLAG"); if (flags & GSS_C_MUTUAL_FLAG) - LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" ); + LOG_LOGINCONT(log_debug, "context flag: GSS_C_MUTUAL_FLAG"); if (flags & GSS_C_REPLAY_FLAG) - LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" ); + LOG_LOGINCONT(log_debug, "context flag: GSS_C_REPLAY_FLAG"); if (flags & GSS_C_SEQUENCE_FLAG) - LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" ); + LOG_LOGINCONT(log_debug, "context flag: GSS_C_SEQUENCE_FLAG"); if (flags & GSS_C_CONF_FLAG) - LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" ); + LOG_LOGINCONT(log_debug, "context flag: GSS_C_CONF_FLAG"); if (flags & GSS_C_INTEG_FLAG) - LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" ); -#endif + LOG_LOGINCONT(log_debug, "context flag: GSS_C_INTEG_FLAG"); } -static void log_principal(gss_name_t server_name) +static void log_service_name(gss_ctx_id_t context) { -#if 0 - /* FIXME: must call gss_canonicalize_name before gss_export_name */ OM_uint32 major_status = 0, minor_status = 0; - 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 *fqdn, *service, *principal, *p; - size_t fqdnlen=0, servicelen=0; - size_t principal_length; - gss_buffer_desc s_princ_buffer; - - /* get all the required information from afpd */ - if (uam_afpserver_option(obj, UAM_OPTION_FQDN, (void*) &fqdn, &fqdnlen) < 0) - return 1; - LOG(log_debug, logtype_uams, "get_afpd_principal: fqdn: %s", fqdn); - - if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0) - return 1; - LOG(log_debug, logtype_uams, "get_afpd_principal: service: %s", service); - - /* we need all the info, log error and return if one's missing */ - if (!service || !servicelen || !fqdn || !fqdnlen) { - 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; - 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; + gss_name_t service_name; + gss_buffer_desc service_name_buffer; + + major_status = gss_inquire_context(&minor_status, + context, + NULL, + &service_name, + NULL, + NULL, + NULL, + NULL, + NULL); + if (major_status != GSS_S_COMPLETE) { + log_status("gss_inquire_context", major_status, minor_status); + return; } - /* - * Build the principal string. - * Format: 'service@fqdn' - */ - 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. - */ - if ((p = strchr(fqdn, ':'))) - *p = '\0'; + major_status = gss_display_name(&minor_status, + service_name, + &service_name_buffer, + NULL); + if (major_status == GSS_S_COMPLETE) { + LOG_LOGINCONT(log_debug, + "service principal is `%s'", + service_name_buffer.value); - strlcat( principal, fqdn, principal_length); - if (p) - *p = ':'; - /* - * 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_NT_HOSTBASED_SERVICE, - server_name ); - - /* - * Get rid of malloc'ed memmory. - * Don't release the s_princ_buffer, we free principal instead. - */ - free(principal); + gss_release_buffer(&minor_status, &service_name_buffer); + } else + log_status("gss_display_name", major_status, minor_status); - if (major_status != GSS_S_COMPLETE) { - /* Importing our service principal failed, bail out. */ - log_status( "import_principal", major_status, minor_status ); - return 1; - } - return 0; + gss_release_name(&minor_status, &service_name); } - -/* get the username */ -static int get_client_username(char *username, int ulen, gss_name_t *client_name) +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; + int ret = 0; /* * To extract the unix username, use gss_display_name on client_name. @@ -218,39 +146,42 @@ static int get_client_username(char *username, int ulen, gss_name_t *client_name * the username before copying it to afpd's buffer. */ - major_status = gss_display_name( &minor_status, *client_name, - &client_name_buffer, (gss_OID *)NULL ); + major_status = gss_display_name(&minor_status, + *client_name, + &client_name_buffer, + NULL); if (major_status != GSS_S_COMPLETE) { - log_status( "display_name", major_status, minor_status ); + log_status("gss_display_name", major_status, minor_status); return 1; } - LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value); + LOG_LOGINCONT(log_debug, + "user principal is `%s'", + client_name_buffer.value); /* chop off realm */ - p = strchr( client_name_buffer.value, '@' ); + p = strchr(client_name_buffer.value, '@'); if (p) *p = 0; /* FIXME: chop off instance? */ - p = strchr( client_name_buffer.value, '/' ); + 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 ) { + size_t cnblen = strlen(client_name_buffer.value); + if (cnblen >= 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); + LOG_LOGINCONT(log_info, + "username `%s' too long (%d)", + client_name_buffer.value, cnblen); ret = 1; - } - else { + } 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 ); + gss_release_buffer(&minor_status, &client_name_buffer); return ret; } @@ -258,9 +189,9 @@ static int get_client_username(char *username, int ulen, gss_name_t *client_name /* wrap afpd's sessionkey */ static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo) { - OM_uint32 status = 0; - int ret=0; + OM_uint32 major_status = 0, minor_status = 0; gss_buffer_desc sesskey_buff, wrap_buff; + int ret = 0; /* * gss_wrap afpd's session_key. @@ -268,202 +199,173 @@ static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo) * 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 machanism. + /* 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); + major_status = gss_wrap(&minor_status, + context, + true, + 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 ); + if (major_status != GSS_S_COMPLETE) { + log_status("gss_wrap", major_status, minor_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); + if (NULL == (sinfo->cryptedkey = malloc(wrap_buff.length))) { + LOG_UAMS(log_error, + "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); + 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 ); + gss_release_buffer(&minor_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; - char *envp; - - if ((envp = getenv("KRB5_KTNAME"))) - LOG(log_debug, logtype_uams, - "acquire credentials: acquiring credentials (uid = %d, keytab = %s)", - (int)geteuid(), envp); - else - LOG(log_debug, logtype_uams, - "acquire credentials: acquiring credentials (uid = %d) - $KRB5_KTNAME not found in env", - (int)geteuid()); - - /* - * 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 ); - - if (major_status != GSS_S_COMPLETE) { - 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) +static int accept_sec_context(gss_ctx_id_t *context, + 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; + OM_uint32 major_status = 0, minor_status = 0, flags = 0; /* Initialize autheticator buffer. */ authenticator_buff->length = 0; authenticator_buff->value = NULL; - LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)", - ticket_buffer->length); + LOG_LOGINCONT(log_debug, + "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. + * Try to accept the secondary context using the token in ticket_buffer. + * We don't care about the principals or 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 ); + major_status = gss_accept_sec_context(&minor_status, + context, + GSS_C_NO_CREDENTIAL, + ticket_buffer, + GSS_C_NO_CHANNEL_BINDINGS, + client_name, + NULL, + authenticator_buff, + &flags, + NULL, + NULL); if (major_status != GSS_S_COMPLETE) { - log_status( "accept_sec_context", major_status, minor_status ); + log_status("gss_accept_sec_context", major_status, minor_status); return 1; } - log_ctx_flags( ret_flags ); + + log_ctx_flags(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, +static int do_gss_auth(void *obj, + char *ibuf, size_t ibuflen, + char *rbuf, int *rbuflen, + char *username, size_t 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_name_t client_name; + gss_ctx_id_t context = 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.length = ibuflen; ticket_buffer.value = ibuf; - 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 ); + if ((ret = accept_sec_context(&context, + &ticket_buffer, + &client_name, + &authenticator_buff))) + return ret; + log_service_name(context); - /* copy the authenticator length into the reply buffer */ - memcpy( rbuf, &auth_len, sizeof(auth_len) ); - *rbuflen += sizeof(auth_len); - rbuf += sizeof(auth_len); + /* We succesfully acquired the secondary context, now get the + username for afpd and gss_wrap the sessionkey */ + if ((ret = get_client_username(username, ulen, &client_name))) + goto cleanup_client_name; - /* copy the authenticator value into the reply buffer */ - memcpy( rbuf, authenticator_buff.value, authenticator_buff.length ); - *rbuflen += authenticator_buff.length; - } + if ((ret = wrap_sessionkey(context, sinfo))) + goto cleanup_client_name; - /* Clean up after ourselves */ - gss_release_name( &status, &client_name ); - if ( authenticator_buff.value) - gss_release_buffer( &status, &authenticator_buff ); + /* Authenticated, construct the reply using: + * authenticator length (uint16_t) + * authenticator + */ + /* copy the authenticator length into the reply buffer */ + uint16_t auth_len = htons(authenticator_buff.length); + memcpy(rbuf, &auth_len, sizeof(auth_len)); + *rbuflen += sizeof(auth_len); + rbuf += sizeof(auth_len); - gss_delete_sec_context( &status, &context_handle, NULL ); - } - gss_release_cred( &status, &server_creds ); + /* copy the authenticator value into the reply buffer */ + memcpy(rbuf, authenticator_buff.value, authenticator_buff.length); + *rbuflen += authenticator_buff.length; -cleanup_vars: - gss_release_name( &status, &server_name ); +cleanup_client_name: + gss_release_name(&status, &client_name); + gss_release_buffer(&status, &authenticator_buff); + gss_delete_sec_context(&status, &context, NULL); return ret; } /* -------------------------- */ -static int gss_login(void *obj, struct passwd **uam_pwd, + +/* + * For the gss uam, this function only needs to return a two-byte + * login-session id. None of the data provided by the client up to this + * point is trustworthy as we'll have a signed ticket to parse in logincont. + */ +static int gss_login(void *obj, + struct passwd **uam_pwd, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen) { - - u_int16_t temp16; - *rbuflen = 0; /* The reply contains a two-byte ID value - note * that Apple's implementation seems to always return 1 as well */ - temp16 = htons( 1 ); + uint16_t temp16 = htons(1); memcpy(rbuf, &temp16, sizeof(temp16)); *rbuflen += sizeof(temp16); + return AFPERR_AUTHCONT; } -static int gss_logincont(void *obj, struct passwd **uam_pwd, +static int gss_logincont(void *obj, + struct passwd **uam_pwd, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen) { struct passwd *pwd = NULL; - u_int16_t login_id; + uint16_t login_id; char *username; - u_int16_t ticket_len; + uint16_t ticket_len; char *p; int rblen; size_t userlen; @@ -472,9 +374,10 @@ static int gss_logincont(void *obj, struct passwd **uam_pwd, /* Apple's AFP 3.1 documentation specifies that this command * takes the following format: * pad (byte) - * id returned in LoginExt response (u_int16_t) - * username (format unspecified) padded, when necessary, to end on an even boundary - * ticket length (u_int16_t) + * id returned in LoginExt response (uint16_t) + * username (format unspecified) + * padded, when necessary, to end on an even boundary + * ticket length (uint16_t) * ticket */ @@ -482,49 +385,49 @@ static int gss_logincont(void *obj, struct passwd **uam_pwd, * format of this request is as follows: * pad (byte) [consumed before login_ext is called] * ?? (byte) - always observed to be 0 - * id returned in LoginExt response (u_int16_t) + * id returned in LoginExt response (uint16_t) * username, encoding unspecified, null terminated C string, * padded when the terminating null is an even numbered byte. * The packet is formated such that the username begins on an * odd numbered byte. Eg if the username is 3 characters and the * terminating null makes 4, expect to pad the the result. * The encoding of this string is unknown. - * ticket length (u_int16_t) + * ticket length (uint16_t) * ticket */ rblen = *rbuflen = 0; if (ibuflen < 1 +sizeof(login_id)) { - LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet"); + LOG_LOGINCONT(log_info, "received incomplete packet"); return AFPERR_PARAM; } ibuf++, ibuflen--; /* ?? */ /* 2 byte ID from LoginExt -- always '00 01' in this implementation */ - memcpy( &login_id, ibuf, sizeof(login_id) ); + memcpy(&login_id, ibuf, sizeof(login_id)); ibuf += sizeof(login_id), ibuflen -= sizeof(login_id); - login_id = ntohs( 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) + if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, &username, &userlen) < 0) 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) + if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, &sinfo, NULL) < 0) return AFPERR_MISC; if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) { /* Should never happen. Most likely way too old afpd version */ - LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set"); + LOG_LOGINCONT(log_error, "internal error: afpd's sessionkey not set"); return AFPERR_MISC; } /* We skip past the 'username' parameter because all that matters is the ticket */ p = ibuf; - while( *ibuf && ibuflen ) { ibuf++, ibuflen--; } + while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; } if (ibuflen < 4) { - LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p); + LOG_LOGINCONT(log_info, "user is %s, no ticket", p); return AFPERR_PARAM; } @@ -532,79 +435,194 @@ static int gss_logincont(void *obj, struct passwd **uam_pwd, if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */ - LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p); + LOG_LOGINCONT(log_debug, "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 ); + 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_LOGINCONT(log_info, + "invalid ticket length (%u > %u)", + ticket_len, ibuflen); return AFPERR_PARAM; } /* 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_NOTAUTH; - } - if (uam_checkuser(pwd) < 0) { - LOG(log_info, logtype_uams, "%s not a valid user", username); - return AFPERR_NOTAUTH; - } - *rbuflen = rblen; - *uam_pwd = pwd; - return AFP_OK; - } else { - LOG(log_info, logtype_uams, "do_gss_auth failed" ); + if (do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) { + LOG_LOGINCONT(log_info, "do_gss_auth() failed" ); *rbuflen = 0; return AFPERR_MISC; } + + /* 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_LOGINCONT(log_info, "uam_getname() failed for %s", username); + return AFPERR_NOTAUTH; + } + if (uam_checkuser(pwd) < 0) { + LOG_LOGINCONT(log_info, "`%s'' not a valid user", username); + return AFPERR_NOTAUTH; + } + + *rbuflen = rblen; + *uam_pwd = pwd; + return AFP_OK; } -/* - * For the krb5 uam, this function only needs to return a two-byte - * login-session id. None of the data provided by the client up to this - * point is trustworthy as we'll have a signed ticket to parse in logincont. - */ -static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd, +/* logout */ +static void gss_logout() { +} + +static int gss_login_ext(void *obj, + char *uname, + struct passwd **uam_pwd, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen) { - u_int16_t temp16; + return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen); +} - *rbuflen = 0; +static int set_principal(AFPObj *obj, char *principal) +{ + size_t len = strlen(principal); - /* The reply contains a two-byte ID value - note - * that Apple's implementation seems to always return 1 as well - */ - temp16 = htons( 1 ); - memcpy(rbuf, &temp16, sizeof(temp16)); - *rbuflen += sizeof(temp16); - return AFPERR_AUTHCONT; + if (len > 255) { + LOG(log_error, logtype_afpd, "set_principal: principal '%s' too long (max=255)", principal, len); + return -1; + } + + /* We store: 1 byte number principals (1) + 1 byte principal name length + zero terminated principal */ + if ((obj->options.k5principal = malloc(len + 3)) == NULL) { + LOG(log_error, logtype_afpd, "set_principal: OOM"); + return -1; + } + + LOG(log_info, logtype_afpd, "Using AFP Kerberos service principal name: %s", principal); + + obj->options.k5principal[0] = 1; + obj->options.k5principal[1] = (unsigned char)len; + obj->options.k5principal_buflen = len + 2; + strncpy(obj->options.k5principal + 2, principal, len); + + return 0; } -/* logout */ -static void gss_logout() { +static int gss_create_principal(AFPObj *obj) +{ + int rv = -1; +#ifdef HAVE_KERBEROS + krb5_context context; + krb5_error_code ret; + const char *error_msg; + krb5_keytab keytab; + krb5_keytab_entry entry; + krb5_principal service_principal; + char *principal; + krb5_kt_cursor cursor; + + if (krb5_init_context(&context)) { + LOG(log_error, logtype_afpd, "gss_create_principal: failed to intialize a krb5_context"); + goto exit; + } + if ((ret = krb5_kt_default(context, &keytab))) + goto krb5_error; + + if (obj->options.k5service && obj->options.fqdn && obj->options.k5realm) { + LOG(log_debug, logtype_afpd, "gss_create_principal: using service principal specified in options"); + + if ((ret = krb5_build_principal(context, + &service_principal, + strlen(obj->options.k5realm), + obj->options.k5realm, + obj->options.k5service, + obj->options.fqdn, + NULL))) + goto krb5_error; + + if ((ret = krb5_kt_get_entry(context, + keytab, + service_principal, + 0, // kvno - wildcard + 0, // enctype - wildcard + &entry)) == KRB5_KT_NOTFOUND) { + krb5_unparse_name(context, service_principal, &principal); + LOG(log_error, logtype_afpd, "gss_create_principal: specified service principal '%s' not found in keytab", principal); +#ifdef HAVE_KRB5_FREE_UNPARSED_NAME + krb5_free_unparsed_name(context, principal); +#else + krb5_xfree(principal); +#endif + goto krb5_cleanup; + } + krb5_free_principal(context, service_principal); + if (ret) + goto krb5_error; + } else { + LOG(log_debug, logtype_afpd, "gss_create_principal: using first entry from keytab as service principal"); + if ((ret = krb5_kt_start_seq_get(context, keytab, &cursor))) + goto krb5_error; + ret = krb5_kt_next_entry(context, keytab, &entry, &cursor); + krb5_kt_end_seq_get(context, keytab, &cursor); + if (ret) + goto krb5_error; + } + + krb5_unparse_name(context, entry.principal, &principal); +#ifdef HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS + krb5_free_keytab_entry_contents(context, &entry); +#elif defined(HAVE_KRB5_KT_FREE_ENTRY) + krb5_kt_free_entry(context, &entry); +#endif + set_principal(obj, principal); + free(principal); + rv = 0; + goto krb5_cleanup; + +krb5_error: + if (ret) { + error_msg = krb5_get_error_message(context, ret); + LOG(log_note, logtype_afpd, "Can't get principal from default keytab: %s", + (char *)error_msg); +#ifdef HAVE_KRB5_FREE_ERROR_MESSAGE + krb5_free_error_message(context, error_msg); +#else + krb5_xfree(error_msg); +#endif + } + +krb5_cleanup: + krb5_kt_close(context, keytab); + krb5_free_context(context); + +#else /* ! HAVE_KERBEROS */ + if (!obj->options.k5service || !obj->options.fqdn || !obj->options.k5realm) + goto exit; + + char principal[255]; + size_t len = snprintf(principal, sizeof(principal), "%s/%s@%s", + obj->options.k5service, obj->options.fqdn, obj->options.k5realm); + (void)set_principal(obj, principal); + rv = 0; +#endif /* HAVE_KERBEROS */ + +exit: + return rv; } -int uam_setup(const char *path) +static int uam_setup(void *handle, const char *path) { - if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2", - gss_login, gss_logincont, gss_logout, gss_login_ext) < 0) - if (uam_register(UAM_SERVER_LOGIN, path, "Client Krb v2", - gss_login, gss_logincont, gss_logout) < 0) - return -1; + AFPObj *obj = (AFPObj *)handle; + if (gss_create_principal(obj) != 0) + return -1; - return 0; + return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2", + gss_login, gss_logincont, gss_logout, gss_login_ext); } static void uam_cleanup(void) @@ -615,5 +633,6 @@ static void uam_cleanup(void) UAM_MODULE_EXPORT struct uam_export uams_gss = { UAM_MODULE_SERVER, UAM_MODULE_VERSION, - uam_setup, uam_cleanup + uam_setup, + uam_cleanup };