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 The FreeRADIUS server project
21 * Copyright 2000 Nathan Neulinger <nneul@umr.edu>
22 * Copyright 2000 Alan DeKok <aland@ox.org>
26 static const char rcsid[] = "$Id$";
28 #include <freeradius-devel/autoconf.h>
34 #include <freeradius-devel/radiusd.h>
35 #include <freeradius-devel/modules.h>
41 typedef struct rlm_krb5_t {
43 const char *service_princ;
44 krb5_context *context;
47 static const CONF_PARSER module_config[] = {
48 { "keytab", PW_TYPE_STRING_PTR,
49 offsetof(rlm_krb5_t,keytab), NULL, NULL },
50 { "service_principal", PW_TYPE_STRING_PTR,
51 offsetof(rlm_krb5_t,service_princ), NULL, NULL },
52 { NULL, -1, 0, NULL, NULL }
56 static int verify_krb5_tgt(krb5_context context, rlm_krb5_t *instance,
57 const char *user, krb5_ccache ccache)
62 krb5_keyblock *keyblock = 0;
64 krb5_auth_context auth_context = NULL;
66 /* arbitrary 64-byte limit on service names; I've never seen a
67 service name this long, and hope never to. -srl */
68 char service[64] = "host";
69 char *servername = NULL;
71 if (instance->service_princ != NULL) {
72 servername = strchr(instance->service_princ, '/');
73 if (servername != NULL) {
77 strncpy(service,instance->service_princ,sizeof(service));
78 service[sizeof(service)-1] = '\0';
80 if (servername != NULL) {
86 memset(&packet, 0, sizeof packet);
87 if ((r = krb5_sname_to_principal(context, servername, service,
88 KRB5_NT_SRV_HST, &princ)))
90 radlog(L_DBG, "rlm_krb5: [%s] krb5_sname_to_principal failed: %s",
91 user, error_message(r));
92 return RLM_MODULE_REJECT;
95 strncpy(phost, krb5_princ_component(c, princ, 1)->data, BUFSIZ);
96 phost[BUFSIZ - 1] = '\0';
99 * Do we have host/<host> keys?
100 * (use default/configured keytab, kvno IGNORE_VNO to get the
101 * first match, and enctype is currently ignored anyhow.)
103 if ((r = krb5_kt_read_service_key(context, instance->keytab, princ, 0,
104 ENCTYPE_DES_CBC_MD5, &keyblock)))
106 /* Keytab or service key does not exist */
107 radlog(L_DBG, "rlm_krb5: verify_krb_v5_tgt: host key not found : %s",
109 return RLM_MODULE_OK;
112 krb5_free_keyblock(context, keyblock);
114 /* Talk to the kdc and construct the ticket. */
115 r = krb5_mk_req(context, &auth_context, 0, service, phost, NULL,
118 krb5_auth_con_free(context, auth_context);
119 auth_context = NULL; /* setup for rd_req */
123 radlog(L_DBG, "rlm_krb5: [%s] krb5_mk_req() failed: %s",
124 user, error_message(r));
125 r = RLM_MODULE_REJECT;
129 if (instance->keytab != NULL) {
130 r = krb5_kt_resolve(context, instance->keytab, &keytab);
133 if (instance->keytab == NULL || r) {
134 r = krb5_kt_default(context, &keytab);
137 /* Hmm? The keytab was just fine a second ago! */
139 radlog(L_AUTH, "rlm_krb5: [%s] krb5_kt_resolve failed: %s",
140 user, error_message(r));
141 r = RLM_MODULE_REJECT;
145 /* Try to use the ticket. */
146 r = krb5_rd_req(context, &auth_context, &packet, princ,
149 krb5_auth_con_free(context, auth_context);
151 krb5_kt_close(context, keytab);
154 radlog(L_AUTH, "rlm_krb5: [%s] krb5_rd_req() failed: %s",
155 user, error_message(r));
156 r = RLM_MODULE_REJECT;
163 krb5_free_data_contents(context, &packet);
169 static int krb5_instantiate(CONF_SECTION *conf, void **instance)
173 krb5_context *context;
175 data = rad_malloc(sizeof(*data));
177 memset(data, 0, sizeof(*data));
179 if (cf_section_parse(conf, data, module_config) < 0) {
184 context = data->context = rad_malloc(sizeof(*context));
186 if ((r = krb5_init_context(context)) ) {
187 radlog(L_AUTH, "rlm_krb5: krb5_init failed: %s",
192 radlog(L_AUTH, "rlm_krb5: krb5_init ok");
200 static int krb5_detach(void *instance)
202 free(((rlm_krb5_t *)instance)->context);
207 /* validate userid/passwd */
210 static int krb5_auth(void *instance, REQUEST *request)
214 krb5_data tgtname = {
221 char cache_name[L_tmpnam + 8];
223 krb5_context context = *((rlm_krb5_t *)instance)->context; /* copy data */
224 const char *user, *pass;
227 * We can only authenticate user requests which HAVE
228 * a User-Name attribute.
230 if (!request->username) {
231 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
232 return RLM_MODULE_INVALID;
236 * We can only authenticate user requests which HAVE
237 * a User-Password attribute.
239 if (!request->password) {
240 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
241 return RLM_MODULE_INVALID;
245 * Ensure that we're being passed a plain-text password,
246 * and not anything else.
248 if (request->password->attribute != PW_PASSWORD) {
249 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
250 return RLM_MODULE_INVALID;
256 user = request->username->vp_strvalue;
257 pass = request->password->vp_strvalue;
259 /* Generate a unique cache_name */
260 memset(cache_name, 0, sizeof(cache_name));
261 strcpy(cache_name, "MEMORY:");
262 (void) tmpnam(&cache_name[7]);
264 if ((r = krb5_cc_resolve(context, cache_name, &ccache))) {
265 radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_resolve(): %s",
266 user, error_message(r));
267 return RLM_MODULE_REJECT;
271 * Actually perform the authentication
273 memset((char *)&kcreds, 0, sizeof(kcreds));
275 if ( (r = krb5_parse_name(context, user, &kcreds.client)) ) {
276 radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
277 user, error_message(r));
278 return RLM_MODULE_REJECT;
281 if ((r = krb5_cc_initialize(context, ccache, kcreds.client))) {
282 radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_initialize(): %s",
283 user, error_message(r));
284 return RLM_MODULE_REJECT;
288 * MIT krb5 verification
290 if ( (r = krb5_build_principal_ext(context, &kcreds.server,
291 krb5_princ_realm(context, kcreds.client)->length,
292 krb5_princ_realm(context, kcreds.client)->data,
295 krb5_princ_realm(context, kcreds.client)->length,
296 krb5_princ_realm(context, kcreds.client)->data,
298 radlog(L_AUTH, "rlm_krb5: [%s] krb5_build_principal_ext failed: %s",
299 user, error_message(r));
300 krb5_cc_destroy(context, ccache);
301 return RLM_MODULE_REJECT;
304 if ( (r = krb5_get_in_tkt_with_password(context,
305 0, NULL, NULL, NULL, pass, ccache, &kcreds, 0)) ) {
306 radlog(L_AUTH, "rlm_krb5: [%s] krb5_g_i_t_w_p failed: %s",
307 user, error_message(r));
308 krb5_free_cred_contents(context, &kcreds);
309 krb5_cc_destroy(context, ccache);
310 return RLM_MODULE_REJECT;
312 /* Now verify the KDC's identity. */
313 r = verify_krb5_tgt(context, (rlm_krb5_t *)instance, user, ccache);
314 krb5_free_cred_contents(context, &kcreds);
315 krb5_cc_destroy(context, ccache);
319 return RLM_MODULE_REJECT;
322 #else /* HEIMDAL_KRB5 */
324 /* validate user/pass, heimdal krb5 way */
325 static int krb5_auth(void *instance, REQUEST *request)
330 krb5_principal userP;
332 krb5_context context = *((rlm_krb5_t *)instance)->context; /* copy data */
333 const char *user, *pass;
336 * We can only authenticate user requests which HAVE
337 * a User-Name attribute.
339 if (!request->username) {
340 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
341 return RLM_MODULE_INVALID;
345 * We can only authenticate user requests which HAVE
346 * a User-Password attribute.
348 if (!request->password) {
349 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
350 return RLM_MODULE_INVALID;
354 * Ensure that we're being passed a plain-text password,
355 * and not anything else.
357 if (request->password->attribute != PW_PASSWORD) {
358 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
359 return RLM_MODULE_INVALID;
365 user = request->username->vp_strvalue;
366 pass = request->password->vp_strvalue;
368 if ( (r = krb5_parse_name(context, user, &userP)) ) {
369 radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
370 user, error_message(r));
371 return RLM_MODULE_REJECT;
375 * Heimdal krb5 verification
377 radlog(L_AUTH, "rlm_krb5: Parsed name is: %s@%s\n",
378 *userP->name.name_string.val,
381 krb5_cc_default(context, &id);
383 ret = krb5_verify_user(context,
389 return RLM_MODULE_OK;
391 radlog(L_AUTH, "rlm_krb5: failed verify_user: %s (%s@%s )",
393 *userP->name.name_string.val,
396 return RLM_MODULE_REJECT;
399 #endif /* HEIMDAL_KRB5 */
401 module_t rlm_krb5 = {
404 RLM_TYPE_THREAD_UNSAFE, /* type: not thread safe */
405 krb5_instantiate, /* instantiation */
406 krb5_detach, /* detach */
408 krb5_auth, /* authenticate */
409 NULL, /* authorize */
410 NULL, /* pre-accounting */
411 NULL, /* accounting */
412 NULL, /* checksimul */
413 NULL, /* pre-proxy */
414 NULL, /* post-proxy */