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>
34 static const CONF_PARSER module_config[] = {
35 { "keytab", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_krb5_t, keytabname), NULL },
36 { "service_principal", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_krb5_t, service_princ), NULL },
37 { NULL, -1, 0, NULL, NULL }
40 static int mod_detach(void *instance)
42 rlm_krb5_t *inst = instance;
45 talloc_free(inst->vic_options);
47 if (inst->gic_options) {
48 krb5_get_init_creds_opt_free(inst->context, inst->gic_options);
52 krb5_free_principal(inst->context, inst->server);
56 /* Don't free hostname, it's just a pointer into service_princ */
57 talloc_free(inst->service);
60 krb5_free_context(inst->context);
62 #ifdef KRB5_IS_THREAD_SAFE
63 fr_connection_pool_delete(inst->pool);
69 static int mod_instantiate(CONF_SECTION *conf, void *instance)
71 rlm_krb5_t *inst = instance;
75 char keytab_name[200];
80 DEBUG("Using Heimdal Kerberos library");
82 DEBUG("Using MIT Kerberos library");
85 if (!krb5_is_thread_safe()) {
87 * rlm_krb5 was built as threadsafe
89 #ifdef KRB5_IS_THREAD_SAFE
90 ERROR("Build time libkrb5 was threadsafe, but run time library claims not to be");
91 ERROR("Modify runtime linker path (LD_LIBRARY_PATH on most systems), to prefer threadsafe libkrb5");
94 * rlm_krb5 was not built as threadsafe
97 WARN("libkrb5 is not threadsafe, recompile it with thread support enabled ("
99 "--enable-pthread-support"
101 "--disable-thread-support=no"
104 WARN("rlm_krb5 will run in single threaded mode, performance may be degraded");
106 WARN("Build time libkrb5 was not threadsafe, but run time library claims to be");
107 WARN("Reconfigure and recompile rlm_krb5 to enable thread support");
111 inst->xlat_name = cf_section_name2(conf);
112 if (!inst->xlat_name) {
113 inst->xlat_name = cf_section_name1(conf);
116 ret = krb5_init_context(&inst->context);
118 ERROR("rlm_krb5 (%s): context initialisation failed: %s", inst->xlat_name,
119 rlm_krb5_error(NULL, ret));
125 * Split service principal into service and host components
126 * they're needed to build the server principal in MIT,
127 * and to set the validation service in Heimdal.
129 if (inst->service_princ) {
131 /* Service principal appears to contain a host component */
132 inst->hostname = strchr(inst->service_princ, '/');
133 if (inst->hostname) {
134 len = (inst->hostname - inst->service_princ);
137 len = strlen(inst->service_princ);
141 inst->service = talloc_array(inst, char, (len + 1));
142 strlcpy(inst->service, inst->service_princ, len + 1);
147 if (inst->hostname) {
148 DEBUG("rlm_krb5 (%s): Ignoring hostname component of service principal \"%s\", not "
149 "needed/supported by Heimdal", inst->xlat_name, inst->hostname);
154 * Convert the service principal string to a krb5 principal.
156 ret = krb5_sname_to_principal(inst->context, inst->hostname, inst->service, KRB5_NT_SRV_HST, &(inst->server));
158 ERROR("rlm_krb5 (%s): Failed parsing service principal: %s", inst->xlat_name,
159 rlm_krb5_error(inst->context, ret));
164 ret = krb5_unparse_name(inst->context, inst->server, &princ_name);
167 ERROR("rlm_krb5 (%s): Failed constructing service principal string: %s", inst->xlat_name,
168 rlm_krb5_error(inst->context, ret));
174 * Not necessarily the same as the config item
176 DEBUG("rlm_krb5 (%s): Using service principal \"%s\"", inst->xlat_name, princ_name);
178 krb5_free_unparsed_name(inst->context, princ_name);
181 * Setup options for getting credentials and verifying them
184 /* For some reason the 'init' version of this function is deprecated */
185 ret = krb5_get_init_creds_opt_alloc(inst->context, &(inst->gic_options));
187 ERROR("rlm_krb5 (%s): Couldn't allocated inital credential options: %s", inst->xlat_name,
188 rlm_krb5_error(inst->context, ret));
194 * Perform basic checks on the keytab
196 ret = inst->keytabname ?
197 krb5_kt_resolve(inst->context, inst->keytabname, &keytab) :
198 krb5_kt_default(inst->context, &keytab);
200 ERROR("rlm_krb5 (%s): Resolving keytab failed: %s", inst->xlat_name,
201 rlm_krb5_error(inst->context, ret));
206 ret = krb5_kt_get_name(inst->context, keytab, keytab_name, sizeof(keytab_name));
207 krb5_kt_close(inst->context, keytab);
209 ERROR("rlm_krb5 (%s): Can't retrieve keytab name: %s", inst->xlat_name,
210 rlm_krb5_error(inst->context, ret));
215 DEBUG("rlm_krb5 (%s): Using keytab \"%s\"", inst->xlat_name, keytab_name);
217 MEM(inst->vic_options = talloc_zero(inst, krb5_verify_init_creds_opt));
218 krb5_verify_init_creds_opt_init(inst->vic_options);
221 #ifdef KRB5_IS_THREAD_SAFE
223 * Initialize the socket pool.
225 inst->pool = fr_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL);
230 inst->conn = mod_conn_create(inst, inst);
238 /** Common function for transforming a User-Name string into a principal.
240 * @param[out] client Where to write the client principal.
241 * @param[in] request Current request.
242 * @param[in] context Kerberos context.
244 static rlm_rcode_t krb5_parse_user(krb5_principal *client, REQUEST *request, krb5_context context)
250 * We can only authenticate user requests which HAVE
251 * a User-Name attribute.
253 if (!request->username) {
254 REDEBUG("Attribute \"User-Name\" is required for authentication");
256 return RLM_MODULE_INVALID;
260 * We can only authenticate user requests which HAVE
261 * a User-Password attribute.
263 if (!request->password) {
264 REDEBUG("Attribute \"User-Password\" is required for authentication");
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 REDEBUG("Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".",
275 request->password->da->name);
277 return RLM_MODULE_INVALID;
280 ret = krb5_parse_name(context, request->username->vp_strvalue, client);
282 REDEBUG("Failed parsing username as principal: %s", rlm_krb5_error(context, ret));
284 return RLM_MODULE_FAIL;
287 krb5_unparse_name(context, *client, &princ_name);
288 RDEBUG("Using client principal \"%s\"", princ_name);
292 krb5_free_unparsed_name(context, princ_name);
294 return RLM_MODULE_OK;
297 /** Log error message and return appropriate rcode
299 * Translate kerberos error codes into return codes.
300 * @param request Current request.
301 * @param ret code from kerberos.
302 * @param conn used in the last operation.
304 static rlm_rcode_t krb5_process_error(REQUEST *request, rlm_krb5_handle_t *conn, int ret)
306 rad_assert(ret != 0);
307 rad_assert(conn); /* Silences warnings */
310 case KRB5_LIBOS_BADPWDMATCH:
311 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
312 REDEBUG("Provided password was incorrect (%i): %s", ret, rlm_krb5_error(conn->context, ret));
313 return RLM_MODULE_REJECT;
315 case KRB5KDC_ERR_KEY_EXP:
316 case KRB5KDC_ERR_CLIENT_REVOKED:
317 case KRB5KDC_ERR_SERVICE_REVOKED:
318 REDEBUG("Account has been locked out (%i): %s", ret, rlm_krb5_error(conn->context, ret));
319 return RLM_MODULE_USERLOCK;
321 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
322 RDEBUG("User not found (%i): %s", ret, rlm_krb5_error(conn->context, ret));
323 return RLM_MODULE_NOTFOUND;
326 REDEBUG("Error verifying credentials (%i): %s", ret, rlm_krb5_error(conn->context, ret));
327 return RLM_MODULE_FAIL;
334 * Validate user/pass (Heimdal)
336 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
338 rlm_krb5_t *inst = instance;
342 rlm_krb5_handle_t *conn;
344 krb5_principal client;
346 #ifdef KRB5_IS_THREAD_SAFE
347 conn = fr_connection_get(inst->pool);
348 if (!conn) return RLM_MODULE_FAIL;
354 * Zero out local storage
356 memset(&client, 0, sizeof(client));
358 rcode = krb5_parse_user(&client, request, conn->context);
359 if (rcode != RLM_MODULE_OK) goto cleanup;
362 * Verify the user, using the options we set in instantiate
364 ret = krb5_verify_user_opt(conn->context, client, request->password->vp_strvalue, &conn->options);
366 rcode = krb5_process_error(request, conn, ret);
371 * krb5_verify_user_opt adds the credentials to the ccache
372 * we specified with krb5_verify_opt_set_ccache.
374 * To make sure we don't accumulate thousands of sets of
375 * credentials, remove them again here.
377 * @todo This should definitely be optional, which means writing code for the MIT
381 krb5_cc_cursor cursor;
384 krb5_cc_start_seq_get(conn->context, conn->ccache, &cursor);
385 for ((ret = krb5_cc_next_cred(conn->context, conn->ccache, &cursor, &cred));
387 (ret = krb5_cc_next_cred(conn->context, conn->ccache, &cursor, &cred))) {
388 krb5_cc_remove_cred(conn->context, conn->ccache, 0, &cred);
390 krb5_cc_end_seq_get(conn->context, conn->ccache, &cursor);
395 krb5_free_principal(conn->context, client);
398 #ifdef KRB5_IS_THREAD_SAFE
399 fr_connection_release(inst->pool, conn);
404 #else /* HEIMDAL_KRB5 */
407 * Validate userid/passwd (MIT)
409 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
411 rlm_krb5_t *inst = instance;
415 rlm_krb5_handle_t *conn;
417 krb5_principal client;
418 krb5_creds init_creds;
419 char *password; /* compiler warnings */
421 rad_assert(inst->context);
423 #ifdef KRB5_IS_THREAD_SAFE
424 conn = fr_connection_get(inst->pool);
425 if (!conn) return RLM_MODULE_FAIL;
431 * Zero out local storage
433 memset(&client, 0, sizeof(client));
434 memset(&init_creds, 0, sizeof(init_creds));
437 * Check we have all the required VPs, and convert the username
440 rcode = krb5_parse_user(&client, request, conn->context);
441 if (rcode != RLM_MODULE_OK) goto cleanup;
444 * Retrieve the TGT from the TGS/KDC and check we can decrypt it.
446 memcpy(&password, &request->password->vp_strvalue, sizeof(password));
447 RDEBUG("Retrieving and decrypting TGT");
448 ret = krb5_get_init_creds_password(conn->context, &init_creds, client, password,
449 NULL, NULL, 0, NULL, inst->gic_options);
451 rcode = krb5_process_error(request, conn, ret);
455 RDEBUG("Attempting to authenticate against service principal");
456 ret = krb5_verify_init_creds(conn->context, &init_creds, inst->server, conn->keytab, NULL, inst->vic_options);
458 rcode = krb5_process_error(request, conn, ret);
463 krb5_free_principal(conn->context, client);
465 krb5_free_cred_contents(conn->context, &init_creds);
467 #ifdef KRB5_IS_THREAD_SAFE
468 fr_connection_release(inst->pool, conn);
473 #endif /* MIT_KRB5 */
475 module_t rlm_krb5 = {
479 #ifdef KRB5_IS_THREAD_SAFE
480 | RLM_TYPE_THREAD_SAFE
485 mod_instantiate, /* instantiation */
486 mod_detach, /* detach */
488 mod_authenticate, /* authenticate */
489 NULL, /* authorize */
490 NULL, /* pre-accounting */
491 NULL, /* accounting */
492 NULL, /* checksimul */
493 NULL, /* pre-proxy */
494 NULL, /* post-proxy */