]> arthur.barton.de Git - netatalk.git/blobdiff - etc/uams/uams_gss.c
Writing metadata xattr on directories with sticky bit set, FR#94
[netatalk.git] / etc / uams / uams_gss.c
index c68f7f6738745f43be3a07f2fb8ea8a8e39665f3..325d1c13bb1e4c54ce2fe0cadfa95899cb7864fb 100644 (file)
@@ -1,9 +1,8 @@
 /*
- * $Id: uams_gss.c,v 1.1 2003-08-22 17:12:45 samnoble Exp $
- *
  * Copyright (c) 1990,1993 Regents of The University of Michigan.
- * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu) 
+ * 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 "config.h"
 #endif /* HAVE_CONFIG_H */
 
-#ifndef ATACC
-#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
 #include <stdlib.h>
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
-#endif /* HAVE_UNISTD_H */
-
-/* STDC check */
-#if STDC_HEADERS
 #include <string.h>
-#else /* STDC_HEADERS */
-#ifndef HAVE_STRCHR
-#define strchr index
-#define strrchr index
-#endif /* HAVE_STRCHR */
-char *strchr (), *strrchr ();
-#ifndef HAVE_MEMCPY
-#define memcpy(d,s,n) bcopy ((s), (d), (n))
-#define memmove(d,s,n) bcopy ((s), (d), (n))
-#endif /* ! HAVE_MEMCPY */
-#endif /* STDC_HEADERS */
+#include <arpa/inet.h>
 
 #include <atalk/logger.h>
-
-// #include <security/pam_appl.h>
-
 #include <atalk/afp.h>
 #include <atalk/uam.h>
+#include <atalk/util.h>
+#include <atalk/compat.h>
+#include <atalk/globals.h>
 
+/* Kerberos includes */
+#ifdef HAVE_GSSAPI_GSSAPI_H
 #include <gssapi/gssapi.h>
-#include <gssapi/gssapi_generic.h>
-#include <gssapi/gssapi_krb5.h>
-
-/* The following routine is derived from code found in some GSS
- * documentation from SUN.
- */
-static void log_status( char *s, OM_uint32 major_status, 
-                       OM_uint32 minor_status )
+#else
+#include <gssapi.h>
+#endif // HAVE_GSSAPI_GSSAPI_H
+
+#ifdef HAVE_KERBEROS
+#ifdef HAVE_KRB5_KRB5_H
+#include <krb5/krb5.h>
+#else
+#include <krb5.h>
+#endif /* HAVE_KRB5_KRB5_H */
+#endif /* HAVE_KERBEROS */
+
+#define LOG_UAMS(log_level, ...) \
+    LOG(log_level, logtype_uams, __VA_ARGS__)
+
+#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)
 {
     gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
     OM_uint32 min_status, maj_status;
     OM_uint32 maj_ctx = 0, min_ctx = 0;
 
     while (1) {
-       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));
-       gss_release_buffer(&min_status, &msg);
-
-       if (!maj_ctx)
-           break;
+        maj_status = gss_display_status( &min_status, major_status,
+                                         GSS_C_GSS_CODE, GSS_C_NULL_OID,
+                                         &maj_ctx, &msg );
+        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,
-                                       &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));
-       gss_release_buffer(&min_status, &msg);
-
-       if (!min_ctx)
-           break;
+        maj_status = gss_display_status( &min_status, minor_status,
+                                         GSS_C_MECH_CODE, GSS_C_NULL_OID,
+                                         &min_ctx, &msg );
+        LOG_UAMS(log_error, "%s %.*s (error %s)",
+                 s, msg.length, msg.value, strerror(errno));
+        gss_release_buffer(&min_status, &msg);
+
+        if (!min_ctx)
+            break;
     }
 }
 
-
-void log_ctx_flags( OM_uint32 flags )
+static void log_ctx_flags(OM_uint32 flags)
 {
     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" );
+        LOG_LOGINCONT(log_debug, "context flag: GSS_C_INTEG_FLAG");
 }
