Pass a threadsafe ctx into fr_connection_pool create callback
[freeradius.git] / src / modules / rlm_krb5 / rlm_krb5.c
index ab86b3f..14743d5 100644 (file)
@@ -1,8 +1,4 @@
 /*
- * rlm_krb5.c  module to authenticate against krb5
- *
- * Version:    $Id$
- *
  *   This program is free software; you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License as published by
  *   the Free Software Foundation; either version 2 of the License, or
  *   You should have received a copy of the GNU General Public License
  *   along with this program; if not, write to the Free Software
  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- *
- * Copyright 2000,2006,2012  The FreeRADIUS server project
- * Copyright 2000  Nathan Neulinger <nneul@umr.edu>
- * Copyright 2000  Alan DeKok <aland@ox.org>
  */
 
-#include       <freeradius-devel/ident.h>
+/**
+ * $Id$
+ * @file rlm_krb5.c
+ * @brief Authenticate users, retrieving their TGT from a Kerberos V5 TDC.
+ *
+ * @copyright 2000,2006,2012-2013  The FreeRADIUS server project
+ * @copyright 2013  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ * @copyright 2000  Nathan Neulinger <nneul@umr.edu>
+ * @copyright 2000  Alan DeKok <aland@ox.org>
+ */
 RCSID("$Id$")
 
-#include       <freeradius-devel/radiusd.h>
-#include       <freeradius-devel/modules.h>
-
-/* krb5 includes */
-#include <krb5.h>
-#include <com_err.h>
-
- /* Arbitrary 64char limit on service names */
-#define SERVICE_NAME_LEN 64
-
-typedef struct rlm_krb5_t {
-       const char *keytab;
-       const char *service_princ;
-       const char *cache;
-       krb5_context *context;
-} rlm_krb5_t;
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/rad_assert.h>
+#include "krb5.h"
 
 static const CONF_PARSER module_config[] = {
-       { "keytab", PW_TYPE_STRING_PTR,
-         offsetof(rlm_krb5_t,keytab), NULL, NULL },
-       { "service_principal", PW_TYPE_STRING_PTR,
-         offsetof(rlm_krb5_t,service_princ), NULL, NULL },
-       { "cache", PW_TYPE_BOOLEAN,
-         offsetof(rlm_krb5_t,cache), NULL, "yes" },
+       { "keytab", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_krb5_t, keytabname), NULL },
+       { "service_principal", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_krb5_t, service_princ), NULL },
        { NULL, -1, 0, NULL, NULL }
 };
 
