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>
27 #include <freeradius-devel/ident.h>
30 #include <freeradius-devel/radiusd.h>
31 #include <freeradius-devel/modules.h>
32 #include <freeradius-devel/rad_assert.h>
38 #define SERVICE_NAME_LEN 64 //!< Maximum length of a service name.
40 /** Instance configuration for rlm_krb5
42 * Holds the configuration and preparsed data for a instance of rlm_krb5.
44 typedef struct rlm_krb5_t {
45 const char *xlat_name; //!< This module's instance name.
46 const char *keytabname; //!< The keytab to resolve the service in.
47 const char *service_princ; //!< The service name provided by the
50 char *hostname; //!< The hostname component of
51 //!< service_princ, or NULL.
52 char *service; //!< The service component of
53 //!< service_princ, or NULL.
55 krb5_context *context; //!< The kerberos context (cloned once per
59 krb5_get_init_creds_opt *gic_options; //!< Options to pass to the
60 //!< get_initial_credentials.
62 krb5_verify_init_creds_opt *vic_options; //!< Options to pass to the
63 //!< validate_initial_creds
66 krb5_principal server; //!< A structure representing the parsed
72 static const CONF_PARSER module_config[] = {
73 { "keytab", PW_TYPE_STRING_PTR,
74 offsetof(rlm_krb5_t,keytabname), NULL, NULL },
75 { "service_principal", PW_TYPE_STRING_PTR,
76 offsetof(rlm_krb5_t,service_princ), NULL, NULL },
77 { NULL, -1, 0, NULL, NULL }
80 static int krb5_detach(void *instance)
82 rlm_krb5_t *inst = instance;
85 free(inst->vic_options);
87 if (inst->gic_options) {
88 krb5_get_init_creds_opt_free(*(inst->context),
93 /* Don't free hostname, it's just a pointer into service_princ */
97 krb5_free_context(*(inst->context));
105 static int krb5_instantiate(CONF_SECTION *conf, void **instance)
110 krb5_context *context;
114 radlog(L_ERR, "rlm_krb5 (*): Using MIT Kerberos library");
116 radlog(L_ERR, "rlm_krb5 (*): Using Heimdal Kerberos library");
119 * @todo Update configure script to call krb5_is_thread_safe,
120 * this should really be a warning.
122 if (!krb5_is_thread_safe()) {
123 radlog(L_ERR, "rlm_krb5 (*): krb5 library is not threadsafe, "
124 "please recompile it with thread support enabled");
129 *instance = inst = talloc_zero(conf, rlm_krb5_t);
130 if (cf_section_parse(conf, inst, module_config) < 0) {
134 inst->xlat_name = cf_section_name2(conf);
135 if (!inst->xlat_name) {
136 inst->xlat_name = cf_section_name1(conf);
139 context = inst->context = rad_calloc(sizeof(*context));
140 ret = krb5_init_context(context);
142 radlog(L_ERR, "rlm_krb5 (%s): Context initialisation "
143 "failed: %s", inst->xlat_name, error_message(ret));
147 radlog(L_DBG, "rlm_krb5 (%s): Context initialised "
148 "successfully", inst->xlat_name);
152 * Split service principal into service and host components
153 * they're needed to build the server principal in MIT,
154 * and to set the validation service in Heimdal.
156 if (inst->service_princ) {
158 /* Service principal appears to contain a host component */
159 inst->hostname = strchr(inst->service_princ, '/');
160 if (inst->hostname) {
161 len = (inst->hostname - inst->service_princ);
164 len = SERVICE_NAME_LEN;
168 inst->service = rad_malloc(len + 1);
169 strlcpy(inst->service, inst->service_princ, len);
175 * Convert the service principal string to a krb5 principal.
177 ret = krb5_sname_to_principal(*context, inst->hostname,
178 inst->service, KRB5_NT_SRV_HST,
181 radlog(L_ERR, "rlm_krb5 (%s): Failed parsing service "
182 "principal: %s", inst->xlat_name, error_message(ret));
187 ret = krb5_unparse_name(*context, inst->server, &princ_name);
190 radlog(L_ERR, "rlm_krb5 (%s): Failed constructing service "
191 "principal string: %s", inst->xlat_name,
198 * Not necessarily the same as the config item
200 radlog(L_DBG, "rlm_krb5 (%s): Using service principal \"%s\"",
201 inst->xlat_name, princ_name);
203 krb5_free_unparsed_name(*context, princ_name);
206 * Setup options for getting credentials and verifying them
209 /* For some reason the 'init' version of this function is deprecated */
210 ret = krb5_get_init_creds_opt_alloc(*context, &(inst->gic_options));
212 radlog(L_ERR, "rlm_krb5 (%s): Couldn't allocated inital "
213 "credential options: %s", inst->xlat_name,
219 inst->vic_options = rad_calloc(sizeof(*(inst->vic_options)));
220 if (!inst->vic_options) goto error;
222 krb5_verify_init_creds_opt_init(inst->vic_options);
223 krb5_verify_init_creds_opt_set_ap_req_nofail(inst->vic_options,
227 if (inst->hostname) {
228 radlog(L_DBG, "rlm_krb5 (%s): Ignoring hostname component of "
229 "service principal \"%s\", not needed/supported by "
230 "Heimdal", inst->xlat_name, hostname);
241 static rlm_rcode_t krb5_parse_user(rlm_krb5_t *inst, REQUEST *request,
242 krb5_principal *client)
248 * We can only authenticate user requests which HAVE
249 * a User-Name attribute.
251 if (!request->username) {
252 RDEBUG("Attribute \"User-Name\" is required for "
255 return RLM_MODULE_INVALID;
259 * We can only authenticate user requests which HAVE
260 * a User-Password attribute.
262 if (!request->password) {
263 RDEBUG("Attribute \"User-Password\" is required for "
266 return RLM_MODULE_INVALID;
270 * Ensure that we're being passed a plain-text password,
271 * and not anything else.
273 if (request->password->da->attr != PW_USER_PASSWORD) {
274 RDEBUG("Attribute \"User-Password\" is required for "
275 "authentication. Cannot use \"%s\".",
276 request->password->da->name);
278 return RLM_MODULE_INVALID;
281 ret = krb5_parse_name(*(inst->context), request->username->vp_strvalue,
284 RDEBUG("Failed parsing username as principal: %s",
287 return RLM_MODULE_FAIL;
290 krb5_unparse_name(*(inst->context), *client, &princ_name);
291 RDEBUG("Using client principal \"%s\"", princ_name);
292 krb5_free_unparsed_name(*(inst->context), princ_name);
294 return RLM_MODULE_OK;
300 * Validate userid/passwd (MIT)
302 static rlm_rcode_t krb5_auth(void *instance, REQUEST *request)
304 rlm_krb5_t *inst = instance;
308 krb5_principal client;
309 krb5_creds init_creds;
311 krb5_context *context = NULL;
314 * All the snippets on threadsafety say that individual threads
315 * must each use their own copy of context.
317 * As we don't have any per thread instantiation, we either have
318 * to clone inst->context on every request, or use the connection
321 * @todo Use the connection API (3.0 only).
323 ret = krb5_copy_context(*(inst->context), context);
325 radlog(L_ERR, "rlm_krb5 (%s): Error cloning krb5 context: %s",
326 inst->xlat_name, error_message(ret));
328 return RLM_MODULE_FAIL;
330 rad_assert(context != NULL); /* tell coverity copy context copies it */
333 * Check we have all the required VPs, and convert the username
336 rcode = krb5_parse_user(inst, request, &client);
337 if (rcode != RLM_MODULE_OK) goto cleanup;
340 * Retrieve the TGT from the TGS/KDC and check we can decrypt it.
342 memset(&init_creds, 0, sizeof(init_creds));
343 ret = krb5_get_init_creds_password(*context, &init_creds, client,
344 request->password->vp_strvalue,
350 case KRB5_LIBOS_BADPWDMATCH:
351 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
352 RDEBUG("Provided password was incorrect: %s",
354 rcode = RLM_MODULE_REJECT;
357 case KRB5KDC_ERR_KEY_EXP:
358 case KRB5KDC_ERR_CLIENT_REVOKED:
359 case KRB5KDC_ERR_SERVICE_REVOKED:
360 RDEBUG("Account has been locked out: %s",
362 rcode = RLM_MODULE_USERLOCK;
365 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
366 RDEBUG("User not found: %s", error_message(ret));
367 rcode = RLM_MODULE_NOTFOUND;
371 radlog(L_ERR, "rlm_krb5 (%s): Failed getting/verifying "
372 "credentials: %s", inst->xlat_name,
374 rcode = RLM_MODULE_FAIL;
381 RDEBUG("Successfully retrieved and decrypted TGT");
383 memset(&keytab, 0, sizeof(keytab));
384 ret = inst->keytabname ?
385 krb5_kt_resolve(*context, inst->keytabname, &keytab) :
386 krb5_kt_default(*context, &keytab);
388 radlog(L_ERR, "rlm_krb5 (%s): Resolving keytab failed: %s",
389 inst->xlat_name, error_message(ret));
394 ret = krb5_verify_init_creds(*context, &init_creds, inst->server,
395 keytab, NULL, inst->vic_options);
401 krb5_free_cred_contents(*context, &init_creds);
402 krb5_free_context(*context);
403 krb5_kt_close(*context, keytab);
412 * Validate user/pass (Heimdal)
414 static rlm_rcode_t krb5_auth(void *instance, REQUEST *request)
416 rlm_krb5_t *inst = instance;
421 krb5_principal client;
424 krb5_verify_opt options;
425 krb5_context *context = NULL;
428 * See above in MIT krb5_auth
430 ret = krb5_copy_context(*(inst->context), context);
432 radlog(L_ERR, "rlm_krb5 (%s): Error cloning krb5 context: %s",
433 inst->xlat_name, error_message(ret));
435 return RLM_MODULE_FAIL;
439 * Setup krb5_verify_user options
441 * Not entirely sure this is necessary, but as we use context
442 * to get the cache handle, we probably do have to do this with
443 * the cloned context.
445 krb5_cc_default(*context, &ccache);
447 krb5_verify_opt_init(&options);
448 krb5_verify_opt_set_ccache(&options, ccache);
450 memset(&keytab, 0, sizeof(keytab));
451 ret = inst->keytabname ?
452 krb5_kt_resolve(*context, inst->keytabname, &keytab) :
453 krb5_kt_default(*context, &keytab);
455 radlog(L_ERR, "rlm_krb5 (%s): Resolving keytab failed: %s",
456 inst->xlat_name, error_message(ret));
461 krb5_verify_opt_set_keytab(&options, keytab);
462 krb5_verify_opt_set_secure(&options, TRUE);
465 krb5_verify_opt_set_service(&options, inst->service);
468 rcode = krb5_parse_user(inst, request, &client);
469 if (rcode != RLM_MODULE_OK) goto cleanup;
472 * Verify the user, using the options we set in instantiate
474 ret = krb5_verify_user_opt(*context, client,
475 request->password->vp_strvalue,
479 case KRB5_LIBOS_BADPWDMATCH:
480 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
481 RDEBUG("Provided password was incorrect: %s",
483 rcode = RLM_MODULE_REJECT;
486 case KRB5KDC_ERR_KEY_EXP:
487 case KRB5KDC_ERR_CLIENT_REVOKED:
488 case KRB5KDC_ERR_SERVICE_REVOKED:
489 RDEBUG("Account has been locked out: %s",
491 rcode = RLM_MODULE_USERLOCK;
494 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
495 RDEBUG("User not found: %s", error_message(ret));
496 rcode = RLM_MODULE_NOTFOUND;
499 radlog(L_ERR, "rlm_krb5 (%s): Verifying user failed: "
500 "%s", inst->xlat_name, error_message(ret));
501 rcode = RLM_MODULE_FAIL;
511 krb5_free_context(*context);
512 krb5_kt_close(*context, keytab);
517 #endif /* HEIMDAL_KRB5 */
519 module_t rlm_krb5 = {
522 RLM_TYPE_THREAD_SAFE | RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,
523 krb5_instantiate, /* instantiation */
524 krb5_detach, /* detach */
526 krb5_auth, /* authenticate */
527 NULL, /* authorize */
528 NULL, /* pre-accounting */
529 NULL, /* accounting */
530 NULL, /* checksimul */
531 NULL, /* pre-proxy */
532 NULL, /* post-proxy */