-/* We work around something I don't entirely understand... */
-#if !defined (GSS_C_NT_HOSTBASED_SERVICE)
-#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
-#endif
 
-#define MIN(a, b) ((a > b) ? b : a)
-/* return 0 on success */
-static int do_gss_auth( char *service, char *ibuf, int ticket_len,
-                char *rbuf, int *rbuflen, char *username, int ulen ) 
+static void log_service_name(gss_ctx_id_t context)
 {
     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;
-    gss_name_t client_name;
-    OM_uint32  ret_flags;
-    int ret = 0;
-    gss_buffer_desc s_princ_buffer;
-
-    s_princ_buffer.value = service;
-    s_princ_buffer.length = strlen( service ) + 1;
-    
-    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 );
+    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( "import_name", major_status, minor_status );
-       ret = 1;
-       goto cleanup_vars;
+        log_status("gss_inquire_context", major_status, minor_status);
+        return;
     }
 
-    LOG(log_debug, logtype_uams, 
-       "uams_gss.c :do_gss_auth: acquiring credentials (uid = %d, keytab = %s)",
-        (int)geteuid(), getenv( "KRB5_KTNAME") );
-    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_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);
+
+        gss_release_buffer(&minor_status, &service_name_buffer);
+    } else
+        log_status("gss_display_name", major_status, minor_status);
+
+    gss_release_name(&minor_status, &service_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 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,
+                                    NULL);
     if (major_status != GSS_S_COMPLETE) {
-       log_status( "acquire_cred", major_status, minor_status );
-       ret = 1;
-       goto cleanup_vars;
+        log_status("gss_display_name", major_status, minor_status);
+        return 1;
     }
-    
-    /* 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.
-     */
-    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" );
-    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;
-
-       log_ctx_flags( ret_flags );
-       /* 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) {
-           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;
-       }
-
-
-       /* Clean up after ourselves */
-        gss_release_name( &minor_status, &client_name );
-        gss_release_buffer( &minor_status, &authenticator_buff );
-        gss_delete_sec_context( &minor_status, 
-                       &context_handle, NULL );
+    LOG_LOGINCONT(log_debug,
+                  "user principal 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 */
+    size_t cnblen = strlen(client_name_buffer.value);
+    if (cnblen >= ulen) {
+        /* The username is too long for afpd's buffer, bail out */
+        LOG_LOGINCONT(log_info,
+                      "username `%s' too long (%d)",
+                      client_name_buffer.value, cnblen);
+        ret = 1;
     } else {
-       log_status( "accept_sec_context", major_status, minor_status );
+        /* copy stripped username to afpd's buffer */
+        strlcpy(username, client_name_buffer.value, ulen);
+    }
+
+    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 major_status = 0, minor_status = 0;
+    gss_buffer_desc sesskey_buff, wrap_buff;
+    int ret = 0;
+
+    /*
+     * 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 */
+    major_status = gss_wrap(&minor_status,
+                            context,
+                            true,
+                            GSS_C_QOP_DEFAULT,
+                            &sesskey_buff,
+                            NULL,
+                            &wrap_buff);
+
+    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_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);
+        sinfo->cryptedkey_len = wrap_buff.length;
     }
-    gss_release_cred( &minor_status, &server_creds );
 
-cleanup_vars:
-    gss_release_name( &minor_status, &server_name );
-    
+    /* we're done with buffer, release */
+    gss_release_buffer(&minor_status, &wrap_buff);
+
     return ret;
 }
 
