From 487b6820f6c6e5d0ce1bc515b2180fa5533a6a50 Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Sun, 15 Mar 2015 18:20:05 -0500 Subject: [PATCH] Revamp name canonicalization code --- lib/asn1/krb5.asn1 | 4 +- lib/gssapi/krb5/acquire_cred.c | 2 +- lib/gssapi/krb5/canonicalize_name.c | 2 +- lib/gssapi/krb5/import_name.c | 13 +- lib/gssapi/krb5/init_sec_context.c | 4 +- lib/krb5/context.c | 1 + lib/krb5/get_cred.c | 125 ++-- lib/krb5/init_creds_pw.c | 13 + lib/krb5/keytab.c | 33 +- lib/krb5/krb5.h | 11 +- lib/krb5/krb5_locl.h | 1 + lib/krb5/principal.c | 915 +++++++++++++--------------- tests/kdc/krb5-canon2.conf.in | 2 +- 13 files changed, 553 insertions(+), 573 deletions(-) diff --git a/lib/asn1/krb5.asn1 b/lib/asn1/krb5.asn1 index 88c4f687a..a38c4d85f 100644 --- a/lib/asn1/krb5.asn1 +++ b/lib/asn1/krb5.asn1 @@ -105,9 +105,9 @@ NAME-TYPE ::= INTEGER { KRB5_NT_MS_PRINCIPAL_AND_ID(-129), -- NT style name and SID KRB5_NT_NTLM(-1200), -- NTLM name, realm is domain KRB5_NT_X509_GENERAL_NAME(-1201), -- x509 general name (base64 encoded) - KRB5_NT_GSS_HOSTBASED_SERVICE(-1202), + KRB5_NT_GSS_HOSTBASED_SERVICE(-1202), -- not used; remove KRB5_NT_CACHE_UUID(-1203), -- name is actually a uuid pointing to ccache, use client name in cache - KRB5_NT_SRV_HST_NEEDS_CANON (-195894762) -- -(0x0bad1dea) + KRB5_NT_SRV_HST_NEEDS_CANON (-195894762) -- Internal: indicates that name canonicalization is needed } -- message types diff --git a/lib/gssapi/krb5/acquire_cred.c b/lib/gssapi/krb5/acquire_cred.c index 03d2b1b79..3306eaf24 100644 --- a/lib/gssapi/krb5/acquire_cred.c +++ b/lib/gssapi/krb5/acquire_cred.c @@ -399,7 +399,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_ext HEIMDAL_MUTEX_init(&handle->cred_id_mutex); if (desired_name != GSS_C_NO_NAME) { - ret = _gsskrb5_canon_name(minor_status, context, NULL, + ret = _gsskrb5_canon_name(minor_status, context, desired_name, &handle->principal); if (ret) { HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); diff --git a/lib/gssapi/krb5/canonicalize_name.c b/lib/gssapi/krb5/canonicalize_name.c index a8450ba9e..62de42358 100644 --- a/lib/gssapi/krb5/canonicalize_name.c +++ b/lib/gssapi/krb5/canonicalize_name.c @@ -48,7 +48,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_canonicalize_name ( GSSAPI_KRB5_INIT (&context); - ret = _gsskrb5_canon_name(minor_status, context, NULL, input_name, &name); + ret = _gsskrb5_canon_name(minor_status, context, input_name, &name); if (ret) return ret; diff --git a/lib/gssapi/krb5/import_name.c b/lib/gssapi/krb5/import_name.c index fc4a41570..716d353a4 100644 --- a/lib/gssapi/krb5/import_name.c +++ b/lib/gssapi/krb5/import_name.c @@ -83,7 +83,6 @@ import_krb5_name (OM_uint32 *minor_status, OM_uint32 _gsskrb5_canon_name(OM_uint32 *minor_status, krb5_context context, - krb5_const_principal sourcename, gss_const_name_t targetname, krb5_principal *out) { krb5_const_principal p = (krb5_const_principal)targetname; @@ -121,10 +120,10 @@ _gsskrb5_canon_name(OM_uint32 *minor_status, krb5_context context, static OM_uint32 -import_hostbased_name (OM_uint32 *minor_status, - krb5_context context, - const gss_buffer_t input_name_buffer, - gss_name_t *output_name) +import_hostbased_name(OM_uint32 *minor_status, + krb5_context context, + const gss_buffer_t input_name_buffer, + gss_name_t *output_name) { krb5_principal princ = NULL; krb5_error_code kerr; @@ -146,7 +145,7 @@ import_hostbased_name (OM_uint32 *minor_status, host = p + 1; } - kerr = krb5_make_principal(context, &princ, NULL, tmp, host, NULL); + kerr = krb5_make_principal(context, &princ, "", tmp, host, NULL); free (tmp); *minor_status = kerr; if (kerr == KRB5_PARSE_ILLCHAR || kerr == KRB5_PARSE_MALFORMED) @@ -154,7 +153,7 @@ import_hostbased_name (OM_uint32 *minor_status, else if (kerr) return GSS_S_FAILURE; - krb5_principal_set_type(context, princ, KRB5_NT_SRV_HST_NEEDS_CANON); + krb5_principal_set_type(context, princ, KRB5_NT_SRV_HST); *output_name = (gss_name_t)princ; return 0; diff --git a/lib/gssapi/krb5/init_sec_context.c b/lib/gssapi/krb5/init_sec_context.c index edc9346d1..4db542ba6 100644 --- a/lib/gssapi/krb5/init_sec_context.c +++ b/lib/gssapi/krb5/init_sec_context.c @@ -224,8 +224,8 @@ gsskrb5_get_creds( ctx->kcred = NULL; } - ret = _gsskrb5_canon_name(minor_status, context, ctx->source, - target_name, &ctx->target); + ret = _gsskrb5_canon_name(minor_status, context, target_name, + &ctx->target); if (ret) return ret; diff --git a/lib/krb5/context.c b/lib/krb5/context.c index bd344f1c5..5a69ea03d 100644 --- a/lib/krb5/context.c +++ b/lib/krb5/context.c @@ -601,6 +601,7 @@ krb5_copy_context(krb5_context context, krb5_context *out) KRB5_LIB_FUNCTION void KRB5_LIB_CALL krb5_free_context(krb5_context context) { + _krb5_free_name_canon_rules(context, context->name_canon_rules); if (context->default_cc_name) free(context->default_cc_name); if (context->default_cc_name_env) diff --git a/lib/krb5/get_cred.c b/lib/krb5/get_cred.c index d417eef2e..2aa425f85 100644 --- a/lib/krb5/get_cred.c +++ b/lib/krb5/get_cred.c @@ -1159,9 +1159,8 @@ check_cc(krb5_context context, krb5_flags options, krb5_ccache ccache, { krb5_error_code ret; krb5_timestamp now; - krb5_times save_times; - - save_times = in_creds->times; + krb5_times save_times = in_creds->times; + NAME_TYPE save_type = in_creds->server->name.name_type; krb5_timeofday(context, &now); @@ -1171,12 +1170,20 @@ check_cc(krb5_context context, krb5_flags options, krb5_ccache ccache, krb5_timeofday(context, &in_creds->times.endtime); options |= KRB5_TC_MATCH_TIMES; } + + if (save_type == KRB5_NT_SRV_HST_NEEDS_CANON) { + /* Avoid name canonicalization in krb5_cc_retrieve_cred() */ + krb5_principal_set_type(context, in_creds->server, KRB5_NT_SRV_HST); + } + ret = krb5_cc_retrieve_cred(context, ccache, (options & - (KRB5_TC_MATCH_KEYTYPE | + (KRB5_TC_DONT_MATCH_REALM | + KRB5_TC_MATCH_KEYTYPE | KRB5_TC_MATCH_TIMES)), in_creds, out_creds); + in_creds->server->name.name_type = save_type; in_creds->times = save_times; return ret; } @@ -1185,23 +1192,19 @@ static void store_cred(krb5_context context, krb5_ccache ccache, krb5_const_principal server_princ, krb5_creds *creds) { - krb5_error_code ret; - krb5_principal tmp_princ = creds->server; - krb5_principal p; + if (strcmp(server_princ->realm, "") == 0) { + krb5_principal tmp_princ = creds->server; + /* + * Store the cred with the pre-canon server princ first so it + * can be found quickly in the future. + */ + creds->server = (krb5_principal)server_princ; + krb5_cc_store_cred(context, ccache, creds); + creds->server = tmp_princ; + /* Then store again with the canonicalized server princ */ + } krb5_cc_store_cred(context, ccache, creds); - if (strcmp(server_princ->realm, "") != 0) - return; - - ret = krb5_copy_principal(context, server_princ, &p); - if (ret) - return; - if (p->name.name_type == KRB5_NT_SRV_HST_NEEDS_CANON) - p->name.name_type = KRB5_NT_SRV_HST; - creds->server = p; - krb5_cc_store_cred(context, ccache, creds); - creds->server = tmp_princ; - krb5_free_principal(context, p); } @@ -1216,8 +1219,9 @@ krb5_get_credentials_with_flags(krb5_context context, krb5_error_code ret; krb5_name_canon_iterator name_canon_iter = NULL; krb5_name_canon_rule_options rule_opts; + krb5_const_principal try_princ = NULL; + krb5_principal save_princ = in_creds->server; krb5_creds **tgts; - krb5_creds *try_creds; krb5_creds *res_creds; int i; @@ -1233,15 +1237,7 @@ krb5_get_credentials_with_flags(krb5_context context, if (res_creds == NULL) return krb5_enomem(context); - if (in_creds->server->name.name_type == KRB5_NT_SRV_HST_NEEDS_CANON) { - ret = check_cc(context, options, ccache, in_creds, res_creds); - if (ret == 0) { - *out_creds = res_creds; - return 0; - } - } - - ret = krb5_name_canon_iterator_start(context, NULL, in_creds, + ret = krb5_name_canon_iterator_start(context, in_creds->server, &name_canon_iter); if (ret) return ret; @@ -1249,10 +1245,12 @@ krb5_get_credentials_with_flags(krb5_context context, next_rule: krb5_free_cred_contents(context, res_creds); memset(res_creds, 0, sizeof (*res_creds)); - ret = krb5_name_canon_iterate_creds(context, &name_canon_iter, &try_creds, - &rule_opts); + ret = krb5_name_canon_iterate(context, &name_canon_iter, &try_princ, + &rule_opts); + in_creds->server = rk_UNCONST(try_princ); if (ret) goto out; + if (name_canon_iter == NULL) { if (options & KRB5_GC_CACHED) ret = KRB5_CC_NOTFOUND; @@ -1261,14 +1259,15 @@ next_rule: goto out; } - ret = check_cc(context, options, ccache, try_creds, res_creds); + ret = check_cc(context, options, ccache, in_creds, res_creds); if (ret == 0) { *out_creds = res_creds; + res_creds = NULL; goto out; } else if(ret != KRB5_CC_END) { goto out; } - if(options & KRB5_GC_CACHED) + if (options & KRB5_GC_CACHED) goto next_rule; if(options & KRB5_GC_USER_USER) @@ -1278,26 +1277,28 @@ next_rule: tgts = NULL; ret = _krb5_get_cred_kdc_any(context, flags, ccache, - try_creds, NULL, NULL, out_creds, &tgts); + in_creds, NULL, NULL, out_creds, &tgts); for (i = 0; tgts && tgts[i]; i++) { if ((options & KRB5_GC_NO_STORE) == 0) krb5_cc_store_cred(context, ccache, tgts[i]); krb5_free_creds(context, tgts[i]); } free(tgts); + + /* We don't yet have TGS w/ FAST, so we can't protect KBR-ERRORs */ if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN && - !(rule_opts & KRB5_NCRO_SECURE)) + !(rule_opts & KRB5_NCRO_USE_FAST)) goto next_rule; if(ret == 0 && (options & KRB5_GC_NO_STORE) == 0) store_cred(context, ccache, in_creds->server, *out_creds); out: + in_creds->server = save_princ; + krb5_free_creds(context, res_creds); krb5_free_name_canon_iterator(context, name_canon_iter); - if (ret) { - krb5_free_creds(context, res_creds); + if (ret) return not_found(context, in_creds->server, ret); - } return 0; } @@ -1419,8 +1420,8 @@ krb5_get_creds(krb5_context context, krb5_creds in_creds; krb5_error_code ret; krb5_creds **tgts; - krb5_creds *try_creds; krb5_creds *res_creds; + krb5_const_principal try_princ = NULL; krb5_name_canon_iterator name_canon_iter = NULL; krb5_name_canon_rule_options rule_opts; int i; @@ -1456,25 +1457,18 @@ krb5_get_creds(krb5_context context, options |= KRB5_TC_MATCH_KEYTYPE; } - /* Check for entry in ccache */ - if (inprinc->name.name_type == KRB5_NT_SRV_HST_NEEDS_CANON) { - ret = check_cc(context, options, ccache, &in_creds, res_creds); - if (ret == 0) { - *out_creds = res_creds; - goto out; - } - } - - ret = krb5_name_canon_iterator_start(context, NULL, &in_creds, + ret = krb5_name_canon_iterator_start(context, in_creds.server, &name_canon_iter); if (ret) goto out; next_rule: - ret = krb5_name_canon_iterate_creds(context, &name_canon_iter, &try_creds, - &rule_opts); + ret = krb5_name_canon_iterate(context, &name_canon_iter, &try_princ, + &rule_opts); + in_creds.server = rk_UNCONST(try_princ); if (ret) - return ret; + goto out; + if (name_canon_iter == NULL) { if (options & KRB5_GC_CACHED) ret = KRB5_CC_NOTFOUND; @@ -1482,23 +1476,25 @@ next_rule: ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto out; } - ret = check_cc(context, options, ccache, try_creds, res_creds); + + ret = check_cc(context, options, ccache, &in_creds, res_creds); if (ret == 0) { *out_creds = res_creds; + res_creds = NULL; goto out; - } else if(ret != KRB5_CC_END) { + } else if (ret != KRB5_CC_END) { goto out; } - if(options & KRB5_GC_CACHED) + if (options & KRB5_GC_CACHED) goto next_rule; - if (try_creds->server->name.name_type == KRB5_NT_SRV_HST) + if (try_princ->name.name_type == KRB5_NT_SRV_HST) flags.b.canonicalize = 1; if (rule_opts & KRB5_NCRO_NO_REFERRALS) flags.b.canonicalize = 0; else flags.b.canonicalize = (options & KRB5_GC_CANONICALIZE) ? 1 : 0; - if(options & KRB5_GC_USER_USER) { + if (options & KRB5_GC_USER_USER) { flags.b.enc_tkt_in_skey = 1; options |= KRB5_GC_NO_STORE; } @@ -1513,7 +1509,7 @@ next_rule: tgts = NULL; ret = _krb5_get_cred_kdc_any(context, flags, ccache, - try_creds, opt ? opt->self : 0, + &in_creds, opt ? opt->self : 0, opt ? opt->ticket : 0, out_creds, &tgts); for (i = 0; tgts && tgts[i]; i++) { @@ -1523,21 +1519,20 @@ next_rule: } free(tgts); + /* We don't yet have TGS w/ FAST, so we can't protect KBR-ERRORs */ if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN && - !(rule_opts & KRB5_NCRO_SECURE)) + !(rule_opts & KRB5_NCRO_USE_FAST)) goto next_rule; - if(ret == 0 && (options & KRB5_GC_NO_STORE) == 0) + if (ret == 0 && (options & KRB5_GC_NO_STORE) == 0) store_cred(context, ccache, inprinc, *out_creds); out: - if (ret) { - krb5_free_creds(context, res_creds); - ret = not_found(context, inprinc, ret); - } + krb5_free_creds(context, res_creds); krb5_free_principal(context, in_creds.client); krb5_free_name_canon_iterator(context, name_canon_iter); - _krb5_debug(context, 5, "krb5_get_creds: ret = %d", ret); + if (ret) + return not_found(context, inprinc, ret); return ret; } diff --git a/lib/krb5/init_creds_pw.c b/lib/krb5/init_creds_pw.c index 9072adab5..60a3a2960 100644 --- a/lib/krb5/init_creds_pw.c +++ b/lib/krb5/init_creds_pw.c @@ -2747,10 +2747,22 @@ krb5_get_init_creds_keytab(krb5_context context, krb5_get_init_creds_opt *options) { krb5_init_creds_context ctx; + krb5_keytab_entry ktent; krb5_error_code ret; + memset(&ktent, 0, sizeof(ktent)); memset(creds, 0, sizeof(*creds)); + if (strcmp(client->realm, "") == 0) { + /* + * Referral realm. We have a keytab, so pick a realm by + * matching in the keytab. + */ + ret = krb5_kt_get_entry(context, keytab, client, 0, 0, &ktent); + if (ret == 0) + client = ktent.principal; + } + ret = krb5_init_creds_init(context, client, NULL, NULL, start_time, options, &ctx); if (ret) goto out; @@ -2768,6 +2780,7 @@ krb5_get_init_creds_keytab(krb5_context context, krb5_process_last_request(context, options, ctx); out: + krb5_kt_free_entry(context, &ktent); if (ret == 0) krb5_init_creds_get_creds(context, ctx, creds); diff --git a/lib/krb5/keytab.c b/lib/krb5/keytab.c index 7aee42f9f..ca37e292a 100644 --- a/lib/krb5/keytab.c +++ b/lib/krb5/keytab.c @@ -519,7 +519,7 @@ krb5_kt_destroy(krb5_context context, */ static krb5_boolean -compare_aliseses(krb5_context context, +compare_aliases(krb5_context context, krb5_keytab_entry *entry, krb5_const_principal principal) { @@ -555,13 +555,19 @@ krb5_kt_compare(krb5_context context, krb5_kvno vno, krb5_enctype enctype) { - if(principal != NULL && - !(krb5_principal_compare(context, entry->principal, principal) || - compare_aliseses(context, entry, principal))) + /* krb5_principal_compare() does not special-case the referral realm */ + if (principal != NULL && strcmp(principal->realm, "") == 0 && + !(krb5_principal_compare_any_realm(context, entry->principal, principal) || + compare_aliases(context, entry, principal))) { + return FALSE; + } else if (principal != NULL && strcmp(principal->realm, "") != 0 && + !(krb5_principal_compare(context, entry->principal, principal) || + compare_aliases(context, entry, principal))) { return FALSE; - if(vno && vno != entry->vno) + } + if (vno && vno != entry->vno) return FALSE; - if(enctype && enctype != entry->keyblock.keytype) + if (enctype && enctype != entry->keyblock.keytype) return FALSE; return TRUE; } @@ -674,23 +680,26 @@ krb5_kt_get_entry(krb5_context context, krb5_keytab_entry *entry) { krb5_error_code ret; - krb5_principal try_princ; + krb5_const_principal try_princ; krb5_name_canon_iterator name_canon_iter; - if (!principal || principal->name.name_type != KRB5_NT_SRV_HST_NEEDS_CANON) + if (!principal) return krb5_kt_get_entry_wrapped(context, id, principal, kvno, enctype, entry); - ret = krb5_name_canon_iterator_start(context, principal, NULL, - &name_canon_iter); + ret = krb5_name_canon_iterator_start(context, principal, &name_canon_iter); if (ret) return ret; do { - ret = krb5_name_canon_iterate_princ(context, &name_canon_iter, - &try_princ, NULL); + ret = krb5_name_canon_iterate(context, &name_canon_iter, &try_princ, + NULL); if (ret) break; + if (try_princ == NULL) { + ret = KRB5_KT_NOTFOUND; + continue; + } ret = krb5_kt_get_entry_wrapped(context, id, try_princ, kvno, enctype, entry); } while (ret == KRB5_KT_NOTFOUND && name_canon_iter); diff --git a/lib/krb5/krb5.h b/lib/krb5/krb5.h index 71b0eb1b2..2eb3afa8e 100644 --- a/lib/krb5/krb5.h +++ b/lib/krb5/krb5.h @@ -923,13 +923,16 @@ typedef struct { */ typedef enum krb5_name_canon_rule_options { - KRB5_NCRO_GC_ONLY = 1 << 0, - KRB5_NCRO_USE_REFERRALS = 1 << 1, - KRB5_NCRO_NO_REFERRALS = 1 << 2, - KRB5_NCRO_SECURE = 1 << 2 + KRB5_NCRO_GC_ONLY = 1 << 0, + KRB5_NCRO_USE_REFERRALS = 1 << 1, + KRB5_NCRO_NO_REFERRALS = 1 << 2, + KRB5_NCRO_USE_FAST = 1 << 3, + KRB5_NCRO_USE_DNSSEC = 1 << 4, + KRB5_NCRO_LOOKUP_REALM = 1 << 5 } krb5_name_canon_rule_options; typedef struct krb5_name_canon_rule_data *krb5_name_canon_rule; +typedef const struct krb5_name_canon_rule_data *krb5_const_name_canon_rule; typedef struct krb5_name_canon_iterator_data *krb5_name_canon_iterator; /* diff --git a/lib/krb5/krb5_locl.h b/lib/krb5/krb5_locl.h index 7feeb0c72..59fe38669 100644 --- a/lib/krb5/krb5_locl.h +++ b/lib/krb5/krb5_locl.h @@ -308,6 +308,7 @@ typedef struct krb5_context_data { hx509_context hx509ctx; #endif unsigned int num_kdc_requests; + krb5_name_canon_rule name_canon_rules; } krb5_context_data; #ifndef KRB5_USE_PATH_TOKENS diff --git a/lib/krb5/principal.c b/lib/krb5/principal.c index d12e69b6a..440deb6b7 100644 --- a/lib/krb5/principal.c +++ b/lib/krb5/principal.c @@ -661,6 +661,23 @@ krb5_principal_set_realm(krb5_context context, return 0; } +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_principal_set_comp_string(krb5_context context, + krb5_principal principal, + unsigned int k, + const char *component) +{ + char *s; + if (k >= princ_num_comp(principal)) + return ERANGE; + s = strdup(component); + if (s == NULL) + return krb5_enomem(context); + free(princ_ncomp(principal, k)); + princ_ncomp(principal, k) = s; + return 0; +} + #ifndef HEIMDAL_SMALLER /** * Build a principal using vararg style building @@ -953,9 +970,11 @@ krb5_principal_compare(krb5_context context, krb5_boolean princs_eq; krb5_const_principal princ2canon; krb5_const_principal other_princ; - krb5_principal try_princ; + krb5_const_principal try_princ; krb5_name_canon_iterator nci; + /* One princ needs canonicalization, the other doesn't */ + if (princ_type(princ1) == KRB5_NT_SRV_HST_NEEDS_CANON) { princ2canon = princ1; other_princ = princ2; @@ -964,12 +983,11 @@ krb5_principal_compare(krb5_context context, other_princ = princ1; } - ret = krb5_name_canon_iterator_start(context, princ2canon, NULL, &nci); + ret = krb5_name_canon_iterator_start(context, princ2canon, &nci); if (ret) return FALSE; do { - ret = krb5_name_canon_iterate_princ(context, &nci, &try_princ, - NULL); + ret = krb5_name_canon_iterate(context, &nci, &try_princ, NULL); if (ret || try_princ == NULL) break; princs_eq = krb5_principal_compare(context, try_princ, other_princ); @@ -985,10 +1003,8 @@ krb5_principal_compare(krb5_context context, * Either neither princ requires canonicalization, both do, or * no applicable name canonicalization rules were found and we fell * through (chances are we'll fail here too in that last case). - * We're not going to do n^2 comparisons in the case of both princs - * requiring canonicalization. */ - if(!krb5_realm_compare(context, princ1, princ2)) + if (!krb5_realm_compare(context, princ1, princ2)) return FALSE; return krb5_principal_compare_any_realm(context, princ1, princ2); } @@ -1246,22 +1262,43 @@ krb5_principal_is_root_krbtgt(krb5_context context, krb5_const_principal p) strcmp(p->name.name_string.val[1], p->realm) == 0; } +static int +tolower_ascii(int c) +{ + if (c >= 'A' || c <= 'Z') + return 'a' + (c - 'A'); + return c; +} typedef enum krb5_name_canon_rule_type { KRB5_NCRT_BOGUS = 0, KRB5_NCRT_AS_IS, KRB5_NCRT_QUALIFY, - KRB5_NCRT_RES_SEARCHLIST, KRB5_NCRT_NSS } krb5_name_canon_rule_type; +#ifdef UINT8_MAX +#define MAXDOTS UINT8_MAX +#else +#define MAXDOTS (255U) +#endif +#ifdef UINT16_MAX +#define MAXORDER UINT16_MAX +#else +#define MAXORDER (65535U) +#endif + struct krb5_name_canon_rule_data { - krb5_name_canon_rule next; krb5_name_canon_rule_type type; krb5_name_canon_rule_options options; - char *domain; - char *realm; - unsigned int mindots; + uint8_t mindots; /* match this many dots or more */ + uint8_t maxdots; /* match no more than this many dots */ + uint16_t explicit_order; /* given order */ + uint16_t order; /* actual order */ + char *match_domain; /* match this stem */ + char *match_realm; /* match this realm */ + char *domain; /* qualify with this domain */ + char *realm; /* qualify with this realm */ }; /** @@ -1307,18 +1344,18 @@ krb5_sname_to_principal(krb5_context context, return KRB5_SNAME_UNSUPP_NAMETYPE; /* if hostname is NULL, use local hostname */ - if (!hostname) { + if (hostname == NULL) { if (gethostname(localname, MAXHOSTNAMELEN)) return errno; hostname = localname; } /* if sname is NULL, use "host" */ - if (!sname) + if (sname == NULL) sname = "host"; remote_host = strdup(hostname); - if (!remote_host) + if (remote_host == NULL) return krb5_enomem(context); if (type == KRB5_NT_SRV_HST) { @@ -1329,35 +1366,37 @@ krb5_sname_to_principal(krb5_context context, if (isupper((int) (*cp))) *cp = tolower((int) (*cp)); + /* + * If there is only one name canon rule and it says to + * canonicalize the old way, do that now, as we used to. + */ ret = _krb5_get_name_canon_rules(context, &rules); if (ret) { _krb5_debug(context, 5, "Failed to get name canon rules: ret = %d", ret); return ret; } - if (rules->type == KRB5_NCRT_NSS && rules->next == NULL) { - _krb5_debug(context, 5, "Using nss for name canon immediately " - "(without reverse lookups)"); - /* For the default rule we'll just canonicalize here */ - ret = krb5_sname_to_principal_old(context, NULL, - remote_host, sname, - KRB5_NT_SRV_HST, - ret_princ); + if (rules[0].type == KRB5_NCRT_NSS && + rules[1].type == KRB5_NCRT_BOGUS) { + _krb5_debug(context, 5, "Using nss for name canon immediately"); + ret = krb5_sname_to_principal_old(context, rules[0].realm, + remote_host, sname, + KRB5_NT_SRV_HST, ret_princ); free(remote_host); - _krb5_free_name_canon_rules(context, rules); return ret; } - _krb5_free_name_canon_rules(context, rules); } - /* Trailing dot(s) would be bad */ + /* Remove trailing dots */ if (remote_host[0]) { - cp = remote_host + strlen(remote_host)-1; - if (*cp == '.') - *cp = '\0'; + for (cp = remote_host + strlen(remote_host)-1; + *cp == '.' && cp > remote_host; + cp--) { + *cp = '\0'; + } } - realm = ""; /* "Referral realm" -- borrowed from newer MIT */ + realm = ""; /* "Referral realm" */ ret = krb5_build_principal(context, ret_princ, strlen(realm), realm, sname, remote_host, @@ -1368,8 +1407,13 @@ krb5_sname_to_principal(krb5_context context, * Hostname canonicalization is done elsewhere (in * krb5_get_credentials() and krb5_kt_get_entry()). * - * We use special magic to indicate to those functions that - * this principal name requires canonicalization. + * We overload the name type to indicate to those functions that + * this principal name requires canonicalization. + * + * We can't use the empty realm to denote the need to + * canonicalize the hostname too: it would mean that users who + * want to assert knowledge of a service's realm must also know + * the canonical hostname, but in practice they don't. */ (*ret_princ)->name.name_type = KRB5_NT_SRV_HST_NEEDS_CANON; @@ -1381,199 +1425,121 @@ krb5_sname_to_principal(krb5_context context, return ret; } -/* - * Helper function to parse name canonicalization rule tokens. - */ +static void +tolower_str(char *s) +{ + for (; *s != '\0'; s++) { + if (isupper(*s)) + *s = tolower_ascii(*s); + } +} + static krb5_error_code rule_parse_token(krb5_context context, krb5_name_canon_rule rule, const char *tok) { long int n; + int needs_type = rule->type == KRB5_NCRT_BOGUS; /* * Rules consist of a sequence of tokens, some of which indicate * what type of rule the rule is, and some of which set rule options - * or ancilliary data. First rule type token wins. + * or ancilliary data. Last rule type token wins. */ + /* Rule type tokens: */ - if (strcmp(tok, "as-is") == 0) { - if (rule->type == KRB5_NCRT_BOGUS) - rule->type = KRB5_NCRT_AS_IS; - } else if (strcmp(tok, "qualify") == 0) { - if (rule->type == KRB5_NCRT_BOGUS) - rule->type = KRB5_NCRT_QUALIFY; - } else if (strcmp(tok, "use-resolver-searchlist") == 0) { - if (rule->type == KRB5_NCRT_BOGUS) - rule->type = KRB5_NCRT_RES_SEARCHLIST; - } else if (strcmp(tok, "nss") == 0) { - if (rule->type == KRB5_NCRT_BOGUS) - rule->type = KRB5_NCRT_NSS; + if (needs_type && strcmp(tok, "as-is") == 0) { + rule->type = KRB5_NCRT_AS_IS; + } else if (needs_type && strcmp(tok, "qualify") == 0) { + rule->type = KRB5_NCRT_QUALIFY; + } else if (needs_type && strcmp(tok, "nss") == 0) { + rule->type = KRB5_NCRT_NSS; /* Rule options: */ - } else if (strcmp(tok, "secure") == 0) { - rule->options |= KRB5_NCRO_SECURE; + } else if (strcmp(tok, "use_fast") == 0) { + rule->options |= KRB5_NCRO_USE_FAST; + } else if (strcmp(tok, "use_dnssec") == 0) { + rule->options |= KRB5_NCRO_USE_DNSSEC; } else if (strcmp(tok, "ccache_only") == 0) { rule->options |= KRB5_NCRO_GC_ONLY; } else if (strcmp(tok, "no_referrals") == 0) { rule->options |= KRB5_NCRO_NO_REFERRALS; + } else if (strcmp(tok, "use_referrals") == 0) { + rule->options &= ~KRB5_NCRO_NO_REFERRALS; + if (rule->realm == NULL) { + rule->realm = strdup(""); + if (rule->realm == NULL) + return krb5_enomem(context); + } + } else if (strcmp(tok, "lookup_realm") == 0) { + rule->options |= KRB5_NCRO_LOOKUP_REALM; + free(rule->realm); + rule->realm = NULL; /* Rule ancilliary data: */ } else if (strncmp(tok, "domain=", strlen("domain=")) == 0) { free(rule->domain); rule->domain = strdup(tok + strlen("domain=")); - if (!rule->domain) + if (rule->domain == NULL) return krb5_enomem(context); + tolower_str(rule->domain); } else if (strncmp(tok, "realm=", strlen("realm=")) == 0) { free(rule->realm); rule->realm = strdup(tok + strlen("realm=")); - if (!rule->realm) + if (rule->realm == NULL) + return krb5_enomem(context); + } else if (strncmp(tok, "match_domain=", strlen("match_domain=")) == 0) { + free(rule->match_domain); + rule->match_domain = strdup(tok + strlen("match_domain=")); + if (rule->match_domain == NULL) + return krb5_enomem(context); + tolower_str(rule->match_domain); + } else if (strncmp(tok, "match_realm=", strlen("match_realm=")) == 0) { + free(rule->match_realm); + rule->match_realm = strdup(tok + strlen("match_realm=")); + if (rule->match_realm == NULL) return krb5_enomem(context); } else if (strncmp(tok, "mindots=", strlen("mindots=")) == 0) { errno = 0; n = strtol(tok + strlen("mindots="), NULL, 10); - if (errno == 0 && n > 0 && n < 8) + if (errno == 0 && n > 0 && n <= MAXDOTS) rule->mindots = n; - } - /* ignore bogus tokens; it's not like we can print to stderr */ - /* XXX Trace bogus tokens! */ - return 0; -} - -/* - * This helper function expands the DNS search list rule into qualify - * rules, one for each domain in the resolver search list. - */ -static krb5_error_code -expand_search_list(krb5_context context, krb5_name_canon_rule *r, size_t *n, - size_t insert_point) -{ -#if defined(HAVE_RES_NINIT) || defined(HAVE_RES_SEARCH) -#ifdef USE_RES_NINIT - struct __res_state statbuf; -#endif /* USE_RES_NINIT */ - krb5_name_canon_rule_options opts; - krb5_name_canon_rule new_r; - char **dnsrch; - char **domains = NULL; - size_t search_list_len; - size_t i; - int ret; - - /* Sanitize */ - heim_assert((*n) > insert_point, - "name canon search list rule expansion: internal error"); - free((*r)[insert_point].domain); - free((*r)[insert_point].realm); - (*r)[insert_point].domain = NULL; - (*r)[insert_point].realm = NULL; - opts = (*r)[insert_point].options; - - /* - * Would it be worthwhile to move this into context->os_context and - * krb5_os_init_context()? - */ -#ifdef USE_RES_NINIT - ret = res_ninit(&statbuf); - if (ret) - return ENOENT; /* XXX Create a better error */ - dnsrch = statbuf.dnsrch; - search_list_len = sizeof (statbuf.dnsrch) / sizeof (*statbuf.dnsrch); -#else - ret = res_init(); - if (ret) - return ENOENT; /* XXX Create a better error */ - dnsrch = _res.dnsrch; - search_list_len = sizeof (_res.dnsrch) / sizeof (*_res.dnsrch); -#endif /* USE_RES_NINIT */ - - for (i = 0; i < search_list_len; i++) { - if (!dnsrch || dnsrch[i] == NULL) { - search_list_len = i; - break; - } - } - - if (search_list_len == 0) { - /* Invalidate this entry and return */ - (*r)[insert_point].type = KRB5_NCRT_BOGUS; - return 0; - } - - /* - * Pre-strdup() the search list so the realloc() below is the last - * point at which we can fail with ENOMEM. - */ - domains = calloc(search_list_len, sizeof (*domains)); - if (domains == NULL) - return krb5_enomem(context); - for (i = 0; i < search_list_len; i++) { - if ((domains[i] = strdup(dnsrch[i])) == NULL) { - while (i > 0) - free(domains[--i]); - return krb5_enomem(context); - } - } - - if (search_list_len > 1) { - /* The -1 here is because we re-use this rule as one of the new rules */ - new_r = realloc(*r, sizeof (**r) * ((*n) + search_list_len - 1)); - if (new_r == NULL) { - for (i = 0; i < search_list_len; i++) - free(domains[i]); - free(domains); - return krb5_enomem(context); - } + } else if (strncmp(tok, "maxdots=", strlen("maxdots=")) == 0) { + errno = 0; + n = strtol(tok + strlen("maxdots="), NULL, 10); + if (errno == 0 && n > 0 && n <= MAXDOTS) + rule->maxdots = n; + } else if (strncmp(tok, "order=", strlen("order=")) == 0) { + errno = 0; + n = strtol(tok + strlen("order="), NULL, 10); + if (errno == 0 && n > 0 && n <= MAXORDER) + rule->explicit_order = n; } else { - new_r = *r; /* search_list_len == 1 */ + _krb5_debug(context, 5, + "Unrecognized name canonicalization rule token %s", tok); + return EINVAL; } - - /* Make room for the new rules */ - if (insert_point < (*n) - 1) { - _krb5_debug(context, 5, "Inserting %ld qualify rules in place of a " - "resolver searchlist rule", (unsigned long)search_list_len); - /* - * Move the rules that follow the search list rule down by - * search_list_len - 1 rules. - */ - memmove(&new_r[insert_point + search_list_len], - &new_r[insert_point + 1], - sizeof (new_r[0]) * ((*n) - (insert_point + 1))); - } - - /* - * Clear in case the search-list rule is at the end of the rules; - * realloc() won't have done this for us. - */ - memset(&new_r[insert_point], 0, sizeof (new_r[0]) * search_list_len); - - /* Setup the new rules */ - for (i = 0; i < search_list_len; i++) { - _krb5_debug(context, 5, "Inserting qualify rule with domain=%s", - dnsrch[i]); - new_r[insert_point + i].type = KRB5_NCRT_QUALIFY; - new_r[insert_point + i].domain = domains[i]; - new_r[insert_point + i].options = opts; - } - free(domains); - - *r = new_r; - *n += search_list_len - 1; /* -1 because we're replacing one rule */ - -#ifdef USE_RES_NINIT - res_ndestroy(&statbuf); -#endif /* USE_RES_NINIT */ - -#else - /* No resolver API by which to get search list -> use name service */ - if ((*r)[insert_point].options & KRB5_NCRO_SECURE) - return ENOTSUP; - (*r)[insert_point].type = KRB5_NCRT_NSS; -#endif /* HAVE_RES_NINIT || HAVE_RES_SEARCH */ - return 0; } -/* - * Helper function to parse name canonicalization rules. - */ +static int +rule_cmp(const void *a, const void *b) +{ + krb5_const_name_canon_rule left = a; + krb5_const_name_canon_rule right = b; + + if (left->type == KRB5_NCRT_BOGUS && + right->type == KRB5_NCRT_BOGUS) + return 0; + if (left->type == KRB5_NCRT_BOGUS) + return 1; + if (right->type == KRB5_NCRT_BOGUS) + return -1; + if (left->explicit_order < right->explicit_order) + return -1; + if (left->explicit_order > right->explicit_order) + return 1; + return left->order - right->order; +} + static krb5_error_code parse_name_canon_rules(krb5_context context, char **rulestrs, krb5_name_canon_rule *rules) @@ -1582,76 +1548,118 @@ parse_name_canon_rules(krb5_context context, char **rulestrs, char *tok; char *cp; char **cpp; - size_t n = 0; + size_t n; size_t i, k; + int do_sort = 0; krb5_name_canon_rule r; - for (cpp = rulestrs; *cpp; cpp++) + *rules = NULL; + + for (n =0, cpp = rulestrs; cpp != NULL && *cpp != NULL; cpp++) n++; + n += 2; /* Always at least one rule; two for the default case */ + if ((r = calloc(n, sizeof (*r))) == NULL) return krb5_enomem(context); - /* This code is written without use of strtok_r() :( */ - for (i = 0, k = 0; i < n; i++) { + for (k = 0; k < n; k++) { + r[k].type = KRB5_NCRT_BOGUS; + r[k].match_domain = NULL; + r[k].match_realm = NULL; + r[k].domain = NULL; + r[k].realm = NULL; + } + + for (i = 0, k = 0; i < n && rulestrs != NULL && rulestrs[i] != NULL; i++) { cp = rulestrs[i]; + r[k].explicit_order = MAXORDER; /* mark order, see below */ + r[k].maxdots = MAXDOTS; + r[k].order = k; /* default order */ + + /* Tokenize and parse value */ do { tok = cp; - cp = strpbrk(cp, ":"); + cp = strchr(cp, ':'); /* XXX use strtok_r() */ if (cp) - *cp++ = '\0'; /* delimit token */ + *cp++ = '\0'; /* delimit token */ ret = rule_parse_token(context, &r[k], tok); + if (ret == EINVAL) { + r[k].type = KRB5_NCRT_BOGUS; + break; + } + if (ret) { + _krb5_free_name_canon_rules(context, r); + return ret; + } } while (cp && *cp); - /* Loosely validate parsed rule */ + if (r[k].explicit_order != MAXORDER) + do_sort = 1; + + /* Validate parsed rule */ if (r[k].type == KRB5_NCRT_BOGUS || (r[k].type == KRB5_NCRT_QUALIFY && !r[k].domain) || (r[k].type == KRB5_NCRT_NSS && (r[k].domain || r[k].realm))) { /* Invalid rule; mark it so and clean up */ r[k].type = KRB5_NCRT_BOGUS; - free(r[k].realm); + free(r[k].match_domain); + free(r[k].match_realm); free(r[k].domain); + free(r[k].realm); r[k].realm = NULL; r[k].domain = NULL; - /* XXX Trace this! */ - continue; /* bogus rule */ + r[k].match_domain = NULL; + r[k].match_realm = NULL; + _krb5_debug(context, 5, + "Ignoring invalid name canonicalization rule %lu", + (unsigned long)i); + continue; } k++; /* good rule */ } - /* Expand search list rules */ - for (i = 0; i < n; i++) { - if (r[i].type != KRB5_NCRT_RES_SEARCHLIST) - continue; - ret = expand_search_list(context, &r, &n, i); - if (ret) - return ret; + if (do_sort) { + /* + * Note that we make make this a stable sort by using appareance + * and explicit order. + */ + qsort(r, n, sizeof(r[0]), rule_cmp); } - /* The first rule has to be valid */ - k = n; - for (i = 0; i < n; i++) { - if (r[i].type != KRB5_NCRT_BOGUS) { - k = i; - break; - } - } - if (k > 0 && k < n) { - r[0] = r[k]; - memset(&r[k], 0, sizeof (r[k])); /* KRB5_NCRT_BOGUS is 0 */ - } - - /* Setup next pointers */ - for (i = 1, k = 0; i < n; i++) { - if (r[i].type == KRB5_NCRT_BOGUS) - continue; - r[k].next = &r[i]; - k++; + if (r[0].type == KRB5_NCRT_BOGUS) { + /* No rules, or no valid rules */ + r[0].type = KRB5_NCRT_NSS; } *rules = r; return 0; /* We don't communicate bad rule errors here */ } +/* + * This exists only because the hostname canonicalization behavior in Heimdal + * (and other implementations of Kerberos) has been to use getaddrinfo(), + * unsafe though it is, for ages. We can't fix it in one day. + */ +static void +make_rules_safe(krb5_context context, krb5_name_canon_rule rules) +{ + /* + * If the only rule were to use the name service (getaddrinfo()) then we're + * bound to fail. We could try to convert that rule to an as-is rule, but + * when we do get a validating resolver we'd be unhappy that we did such a + * conversion. Better let the user get failures and make them think about + * their naming rules. + */ + if (rules != NULL) + return; + for (; rules[0].type != KRB5_NCRT_BOGUS; rules++) { + if (rules->type == KRB5_NCRT_NSS) + rules->options |= KRB5_NCRO_USE_DNSSEC; + else + rules->options |= KRB5_NCRO_USE_FAST; + } +} + /** * This function returns an array of host-based service name * canonicalization rules. The array of rules is organized as a list. @@ -1665,61 +1673,28 @@ _krb5_get_name_canon_rules(krb5_context context, krb5_name_canon_rule *rules) { krb5_error_code ret; char **values = NULL; - char *realm = NULL; - *rules = NULL; - ret = krb5_get_default_realm(context, &realm); - if (ret == KRB5_CONFIG_NODEFREALM || ret == KRB5_CONFIG_CANTOPEN) - realm = NULL; - else if (ret) - return ret; - - if (realm) { - values = krb5_config_get_strings(context, NULL, - "realms", - realm, - "name_canon_rules", NULL); - free(realm); - } - if (!values) { - values = krb5_config_get_strings(context, NULL, - "libdefaults", - "name_canon_rules", NULL); - } - - if (!values || !values[0]) { - /* Default rule: do the dreaded getaddrinfo()/getnameinfo() dance */ - if ((*rules = calloc(1, sizeof (**rules))) == NULL) - return krb5_enomem(context); - (*rules)->type = KRB5_NCRT_NSS; - return 0; - } + *rules = context->name_canon_rules; + if (*rules != NULL) + return 0; + values = krb5_config_get_strings(context, NULL, + "libdefaults", "name_canon_rules", NULL); ret = parse_name_canon_rules(context, values, rules); krb5_config_free_strings(values); if (ret) return ret; - { - size_t k; - krb5_name_canon_rule r; - for (k = 0, r = *rules; r; r = r->next, k++) { - _krb5_debug(context, 5, - "Name canon rule %ld type=%d, options=%x, mindots=%d, " - "domain=%s, realm=%s", - (unsigned long)k, r->type, r->options, r->mindots, - r->domain ? r->domain : "", - r->realm ? r->realm : "" - ); - } - } + if (krb5_config_get_bool_default(context, NULL, FALSE, + "libdefaults", "safe_name_canon", NULL)) + make_rules_safe(context, *rules); - if ((*rules)[0].type != KRB5_NCRT_BOGUS) - return 0; /* success! */ - free(*rules); - *rules = NULL; - /* fall through to return default rule */ - _krb5_debug(context, 5, "All name canon rules are bogus!"); + heim_assert(rules != NULL && (*rules)[0].type != KRB5_NCRT_BOGUS, + "internal error in parsing principal name " + "canonicalization rules"); + + /* Memoize */ + context->name_canon_rules = *rules; return 0; } @@ -1731,178 +1706,198 @@ get_host_realm(krb5_context context, const char *hostname, char **realm) char **hrealms = NULL; *realm = NULL; - if ((ret = krb5_get_host_realm(context, hostname, &hrealms))) + ret = krb5_get_host_realm(context, hostname, &hrealms); + if (ret) return ret; - if (!hrealms) + if (hrealms == NULL) return KRB5_ERR_HOST_REALM_UNKNOWN; /* krb5_set_error() already done */ - if (!hrealms[0]) { + if (hrealms[0] == NULL) { krb5_free_host_realm(context, hrealms); return KRB5_ERR_HOST_REALM_UNKNOWN; /* krb5_set_error() already done */ } *realm = strdup(hrealms[0]); krb5_free_host_realm(context, hrealms); + if (*realm == NULL) + return krb5_enomem(context); return 0; } -/** - * Apply a name canonicalization rule to a principal. - * - * @param context Kerberos context - * @param rule name canon rule - * @param in_princ principal name - * @param out_print resulting principal name - * @param rule_opts options for this rule - */ -KRB5_LIB_FUNCTION krb5_error_code -_krb5_apply_name_canon_rule(krb5_context context, krb5_name_canon_rule rule, - krb5_const_principal in_princ, krb5_principal *out_princ, - krb5_name_canon_rule_options *rule_opts) +static int +is_domain_suffix(const char *domain, const char *suffix) { + size_t dlen = strlen(domain); + size_t slen = strlen(suffix); + + if (dlen < slen + 2) + return 0; + + if (strcasecmp(domain + (dlen - slen), suffix) != 0) + return 0; + + if (domain[(dlen - slen) - 1] != '.') + return 0; + return 1; +} + +/* + * Applies a name canonicalization rule to a principal. + * + * Returns zero and no out_princ if the rule does not match. + * Returns zero and an out_princ if the rule does match. + */ +static krb5_error_code +apply_name_canon_rule(krb5_context context, krb5_name_canon_rule rules, + size_t rule_idx, krb5_const_principal in_princ, + krb5_principal *out_princ, + krb5_name_canon_rule_options *rule_opts) +{ + krb5_name_canon_rule rule = &rules[rule_idx]; krb5_error_code ret; unsigned int ndots = 0; - char *realm = NULL; + krb5_principal nss = NULL; const char *sname = NULL; - const char *hostname = NULL; - char *new_hostname = NULL; + const char *orig_hostname = NULL; + const char *new_hostname = NULL; + const char *new_realm = NULL; const char *cp; + char *tmp_hostname = NULL; + char *tmp_realm = NULL; - heim_assert(in_princ->name.name_type == KRB5_NT_SRV_HST_NEEDS_CANON, - "internal error: principal does not need canon"); - *out_princ = NULL; - if (rule_opts) - *rule_opts = 0; + *out_princ = NULL; /* Signal no match */ + + if (rule_opts != NULL) + *rule_opts = rule->options; if (rule->type == KRB5_NCRT_BOGUS) return 0; /* rule doesn't apply */ sname = krb5_principal_get_comp_string(context, in_princ, 0); - hostname = krb5_principal_get_comp_string(context, in_princ, 1); + orig_hostname = krb5_principal_get_comp_string(context, in_princ, 1); - _krb5_debug(context, 5, "Applying a name rule (type %d) to %s", rule->type, - hostname); - if (rule_opts) - *rule_opts = rule->options; + _krb5_debug(context, 5, N_("Applying a name rule (type %d) to %s", ""), + rule->type, orig_hostname); + + if (rule->mindots > 0 || rule->maxdots > 0) { + for (cp = strchr(orig_hostname, '.'); cp && *cp; cp = strchr(cp + 1, '.')) + ndots++; + } + if (rule->mindots > 0 && ndots < rule->mindots) + return 0; + if (ndots > rule->maxdots) + return 0; ret = 0; + + if (rule->match_domain != NULL && + !is_domain_suffix(orig_hostname, rule->match_domain)) + return 0; + + if (rule->match_realm != NULL && + strcmp(rule->match_realm, in_princ->realm) != 0) + return 0; + + new_realm = rule->realm; switch (rule->type) { case KRB5_NCRT_AS_IS: - if (rule->mindots > 0) { - for (cp = strchr(hostname, '.'); cp && *cp; cp = strchr(cp, '.')) - ndots++; - if (ndots < rule->mindots) - goto out; /* *out_princ == NULL; rule doesn't apply */ - } - if (rule->domain) { - cp = strstr(hostname, rule->domain); - if (cp == NULL) - goto out; /* *out_princ == NULL; rule doesn't apply */ - if (cp != hostname && cp[-1] != '.') - goto out; - } - /* Rule matches, copy princ with hostname as-is, with normal magic */ - realm = rule->realm; - if (!realm) { - ret = get_host_realm(context, hostname, &realm); - if (ret) - goto out; - } - _krb5_debug(context, 5, "As-is rule building a princ with realm=%s, " - "sname=%s, and hostname=%s", realm, sname, hostname); - ret = krb5_build_principal(context, out_princ, - strlen(realm), - realm, sname, hostname, - (char *)0); - goto out; break; case KRB5_NCRT_QUALIFY: - /* - * Note that we should never get these rules even if specified - * in krb5.conf. See rule parser. - */ heim_assert(rule->domain != NULL, "missing domain for qualify name canon rule"); - cp = strchr(hostname, '.'); - if (cp && (cp = strstr(cp, rule->domain))) { - new_hostname = strdup(hostname); - if (new_hostname == NULL) { - ret = krb5_enomem(context); - goto out; - } - - } else { - asprintf(&new_hostname, "%s%s%s", hostname, - rule->domain[0] != '.' ? "." : "", - rule->domain); - if (new_hostname == NULL) { - ret = krb5_enomem(context); - goto out; - } - } - realm = rule->realm; - if (!realm) { - ret = get_host_realm(context, new_hostname, &realm); - if (ret) - goto out; - } - _krb5_debug(context, 5, "Building a princ with realm=%s, sname=%s, " - "and hostname=%s", realm, sname, new_hostname); - ret = krb5_build_principal(context, out_princ, - strlen(realm), realm, - sname, new_hostname, (char *)0); - goto out; + if (asprintf(&tmp_hostname, "%s.%s", orig_hostname, + rule->domain) == -1 || tmp_hostname == NULL) { + ret = krb5_enomem(context); + goto out; + } + new_hostname = tmp_hostname; break; case KRB5_NCRT_NSS: - _krb5_debug(context, 5, "Using name service lookups (without " - "reverse lookups)"); + if ((rule->options & KRB5_NCRO_USE_DNSSEC)) { + ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; + krb5_set_error_message(context, ret, + "Secure hostname resolution not supported"); + goto out; + } + _krb5_debug(context, 5, "Using name service lookups"); ret = krb5_sname_to_principal_old(context, rule->realm, - hostname, sname, - KRB5_NT_SRV_HST, - out_princ); - if (rule->next != NULL && - (ret == KRB5_ERR_BAD_HOSTNAME || - ret == KRB5_ERR_HOST_REALM_UNKNOWN)) + orig_hostname, sname, + KRB5_NT_SRV_HST, + &nss); + if (rules[rule_idx + 1].type != KRB5_NCRT_BOGUS && + (ret == KRB5_ERR_BAD_HOSTNAME || + ret == KRB5_ERR_HOST_REALM_UNKNOWN)) { /* * Bad hostname / realm unknown -> rule inapplicable if * there's more rules. If it's the last rule then we want * to return all errors from krb5_sname_to_principal_old() * here. */ - ret = 0; - goto out; - break; + ret = 0; + goto out; + } + if (ret) + goto out; + + new_hostname = krb5_principal_get_comp_string(context, nss, 1); + new_realm = krb5_principal_get_realm(context, nss); + break; default: - /* Can't happen, but we need this to shut up gcc */ - break; + /* Can't happen */ + ret = 0; + goto out; } -out: - if (!ret && *out_princ) { + /* + * This rule applies. + * + * Copy in_princ and mutate the copy per the matched rule. + * + * This way we apply to principals with two or more components, such as + * domain-based names. + */ + ret = krb5_copy_principal(context, in_princ, out_princ); + if (ret) + goto out; + + if (new_realm == NULL && (rule->options & KRB5_NCRO_LOOKUP_REALM) != 0) { + ret = get_host_realm(context, new_hostname, &tmp_realm); + if (ret) + goto out; + new_realm = tmp_realm; + } + + if (new_realm != NULL) + krb5_principal_set_realm(context, *out_princ, new_realm); + if (new_hostname != NULL) + krb5_principal_set_comp_string(context, *out_princ, 1, new_hostname); + if (princ_type(*out_princ) == KRB5_NT_SRV_HST_NEEDS_CANON) + princ_type(*out_princ) = KRB5_NT_SRV_HST; + + /* Trace rule application */ + { krb5_error_code ret2; char *unparsed; ret2 = krb5_unparse_name(context, *out_princ, &unparsed); if (ret2) { - _krb5_debug(context, 5, "Couldn't unparse resulting princ! (%d)", - ret); + _krb5_debug(context, 5, + N_("Couldn't unparse canonicalized princicpal (%d)", + ""), + ret); } else { - _krb5_debug(context, 5, "Name canon rule application yields this " - "unparsed princ: %s", unparsed); + _krb5_debug(context, 5, + N_("Name canon rule application yields %s", ""), + unparsed); free(unparsed); } - } else if (!ret) { - _krb5_debug(context, 5, "Name canon rule did not apply"); - } else { - _krb5_debug(context, 5, "Name canon rule application error: %d", ret); } - if (new_hostname) - free(new_hostname); - if (realm != rule->realm) - free(realm); - if (*out_princ) - (*out_princ)->name.name_type = KRB5_NT_SRV_HST; + +out: + free(tmp_hostname); + free(tmp_realm); + krb5_free_principal(context, nss); if (ret) krb5_set_error_message(context, ret, N_("Name canon rule application failed", "")); @@ -1915,25 +1910,28 @@ out: KRB5_LIB_FUNCTION void _krb5_free_name_canon_rules(krb5_context context, krb5_name_canon_rule rules) { - krb5_name_canon_rule r; + size_t k; - for (r = rules; r; r = r->next) { - free(r->realm); - free(r->domain); + if (rules == NULL) + return; + + for (k = 0; rules[k].type != KRB5_NCRT_BOGUS; k++) { + free(rules[k].match_domain); + free(rules[k].match_realm); + free(rules[k].domain); + free(rules[k].realm); } - free(rules); - rules = NULL; } struct krb5_name_canon_iterator_data { krb5_name_canon_rule rules; - krb5_name_canon_rule rule; - krb5_const_principal in_princ; - krb5_principal tmp_princ; - krb5_creds *creds; - int is_trivial; - int done; + krb5_const_principal in_princ; /* given princ */ + krb5_const_principal out_princ; /* princ to be output */ + krb5_principal tmp_princ; /* to be freed */ + int is_trivial; /* no canon to be done */ + int done; /* no more rules to be applied */ + size_t cursor; /* current/next rule */ }; /** @@ -1941,47 +1939,30 @@ struct krb5_name_canon_iterator_data { * * @param context Kerberos context * @param in_princ principal name to be canonicalized OR - * @param in_creds credentials whose server is to be canonicalized * @param iter output iterator object */ KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL krb5_name_canon_iterator_start(krb5_context context, krb5_const_principal in_princ, - krb5_creds *in_creds, krb5_name_canon_iterator *iter) { krb5_error_code ret; krb5_name_canon_iterator state; - krb5_const_principal princ; *iter = NULL; state = calloc(1, sizeof (*state)); if (state == NULL) return krb5_enomem(context); - princ = in_princ ? in_princ : in_creds->server; + state->in_princ = in_princ; - if (princ_type(princ) != KRB5_NT_SRV_HST_NEEDS_CANON) { - /* - * Name needs no canon -> trivial iterator; we still want an - * iterator just so as to keep callers simple. - */ - state->is_trivial = 1; - state->creds = in_creds; - } else { + if (princ_type(state->in_princ) == KRB5_NT_SRV_HST_NEEDS_CANON) { ret = _krb5_get_name_canon_rules(context, &state->rules); if (ret) goto out; - state->rule = state->rules; - } - - state->in_princ = princ; - if (in_creds) { - if (!state->is_trivial) { - ret = krb5_copy_creds(context, in_creds, &state->creds); - if (ret) goto out; - } - state->tmp_princ = state->creds->server; /* so we don't leak */ + } else { + /* Name needs no canon -> trivial iterator: in_princ is canonical */ + state->is_trivial = 1; } *iter = state; @@ -1996,9 +1977,9 @@ out: * Helper for name canon iteration. */ static krb5_error_code -krb5_name_canon_iterate(krb5_context context, - krb5_name_canon_iterator *iter, - krb5_name_canon_rule_options *rule_opts) +name_canon_iterate(krb5_context context, + krb5_name_canon_iterator *iter, + krb5_name_canon_rule_options *rule_opts) { krb5_error_code ret; krb5_name_canon_iterator state = *iter; @@ -2006,8 +1987,9 @@ krb5_name_canon_iterate(krb5_context context, if (rule_opts) *rule_opts = 0; - if (!state) + if (state == NULL) return 0; + if (state->done) { krb5_free_name_canon_iterator(context, state); *iter = NULL; @@ -2015,28 +1997,37 @@ krb5_name_canon_iterate(krb5_context context, } if (state->is_trivial && !state->done) { + state->out_princ = state->in_princ; state->done = 1; return 0; } + heim_assert(state->rules != NULL && + state->rules[state->cursor].type != KRB5_NCRT_BOGUS, + "Internal error during name canonicalization"); + do { krb5_free_principal(context, state->tmp_princ); - ret = _krb5_apply_name_canon_rule(context, state->rule, + ret = apply_name_canon_rule(context, state->rules, state->cursor, state->in_princ, &state->tmp_princ, rule_opts); - if (ret) + if (ret) { + krb5_free_name_canon_iterator(context, state); + *iter = NULL; return ret; - state->rule = state->rule->next; - } while (state->rule != NULL && state->tmp_princ == NULL); + } + state->cursor++; + } while (state->tmp_princ == NULL && + state->rules[state->cursor].type != KRB5_NCRT_BOGUS); + if (state->rules[state->cursor].type == KRB5_NCRT_BOGUS) + state->done = 1; + + state->out_princ = state->tmp_princ; if (state->tmp_princ == NULL) { krb5_free_name_canon_iterator(context, state); *iter = NULL; return 0; } - if (state->creds) - state->creds->server = state->tmp_princ; - if (state->rule == NULL) - state->done = 1; return 0; } @@ -2052,43 +2043,18 @@ krb5_name_canon_iterate(krb5_context context, * @param rule_opts output rule options */ KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -krb5_name_canon_iterate_princ(krb5_context context, - krb5_name_canon_iterator *iter, - krb5_principal *try_princ, - krb5_name_canon_rule_options *rule_opts) +krb5_name_canon_iterate(krb5_context context, + krb5_name_canon_iterator *iter, + krb5_const_principal *try_princ, + krb5_name_canon_rule_options *rule_opts) { krb5_error_code ret; *try_princ = NULL; - ret = krb5_name_canon_iterate(context, iter, rule_opts); - if (*iter) - *try_princ = (*iter)->tmp_princ; - return ret; -} -/** - * Iteratively apply name canon rules, outputing a krb5_creds and rule - * options each time. Iteration completes when the @iter is NULL on - * return or when an error is returned. Callers must free the iterator - * if they abandon it mid-way. - * - * @param context Kerberos context - * @param iter name canon rule iterator - * @param try_creds output krb5_creds - * @param rule_opts output rule options - */ -KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -krb5_name_canon_iterate_creds(krb5_context context, - krb5_name_canon_iterator *iter, - krb5_creds **try_creds, - krb5_name_canon_rule_options *rule_opts) -{ - krb5_error_code ret; - - *try_creds = NULL; - ret = krb5_name_canon_iterate(context, iter, rule_opts); + ret = name_canon_iterate(context, iter, rule_opts); if (*iter) - *try_creds = (*iter)->creds; + *try_princ = (*iter)->out_princ; return ret; } @@ -2101,14 +2067,7 @@ krb5_free_name_canon_iterator(krb5_context context, { if (iter == NULL) return; - if (!iter->is_trivial) { - if (iter->creds) { - krb5_free_creds(context, iter->creds); - iter->tmp_princ = NULL; - } - if (iter->tmp_princ) - krb5_free_principal(context, iter->tmp_princ); - _krb5_free_name_canon_rules(context, iter->rules); - } + if (iter->tmp_princ) + krb5_free_principal(context, iter->tmp_princ); free(iter); } diff --git a/tests/kdc/krb5-canon2.conf.in b/tests/kdc/krb5-canon2.conf.in index 63a11f4e4..01d33f663 100644 --- a/tests/kdc/krb5-canon2.conf.in +++ b/tests/kdc/krb5-canon2.conf.in @@ -5,7 +5,7 @@ name_canon_rules = as-is:realm=TEST.H5L.SE name_canon_rules = as-is:realm=TEST2.H5L.SE name_canon_rules = as-is:realm=TEST3.H5L.SE - name_canon_rules = use-resolver-searchlist + name_canon_rules = nss [appdefaults] pkinit_anchors = FILE:@srcdir@/../../lib/hx509/data/ca.crt