Home | History | Annotate | Line # | Download | only in krb5
      1 /*	$NetBSD: principal.c,v 1.4 2023/06/19 21:41:44 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1997-2007 Kungliga Tekniska Hgskolan
      5  * (Royal Institute of Technology, Stockholm, Sweden).
      6  * All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  *
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  *
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * 3. Neither the name of the Institute nor the names of its contributors
     20  *    may be used to endorse or promote products derived from this software
     21  *    without specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
     24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
     27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33  * SUCH DAMAGE.
     34  */
     35 
     36 /**
     37  * @page krb5_principal_intro The principal handing functions.
     38  *
     39  * A Kerberos principal is a email address looking string that
     40  * contains two parts separated by @.  The second part is the kerberos
     41  * realm the principal belongs to and the first is a list of 0 or
     42  * more components. For example
     43  * @verbatim
     44 lha (at) SU.SE
     45 host/hummel.it.su.se@SU.SE
     46 host/admin@H5L.ORG
     47 @endverbatim
     48  *
     49  * See the library functions here: @ref krb5_principal
     50  */
     51 
     52 #include "krb5_locl.h"
     53 #ifdef HAVE_RES_SEARCH
     54 #define USE_RESOLVER
     55 #endif
     56 #ifdef HAVE_ARPA_NAMESER_H
     57 #include <arpa/nameser.h>
     58 #endif
     59 #include <fnmatch.h>
     60 #include <krb5/resolve.h>
     61 
     62 #define princ_num_comp(P) ((P)->name.name_string.len)
     63 #define princ_type(P) ((P)->name.name_type)
     64 #define princ_comp(P) ((P)->name.name_string.val)
     65 #define princ_ncomp(P, N) ((P)->name.name_string.val[(N)])
     66 #define princ_realm(P) ((P)->realm)
     67 
     68 static krb5_error_code
     69 set_default_princ_type(krb5_principal p, NAME_TYPE defnt)
     70 {
     71     if (princ_num_comp(p) > 1 && strcmp(princ_ncomp(p, 0), KRB5_TGS_NAME) == 0)
     72         princ_type(p) = KRB5_NT_SRV_INST;
     73     else if (princ_num_comp(p) > 1 && strcmp(princ_ncomp(p, 0), "host") == 0)
     74         princ_type(p) = KRB5_NT_SRV_HST;
     75     else if (princ_num_comp(p) > 1 && strcmp(princ_ncomp(p, 0), "kca_service") == 0)
     76 	princ_type(p) = KRB5_NT_SRV_HST;
     77     else if (princ_num_comp(p) == 2 &&
     78              strcmp(princ_ncomp(p, 0), KRB5_WELLKNOWN_NAME) == 0)
     79         princ_type(p) = KRB5_NT_WELLKNOWN;
     80     else if (princ_num_comp(p) == 1 && strchr(princ_ncomp(p, 0), '@') != NULL)
     81         princ_type(p) = KRB5_NT_SMTP_NAME;
     82     else
     83         princ_type(p) = defnt;
     84     return 0;
     85 }
     86 
     87 static krb5_error_code append_component(krb5_context, krb5_principal,
     88                                         const char *, size_t);
     89 
     90 /**
     91  * Frees a Kerberos principal allocated by the library with
     92  * krb5_parse_name(), krb5_make_principal() or any other related
     93  * principal functions.
     94  *
     95  * @param context A Kerberos context.
     96  * @param p a principal to free.
     97  *
     98  * @return An krb5 error code, see krb5_get_error_message().
     99  *
    100  * @ingroup krb5_principal
    101  */
    102 
    103 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
    104 krb5_free_principal(krb5_context context,
    105 		    krb5_principal p)
    106 {
    107     if(p){
    108 	free_Principal(p);
    109 	free(p);
    110     }
    111 }
    112 
    113 /**
    114  * Set the type of the principal
    115  *
    116  * @param context A Kerberos context.
    117  * @param principal principal to set the type for
    118  * @param type the new type
    119  *
    120  * @return An krb5 error code, see krb5_get_error_message().
    121  *
    122  * @ingroup krb5_principal
    123  */
    124 
    125 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
    126 krb5_principal_set_type(krb5_context context,
    127 			krb5_principal principal,
    128 			int type)
    129 {
    130     princ_type(principal) = type;
    131 }
    132 
    133 /**
    134  * Get the type of the principal
    135  *
    136  * @param context A Kerberos context.
    137  * @param principal principal to get the type for
    138  *
    139  * @return the type of principal
    140  *
    141  * @ingroup krb5_principal
    142  */
    143 
    144 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
    145 krb5_principal_get_type(krb5_context context,
    146 			krb5_const_principal principal)
    147 {
    148     return princ_type(principal);
    149 }
    150 
    151 /**
    152  * Get the realm of the principal
    153  *
    154  * @param context A Kerberos context.
    155  * @param principal principal to get the realm for
    156  *
    157  * @return realm of the principal, don't free or use after krb5_principal is freed
    158  *
    159  * @ingroup krb5_principal
    160  */
    161 
    162 KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
    163 krb5_principal_get_realm(krb5_context context,
    164 			 krb5_const_principal principal)
    165 {
    166     return princ_realm(principal);
    167 }
    168 
    169 KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
    170 krb5_principal_get_comp_string(krb5_context context,
    171 			       krb5_const_principal principal,
    172 			       unsigned int component)
    173 {
    174     if(component >= princ_num_comp(principal))
    175        return NULL;
    176     return princ_ncomp(principal, component);
    177 }
    178 
    179 /**
    180  * Get number of component is principal.
    181  *
    182  * @param context Kerberos 5 context
    183  * @param principal principal to query
    184  *
    185  * @return number of components in string
    186  *
    187  * @ingroup krb5_principal
    188  */
    189 
    190 KRB5_LIB_FUNCTION unsigned int KRB5_LIB_CALL
    191 krb5_principal_get_num_comp(krb5_context context,
    192 			    krb5_const_principal principal)
    193 {
    194     return princ_num_comp(principal);
    195 }
    196 
    197 /**
    198  * Parse a name into a krb5_principal structure, flags controls the behavior.
    199  *
    200  * @param context Kerberos 5 context
    201  * @param name name to parse into a Kerberos principal
    202  * @param flags flags to control the behavior
    203  * @param principal returned principal, free with krb5_free_principal().
    204  *
    205  * @return An krb5 error code, see krb5_get_error_message().
    206  *
    207  * @ingroup krb5_principal
    208  */
    209 
    210 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    211 krb5_parse_name_flags(krb5_context context,
    212 		      const char *name,
    213 		      int flags,
    214 		      krb5_principal *principal)
    215 {
    216     krb5_error_code ret;
    217     heim_general_string *comp;
    218     heim_general_string realm = NULL;
    219     int ncomp;
    220 
    221     const char *p;
    222     char *q;
    223     char *s;
    224     char *start;
    225 
    226     int n;
    227     char c;
    228     int got_realm = 0;
    229     int first_at = 1;
    230     int no_realm = flags & KRB5_PRINCIPAL_PARSE_NO_REALM;
    231     int require_realm = flags & KRB5_PRINCIPAL_PARSE_REQUIRE_REALM;
    232     int enterprise = flags & KRB5_PRINCIPAL_PARSE_ENTERPRISE;
    233     int ignore_realm = flags & KRB5_PRINCIPAL_PARSE_IGNORE_REALM;
    234     int no_def_realm = flags & KRB5_PRINCIPAL_PARSE_NO_DEF_REALM;
    235 
    236     *principal = NULL;
    237 
    238     if (no_realm && require_realm) {
    239 	krb5_set_error_message(context, KRB5_ERR_NO_SERVICE,
    240 			       N_("Can't require both realm and "
    241 				  "no realm at the same time", ""));
    242 	return KRB5_ERR_NO_SERVICE;
    243     }
    244 
    245     /* count number of component,
    246      * enterprise names only have one component
    247      */
    248     ncomp = 1;
    249     if (!enterprise) {
    250 	for (p = name; *p; p++) {
    251 	    if (*p=='\\') {
    252 		if (!p[1]) {
    253 		    krb5_set_error_message(context, KRB5_PARSE_MALFORMED,
    254 					   N_("trailing \\ in principal name", ""));
    255 		    return KRB5_PARSE_MALFORMED;
    256 		}
    257 		p++;
    258 	    } else if (*p == '/')
    259 		ncomp++;
    260 	    else if (*p == '@')
    261 		break;
    262 	}
    263     }
    264     comp = calloc(ncomp, sizeof(*comp));
    265     if (comp == NULL)
    266 	return krb5_enomem(context);
    267 
    268     n = 0;
    269     p = start = q = s = strdup(name);
    270     if (start == NULL) {
    271 	free(comp);
    272 	return krb5_enomem(context);
    273     }
    274     while (*p) {
    275 	c = *p++;
    276 	if (c == '\\') {
    277 	    c = *p++;
    278 	    if (c == 'n')
    279 		c = '\n';
    280 	    else if (c == 't')
    281 		c = '\t';
    282 	    else if (c == 'b')
    283 		c = '\b';
    284 	    else if (c == '0')
    285 		c = '\0';
    286 	    else if (c == '\0') {
    287 		ret = KRB5_PARSE_MALFORMED;
    288 		krb5_set_error_message(context, ret,
    289 				       N_("trailing \\ in principal name", ""));
    290 		goto exit;
    291 	    }
    292 	} else if (enterprise && first_at) {
    293 	    if (c == '@')
    294 		first_at = 0;
    295 	} else if ((c == '/' && !enterprise) || c == '@') {
    296 	    if (got_realm) {
    297 		ret = KRB5_PARSE_MALFORMED;
    298 		krb5_set_error_message(context, ret,
    299 				       N_("part after realm in principal name", ""));
    300 		goto exit;
    301 	    } else {
    302 		comp[n] = malloc(q - start + 1);
    303 		if (comp[n] == NULL) {
    304 		    ret = krb5_enomem(context);
    305 		    goto exit;
    306 		}
    307 		memcpy(comp[n], start, q - start);
    308 		comp[n][q - start] = 0;
    309 		n++;
    310 	    }
    311 	    if (c == '@')
    312 		got_realm = 1;
    313 	    start = q;
    314 	    continue;
    315 	}
    316 	if (got_realm && (c == '/' || c == '\0')) {
    317 	    ret = KRB5_PARSE_MALFORMED;
    318 	    krb5_set_error_message(context, ret,
    319 				   N_("part after realm in principal name", ""));
    320 	    goto exit;
    321 	}
    322 	*q++ = c;
    323     }
    324     if (got_realm) {
    325 	if (no_realm) {
    326 	    ret = KRB5_PARSE_MALFORMED;
    327 	    krb5_set_error_message(context, ret,
    328 				   N_("realm found in 'short' principal "
    329 				      "expected to be without one", ""));
    330 	    goto exit;
    331 	}
    332 	if (!ignore_realm) {
    333 	    realm = malloc(q - start + 1);
    334 	    if (realm == NULL) {
    335 		ret = krb5_enomem(context);
    336 		goto exit;
    337 	    }
    338 	    memcpy(realm, start, q - start);
    339 	    realm[q - start] = 0;
    340 	}
    341     } else {
    342 	if (require_realm) {
    343 	    ret = KRB5_PARSE_MALFORMED;
    344 	    krb5_set_error_message(context, ret,
    345 				   N_("realm NOT found in principal "
    346 				      "expected to be with one", ""));
    347 	    goto exit;
    348 	} else if (no_realm || no_def_realm) {
    349 	    realm = NULL;
    350 	} else {
    351 	    ret = krb5_get_default_realm(context, &realm);
    352 	    if (ret)
    353 		goto exit;
    354 	}
    355 
    356 	comp[n] = malloc(q - start + 1);
    357 	if (comp[n] == NULL) {
    358 	    ret = krb5_enomem(context);
    359 	    goto exit;
    360 	}
    361 	memcpy(comp[n], start, q - start);
    362 	comp[n][q - start] = 0;
    363 	n++;
    364     }
    365     *principal = calloc(1, sizeof(**principal));
    366     if (*principal == NULL) {
    367 	ret = krb5_enomem(context);
    368 	goto exit;
    369     }
    370     (*principal)->name.name_string.val = comp;
    371     princ_num_comp(*principal) = n;
    372     (*principal)->realm = realm;
    373     if (enterprise)
    374         princ_type(*principal) = KRB5_NT_ENTERPRISE_PRINCIPAL;
    375     else
    376         set_default_princ_type(*principal, KRB5_NT_PRINCIPAL);
    377     free(s);
    378     return 0;
    379 exit:
    380     while (n>0) {
    381 	free(comp[--n]);
    382     }
    383     free(comp);
    384     krb5_free_default_realm(context, realm);
    385     free(s);
    386     return ret;
    387 }
    388 
    389 /**
    390  * Parse a name into a krb5_principal structure
    391  *
    392  * @param context Kerberos 5 context
    393  * @param name name to parse into a Kerberos principal
    394  * @param principal returned principal, free with krb5_free_principal().
    395  *
    396  * @return An krb5 error code, see krb5_get_error_message().
    397  *
    398  * @ingroup krb5_principal
    399  */
    400 
    401 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    402 krb5_parse_name(krb5_context context,
    403 		const char *name,
    404 		krb5_principal *principal)
    405 {
    406     return krb5_parse_name_flags(context, name, 0, principal);
    407 }
    408 
    409 static const char quotable_chars[] = " \n\t\b\\/@";
    410 static const char replace_chars[] = " ntb\\/@";
    411 
    412 #define add_char(BASE, INDEX, LEN, C) do { if((INDEX) < (LEN)) (BASE)[(INDEX)++] = (C); }while(0);
    413 
    414 static size_t
    415 quote_string(const char *s, char *out, size_t idx, size_t len, int display)
    416 {
    417     const char *p, *q;
    418     for(p = s; *p && idx < len; p++){
    419 	q = strchr(quotable_chars, *p);
    420 	if (q && display) {
    421 	    add_char(out, idx, len, replace_chars[q - quotable_chars]);
    422 	} else if (q) {
    423 	    add_char(out, idx, len, '\\');
    424 	    add_char(out, idx, len, replace_chars[q - quotable_chars]);
    425 	}else
    426 	    add_char(out, idx, len, *p);
    427     }
    428     if(idx < len)
    429 	out[idx] = '\0';
    430     return idx;
    431 }
    432 
    433 
    434 static krb5_error_code
    435 unparse_name_fixed(krb5_context context,
    436 		   krb5_const_principal principal,
    437 		   char *name,
    438 		   size_t len,
    439 		   int flags)
    440 {
    441     size_t idx = 0;
    442     size_t i;
    443     int short_form = (flags & KRB5_PRINCIPAL_UNPARSE_SHORT) != 0;
    444     int no_realm = (flags & KRB5_PRINCIPAL_UNPARSE_NO_REALM) != 0;
    445     int display = (flags & KRB5_PRINCIPAL_UNPARSE_DISPLAY) != 0;
    446 
    447     if (!no_realm && princ_realm(principal) == NULL) {
    448 	krb5_set_error_message(context, ERANGE,
    449 			       N_("Realm missing from principal, "
    450 				  "can't unparse", ""));
    451 	return ERANGE;
    452     }
    453 
    454     for(i = 0; i < princ_num_comp(principal); i++){
    455 	if(i)
    456 	    add_char(name, idx, len, '/');
    457 	idx = quote_string(princ_ncomp(principal, i), name, idx, len, display);
    458 	if(idx == len) {
    459 	    krb5_set_error_message(context, ERANGE,
    460 				   N_("Out of space printing principal", ""));
    461 	    return ERANGE;
    462 	}
    463     }
    464     /* add realm if different from default realm */
    465     if(short_form && !no_realm) {
    466 	krb5_realm r;
    467 	krb5_error_code ret;
    468 	ret = krb5_get_default_realm(context, &r);
    469 	if(ret)
    470 	    return ret;
    471 	if(strcmp(princ_realm(principal), r) != 0)
    472 	    short_form = 0;
    473 	krb5_free_default_realm(context, r);
    474     }
    475     if(!short_form && !no_realm) {
    476 	add_char(name, idx, len, '@');
    477 	idx = quote_string(princ_realm(principal), name, idx, len, display);
    478 	if(idx == len) {
    479 	    krb5_set_error_message(context, ERANGE,
    480 				   N_("Out of space printing "
    481 				      "realm of principal", ""));
    482 	    return ERANGE;
    483 	}
    484     }
    485     return 0;
    486 }
    487 
    488 /**
    489  * Unparse the principal name to a fixed buffer
    490  *
    491  * @param context A Kerberos context.
    492  * @param principal principal to unparse
    493  * @param name buffer to write name to
    494  * @param len length of buffer
    495  *
    496  * @return An krb5 error code, see krb5_get_error_message().
    497  *
    498  * @ingroup krb5_principal
    499  */
    500 
    501 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    502 krb5_unparse_name_fixed(krb5_context context,
    503 			krb5_const_principal principal,
    504 			char *name,
    505 			size_t len)
    506 {
    507     return unparse_name_fixed(context, principal, name, len, 0);
    508 }
    509 
    510 /**
    511  * Unparse the principal name to a fixed buffer. The realm is skipped
    512  * if its a default realm.
    513  *
    514  * @param context A Kerberos context.
    515  * @param principal principal to unparse
    516  * @param name buffer to write name to
    517  * @param len length of buffer
    518  *
    519  * @return An krb5 error code, see krb5_get_error_message().
    520  *
    521  * @ingroup krb5_principal
    522  */
    523 
    524 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    525 krb5_unparse_name_fixed_short(krb5_context context,
    526 			      krb5_const_principal principal,
    527 			      char *name,
    528 			      size_t len)
    529 {
    530     return unparse_name_fixed(context, principal, name, len,
    531 			      KRB5_PRINCIPAL_UNPARSE_SHORT);
    532 }
    533 
    534 /**
    535  * Unparse the principal name with unparse flags to a fixed buffer.
    536  *
    537  * @param context A Kerberos context.
    538  * @param principal principal to unparse
    539  * @param flags unparse flags
    540  * @param name buffer to write name to
    541  * @param len length of buffer
    542  *
    543  * @return An krb5 error code, see krb5_get_error_message().
    544  *
    545  * @ingroup krb5_principal
    546  */
    547 
    548 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    549 krb5_unparse_name_fixed_flags(krb5_context context,
    550 			      krb5_const_principal principal,
    551 			      int flags,
    552 			      char *name,
    553 			      size_t len)
    554 {
    555     return unparse_name_fixed(context, principal, name, len, flags);
    556 }
    557 
    558 static krb5_error_code
    559 unparse_name(krb5_context context,
    560 	     krb5_const_principal principal,
    561 	     char **name,
    562 	     int flags)
    563 {
    564     size_t len = 0, plen;
    565     size_t i;
    566     krb5_error_code ret;
    567     /* count length */
    568     if (princ_realm(principal)) {
    569 	plen = strlen(princ_realm(principal));
    570 
    571 	if(strcspn(princ_realm(principal), quotable_chars) == plen)
    572 	    len += plen;
    573 	else
    574 	    len += 2*plen;
    575 	len++; /* '@' */
    576     }
    577     for(i = 0; i < princ_num_comp(principal); i++){
    578 	plen = strlen(princ_ncomp(principal, i));
    579 	if(strcspn(princ_ncomp(principal, i), quotable_chars) == plen)
    580 	    len += plen;
    581 	else
    582 	    len += 2*plen;
    583 	len++;
    584     }
    585     len++; /* '\0' */
    586     *name = malloc(len);
    587     if(*name == NULL)
    588 	return krb5_enomem(context);
    589     ret = unparse_name_fixed(context, principal, *name, len, flags);
    590     if(ret) {
    591 	free(*name);
    592 	*name = NULL;
    593     }
    594     return ret;
    595 }
    596 
    597 /**
    598  * Unparse the Kerberos name into a string
    599  *
    600  * @param context Kerberos 5 context
    601  * @param principal principal to query
    602  * @param name resulting string, free with krb5_xfree()
    603  *
    604  * @return An krb5 error code, see krb5_get_error_message().
    605  *
    606  * @ingroup krb5_principal
    607  */
    608 
    609 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    610 krb5_unparse_name(krb5_context context,
    611 		  krb5_const_principal principal,
    612 		  char **name)
    613 {
    614     return unparse_name(context, principal, name, 0);
    615 }
    616 
    617 /**
    618  * Unparse the Kerberos name into a string
    619  *
    620  * @param context Kerberos 5 context
    621  * @param principal principal to query
    622  * @param flags flag to determine the behavior
    623  * @param name resulting string, free with krb5_xfree()
    624  *
    625  * @return An krb5 error code, see krb5_get_error_message().
    626  *
    627  * @ingroup krb5_principal
    628  */
    629 
    630 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    631 krb5_unparse_name_flags(krb5_context context,
    632 			krb5_const_principal principal,
    633 			int flags,
    634 			char **name)
    635 {
    636     return unparse_name(context, principal, name, flags);
    637 }
    638 
    639 /**
    640  * Unparse the principal name to a allocated buffer. The realm is
    641  * skipped if its a default realm.
    642  *
    643  * @param context A Kerberos context.
    644  * @param principal principal to unparse
    645  * @param name returned buffer, free with krb5_xfree()
    646  *
    647  * @return An krb5 error code, see krb5_get_error_message().
    648  *
    649  * @ingroup krb5_principal
    650  */
    651 
    652 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    653 krb5_unparse_name_short(krb5_context context,
    654 			krb5_const_principal principal,
    655 			char **name)
    656 {
    657     return unparse_name(context, principal, name, KRB5_PRINCIPAL_UNPARSE_SHORT);
    658 }
    659 
    660 /**
    661  * Set a new realm for a principal, and as a side-effect free the
    662  * previous realm.
    663  *
    664  * @param context A Kerberos context.
    665  * @param principal principal set the realm for
    666  * @param realm the new realm to set
    667  *
    668  * @return An krb5 error code, see krb5_get_error_message().
    669  *
    670  * @ingroup krb5_principal
    671  */
    672 
    673 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    674 krb5_principal_set_realm(krb5_context context,
    675 			 krb5_principal principal,
    676 			 krb5_const_realm realm)
    677 {
    678     if (princ_realm(principal))
    679 	free(princ_realm(principal));
    680 
    681     if (realm == NULL)
    682 	princ_realm(principal) = NULL;
    683     else if ((princ_realm(principal) = strdup(realm)) == NULL)
    684 	return krb5_enomem(context);
    685     return 0;
    686 }
    687 
    688 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    689 krb5_principal_set_comp_string(krb5_context context,
    690 			       krb5_principal principal,
    691 			       unsigned int k,
    692                                const char *component)
    693 {
    694     char *s;
    695     size_t i;
    696 
    697     for (i = princ_num_comp(principal); i <= k; i++)
    698         append_component(context, principal, "", 0);
    699     s = strdup(component);
    700     if (s == NULL)
    701         return krb5_enomem(context);
    702     free(princ_ncomp(principal, k));
    703     princ_ncomp(principal, k) = s;
    704     return 0;
    705 }
    706 
    707 #ifndef HEIMDAL_SMALLER
    708 /**
    709  * Build a principal using vararg style building
    710  *
    711  * @param context A Kerberos context.
    712  * @param principal returned principal
    713  * @param rlen length of realm
    714  * @param realm realm name
    715  * @param ... a list of components ended with NULL.
    716  *
    717  * @return An krb5 error code, see krb5_get_error_message().
    718  *
    719  * @ingroup krb5_principal
    720  */
    721 
    722 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    723 krb5_build_principal(krb5_context context,
    724 		     krb5_principal *principal,
    725 		     int rlen,
    726 		     krb5_const_realm realm,
    727 		     ...)
    728 {
    729     krb5_error_code ret;
    730     va_list ap;
    731     va_start(ap, realm);
    732     ret = krb5_build_principal_va(context, principal, rlen, realm, ap);
    733     va_end(ap);
    734     return ret;
    735 }
    736 #endif
    737 
    738 /**
    739  * Build a principal using vararg style building
    740  *
    741  * @param context A Kerberos context.
    742  * @param principal returned principal
    743  * @param realm realm name
    744  * @param ... a list of components ended with NULL.
    745  *
    746  * @return An krb5 error code, see krb5_get_error_message().
    747  *
    748  * @ingroup krb5_principal
    749  */
    750 
    751 /* coverity[+alloc : arg-*1] */
    752 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    753 krb5_make_principal(krb5_context context,
    754 		    krb5_principal *principal,
    755 		    krb5_const_realm realm,
    756 		    ...)
    757 {
    758     krb5_error_code ret;
    759     krb5_realm r = NULL;
    760     va_list ap;
    761     if(realm == NULL) {
    762 	ret = krb5_get_default_realm(context, &r);
    763 	if(ret)
    764 	    return ret;
    765 	realm = r;
    766     }
    767     va_start(ap, realm);
    768     ret = krb5_build_principal_va(context, principal, strlen(realm), realm, ap);
    769     va_end(ap);
    770     if(r)
    771 	krb5_free_default_realm(context, r);
    772     return ret;
    773 }
    774 
    775 static krb5_error_code
    776 append_component(krb5_context context, krb5_principal p,
    777 		 const char *comp,
    778 		 size_t comp_len)
    779 {
    780     heim_general_string *tmp;
    781     size_t len = princ_num_comp(p);
    782 
    783     tmp = realloc(princ_comp(p), (len + 1) * sizeof(*tmp));
    784     if(tmp == NULL)
    785 	return krb5_enomem(context);
    786     princ_comp(p) = tmp;
    787     princ_ncomp(p, len) = malloc(comp_len + 1);
    788     if (princ_ncomp(p, len) == NULL)
    789 	return krb5_enomem(context);
    790     memcpy (princ_ncomp(p, len), comp, comp_len);
    791     princ_ncomp(p, len)[comp_len] = '\0';
    792     princ_num_comp(p)++;
    793     return 0;
    794 }
    795 
    796 static krb5_error_code
    797 va_ext_princ(krb5_context context, krb5_principal p, va_list ap)
    798 {
    799     krb5_error_code ret = 0;
    800 
    801     while (1){
    802 	const char *s;
    803 	int len;
    804 
    805 	if ((len = va_arg(ap, int)) == 0)
    806 	    break;
    807 	s = va_arg(ap, const char*);
    808 	if ((ret = append_component(context, p, s, len)) != 0)
    809 	    break;
    810     }
    811     return ret;
    812 }
    813 
    814 static krb5_error_code
    815 va_princ(krb5_context context, krb5_principal p, va_list ap)
    816 {
    817     krb5_error_code ret = 0;
    818 
    819     while (1){
    820 	const char *s;
    821 
    822 	if ((s = va_arg(ap, const char*)) == NULL)
    823 	    break;
    824 	if ((ret = append_component(context, p, s, strlen(s))) != 0)
    825 	    break;
    826     }
    827     return ret;
    828 }
    829 
    830 static krb5_error_code
    831 build_principal(krb5_context context,
    832 		krb5_principal *principal,
    833 		int rlen,
    834 		krb5_const_realm realm,
    835 		krb5_error_code (*func)(krb5_context, krb5_principal, va_list),
    836 		va_list ap)
    837 {
    838     krb5_error_code ret;
    839     krb5_principal p;
    840 
    841     *principal = NULL;
    842     p = calloc(1, sizeof(*p));
    843     if (p == NULL)
    844 	return krb5_enomem(context);
    845 
    846     princ_realm(p) = strdup(realm);
    847     if (p->realm == NULL) {
    848 	free(p);
    849 	return krb5_enomem(context);
    850     }
    851 
    852     ret = func(context, p, ap);
    853     if (ret == 0) {
    854 	*principal = p;
    855         set_default_princ_type(p, KRB5_NT_PRINCIPAL);
    856     } else
    857 	krb5_free_principal(context, p);
    858     return ret;
    859 }
    860 
    861 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    862 krb5_build_principal_va(krb5_context context,
    863 			krb5_principal *principal,
    864 			int rlen,
    865 			krb5_const_realm realm,
    866 			va_list ap)
    867 {
    868     return build_principal(context, principal, rlen, realm, va_princ, ap);
    869 }
    870 
    871 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    872 krb5_build_principal_va_ext(krb5_context context,
    873 			    krb5_principal *principal,
    874 			    int rlen,
    875 			    krb5_const_realm realm,
    876 			    va_list ap)
    877 {
    878     return build_principal(context, principal, rlen, realm, va_ext_princ, ap);
    879 }
    880 
    881 
    882 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    883 krb5_build_principal_ext(krb5_context context,
    884 			 krb5_principal *principal,
    885 			 int rlen,
    886 			 krb5_const_realm realm,
    887 			 ...)
    888 {
    889     krb5_error_code ret;
    890     va_list ap;
    891     va_start(ap, realm);
    892     ret = krb5_build_principal_va_ext(context, principal, rlen, realm, ap);
    893     va_end(ap);
    894     return ret;
    895 }
    896 
    897 /**
    898  * Copy a principal
    899  *
    900  * @param context A Kerberos context.
    901  * @param inprinc principal to copy
    902  * @param outprinc copied principal, free with krb5_free_principal()
    903  *
    904  * @return An krb5 error code, see krb5_get_error_message().
    905  *
    906  * @ingroup krb5_principal
    907  */
    908 
    909 
    910 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    911 krb5_copy_principal(krb5_context context,
    912 		    krb5_const_principal inprinc,
    913 		    krb5_principal *outprinc)
    914 {
    915     krb5_principal p = malloc(sizeof(*p));
    916     if (p == NULL)
    917 	return krb5_enomem(context);
    918     if(copy_Principal(inprinc, p)) {
    919 	free(p);
    920 	return krb5_enomem(context);
    921     }
    922     *outprinc = p;
    923     return 0;
    924 }
    925 
    926 /**
    927  * Return TRUE iff princ1 == princ2 (without considering the realm)
    928  *
    929  * @param context Kerberos 5 context
    930  * @param princ1 first principal to compare
    931  * @param princ2 second principal to compare
    932  *
    933  * @return non zero if equal, 0 if not
    934  *
    935  * @ingroup krb5_principal
    936  * @see krb5_principal_compare()
    937  * @see krb5_realm_compare()
    938  */
    939 
    940 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
    941 krb5_principal_compare_any_realm(krb5_context context,
    942 				 krb5_const_principal princ1,
    943 				 krb5_const_principal princ2)
    944 {
    945     size_t i;
    946     if(princ_num_comp(princ1) != princ_num_comp(princ2))
    947 	return FALSE;
    948     for(i = 0; i < princ_num_comp(princ1); i++){
    949 	if(strcmp(princ_ncomp(princ1, i), princ_ncomp(princ2, i)) != 0)
    950 	    return FALSE;
    951     }
    952     return TRUE;
    953 }
    954 
    955 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
    956 _krb5_principal_compare_PrincipalName(krb5_context context,
    957 				      krb5_const_principal princ1,
    958 				      PrincipalName *princ2)
    959 {
    960     size_t i;
    961     if (princ_num_comp(princ1) != princ2->name_string.len)
    962 	return FALSE;
    963     for(i = 0; i < princ_num_comp(princ1); i++){
    964 	if(strcmp(princ_ncomp(princ1, i), princ2->name_string.val[i]) != 0)
    965 	    return FALSE;
    966     }
    967     return TRUE;
    968 }
    969 
    970 
    971 /**
    972  * Compares the two principals, including realm of the principals and returns
    973  * TRUE if they are the same and FALSE if not.
    974  *
    975  * @param context Kerberos 5 context
    976  * @param princ1 first principal to compare
    977  * @param princ2 second principal to compare
    978  *
    979  * @ingroup krb5_principal
    980  * @see krb5_principal_compare_any_realm()
    981  * @see krb5_realm_compare()
    982  */
    983 
    984 /*
    985  * return TRUE iff princ1 == princ2
    986  */
    987 
    988 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
    989 krb5_principal_compare(krb5_context context,
    990 		       krb5_const_principal princ1,
    991 		       krb5_const_principal princ2)
    992 {
    993     if (!krb5_realm_compare(context, princ1, princ2))
    994 	return FALSE;
    995     return krb5_principal_compare_any_realm(context, princ1, princ2);
    996 }
    997 
    998 /**
    999  * return TRUE iff realm(princ1) == realm(princ2)
   1000  *
   1001  * @param context Kerberos 5 context
   1002  * @param princ1 first principal to compare
   1003  * @param princ2 second principal to compare
   1004  *
   1005  * @ingroup krb5_principal
   1006  * @see krb5_principal_compare_any_realm()
   1007  * @see krb5_principal_compare()
   1008  */
   1009 
   1010 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
   1011 krb5_realm_compare(krb5_context context,
   1012 		   krb5_const_principal princ1,
   1013 		   krb5_const_principal princ2)
   1014 {
   1015     return strcmp(princ_realm(princ1), princ_realm(princ2)) == 0;
   1016 }
   1017 
   1018 /**
   1019  * return TRUE iff princ matches pattern
   1020  *
   1021  * @ingroup krb5_principal
   1022  */
   1023 
   1024 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
   1025 krb5_principal_match(krb5_context context,
   1026 		     krb5_const_principal princ,
   1027 		     krb5_const_principal pattern)
   1028 {
   1029     size_t i;
   1030     if(princ_num_comp(princ) != princ_num_comp(pattern))
   1031 	return FALSE;
   1032     if(fnmatch(princ_realm(pattern), princ_realm(princ), 0) != 0)
   1033 	return FALSE;
   1034     for(i = 0; i < princ_num_comp(princ); i++){
   1035 	if(fnmatch(princ_ncomp(pattern, i), princ_ncomp(princ, i), 0) != 0)
   1036 	    return FALSE;
   1037     }
   1038     return TRUE;
   1039 }
   1040 
   1041 /*
   1042  * This is the original krb5_sname_to_principal(), renamed to be a
   1043  * helper of the new one.
   1044  */
   1045 static krb5_error_code
   1046 krb5_sname_to_principal_old(krb5_context context,
   1047 			    const char *realm,
   1048 			    const char *hostname,
   1049 			    const char *sname,
   1050 			    int32_t type,
   1051 			    krb5_principal *ret_princ)
   1052 {
   1053     krb5_error_code ret;
   1054     char localhost[MAXHOSTNAMELEN];
   1055     char **realms = NULL, *host = NULL;
   1056 
   1057     if(type != KRB5_NT_SRV_HST && type != KRB5_NT_UNKNOWN) {
   1058 	krb5_set_error_message(context, KRB5_SNAME_UNSUPP_NAMETYPE,
   1059 			       N_("unsupported name type %d", ""),
   1060 			       (int)type);
   1061 	return KRB5_SNAME_UNSUPP_NAMETYPE;
   1062     }
   1063     if(hostname == NULL) {
   1064 	ret = gethostname(localhost, sizeof(localhost) - 1);
   1065 	if (ret != 0) {
   1066 	    ret = errno;
   1067 	    krb5_set_error_message(context, ret,
   1068 				   N_("Failed to get local hostname", ""));
   1069 	    return ret;
   1070 	}
   1071 	localhost[sizeof(localhost) - 1] = '\0';
   1072 	hostname = localhost;
   1073     }
   1074     if(sname == NULL)
   1075 	sname = "host";
   1076     if(type == KRB5_NT_SRV_HST) {
   1077 	if (realm)
   1078 	    ret = krb5_expand_hostname(context, hostname, &host);
   1079 	else
   1080 	    ret = krb5_expand_hostname_realms(context, hostname,
   1081 					      &host, &realms);
   1082 	if (ret)
   1083 	    return ret;
   1084 	strlwr(host);
   1085 	hostname = host;
   1086 	if (!realm)
   1087 	    realm = realms[0];
   1088     } else if (!realm) {
   1089 	ret = krb5_get_host_realm(context, hostname, &realms);
   1090 	if(ret)
   1091 	    return ret;
   1092 	realm = realms[0];
   1093     }
   1094 
   1095     ret = krb5_make_principal(context, ret_princ, realm, sname,
   1096 			      hostname, NULL);
   1097     if(host)
   1098 	free(host);
   1099     if (realms)
   1100 	krb5_free_host_realm(context, realms);
   1101     return ret;
   1102 }
   1103 
   1104 static const struct {
   1105     const char *type;
   1106     int32_t value;
   1107 } nametypes[] = {
   1108     { "UNKNOWN", KRB5_NT_UNKNOWN },
   1109     { "PRINCIPAL", KRB5_NT_PRINCIPAL },
   1110     { "SRV_INST", KRB5_NT_SRV_INST },
   1111     { "SRV_HST", KRB5_NT_SRV_HST },
   1112     { "SRV_XHST", KRB5_NT_SRV_XHST },
   1113     { "UID", KRB5_NT_UID },
   1114     { "X500_PRINCIPAL", KRB5_NT_X500_PRINCIPAL },
   1115     { "SMTP_NAME", KRB5_NT_SMTP_NAME },
   1116     { "ENTERPRISE_PRINCIPAL", KRB5_NT_ENTERPRISE_PRINCIPAL },
   1117     { "WELLKNOWN", KRB5_NT_WELLKNOWN },
   1118     { "SRV_HST_DOMAIN", KRB5_NT_SRV_HST_DOMAIN },
   1119     { "ENT_PRINCIPAL_AND_ID", KRB5_NT_ENT_PRINCIPAL_AND_ID },
   1120     { "MS_PRINCIPAL", KRB5_NT_MS_PRINCIPAL },
   1121     { "MS_PRINCIPAL_AND_ID", KRB5_NT_MS_PRINCIPAL_AND_ID },
   1122     { "SRV_HST_NEEDS_CANON", KRB5_NT_SRV_HST_NEEDS_CANON },
   1123     { NULL, 0 }
   1124 };
   1125 
   1126 /**
   1127  * Parse nametype string and return a nametype integer
   1128  *
   1129  * @ingroup krb5_principal
   1130  */
   1131 
   1132 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
   1133 krb5_parse_nametype(krb5_context context, const char *str, int32_t *nametype)
   1134 {
   1135     size_t i;
   1136 
   1137     for(i = 0; nametypes[i].type; i++) {
   1138 	if (strcasecmp(nametypes[i].type, str) == 0) {
   1139 	    *nametype = nametypes[i].value;
   1140 	    return 0;
   1141 	}
   1142     }
   1143     krb5_set_error_message(context, KRB5_PARSE_MALFORMED,
   1144 			   N_("Failed to find name type %s", ""), str);
   1145     return KRB5_PARSE_MALFORMED;
   1146 }
   1147 
   1148 /**
   1149  * Returns true if name is Kerberos NULL name
   1150  *
   1151  * @ingroup krb5_principal
   1152  */
   1153 
   1154 krb5_boolean KRB5_LIB_FUNCTION
   1155 krb5_principal_is_null(krb5_context context, krb5_const_principal principal)
   1156 {
   1157     if (principal->name.name_type == KRB5_NT_WELLKNOWN &&
   1158 	principal->name.name_string.len == 2 &&
   1159 	strcmp(principal->name.name_string.val[0], "WELLKNOWN") == 0 &&
   1160 	strcmp(principal->name.name_string.val[1], "NULL") == 0)
   1161 	return TRUE;
   1162     return FALSE;
   1163 }
   1164 
   1165 const char _krb5_wellknown_lkdc[] = "WELLKNOWN:COM.APPLE.LKDC";
   1166 static const char lkdc_prefix[] = "LKDC:";
   1167 
   1168 /**
   1169  * Returns true if name is Kerberos an LKDC realm
   1170  *
   1171  * @ingroup krb5_principal
   1172  */
   1173 
   1174 krb5_boolean KRB5_LIB_FUNCTION
   1175 krb5_realm_is_lkdc(const char *realm)
   1176 {
   1177 
   1178     return strncmp(realm, lkdc_prefix, sizeof(lkdc_prefix)-1) == 0 ||
   1179 	strncmp(realm, _krb5_wellknown_lkdc, sizeof(_krb5_wellknown_lkdc) - 1) == 0;
   1180 }
   1181 
   1182 /**
   1183  * Returns true if name is Kerberos an LKDC realm
   1184  *
   1185  * @ingroup krb5_principal
   1186  */
   1187 
   1188 krb5_boolean KRB5_LIB_FUNCTION
   1189 krb5_principal_is_lkdc(krb5_context context, krb5_const_principal principal)
   1190 {
   1191     return krb5_realm_is_lkdc(principal->realm);
   1192 }
   1193 
   1194 /**
   1195  * Returns true if name is Kerberos an LKDC realm
   1196  *
   1197  * @ingroup krb5_principal
   1198  */
   1199 
   1200 krb5_boolean KRB5_LIB_FUNCTION
   1201 krb5_principal_is_pku2u(krb5_context context, krb5_const_principal principal)
   1202 {
   1203     return strcmp(principal->realm, KRB5_PKU2U_REALM_NAME) == 0;
   1204 }
   1205 
   1206 /**
   1207  * Check if the cname part of the principal is a krbtgt principal
   1208  *
   1209  * @ingroup krb5_principal
   1210  */
   1211 
   1212 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
   1213 krb5_principal_is_krbtgt(krb5_context context, krb5_const_principal p)
   1214 {
   1215     return p->name.name_string.len == 2 &&
   1216 	strcmp(p->name.name_string.val[0], KRB5_TGS_NAME) == 0;
   1217 }
   1218 
   1219 /**
   1220  * Returns true iff name is an WELLKNOWN:ORG.H5L.HOSTBASED-SERVICE
   1221  *
   1222  * @ingroup krb5_principal
   1223  */
   1224 
   1225 krb5_boolean KRB5_LIB_FUNCTION
   1226 krb5_principal_is_gss_hostbased_service(krb5_context context,
   1227 					krb5_const_principal principal)
   1228 {
   1229     if (principal == NULL)
   1230 	return FALSE;
   1231     if (principal->name.name_string.len != 2)
   1232 	return FALSE;
   1233     if (strcmp(principal->name.name_string.val[1], KRB5_GSS_HOSTBASED_SERVICE_NAME) != 0)
   1234 	return FALSE;
   1235     return TRUE;
   1236 }
   1237 
   1238 /**
   1239  * Check if the cname part of the principal is a initial or renewed krbtgt principal
   1240  *
   1241  * @ingroup krb5_principal
   1242  */
   1243 
   1244 krb5_boolean KRB5_LIB_FUNCTION
   1245 krb5_principal_is_root_krbtgt(krb5_context context, krb5_const_principal p)
   1246 {
   1247     return p->name.name_string.len == 2 &&
   1248 	strcmp(p->name.name_string.val[0], KRB5_TGS_NAME) == 0 &&
   1249 	strcmp(p->name.name_string.val[1], p->realm) == 0;
   1250 }
   1251 
   1252 /**
   1253  * Returns true iff name is WELLKNOWN/ANONYMOUS
   1254  *
   1255  * @ingroup krb5_principal
   1256  */
   1257 
   1258 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
   1259 krb5_principal_is_anonymous(krb5_context context,
   1260 			     krb5_const_principal p,
   1261 			     unsigned int flags)
   1262 {
   1263     /*
   1264      * Heimdal versions 7.5 and below left the name-type at KRB5_NT_PRINCIPAL
   1265      * even with anonymous pkinit responses.  To retain interoperability with
   1266      * legacy KDCs, the name-type is not checked by the client after requesting
   1267      * a fully anonymous ticket.
   1268      */
   1269     if (!(flags & KRB5_ANON_IGNORE_NAME_TYPE) &&
   1270         p->name.name_type != KRB5_NT_WELLKNOWN &&
   1271         p->name.name_type != KRB5_NT_UNKNOWN)
   1272         return FALSE;
   1273 
   1274     if (p->name.name_string.len != 2 ||
   1275         strcmp(p->name.name_string.val[0], KRB5_WELLKNOWN_NAME) != 0 ||
   1276         strcmp(p->name.name_string.val[1], KRB5_ANON_NAME) != 0)
   1277         return FALSE;
   1278 
   1279     /*
   1280      * While unauthenticated clients SHOULD get "WELLKNOWN:ANONYMOUS" as their
   1281      * realm, Heimdal KDCs prior to 7.0 returned the requested realm.  While
   1282      * such tickets might lead *servers* to unwittingly grant access to fully
   1283      * anonymous clients, trusting that the client was authenticated to the
   1284      * realm in question, doing it right is the KDC's job, the client should
   1285      * not refuse such a ticket.
   1286      *
   1287      * If we ever do decide to enforce WELLKNOWN:ANONYMOUS for unauthenticated
   1288      * clients, it is essential that calls that pass KRB5_ANON_MATCH_ANY still
   1289      * ignore the realm, as in that case either case matches one of the two
   1290      * possible conditions.
   1291      */
   1292     if (flags & KRB5_ANON_MATCH_UNAUTHENTICATED)
   1293         return TRUE;
   1294 
   1295     /*
   1296      * Finally, authenticated clients that asked to be only anonymized do
   1297      * legitimately expect a non-anon realm.
   1298      */
   1299     return strcmp(p->realm, KRB5_ANON_REALM) != 0;
   1300 }
   1301 
   1302 static int
   1303 tolower_ascii(int c)
   1304 {
   1305     if (c >= 'A' || c <= 'Z')
   1306         return 'a' + (c - 'A');
   1307     return c;
   1308 }
   1309 
   1310 typedef enum krb5_name_canon_rule_type {
   1311 	KRB5_NCRT_BOGUS = 0,
   1312 	KRB5_NCRT_AS_IS,
   1313 	KRB5_NCRT_QUALIFY,
   1314 	KRB5_NCRT_NSS
   1315 } krb5_name_canon_rule_type;
   1316 
   1317 #ifdef UINT8_MAX
   1318 #define MAXDOTS UINT8_MAX
   1319 #else
   1320 #define MAXDOTS (255U)
   1321 #endif
   1322 #ifdef UINT16_MAX
   1323 #define MAXORDER UINT16_MAX
   1324 #else
   1325 #define MAXORDER (65535U)
   1326 #endif
   1327 
   1328 struct krb5_name_canon_rule_data {
   1329 	krb5_name_canon_rule_type type;
   1330 	krb5_name_canon_rule_options options;
   1331 	uint8_t mindots;          /* match this many dots or more */
   1332 	uint8_t maxdots;          /* match no more than this many dots */
   1333         uint16_t explicit_order;    /* given order */
   1334         uint16_t order;             /* actual order */
   1335 	char *match_domain;         /* match this stem */
   1336 	char *match_realm;          /* match this realm */
   1337 	char *domain;               /* qualify with this domain */
   1338 	char *realm;                /* qualify with this realm */
   1339 };
   1340 
   1341 /**
   1342  * Create a principal for the given service running on the given
   1343  * hostname. If KRB5_NT_SRV_HST is used, the hostname is canonicalized
   1344  * according the configured name canonicalization rules, with
   1345  * canonicalization delayed in some cases.  One rule involves DNS, which
   1346  * is insecure unless DNSSEC is used, but we don't use DNSSEC-capable
   1347  * resolver APIs here, so that if DNSSEC is used we wouldn't know it.
   1348  *
   1349  * Canonicalization is immediate (not delayed) only when there is only
   1350  * one canonicalization rule and that rule indicates that we should do a
   1351  * host lookup by name (i.e., DNS).
   1352  *
   1353  * @param context A Kerberos context.
   1354  * @param hostname hostname to use
   1355  * @param sname Service name to use
   1356  * @param type name type of principal, use KRB5_NT_SRV_HST or KRB5_NT_UNKNOWN.
   1357  * @param ret_princ return principal, free with krb5_free_principal().
   1358  *
   1359  * @return An krb5 error code, see krb5_get_error_message().
   1360  *
   1361  * @ingroup krb5_principal
   1362  */
   1363 
   1364 /* coverity[+alloc : arg-*4] */
   1365 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
   1366 krb5_sname_to_principal(krb5_context context,
   1367 			const char *hostname,
   1368 			const char *sname,
   1369 			int32_t type,
   1370 			krb5_principal *ret_princ)
   1371 {
   1372     char *realm, *remote_host;
   1373     krb5_error_code ret;
   1374     register char *cp;
   1375     char localname[MAXHOSTNAMELEN];
   1376 
   1377     *ret_princ = NULL;
   1378 
   1379     if ((type != KRB5_NT_UNKNOWN) &&
   1380 	(type != KRB5_NT_SRV_HST))
   1381 	return KRB5_SNAME_UNSUPP_NAMETYPE;
   1382 
   1383     /* if hostname is NULL, use local hostname */
   1384     if (hostname == NULL) {
   1385 	if (gethostname(localname, MAXHOSTNAMELEN))
   1386 	    return errno;
   1387 	hostname = localname;
   1388     }
   1389 
   1390     /* if sname is NULL, use "host" */
   1391     if (sname == NULL)
   1392 	sname = "host";
   1393 
   1394     remote_host = strdup(hostname);
   1395     if (remote_host == NULL)
   1396 	return krb5_enomem(context);
   1397 
   1398     if (type == KRB5_NT_SRV_HST) {
   1399 	krb5_name_canon_rule rules;
   1400 
   1401 	/* Lower-case the hostname, because that's the convention */
   1402 	for (cp = remote_host; *cp; cp++)
   1403 	    if (isupper((int) (*cp)))
   1404 		*cp = tolower((int) (*cp));
   1405 
   1406         /*
   1407          * If there is only one name canon rule and it says to
   1408          * canonicalize the old way, do that now, as we used to.
   1409          */
   1410 	ret = _krb5_get_name_canon_rules(context, &rules);
   1411 	if (ret) {
   1412 	    _krb5_debug(context, 5, "Failed to get name canon rules: ret = %d",
   1413 			ret);
   1414 	    free(remote_host);
   1415 	    return ret;
   1416 	}
   1417 	if (rules[0].type == KRB5_NCRT_NSS &&
   1418             rules[1].type == KRB5_NCRT_BOGUS) {
   1419 	    _krb5_debug(context, 5, "Using nss for name canon immediately");
   1420             ret = krb5_sname_to_principal_old(context, rules[0].realm,
   1421                                               remote_host, sname,
   1422                                               KRB5_NT_SRV_HST, ret_princ);
   1423 	    free(remote_host);
   1424 	    return ret;
   1425 	}
   1426     }
   1427 
   1428     /* Remove trailing dots */
   1429     if (remote_host[0]) {
   1430 	for (cp = remote_host + strlen(remote_host)-1;
   1431              *cp == '.' && cp > remote_host;
   1432              cp--) {
   1433             *cp = '\0';
   1434         }
   1435     }
   1436 
   1437     realm = ""; /* "Referral realm" */
   1438 
   1439     ret = krb5_build_principal(context, ret_princ, strlen(realm),
   1440 				  realm, sname, remote_host,
   1441 				  (char *)0);
   1442 
   1443     if (ret == 0 && type == KRB5_NT_SRV_HST) {
   1444 	/*
   1445 	 * Hostname canonicalization is done elsewhere (in
   1446 	 * krb5_get_credentials() and krb5_kt_get_entry()).
   1447 	 *
   1448          * We overload the name type to indicate to those functions that
   1449          * this principal name requires canonicalization.
   1450          *
   1451          * We can't use the empty realm to denote the need to
   1452          * canonicalize the hostname too: it would mean that users who
   1453          * want to assert knowledge of a service's realm must also know
   1454          * the canonical hostname, but in practice they don't.
   1455 	 */
   1456 	(*ret_princ)->name.name_type = KRB5_NT_SRV_HST_NEEDS_CANON;
   1457 
   1458 	_krb5_debug(context, 5, "Building a delayed canon principal for %s/%s@",
   1459 		sname, remote_host);
   1460     }
   1461 
   1462     free(remote_host);
   1463     return ret;
   1464 }
   1465 
   1466 static void
   1467 tolower_str(char *s)
   1468 {
   1469     for (; *s != '\0'; s++) {
   1470         if (isupper(*s))
   1471             *s = tolower_ascii(*s);
   1472     }
   1473 }
   1474 
   1475 static krb5_error_code
   1476 rule_parse_token(krb5_context context, krb5_name_canon_rule rule,
   1477 		 const char *tok)
   1478 {
   1479     long int n;
   1480     int needs_type = rule->type == KRB5_NCRT_BOGUS;
   1481 
   1482     /*
   1483      * Rules consist of a sequence of tokens, some of which indicate
   1484      * what type of rule the rule is, and some of which set rule options
   1485      * or ancilliary data.  Last rule type token wins.
   1486      */
   1487 
   1488     /* Rule type tokens: */
   1489     if (needs_type && strcmp(tok, "as-is") == 0) {
   1490         rule->type = KRB5_NCRT_AS_IS;
   1491     } else if (needs_type && strcmp(tok, "qualify") == 0) {
   1492         rule->type = KRB5_NCRT_QUALIFY;
   1493     } else if (needs_type && strcmp(tok, "nss") == 0) {
   1494         rule->type = KRB5_NCRT_NSS;
   1495     /* Rule options: */
   1496     } else if (strcmp(tok, "use_fast") == 0) {
   1497 	rule->options |= KRB5_NCRO_USE_FAST;
   1498     } else if (strcmp(tok, "use_dnssec") == 0) {
   1499 	rule->options |= KRB5_NCRO_USE_DNSSEC;
   1500     } else if (strcmp(tok, "ccache_only") == 0) {
   1501 	rule->options |= KRB5_NCRO_GC_ONLY;
   1502     } else if (strcmp(tok, "no_referrals") == 0) {
   1503 	rule->options |= KRB5_NCRO_NO_REFERRALS;
   1504     } else if (strcmp(tok, "use_referrals") == 0) {
   1505 	rule->options &= ~KRB5_NCRO_NO_REFERRALS;
   1506         if (rule->realm == NULL) {
   1507             rule->realm = strdup("");
   1508             if (rule->realm == NULL)
   1509                 return krb5_enomem(context);
   1510         }
   1511     } else if (strcmp(tok, "lookup_realm") == 0) {
   1512         rule->options |= KRB5_NCRO_LOOKUP_REALM;
   1513         free(rule->realm);
   1514         rule->realm = NULL;
   1515     /* Rule ancilliary data: */
   1516     } else if (strncmp(tok, "domain=", strlen("domain=")) == 0) {
   1517 	free(rule->domain);
   1518 	rule->domain = strdup(tok + strlen("domain="));
   1519 	if (rule->domain == NULL)
   1520 	    return krb5_enomem(context);
   1521         tolower_str(rule->domain);
   1522     } else if (strncmp(tok, "realm=", strlen("realm=")) == 0) {
   1523 	free(rule->realm);
   1524 	rule->realm = strdup(tok + strlen("realm="));
   1525 	if (rule->realm == NULL)
   1526 	    return krb5_enomem(context);
   1527     } else if (strncmp(tok, "match_domain=", strlen("match_domain=")) == 0) {
   1528 	free(rule->match_domain);
   1529 	rule->match_domain = strdup(tok + strlen("match_domain="));
   1530 	if (rule->match_domain == NULL)
   1531 	    return krb5_enomem(context);
   1532         tolower_str(rule->match_domain);
   1533     } else if (strncmp(tok, "match_realm=", strlen("match_realm=")) == 0) {
   1534 	free(rule->match_realm);
   1535 	rule->match_realm = strdup(tok + strlen("match_realm="));
   1536 	if (rule->match_realm == NULL)
   1537 	    return krb5_enomem(context);
   1538     } else if (strncmp(tok, "mindots=", strlen("mindots=")) == 0) {
   1539 	errno = 0;
   1540 	n = strtol(tok + strlen("mindots="), NULL, 10);
   1541 	if (errno == 0 && n > 0 && n <= MAXDOTS)
   1542 	    rule->mindots = n;
   1543     } else if (strncmp(tok, "maxdots=", strlen("maxdots=")) == 0) {
   1544 	errno = 0;
   1545 	n = strtol(tok + strlen("maxdots="), NULL, 10);
   1546 	if (errno == 0 && n > 0 && n <= MAXDOTS)
   1547 	    rule->maxdots = n;
   1548     } else if (strncmp(tok, "order=", strlen("order=")) == 0) {
   1549 	errno = 0;
   1550 	n = strtol(tok + strlen("order="), NULL, 10);
   1551 	if (errno == 0 && n > 0 && n <= MAXORDER)
   1552 	    rule->explicit_order = n;
   1553     } else {
   1554         _krb5_debug(context, 5,
   1555                     "Unrecognized name canonicalization rule token %s", tok);
   1556         return EINVAL;
   1557     }
   1558     return 0;
   1559 }
   1560 
   1561 static int
   1562 rule_cmp(const void *a, const void *b)
   1563 {
   1564     krb5_const_name_canon_rule left = a;
   1565     krb5_const_name_canon_rule right = b;
   1566 
   1567     if (left->type == KRB5_NCRT_BOGUS &&
   1568         right->type == KRB5_NCRT_BOGUS)
   1569         return 0;
   1570     if (left->type == KRB5_NCRT_BOGUS)
   1571         return 1;
   1572     if (right->type == KRB5_NCRT_BOGUS)
   1573         return -1;
   1574     if (left->explicit_order < right->explicit_order)
   1575         return -1;
   1576     if (left->explicit_order > right->explicit_order)
   1577         return 1;
   1578     return left->order - right->order;
   1579 }
   1580 
   1581 static krb5_error_code
   1582 parse_name_canon_rules(krb5_context context, char **rulestrs,
   1583 		       krb5_name_canon_rule *rules)
   1584 {
   1585     krb5_error_code ret;
   1586     char *tok;
   1587     char *cp;
   1588     char **cpp;
   1589     size_t n;
   1590     size_t i, k;
   1591     int do_sort = 0;
   1592     krb5_name_canon_rule r;
   1593 
   1594     *rules = NULL;
   1595 
   1596     for (n =0, cpp = rulestrs; cpp != NULL && *cpp != NULL; cpp++)
   1597 	n++;
   1598 
   1599     n += 2; /* Always at least one rule; two for the default case */
   1600 
   1601     if ((r = calloc(n, sizeof (*r))) == NULL)
   1602 	return krb5_enomem(context);
   1603 
   1604     for (k = 0; k < n; k++) {
   1605         r[k].type = KRB5_NCRT_BOGUS;
   1606         r[k].match_domain = NULL;
   1607         r[k].match_realm = NULL;
   1608         r[k].domain = NULL;
   1609         r[k].realm = NULL;
   1610     }
   1611 
   1612     for (i = 0, k = 0; i < n && rulestrs != NULL && rulestrs[i] != NULL; i++) {
   1613 	cp = rulestrs[i];
   1614         r[k].explicit_order = MAXORDER; /* mark order, see below */
   1615         r[k].maxdots = MAXDOTS;
   1616         r[k].order = k;         /* default order */
   1617 
   1618         /* Tokenize and parse value */
   1619 	do {
   1620 	    tok = cp;
   1621 	    cp = strchr(cp, ':');   /* XXX use strtok_r() */
   1622 	    if (cp)
   1623 		*cp++ = '\0';       /* delimit token */
   1624 	    ret = rule_parse_token(context, &r[k], tok);
   1625             if (ret == EINVAL) {
   1626                 r[k].type = KRB5_NCRT_BOGUS;
   1627                 break;
   1628             }
   1629             if (ret) {
   1630                 _krb5_free_name_canon_rules(context, r);
   1631                 return ret;
   1632             }
   1633 	} while (cp && *cp);
   1634         if (r[k].explicit_order != MAXORDER)
   1635             do_sort = 1;
   1636 
   1637 	/* Validate parsed rule */
   1638 	if (r[k].type == KRB5_NCRT_BOGUS ||
   1639 	    (r[k].type == KRB5_NCRT_QUALIFY && !r[k].domain) ||
   1640 	    (r[k].type == KRB5_NCRT_NSS && r[k].domain)) {
   1641 	    /* Invalid rule; mark it so and clean up */
   1642 	    r[k].type = KRB5_NCRT_BOGUS;
   1643 	    free(r[k].match_domain);
   1644 	    free(r[k].match_realm);
   1645 	    free(r[k].domain);
   1646 	    free(r[k].realm);
   1647 	    r[k].realm = NULL;
   1648 	    r[k].domain = NULL;
   1649 	    r[k].match_domain = NULL;
   1650 	    r[k].match_realm = NULL;
   1651             _krb5_debug(context, 5,
   1652                         "Ignoring invalid name canonicalization rule %lu",
   1653                         (unsigned long)i);
   1654 	    continue;
   1655 	}
   1656 	k++; /* good rule */
   1657     }
   1658 
   1659     if (do_sort) {
   1660         /*
   1661          * Note that we make make this a stable sort by using appareance
   1662          * and explicit order.
   1663          */
   1664         qsort(r, n, sizeof(r[0]), rule_cmp);
   1665     }
   1666 
   1667     if (r[0].type == KRB5_NCRT_BOGUS) {
   1668         /* No rules, or no valid rules */
   1669         r[0].type = KRB5_NCRT_NSS;
   1670     }
   1671 
   1672     *rules = r;
   1673     return 0; /* We don't communicate bad rule errors here */
   1674 }
   1675 
   1676 /*
   1677  * This exists only because the hostname canonicalization behavior in Heimdal
   1678  * (and other implementations of Kerberos) has been to use getaddrinfo(),
   1679  * unsafe though it is, for ages.  We can't fix it in one day.
   1680  */
   1681 static void
   1682 make_rules_safe(krb5_context context, krb5_name_canon_rule rules)
   1683 {
   1684     /*
   1685      * If the only rule were to use the name service (getaddrinfo()) then we're
   1686      * bound to fail.  We could try to convert that rule to an as-is rule, but
   1687      * when we do get a validating resolver we'd be unhappy that we did such a
   1688      * conversion.  Better let the user get failures and make them think about
   1689      * their naming rules.
   1690      */
   1691     if (rules == NULL)
   1692         return;
   1693     for (; rules[0].type != KRB5_NCRT_BOGUS; rules++) {
   1694         if (rules->type == KRB5_NCRT_NSS)
   1695             rules->options |= KRB5_NCRO_USE_DNSSEC;
   1696         else
   1697             rules->options |= KRB5_NCRO_USE_FAST;
   1698     }
   1699 }
   1700 
   1701 /**
   1702  * This function returns an array of host-based service name
   1703  * canonicalization rules.  The array of rules is organized as a list.
   1704  * See the definition of krb5_name_canon_rule.
   1705  *
   1706  * @param context A Kerberos context.
   1707  * @param rules   Output location for array of rules.
   1708  */
   1709 KRB5_LIB_FUNCTION krb5_error_code
   1710 _krb5_get_name_canon_rules(krb5_context context, krb5_name_canon_rule *rules)
   1711 {
   1712     krb5_error_code ret;
   1713     char **values = NULL;
   1714 
   1715     *rules = context->name_canon_rules;
   1716     if (*rules != NULL)
   1717         return 0;
   1718 
   1719     values = krb5_config_get_strings(context, NULL,
   1720                                      "libdefaults", "name_canon_rules", NULL);
   1721     ret = parse_name_canon_rules(context, values, rules);
   1722     krb5_config_free_strings(values);
   1723     if (ret)
   1724 	return ret;
   1725 
   1726     if (krb5_config_get_bool_default(context, NULL, FALSE,
   1727                                      "libdefaults", "safe_name_canon", NULL))
   1728         make_rules_safe(context, *rules);
   1729 
   1730     heim_assert(rules != NULL && (*rules)[0].type != KRB5_NCRT_BOGUS,
   1731                 "internal error in parsing principal name "
   1732                 "canonicalization rules");
   1733 
   1734     /* Memoize */
   1735     context->name_canon_rules = *rules;
   1736 
   1737     return 0;
   1738 }
   1739 
   1740 static krb5_error_code
   1741 get_host_realm(krb5_context context, const char *hostname, char **realm)
   1742 {
   1743     krb5_error_code ret;
   1744     char **hrealms = NULL;
   1745 
   1746     *realm = NULL;
   1747     ret = krb5_get_host_realm(context, hostname, &hrealms);
   1748     if (ret)
   1749 	return ret;
   1750     if (hrealms == NULL)
   1751 	return KRB5_ERR_HOST_REALM_UNKNOWN; /* krb5_set_error() already done */
   1752     if (hrealms[0] == NULL) {
   1753 	krb5_free_host_realm(context, hrealms);
   1754 	return KRB5_ERR_HOST_REALM_UNKNOWN; /* krb5_set_error() already done */
   1755     }
   1756     *realm = strdup(hrealms[0]);
   1757     krb5_free_host_realm(context, hrealms);
   1758     if (*realm == NULL)
   1759         return krb5_enomem(context);
   1760     return 0;
   1761 }
   1762 
   1763 static int
   1764 is_domain_suffix(const char *domain, const char *suffix)
   1765 {
   1766     size_t dlen = strlen(domain);
   1767     size_t slen = strlen(suffix);
   1768 
   1769     if (dlen < slen + 2)
   1770         return 0;
   1771 
   1772     if (strcasecmp(domain + (dlen - slen), suffix) != 0)
   1773         return 0;
   1774 
   1775     if (domain[(dlen - slen) - 1] != '.')
   1776         return 0;
   1777     return 1;
   1778 }
   1779 
   1780 /*
   1781  * Applies a name canonicalization rule to a principal.
   1782  *
   1783  * Returns zero and no out_princ if the rule does not match.
   1784  * Returns zero and an out_princ if the rule does match.
   1785  */
   1786 static krb5_error_code
   1787 apply_name_canon_rule(krb5_context context, krb5_name_canon_rule rules,
   1788                       size_t rule_idx, krb5_const_principal in_princ,
   1789                       krb5_principal *out_princ,
   1790                       krb5_name_canon_rule_options *rule_opts)
   1791 {
   1792     krb5_name_canon_rule rule = &rules[rule_idx];
   1793     krb5_error_code ret;
   1794     unsigned int ndots = 0;
   1795     krb5_principal nss = NULL;
   1796     const char *sname = NULL;
   1797     const char *orig_hostname = NULL;
   1798     const char *new_hostname = NULL;
   1799     const char *new_realm = NULL;
   1800     const char *port = "";
   1801     const char *cp;
   1802     char *hostname_sans_port = NULL;
   1803     char *hostname_with_port = NULL;
   1804     char *tmp_hostname = NULL;
   1805     char *tmp_realm = NULL;
   1806 
   1807     *out_princ = NULL; /* Signal no match */
   1808 
   1809     if (rule_opts != NULL)
   1810 	*rule_opts = rule->options;
   1811 
   1812     if (rule->type == KRB5_NCRT_BOGUS)
   1813 	return 0; /* rule doesn't apply */
   1814 
   1815     sname = krb5_principal_get_comp_string(context, in_princ, 0);
   1816     orig_hostname = krb5_principal_get_comp_string(context, in_princ, 1);
   1817 
   1818     /*
   1819      * Some apps want to use the very non-standard svc/hostname:port@REALM
   1820      * form.  We do our best to support that here :(
   1821      */
   1822     port = strchr(orig_hostname, ':');
   1823     if (port != NULL) {
   1824         hostname_sans_port = strndup(orig_hostname, port - orig_hostname);
   1825         if (hostname_sans_port == NULL)
   1826             return krb5_enomem(context);
   1827         orig_hostname = hostname_sans_port;
   1828     }
   1829 
   1830     _krb5_debug(context, 5, N_("Applying a name rule (type %d) to %s", ""),
   1831                 rule->type, orig_hostname);
   1832 
   1833     if (rule->mindots > 0 || rule->maxdots > 0) {
   1834         for (cp = strchr(orig_hostname, '.'); cp && *cp; cp = strchr(cp + 1, '.'))
   1835             ndots++;
   1836     }
   1837     if (rule->mindots > 0 && ndots < rule->mindots)
   1838             return 0;
   1839     if (ndots > rule->maxdots)
   1840             return 0;
   1841 
   1842     if (rule->match_domain != NULL &&
   1843         !is_domain_suffix(orig_hostname, rule->match_domain))
   1844         return 0;
   1845 
   1846     if (rule->match_realm != NULL &&
   1847         strcmp(rule->match_realm, in_princ->realm) != 0)
   1848           return 0;
   1849 
   1850     new_realm = rule->realm;
   1851     switch (rule->type) {
   1852     case KRB5_NCRT_AS_IS:
   1853 	break;
   1854 
   1855     case KRB5_NCRT_QUALIFY:
   1856 	heim_assert(rule->domain != NULL,
   1857 		    "missing domain for qualify name canon rule");
   1858         if (asprintf(&tmp_hostname, "%s.%s", orig_hostname,
   1859                      rule->domain) == -1 || tmp_hostname == NULL) {
   1860             ret = krb5_enomem(context);
   1861             goto out;
   1862         }
   1863         new_hostname = tmp_hostname;
   1864 	break;
   1865 
   1866     case KRB5_NCRT_NSS:
   1867         if ((rule->options & KRB5_NCRO_USE_DNSSEC)) {
   1868             ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
   1869             krb5_set_error_message(context, ret,
   1870                                    "Secure hostname resolution not supported");
   1871             goto out;
   1872         }
   1873 	_krb5_debug(context, 5, "Using name service lookups");
   1874 	ret = krb5_sname_to_principal_old(context, rule->realm,
   1875 					  orig_hostname, sname,
   1876 					  KRB5_NT_SRV_HST,
   1877 					  &nss);
   1878 	if (rules[rule_idx + 1].type != KRB5_NCRT_BOGUS &&
   1879             (ret == KRB5_ERR_BAD_HOSTNAME ||
   1880 	     ret == KRB5_ERR_HOST_REALM_UNKNOWN)) {
   1881 	    /*
   1882 	     * Bad hostname / realm unknown -> rule inapplicable if
   1883 	     * there's more rules.  If it's the last rule then we want
   1884 	     * to return all errors from krb5_sname_to_principal_old()
   1885 	     * here.
   1886 	     */
   1887             ret = 0;
   1888             goto out;
   1889         }
   1890         if (ret)
   1891             goto out;
   1892 
   1893         new_hostname = krb5_principal_get_comp_string(context, nss, 1);
   1894         new_realm = krb5_principal_get_realm(context, nss);
   1895         break;
   1896 
   1897     default:
   1898         /* Can't happen */
   1899         ret = 0;
   1900 	goto out;
   1901     }
   1902 
   1903     /*
   1904      * This rule applies.
   1905      *
   1906      * Copy in_princ and mutate the copy per the matched rule.
   1907      *
   1908      * This way we apply to principals with two or more components, such as
   1909      * domain-based names.
   1910      */
   1911     ret = krb5_copy_principal(context, in_princ, out_princ);
   1912     if (ret)
   1913         goto out;
   1914 
   1915     if (new_realm == NULL && (rule->options & KRB5_NCRO_LOOKUP_REALM) != 0) {
   1916         ret = get_host_realm(context, new_hostname, &tmp_realm);
   1917         if (ret)
   1918             goto out;
   1919         new_realm = tmp_realm;
   1920     }
   1921 
   1922     /* If we stripped off a :port, add it back in */
   1923     if (port != NULL && new_hostname != NULL) {
   1924         if (asprintf(&hostname_with_port, "%s%s", new_hostname, port) == -1 ||
   1925             hostname_with_port == NULL) {
   1926             ret = krb5_enomem(context);
   1927             goto out;
   1928         }
   1929         new_hostname = hostname_with_port;
   1930     }
   1931 
   1932     if (new_realm != NULL)
   1933         krb5_principal_set_realm(context, *out_princ, new_realm);
   1934     if (new_hostname != NULL)
   1935         krb5_principal_set_comp_string(context, *out_princ, 1, new_hostname);
   1936     if (princ_type(*out_princ) == KRB5_NT_SRV_HST_NEEDS_CANON)
   1937         princ_type(*out_princ) = KRB5_NT_SRV_HST;
   1938 
   1939     /* Trace rule application */
   1940     {
   1941 	krb5_error_code ret2;
   1942 	char *unparsed;
   1943 
   1944 	ret2 = krb5_unparse_name(context, *out_princ, &unparsed);
   1945 	if (ret2) {
   1946 	    _krb5_debug(context, 5,
   1947                         N_("Couldn't unparse canonicalized princicpal (%d)",
   1948                            ""),
   1949                         ret);
   1950 	} else {
   1951 	    _krb5_debug(context, 5,
   1952                         N_("Name canon rule application yields %s", ""),
   1953                         unparsed);
   1954 	    free(unparsed);
   1955 	}
   1956     }
   1957 
   1958 out:
   1959     free(hostname_sans_port);
   1960     free(hostname_with_port);
   1961     free(tmp_hostname);
   1962     free(tmp_realm);
   1963     krb5_free_principal(context, nss);
   1964     if (ret)
   1965 	krb5_set_error_message(context, ret,
   1966 			       N_("Name canon rule application failed", ""));
   1967     return ret;
   1968 }
   1969 
   1970 /**
   1971  * Free name canonicalization rules
   1972  */
   1973 KRB5_LIB_FUNCTION void
   1974 _krb5_free_name_canon_rules(krb5_context context, krb5_name_canon_rule rules)
   1975 {
   1976     size_t k;
   1977 
   1978     if (rules == NULL)
   1979         return;
   1980 
   1981     for (k = 0; rules[k].type != KRB5_NCRT_BOGUS; k++) {
   1982 	free(rules[k].match_domain);
   1983 	free(rules[k].match_realm);
   1984 	free(rules[k].domain);
   1985 	free(rules[k].realm);
   1986     }
   1987     free(rules);
   1988 }
   1989 
   1990 struct krb5_name_canon_iterator_data {
   1991     krb5_name_canon_rule	rules;
   1992     krb5_const_principal	in_princ;   /* given princ */
   1993     krb5_const_principal	out_princ;  /* princ to be output */
   1994     krb5_principal		tmp_princ;  /* to be freed */
   1995     int				is_trivial; /* no canon to be done */
   1996     int				done;       /* no more rules to be applied */
   1997     size_t                      cursor;     /* current/next rule */
   1998 };
   1999 
   2000 /**
   2001  * Initialize name canonicalization iterator.
   2002  *
   2003  * @param context   Kerberos context
   2004  * @param in_princ  principal name to be canonicalized OR
   2005  * @param iter	    output iterator object
   2006  */
   2007 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
   2008 krb5_name_canon_iterator_start(krb5_context context,
   2009 			       krb5_const_principal in_princ,
   2010 			       krb5_name_canon_iterator *iter)
   2011 {
   2012     krb5_error_code ret;
   2013     krb5_name_canon_iterator state;
   2014 
   2015     *iter = NULL;
   2016 
   2017     state = calloc(1, sizeof (*state));
   2018     if (state == NULL)
   2019 	return krb5_enomem(context);
   2020     state->in_princ = in_princ;
   2021 
   2022     if (princ_type(state->in_princ) == KRB5_NT_SRV_HST_NEEDS_CANON) {
   2023 	ret = _krb5_get_name_canon_rules(context, &state->rules);
   2024 	if (ret)
   2025 	    goto out;
   2026     } else {
   2027 	/* Name needs no canon -> trivial iterator: in_princ is canonical */
   2028 	state->is_trivial = 1;
   2029     }
   2030 
   2031     *iter = state;
   2032     return 0;
   2033 
   2034 out:
   2035     krb5_free_name_canon_iterator(context, state);
   2036     return krb5_enomem(context);
   2037 }
   2038 
   2039 /*
   2040  * Helper for name canon iteration.
   2041  */
   2042 static krb5_error_code
   2043 name_canon_iterate(krb5_context context,
   2044                    krb5_name_canon_iterator *iter,
   2045                    krb5_name_canon_rule_options *rule_opts)
   2046 {
   2047     krb5_error_code ret;
   2048     krb5_name_canon_iterator state = *iter;
   2049 
   2050     if (rule_opts)
   2051 	*rule_opts = 0;
   2052 
   2053     if (state == NULL)
   2054 	return 0;
   2055 
   2056     if (state->done) {
   2057 	krb5_free_name_canon_iterator(context, state);
   2058 	*iter = NULL;
   2059 	return 0;
   2060     }
   2061 
   2062     if (state->is_trivial && !state->done) {
   2063         state->out_princ = state->in_princ;
   2064 	state->done = 1;
   2065 	return 0;
   2066     }
   2067 
   2068     heim_assert(state->rules != NULL &&
   2069                 state->rules[state->cursor].type != KRB5_NCRT_BOGUS,
   2070                 "Internal error during name canonicalization");
   2071 
   2072     do {
   2073 	krb5_free_principal(context, state->tmp_princ);
   2074 	ret = apply_name_canon_rule(context, state->rules, state->cursor,
   2075 	    state->in_princ, &state->tmp_princ, rule_opts);
   2076 	if (ret) {
   2077             krb5_free_name_canon_iterator(context, state);
   2078             *iter = NULL;
   2079 	    return ret;
   2080         }
   2081 	state->cursor++;
   2082     } while (state->tmp_princ == NULL &&
   2083              state->rules[state->cursor].type != KRB5_NCRT_BOGUS);
   2084 
   2085     if (state->rules[state->cursor].type == KRB5_NCRT_BOGUS)
   2086         state->done = 1;
   2087 
   2088     state->out_princ = state->tmp_princ;
   2089     if (state->tmp_princ == NULL) {
   2090 	krb5_free_name_canon_iterator(context, state);
   2091 	*iter = NULL;
   2092 	return 0;
   2093     }
   2094     return 0;
   2095 }
   2096 
   2097 /**
   2098  * Iteratively apply name canon rules, outputing a principal and rule
   2099  * options each time.  Iteration completes when the @iter is NULL on
   2100  * return or when an error is returned.  Callers must free the iterator
   2101  * if they abandon it mid-way.
   2102  *
   2103  * @param context   Kerberos context
   2104  * @param iter	    name canon rule iterator (input/output)
   2105  * @param try_princ output principal name
   2106  * @param rule_opts output rule options
   2107  */
   2108 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
   2109 krb5_name_canon_iterate(krb5_context context,
   2110                         krb5_name_canon_iterator *iter,
   2111                         krb5_const_principal *try_princ,
   2112                         krb5_name_canon_rule_options *rule_opts)
   2113 {
   2114     krb5_error_code ret;
   2115 
   2116     *try_princ = NULL;
   2117 
   2118     ret = name_canon_iterate(context, iter, rule_opts);
   2119     if (*iter)
   2120 	*try_princ = (*iter)->out_princ;
   2121     return ret;
   2122 }
   2123 
   2124 /**
   2125  * Free a name canonicalization rule iterator.
   2126  */
   2127 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
   2128 krb5_free_name_canon_iterator(krb5_context context,
   2129 			      krb5_name_canon_iterator iter)
   2130 {
   2131     if (iter == NULL)
   2132 	return;
   2133     if (iter->tmp_princ)
   2134         krb5_free_principal(context, iter->tmp_princ);
   2135     free(iter);
   2136 }
   2137