-/* -------------------------- */
-static int gss_login(void *obj, struct passwd **uam_pwd,
-                    char *ibuf, int ibuflen,
-                    char *rbuf, int *rbuflen)
+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, flags = 0;
+
+    /* Initialize autheticator buffer. */
+    authenticator_buff->length = 0;
+    authenticator_buff->value = NULL;
+
+    LOG_LOGINCONT(log_debug,
+                  "accepting context (ticketlen: %u)",
+                  ticket_buffer->length);
+
+    /*
+     * 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,
+                                          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("gss_accept_sec_context", major_status, minor_status);
+        return 1;
+    }
+
+    log_ctx_flags(flags);
+    return 0;
+}
+
+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 client_name;
+    gss_ctx_id_t context = GSS_C_NO_CONTEXT;
+    gss_buffer_desc ticket_buffer, authenticator_buff;
+    int ret = 0;
+
+    /*
+     * 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 = ibuflen;
+    ticket_buffer.value = ibuf;
+
+    if ((ret = accept_sec_context(&context,
+                                  &ticket_buffer,
+                                  &client_name,
+                                  &authenticator_buff)))
+        return ret;
+    log_service_name(context);
+
+    /* 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;
 
-    u_int16_t  temp16;
+    if ((ret = wrap_sessionkey(context, sinfo)))
+        goto cleanup_client_name;
 
+    /* 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);
+
+    /* copy the authenticator value into the reply buffer */
+    memcpy(rbuf, authenticator_buff.value, authenticator_buff.length);
+    *rbuflen += authenticator_buff.length;
+
+cleanup_client_name:
+    gss_release_name(&status, &client_name);
+    gss_release_buffer(&status, &authenticator_buff);
+    gss_delete_sec_context(&status, &context, NULL);
+
+    return ret;
+}
+
+/* -------------------------- */
+
+/*
+ * 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)
+{
     *rbuflen = 0;
 
-    /* The reply contains a two-byte ID value - note 
+    /* 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,
-                     char *ibuf, int ibuflen,
-                     char *rbuf, int *rbuflen)
+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;
-    char *service;
-    int userlen, servicelen;
+    size_t userlen;
+    struct session_info *sinfo;
 
     /* 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
      */
 
@@ -248,125 +385,254 @@ 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)
-     * username, encoding unspecified, null terminated C string, 
+     * 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 
+     *   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_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);
 
-    if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
+    /* get the username buffer from apfd */
+    if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, &username, &userlen) < 0)
         return AFPERR_MISC;
 
-    if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
-       return AFPERR_MISC;
+    /* get the session_info structure from afpd. We need the session key */
+    if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, &sinfo, NULL) < 0)
+        return AFPERR_MISC;
 
-    if (service == NULL) 
-       return AFPERR_MISC;
+    if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
+        /* Should never happen. Most likely way too old afpd version */
+        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_debug, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
-       return AFPERR_PARAM;
+        LOG_LOGINCONT(log_info, "user is %s, no ticket", p);
+        return AFPERR_PARAM;
     }
 
     ibuf++, ibuflen--; /* null termination */
 
     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");
-       return AFPERR_PARAM;
+        LOG_LOGINCONT(log_info,
+                      "invalid ticket length (%u > %u)",
+                      ticket_len, ibuflen);
+        return AFPERR_PARAM;
     }
 
-    if (!do_gss_auth(service, ibuf, ticket_len, rbuf, &rblen, username, userlen)) {
-       char *at = strchr( username, '@' );
-
-       // Chop off the realm name
-       if (at)
-           *at = '\0';
-       if((pwd = uam_getname( username, userlen )) == NULL) {
-           LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
-           return AFPERR_PARAM;
-       }
-       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" );
-       *rbuflen = 0;
+    /* now try to authenticate */
+    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,
-                    char *ibuf, int ibuflen,
-                    char *rbuf, int *rbuflen)
+/* 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;
 }
 
-static 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)
+    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)
 {
-  uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
+    uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
 }
 
 UAM_MODULE_EXPORT struct uam_export uams_gss = {
-  UAM_MODULE_SERVER,
-  UAM_MODULE_VERSION,
-  uam_setup, uam_cleanup
+    UAM_MODULE_SERVER,
+    UAM_MODULE_VERSION,
+    uam_setup,
+    uam_cleanup
 };
-#endif