/*
- * $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.
*/
#include <atalk/logger.h>
#include <atalk/afp.h>
#include <atalk/uam.h>
+#include <atalk/util.h>
/* Kerberos includes */
}
-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;
}
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
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--; /* ?? */
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 */
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;