/* SSSD User tools Copyright (C) Stephen Gallagher 2009 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 #include "confdb/confdb.h" #include "util/util.h" #ifdef HAVE_LIBPCRE_LESSER_THAN_7 #define NAME_DOMAIN_PATTERN_OPTIONS (PCRE_EXTENDED) #else #define NAME_DOMAIN_PATTERN_OPTIONS (PCRE_DUPNAMES | PCRE_EXTENDED) #endif char *get_username_from_uid(TALLOC_CTX *mem_ctx, uid_t uid) { char *username; struct passwd *pwd; pwd = getpwuid(uid); if (!pwd) return NULL; username = talloc_strdup(mem_ctx, pwd->pw_name); return username; } /* Function returns given realm name as new uppercase string */ char *get_uppercase_realm(TALLOC_CTX *memctx, const char *name) { char *realm; char *c; realm = talloc_strdup(memctx, name); if (!realm) { return NULL; } c = realm; while(*c != '\0') { *c = toupper(*c); c++; } return realm; } static int sss_names_ctx_destructor(struct sss_names_ctx *snctx) { if (snctx->re) { pcre_free(snctx->re); snctx->re = NULL; } return 0; } #define IPA_AD_DEFAULT_RE "(((?P[^\\\\]+)\\\\(?P.+$))|" \ "((?P[^@]+)@(?P.+$))|" \ "(^(?P[^@\\\\]+)$))" static errno_t get_id_provider_default_re(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, const char *conf_path, char **re_pattern) { #ifdef HAVE_LIBPCRE_LESSER_THAN_7 DEBUG(SSSDBG_MINOR_FAILURE, ("The libpcre version on this system is too old. Only " "the user@DOMAIN name fully qualified name format will " "be supported\n")); *re_pattern = NULL; return EOK; #else int ret; size_t c; char *id_provider = NULL; struct provider_default_re { const char *name; const char *re; } provider_default_re[] = {{"ipa", IPA_AD_DEFAULT_RE}, {"ad", IPA_AD_DEFAULT_RE}, {NULL, NULL}}; ret = confdb_get_string(cdb, mem_ctx, conf_path, CONFDB_DOMAIN_ID_PROVIDER, NULL, &id_provider); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Failed to read ID provider " \ "from conf db.\n")); goto done; } if (id_provider == NULL) { *re_pattern = NULL; } else { for (c = 0; provider_default_re[c].name != NULL; c++) { if (strcmp(id_provider, provider_default_re[c].name) == 0) { *re_pattern = talloc_strdup(mem_ctx, provider_default_re[c].re); if (*re_pattern == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); ret = ENOMEM; goto done; } break; } } } ret = EOK; done: talloc_free(id_provider); return ret; #endif } static errno_t sss_fqnames_init(struct sss_names_ctx *nctx, const char *fq_fmt) { struct pattern_desc { const char *pattern; const char *desc; int flag; }; struct pattern_desc fqname_patterns[] = { { "%1$s", "user name", FQ_FMT_NAME }, { "%2$s", "domain name", FQ_FMT_DOMAIN }, { "%3$s", "domain flat name", FQ_FMT_FLAT_NAME }, { NULL, NULL, 0 } }; nctx->fq_fmt = talloc_strdup(nctx, fq_fmt); if (nctx->fq_fmt == NULL) { return ENOMEM; } DEBUG(SSSDBG_CONF_SETTINGS, ("Using fq format [%s].\n", nctx->fq_fmt)); /* Fail if the name specifier is missing and warn if the domain * specifier is missing */ if (strstr(fq_fmt, fqname_patterns[0].pattern) == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("Username pattern not found in [%s]\n", nctx->fq_fmt)); return ENOENT; } nctx->fq_flags = FQ_FMT_NAME; for (int i = 1; fqname_patterns[i].pattern; i++) { char *s; s = strstr(fq_fmt, fqname_patterns[i].pattern); if (s == NULL) { /* Append the format specifier */ nctx->fq_fmt = talloc_strdup_append(nctx->fq_fmt, fqname_patterns[i].pattern); if (nctx->fq_fmt == NULL) { return ENOMEM; } continue; } DEBUG(SSSDBG_CONF_SETTINGS, ("Found the pattern for %s\n", fqname_patterns[i].desc)); nctx->fq_flags |= fqname_patterns[i].flag; } return EOK; } int sss_names_init_from_args(TALLOC_CTX *mem_ctx, const char *re_pattern, const char *fq_fmt, struct sss_names_ctx **out) { struct sss_names_ctx *ctx; const char *errstr; int errval; int errpos; int ret; ctx = talloc_zero(mem_ctx, struct sss_names_ctx); if (!ctx) return ENOMEM; talloc_set_destructor(ctx, sss_names_ctx_destructor); ctx->re_pattern = talloc_strdup(ctx, re_pattern); if (ctx->re_pattern == NULL) { ret = ENOMEM; goto done; } DEBUG(SSSDBG_CONF_SETTINGS, ("Using re [%s].\n", ctx->re_pattern)); ret = sss_fqnames_init(ctx, fq_fmt); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Could not check the FQ names format" "[%d]: %s\n", ret, sss_strerror(ret))); goto done; } ctx->re = pcre_compile2(ctx->re_pattern, NAME_DOMAIN_PATTERN_OPTIONS, &errval, &errstr, &errpos, NULL); if (!ctx->re) { DEBUG(1, ("Invalid Regular Expression pattern at position %d." " (Error: %d [%s])\n", errpos, errval, errstr)); ret = EFAULT; goto done; } *out = ctx; ret = EOK; done: if (ret != EOK) { talloc_free(ctx); } return ret; } int sss_names_init(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, const char *domain, struct sss_names_ctx **out) { TALLOC_CTX *tmpctx = NULL; char *conf_path; char *re_pattern; char *fq_fmt; int ret; tmpctx = talloc_new(NULL); if (tmpctx == NULL) { ret = ENOMEM; goto done; } conf_path = talloc_asprintf(tmpctx, CONFDB_DOMAIN_PATH_TMPL, domain); if (conf_path == NULL) { ret = ENOMEM; goto done; } ret = confdb_get_string(cdb, tmpctx, conf_path, CONFDB_NAME_REGEX, NULL, &re_pattern); if (ret != EOK) goto done; /* If not found in the domain, look in globals */ if (re_pattern == NULL) { ret = confdb_get_string(cdb, tmpctx, CONFDB_MONITOR_CONF_ENTRY, CONFDB_NAME_REGEX, NULL, &re_pattern); if (ret != EOK) goto done; } if (re_pattern == NULL) { ret = get_id_provider_default_re(tmpctx, cdb, conf_path, &re_pattern); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Failed to get provider default regular " \ "expression for domain [%s].\n", domain)); goto done; } } if (!re_pattern) { re_pattern = talloc_strdup(tmpctx, "(?P[^@]+)@?(?P[^@]*$)"); if (!re_pattern) { ret = ENOMEM; goto done; } #ifdef HAVE_LIBPCRE_LESSER_THAN_7 } else { DEBUG(2, ("This binary was build with a version of libpcre that does " "not support non-unique named subpatterns.\n")); DEBUG(2, ("Please make sure that your pattern [%s] only contains " "subpatterns with a unique name and uses " "the Python syntax (?P).\n", re_pattern)); #endif } ret = confdb_get_string(cdb, tmpctx, conf_path, CONFDB_FULL_NAME_FORMAT, NULL, &fq_fmt); if (ret != EOK) goto done; /* If not found in the domain, look in globals */ if (fq_fmt == NULL) { ret = confdb_get_string(cdb, tmpctx, CONFDB_MONITOR_CONF_ENTRY, CONFDB_FULL_NAME_FORMAT, NULL, &fq_fmt); if (ret != EOK) goto done; } if (!fq_fmt) { fq_fmt = talloc_strdup(tmpctx, "%1$s@%2$s"); if (!fq_fmt) { ret = ENOMEM; goto done; } } ret = sss_names_init_from_args(mem_ctx, re_pattern, fq_fmt, out); done: talloc_free(tmpctx); return ret; } int sss_parse_name(TALLOC_CTX *memctx, struct sss_names_ctx *snctx, const char *orig, char **domain, char **name) { pcre *re = snctx->re; const char *result; int ovec[30]; int origlen; int ret, strnum; origlen = strlen(orig); ret = pcre_exec(re, NULL, orig, origlen, 0, PCRE_NOTEMPTY, ovec, 30); if (ret == PCRE_ERROR_NOMATCH) { return EINVAL; } else if (ret < 0) { DEBUG(SSSDBG_MINOR_FAILURE, ("PCRE Matching error, %d\n", ret)); return EINVAL; } if (ret == 0) { DEBUG(1, ("Too many matches, the pattern is invalid.\n")); } strnum = ret; result = NULL; ret = pcre_get_named_substring(re, orig, ovec, strnum, "name", &result); if (ret < 0 || !result) { DEBUG(2, ("Name not found!\n")); return EINVAL; } *name = talloc_strdup(memctx, result); pcre_free_substring(result); if (!*name) return ENOMEM; result = NULL; ret = pcre_get_named_substring(re, orig, ovec, strnum, "domain", &result); if (ret < 0 || !result) { DEBUG(4, ("Domain not provided!\n")); *domain = NULL; } else { /* ignore "" string */ if (*result) { *domain = talloc_strdup(memctx, result); pcre_free_substring(result); if (!*domain) return ENOMEM; } else { pcre_free_substring(result); *domain = NULL; } } return EOK; } static struct sss_domain_info * match_any_domain_or_subdomain_name( struct sss_domain_info *dom, const char *dmatch) { if (strcasecmp(dom->name, dmatch) == 0 || (dom->flat_name != NULL && strcasecmp(dom->flat_name, dmatch) == 0)) { return dom; } return find_subdomain_by_name(dom, dmatch, true); } int sss_parse_name_for_domains(TALLOC_CTX *memctx, struct sss_domain_info *domains, const char *default_domain, const char *orig, char **domain, char **name) { struct sss_domain_info *dom, *match; char *rdomain, *rname; char *dmatch, *nmatch; char *candidate_name = NULL; char *candidate_domain = NULL; bool name_mismatch = false; TALLOC_CTX *tmp_ctx; int ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) return ENOMEM; rname = NULL; rdomain = NULL; for (dom = domains; dom != NULL; dom = get_next_domain(dom, false)) { ret = sss_parse_name(tmp_ctx, dom->names, orig, &dmatch, &nmatch); if (ret == EOK) { /* * If the name matched without the domain part, make note of it. * All the other domain expressions must agree on the domain-less * name. */ if (dmatch == NULL) { if (candidate_name == NULL) { candidate_name = nmatch; } else if (strcasecmp(candidate_name, nmatch) != 0) { name_mismatch = true; } /* * If a domain was returned, then it must match the name of the * domain that this expression was found on, or one of the * subdomains. */ } else { match = match_any_domain_or_subdomain_name (dom, dmatch); if (match != NULL) { DEBUG(SSSDBG_FUNC_DATA, ("name '%s' matched expression for " "domain '%s', user is %s\n", orig, match->name, nmatch)); rdomain = talloc_strdup(tmp_ctx, match->name); if (rdomain == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); ret = ENOMEM; goto done; } rname = nmatch; break; } else if (candidate_name == NULL) { candidate_domain = dmatch; } } /* EINVAL is returned when name doesn't match */ } else if (ret != EINVAL) { goto done; } } if (rdomain == NULL && rname == NULL) { if (candidate_name && !name_mismatch) { DEBUG(SSSDBG_FUNC_DATA, ("name '%s' matched without domain, " \ "user is %s\n", orig, nmatch)); rdomain = NULL; if (default_domain != NULL) { rdomain = talloc_strdup(tmp_ctx, default_domain); if (rdomain == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); ret = ENOMEM; goto done; } for (dom = domains; dom != NULL; dom = get_next_domain(dom, false)) { match = match_any_domain_or_subdomain_name(dom, rdomain); if (match != NULL) { break; } } if (match == NULL) { DEBUG(SSSDBG_FUNC_DATA, ("default domain [%s] is currently " \ "not know, trying to look it up.\n", rdomain)); *domain = talloc_steal(memctx, rdomain); ret = EAGAIN; goto done; } } DEBUG(SSSDBG_FUNC_DATA, ("using default domain [%s]\n", rdomain)); rname = candidate_name; } else if (candidate_domain) { *domain = talloc_steal(memctx, candidate_domain); ret = EAGAIN; goto done; } } if (rdomain == NULL && rname == NULL) { DEBUG(SSSDBG_TRACE_FUNC, ("name '%s' did not match any domain's expression\n", orig)); ret = EINVAL; goto done; } if (domain != NULL) { *domain = talloc_steal(memctx, rdomain); } if (name != NULL) { *name = talloc_steal(memctx, rname); } ret = EOK; done: talloc_free(tmp_ctx); return ret; } char * sss_get_cased_name(TALLOC_CTX *mem_ctx, const char *orig_name, bool case_sensitive) { return case_sensitive ? talloc_strdup(mem_ctx, orig_name) : sss_tc_utf8_str_tolower(mem_ctx, orig_name); } errno_t sss_get_cased_name_list(TALLOC_CTX *mem_ctx, const char * const *orig, bool case_sensitive, const char ***_cased) { const char **out; size_t num, i; if (orig == NULL) { *_cased = NULL; return EOK; } for (num=0; orig[num]; num++); /* count the num of strings */ if (num == 0) { *_cased = NULL; return EOK; } out = talloc_array(mem_ctx, const char *, num + 1); if (out == NULL) { return ENOMEM; } for (i = 0; i < num; i++) { out[i] = sss_get_cased_name(out, orig[i], case_sensitive); if (out[i] == NULL) { talloc_free(out); return ENOMEM; } } out[num] = NULL; *_cased = out; return EOK; } static inline const char * safe_fq_str(struct sss_names_ctx *nctx, uint8_t part, const char *str) { return nctx->fq_flags & part ? str : ""; } static inline const char * safe_flat_name(struct sss_names_ctx *nctx, struct sss_domain_info *domain) { const char *s; s = safe_fq_str(nctx, FQ_FMT_FLAT_NAME, domain->flat_name); if (s == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("Flat name requested but domain has no" "flat name set, falling back to domain name\n")); s = domain->name; } return s; } static inline size_t fq_part_len(struct sss_names_ctx *nctx, struct sss_domain_info *dom, uint8_t part, const char *str) { const char *s = str; if (part == FQ_FMT_FLAT_NAME) { s = safe_flat_name(nctx, dom); } return nctx->fq_flags & part ? strlen(s) : 0; } char * sss_tc_fqname(TALLOC_CTX *mem_ctx, struct sss_names_ctx *nctx, struct sss_domain_info *domain, const char *name) { if (domain == NULL || nctx == NULL) return NULL; return talloc_asprintf(mem_ctx, nctx->fq_fmt, safe_fq_str(nctx, FQ_FMT_NAME, name), safe_fq_str(nctx, FQ_FMT_DOMAIN, domain->name), safe_flat_name(nctx, domain)); } int sss_fqname(char *str, size_t size, struct sss_names_ctx *nctx, struct sss_domain_info *domain, const char *name) { if (domain == NULL || nctx == NULL) return -EINVAL; return snprintf(str, size, nctx->fq_fmt, safe_fq_str(nctx, FQ_FMT_NAME, name), safe_fq_str(nctx, FQ_FMT_DOMAIN, domain->name), safe_flat_name(nctx, domain)); } size_t sss_fqdom_len(struct sss_names_ctx *nctx, struct sss_domain_info *domain) { size_t len = fq_part_len(nctx, domain, FQ_FMT_DOMAIN, domain->name); len += fq_part_len(nctx, domain, FQ_FMT_FLAT_NAME, domain->flat_name); return len; }