diff --git a/kdc/pkinit.c b/kdc/pkinit.c index d85b15650..452515d19 100644 --- a/kdc/pkinit.c +++ b/kdc/pkinit.c @@ -1608,6 +1608,7 @@ match_rfc_san(krb5_context context, return KRB5_KDC_ERR_CLIENT_NAME_MISMATCH; } + memset(&principal, 0, sizeof (principal)); principal.name = kn.principalName; principal.realm = kn.realm; diff --git a/kpasswd/kpasswdd.c b/kpasswd/kpasswdd.c index 690bd6d6a..a9a41f6bf 100644 --- a/kpasswd/kpasswdd.c +++ b/kpasswd/kpasswdd.c @@ -293,6 +293,7 @@ change (krb5_auth_context auth_context, if (chpw.targname) { krb5_principal_data princ; + memset(&princ, 0, sizeof (princ)); princ.name = *chpw.targname; princ.realm = *chpw.targrealm; if (princ.realm == NULL) { diff --git a/kuser/kgetcred.c b/kuser/kgetcred.c index 7742eca40..f9c913fcd 100644 --- a/kuser/kgetcred.c +++ b/kuser/kgetcred.c @@ -85,7 +85,7 @@ main(int argc, char **argv) krb5_creds *out; int optidx = 0; krb5_get_creds_opt opt; - krb5_principal server; + krb5_principal server = NULL; krb5_principal impersonate = NULL; setprogname (argv[0]); @@ -108,9 +108,6 @@ main(int argc, char **argv) argc -= optidx; argv += optidx; - if (argc != 1) - usage (1); - if(cache_str) { ret = krb5_cc_resolve(context, cache_str, &cache); if (ret) @@ -190,18 +187,70 @@ main(int argc, char **argv) KRB5_GC_CONSTRAINED_DELEGATION); } - ret = krb5_parse_name(context, argv[0], &server); - if (ret) - krb5_err (context, 1, ret, "krb5_parse_name %s", argv[0]); - if (nametype_str) { int32_t nametype; + int do_sn2p = 1; + char *sname = NULL; + char *hname = NULL; ret = krb5_parse_nametype(context, nametype_str, &nametype); if (ret) krb5_err(context, 1, ret, "krb5_parse_nametype"); - server->name.name_type = (NAME_TYPE)nametype; + if (nametype == KRB5_NT_SRV_HST) { + if (argc == 1) { + char *cp; + + for (cp = sname; *cp; cp++) { + if (cp[0] == '\\') { + cp++; + } else if (cp[0] == '@' && cp[1] != '\0') { + /* If a realm is given we assume no canon is needed */ + do_sn2p = 0; + break; + } + } + if (do_sn2p) { + sname = argv[0]; + for (cp = sname; *cp; cp++) { + if (cp[0] == '\\') { + cp++; + } else if (cp[0] == '/') { + *cp = '\0'; + hname = cp + 1; + } else if (cp[0] == '@') { + *cp = '\0'; + break; + } + } + } + } else if (argc == 2) { + sname = argv[0]; + hname = argv[1]; + } else if (argc != 0) { + usage(1); + } + ret = krb5_sname_to_principal(context, hname, sname, + KRB5_NT_SRV_HST, &server); + if (ret) + krb5_err(context, 1, ret, "krb5_sname_to_principal %s/%s", + (sname && *sname) ? sname : "", + (hname && *hname) ? hname : ""); + + } else { + if (argc != 1) + usage(1); + ret = krb5_parse_name(context, argv[0], &server); + if (ret) + krb5_err (context, 1, ret, "krb5_parse_name %s", argv[0]); + server->name.name_type = (NAME_TYPE)nametype; + } + } else if (argc == 1) { + ret = krb5_parse_name(context, argv[0], &server); + if (ret) + krb5_err (context, 1, ret, "krb5_parse_name %s", argv[0]); + } else { + usage(1); } ret = krb5_get_creds(context, opt, cache, server, &out); diff --git a/lib/asn1/krb5.asn1 b/lib/asn1/krb5.asn1 index 568fe0cd0..265b5f024 100644 --- a/lib/asn1/krb5.asn1 +++ b/lib/asn1/krb5.asn1 @@ -89,7 +89,8 @@ NAME-TYPE ::= INTEGER { KRB5_NT_ENT_PRINCIPAL_AND_ID(-130), -- Windows 2000 UPN and SID KRB5_NT_MS_PRINCIPAL(-128), -- NT 4 style name KRB5_NT_MS_PRINCIPAL_AND_ID(-129), -- NT style name and SID - KRB5_NT_NTLM(-1200) -- NTLM name, realm is domain + KRB5_NT_NTLM(-1200), -- NTLM name, realm is domain + KRB5_NT_SRV_HST_NEEDS_CANON (-195894762) -- -(0x0bad1dea) } -- message types @@ -269,6 +270,10 @@ PrincipalName ::= SEQUENCE { Principal ::= SEQUENCE { name[0] PrincipalName, realm[1] Realm + -- Note that while it'd be nice to be able to add OPTIONAL + -- fields at the end here there are issues regarding + -- applications that allocate krb5_principal_data's on the + -- stack. } Principals ::= SEQUENCE OF Principal diff --git a/lib/krb5/asn1_glue.c b/lib/krb5/asn1_glue.c index a821faff9..5b2a2a328 100644 --- a/lib/krb5/asn1_glue.c +++ b/lib/krb5/asn1_glue.c @@ -53,7 +53,7 @@ _krb5_principalname2krb5_principal (krb5_context context, krb5_error_code ret; krb5_principal p; - p = malloc(sizeof(*p)); + p = calloc(1, sizeof(*p)); if (p == NULL) return ENOMEM; ret = copy_PrincipalName(&from, &p->name); diff --git a/lib/krb5/get_cred.c b/lib/krb5/get_cred.c index e3bb23a2e..46bf70bf5 100644 --- a/lib/krb5/get_cred.c +++ b/lib/krb5/get_cred.c @@ -1116,6 +1116,68 @@ _krb5_get_cred_kdc_any(krb5_context context, ret_tgts); } +static krb5_error_code +check_cc(krb5_context context, krb5_flags options, krb5_ccache ccache, + krb5_creds *in_creds, krb5_creds *out_creds) +{ + krb5_error_code ret; + krb5_timestamp timeret; + + /* + * If we got a credential, check if credential is expired before + * returning it. + */ + ret = krb5_cc_retrieve_cred(context, ccache, + options & KRB5_TC_MATCH_KEYTYPE, + in_creds, out_creds); + if (ret != 0) + return ret; /* Caller will check for KRB5_CC_END */ + + /* + * If we got a credential, check if credential is expired before + * returning it, but only if KRB5_GC_EXPIRED_OK is not set. + */ + + /* If expired ok, don't bother checking */ + if (options & KRB5_GC_EXPIRED_OK) + return 0; + + krb5_timeofday(context, &timeret); + if (out_creds->times.endtime > timeret) + return 0; + + /* Expired and not ok; remove and pretend we didn't find it */ + if (options & KRB5_GC_CACHED) + krb5_cc_remove_cred(context, ccache, 0, out_creds); + + krb5_free_cred_contents(context, out_creds); + memset(out_creds, 0, sizeof (*out_creds)); + return KRB5_CC_END; +} + +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; + + 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); +} + KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL krb5_get_credentials_with_flags(krb5_context context, @@ -1126,7 +1188,10 @@ krb5_get_credentials_with_flags(krb5_context context, krb5_creds **out_creds) { krb5_error_code ret; + krb5_name_canon_iterator name_canon_iter = NULL; + krb5_name_canon_rule_options rule_opts; krb5_creds **tgts; + krb5_creds *try_creds; krb5_creds *res_creds; int i; @@ -1134,6 +1199,7 @@ krb5_get_credentials_with_flags(krb5_context context, ret = krb5_enctype_valid(context, in_creds->session.keytype); if (ret) return ret; + options |= KRB5_TC_MATCH_KEYTYPE; } *out_creds = NULL; @@ -1144,46 +1210,43 @@ krb5_get_credentials_with_flags(krb5_context context, return ENOMEM; } - if (in_creds->session.keytype) - options |= KRB5_TC_MATCH_KEYTYPE; - - /* - * If we got a credential, check if credential is expired before - * returning it. - */ - ret = krb5_cc_retrieve_cred(context, - ccache, - in_creds->session.keytype ? - KRB5_TC_MATCH_KEYTYPE : 0, - in_creds, res_creds); - /* - * If we got a credential, check if credential is expired before - * returning it, but only if KRB5_GC_EXPIRED_OK is not set. - */ - if (ret == 0) { - krb5_timestamp timeret; - - /* If expired ok, don't bother checking */ - if(options & KRB5_GC_EXPIRED_OK) { - *out_creds = res_creds; - return 0; - } - - krb5_timeofday(context, &timeret); - if(res_creds->times.endtime > timeret) { + 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; } - if(options & KRB5_GC_CACHED) - krb5_cc_remove_cred(context, ccache, 0, res_creds); - - } else if(ret != KRB5_CC_END) { - free(res_creds); - return ret; } - free(res_creds); + + ret = krb5_name_canon_iterator_start(context, NULL, in_creds, + &name_canon_iter); + if (ret) + return ret; + +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); + if (ret) + goto out; + if (name_canon_iter == NULL) { + if (options & KRB5_GC_CACHED) + ret = KRB5_CC_NOTFOUND; + else + ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; + goto out; + } + + ret = check_cc(context, options, ccache, try_creds, res_creds); + if (ret == 0) { + *out_creds = res_creds; + goto out; + } else if(ret != KRB5_CC_END) { + goto out; + } if(options & KRB5_GC_CACHED) - return not_found(context, in_creds->server, KRB5_CC_NOTFOUND); + goto next_rule; if(options & KRB5_GC_USER_USER) flags.b.enc_tkt_in_skey = 1; @@ -1192,15 +1255,26 @@ krb5_get_credentials_with_flags(krb5_context context, tgts = NULL; ret = _krb5_get_cred_kdc_any(context, flags, ccache, - in_creds, NULL, NULL, out_creds, &tgts); + try_creds, NULL, NULL, out_creds, &tgts); for(i = 0; tgts && tgts[i]; i++) { krb5_cc_store_cred(context, ccache, tgts[i]); krb5_free_creds(context, tgts[i]); } free(tgts); + if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN && + !(rule_opts & KRB5_NCRO_SECURE)) + goto next_rule; + if(ret == 0 && (options & KRB5_GC_NO_STORE) == 0) - krb5_cc_store_cred(context, ccache, *out_creds); - return ret; + store_cred(context, ccache, in_creds->server, *out_creds); + +out: + krb5_free_name_canon_iterator(context, name_canon_iter); + if (!ret) { + krb5_free_creds(context, res_creds); + return not_found(context, in_creds->server, ret); + } + return 0; } KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL @@ -1315,7 +1389,6 @@ krb5_get_creds_opt_set_ticket(krb5_context context, } - KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL krb5_get_creds(krb5_context context, krb5_get_creds_opt opt, @@ -1328,7 +1401,10 @@ 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_name_canon_iterator name_canon_iter = NULL; + krb5_name_canon_rule_options rule_opts; int i; if (opt && opt->enctype) { @@ -1364,48 +1440,42 @@ krb5_get_creds(krb5_context context, options |= KRB5_TC_MATCH_KEYTYPE; } - /* - * If we got a credential, check if credential is expired before - * returning it. - */ - ret = krb5_cc_retrieve_cred(context, - ccache, - options & KRB5_TC_MATCH_KEYTYPE, - &in_creds, res_creds); - /* - * If we got a credential, check if credential is expired before - * returning it, but only if KRB5_GC_EXPIRED_OK is not set. - */ - if (ret == 0) { - krb5_timestamp timeret; - - /* If expired ok, don't bother checking */ - if(options & KRB5_GC_EXPIRED_OK) { - *out_creds = res_creds; - krb5_free_principal(context, in_creds.client); - goto out; - } - - krb5_timeofday(context, &timeret); - if(res_creds->times.endtime > timeret) { + /* 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; - krb5_free_principal(context, in_creds.client); - goto out; + goto out; } - if(options & KRB5_GC_CACHED) - krb5_cc_remove_cred(context, ccache, 0, res_creds); + } + ret = krb5_name_canon_iterator_start(context, NULL, &in_creds, + &name_canon_iter); + if (ret) + goto out; + +next_rule: + ret = krb5_name_canon_iterate_creds(context, &name_canon_iter, &try_creds, + &rule_opts); + if (ret) + return ret; + if (name_canon_iter == NULL) { + if (options & KRB5_GC_CACHED) + ret = KRB5_CC_NOTFOUND; + else + ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; + goto out; + } + ret = check_cc(context, options, ccache, try_creds, res_creds); + if (ret == 0) { + *out_creds = res_creds; + goto out; } else if(ret != KRB5_CC_END) { - free(res_creds); - krb5_free_principal(context, in_creds.client); - goto out; - } - free(res_creds); - if(options & KRB5_GC_CACHED) { - krb5_free_principal(context, in_creds.client); - ret = not_found(context, in_creds.server, KRB5_CC_NOTFOUND); goto out; } + if(options & KRB5_GC_CACHED) + goto next_rule; + if(options & KRB5_GC_USER_USER) { flags.b.enc_tkt_in_skey = 1; options |= KRB5_GC_NO_STORE; @@ -1423,18 +1493,28 @@ krb5_get_creds(krb5_context context, tgts = NULL; ret = _krb5_get_cred_kdc_any(context, flags, ccache, - &in_creds, opt->self, opt->ticket, + try_creds, opt->self, opt->ticket, out_creds, &tgts); - krb5_free_principal(context, in_creds.client); for(i = 0; tgts && tgts[i]; i++) { krb5_cc_store_cred(context, ccache, tgts[i]); krb5_free_creds(context, tgts[i]); } free(tgts); - if(ret == 0 && (options & KRB5_GC_NO_STORE) == 0) - krb5_cc_store_cred(context, ccache, *out_creds); - out: + if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN && + !(rule_opts & KRB5_NCRO_SECURE)) + goto next_rule; + + if(ret == 0 && (options & KRB5_GC_NO_STORE) == 0) + store_cred(context, ccache, inprinc, *out_creds); + +out: + if (ret != 0) { + krb5_free_creds(context, res_creds); + ret = not_found(context, inprinc, ret); + } + 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); return ret; diff --git a/lib/krb5/keytab.c b/lib/krb5/keytab.c index 8ca515f21..e919e2243 100644 --- a/lib/krb5/keytab.c +++ b/lib/krb5/keytab.c @@ -588,30 +588,13 @@ _krb5_kt_principal_not_found(krb5_context context, return ret; } - -/** - * Retrieve the keytab entry for `principal, kvno, enctype' into `entry' - * from the keytab `id'. Matching is done like krb5_kt_compare(). - * - * @param context a Keberos context. - * @param id a keytab. - * @param principal principal to match, NULL matches all principals. - * @param kvno key version to match, 0 matches all key version numbers. - * @param enctype encryption type to match, 0 matches all encryption types. - * @param entry the returned entry, free with krb5_kt_free_entry(). - * - * @return Return an error code or 0, see krb5_get_error_message(). - * - * @ingroup krb5_keytab - */ - -KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -krb5_kt_get_entry(krb5_context context, - krb5_keytab id, - krb5_const_principal principal, - krb5_kvno kvno, - krb5_enctype enctype, - krb5_keytab_entry *entry) +static krb5_error_code +krb5_kt_get_entry_wrapped(krb5_context context, + krb5_keytab id, + krb5_const_principal principal, + krb5_kvno kvno, + krb5_enctype enctype, + krb5_keytab_entry *entry) { krb5_keytab_entry tmp; krb5_error_code ret; @@ -654,6 +637,56 @@ krb5_kt_get_entry(krb5_context context, return 0; } +/** + * Retrieve the keytab entry for `principal, kvno, enctype' into `entry' + * from the keytab `id'. Matching is done like krb5_kt_compare(). + * + * @param context a Keberos context. + * @param id a keytab. + * @param principal principal to match, NULL matches all principals. + * @param kvno key version to match, 0 matches all key version numbers. + * @param enctype encryption type to match, 0 matches all encryption types. + * @param entry the returned entry, free with krb5_kt_free_entry(). + * + * @return Return an error code or 0, see krb5_get_error_message(). + * + * @ingroup krb5_keytab + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_kt_get_entry(krb5_context context, + krb5_keytab id, + krb5_const_principal principal, + krb5_kvno kvno, + krb5_enctype enctype, + krb5_keytab_entry *entry) +{ + krb5_error_code ret; + krb5_principal try_princ; + krb5_name_canon_iterator name_canon_iter; + + if (!principal || principal->name.name_type != KRB5_NT_SRV_HST_NEEDS_CANON) + return krb5_kt_get_entry_wrapped(context, id, principal, kvno, enctype, + entry); + + ret = krb5_name_canon_iterator_start(context, principal, NULL, + &name_canon_iter); + if (ret) + return ret; + + do { + ret = krb5_name_canon_iterate_princ(context, &name_canon_iter, + &try_princ, NULL); + if (ret) + break; + ret = krb5_kt_get_entry_wrapped(context, id, try_princ, kvno, + enctype, entry); + } while (ret == KRB5_KT_NOTFOUND && name_canon_iter); + + krb5_free_name_canon_iterator(context, name_canon_iter); + return ret; +} + /** * Copy the contents of `in' into `out'. * diff --git a/lib/krb5/krb5.h b/lib/krb5/krb5.h index 9c0f56694..4a7c2ca30 100644 --- a/lib/krb5/krb5.h +++ b/lib/krb5/krb5.h @@ -883,6 +883,39 @@ typedef struct { krb5int32 ks_salttype; }krb5_key_salt_tuple; +/* + * Name canonicalization rules + */ + +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; + +typedef enum krb5_name_canon_rule_options { + KRB5_NCRO_GC_ONLY = 1 << 0, + KRB5_NCRO_NO_REFERRALS = 1 << 1, + KRB5_NCRO_NO_REVLOOKUP = 1 << 2, + KRB5_NCRO_SECURE = 1 << 3 +} krb5_name_canon_rule_options; + +typedef struct krb5_name_canon_rule *krb5_name_canon_rule; +struct krb5_name_canon_rule { + krb5_name_canon_rule next; + krb5_name_canon_rule_type type; + krb5_name_canon_rule_options options; + char *domain; + char *realm; + unsigned int mindots; +}; + +typedef struct krb5_name_canon_iterator *krb5_name_canon_iterator; + +#define krb5int_name_canon_rule_next(context, rule) (rule->next) + /* * */ diff --git a/lib/krb5/principal.c b/lib/krb5/principal.c index a10d2d079..0b6325a0e 100644 --- a/lib/krb5/principal.c +++ b/lib/krb5/principal.c @@ -57,6 +57,8 @@ host/admin@H5L.ORG #include #include "resolve.h" +#include + #define princ_num_comp(P) ((P)->name.name_string.len) #define princ_type(P) ((P)->name.name_type) #define princ_comp(P) ((P)->name.name_string.val) @@ -346,7 +348,7 @@ krb5_parse_name_flags(krb5_context context, comp[n][q - start] = 0; n++; } - *principal = malloc(sizeof(**principal)); + *principal = calloc(1, sizeof(**principal)); if (*principal == NULL) { ret = ENOMEM; krb5_set_error_message(context, ret, @@ -965,6 +967,48 @@ krb5_principal_compare(krb5_context context, krb5_const_principal princ1, krb5_const_principal princ2) { + if ((princ_type(princ1) == KRB5_NT_SRV_HST_NEEDS_CANON || + princ_type(princ2) == KRB5_NT_SRV_HST_NEEDS_CANON) && + princ_type(princ2) != princ_type(princ1)) { + krb5_error_code ret; + krb5_boolean princs_eq; + krb5_const_principal princ2canon; + krb5_const_principal other_princ; + krb5_principal try_princ; + krb5_name_canon_iterator nci; + + if (princ_type(princ1) == KRB5_NT_SRV_HST_NEEDS_CANON) { + princ2canon = princ1; + other_princ = princ2; + } else { + princ2canon = princ2; + other_princ = princ1; + } + + ret = krb5_name_canon_iterator_start(context, princ2canon, NULL, &nci); + if (ret) + return ret; + do { + ret = krb5_name_canon_iterate_princ(context, &nci, &try_princ, + NULL); + if (ret || try_princ == NULL) + break; + princs_eq = krb5_principal_compare(context, try_princ, other_princ); + if (princs_eq) { + krb5_free_name_canon_iterator(context, nci); + return TRUE; + } + } while (nci != NULL); + krb5_free_name_canon_iterator(context, nci); + } + + /* + * 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)) return FALSE; return krb5_principal_compare_any_realm(context, princ1, princ2); @@ -1013,28 +1057,17 @@ krb5_principal_match(krb5_context context, return TRUE; } -/** - * Create a principal for the service running on hostname. If - * KRB5_NT_SRV_HST is used, the hostname is canonization using DNS (or - * some other service), this is potentially insecure. - * - * @param context A Kerberos context. - * @param hostname hostname to use - * @param sname Service name to use - * @param type name type of pricipal, use KRB5_NT_SRV_HST or KRB5_NT_UNKNOWN. - * @param ret_princ return principal, free with krb5_free_principal(). - * - * @return An krb5 error code, see krb5_get_error_message(). - * - * @ingroup krb5_principal +/* + * This is the original krb5_sname_to_principal(), renamed to be a + * helper of the new one. */ - -KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -krb5_sname_to_principal (krb5_context context, - const char *hostname, - const char *sname, - int32_t type, - krb5_principal *ret_princ) +static krb5_error_code +krb5_sname_to_principal_old(krb5_context context, + const char *realm, + const char *hostname, + const char *sname, + int32_t type, + krb5_principal *ret_princ) { krb5_error_code ret; char localhost[MAXHOSTNAMELEN]; @@ -1060,19 +1093,25 @@ krb5_sname_to_principal (krb5_context context, if(sname == NULL) sname = "host"; if(type == KRB5_NT_SRV_HST) { - ret = krb5_expand_hostname_realms (context, hostname, - &host, &realms); + if (realm) + ret = krb5_expand_hostname(context, hostname, &host); + else + ret = krb5_expand_hostname_realms(context, hostname, + &host, &realms); if (ret) return ret; strlwr(host); hostname = host; - } else { + if (!realm) + realm = realms[0]; + } else if (!realm) { ret = krb5_get_host_realm(context, hostname, &realms); if(ret) return ret; + realm = realms[0]; } - ret = krb5_make_principal(context, ret_princ, realms[0], sname, + ret = krb5_make_principal(context, ret_princ, realm, sname, hostname, NULL); if(host) free(host); @@ -1096,6 +1135,7 @@ static const struct { { "ENT_PRINCIPAL_AND_ID", KRB5_NT_ENT_PRINCIPAL_AND_ID }, { "MS_PRINCIPAL", KRB5_NT_MS_PRINCIPAL }, { "MS_PRINCIPAL_AND_ID", KRB5_NT_MS_PRINCIPAL_AND_ID }, + { "SRV_HST_NEEDS_CANON", KRB5_NT_SRV_HST_NEEDS_CANON }, { NULL, 0 } }; @@ -1134,3 +1174,840 @@ krb5_principal_is_krbtgt(krb5_context context, krb5_const_principal p) strcmp(p->name.name_string.val[0], KRB5_TGS_NAME) == 0; } + +/** + * Create a principal for the given service running on the given + * hostname. If KRB5_NT_SRV_HST is used, the hostname is canonicalized + * according the configured name canonicalization rules, with + * canonicalization delayed in some cases. One rule involves DNS, which + * is insecure unless DNSSEC is used, but we don't use DNSSEC-capable + * resolver APIs here, so that if DNSSEC is used we wouldn't know it. + * + * Canonicalization is immediate (not delayed) only when there is only + * one canonicalization rule and that rule indicates that we should do a + * host lookup by name (i.e., DNS). + * + * @param context A Kerberos context. + * @param hostname hostname to use + * @param sname Service name to use + * @param type name type of pricipal, use KRB5_NT_SRV_HST or KRB5_NT_UNKNOWN. + * @param ret_princ return principal, free with krb5_free_principal(). + * + * @return An krb5 error code, see krb5_get_error_message(). + * + * @ingroup krb5_principal + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_sname_to_principal(krb5_context context, + const char *hostname, + const char *sname, + int32_t type, + krb5_principal *ret_princ) +{ + char *realm, *remote_host; + krb5_error_code ret; + register char *cp; + char localname[MAXHOSTNAMELEN]; + + if ((type != KRB5_NT_UNKNOWN) && + (type != KRB5_NT_SRV_HST)) + return KRB5_SNAME_UNSUPP_NAMETYPE; + + /* if hostname is NULL, use local hostname */ + if (!hostname) { + if (gethostname(localname, MAXHOSTNAMELEN)) + return errno; + hostname = localname; + } + + /* if sname is NULL, use "host" */ + if (!sname) + sname = "host"; + + remote_host = strdup(hostname); + if (!remote_host) + return ENOMEM; + + if (type == KRB5_NT_SRV_HST) { + krb5_name_canon_rule rules; + + /* Lower-case the hostname, because that's the convention */ + for (cp = remote_host; *cp; cp++) + if (isupper((int) (*cp))) + *cp = tolower((int) (*cp)); + + ret = krb5int_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); + free(remote_host); + krb5int_free_name_canon_rules(context, rules); + return ret; + } + krb5int_free_name_canon_rules(context, rules); + } + + /* Trailing dot(s) would be bad */ + if (remote_host[0]) { + cp = remote_host + strlen(remote_host)-1; + if (*cp == '.') + *cp = '\0'; + } + + realm = ""; /* "Referral realm" -- borrowed from newer MIT */ + + ret = krb5_build_principal(context, ret_princ, strlen(realm), + realm, sname, remote_host, + (char *)0); + + if (type == KRB5_NT_SRV_HST) { + /* + * 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. + */ + (*ret_princ)->name.name_type = KRB5_NT_SRV_HST_NEEDS_CANON; + + _krb5_debug(context, 5, "Building a delayed canon principal for %s/%s@", + sname, remote_host); + } + + free(remote_host); + return ret; +} + +/* + * Helper function to parse name canonicalization rule tokens. + */ +static +krb5_error_code +rule_parse_token(krb5_context context, krb5_name_canon_rule rule, + const char *tok) +{ + long int n; + + /* + * 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. + */ + /* 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; + /* Rule options: */ + } else if (strcmp(tok, "secure") == 0) { + rule->options |= KRB5_NCRO_SECURE; + } else if (strcmp(tok, "weak") == 0) { + rule->options &= ~KRB5_NCRO_SECURE; + } 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; + } else if (strcmp(tok, "reverse_lookup") == 0) { + rule->options &= ~KRB5_NCRO_NO_REVLOOKUP; + } else if (strcmp(tok, "no_reverse_lookup") == 0) { + rule->options |= KRB5_NCRO_NO_REVLOOKUP; + /* Rule ancilliary data: */ + } else if (strncmp(tok, "domain=", strlen("domain=")) == 0) { + free(rule->domain); + rule->domain = strdup(tok + strlen("domain=")); + if (!rule->domain) + return ENOMEM; + } else if (strncmp(tok, "realm=", strlen("realm=")) == 0) { + free(rule->realm); + rule->realm = strdup(tok + strlen("realm=")); + if (!rule->realm) + return ENOMEM; + } else if (strncmp(tok, "mindots=", strlen("mindots=")) == 0) { + errno = 0; + n = strtol(tok + strlen("mindots="), NULL, 10); + if (errno == 0 && n > 0 && n < 8) + 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 srch_list_len; + size_t i; + int ret; + + /* Sanitize */ + assert((*n) > insert_point); + 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; + srch_list_len = sizeof (statbuf.dnsrch) / sizeof (*statbuf.dnsrch); +#else + ret = res_init(); + if (ret) + return ENOENT; /* XXX Create a better error */ + dnsrch = _res.dnsrch; + srch_list_len = sizeof (_res.dnsrch) / sizeof (*_res.dnsrch); +#endif /* USE_RES_NINIT */ + + for (i = 0; i < srch_list_len; i++) { + if (!dnsrch || dnsrch[i] == NULL) { + srch_list_len = i; + break; + } + } + + if (srch_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(srch_list_len, sizeof (*domains)); + if (domains == NULL) + return ENOMEM; + for (i = 0; i < srch_list_len; i++) { + if ((domains[i] = strdup(dnsrch[i])) == NULL) { + for (i--; i >= 0; i--) + free(domains[i]); + return ENOMEM; + } + } + + if (srch_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) + srch_list_len - 1)); + if (new_r == NULL) { + for (i = 0; i < srch_list_len; i++) + free(domains[i]); + free(domains); + return ENOMEM; + } + } else { + new_r = *r; /* srch_list_len == 1 */ + } + + /* Make room for the new rules */ + if (insert_point < (*n) - 1) { + _krb5_debug(context, 5, "Inserting %d qualify rules in place of a " + "resolver searchlist rule", srch_list_len); + /* + * Move the rules that follow the search list rule down by + * srch_list_len - 1 rules. + */ + memmove(&new_r[insert_point + srch_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]) * srch_list_len); + + /* Setup the new rules */ + for (i = 0; i < srch_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 = new_r[insert_point].options; + } + free(domains); + + *r = new_r; + *n += srch_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 +krb5_error_code +parse_name_canon_rules(krb5_context context, char **rulestrs, + krb5_name_canon_rule *rules) +{ + krb5_error_code ret; + char *tok; + char *cp; + char **cpp; + unsigned int n = 0; + unsigned int i, k; + krb5_name_canon_rule r; + + for (cpp = rulestrs; *cpp; cpp++) + n++; + + if ((r = calloc(n, sizeof (*r))) == NULL) + return ENOMEM; + + /* This code is written without use of strtok_r() :( */ + for (i = 0, k = 0; i < n; i++) { + cp = rulestrs[i]; + do { + tok = cp; + cp = strpbrk(cp, ":"); + if (cp) + *cp++ = '\0'; /* delimit token */ + ret = rule_parse_token(context, &r[k], tok); + } while (cp && *cp); + /* Loosely 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].domain); + r[k].realm = NULL; + r[k].domain = NULL; + /* XXX Trace this! */ + continue; /* bogus rule */ + } + 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; + } + + /* 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++; + } + + *rules = r; + return 0; /* We don't communicate bad rule errors here */ +} + +/** + * This function returns an array of host-based service name + * canonicalization rules. The array of rules is organized as a list. + * See the definition of krb5_name_canon_rule. + * + * @param context A Kerberos context. + * @param rules Output location for array of rules. + */ +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5int_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, + "libdefaults", + 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 ENOMEM; + (*rules)->type = KRB5_NCRT_NSS; + return 0; + } + + 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 %d type=%d, options=%x, mindots=%d, " + "domain=%s, realm=%s", + k, r->type, r->options, r->mindots, + r->domain ? r->domain : "", + r->realm ? r->realm : "" + ); + } + } + + 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!"); + + return 0; +} + +static +krb5_error_code +get_host_realm(krb5_context context, const char *hostname, char **realm) +{ + krb5_error_code ret; + char **hrealms = NULL; + + *realm = NULL; + if ((ret = krb5_get_host_realm(context, hostname, &hrealms))) + return ret; + if (!hrealms) + return KRB5_ERR_HOST_REALM_UNKNOWN; + if (!hrealms[0]) { + krb5_free_host_realm(context, hrealms); + return KRB5_ERR_HOST_REALM_UNKNOWN; + } + *realm = strdup(hrealms[0]); + krb5_free_host_realm(context, hrealms); + 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_LIB_CALL +krb5int_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) +{ + krb5_error_code ret; + unsigned int ndots = 0; + char *realm = NULL; + const char *sname = NULL; + const char *hostname = NULL; + char *new_hostname; + const char *cp; + + assert(in_princ->name.name_type == KRB5_NT_SRV_HST_NEEDS_CANON); + *out_princ = NULL; + if (rule_opts) + *rule_opts = 0; + 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); + _krb5_debug(context, 5, "Applying a name rule (type %d) to %s", rule->type, + hostname); + if (rule_opts) + *rule_opts = rule->options; + ret = 0; + 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", rule->realm, sname, hostname); + ret = krb5_build_principal(context, out_princ, + strlen(rule->realm), + rule->realm, sname, hostname, + (char *)0); + goto out; + break; + case KRB5_NCRT_QUALIFY: + assert(rule->domain != NULL); + cp = strchr(hostname, '.'); + if (cp && (cp = strstr(cp, rule->domain))) { + new_hostname = strdup(hostname); + if (new_hostname == NULL) { + ret = ENOMEM; + goto out; + } + + } else { + size_t len; + + len = strlen(hostname) + strlen(rule->domain) + 2; + if ((new_hostname = malloc(len)) == NULL) { + ret = ENOMEM; + goto out; + } + /* We use strcpy() and strcat() for portability for now */ + strcpy(new_hostname, hostname); + if (rule->domain[0] != '.') + strcat(new_hostname, "."); + strcat(new_hostname, rule->domain); + } + 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", rule->realm, sname, new_hostname); + ret = krb5_build_principal(context, out_princ, + strlen(realm), realm, + sname, new_hostname, (char *)0); + free(new_hostname); + goto out; + break; + case KRB5_NCRT_NSS: + _krb5_debug(context, 5, "Using name service lookups (without " + "reverse 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)) + /* + * 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; + default: + /* Can't happen, but we need this to shut up gcc */ + break; + } + +out: + if (!ret && out_princ) { + char *unparsed; + ret = krb5_unparse_name(context, *out_princ, &unparsed); + if (ret) { + _krb5_debug(context, 5, "Couldn't unparse resulting princ! (%d)", + ret); + } else { + _krb5_debug(context, 5, "Name canon rule application yields this " + "unparsed princ: %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 (realm != rule->realm) + free(realm); + if (*out_princ) + (*out_princ)->name.name_type = KRB5_NT_SRV_HST; + return ret; +} + +/** + * Free name canonicalization rules + */ +KRB5_LIB_FUNCTION void KRB5_LIB_CALL +krb5int_free_name_canon_rules(krb5_context context, krb5_name_canon_rule rules) +{ + krb5_name_canon_rule r; + + for (r = rules; r; r = r->next) { + free(r->realm); + free(r->domain); + } + + free(rules); + rules = NULL; +} + +struct krb5_name_canon_iterator { + 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; +}; + +/** + * Initialize name canonicalization iterator. + * + * @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 ENOMEM; + princ = in_princ ? in_princ : in_creds->server; + + 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 { + ret = krb5int_get_name_canon_rules(context, &state->rules); + if (ret) goto err; + state->rule = state->rules; + } + + state->in_princ = princ; + if (in_creds) { + ret = krb5_copy_creds(context, in_creds, &state->creds); + if (ret) goto err; + state->tmp_princ = state->creds->server; /* so we don't leak */ + } + + *iter = state; + return 0; + +err: + krb5_free_name_canon_iterator(context, state); + return ENOMEM; +} + +/* + * 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) +{ + krb5_error_code ret; + krb5_name_canon_iterator state = *iter; + + if (!state) + return 0; + if (state->done) { + krb5_free_name_canon_iterator(context, state); + *iter = NULL; + return 0; + } + + if (state->is_trivial && !state->done) { + state->done = 1; + return 0; + } + + krb5_free_principal(context, state->tmp_princ); + do { + ret = krb5int_apply_name_canon_rule(context, state->rule, + state->in_princ, &state->tmp_princ, rule_opts); + if (ret) + return ret; + state->rule = state->rule->next; + } while (state->rule != NULL && state->tmp_princ == NULL); + + 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; +} + +/** + * Iteratively apply name canon rules, outputing a principal 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 (input/output) + * @param try_princ output principal name + * @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_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); + if (*iter) + *try_creds = (*iter)->creds; + return ret; +} + +/** + * Free a name canonicalization rule iterator. + */ +KRB5_LIB_FUNCTION void KRB5_LIB_CALL +krb5_free_name_canon_iterator(krb5_context context, + krb5_name_canon_iterator iter) +{ + 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); + krb5int_free_name_canon_rules(context, iter->rules); + } + free(iter); +}