/* SSSD Kerberos 5 Backend Module -- Utilities Authors: Sumit Bose Copyright (C) 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 . */ #include #include #include #include "providers/krb5/krb5_utils.h" #include "providers/krb5/krb5_ccache.h" #include "providers/krb5/krb5_auth.h" #include "src/util/find_uid.h" #include "util/util.h" errno_t find_or_guess_upn(TALLOC_CTX *mem_ctx, struct ldb_message *msg, struct krb5_ctx *krb5_ctx, struct sss_domain_info *dom, const char *user, const char *user_dom, char **_upn) { const char *upn = NULL; int ret; if (krb5_ctx == NULL || dom == NULL || user == NULL || _upn == NULL) { return EINVAL; } if (msg != NULL) { upn = ldb_msg_find_attr_as_string(msg, SYSDB_CANONICAL_UPN, NULL); if (upn != NULL) { ret = EOK; goto done; } upn = ldb_msg_find_attr_as_string(msg, SYSDB_UPN, NULL); if (upn != NULL) { ret = EOK; goto done; } } ret = krb5_get_simple_upn(mem_ctx, krb5_ctx, dom, user, user_dom, _upn); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "krb5_get_simple_upn failed.\n"); return ret; } done: if (ret == EOK && upn != NULL) { *_upn = talloc_strdup(mem_ctx, upn); if (*_upn == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); return ENOMEM; } } return ret; } errno_t check_if_cached_upn_needs_update(struct sysdb_ctx *sysdb, struct sss_domain_info *domain, const char *user, const char *upn) { TALLOC_CTX *tmp_ctx; int ret; int sret; const char *attrs[] = {SYSDB_UPN, SYSDB_CANONICAL_UPN, NULL}; struct sysdb_attrs *new_attrs; struct ldb_result *res; bool in_transaction = false; const char *cached_upn; const char *cached_canonical_upn; if (sysdb == NULL || user == NULL || upn == NULL) { return EINVAL; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); return ENOMEM; } ret = sysdb_get_user_attr(tmp_ctx, domain, user, attrs, &res); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_user_attr failed.\n"); goto done; } if (res->count != 1) { DEBUG(SSSDBG_OP_FAILURE, "[%d] user objects for name [%s] found, " \ "expected 1.\n", res->count, user); ret = EINVAL; goto done; } cached_upn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_UPN, NULL); if (cached_upn != NULL && strcmp(cached_upn, upn) == 0) { DEBUG(SSSDBG_TRACE_ALL, "Cached UPN and new one match, " "nothing to do.\n"); ret = EOK; goto done; } cached_canonical_upn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_CANONICAL_UPN, NULL); if (cached_canonical_upn != NULL && strcmp(cached_canonical_upn, upn) == 0) { DEBUG(SSSDBG_TRACE_ALL, "Cached canonical UPN and new one match, " "nothing to do.\n"); ret = EOK; goto done; } DEBUG(SSSDBG_TRACE_LIBS, "Replacing canonical UPN [%s] with [%s] " \ "for user [%s].\n", cached_canonical_upn == NULL ? "empty" : cached_canonical_upn, upn, user); new_attrs = sysdb_new_attrs(tmp_ctx); if (new_attrs == NULL) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); ret = ENOMEM; goto done; } ret = sysdb_attrs_add_string(new_attrs, SYSDB_CANONICAL_UPN, upn); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string failed.\n"); goto done; } ret = sysdb_transaction_start(sysdb); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Error %d starting transaction (%s)\n", ret, strerror(ret)); goto done; } in_transaction = true; ret = sysdb_set_entry_attr(sysdb, res->msgs[0]->dn, new_attrs, cached_canonical_upn == NULL ? SYSDB_MOD_ADD : SYSDB_MOD_REP); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_entry_attr failed [%d][%s].\n", ret, strerror(ret)); goto done; } ret = sysdb_transaction_commit(sysdb); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to commit transaction!\n"); goto done; } in_transaction = false; ret = EOK; done: if (in_transaction) { sret = sysdb_transaction_cancel(sysdb); if (sret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); } } talloc_free(tmp_ctx); return ret; } #define S_EXP_UID "{uid}" #define L_EXP_UID (sizeof(S_EXP_UID) - 1) #define S_EXP_USERID "{USERID}" #define L_EXP_USERID (sizeof(S_EXP_USERID) - 1) #define S_EXP_EUID "{euid}" #define L_EXP_EUID (sizeof(S_EXP_EUID) - 1) #define S_EXP_USERNAME "{username}" #define L_EXP_USERNAME (sizeof(S_EXP_USERNAME) - 1) static errno_t check_ccache_re(const char *filename, pcre *illegal_re) { errno_t ret; ret = pcre_exec(illegal_re, NULL, filename, strlen(filename), 0, 0, NULL, 0); if (ret == 0) { DEBUG(SSSDBG_OP_FAILURE, "Illegal pattern in ccache directory name [%s].\n", filename); return EINVAL; } else if (ret == PCRE_ERROR_NOMATCH) { DEBUG(SSSDBG_TRACE_LIBS, "Ccache directory name [%s] does not contain " "illegal patterns.\n", filename); return EOK; } DEBUG(SSSDBG_CRIT_FAILURE, "pcre_exec failed [%d].\n", ret); return EFAULT; } char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr, const char *template, pcre *illegal_re, bool file_mode, bool case_sensitive) { char *copy; char *p; char *n; char *result = NULL; char *dummy; char *name; char *res = NULL; const char *cache_dir_tmpl; TALLOC_CTX *tmp_ctx = NULL; char action; bool rerun; int ret; if (template == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing template.\n"); return NULL; } tmp_ctx = talloc_new(NULL); if (!tmp_ctx) return NULL; copy = talloc_strdup(tmp_ctx, template); if (copy == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); goto done; } result = talloc_strdup(tmp_ctx, ""); if (result == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); goto done; } p = copy; while ( (n = strchr(p, '%')) != NULL) { *n = '\0'; n++; if ( *n == '\0' ) { DEBUG(SSSDBG_CRIT_FAILURE, "format error, single %% at the end of the template.\n"); goto done; } rerun = true; action = *n; while (rerun) { rerun = false; switch (action) { case 'u': if (kr->pd->user == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand user name template " "because user name is empty.\n"); goto done; } name = sss_get_cased_name(tmp_ctx, kr->pd->user, case_sensitive); if (!name) { DEBUG(SSSDBG_CRIT_FAILURE, "sss_get_cased_name failed\n"); goto done; } result = talloc_asprintf_append(result, "%s%s", p, name); break; case 'U': if (kr->uid <= 0) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand uid template " "because uid is invalid.\n"); goto done; } result = talloc_asprintf_append(result, "%s%"SPRIuid, p, kr->uid); break; case 'p': if (kr->upn == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand user principal name template " "because upn is empty.\n"); goto done; } result = talloc_asprintf_append(result, "%s%s", p, kr->upn); break; case '%': result = talloc_asprintf_append(result, "%s%%", p); break; case 'r': dummy = dp_opt_get_string(kr->krb5_ctx->opts, KRB5_REALM); if (dummy == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing kerberos realm.\n"); goto done; } result = talloc_asprintf_append(result, "%s%s", p, dummy); break; case 'h': if (kr->homedir == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand home directory template " "because the path is not available.\n"); goto done; } result = talloc_asprintf_append(result, "%s%s", p, kr->homedir); break; case 'd': if (file_mode) { cache_dir_tmpl = dp_opt_get_string(kr->krb5_ctx->opts, KRB5_CCACHEDIR); if (cache_dir_tmpl == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing credential cache directory.\n"); goto done; } dummy = expand_ccname_template(tmp_ctx, kr, cache_dir_tmpl, illegal_re, false, case_sensitive); if (dummy == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Expanding credential cache directory " "template failed.\n"); goto done; } result = talloc_asprintf_append(result, "%s%s", p, dummy); talloc_zfree(dummy); } else { DEBUG(SSSDBG_CRIT_FAILURE, "'%%d' is not allowed in this template.\n"); goto done; } break; case 'P': if (!file_mode) { DEBUG(SSSDBG_CRIT_FAILURE, "'%%P' is not allowed in this template.\n"); goto done; } if (kr->pd->cli_pid == 0) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand PID template " "because PID is not available.\n"); goto done; } result = talloc_asprintf_append(result, "%s%d", p, kr->pd->cli_pid); break; /* Additional syntax from krb5.conf default_ccache_name */ case '{': if (strncmp(n , S_EXP_UID, L_EXP_UID) == 0) { action = 'U'; n += L_EXP_UID - 1; rerun = true; continue; } else if (strncmp(n , S_EXP_USERID, L_EXP_USERID) == 0) { action = 'U'; n += L_EXP_USERID - 1; rerun = true; continue; } else if (strncmp(n , S_EXP_EUID, L_EXP_EUID) == 0) { /* SSSD does not distinguish betwen uid and euid, * so we treat both the same way */ action = 'U'; n += L_EXP_EUID - 1; rerun = true; continue; } else if (strncmp(n , S_EXP_USERNAME, L_EXP_USERNAME) == 0) { action = 'u'; n += L_EXP_USERNAME - 1; rerun = true; continue; } else { /* ignore any expansion variable we do not understand and * let libkrb5 hndle it or fail */ name = n; n = strchr(name, '}'); if (!n) { DEBUG(SSSDBG_CRIT_FAILURE, "Invalid substitution sequence in cache " "template. Missing closing '}' in [%s].\n", template); goto done; } result = talloc_asprintf_append(result, "%s%%%.*s", p, (int)(n - name + 1), name); } break; default: DEBUG(SSSDBG_CRIT_FAILURE, "format error, unknown template [%%%c].\n", *n); goto done; } } if (result == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n"); goto done; } p = n + 1; } result = talloc_asprintf_append(result, "%s", p); if (result == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n"); goto done; } if (illegal_re != NULL) { ret = check_ccache_re(result, illegal_re); if (ret != EOK) { goto done; } } res = talloc_move(mem_ctx, &result); done: talloc_zfree(tmp_ctx); return res; } errno_t get_domain_or_subdomain(struct be_ctx *be_ctx, char *domain_name, struct sss_domain_info **dom) { if (domain_name != NULL && strcasecmp(domain_name, be_ctx->domain->name) != 0) { *dom = find_domain_by_name(be_ctx->domain, domain_name, true); if (*dom == NULL) { DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); return ENOMEM; } } else { *dom = be_ctx->domain; } return EOK; }