2 * rlm_krb5.c module to authenticate against krb5
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2000,2006,2012 The FreeRADIUS server project
21 * Copyright 2000 Nathan Neulinger <nneul@umr.edu>
22 * Copyright 2000 Alan DeKok <aland@ox.org>
25 #include <freeradius-devel/ident.h>
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/modules.h>
35 /* Arbitrary 64char limit on service names */
36 #define SERVICE_NAME_LEN 64
38 typedef struct rlm_krb5_t {
40 const char *service_princ;
42 krb5_context *context;
45 static const CONF_PARSER module_config[] = {
46 { "keytab", PW_TYPE_STRING_PTR,
47 offsetof(rlm_krb5_t,keytab), NULL, NULL },
48 { "service_principal", PW_TYPE_STRING_PTR,
49 offsetof(rlm_krb5_t,service_princ), NULL, NULL },
50 { "cache", PW_TYPE_BOOLEAN,
51 offsetof(rlm_krb5_t,cache), NULL, "yes" },
52 { NULL, -1, 0, NULL, NULL }
57 static int krb5_build_auth_context(rlm_krb5_t *inst,
59 krb5_auth_context *auth_context)
64 ret = krb5_auth_con_init(context, auth_context);
68 ret = krb5_auth_con_getflags(context, *auth_context, &flags);
72 if (!inst->cache && (flags & KRB5_AUTH_CONTEXT_DO_TIME)) {
73 ret = krb5_auth_con_setflags(context, *auth_context, flags & ~KRB5_AUTH_CONTEXT_DO_TIME);
82 static int verify_krb5_tgt(krb5_context context, rlm_krb5_t *inst,
83 const char *user, krb5_ccache ccache)
89 krb5_keyblock *keyblock = 0;
90 krb5_data packet, *server;
91 krb5_auth_context auth_context = NULL;
94 char service[SERVICE_NAME_LEN] = "host";
95 char *server_name = NULL;
98 /* krb5_kt_read_service_key lacks const qualifier */
99 memcpy(&keytab_name, &inst->keytab, sizeof(keytab_name));
101 if (inst->service_princ != NULL) {
102 server_name = strchr(inst->service_princ, '/');
103 if (server_name != NULL) {
107 strlcpy(service, inst->service_princ, sizeof(service));
109 if (server_name != NULL) {
115 memset(&packet, 0, sizeof packet);
116 ret = krb5_sname_to_principal(context, server_name, service,
117 KRB5_NT_SRV_HST, &princ);
119 radlog(L_DBG, "rlm_krb5: [%s] krb5_sname_to_principal failed: %s",
120 user, error_message(ret));
122 return RLM_MODULE_REJECT;
125 server = krb5_princ_component(c, princ, 1);
127 radlog(L_DBG, "rlm_krb5: [%s] krb5_princ_component failed.",
130 return RLM_MODULE_REJECT;
133 strlcpy(phost, server->data, sizeof(phost));
136 * Do we have host/<host> keys?
137 * (use default/configured keytab, kvno IGNORE_VNO to get the
138 * first match, and enctype is currently ignored anyhow.)
140 ret = krb5_kt_read_service_key(context, keytab_name, princ, 0,
141 ENCTYPE_DES_CBC_MD5, &keyblock);
143 /* Keytab or service key does not exist */
144 radlog(L_DBG, "rlm_krb5: verify_krb_v5_tgt: host key not found : %s",
147 return RLM_MODULE_OK;
151 krb5_free_keyblock(context, keyblock);
154 * Talk to the kdc and construct the ticket.
156 ret = krb5_build_auth_context(inst, context, &auth_context);
158 radlog(L_DBG, "rlm_krb5: [%s] krb5_build_auth_context() failed: %s",
159 user, error_message(ret));
161 rcode = RLM_MODULE_REJECT;
165 ret = krb5_mk_req(context, &auth_context, 0, service, phost, NULL,
168 krb5_auth_con_free(context, auth_context);
169 auth_context = NULL; /* setup for rd_req */
173 radlog(L_DBG, "rlm_krb5: [%s] krb5_mk_req() failed: %s",
174 user, error_message(ret));
176 rcode = RLM_MODULE_REJECT;
180 if (keytab_name != NULL) {
181 ret = krb5_kt_resolve(context, keytab_name, &keytab);
184 if (keytab_name == NULL || ret) {
185 ret = krb5_kt_default(context, &keytab);
188 /* Hmm? The keytab was just fine a second ago! */
190 radlog(L_AUTH, "rlm_krb5: [%s] krb5_kt_resolve failed: %s",
191 user, error_message(ret));
193 rcode = RLM_MODULE_REJECT;
197 /* Try to use the ticket. */
198 ret = krb5_build_auth_context(inst, context, &auth_context);
200 radlog(L_DBG, "rlm_krb5: [%s] krb5_build_auth_context() failed: %s",
201 user, error_message(ret));
203 rcode = RLM_MODULE_REJECT;
207 ret = krb5_rd_req(context, &auth_context, &packet, princ,
210 krb5_auth_con_free(context, auth_context);
212 krb5_kt_close(context, keytab);
215 radlog(L_AUTH, "rlm_krb5: [%s] krb5_rd_req() failed: %s",
216 user, error_message(ret));
218 rcode = RLM_MODULE_REJECT;
220 rcode = RLM_MODULE_OK;
225 krb5_free_data_contents(context, &packet);
232 static int krb5_instantiate(CONF_SECTION *conf, void **instance)
236 krb5_context *context;
238 data = rad_malloc(sizeof(*data));
240 memset(data, 0, sizeof(*data));
242 if (cf_section_parse(conf, data, module_config) < 0) {
247 context = data->context = rad_malloc(sizeof(*context));
249 ret = krb5_init_context(context);
251 radlog(L_AUTH, "rlm_krb5: krb5_init failed: %s",
257 radlog(L_AUTH, "rlm_krb5: krb5_init ok");
265 static int krb5_detach(void *instance)
267 free(((rlm_krb5_t *)instance)->context);
274 * Validate userid/passwd (MIT)
277 static int krb5_auth(void *instance, REQUEST *request)
279 rlm_krb5_t *inst = instance;
282 static char tgs_name[] = KRB5_TGS_NAME;
283 krb5_data tgtname = {
292 /* MEMORY: + unsigned int (20) + NULL */
295 krb5_context context = *(inst->context); /* copy data */
296 const char *user, *pass;
299 * We can only authenticate user requests which HAVE
300 * a User-Name attribute.
302 if (!request->username) {
303 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
305 return RLM_MODULE_INVALID;
309 * We can only authenticate user requests which HAVE
310 * a User-Password attribute.
312 if (!request->password) {
313 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
315 return RLM_MODULE_INVALID;
319 * Ensure that we're being passed a plain-text password,
320 * and not anything else.
322 if (request->password->attribute != PW_USER_PASSWORD) {
323 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
325 return RLM_MODULE_INVALID;
328 user = request->username->vp_strvalue;
329 pass = request->password->vp_strvalue;
332 * Generate a unique cache_name.
334 snprintf(cache_name, sizeof(cache_name), "MEMORY:%u", request->number);
336 ret = krb5_cc_resolve(context, cache_name, &ccache);
338 radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_resolve(): %s",
339 user, error_message(ret));
341 return RLM_MODULE_REJECT;
345 * Actually perform the authentication.
347 memset((char *)&kcreds, 0, sizeof(kcreds));
349 ret = krb5_parse_name(context, user, &kcreds.client);
351 radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
352 user, error_message(ret));
354 return RLM_MODULE_REJECT;
357 ret = krb5_cc_initialize(context, ccache, kcreds.client);
359 radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_initialize(): %s",
360 user, error_message(ret));
362 return RLM_MODULE_REJECT;
366 * MIT krb5 verification.
368 ret = krb5_build_principal_ext(context, &kcreds.server,
369 krb5_princ_realm(context, kcreds.client)->length,
370 krb5_princ_realm(context, kcreds.client)->data,
373 krb5_princ_realm(context, kcreds.client)->length,
374 krb5_princ_realm(context, kcreds.client)->data,
377 radlog(L_AUTH, "rlm_krb5: [%s] krb5_build_principal_ext failed: %s",
378 user, error_message(ret));
379 krb5_cc_destroy(context, ccache);
381 return RLM_MODULE_REJECT;
384 ret = krb5_get_in_tkt_with_password(context, 0, NULL, NULL, NULL, pass,
387 radlog(L_AUTH, "rlm_krb5: [%s] krb5_g_i_t_w_p failed: %s",
388 user, error_message(ret));
389 krb5_free_cred_contents(context, &kcreds);
390 krb5_cc_destroy(context, ccache);
392 return RLM_MODULE_REJECT;
396 * Now verify the KDC's identity.
398 ret = verify_krb5_tgt(context, inst, user, ccache);
399 krb5_free_cred_contents(context, &kcreds);
400 krb5_cc_destroy(context, ccache);
405 #else /* HEIMDAL_KRB5 */
408 * validate user/pass (Heimdal)
410 static int krb5_auth(void *instance, REQUEST *request)
412 rlm_krb5_t *inst = instance;
416 krb5_principal userP;
418 krb5_context context = *(inst->context); /* copy data */
419 const char *user, *pass;
421 char service[SERVICE_NAME_LEN] = "host";
422 char *server_name = NULL;
425 krb5_verify_opt krb_verify_options;
428 if (inst->service_princ != NULL) {
429 server_name = strchr(inst->service_princ, '/');
430 if (server_name != NULL) {
434 strlcpy(service, inst->service_princ, sizeof(service));
435 if (server_name != NULL) {
442 * We can only authenticate user requests which HAVE
443 * a User-Name attribute.
445 if (!request->username) {
446 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
448 return RLM_MODULE_INVALID;
452 * We can only authenticate user requests which HAVE
453 * a User-Password attribute.
455 if (!request->password) {
456 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
458 return RLM_MODULE_INVALID;
462 * Ensure that we're being passed a plain-text password,
463 * and not anything else.
465 if (request->password->attribute != PW_USER_PASSWORD) {
466 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
468 return RLM_MODULE_INVALID;
471 user = request->username->vp_strvalue;
472 pass = request->password->vp_strvalue;
474 ret = krb5_parse_name(context, user, &userP);
476 radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
477 user, error_message(ret));
479 return RLM_MODULE_REJECT;
483 * Heimdal krb5 verification.
487 * The following bit allows us to also log user/instance@REALM if someone
488 * logs in using an instance.
490 ret = krb5_unparse_name(context, userP, &princ_name);
492 radlog(L_AUTH, "rlm_krb5: Unparsable name");
494 radlog(L_AUTH, "rlm_krb5: Parsed name is: %s", princ_name);
498 krb5_cc_default(context, &id);
501 * Set up krb5_verify_user options.
503 krb5_verify_opt_init(&krb_verify_options);
504 krb5_verify_opt_set_ccache(&krb_verify_options, id);
507 * Resolve keytab name. This allows us to use something other than
508 * the default system keytab
510 if (inst->keytab != NULL) {
511 ret = krb5_kt_resolve(context, inst->keytab, &keytab);
513 radlog(L_AUTH, "rlm_krb5: unable to resolve keytab %s: %s",
514 inst->keytab, error_message(ret));
515 krb5_kt_close(context, keytab);
517 return RLM_MODULE_REJECT;
520 krb5_verify_opt_set_keytab(&krb_verify_options, keytab);
524 * Verify aquired credentials against the keytab.
526 krb5_verify_opt_set_secure(&krb_verify_options, 1);
529 * Allow us to use an arbitrary service name.
531 krb5_verify_opt_set_service(&krb_verify_options, service);
534 * Verify the user, using the above set options.
536 ret = krb5_verify_user_opt(context, userP, pass, &krb_verify_options);
539 * We are done with the keytab, close it.
541 krb5_kt_close(context, keytab);
544 return RLM_MODULE_OK;
546 radlog(L_AUTH, "rlm_krb5: failed verify_user: %s (%s@%s)",
548 *userP->name.name_string.val,
551 return RLM_MODULE_REJECT;
554 #endif /* HEIMDAL_KRB5 */
556 module_t rlm_krb5 = {
559 RLM_TYPE_THREAD_UNSAFE, /* type: not thread safe */
560 krb5_instantiate, /* instantiation */
561 krb5_detach, /* detach */
563 krb5_auth, /* authenticate */
564 NULL, /* authorize */
565 NULL, /* pre-accounting */
566 NULL, /* accounting */
567 NULL, /* checksimul */
568 NULL, /* pre-proxy */
569 NULL, /* post-proxy */