diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:49:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:49:46 +0000 |
commit | 50b37d4a27d3295a29afca2286f1a5a086142cec (patch) | |
tree | 9212f763934ee090ef72d823f559f52ce387f268 /src/modules/rlm_krb5/rlm_krb5.c | |
parent | Initial commit. (diff) | |
download | freeradius-50b37d4a27d3295a29afca2286f1a5a086142cec.tar.xz freeradius-50b37d4a27d3295a29afca2286f1a5a086142cec.zip |
Adding upstream version 3.2.1+dfsg.upstream/3.2.1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/modules/rlm_krb5/rlm_krb5.c')
-rw-r--r-- | src/modules/rlm_krb5/rlm_krb5.c | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/src/modules/rlm_krb5/rlm_krb5.c b/src/modules/rlm_krb5/rlm_krb5.c new file mode 100644 index 0000000..152e78b --- /dev/null +++ b/src/modules/rlm_krb5/rlm_krb5.c @@ -0,0 +1,470 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * @file rlm_krb5.c + * @brief Authenticate users, retrieving their TGT from a Kerberos V5 TDC. + * + * @copyright 2000,2006,2012-2013 The FreeRADIUS server project + * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org> + * @copyright 2000 Nathan Neulinger <nneul@umr.edu> + * @copyright 2000 Alan DeKok <aland@ox.org> + */ +RCSID("$Id$") + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/modules.h> +#include <freeradius-devel/rad_assert.h> +#include "krb5.h" + +static const CONF_PARSER module_config[] = { + { "keytab", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_krb5_t, keytabname), NULL }, + { "service_principal", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_krb5_t, service_princ), NULL }, + CONF_PARSER_TERMINATOR +}; + +static int mod_detach(void *instance) +{ + rlm_krb5_t *inst = instance; + +#ifndef HEIMDAL_KRB5 + talloc_free(inst->vic_options); + + if (inst->gic_options) krb5_get_init_creds_opt_free(inst->context, inst->gic_options); + if (inst->server) krb5_free_principal(inst->context, inst->server); +#endif + + /* Don't free hostname, it's just a pointer into service_princ */ + talloc_free(inst->service); + + if (inst->context) krb5_free_context(inst->context); +#ifdef KRB5_IS_THREAD_SAFE + fr_connection_pool_free(inst->pool); +#endif + + return 0; +} + +static int mod_instantiate(CONF_SECTION *conf, void *instance) +{ + rlm_krb5_t *inst = instance; + krb5_error_code ret; +#ifndef HEIMDAL_KRB5 + krb5_keytab keytab; + char keytab_name[200]; + char *princ_name; +#endif + +#ifdef HEIMDAL_KRB5 + DEBUG("Using Heimdal Kerberos library"); +#else + DEBUG("Using MIT Kerberos library"); +#endif + + if (!krb5_is_thread_safe()) { +/* + * rlm_krb5 was built as threadsafe + */ +#ifdef KRB5_IS_THREAD_SAFE + ERROR("Build time libkrb5 was threadsafe, but run time library claims not to be"); + ERROR("Modify runtime linker path (LD_LIBRARY_PATH on most systems), to prefer threadsafe libkrb5"); + return -1; +/* + * rlm_krb5 was not built as threadsafe + */ +#else + radlog(L_WARN, "libkrb5 is not threadsafe, recompile it with thread support enabled (" +# ifdef HEIMDAL_KRB5 + "--enable-pthread-support" +# else + "--disable-thread-support=no" +# endif + ")"); + WARN("rlm_krb5 will run in single threaded mode, performance may be degraded"); + } else { + WARN("Build time libkrb5 was not threadsafe, but run time library claims to be"); + WARN("Reconfigure and recompile rlm_krb5 to enable thread support"); +#endif + } + + inst->xlat_name = cf_section_name2(conf); + if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf); + + ret = krb5_init_context(&inst->context); + if (ret) { + ERROR("rlm_krb5 (%s): context initialisation failed: %s", inst->xlat_name, + rlm_krb5_error(NULL, ret)); + + return -1; + } + + /* + * Split service principal into service and host components + * they're needed to build the server principal in MIT, + * and to set the validation service in Heimdal. + */ + if (inst->service_princ) { + size_t len; + /* Service principal appears to contain a host component */ + inst->hostname = strchr(inst->service_princ, '/'); + if (inst->hostname) { + len = (inst->hostname - inst->service_princ); + inst->hostname++; + } else { + len = strlen(inst->service_princ); + } + + if (len) { + inst->service = talloc_array(inst, char, (len + 1)); + strlcpy(inst->service, inst->service_princ, len + 1); + } + } + +#ifdef HEIMDAL_KRB5 + if (inst->hostname) DEBUG("rlm_krb5 (%s): Ignoring hostname component of service principal \"%s\", not " + "needed/supported by Heimdal", inst->xlat_name, inst->hostname); +#else + + /* + * Convert the service principal string to a krb5 principal. + */ + ret = krb5_sname_to_principal(inst->context, inst->hostname, inst->service, KRB5_NT_SRV_HST, &(inst->server)); + if (ret) { + ERROR("rlm_krb5 (%s): Failed parsing service principal: %s", inst->xlat_name, + rlm_krb5_error(inst->context, ret)); + + return -1; + } + + ret = krb5_unparse_name(inst->context, inst->server, &princ_name); + if (ret) { + /* Uh? */ + ERROR("rlm_krb5 (%s): Failed constructing service principal string: %s", inst->xlat_name, + rlm_krb5_error(inst->context, ret)); + + return -1; + } + + /* + * Not necessarily the same as the config item + */ + DEBUG("rlm_krb5 (%s): Using service principal \"%s\"", inst->xlat_name, princ_name); + krb5_free_unparsed_name(inst->context, princ_name); + + /* + * Setup options for getting credentials and verifying them + */ + ret = krb5_get_init_creds_opt_alloc(inst->context, &(inst->gic_options)); /* For some reason the 'init' version + of this function is deprecated */ + if (ret) { + ERROR("rlm_krb5 (%s): Couldn't allocated initial credential options: %s", inst->xlat_name, + rlm_krb5_error(inst->context, ret)); + + return -1; + } + + /* + * Perform basic checks on the keytab + */ + ret = inst->keytabname ? + krb5_kt_resolve(inst->context, inst->keytabname, &keytab) : + krb5_kt_default(inst->context, &keytab); + if (ret) { + ERROR("rlm_krb5 (%s): Resolving keytab failed: %s", inst->xlat_name, + rlm_krb5_error(inst->context, ret)); + + return -1; + } + + ret = krb5_kt_get_name(inst->context, keytab, keytab_name, sizeof(keytab_name)); + krb5_kt_close(inst->context, keytab); + if (ret) { + ERROR("rlm_krb5 (%s): Can't retrieve keytab name: %s", inst->xlat_name, + rlm_krb5_error(inst->context, ret)); + + return -1; + } + + DEBUG("rlm_krb5 (%s): Using keytab \"%s\"", inst->xlat_name, keytab_name); + + MEM(inst->vic_options = talloc_zero(inst, krb5_verify_init_creds_opt)); + krb5_verify_init_creds_opt_init(inst->vic_options); +#endif + +#ifdef KRB5_IS_THREAD_SAFE + /* + * Initialize the socket pool. + */ + inst->pool = fr_connection_pool_module_init(conf, inst, mod_conn_create, NULL, NULL); + if (!inst->pool) return -1; +#else + inst->conn = mod_conn_create(inst, inst); + if (!inst->conn) return -1; +#endif + return 0; +} + +/** Common function for transforming a User-Name string into a principal. + * + * @param[out] client Where to write the client principal. + * @param[in] request Current request. + * @param[in] context Kerberos context. + */ +static rlm_rcode_t krb5_parse_user(krb5_principal *client, REQUEST *request, krb5_context context) +{ + krb5_error_code ret; + char *princ_name; + + /* + * We can only authenticate user requests which HAVE + * a User-Name attribute. + */ + if (!request->username) { + REDEBUG("Attribute \"User-Name\" is required for authentication"); + + return RLM_MODULE_INVALID; + } + + /* + * We can only authenticate user requests which HAVE + * a User-Password attribute. + */ + if (!request->password) { + REDEBUG("Attribute \"User-Password\" is required for authentication"); + + return RLM_MODULE_INVALID; + } + + /* + * Ensure that we're being passed a plain-text password, + * and not anything else. + */ + if (request->password->da->attr != PW_USER_PASSWORD) { + REDEBUG("Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", + request->password->da->name); + + return RLM_MODULE_INVALID; + } + + ret = krb5_parse_name(context, request->username->vp_strvalue, client); + if (ret) { + REDEBUG("Failed parsing username as principal: %s", rlm_krb5_error(context, ret)); + + return RLM_MODULE_FAIL; + } + + krb5_unparse_name(context, *client, &princ_name); + RDEBUG("Using client principal \"%s\"", princ_name); +#ifdef HEIMDAL_KRB5 + free(princ_name); +#else + krb5_free_unparsed_name(context, princ_name); +#endif + return RLM_MODULE_OK; +} + +/** Log error message and return appropriate rcode + * + * Translate kerberos error codes into return codes. + * @param request Current request. + * @param ret code from kerberos. + * @param conn used in the last operation. + */ +static rlm_rcode_t krb5_process_error(REQUEST *request, rlm_krb5_handle_t *conn, int ret) +{ + rad_assert(ret != 0); + rad_assert(conn); /* Silences warnings */ + + switch (ret) { + case KRB5_LIBOS_BADPWDMATCH: + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + REDEBUG("Provided password was incorrect (%i): %s", ret, rlm_krb5_error(conn->context, ret)); + return RLM_MODULE_REJECT; + + case KRB5KDC_ERR_KEY_EXP: + case KRB5KDC_ERR_CLIENT_REVOKED: + case KRB5KDC_ERR_SERVICE_REVOKED: + REDEBUG("Account has been locked out (%i): %s", ret, rlm_krb5_error(conn->context, ret)); + return RLM_MODULE_USERLOCK; + + case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: + RDEBUG("User not found (%i): %s", ret, rlm_krb5_error(conn->context, ret)); + return RLM_MODULE_NOTFOUND; + + default: + REDEBUG("Error verifying credentials (%i): %s", ret, rlm_krb5_error(conn->context, ret)); + return RLM_MODULE_FAIL; + } +} + +#ifdef HEIMDAL_KRB5 + +/* + * Validate user/pass (Heimdal) + */ +static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request) +{ + rlm_krb5_t *inst = instance; + rlm_rcode_t rcode; + krb5_error_code ret; + + rlm_krb5_handle_t *conn; + + krb5_principal client; + +# ifdef KRB5_IS_THREAD_SAFE + conn = fr_connection_get(inst->pool); + if (!conn) return RLM_MODULE_FAIL; +# else + conn = inst->conn; +# endif + + /* + * Zero out local storage + */ + memset(&client, 0, sizeof(client)); + + rcode = krb5_parse_user(&client, request, conn->context); + if (rcode != RLM_MODULE_OK) goto release; + + /* + * Verify the user, using the options we set in instantiate + */ + ret = krb5_verify_user_opt(conn->context, client, request->password->vp_strvalue, &conn->options); + if (ret) { + rcode = krb5_process_error(request, conn, ret); + goto cleanup; + } + + /* + * krb5_verify_user_opt adds the credentials to the ccache + * we specified with krb5_verify_opt_set_ccache. + * + * To make sure we don't accumulate thousands of sets of + * credentials, remove them again here. + * + * @todo This should definitely be optional, which means writing code for the MIT + * variant as well. + */ + { + krb5_cc_cursor cursor; + krb5_creds cred; + + krb5_cc_start_seq_get(conn->context, conn->ccache, &cursor); + for (ret = krb5_cc_next_cred(conn->context, conn->ccache, &cursor, &cred); + ret == 0; + ret = krb5_cc_next_cred(conn->context, conn->ccache, &cursor, &cred)) { + krb5_cc_remove_cred(conn->context, conn->ccache, 0, &cred); + } + krb5_cc_end_seq_get(conn->context, conn->ccache, &cursor); + } + +cleanup: + krb5_free_principal(conn->context, client); + +release: +# ifdef KRB5_IS_THREAD_SAFE + fr_connection_release(inst->pool, conn); +# endif + return rcode; +} + +#else /* HEIMDAL_KRB5 */ + +/* + * Validate userid/passwd (MIT) + */ +static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request) +{ + rlm_krb5_t *inst = instance; + rlm_rcode_t rcode; + krb5_error_code ret; + + rlm_krb5_handle_t *conn; + + krb5_principal client; + krb5_creds init_creds; + char *password; /* compiler warnings */ + + rad_assert(inst->context); + +# ifdef KRB5_IS_THREAD_SAFE + conn = fr_connection_get(inst->pool); + if (!conn) return RLM_MODULE_FAIL; +# else + conn = inst->conn; +# endif + + /* + * Zero out local storage + */ + memset(&client, 0, sizeof(client)); + memset(&init_creds, 0, sizeof(init_creds)); + + /* + * Check we have all the required VPs, and convert the username + * into a principal. + */ + rcode = krb5_parse_user(&client, request, conn->context); + if (rcode != RLM_MODULE_OK) goto release; + + /* + * Retrieve the TGT from the TGS/KDC and check we can decrypt it. + */ + memcpy(&password, &request->password->vp_strvalue, sizeof(password)); + RDEBUG("Retrieving and decrypting TGT"); + ret = krb5_get_init_creds_password(conn->context, &init_creds, client, password, + NULL, NULL, 0, NULL, inst->gic_options); + if (ret) { + rcode = krb5_process_error(request, conn, ret); + goto cleanup; + } + + RDEBUG("Attempting to authenticate against service principal"); + ret = krb5_verify_init_creds(conn->context, &init_creds, inst->server, conn->keytab, NULL, inst->vic_options); + if (ret) rcode = krb5_process_error(request, conn, ret); + +cleanup: + krb5_free_principal(conn->context, client); + krb5_free_cred_contents(conn->context, &init_creds); + +release: +# ifdef KRB5_IS_THREAD_SAFE + fr_connection_release(inst->pool, conn); +# endif + return rcode; +} + +#endif /* MIT_KRB5 */ + +extern module_t rlm_krb5; +module_t rlm_krb5 = { + .magic = RLM_MODULE_INIT, + .name = "krb5", + .type = RLM_TYPE_HUP_SAFE +#ifdef KRB5_IS_THREAD_SAFE + | RLM_TYPE_THREAD_SAFE +#endif + , + .inst_size = sizeof(rlm_krb5_t), + .config = module_config, + .instantiate = mod_instantiate, + .detach = mod_detach, + .methods = { + [MOD_AUTHENTICATE] = mod_authenticate + }, +}; |