diff options
Diffstat (limited to 'src/lib/certmap/sss_certmap.c')
-rw-r--r-- | src/lib/certmap/sss_certmap.c | 1398 |
1 files changed, 1398 insertions, 0 deletions
diff --git a/src/lib/certmap/sss_certmap.c b/src/lib/certmap/sss_certmap.c new file mode 100644 index 000000000..0028a120c --- /dev/null +++ b/src/lib/certmap/sss_certmap.c @@ -0,0 +1,1398 @@ +/* + SSSD + + Library for rule based certificate to user mapping + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2017 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 "util/util.h" +#include "util/cert.h" +#include "util/crypto/sss_crypto.h" +#include "lib/certmap/sss_certmap.h" +#include "lib/certmap/sss_certmap_int.h" + +#include "util/crypto/nss/nss_util.h" + +int debug_level; +void sss_debug_fn(const char *file, + long line, + const char *function, + int level, + const char *format, ...) +{ + return; +} + +//#define ERRMSG(text) text "("__FILE__":"__LINE__" "__FUNCTION__")" +#define ERRMSG(text) text + +int sss_certmap_init(TALLOC_CTX *mem_ctx, + sss_certmap_ext_debug *debug, void *debug_priv, + struct sss_certmap_ctx **ctx) +{ + if (ctx == NULL) { + return EINVAL; + } + + *ctx = talloc_zero(mem_ctx, struct sss_certmap_ctx); + if (*ctx == NULL) { + return ENOMEM; + } + + (*ctx)->debug = debug; + (*ctx)->debug_priv = debug_priv; + + CM_DEBUG((*ctx), "sss_certmap initialized.\n"); + return EOK; +} + +void sss_certmap_free_ctx(struct sss_certmap_ctx *ctx) +{ + talloc_free(ctx); +} + +void sss_certmap_free_filter_and_domains(char *filter, char **domains) +{ + talloc_free(filter); + talloc_free(domains); +} + +const char *sss_certmap_err_msg(struct sss_certmap_ctx *ctx) +{ + if (ctx->err_msg != NULL) { + return ctx->err_msg; + } + + return "(no error messages available)"; +} + +static int get_type_prefix(TALLOC_CTX *mem_ctx, const char *match_rule, + char **type, const char **rule_start) +{ + const char *c; + char *delim; + + *type = NULL; + *rule_start = match_rule; + + delim = strchr(match_rule, ':'); + if (delim == NULL) { + /* no type prefix found */ + return 0; + } + + /* rule starts with ':', empty type */ + if (delim == match_rule) { + *rule_start = delim + 1; + return EOK; + } + + for (c = match_rule; c < delim; c++) { + /* type prefix may only contain digits and upper-case ASCII characters */ + if (!(isascii(*c) && (isdigit(*c) || isupper(*c)))) { + /* no type prefix found */ + return 0; + } + } + + *rule_start = delim + 1; + *type = talloc_strndup(mem_ctx, match_rule, (delim - match_rule)); + if (*type == NULL) { + return ENOMEM; + } + + return 0; +} + +static int component_list_destructor(void *data) +{ + struct component_list *comp = talloc_get_type(data, struct component_list); + + if (comp != NULL) { + regfree(&(comp->regexp)); + } + + return 0; +} + +/* + * The syntax of the MIT Kerberos style matching rules is: + * [KRB5:][relation-operator]component-rule ... + * + * where: + * + * relation-operator + * can be either &&, meaning all component rules must match, or ||, + * meaning only one component rule must match. The default is &&. + * + * component-rule + * can be one of the following. Note that there is no punctuation or whitespace between component rules. + * <SUBJECT>regular-expression + * <ISSUER>regular-expression + * <SAN>regular-expression + * <EKU>extended-key-usage + * <KU>key-usage + * + * TODO: + * generic extension handling for e.g. MSFT 1.3.6.1.4.1.311.20.2 + */ + +static int get_comp_value(TALLOC_CTX *mem_ctx, + struct sss_certmap_ctx *ctx, + const char **cur, + struct component_list **_comp) + +{ + struct component_list *comp = NULL; + const char *end; + int ret; + + comp = talloc_zero(mem_ctx, struct component_list); + if (comp == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + + talloc_set_destructor((TALLOC_CTX *) comp, component_list_destructor); + + end = strchr(*cur, '<'); + + if (end == NULL) { + comp->val = talloc_strdup(comp, *cur); + } else { + comp->val = talloc_strndup(comp, *cur, end - *cur); + } + if (comp->val == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + if (*(comp->val) == '\0') { + ctx->err_msg = ERRMSG("Missing component value."); + ret = EINVAL; + goto done; + } + + *cur += strlen(comp->val); + *_comp = comp; + ret = 0; + +done: + if (ret != 0) { + talloc_free(comp); + } + + return ret; +} + +static int parse_krb5_get_eku_value(TALLOC_CTX *mem_ctx, + struct sss_certmap_ctx *ctx, + const char **cur, + struct component_list **_comp) +{ + struct component_list *comp = NULL; + int ret; + char **eku_list; + size_t c; + size_t k; + const char *o; + size_t e = 0; + int eku_list_size; + + struct ext_key_usage { + const char *name; + const char *oid; + } ext_key_usage[] = { + /* RFC 3280 section 4.2.1.13 */ + {"serverAuth", "1.3.6.1.5.5.7.3.1"}, + {"clientAuth", "1.3.6.1.5.5.7.3.2"}, + {"codeSigning", "1.3.6.1.5.5.7.3.3"}, + {"emailProtection", "1.3.6.1.5.5.7.3.4"}, + {"timeStamping", "1.3.6.1.5.5.7.3.8"}, + {"OCSPSigning", "1.3.6.1.5.5.7.3.9"}, + + /* RFC 4556 section 3.2.2 */ + {"KPClientAuth", "1.3.6.1.5.2.3.4"}, + {"pkinit", "1.3.6.1.5.2.3.4"}, + + /* https://support.microsoft.com/en-us/help/287547/object-ids-associated-with-microsoft-cryptography*/ + {"msScLogin", "1.3.6.1.4.1.311.20.2.2"}, + + {NULL ,0} + }; + + ret = get_comp_value(mem_ctx, ctx, cur, &comp); + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to parse regexp."); + goto done; + } + + ret = split_on_separator(mem_ctx, comp->val, ',', true, true, + &eku_list, &eku_list_size); + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to split list."); + CM_DEBUG(ctx, "Failed to split list."); + goto done; + } + + for (c = 0; eku_list[c] != NULL; c++) { + for (k = 0; ext_key_usage[k].name != NULL; k++) { +CM_DEBUG(ctx, "[%s][%s].", eku_list[c], ext_key_usage[k].name); + if (strcasecmp(eku_list[c], ext_key_usage[k].name) == 0) { + if (comp->eku_oid_list == NULL) { + comp->eku_oid_list = talloc_zero_array(comp, const char *, + eku_list_size + 1); + if (comp->eku_oid_list == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + } + + comp->eku_oid_list[e] = talloc_strdup(comp->eku_oid_list, + ext_key_usage[k].oid); + if (comp->eku_oid_list[e] == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + e++; + break; + } + } + + if (ext_key_usage[k].name == NULL) { + /* check for an dotted-decimal OID */ + if (*(eku_list[c]) != '.') { + o = eku_list[c]; + while (*o != '\0') { + if (*o != '.' && !isdigit(*o)) { + break; + } + o++; + } + if (*o == '\0' && *(o - 1) != '.') { + /* looks like a OID, only '.' and digits */ + comp->eku_oid_list[e] = talloc_strdup(comp->eku_oid_list, + eku_list[c]); + if (comp->eku_oid_list[e] == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + e++; + continue; + } + } + CM_DEBUG(ctx, "No matching extended key usage found."); + ret = EINVAL; + goto done; + } + } + + ret = 0; + +done: + if (ret == 0) { + *_comp = comp; + } else { + talloc_free(comp); + } + + return ret; +} + +static int parse_krb5_get_ku_value(TALLOC_CTX *mem_ctx, + struct sss_certmap_ctx *ctx, + const char **cur, + struct component_list **_comp) +{ + struct component_list *comp = NULL; + int ret; + char **ku_list; + size_t c; + size_t k; + + struct key_usage { + const char *name; + uint32_t flag; + } key_usage[] = { + {"digitalSignature" , SSS_KU_DIGITAL_SIGNATURE}, + {"nonRepudiation" , SSS_KU_NON_REPUDIATION}, + {"keyEncipherment" , SSS_KU_KEY_ENCIPHERMENT}, + {"dataEncipherment" , SSS_KU_DATA_ENCIPHERMENT}, + {"keyAgreement" , SSS_KU_KEY_AGREEMENT}, + {"keyCertSign" , SSS_KU_KEY_CERT_SIGN}, + {"cRLSign" , SSS_KU_CRL_SIGN}, + {"encipherOnly" , SSS_KU_ENCIPHER_ONLY}, + {"decipherOnly" , SSS_KU_DECIPHER_ONLY}, + {NULL ,0} + }; + + + ret = get_comp_value(mem_ctx, ctx, cur, &comp); + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to get value."); + CM_DEBUG(ctx, "Failed to get value."); + goto done; + } + + ret = split_on_separator(mem_ctx, comp->val, ',', true, true, + &ku_list, NULL); + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to split list."); + CM_DEBUG(ctx, "Failed to split list."); + goto done; + } + + for (c = 0; ku_list[c] != NULL; c++) { + for (k = 0; key_usage[k].name != NULL; k++) { + if (strcasecmp(ku_list[c], key_usage[k].name) == 0) { + comp->ku |= key_usage[k].flag; + break; + } + } + + if (key_usage[k].name == NULL) { + /* FIXME: add check for numerical ku */ + CM_DEBUG(ctx, "No matching key usage found."); + ret = EINVAL; + goto done; + } + } + + ret = 0; + +done: + if (ret == 0) { + *_comp = comp; + } else { + talloc_free(comp); + } + + return ret; +} + +static int parse_krb5_get_component_value(TALLOC_CTX *mem_ctx, + struct sss_certmap_ctx *ctx, + const char **cur, + struct component_list **_comp) +{ + struct component_list *comp = NULL; + int ret; + + ret = get_comp_value(mem_ctx, ctx, cur, &comp); + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to parse regexp."); + goto done; + } + + ret = regcomp(&(comp->regexp), comp->val, REG_EXTENDED); + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to parse regexp."); + goto done; + } + + ret = 0; + +done: + if (ret == 0) { + *_comp = comp; + } else { + talloc_free(comp); + } + + return ret; +} + +static int parse_krb5_match_rule(struct sss_certmap_ctx *ctx, + const char *rule_start, + struct krb5_match_rule **match_rule) +{ + const char *cur; + struct krb5_match_rule *rule; + struct component_list *comp; + int ret; + + rule = talloc_zero(ctx, struct krb5_match_rule); + if (rule == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + + cur = rule_start; + /* check relation */ + if (strncmp(cur, "&&", 2) == 0) { + rule->r = relation_and; + cur += 2; + } else if (strncmp(cur, "||", 2) == 0) { + rule->r = relation_or; + cur += 2; + } else { + rule->r = relation_and; + } + + while (*cur != '\0') { + /* new component must start with '<' */ + if (*cur != '<') { + ctx->err_msg = ERRMSG("Invalid KRB5 matching rule."); + ret = EINVAL; + goto done; + } + cur++; + + if (strncmp(cur, "ISSUER>", 7) == 0) { + cur += 7; + ret = parse_krb5_get_component_value(rule, ctx, &cur, &comp); + if (ret != 0) { + goto done; + } + DLIST_ADD(rule->issuer, comp); + } else if (strncmp(cur, "SUBJECT>", 8) == 0) { + cur += 8; + ret = parse_krb5_get_component_value(rule, ctx, &cur, &comp); + if (ret != 0) { + goto done; + } + DLIST_ADD(rule->subject, comp); + } else if (strncmp(cur, "KU>", 3) == 0) { + cur += 3; + ret = parse_krb5_get_ku_value(rule, ctx, &cur, &comp); + if (ret != 0) { + goto done; + } + DLIST_ADD(rule->ku, comp); + } else if (strncmp(cur, "EKU>", 4) == 0) { + cur += 4; + ret = parse_krb5_get_eku_value(rule, ctx, &cur, &comp); + if (ret != 0) { + goto done; + } + DLIST_ADD(rule->eku, comp); + } else { + ctx->err_msg = ERRMSG("Invalid KRB5 matching rule."); + ret = EINVAL; + goto done; + } + } + + ret = 0; + +done: + if (ret == 0) { + *match_rule = rule; + } else { + talloc_free(rule); + } + + return ret; +} + +static int parse_match_rule(struct sss_certmap_ctx *ctx, const char *match_rule, + struct krb5_match_rule **parsed_match_rule) +{ + int ret; + char *type; + const char *rule_start; + + ret = get_type_prefix(ctx, match_rule, &type, &rule_start); + if (ret != EOK) { + ctx->err_msg = ERRMSG("Failed to read rule type."); + goto done; + } + + if (type == NULL || strcmp(type, "KRB5") == 0) { + ret = parse_krb5_match_rule(ctx, rule_start, parsed_match_rule); + if (ret != EOK) { + ctx->err_msg = ERRMSG("Failed to parse KRB5 matching rule.\n"); + goto done; + } + } else { + ctx->err_msg = ERRMSG("Unsupported matching rule type.\n"); + ret = ESRCH; + goto done; + } + + ret = EOK; + +done: + talloc_free(type); + + return ret; +} + +struct template_table { + const char *name; + const char **attr_name; + const char **conversion; +}; + +const char *empty[] = {NULL}; +const char *name_attr[] = {"name", NULL}; +const char *x500_conv[] = {"x500", "x500ad", NULL}; +const char *bin_conv[] = {"bin", "base64", NULL}; + +struct template_table template_table[] = { + {"issuer_dn", empty, x500_conv}, + {"subject_dn", empty, x500_conv}, + {"subject_nt_principal", name_attr, empty}, + {"cert", empty, bin_conv}, + {NULL, NULL, NULL}}; + +static int check_parsed_template(struct sss_certmap_ctx *ctx, + struct parsed_template *parsed) +{ + size_t n; + size_t a; + size_t c; + bool attr_name_valid = false; + bool conversion_valid = false; + + for (n = 0; template_table[n].name != NULL; n++) { + if (strcmp(template_table[n].name, parsed->name) != 0) { + continue; + } + + if (parsed->attr_name != NULL) { + for (a = 0; template_table[n].attr_name[a] != NULL; a++) { + if (strcmp(template_table[n].attr_name[a], + parsed->attr_name) == 0) { + attr_name_valid = true; + break; + } + } + } else { + attr_name_valid = true; + } + + if (parsed->conversion != NULL) { + for (c = 0; template_table[n].conversion[c] != NULL; c++) { + if (strcmp(template_table[n].conversion[c], + parsed->conversion) == 0) { + conversion_valid = true; + break; + } + } + } else { + conversion_valid = true; + } + + if (attr_name_valid && conversion_valid) { + return 0; + } + } + + return EINVAL; +} + +static int parse_template(TALLOC_CTX *mem_ctx, struct sss_certmap_ctx *ctx, + const char *template, + struct parsed_template **parsed_template) +{ + int ret; + struct parsed_template *parsed = NULL; + const char *dot; + const char *excl; + const char *p; + + parsed = talloc_zero(mem_ctx, struct parsed_template); + if (parsed == NULL) { + //ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + + dot = strchr(template, '.'); + if (dot != NULL) { + p = strchr(dot + 1, '.'); + if (p != NULL) { + //ctx->err_msg = ERRMSG("Only one '.' allowed in template."); + CM_DEBUG(ctx, "Only one '.' allowed in template."); + ret = EINVAL; + goto done; + } + + if (dot == template) { + //ctx->err_msg = ERRMSG("Missing name in template."); + CM_DEBUG(ctx, "Missing name in template."); + ret = EINVAL; + goto done; + } + } + + excl = strchr(template, '!'); + if (excl != NULL) { + p = strchr(excl + 1, '!'); + if (p != NULL) { + //ctx->err_msg = ERRMSG("Only one '!' allowed in template."); + CM_DEBUG(ctx, "Only one '!' allowed in template."); + ret = EINVAL; + goto done; + } + + if (excl == template) { + //ctx->err_msg = ERRMSG("Missing name in template."); + CM_DEBUG(ctx, "Missing name in template."); + ret = EINVAL; + goto done; + } + } + + if (excl != NULL && excl[1] != '\0') { + parsed->conversion = talloc_strdup(parsed, excl + 1); + if (parsed->conversion == NULL) { + //ctx->err_msg = ERRMSG("Memory allocation failed."); + CM_DEBUG(ctx, "Memory allocation failed."); + ret = ENOMEM; + goto done; + } + } + + if (dot != NULL && dot[1] != '\0' && dot[1] != '!') { + if (excl == NULL) { + parsed->attr_name = talloc_strdup(parsed, dot + 1); + } else { + parsed->attr_name = talloc_strndup(parsed, dot + 1, + (excl - dot - 1)); + } + if (parsed->attr_name == NULL) { + CM_DEBUG(ctx, "Memory allocation failed."); + ret = ENOMEM; + goto done; + } + } + + if (dot != NULL) { + parsed->name = talloc_strndup(parsed, template, (dot - template)); + } else if (excl != NULL) { + parsed->name = talloc_strndup(parsed, template, (excl - template)); + } else { + parsed->name = talloc_strdup(parsed, template); + } + if (parsed->name == NULL) { + //ctx->err_msg = ERRMSG("Memory allocation failed."); + CM_DEBUG(ctx, "Memory allocation failed."); + ret = ENOMEM; + goto done; + } + + ret = check_parsed_template(ctx, parsed); + if (ret != 0) { + CM_DEBUG(ctx, "Parse template invalid."); + goto done; + } + + ret = 0; + +done: + if (ret == 0) { + *parsed_template = parsed; + } else { + talloc_free(parsed); + } + + return ret; +} + +static int add_comp(struct sss_certmap_ctx *ctx, struct ldap_mapping_rule *rule, + const char *string, enum comp_type type) +{ + int ret; + struct ldap_mapping_rule_comp *comp; + + comp = talloc_zero(rule, struct ldap_mapping_rule_comp); + if (comp == NULL) { + return ENOMEM; + } + + comp->type = type; + comp->val = talloc_strdup(comp, string); + if (comp->val == NULL) { + talloc_free(comp); + return ENOMEM; + } + + if (type == comp_template) { + ret = parse_template(comp, ctx, string, &comp->parsed_template); + if (ret != 0) { + talloc_free(comp); + return ret; + } + } + + DLIST_ADD_END(rule->list, comp, struct ldap_mapping_rule_comp *); + + return 0; +} + +static int add_string(struct sss_certmap_ctx *ctx, + struct ldap_mapping_rule *rule, const char *string) +{ + return add_comp(ctx, rule, string, comp_string); +} + +static int add_template(struct sss_certmap_ctx *ctx, + struct ldap_mapping_rule *rule, const char *string) +{ + return add_comp(ctx, rule, string, comp_template); +} + +static int parse_ldap_mapping_rule(struct sss_certmap_ctx *ctx, + const char *rule_start, + struct ldap_mapping_rule **mapping_rule) +{ + size_t c; + const char *cur; + char *tmp_string = NULL; + size_t tmp_string_size; + struct ldap_mapping_rule *rule = NULL; + int ret; + bool in_template = false; + + rule = talloc_zero(ctx, struct ldap_mapping_rule); + if (rule == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + + tmp_string_size = strlen(rule_start) + 1; + tmp_string = talloc_zero_size(ctx, tmp_string_size); + if (tmp_string == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + + cur = rule_start; + c = 0; + + while (*cur != '\0') { + if (c > tmp_string_size) { + ctx->err_msg = ERRMSG("Cannot parse mapping rule."); + ret = EIO; + goto done; + } + switch (*cur) { + case '{': + if (in_template) { + ctx->err_msg = ERRMSG("'{' not allowed in templates."); + ret = EINVAL; + goto done; + } + if (cur[1] == '{') { + /* Add only a single '{' to the output */ + tmp_string[c] = '{'; + c++; + cur += 2; + } else { + if (c != 0) { + ret = add_string(ctx, rule, tmp_string); + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to add string."); + ret = EINVAL; + goto done; + } + memset(tmp_string, 0, tmp_string_size); + c = 0; + } + cur++; + in_template = true; + } + break; + case '}': + if (cur[1] == '}') { + if (in_template) { + ctx->err_msg = ERRMSG("'}}' not allowed in templates."); + ret = EINVAL; + goto done; + } else { + /* Add only a single '}' to the output */ + tmp_string[c] = '}'; + c++; + cur += 2; + } + } else { + ret = add_template(ctx, rule, tmp_string); + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to add template."); + ret = EINVAL; + goto done; + } + memset(tmp_string, 0, tmp_string_size); + c = 0; + cur++; + in_template = false; + } + break; + default: + tmp_string[c] = *cur; + c++; + cur++; + } + } + if (in_template) { + ctx->err_msg = ERRMSG("Rule ended inside template."); + ret = EINVAL; + goto done; + } + if (c != 0) { + ret = add_string(ctx, rule, tmp_string); + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to add string."); + ret = EINVAL; + goto done; + } + } + + ret = 0; + +done: + if (ret == 0) { + *mapping_rule = rule; + } else { + talloc_free(rule); + } + + talloc_free(tmp_string); + + return ret; +} + +static int parse_mapping_rule(struct sss_certmap_ctx *ctx, + const char *mapping_rule, + struct ldap_mapping_rule **parsed_mapping_rule) +{ + int ret; + char *type; + const char *rule_start; + + ret = get_type_prefix(ctx, mapping_rule, &type, &rule_start); + if (ret != EOK) { + ctx->err_msg = ERRMSG("Failed to read rule type."); + goto done; + } + + if (type == NULL || strcmp(type, "LDAP") == 0) { + ret = parse_ldap_mapping_rule(ctx, rule_start, parsed_mapping_rule); + if (ret != EOK) { + ctx->err_msg = ERRMSG("Failed to parse LDAP mapping rule.\n"); + goto done; + } + } else { + ctx->err_msg = ERRMSG("Unsupported mapping rule type.\n"); + ret = ESRCH; + goto done; + } + + ret = EOK; + +done: + talloc_free(type); + + return ret; +} + +int sss_certmap_add_rule(struct sss_certmap_ctx *ctx, + unsigned int priority, const char *match_rule, + const char *map_rule, const char **domains) +{ + size_t c; + int ret; + struct match_map_rule *rule; + struct TALLOC_CTX *tmp_ctx; + struct priority_list *p; + struct priority_list *p_new; + struct krb5_match_rule *parsed_match_rule; + struct ldap_mapping_rule *parsed_mapping_rule; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + return ENOMEM; + } + + rule = talloc_zero(tmp_ctx, struct match_map_rule); + if (rule == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + + rule->priority = priority; + + if (match_rule == NULL) { + match_rule = DEFAULT_MATCH_RULE; + } + ret = parse_match_rule(ctx, match_rule, &parsed_match_rule); + if (ret == 0) { + rule->parsed_match_rule = talloc_steal(rule, parsed_match_rule); + rule->match_rule = talloc_strdup(rule, match_rule); + if (rule->match_rule == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + } else if (ret == ESRCH) { + /* report unsupported rules */ + goto done; + } else { + goto done; + } + + if (map_rule == NULL) { + map_rule = DEFAULT_MAP_RULE; + } + ret = parse_mapping_rule(ctx, map_rule, &parsed_mapping_rule); + if (ret == 0) { + rule->parsed_mapping_rule = talloc_steal(rule, parsed_mapping_rule); + rule->map_rule = talloc_strdup(rule, map_rule); + if (rule->map_rule == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + } else if (ret == ESRCH) { + /* report unsupported rules */ + goto done; + } else { + goto done; + } + + if (domains != NULL && *domains != NULL) { + for (c = 0; domains[c] != NULL; c++); + rule->domains = talloc_zero_array(rule, char *, c + 1); + if (rule->domains == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + for (c = 0; domains[c] != NULL; c++) { + rule->domains[c] = talloc_strdup(rule->domains, domains[c]); + if (rule->domains[c] == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + } + } + + if (ctx->prio_list == NULL) { + ctx->prio_list = talloc_zero(ctx, struct priority_list); + if (ctx->prio_list == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + + ctx->prio_list->priority = rule->priority; + ctx->prio_list->rule_list = rule; + } else { + for (p = ctx->prio_list; p != NULL && p->priority < rule->priority; + p = p->next); + if (p != NULL && p->priority == priority) { + DLIST_ADD(p->rule_list, rule); + } else { + p_new = talloc_zero(ctx, struct priority_list); + if (p_new == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + + p_new->priority = rule->priority; + p_new->rule_list = rule; + + if (p == NULL) { + DLIST_ADD_END(ctx->prio_list, p_new, struct priority_list *); + } else if (p->prev == NULL) { + DLIST_ADD(ctx->prio_list, p_new); + } else { + DLIST_ADD_AFTER(ctx->prio_list, p_new, p->prev); + } + } + } + + talloc_steal(ctx, rule); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static int expand_cert(struct sss_certmap_ctx *ctx, + struct parsed_template *parsed_template, + struct sss_cert_content *cert_content, + char **expanded) +{ + int ret; + char *tmp_str = NULL; + + if (parsed_template->conversion == NULL + || strcmp(parsed_template->conversion, "bin") == 0) { + ret = bin_to_ldap_filter_value(ctx, cert_content->cert_der, + cert_content->cert_der_size, &tmp_str); + if (ret != 0) { + ctx->err_msg = ERRMSG("bin conversion failed."); + CM_DEBUG(ctx, "bin conversion failed."); + goto done; + } + } else if (strcmp(parsed_template->conversion, "base64") == 0) { + tmp_str = sss_base64_encode(ctx, cert_content->cert_der, + cert_content->cert_der_size); + if (tmp_str == NULL) { + ctx->err_msg = ERRMSG("base64 conversion failed."); + CM_DEBUG(ctx, "base64 conversion failed."); + ret = ENOMEM; + goto done; + } + } else { + ctx->err_msg = ERRMSG("Unsupported conversion."); + CM_DEBUG(ctx, "Unsupported conversion."); + ret = EINVAL; + goto done; + } + + ret = 0; + +done: + if (ret == 0) { + *expanded = tmp_str; + } else { + talloc_free(tmp_str); + } + + return ret; +} + +static int get_dn_str(struct sss_certmap_ctx *ctx, const char *conversion, + const char **rdn_list, char **result) +{ + char *str = NULL; + size_t c; + int ret; + + str = talloc_strdup(ctx, ""); + if (str == NULL) { + ret = ENOMEM; + goto done; + } + if (conversion == NULL || strcmp(conversion, "ldap") == 0) { + for (c = 0; rdn_list[c] != NULL; c++); + while (c != 0) { + c--; + str = talloc_asprintf_append(str, "%s%s", + (rdn_list[c + 1] == NULL) ? "" : ",", + rdn_list[c]); + if (str == NULL) { + ret = ENOMEM; + goto done; + } + }; + } else if (strcmp(conversion, "x500") == 0) { + for (c = 0; rdn_list[c] != NULL; c++) { + str = talloc_asprintf_append(str, "%s%s", (c == 0) ? "" : ",", + rdn_list[c]); + if (str == NULL) { + ret = ENOMEM; + goto done; + } + } + } else { + ret = EINVAL; + goto done; + } + + ret = 0; + +done: + if (ret == 0) { + *result = str; + } else { + talloc_free(str); + } + + return ret; +} + +static int expand_template(struct sss_certmap_ctx *ctx, + struct parsed_template *parsed_template, + struct sss_cert_content *cert_content, + char **expanded) +{ + int ret; + char *exp; + + if (strcmp("issuer_dn", parsed_template->name) == 0) { + //exp = talloc_strdup(ctx, cert_content->issuer_str); + ret = get_dn_str(ctx, parsed_template->conversion, + cert_content->issuer_rdn_list, &exp); + } else if (strcmp("subject_dn", parsed_template->name) == 0) { + //exp = talloc_strdup(ctx, cert_content->subject_str); + ret = get_dn_str(ctx, parsed_template->conversion, + cert_content->subject_rdn_list, &exp); + } else if (strcmp("cert", parsed_template->name) == 0) { + ret = expand_cert(ctx, parsed_template, cert_content, &exp); + } else { + ctx->err_msg = ERRMSG("Unsupported template name."); + CM_DEBUG(ctx, "Unsupported template name."); + ret = EINVAL; + goto done; + } + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to expand template."); + CM_DEBUG(ctx, "Failed to expand [%s] template.", parsed_template->name); + goto done; + } + + if (exp == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + + ret = 0; + +done: + if (ret == 0) { + *expanded = exp; + } else { + talloc_free(exp); + } + + return ret; +} + +static int get_filter(struct sss_certmap_ctx *ctx, + struct ldap_mapping_rule *parsed_mapping_rule, + struct sss_cert_content *cert_content, + char **filter) +{ + struct ldap_mapping_rule_comp *comp; + char *result = NULL; + char *expanded = NULL; + int ret; + + result = talloc_strdup(ctx, ""); + if (result == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + return ENOMEM; + } + + for (comp = parsed_mapping_rule->list; comp != NULL; comp = comp->next) { + if (comp->type == comp_string) { + result = talloc_strdup_append(result, comp->val); + } else if (comp->type == comp_template) { + ret = expand_template(ctx, comp->parsed_template, cert_content, + &expanded); + if (ret != 0) { + CM_DEBUG(ctx, "Failed to expanded template."); + ctx->err_msg = ERRMSG("Failed to expand template."); + goto done; + } + + result = talloc_strdup_append(result, expanded); + talloc_free(expanded); + expanded = NULL; + if (result == NULL) { + ctx->err_msg = ERRMSG("Memory allocation failed."); + ret = ENOMEM; + goto done; + } + } else { + ret = EINVAL; + CM_DEBUG(ctx, "Unsupported component type."); + ctx->err_msg = ERRMSG("Unsupported component type."); + goto done; + } + } + + ret = 0; +done: + talloc_free(expanded); + if (ret == 0) { + *filter = result; + } else { + talloc_free(result); + } + + return ret; +} + +static int do_match(struct krb5_match_rule *parsed_match_rule, + struct sss_cert_content *cert_content) +{ + struct component_list *comp; + bool match = false; + + if (parsed_match_rule == NULL || cert_content == NULL) { + return EINVAL; + } + + /* Issuer */ + if (cert_content->issuer_str != NULL) { + for (comp = parsed_match_rule->issuer; comp != NULL; comp = comp->next) { + match = (regexec(&(comp->regexp), cert_content->issuer_str, + 0, NULL, 0) == 0); + if (match && parsed_match_rule->r == relation_or) { + /* match */ + return 0; + } else if (!match && parsed_match_rule->r == relation_and) { + /* no match */ + return ENOENT; + } + + } + } + + if (cert_content->subject_str != NULL) { + for (comp = parsed_match_rule->subject; comp != NULL; comp = comp->next) { + if (match && parsed_match_rule->r == relation_or) { + match = (regexec(&(comp->regexp), cert_content->issuer_str, + 0, NULL, 0) == 0); + /* match */ + return 0; + } else if (!match && parsed_match_rule->r == relation_and) { + /* no match */ + return ENOENT; + } + + } + } + + if (match) { + /* match */ + return 0; + } + + /* no match */ + return ENOENT; +} + +int sss_certmap_match_cert(struct sss_certmap_ctx *ctx, + uint8_t *der_cert, size_t der_size) +{ + int ret; + struct match_map_rule *r; + struct priority_list *p; + struct sss_cert_content *cert_content = NULL; + + ret = sss_cert_get_content(ctx, der_cert, der_size, &cert_content); + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to get certificate content."); + return ret; + } + + for (p = ctx->prio_list; p != NULL; p = p->next) { + for (r = p->rule_list; r != NULL; r = r->next) { + ret = do_match(r->parsed_match_rule, cert_content); + if (ret == 0) { + /* match */ + goto done; + } + } + } + + ret = ENOENT; +done: + talloc_free(cert_content); + + return ret; +} + +int sss_certmap_get_search_filter(struct sss_certmap_ctx *ctx, + uint8_t *der_cert, size_t der_size, + char **_filter, char ***_domains) +{ + int ret; + struct match_map_rule *r; + struct priority_list *p; + struct sss_cert_content *cert_content = NULL; + char *filter = NULL; + char **domains = NULL; + size_t c; + + if (_filter == NULL || _domains == NULL) { + return EINVAL; + } + + ret = sss_cert_get_content(ctx, der_cert, der_size, &cert_content); + if (ret != 0) { + ctx->err_msg = ERRMSG("Failed to get certificate content."); + return ret; + } + + for (p = ctx->prio_list; p != NULL; p = p->next) { + for (r = p->rule_list; r != NULL; r = r->next) { + ret = do_match(r->parsed_match_rule, cert_content); + if (ret == 0) { + /* match */ + ret = get_filter(ctx, r->parsed_mapping_rule, cert_content, + &filter); + if (ret != 0) { + CM_DEBUG(ctx, "Failed to get filter"); + ctx->err_msg = ERRMSG("Failed to get filter."); + goto done; + } + + if (r->domains != NULL) { + for (c = 0; r->domains[c] != NULL; c++); + domains = talloc_zero_array(ctx, char *, c + 1); + if (domains == NULL) { + ctx->err_msg = ERRMSG("Memory allocation false."); + ret = ENOMEM; + goto done; + } + + for (c = 0; r->domains[c] != NULL; c++) { + domains[c] = talloc_strdup(domains, r->domains[c]); + if (domains[c] == NULL) { + ctx->err_msg = ERRMSG("Memory allocation false."); + ret = ENOMEM; + goto done; + } + } + } + + ret = 0; + goto done; + } + } + } + + ret = ENOENT; + +done: + talloc_free(cert_content); + if (ret == 0) { + *_filter = filter; + *_domains = domains; + } else { + talloc_free(filter); + talloc_free(domains); + } + + return ret; +} |