2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * @brief Authenticate users, retrieving their TGT from a Kerberos V5 TDC.
22 * @copyright 2000,2006,2012-2013 The FreeRADIUS server project
23 * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
24 * @copyright 2000 Nathan Neulinger <nneul@umr.edu>
25 * @copyright 2000 Alan DeKok <aland@ox.org>
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/modules.h>
31 #include <freeradius-devel/rad_assert.h>
37 # include <et/com_err.h>
43 * Work around bug in krb5_copy_context which attempts to copy the list
44 * of tgs_kytpes and tkt_ktypes associated with a context... except by
45 * default the pointers to those lists are NULL, and so it SEGVs
47 * The functions sigs below are not provided by krb5.h, but are available
50 #if !defined(HEIMDAL_KRB5) && KRB5_IS_THREAD_SAFE
52 krb5_set_default_in_tkt_ktypes(krb5_context context, const krb5_enctype *etypes);
55 krb5_get_default_in_tkt_ktypes(krb5_context context, krb5_enctype **ktypes);
58 krb5_get_tgs_ktypes(krb5_context context, krb5_const_principal princ, krb5_enctype **ktypes);
61 krb5_set_default_tgs_ktypes(krb5_context context, const krb5_enctype *etypes);
64 krb5_free_ktypes(krb5_context context, krb5_enctype *val);
67 /** Instance configuration for rlm_krb5
69 * Holds the configuration and preparsed data for a instance of rlm_krb5.
71 typedef struct rlm_krb5_t {
72 char const *xlat_name; //!< This module's instance name.
73 char const *keytabname; //!< The keytab to resolve the service in.
74 char const *service_princ; //!< The service name provided by the
77 char *hostname; //!< The hostname component of
78 //!< service_princ, or NULL.
79 char *service; //!< The service component of service_princ, or NULL.
81 krb5_context context; //!< The kerberos context (cloned once per request).
84 krb5_get_init_creds_opt *gic_options; //!< Options to pass to the get_initial_credentials
86 krb5_verify_init_creds_opt *vic_options; //!< Options to pass to the validate_initial_creds
89 krb5_principal server; //!< A structure representing the parsed
95 static const CONF_PARSER module_config[] = {
96 { "keytab", PW_TYPE_STRING_PTR, offsetof(rlm_krb5_t, keytabname), NULL, NULL },
97 { "service_principal", PW_TYPE_STRING_PTR, offsetof(rlm_krb5_t,service_princ), NULL, NULL },
98 { NULL, -1, 0, NULL, NULL }
101 static int krb5_detach(void *instance)
103 rlm_krb5_t *inst = instance;
106 talloc_free(inst->vic_options);
108 if (inst->gic_options) {
109 krb5_get_init_creds_opt_free(inst->context, inst->gic_options);
113 krb5_free_principal(inst->context, inst->server);
117 /* Don't free hostname, it's just a pointer into service_princ */
118 talloc_free(inst->service);
121 krb5_free_context(inst->context);
127 static int krb5_instantiate(CONF_SECTION *conf, void *instance)
129 rlm_krb5_t *inst = instance;
133 char keytab_name[200];
138 DEBUG("Using Heimdal Kerberos library");
140 DEBUG("Using MIT Kerberos library");
143 #ifndef KRB5_IS_THREAD_SAFE
144 if (!krb5_is_thread_safe()) {
145 DEBUGI("libkrb5 is not threadsafe, recompile it with thread support enabled");
146 WDEBUG("rlm_krb5 will run in single threaded mode, performance may be degraded");
148 WDEBUG("Build time libkrb5 was not threadsafe, but run time library claims to be");
149 WDEBUG("Reconfigure and recompile rlm_krb5 to enable thread support");
153 inst->xlat_name = cf_section_name2(conf);
154 if (!inst->xlat_name) {
155 inst->xlat_name = cf_section_name1(conf);
158 ret = krb5_init_context(&inst->context);
160 EDEBUG("rlm_krb5 (%s): Context initialisation failed: %s", inst->xlat_name, error_message(ret));
165 DEBUG("rlm_krb5 (%s): Context initialised successfully", inst->xlat_name);
168 * Split service principal into service and host components
169 * they're needed to build the server principal in MIT,
170 * and to set the validation service in Heimdal.
172 if (inst->service_princ) {
174 /* Service principal appears to contain a host component */
175 inst->hostname = strchr(inst->service_princ, '/');
176 if (inst->hostname) {
177 len = (inst->hostname - inst->service_princ);
180 len = strlen(inst->service_princ);
184 inst->service = talloc_array(inst, char, (len + 1));
185 strlcpy(inst->service, inst->service_princ, len + 1);
190 if (inst->hostname) {
191 DEBUG("rlm_krb5 (%s): Ignoring hostname component of service principal \"%s\", not "
192 "needed/supported by Heimdal", inst->xlat_name, inst->hostname);
197 * Convert the service principal string to a krb5 principal.
199 ret = krb5_sname_to_principal(inst->context, inst->hostname, inst->service, KRB5_NT_SRV_HST, &(inst->server));
201 EDEBUG("rlm_krb5 (%s): Failed parsing service principal: %s", inst->xlat_name, error_message(ret));
206 ret = krb5_unparse_name(inst->context, inst->server, &princ_name);
209 EDEBUG("rlm_krb5 (%s): Failed constructing service principal string: %s", inst->xlat_name,
216 * Not necessarily the same as the config item
218 DEBUG("rlm_krb5 (%s): Using service principal \"%s\"", inst->xlat_name, princ_name);
220 krb5_free_unparsed_name(inst->context, princ_name);
223 * Setup options for getting credentials and verifying them
226 /* For some reason the 'init' version of this function is deprecated */
227 ret = krb5_get_init_creds_opt_alloc(inst->context, &(inst->gic_options));
229 EDEBUG("rlm_krb5 (%s): Couldn't allocated inital credential options: %s", inst->xlat_name,
236 * Perform basic checks on the keytab
238 ret = inst->keytabname ?
239 krb5_kt_resolve(inst->context, inst->keytabname, &keytab) :
240 krb5_kt_default(inst->context, &keytab);
242 EDEBUG("rlm_krb5 (%s): Resolving keytab failed: %s", inst->xlat_name, error_message(ret));
247 ret = krb5_kt_get_name(inst->context, keytab, keytab_name, sizeof(keytab_name));
248 krb5_kt_close(inst->context, keytab);
250 EDEBUG("rlm_krb5 (%s): Can't retrieve keytab name: %s", inst->xlat_name, error_message(ret));
255 DEBUG("rlm_krb5 (%s): Using keytab \"%s\"", inst->xlat_name, keytab_name);
257 MEM(inst->vic_options = talloc_zero(inst, krb5_verify_init_creds_opt));
259 krb5_verify_init_creds_opt_init(inst->vic_options);
260 krb5_verify_init_creds_opt_set_ap_req_nofail(inst->vic_options, true);
262 # ifdef KRB5_IS_THREAD_SAFE
264 * Explicitly set enctypes to work around bug in krb5_copy_context
267 krb5_enctype *enctypes;
269 krb5_get_default_in_tkt_ktypes(inst->context, &enctypes);
270 krb5_set_default_in_tkt_ktypes(inst->context, enctypes);
271 krb5_free_ktypes(inst->context, enctypes);
273 krb5_get_tgs_ktypes(inst->context, inst->server, &enctypes);
274 krb5_set_default_tgs_ktypes(inst->context, enctypes);
275 krb5_free_ktypes(inst->context, enctypes);
283 static rlm_rcode_t krb5_parse_user(REQUEST *request, krb5_context context, krb5_principal *client)
289 * We can only authenticate user requests which HAVE
290 * a User-Name attribute.
292 if (!request->username) {
293 REDEBUG("Attribute \"User-Name\" is required for authentication");
295 return RLM_MODULE_INVALID;
299 * We can only authenticate user requests which HAVE
300 * a User-Password attribute.
302 if (!request->password) {
303 REDEBUG("Attribute \"User-Password\" is required for authentication");
305 return RLM_MODULE_INVALID;
309 * Ensure that we're being passed a plain-text password,
310 * and not anything else.
312 if (request->password->da->attr != PW_USER_PASSWORD) {
313 REDEBUG("Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".",
314 request->password->da->name);
316 return RLM_MODULE_INVALID;
319 ret = krb5_parse_name(context, request->username->vp_strvalue, client);
321 REDEBUG("Failed parsing username as principal: %s", error_message(ret));
323 return RLM_MODULE_FAIL;
326 krb5_unparse_name(context, *client, &princ_name);
327 RDEBUG("Using client principal \"%s\"", princ_name);
331 krb5_free_unparsed_name(context, princ_name);
333 return RLM_MODULE_OK;
339 * Validate user/pass (Heimdal)
341 static rlm_rcode_t krb5_auth(void *instance, REQUEST *request)
343 rlm_krb5_t *inst = instance;
348 krb5_principal client;
351 krb5_verify_opt options;
352 krb5_context context;
354 rad_assert(inst->context);
356 #ifdef KRB5_IS_THREAD_SAFE
358 * See above in MIT krb5_auth
360 ret = krb5_copy_context(inst->context, &context);
362 REDEBUG("Error cloning krb5 context: %s", error_message(ret));
364 return RLM_MODULE_FAIL;
367 context = inst->context;
371 * Zero out local storage
373 memset(&keytab, 0, sizeof(keytab));
374 memset(&client, 0, sizeof(client));
377 * Setup krb5_verify_user options
379 * Not entirely sure this is necessary, but as we use context
380 * to get the cache handle, we probably do have to do this with
381 * the cloned context.
383 krb5_cc_default(context, &ccache);
385 krb5_verify_opt_init(&options);
386 krb5_verify_opt_set_ccache(&options, ccache);
387 ret = inst->keytabname ?
388 krb5_kt_resolve(context, inst->keytabname, &keytab) :
389 krb5_kt_default(context, &keytab);
391 REDEBUG("Resolving keytab failed: %s", error_message(ret));
392 rcode = RLM_MODULE_FAIL;
397 krb5_verify_opt_set_keytab(&options, keytab);
398 krb5_verify_opt_set_secure(&options, true);
401 krb5_verify_opt_set_service(&options, inst->service);
404 rcode = krb5_parse_user(request, context, &client);
405 if (rcode != RLM_MODULE_OK) goto cleanup;
408 * Verify the user, using the options we set in instantiate
410 ret = krb5_verify_user_opt(context, client, request->password->vp_strvalue, &options);
413 case KRB5_LIBOS_BADPWDMATCH:
414 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
415 REDEBUG("Provided password was incorrect (%i): %s", ret, error_message(ret));
416 rcode = RLM_MODULE_REJECT;
419 case KRB5KDC_ERR_KEY_EXP:
420 case KRB5KDC_ERR_CLIENT_REVOKED:
421 case KRB5KDC_ERR_SERVICE_REVOKED:
422 REDEBUG("Account has been locked out (%i): %s", ret, error_message(ret));
423 rcode = RLM_MODULE_USERLOCK;
426 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
427 RDEBUG("User not found: %s (%i)", ret, error_message(ret));
428 rcode = RLM_MODULE_NOTFOUND;
431 REDEBUG("Error verifying credentials (%i): %s", ret, error_message(ret));
432 rcode = RLM_MODULE_FAIL;
442 krb5_free_principal(context, client);
445 krb5_kt_close(context, keytab);
447 #ifdef KRB5_IS_THREAD_SAFE
448 krb5_free_context(context);
453 #else /* HEIMDAL_KRB5 */
456 * Validate userid/passwd (MIT)
458 static rlm_rcode_t krb5_auth(void *instance, REQUEST *request)
460 rlm_krb5_t *inst = instance;
464 krb5_principal client;
465 krb5_creds init_creds;
466 krb5_keytab keytab; /* ktid */
467 krb5_context context;
468 char *password; /* compiler warnings */
470 rad_assert(inst->context);
472 #ifdef KRB5_IS_THREAD_SAFE
474 * All the snippets on threadsafety say that individual threads
475 * must each use their own copy of context.
477 * As we don't have any per thread instantiation, we either have
478 * to clone inst->context on every request, or use the connection
481 * @todo Use the connection API (3.0 only).
483 ret = krb5_copy_context(inst->context, &context);
485 REDEBUG("Error cloning krb5 context: %s", error_message(ret));
487 return RLM_MODULE_FAIL;
489 rad_assert(context != NULL); /* tell coverity copy context copies it */
491 context = inst->context;
495 * Zero out local storage
497 memset(&keytab, 0, sizeof(keytab));
498 memset(&client, 0, sizeof(client));
499 memset(&init_creds, 0, sizeof(init_creds));
502 * Check we have all the required VPs, and convert the username
505 rcode = krb5_parse_user(request, context, &client);
506 if (rcode != RLM_MODULE_OK) goto cleanup;
511 ret = inst->keytabname ?
512 krb5_kt_resolve(context, inst->keytabname, &keytab) :
513 krb5_kt_default(context, &keytab);
515 REDEBUG("Resolving keytab failed: %s", error_message(ret));
521 * Retrieve the TGT from the TGS/KDC and check we can decrypt it.
523 memcpy(&password, &request->password->vp_strvalue, sizeof(password));
524 ret = krb5_get_init_creds_password(context, &init_creds, client, password,
525 NULL, NULL, 0, NULL, inst->gic_options);
530 case KRB5_LIBOS_BADPWDMATCH:
531 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
532 REDEBUG("Provided password was incorrect (%i): %s", ret, error_message(ret));
533 rcode = RLM_MODULE_REJECT;
536 case KRB5KDC_ERR_KEY_EXP:
537 case KRB5KDC_ERR_CLIENT_REVOKED:
538 case KRB5KDC_ERR_SERVICE_REVOKED:
539 REDEBUG("Account has been locked out (%i): %s", ret, error_message(ret));
540 rcode = RLM_MODULE_USERLOCK;
543 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
544 REDEBUG("User not found (%i): %s", ret, error_message(ret));
545 rcode = RLM_MODULE_NOTFOUND;
549 REDEBUG("Error retrieving or verifying credentials (%i): %s", ret, error_message(ret));
550 rcode = RLM_MODULE_FAIL;
557 RDEBUG("Successfully retrieved and decrypted TGT");
559 ret = krb5_verify_init_creds(context, &init_creds, inst->server, keytab, NULL, inst->vic_options);
566 krb5_free_principal(context, client);
569 krb5_kt_close(context, keytab);
572 krb5_free_cred_contents(context, &init_creds);
574 #ifdef KRB5_IS_THREAD_SAFE
575 krb5_free_context(context);
582 #endif /* MIT_KRB5 */
584 module_t rlm_krb5 = {
587 RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE
588 #ifdef KRB5_IS_THREAD_SAFE
589 | RLM_TYPE_THREAD_SAFE
594 krb5_instantiate, /* instantiation */
595 krb5_detach, /* detach */
597 krb5_auth, /* authenticate */
598 NULL, /* authorize */
599 NULL, /* pre-accounting */
600 NULL, /* accounting */
601 NULL, /* checksimul */
602 NULL, /* pre-proxy */
603 NULL, /* post-proxy */