+static int mod_detach(void *instance)
+{
+       rlm_krb5_t *inst = instance;
+
 #ifndef HEIMDAL_KRB5
+       talloc_free(inst->vic_options);
 
-static int krb5_build_auth_context(rlm_krb5_t *inst,
-                                  krb5_context context,
-                                  krb5_auth_context *auth_context)
-{
-       int ret;
-       krb5_int32 flags;
-       
-       ret = krb5_auth_con_init(context, auth_context);
-       if (ret)
-               return ret;
-       
-       ret = krb5_auth_con_getflags(context, *auth_context, &flags);
-       if (ret)
-               return ret;
-               
-       if (!inst->cache && (flags & KRB5_AUTH_CONTEXT_DO_TIME)) {
-               ret = krb5_auth_con_setflags(context, *auth_context, flags & ~KRB5_AUTH_CONTEXT_DO_TIME);
-
-               if (ret)
-                       return ret;
+       if (inst->gic_options) {
+               krb5_get_init_creds_opt_free(inst->context, inst->gic_options);
        }
-       
+
+       if (inst->server) {
+               krb5_free_principal(inst->context, inst->server);
+       }
+#endif
+
+       /* Don't free hostname, it's just a pointer into service_princ */
+       talloc_free(inst->service);
+
+       if (inst->context) {
+               krb5_free_context(inst->context);
+       }
+#ifdef KRB5_IS_THREAD_SAFE
+       fr_connection_pool_delete(inst->pool);
+#endif
+
        return 0;
 }
 
-static int verify_krb5_tgt(krb5_context context, rlm_krb5_t *inst,
-                          const char *user, krb5_ccache ccache)
+static int mod_instantiate(CONF_SECTION *conf, void *instance)
 {
-       int rcode;
-       int ret;
-       char phost[BUFSIZ];
-       krb5_principal princ;
-       krb5_keyblock *keyblock = 0;
-       krb5_data packet, *server;
-       krb5_auth_context auth_context = NULL;
+       rlm_krb5_t *inst = instance;
+       krb5_error_code ret;
+#ifndef HEIMDAL_KRB5
        krb5_keytab keytab;
+       char keytab_name[200];
+       char *princ_name;
+#endif
 
-       char service[SERVICE_NAME_LEN] = "host";
-       char *server_name = NULL;
-       char *keytab_name;
-       
-       /* krb5_kt_read_service_key lacks const qualifier */
-       memcpy(keytab_name, inst->keytab, sizeof(keytab_name));
-
-       if (inst->service_princ != NULL) {
-               server_name = strchr(inst->service_princ, '/');
-               if (server_name != NULL) {
-                       *server_name = '\0';
-               }
+#ifdef HEIMDAL_KRB5
+       DEBUG("Using Heimdal Kerberos library");
+#else
+       DEBUG("Using MIT Kerberos library");
+#endif
 
-               strlcpy(service, inst->service_princ, sizeof(service));
+       if (!krb5_is_thread_safe()) {
+/*
+ *     rlm_krb5 was built as threadsafe
+ */
+#ifdef KRB5_IS_THREAD_SAFE
+               ERROR("Build time libkrb5 was threadsafe, but run time library claims not to be");
+               ERROR("Modify runtime linker path (LD_LIBRARY_PATH on most systems), to prefer threadsafe libkrb5");
+               return -1;
+/*
+ *     rlm_krb5 was not built as threadsafe
+ */
+#else
+               WARN("libkrb5 is not threadsafe, recompile it with thread support enabled ("
+#  ifdef HEIMDAL_KRB5
+                      "--enable-pthread-support"
+#  else
+                      "--disable-thread-support=no"
+#  endif
+                      ")");
+               WARN("rlm_krb5 will run in single threaded mode, performance may be degraded");
+       } else {
+               WARN("Build time libkrb5 was not threadsafe, but run time library claims to be");
+               WARN("Reconfigure and recompile rlm_krb5 to enable thread support");
+#endif
+       }
 
-               if (server_name != NULL) {
-                       *server_name = '/';
-                       server_name++;
-               }
+       inst->xlat_name = cf_section_name2(conf);
+       if (!inst->xlat_name) {
+               inst->xlat_name = cf_section_name1(conf);
        }
 
-       memset(&packet, 0, sizeof packet);
-       ret = krb5_sname_to_principal(context, server_name, service,
-                                     KRB5_NT_SRV_HST, &princ);
+       ret = krb5_init_context(&inst->context);
        if (ret) {
-               radlog(L_DBG, "rlm_krb5: [%s] krb5_sname_to_principal failed: %s",
-                       user, error_message(ret));
+               ERROR("rlm_krb5 (%s): context initialisation failed: %s", inst->xlat_name,
+                     rlm_krb5_error(NULL, ret));
 
-               return RLM_MODULE_REJECT;
+               return -1;
        }
 
-       server = krb5_princ_component(c, princ, 1);
-       if (!server) {
-               radlog(L_DBG, "rlm_krb5: [%s] krb5_princ_component failed.",
-                      user);
+       /*
+        *      Split service principal into service and host components
+        *      they're needed to build the server principal in MIT,
+        *      and to set the validation service in Heimdal.
+        */
+       if (inst->service_princ) {
+               size_t len;
+               /* Service principal appears to contain a host component */
+               inst->hostname = strchr(inst->service_princ, '/');
+               if (inst->hostname) {
+                       len = (inst->hostname - inst->service_princ);
+                       inst->hostname++;
+               } else {
+                       len = strlen(inst->service_princ);
+               }
 
-               return RLM_MODULE_REJECT;
+               if (len) {
+                       inst->service = talloc_array(inst, char, (len + 1));
+                       strlcpy(inst->service, inst->service_princ, len + 1);
+               }
        }
-       
-       strlcpy(phost, server->data, sizeof(phost));
 
-       /*
-        *  Do we have host/<host> keys?
-        *  (use default/configured keytab, kvno IGNORE_VNO to get the
-        *  first match, and enctype is currently ignored anyhow.)
-        */
-       ret = krb5_kt_read_service_key(context, keytab_name, princ, 0,
-                                      ENCTYPE_DES_CBC_MD5, &keyblock);
-       if (ret) {
-               /* Keytab or service key does not exist */
-               radlog(L_DBG, "rlm_krb5: verify_krb_v5_tgt: host key not found : %s",
-                      error_message(ret));
-                      
-               return RLM_MODULE_OK;
+#ifdef HEIMDAL_KRB5
+       if (inst->hostname) {
+               DEBUG("rlm_krb5 (%s): Ignoring hostname component of service principal \"%s\", not "
+                     "needed/supported by Heimdal", inst->xlat_name, inst->hostname);
        }
-       
-       if (keyblock)
-               krb5_free_keyblock(context, keyblock);
+#else
 
        /*
-        *  Talk to the kdc and construct the ticket.
+        *      Convert the service principal string to a krb5 principal.
         */
-       ret = krb5_build_auth_context(inst, context, &auth_context);
+       ret = krb5_sname_to_principal(inst->context, inst->hostname, inst->service, KRB5_NT_SRV_HST, &(inst->server));
        if (ret) {
-               radlog(L_DBG, "rlm_krb5: [%s] krb5_build_auth_context() failed: %s",
-                      user, error_message(ret));
-                      
-               rcode = RLM_MODULE_REJECT;
-               goto cleanup;
-       }
-       
-       ret = krb5_mk_req(context, &auth_context, 0, service, phost, NULL,
-                         ccache, &packet);
-       if (auth_context) {
-               krb5_auth_con_free(context, auth_context);
-               auth_context = NULL; /* setup for rd_req */
+               ERROR("rlm_krb5 (%s): Failed parsing service principal: %s", inst->xlat_name,
+                     rlm_krb5_error(inst->context, ret));
+
+               return -1;
        }
 
+       ret = krb5_unparse_name(inst->context, inst->server, &princ_name);
        if (ret) {
-               radlog(L_DBG, "rlm_krb5: [%s] krb5_mk_req() failed: %s",
-                      user, error_message(ret));
+               /* Uh? */
+               ERROR("rlm_krb5 (%s): Failed constructing service principal string: %s", inst->xlat_name,
+                     rlm_krb5_error(inst->context, ret));
 
-               rcode = RLM_MODULE_REJECT;
-               goto cleanup;
+               return -1;
        }
 
-       if (keytab_name != NULL) {
-               ret = krb5_kt_resolve(context, keytab_name, &keytab);
-       }
+       /*
+        *      Not necessarily the same as the config item
+        */
+       DEBUG("rlm_krb5 (%s): Using service principal \"%s\"", inst->xlat_name, princ_name);
 
-       if (keytab_name == NULL || ret) {
-               ret = krb5_kt_default(context, &keytab);
-       }
+       krb5_free_unparsed_name(inst->context, princ_name);
 
-       /* Hmm?  The keytab was just fine a second ago! */
+       /*
+        *      Setup options for getting credentials and verifying them
+        */
+
+       /* For some reason the 'init' version of this function is deprecated */
+       ret = krb5_get_init_creds_opt_alloc(inst->context, &(inst->gic_options));
        if (ret) {
-               radlog(L_AUTH, "rlm_krb5: [%s] krb5_kt_resolve failed: %s",
-                       user, error_message(ret));
-                       
-               rcode = RLM_MODULE_REJECT;
-               goto cleanup;
+               ERROR("rlm_krb5 (%s): Couldn't allocated inital credential options: %s", inst->xlat_name,
+                     rlm_krb5_error(inst->context, ret));
+
+               return -1;
        }
 
-       /* Try to use the ticket. */
-       ret = krb5_build_auth_context(inst, context, &auth_context);
+       /*
+        *      Perform basic checks on the keytab
+        */
+       ret = inst->keytabname ?
+               krb5_kt_resolve(inst->context, inst->keytabname, &keytab) :
+               krb5_kt_default(inst->context, &keytab);
        if (ret) {
-               radlog(L_DBG, "rlm_krb5: [%s] krb5_build_auth_context() failed: %s",
-                      user, error_message(ret));
+               ERROR("rlm_krb5 (%s): Resolving keytab failed: %s", inst->xlat_name,
+                     rlm_krb5_error(inst->context, ret));
 
-               rcode = RLM_MODULE_REJECT;
-               goto cleanup;
+               return -1;
        }
-       
-       ret = krb5_rd_req(context, &auth_context, &packet, princ,
-                         keytab, NULL, NULL);
-       if (auth_context)
-               krb5_auth_con_free(context, auth_context);
-
-       krb5_kt_close(context, keytab);
 
+       ret = krb5_kt_get_name(inst->context, keytab, keytab_name, sizeof(keytab_name));
+       krb5_kt_close(inst->context, keytab);
        if (ret) {
-               radlog(L_AUTH, "rlm_krb5: [%s] krb5_rd_req() failed: %s",
-                      user, error_message(ret));
+               ERROR("rlm_krb5 (%s): Can't retrieve keytab name: %s", inst->xlat_name,
+                     rlm_krb5_error(inst->context, ret));
 
-               rcode = RLM_MODULE_REJECT;
-       } else {
-               rcode = RLM_MODULE_OK;
-       }
-       
-cleanup:
-       if (packet.data) {
-               krb5_free_data_contents(context, &packet);
+               return -1;
        }
-       
-       return rcode;
-}
-#endif
-
-static int krb5_instantiate(CONF_SECTION *conf, void **instance)
-{
-       int ret;
-       rlm_krb5_t *data;
-       krb5_context *context;
 
-       data = rad_malloc(sizeof(*data));
+       DEBUG("rlm_krb5 (%s): Using keytab \"%s\"", inst->xlat_name, keytab_name);
 
-       memset(data, 0, sizeof(*data));
+       MEM(inst->vic_options = talloc_zero(inst, krb5_verify_init_creds_opt));
+       krb5_verify_init_creds_opt_init(inst->vic_options);
+#endif
 
-       if (cf_section_parse(conf, data, module_config) < 0) {
-               free(data);
+#ifdef KRB5_IS_THREAD_SAFE
+       /*
+        *      Initialize the socket pool.
+        */
+       inst->pool = fr_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL, NULL);
+       if (!inst->pool) {
                return -1;
        }
-       
-       context = data->context = rad_malloc(sizeof(*context));
-
-       ret = krb5_init_context(context);
-       if (ret) {
-               radlog(L_AUTH, "rlm_krb5: krb5_init failed: %s",
-                      error_message(ret));
-  
-               free(data);
+#else
+       inst->conn = mod_conn_create(inst, inst);
+       if (!inst->conn) {
                return -1;
-       } else {
-               radlog(L_AUTH, "rlm_krb5: krb5_init ok");
        }
-       
-       *instance = data;
-       
-       return 0;
-}
-
-static int krb5_detach(void *instance)
-{
-       free(((rlm_krb5_t *)instance)->context);
-       free(instance);
-       
+#endif
        return 0;
 }
 
-/* 
- *  Validate userid/passwd (MIT)
+/** Common function for transforming a User-Name string into a principal.
+ *
+ * @param[out] client Where to write the client principal.
+ * @param[in] request Current request.
+ * @param[in] context Kerberos context.
  */
-#ifndef HEIMDAL_KRB5
-static int krb5_auth(void *instance, REQUEST *request)
+static rlm_rcode_t krb5_parse_user(krb5_principal *client, REQUEST *request, krb5_context context)
 {
-       rlm_krb5_t *inst = instance;
-       int ret;
-       
-       static char tgs_name[] = KRB5_TGS_NAME;
-       krb5_data tgtname = {
-               0,
-               KRB5_TGS_NAME_SIZE,
-               tgs_name
-       };
-       
-       krb5_creds kcreds;
-       krb5_ccache ccache;
-       
-       /* MEMORY: + unsigned int (20) + NULL */
-       char cache_name[28];
-
-       krb5_context context = *(inst->context); /* copy data */
-       const char *user, *pass;
+       krb5_error_code ret;
+       char *princ_name;
 
        /*
-        *  We can only authenticate user requests which HAVE
-        *  a User-Name attribute.
+        *      We can only authenticate user requests which HAVE
+        *      a User-Name attribute.
         */
        if (!request->username) {
-               radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
-               
+               REDEBUG("Attribute \"User-Name\" is required for authentication");
+
                return RLM_MODULE_INVALID;
        }
 
        /*
-        *  We can only authenticate user requests which HAVE
-        *  a User-Password attribute.
+        *      We can only authenticate user requests which HAVE
+        *      a User-Password attribute.
         */
        if (!request->password) {
-               radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
-               
+               REDEBUG("Attribute \"User-Password\" is required for authentication");
+
                return RLM_MODULE_INVALID;
        }
 
        /*
-        *  Ensure that we're being passed a plain-text password,
-        *  and not anything else.
+        *      Ensure that we're being passed a plain-text password,
+        *      and not anything else.
         */
-       if (request->password->attribute != PW_USER_PASSWORD) {
-               radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.  Cannot use \"%s\".", request->password->name);
-               
+       if (request->password->da->attr != PW_USER_PASSWORD) {
+               REDEBUG("Attribute \"User-Password\" is required for authentication.  Cannot use \"%s\".",
+                       request->password->da->name);
+
                return RLM_MODULE_INVALID;
        }
 
-       user = request->username->vp_strvalue;
-       pass = request->password->vp_strvalue;
-
-       /*
-        *  Generate a unique cache_name.
-        */
-       snprintf(cache_name, sizeof(cache_name), "MEMORY:%u", request->number);
-
-       ret = krb5_cc_resolve(context, cache_name, &ccache);
+       ret = krb5_parse_name(context, request->username->vp_strvalue, client);
        if (ret) {
-               radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_resolve(): %s",
-                      user, error_message(ret));
-                      
-               return RLM_MODULE_REJECT;
+               REDEBUG("Failed parsing username as principal: %s", rlm_krb5_error(context, ret));
+
+               return RLM_MODULE_FAIL;
        }
 
-       /*
-        *  Actually perform the authentication.
-        */
-       memset((char *)&kcreds, 0, sizeof(kcreds));
+       krb5_unparse_name(context, *client, &princ_name);
+       RDEBUG("Using client principal \"%s\"", princ_name);
+#ifdef HEIMDAL_KRB5
+       free(princ_name);
+#else
+       krb5_free_unparsed_name(context, princ_name);
+#endif
+       return RLM_MODULE_OK;
+}
 
-       ret = krb5_parse_name(context, user, &kcreds.client);
-       if (ret) {
-               radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
-                      user, error_message(ret));
-                      
-               return RLM_MODULE_REJECT;
-       }
+/** Log error message and return appropriate rcode
+ *
+ * Translate kerberos error codes into return codes.
+ * @param request Current request.
+ * @param ret code from kerberos.
+ * @param conn used in the last operation.
+ */
+static rlm_rcode_t krb5_process_error(REQUEST *request, rlm_krb5_handle_t *conn, int ret)
+{
+       rad_assert(ret != 0);
+       rad_assert(conn);       /* Silences warnings */
 
-       ret = krb5_cc_initialize(context, ccache, kcreds.client);
-       if (ret) {
-               radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_initialize(): %s",
-                      user, error_message(ret));
-                      
+       switch (ret) {
+       case KRB5_LIBOS_BADPWDMATCH:
+       case KRB5KRB_AP_ERR_BAD_INTEGRITY:
+               REDEBUG("Provided password was incorrect (%i): %s", ret, rlm_krb5_error(conn->context, ret));
                return RLM_MODULE_REJECT;
-       }
 
-       /*
-        *  MIT krb5 verification.
-        */
-       ret = krb5_build_principal_ext(context, &kcreds.server,
-                       krb5_princ_realm(context, kcreds.client)->length,
-                       krb5_princ_realm(context, kcreds.client)->data,
-                       tgtname.length,
-                       tgtname.data,
-                       krb5_princ_realm(context, kcreds.client)->length,
-                       krb5_princ_realm(context, kcreds.client)->data,
-                       0);
-       if (ret) {
-               radlog(L_AUTH, "rlm_krb5: [%s] krb5_build_principal_ext failed: %s",
-                       user, error_message(ret));
-               krb5_cc_destroy(context, ccache);
-               
-               return RLM_MODULE_REJECT;
-       }
+       case KRB5KDC_ERR_KEY_EXP:
+       case KRB5KDC_ERR_CLIENT_REVOKED:
+       case KRB5KDC_ERR_SERVICE_REVOKED:
+               REDEBUG("Account has been locked out (%i): %s", ret, rlm_krb5_error(conn->context, ret));
+               return RLM_MODULE_USERLOCK;
 
-       ret = krb5_get_in_tkt_with_password(context, 0, NULL, NULL, NULL, pass,
-                                           ccache, &kcreds, 0);
-       if (ret) {
-               radlog(L_AUTH, "rlm_krb5: [%s] krb5_g_i_t_w_p failed: %s",
-                      user, error_message(ret));
-               krb5_free_cred_contents(context, &kcreds);
-               krb5_cc_destroy(context, ccache);
-               
-               return RLM_MODULE_REJECT;
-       }
+       case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
+               RDEBUG("User not found (%i): %s", ret, rlm_krb5_error(conn->context, ret));
+               return RLM_MODULE_NOTFOUND;
 
-       /*
-        *  Now verify the KDC's identity.
-        */
-       ret = verify_krb5_tgt(context, inst, user, ccache);
-       krb5_free_cred_contents(context, &kcreds);
-       krb5_cc_destroy(context, ccache);
-       
-       return ret;
+       default:
+               REDEBUG("Error verifying credentials (%i): %s", ret, rlm_krb5_error(conn->context, ret));
+               return RLM_MODULE_FAIL;
+       }
 }
 
-#else /* HEIMDAL_KRB5 */
+#ifdef HEIMDAL_KRB5
 
 /*
- *  validate user/pass (Heimdal)
+ *     Validate user/pass (Heimdal)
  */
-static int krb5_auth(void *instance, REQUEST *request)
+static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
 {
        rlm_krb5_t *inst = instance;
-
+       rlm_rcode_t rcode;
        krb5_error_code ret;
-       krb5_ccache id;
-       krb5_principal userP;
 
-       krb5_context context = *(inst->context); /* copy data */
-       const char *user, *pass;
+       rlm_krb5_handle_t *conn;
 
-       char service[SERVICE_NAME_LEN] = "host";
-       char *server_name = NULL;
-       char *princ_name;
+       krb5_principal client;
 
-       krb5_verify_opt krb_verify_options;
-       krb5_keytab keytab;
+#ifdef KRB5_IS_THREAD_SAFE
+       conn = fr_connection_get(inst->pool);
+       if (!conn) return RLM_MODULE_FAIL;
+#else
+       conn = inst->conn;
+#endif
 
-       if (inst->service_princ != NULL) {
-               server_name = strchr(inst->service_princ, '/');
-               if (server_name != NULL) {
-                       *server_name = '\0';
-               }
+       /*
+        *      Zero out local storage
+        */
+       memset(&client, 0, sizeof(client));
 
-               strlcpy(service, inst->service_princ, sizeof(service));
-               if (server_name != NULL) {
-                       *server_name = '/';
-                       server_name++;
-               }
-       }
+       rcode = krb5_parse_user(&client, request, conn->context);
+       if (rcode != RLM_MODULE_OK) goto cleanup;
 
        /*
-        *  We can only authenticate user requests which HAVE
-        *  a User-Name attribute.
+        *      Verify the user, using the options we set in instantiate
         */
-       if (!request->username) {
-               radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
-               
-               return RLM_MODULE_INVALID;
+       ret = krb5_verify_user_opt(conn->context, client, request->password->vp_strvalue, &conn->options);
+       if (ret) {
+               rcode = krb5_process_error(request, conn, ret);
+               goto cleanup;
        }
 
        /*
-        *  We can only authenticate user requests which HAVE
-        *  a User-Password attribute.
+        *      krb5_verify_user_opt adds the credentials to the ccache
+        *      we specified with krb5_verify_opt_set_ccache.
+        *
+        *      To make sure we don't accumulate thousands of sets of
+        *      credentials, remove them again here.
+        *
+        * @todo This should definitely be optional, which means writing code for the MIT
+        *       variant as well.
         */
-       if (!request->password) {
-               radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
-               
-               return RLM_MODULE_INVALID;
+       {
+               krb5_cc_cursor cursor;
+               krb5_creds cred;
+
+               krb5_cc_start_seq_get(conn->context, conn->ccache, &cursor);
+               for ((ret = krb5_cc_next_cred(conn->context, conn->ccache, &cursor, &cred));
+                    ret == 0;
+                    (ret = krb5_cc_next_cred(conn->context, conn->ccache, &cursor, &cred))) {
+                    krb5_cc_remove_cred(conn->context, conn->ccache, 0, &cred);
+               }
+               krb5_cc_end_seq_get(conn->context, conn->ccache, &cursor);
        }
 
-       /*
-        *  Ensure that we're being passed a plain-text password,
-        *  and not anything else.
-        */
-       if (request->password->attribute != PW_USER_PASSWORD) {
-               radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.  Cannot use \"%s\".", request->password->name);
-               
-               return RLM_MODULE_INVALID;
+cleanup:
+       if (client) {
+               krb5_free_principal(conn->context, client);
        }
 
-       user = request->username->vp_strvalue;
-       pass = request->password->vp_strvalue;
+#ifdef KRB5_IS_THREAD_SAFE
+       fr_connection_release(inst->pool, conn);
+#endif
+       return rcode;
+}
 
-       ret = krb5_parse_name(context, user, &userP);
-       if (ret) {
-               radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
-                      user, error_message(ret));
-                      
-               return RLM_MODULE_REJECT;
-       }
+#else  /* HEIMDAL_KRB5 */
 
-       /*
-        *  Heimdal krb5 verification.
-        */
-        
-       /*
-        *  The following bit allows us to also log user/instance@REALM if someone
-        *  logs in using an instance.
-        */
-       ret = krb5_unparse_name(context, userP, &princ_name);
-       if (ret != 0) {
-               radlog(L_AUTH, "rlm_krb5: Unparsable name");
-       } else {
-               radlog(L_AUTH, "rlm_krb5: Parsed name is: %s", princ_name);
-               free(princ_name);
-       }
+/*
+ *  Validate userid/passwd (MIT)
+ */
+static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
+{
+       rlm_krb5_t *inst = instance;
+       rlm_rcode_t rcode;
+       krb5_error_code ret;
 
-       krb5_cc_default(context, &id);
+       rlm_krb5_handle_t *conn;
 
-       /*
-        *  Set up krb5_verify_user options.
-        */
-       krb5_verify_opt_init(&krb_verify_options);
-       krb5_verify_opt_set_ccache(&krb_verify_options, id);
+       krb5_principal client;
+       krb5_creds init_creds;
+       char *password;         /* compiler warnings */
 
-       /*
-        *  Resolve keytab name. This allows us to use something other than
-        *  the default system keytab
-        */
-       if (inst->keytab != NULL) {
-               ret = krb5_kt_resolve(context, inst->keytab, &keytab);
-               if (ret) {
-                       radlog(L_AUTH, "rlm_krb5: unable to resolve keytab %s: %s",
-                              inst->keytab, error_message(ret));
-                       krb5_kt_close(context, keytab);
-                       
-                       return RLM_MODULE_REJECT;
-               }
-               
-               krb5_verify_opt_set_keytab(&krb_verify_options, keytab);
-       }
+       rad_assert(inst->context);
 
-       /*
-        *  Verify aquired credentials against the keytab.
-        */
-       krb5_verify_opt_set_secure(&krb_verify_options, 1);
+#ifdef KRB5_IS_THREAD_SAFE
+       conn = fr_connection_get(inst->pool);
+       if (!conn) return RLM_MODULE_FAIL;
+#else
+       conn = inst->conn;
+#endif
 
        /*
-        *  Allow us to use an arbitrary service name.
+        *      Zero out local storage
         */
-       krb5_verify_opt_set_service(&krb_verify_options, service);
+       memset(&client, 0, sizeof(client));
+       memset(&init_creds, 0, sizeof(init_creds));
 
-       /* 
-        *  Verify the user, using the above set options.
+       /*
+        *      Check we have all the required VPs, and convert the username
+        *      into a principal.
         */
-       ret = krb5_verify_user_opt(context, userP, pass, &krb_verify_options);
+       rcode = krb5_parse_user(&client, request, conn->context);
+       if (rcode != RLM_MODULE_OK) goto cleanup;
 
        /*
-        *  We are done with the keytab, close it.
+        *      Retrieve the TGT from the TGS/KDC and check we can decrypt it.
         */
-       krb5_kt_close(context, keytab);
+       memcpy(&password, &request->password->vp_strvalue, sizeof(password));
+       RDEBUG("Retrieving and decrypting TGT");
+       ret = krb5_get_init_creds_password(conn->context, &init_creds, client, password,
+                                          NULL, NULL, 0, NULL, inst->gic_options);
+       if (ret) {
+               rcode = krb5_process_error(request, conn, ret);
+               goto cleanup;
+       }
 
-       if (ret == 0)
-               return RLM_MODULE_OK;
+       RDEBUG("Attempting to authenticate against service principal");
+       ret = krb5_verify_init_creds(conn->context, &init_creds, inst->server, conn->keytab, NULL, inst->vic_options);
+       if (ret) {
+               rcode = krb5_process_error(request, conn, ret);
+       }
 
-       radlog(L_AUTH, "rlm_krb5: failed verify_user: %s (%s@%s)",
-              error_message(ret),
-              *userP->name.name_string.val,
-              userP->realm);
+cleanup:
+       if (client) {
+               krb5_free_principal(conn->context, client);
+       }
+       krb5_free_cred_contents(conn->context, &init_creds);
 
-       return RLM_MODULE_REJECT;
+#ifdef KRB5_IS_THREAD_SAFE
+       fr_connection_release(inst->pool, conn);
+#endif
+       return rcode;
 }
 
-#endif /* HEIMDAL_KRB5 */
+#endif /* MIT_KRB5 */
 
 module_t rlm_krb5 = {
        RLM_MODULE_INIT,
-       "Kerberos",
-       RLM_TYPE_THREAD_UNSAFE, /* type: not thread safe */
-       krb5_instantiate,               /* instantiation */
-       krb5_detach,                    /* detach */
+       "krb5",
+       RLM_TYPE_HUP_SAFE
+#ifdef KRB5_IS_THREAD_SAFE
+       | RLM_TYPE_THREAD_SAFE
+#endif
+       ,
+       sizeof(rlm_krb5_t),
+       module_config,
+       mod_instantiate,                /* instantiation */
+       mod_detach,                     /* detach */
        {
-               krb5_auth,              /* authenticate */
+               mod_authenticate,       /* authenticate */
                NULL,                   /* authorize */
                NULL,                   /* pre-accounting */
                NULL,                   /* accounting */