diff options
Diffstat (limited to 'src/providers/krb5/krb5_common.c')
-rw-r--r-- | src/providers/krb5/krb5_common.c | 1261 |
1 files changed, 1261 insertions, 0 deletions
diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c new file mode 100644 index 0000000..6498258 --- /dev/null +++ b/src/providers/krb5/krb5_common.c @@ -0,0 +1,1261 @@ +/* + SSSD + + Kerberos Provider Common Functions + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2008-2009 Red Hat + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <ctype.h> + +#include "providers/backend.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_opts.h" +#include "providers/krb5/krb5_utils.h" +#include "providers/fail_over.h" + +#ifdef HAVE_KRB5_CC_COLLECTION +/* krb5 profile functions */ +#include <profile.h> +#endif + +static errno_t check_lifetime(TALLOC_CTX *mem_ctx, struct dp_option *opts, + const int opt_id, char **lifetime_str) +{ + int ret; + char *str = NULL; + krb5_deltat lifetime; + + str = dp_opt_get_string(opts, opt_id); + if (str == NULL || *str == '\0') { + DEBUG(SSSDBG_FUNC_DATA, "No lifetime configured.\n"); + *lifetime_str = NULL; + return EOK; + } + + if (isdigit(str[strlen(str)-1])) { + str = talloc_asprintf(mem_ctx, "%ss", str); + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(opts, opt_id, str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + } else { + str = talloc_strdup(mem_ctx, str); + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + + ret = krb5_string_to_deltat(str, &lifetime); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid value [%s] for a lifetime.\n", str); + ret = EINVAL; + goto done; + } + + *lifetime_str = str; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(str); + } + + return ret; +} + +#ifdef HAVE_KRB5_CC_COLLECTION +/* source default_ccache_name from krb5.conf */ +static errno_t sss_get_system_ccname_template(TALLOC_CTX *mem_ctx, + char **ccname) +{ + krb5_context ctx; + profile_t p; + char *value = NULL; + long ret; + + *ccname = NULL; + + ret = sss_krb5_init_context(&ctx); + if (ret) return ret; + + ret = krb5_get_profile(ctx, &p); + if (ret) goto done; + + ret = profile_get_string(p, "libdefaults", "default_ccache_name", + NULL, NULL, &value); + profile_release(p); + if (ret) goto done; + + if (!value) { + ret = ERR_NOT_FOUND; + goto done; + } + + *ccname = talloc_strdup(mem_ctx, value); + if (*ccname == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + krb5_free_context(ctx); + free(value); + return ret; +} +#else +static errno_t sss_get_system_ccname_template(TALLOC_CTX *mem_ctx, + char **ccname) +{ + DEBUG(SSSDBG_CONF_SETTINGS, + "Your kerberos library does not support the default_ccache_name " + "option or the profile library. Please use krb5_ccname_template " + "in sssd.conf if you want to change the default\n"); + *ccname = NULL; + return ERR_NOT_FOUND; +} +#endif + +static void sss_check_cc_template(const char *cc_template) +{ + size_t template_len; + + template_len = strlen(cc_template); + if (template_len >= 6 && + strcmp(cc_template + (template_len - 6), "XXXXXX") != 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "ccache file name template [%s] doesn't " + "contain randomizing characters (XXXXXX), file might not " + "be rewritable\n", cc_template); + } +} + +errno_t sss_krb5_check_options(struct dp_option *opts, + struct sss_domain_info *dom, + struct krb5_ctx *krb5_ctx) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret; + const char *realm; + const char *dummy; + char *ccname; + + if (opts == NULL || dom == NULL || krb5_ctx == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + realm = dp_opt_get_cstring(opts, KRB5_REALM); + if (realm == NULL) { + ret = dp_opt_set_string(opts, KRB5_REALM, dom->name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + realm = dom->name; + } + + krb5_ctx->realm = talloc_strdup(krb5_ctx, realm); + if (krb5_ctx->realm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to set realm, krb5_child might not work as expected.\n"); + } + + ret = check_lifetime(krb5_ctx, opts, KRB5_RENEWABLE_LIFETIME, + &krb5_ctx->rlife_str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to check value of krb5_renewable_lifetime. [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + ret = check_lifetime(krb5_ctx, opts, KRB5_LIFETIME, + &krb5_ctx->lifetime_str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to check value of krb5_lifetime. [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + krb5_ctx->use_fast_str = dp_opt_get_cstring(opts, KRB5_USE_FAST); + if (krb5_ctx->use_fast_str != NULL) { + ret = check_fast(krb5_ctx->use_fast_str, &krb5_ctx->use_fast); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "check_fast failed.\n"); + goto done; + } + + if (krb5_ctx->use_fast) { + krb5_ctx->fast_principal = dp_opt_get_cstring(opts, + KRB5_FAST_PRINCIPAL); + krb5_ctx->fast_use_anonymous_pkinit = dp_opt_get_bool(opts, + KRB5_FAST_USE_ANONYMOUS_PKINIT); + } + } + + /* In contrast to MIT KDCs AD does not automatically canonicalize the + * enterprise principal in an AS request but requires the canonicalize + * flags to be set. To be on the safe side we always enable + * canonicalization if enterprise principals are used. */ + krb5_ctx->canonicalize = false; + if (dp_opt_get_bool(opts, KRB5_CANONICALIZE) + || dp_opt_get_bool(opts, KRB5_USE_ENTERPRISE_PRINCIPAL)) { + krb5_ctx->canonicalize = true; + } + + dummy = dp_opt_get_cstring(opts, KRB5_KDC); + if (dummy == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No KDC explicitly configured, using defaults.\n"); + } + + dummy = dp_opt_get_cstring(opts, KRB5_KPASSWD); + if (dummy == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No kpasswd server explicitly configured, " + "using the KDC or defaults.\n"); + } + + ccname = dp_opt_get_string(opts, KRB5_CCNAME_TMPL); + if (ccname != NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "The credential ccache name template has been explicitly set " + "in sssd.conf, it is recommended to set default_ccache_name " + "in krb5.conf instead so that a system default is used\n"); + ccname = talloc_strdup(tmp_ctx, ccname); + if (!ccname) { + ret = ENOMEM; + goto done; + } + } else { + ret = sss_get_system_ccname_template(tmp_ctx, &ccname); + if (ret && ret != ERR_NOT_FOUND) { + goto done; + } + if (ret == ERR_NOT_FOUND) { + /* Use fallback default */ + ccname = talloc_strdup(tmp_ctx, DEFAULT_CCNAME_TEMPLATE); + if (!ccname) { + ret = ENOMEM; + goto done; + } + } + + /* set back in opts */ + ret = dp_opt_set_string(opts, KRB5_CCNAME_TMPL, ccname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + } + + if ((ccname[0] == '/') || (strncmp(ccname, "FILE:", 5) == 0)) { + DEBUG(SSSDBG_CONF_SETTINGS, "ccache is of type FILE\n"); + /* warn if the file type (which is usually created in a sticky bit + * laden directory) does not have randomizing characters */ + sss_check_cc_template(ccname); + + if (ccname[0] == '/') { + /* /path/to/cc prepend FILE: */ + DEBUG(SSSDBG_CONF_SETTINGS, "The ccname template was " + "missing an explicit type, but is an absolute " + "path specifier. Assuming FILE:\n"); + + ccname = talloc_asprintf(tmp_ctx, "FILE:%s", ccname); + if (!ccname) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(opts, KRB5_CCNAME_TMPL, ccname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t krb5_try_kdcip(struct confdb_ctx *cdb, const char *conf_path, + struct dp_option *opts, int opt_id) +{ + char *krb5_servers = NULL; + errno_t ret; + + krb5_servers = dp_opt_get_string(opts, opt_id); + if (krb5_servers == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No KDC found in configuration, trying legacy option\n"); + ret = confdb_get_string(cdb, NULL, conf_path, + "krb5_kdcip", NULL, &krb5_servers); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "confdb_get_string failed.\n"); + return ret; + } + + if (krb5_servers != NULL) + { + ret = dp_opt_set_string(opts, opt_id, krb5_servers); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + talloc_free(krb5_servers); + return ret; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Set krb5 server [%s] based on legacy krb5_kdcip option\n", + krb5_servers); + DEBUG(SSSDBG_FATAL_FAILURE, + "Your configuration uses the deprecated option " + "'krb5_kdcip' to specify the KDC. Please change the " + "configuration to use the 'krb5_server' option " + "instead.\n"); + talloc_free(krb5_servers); + } + } + + return EOK; +} + +errno_t sss_krb5_get_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb, + const char *conf_path, struct dp_option **_opts) +{ + int ret; + struct dp_option *opts; + + ret = dp_get_options(memctx, cdb, conf_path, default_krb5_opts, + KRB5_OPTS, &opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_get_options failed.\n"); + goto done; + } + + /* If there is no KDC, try the deprecated krb5_kdcip option, too */ + /* FIXME - this can be removed in a future version */ + ret = krb5_try_kdcip(cdb, conf_path, opts, KRB5_KDC); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_try_kdcip failed.\n"); + goto done; + } + + *_opts = opts; + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(opts); + } + + return ret; +} + +void sss_krb5_parse_lookahead(const char *param, size_t *primary, size_t *backup) +{ + int ret; + + if (primary == NULL || backup == NULL) { + return; + } + + *primary = SSS_KRB5_LOOKAHEAD_PRIMARY_DEFAULT; + *backup = SSS_KRB5_LOOKAHEAD_BACKUP_DEFAULT; + + if (param == NULL) { + return; + } + + if (strchr(param, ':')) { + ret = sscanf(param, "%zu:%zu", primary, backup); + if (ret != 2) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not parse krb5_kdcinfo_lookahead!\n"); + } + } else { + ret = sscanf(param, "%zu", primary); + if (ret != 1) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not parse krb5_kdcinfo_lookahead!\n"); + } + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Option krb5_kdcinfo_lookahead set to %zu:%zu", + *primary, *backup); +} + + +static int remove_info_files_destructor(void *p) +{ + int ret; + struct remove_info_files_ctx *ctx = talloc_get_type(p, + struct remove_info_files_ctx); + + ret = remove_krb5_info_files(ctx, ctx->realm); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "remove_krb5_info_files failed.\n"); + } + ctx->krb5_service->removal_callback_available = false; + + return 0; +} + +static errno_t +krb5_add_krb5info_offline_callback(struct krb5_service *krb5_service) +{ + int ret; + struct remove_info_files_ctx *ctx = NULL; + + if (krb5_service == NULL || krb5_service->name == NULL + || krb5_service->realm == NULL + || krb5_service->be_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing KDC service name or realm!\n"); + return EINVAL; + } + + if (krb5_service->removal_callback_available) { + DEBUG(SSSDBG_TRACE_ALL, + "Removal callback already available for service [%s].\n", + krb5_service->name); + return EOK; + } + + ctx = talloc_zero(krb5_service->be_ctx, struct remove_info_files_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zfree failed.\n"); + return ENOMEM; + } + + ctx->realm = talloc_strdup(ctx, krb5_service->realm); + if (ctx->realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed!\n"); + ret = ENOMEM; + goto done; + } + + ctx->be_ctx = krb5_service->be_ctx; + ctx->krb5_service = krb5_service; + ctx->kdc_service_name = talloc_strdup(ctx, krb5_service->name); + if (ctx->kdc_service_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed!\n"); + ret = ENOMEM; + goto done; + } + + ret = be_add_offline_cb(ctx, krb5_service->be_ctx, + remove_krb5_info_files_callback, ctx, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_add_offline_cb failed.\n"); + goto done; + } + + talloc_set_destructor((TALLOC_CTX *) ctx, remove_info_files_destructor); + krb5_service->removal_callback_available = true; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(ctx); + } + + return ret; +} + +static errno_t write_krb5info_file_contents(struct krb5_service *krb5_service, + const char *contents, + const char *service) +{ + int ret; + int fd = -1; + char *tmp_name = NULL; + char *krb5info_name = NULL; + TALLOC_CTX *tmp_ctx = NULL; + const char *name_tmpl = NULL; + size_t server_len; + ssize_t written; + + if (krb5_service == NULL || krb5_service->realm == NULL + || *krb5_service->realm == '\0' + || contents == NULL || *contents == '\0' + || service == NULL || *service == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing or empty realm, server or service.\n"); + return EINVAL; + } + + if (sss_krb5_realm_has_proxy(krb5_service->realm)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "KDC Proxy available for realm [%s], no kdcinfo file created.\n", + krb5_service->realm); + return EOK; + } + + if (strcmp(service, SSS_KRB5KDC_FO_SRV) == 0) { + name_tmpl = KDCINFO_TMPL; + } else if (strcmp(service, SSS_KRB5KPASSWD_FO_SRV) == 0) { + name_tmpl = KPASSWDINFO_TMPL; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported service [%s].\n", service); + return EINVAL; + } + + server_len = strlen(contents); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + tmp_name = talloc_asprintf(tmp_ctx, PUBCONF_PATH"/.krb5info_dummy_XXXXXX"); + if (tmp_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + krb5info_name = talloc_asprintf(tmp_ctx, name_tmpl, krb5_service->realm); + if (krb5info_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + fd = sss_unique_file(tmp_ctx, tmp_name, &ret); + if (fd == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_unique_file failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + errno = 0; + written = sss_atomic_write_s(fd, discard_const(contents), server_len); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "write failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + if (written != server_len) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Write error, wrote [%zd] bytes, expected [%zu]\n", + written, server_len); + ret = EIO; + goto done; + } + + ret = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fchmod failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + ret = close(fd); + fd = -1; + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "close failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + ret = rename(tmp_name, krb5info_name); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "rename failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + ret = krb5_add_krb5info_offline_callback(krb5_service); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add offline callback, krb5info " + "file might not be removed properly.\n"); + } + + ret = EOK; +done: + if (fd != -1) { + close(fd); + } + + talloc_free(tmp_ctx); + return ret; +} + +errno_t write_krb5info_file(struct krb5_service *krb5_service, + const char **server_list, + const char *service) +{ + int i; + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + char *contents = NULL; + + if (krb5_service == NULL || server_list == NULL || service == NULL) { + return EINVAL; + } + + if (server_list[0] == NULL) { + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + contents = talloc_strdup(tmp_ctx, ""); + if (contents == NULL) { + ret = ENOMEM; + goto done; + } + + i = 0; + do { + contents = talloc_asprintf_append(contents, "%s\n", server_list[i]); + if (contents == NULL) { + ret = ENOMEM; + goto done; + } + i++; + } while (server_list[i] != NULL); + + ret = write_krb5info_file_contents(krb5_service, contents, service); +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char* fo_server_address_or_name(TALLOC_CTX *tmp_ctx, struct fo_server *server) +{ + struct resolv_hostent *srvaddr; + char *address; + + if (!server) return NULL; + + srvaddr = fo_get_server_hostent(server); + if (srvaddr) { + address = resolv_get_string_address(tmp_ctx, srvaddr); + if (address) { + return sss_escape_ip_address(tmp_ctx, + srvaddr->family, + address); + } + } + + address = discard_const(fo_get_server_name(server)); + if (address != NULL && fo_get_use_search_list(server) == false) { + if (address[strlen(address)-1] != '.') { + address = talloc_asprintf(tmp_ctx, "%s.", address); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_asprintf failed.\n"); + } + } + } + return address; +} + +errno_t write_krb5info_file_from_fo_server(struct krb5_service *krb5_service, + struct fo_server *server, + bool force_default_port, + const char *service, + bool (*filter)(struct fo_server *)) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char **server_list; + size_t server_idx; + struct fo_server *item; + int primary; + int port; + const char *address; + errno_t ret; + size_t n_lookahead_primary; + size_t n_lookahead_backup; + + if (krb5_service == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "The krb5_service must not be NULL!\n"); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed\n"); + return ENOMEM; + } + + n_lookahead_primary = krb5_service->lookahead_primary; + n_lookahead_backup = krb5_service->lookahead_backup; + + server_idx = 0; + server_list = talloc_zero_array(tmp_ctx, + const char *, + fo_server_count(server) + 1); + if (server_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array failed\n"); + talloc_free(tmp_ctx); + return ENOMEM; + } + + if (filter == NULL || filter(server) == false) { + address = fo_server_address_or_name(tmp_ctx, server); + if (address) { + if (!force_default_port) { + port = fo_get_server_port(server); + if (port != 0) { + address = talloc_asprintf(tmp_ctx, "%s:%d", address, port); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + talloc_free(tmp_ctx); + return ENOMEM; + } + } + } + + server_list[server_idx++] = address; + if (fo_is_server_primary(server)) { + if (n_lookahead_primary > 0) { + n_lookahead_primary--; + } + } else { + if (n_lookahead_backup > 0) { + n_lookahead_backup--; + } + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Server without name and address found in list.\n"); + } + } + + for (primary = 1; primary >= 0; --primary) { + for (item = fo_server_next(server) ? fo_server_next(server) : fo_server_first(server); + item != server; + item = fo_server_next(item) ? fo_server_next(item) : fo_server_first(item)) { + + if (primary && n_lookahead_primary == 0) break; + if (!primary && n_lookahead_backup == 0) break; + if (primary && !fo_is_server_primary(item)) continue; + if (!primary && fo_is_server_primary(item)) continue; + if (filter != NULL && filter(item)) continue; + + address = fo_server_address_or_name(tmp_ctx, item); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Server without name and address found in list.\n"); + continue; + } + + if (!force_default_port) { + port = fo_get_server_port(item); + if (port != 0) { + address = talloc_asprintf(tmp_ctx, "%s:%d", address, port); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + talloc_free(tmp_ctx); + return ENOMEM; + } + } + } + + server_list[server_idx++] = address; + if (primary) { + n_lookahead_primary--; + } else { + n_lookahead_backup--; + } + } + } + if (server_list[0] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "There is no server that can be written into kdc info file.\n"); + ret = EINVAL; + } else { + ret = write_krb5info_file(krb5_service, + server_list, + service); + } + talloc_free(tmp_ctx); + return ret; +} + + +static void krb5_resolve_callback(void *private_data, struct fo_server *server) +{ + struct krb5_service *krb5_service; + int ret; + + krb5_service = talloc_get_type(private_data, struct krb5_service); + if (!krb5_service) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bad private_data\n"); + return; + } + + if (krb5_service->write_kdcinfo) { + ret = write_krb5info_file_from_fo_server(krb5_service, + server, + false, + krb5_service->name, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "write to %s/kdcinfo.%s failed, authentication might fail.\n", + PUBCONF_PATH, krb5_service->realm); + } + } +} + +static errno_t _krb5_servers_init(struct be_ctx *ctx, + struct krb5_service *service, + const char *service_name, + const char *servers, + bool primary) +{ + TALLOC_CTX *tmp_ctx; + char **list = NULL; + errno_t ret = 0; + int i; + char *port_str; + long port; + char *server_spec; + char *endptr; + struct servent *servent; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = split_on_separator(tmp_ctx, servers, ',', true, true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse server list!\n"); + goto done; + } + + for (i = 0; list[i]; i++) { + talloc_steal(service, list[i]); + server_spec = talloc_strdup(service, list[i]); + if (!server_spec) { + ret = ENOMEM; + goto done; + } + + if (be_fo_is_srv_identifier(server_spec)) { + if (!primary) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add server [%s] to failover service: " + "SRV resolution only allowed for primary servers!\n", + list[i]); + continue; + } + + ret = be_fo_add_srv_server(ctx, service_name, service_name, NULL, + BE_FO_PROTO_UDP, true, NULL); + if (ret) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added service lookup\n"); + continue; + } + + /* Do not try to get port number if last character is ']' */ + if (server_spec[strlen(server_spec) - 1] != ']') { + port_str = strrchr(server_spec, ':'); + } else { + port_str = NULL; + } + + if (port_str == NULL) { + port = 0; + } else { + *port_str = '\0'; + ++port_str; + if (isdigit(*port_str)) { + errno = 0; + port = strtol(port_str, &endptr, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "strtol failed on [%s]: [%d][%s].\n", port_str, + ret, strerror(ret)); + goto done; + } + if (*endptr != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Found additional characters [%s] in port number " + "[%s].\n", endptr, port_str); + ret = EINVAL; + goto done; + } + + if (port < 1 || port > 65535) { + DEBUG(SSSDBG_CRIT_FAILURE, "Illegal port number [%ld].\n", port); + ret = EINVAL; + goto done; + } + } else if (isalpha(*port_str)) { + servent = getservbyname(port_str, NULL); + if (servent == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "getservbyname cannot find service [%s].\n", + port_str); + ret = EINVAL; + goto done; + } + + port = servent->s_port; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported port specifier in [%s].\n", list[i]); + ret = EINVAL; + goto done; + } + } + + /* It could be ipv6 address in square brackets. Remove + * the brackets if needed. */ + ret = remove_ipv6_brackets(server_spec); + if (ret != EOK) { + goto done; + } + + ret = be_fo_add_server(ctx, service_name, server_spec, (int) port, + list[i], primary); + if (ret && ret != EEXIST) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added Server %s\n", list[i]); + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static inline errno_t +krb5_primary_servers_init(struct be_ctx *ctx, struct krb5_service *service, + const char *service_name, const char *servers) +{ + return _krb5_servers_init(ctx, service, service_name, servers, true); +} + +static inline errno_t +krb5_backup_servers_init(struct be_ctx *ctx, struct krb5_service *service, + const char *service_name, const char *servers) +{ + return _krb5_servers_init(ctx, service, service_name, servers, false); +} + +static int krb5_user_data_cmp(void *ud1, void *ud2) +{ + return strcasecmp((char*) ud1, (char*) ud2); +} + +struct krb5_service *krb5_service_new(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + const char *service_name, + const char *realm, + bool use_kdcinfo, + size_t n_lookahead_primary, + size_t n_lookahead_backup) +{ + struct krb5_service *service; + + service = talloc_zero(mem_ctx, struct krb5_service); + if (service == NULL) { + return NULL; + } + + service->name = talloc_strdup(service, service_name); + if (service->name == NULL) { + talloc_free(service); + return NULL; + } + + service->realm = talloc_strdup(service, realm); + if (service->realm == NULL) { + talloc_free(service); + return NULL; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "write_kdcinfo for realm %s set to %s\n", + realm, + use_kdcinfo ? "true" : "false"); + service->write_kdcinfo = use_kdcinfo; + service->lookahead_primary = n_lookahead_primary; + service->lookahead_backup = n_lookahead_backup; + + service->be_ctx = be_ctx; + return service; +} + +int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *service_name, + const char *primary_servers, + const char *backup_servers, + const char *realm, + bool use_kdcinfo, + size_t n_lookahead_primary, + size_t n_lookahead_backup, + struct krb5_service **_service) +{ + TALLOC_CTX *tmp_ctx; + struct krb5_service *service; + int ret; + + tmp_ctx = talloc_new(memctx); + if (!tmp_ctx) { + return ENOMEM; + } + + service = krb5_service_new(tmp_ctx, ctx, service_name, realm, use_kdcinfo, + n_lookahead_primary, n_lookahead_backup); + if (!service) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_service(ctx, service_name, krb5_user_data_cmp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create failover service!\n"); + goto done; + } + + if (!primary_servers) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No primary servers defined, using service discovery\n"); + primary_servers = BE_SRV_IDENTIFIER; + } + + ret = krb5_primary_servers_init(ctx, service, service_name, primary_servers); + if (ret != EOK) { + goto done; + } + + if (backup_servers) { + ret = krb5_backup_servers_init(ctx, service, service_name, + backup_servers); + if (ret != EOK) { + goto done; + } + } + + ret = be_fo_service_add_callback(memctx, ctx, service_name, + krb5_resolve_callback, service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add failover callback!\n"); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *_service = talloc_steal(memctx, service); + } + talloc_zfree(tmp_ctx); + return ret; +} + + +errno_t remove_krb5_info_files(TALLOC_CTX *mem_ctx, const char *realm) +{ + int ret; + errno_t err; + char *file; + + file = talloc_asprintf(mem_ctx, KDCINFO_TMPL, realm); + if(file == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + errno = 0; + ret = unlink(file); + if (ret == -1) { + err = errno; + DEBUG(SSSDBG_FUNC_DATA, "Could not remove [%s], [%d][%s]\n", file, + err, strerror(err)); + } + + file = talloc_asprintf(mem_ctx, KPASSWDINFO_TMPL, realm); + if(file == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + errno = 0; + ret = unlink(file); + if (ret == -1) { + err = errno; + DEBUG(SSSDBG_FUNC_DATA, "Could not remove [%s], [%d][%s]\n", file, + err, strerror(err)); + } + + return EOK; +} + +void remove_krb5_info_files_callback(void *pvt) +{ + int ret; + struct remove_info_files_ctx *ctx = talloc_get_type(pvt, + struct remove_info_files_ctx); + + ret = be_fo_run_callbacks_at_next_request(ctx->be_ctx, + ctx->kdc_service_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "be_fo_run_callbacks_at_next_request(kdc_service_name) failed, " + "krb5 info files will not be removed, because " + "it is unclear if they will be recreated properly.\n"); + return; + } + if (ctx->kpasswd_service_name != NULL) { + ret = be_fo_run_callbacks_at_next_request(ctx->be_ctx, + ctx->kpasswd_service_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "be_fo_run_callbacks_at_next_request(kpasswd_service_name) failed, " + "krb5 info files will not be removed, because " + "it is unclear if they will be recreated properly.\n"); + return; + } + } + + /* Freeing the remove_info_files_ctx will remove the related krb5info + * file. Additionally the callback from the list of callbacks is removed, + * it will be added again when a new krb5info file is created. */ + talloc_free(ctx); +} + +errno_t krb5_get_simple_upn(TALLOC_CTX *mem_ctx, struct krb5_ctx *krb5_ctx, + struct sss_domain_info *dom, const char *username, + const char *user_dom, char **_upn) +{ + const char *realm = NULL; + char *uc_dom = NULL; + char *upn; + char *name; + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (user_dom != NULL && dom->name != NULL && + strcasecmp(dom->name, user_dom) != 0) { + uc_dom = get_uppercase_realm(tmp_ctx, user_dom); + if (uc_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "get_uppercase_realm failed.\n"); + ret = ENOMEM; + goto done; + } + } else { + realm = dp_opt_get_cstring(krb5_ctx->opts, KRB5_REALM); + if (realm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing Kerberos realm.\n"); + ret = ENOMEM; + goto done; + } + } + + /* The internal username is qualified, but we are only interested in + * the name part + */ + ret = sss_parse_internal_fqname(tmp_ctx, username, &name, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not parse [%s] into name and " + "domain components, login might fail\n", username); + upn = talloc_strdup(tmp_ctx, username); + } else { + /* NOTE: this is a hack, works only in some environments */ + upn = talloc_asprintf(tmp_ctx, "%s@%s", + name, realm != NULL ? realm : uc_dom); + } + + if (upn == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Using simple UPN [%s].\n", upn); + *_upn = talloc_steal(mem_ctx, upn); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t compare_principal_realm(const char *upn, const char *realm, + bool *different_realm) +{ + char *at_sign; + + if (upn == NULL || realm == NULL || different_realm == NULL || + *upn == '\0' || *realm == '\0') { + return EINVAL; + } + + at_sign = strchr(upn, '@'); + + if (at_sign == NULL) { + return EINVAL; + } + + if (strcmp(realm, at_sign + 1) == 0) { + *different_realm = false; + } else { + *different_realm = true; + } + + return EOK; +} |