kafs: Fix a warning
[heimdal.git] / lib / krb5 / principal.c
blobdc6692ff2760b96b2066612303ac2184819c0eee
1 /*
2 * Copyright (c) 1997-2007 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
34 /**
35 * @page krb5_principal_intro The principal handing functions.
37 * A Kerberos principal is a email address looking string that
38 * contains two parts separated by @. The second part is the kerberos
39 * realm the principal belongs to and the first is a list of 0 or
40 * more components. For example
41 * @verbatim
42 lha@SU.SE
43 host/hummel.it.su.se@SU.SE
44 host/admin@H5L.ORG
45 @endverbatim
47 * See the library functions here: @ref krb5_principal
50 #include "krb5_locl.h"
51 #ifdef HAVE_RES_SEARCH
52 #define USE_RESOLVER
53 #endif
54 #ifdef HAVE_ARPA_NAMESER_H
55 #include <arpa/nameser.h>
56 #endif
57 #include <fnmatch.h>
58 #include "resolve.h"
60 #define princ_num_comp(P) ((P)->name.name_string.len)
61 #define princ_type(P) ((P)->name.name_type)
62 #define princ_comp(P) ((P)->name.name_string.val)
63 #define princ_ncomp(P, N) ((P)->name.name_string.val[(N)])
64 #define princ_realm(P) ((P)->realm)
66 static krb5_error_code
67 set_default_princ_type(krb5_principal p, NAME_TYPE defnt)
69 if (princ_num_comp(p) > 1 && strcmp(princ_ncomp(p, 0), KRB5_TGS_NAME) == 0)
70 princ_type(p) = KRB5_NT_SRV_INST;
71 else if (princ_num_comp(p) > 1 && strcmp(princ_ncomp(p, 0), "host") == 0)
72 princ_type(p) = KRB5_NT_SRV_HST;
73 else if (princ_num_comp(p) > 1 && strcmp(princ_ncomp(p, 0), "kca_service") == 0)
74 princ_type(p) = KRB5_NT_SRV_HST;
75 else if (princ_num_comp(p) == 2 &&
76 strcmp(princ_ncomp(p, 0), KRB5_WELLKNOWN_NAME) == 0)
77 princ_type(p) = KRB5_NT_WELLKNOWN;
78 else if (princ_num_comp(p) == 1 && strchr(princ_ncomp(p, 0), '@') != NULL)
79 princ_type(p) = KRB5_NT_SMTP_NAME;
80 else
81 princ_type(p) = defnt;
82 return 0;
85 static krb5_error_code append_component(krb5_context, krb5_principal,
86 const char *, size_t);
88 /**
89 * Frees a Kerberos principal allocated by the library with
90 * krb5_parse_name(), krb5_make_principal() or any other related
91 * principal functions.
93 * @param context A Kerberos context.
94 * @param p a principal to free.
96 * @return An krb5 error code, see krb5_get_error_message().
98 * @ingroup krb5_principal
101 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
102 krb5_free_principal(krb5_context context,
103 krb5_principal p)
105 if(p){
106 free_Principal(p);
107 free(p);
112 * Set the type of the principal
114 * @param context A Kerberos context.
115 * @param principal principal to set the type for
116 * @param type the new type
118 * @return An krb5 error code, see krb5_get_error_message().
120 * @ingroup krb5_principal
123 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
124 krb5_principal_set_type(krb5_context context,
125 krb5_principal principal,
126 int type)
128 princ_type(principal) = type;
132 * Get the type of the principal
134 * @param context A Kerberos context.
135 * @param principal principal to get the type for
137 * @return the type of principal
139 * @ingroup krb5_principal
142 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
143 krb5_principal_get_type(krb5_context context,
144 krb5_const_principal principal)
146 return princ_type(principal);
150 * Get the realm of the principal
152 * @param context A Kerberos context.
153 * @param principal principal to get the realm for
155 * @return realm of the principal, don't free or use after krb5_principal is freed
157 * @ingroup krb5_principal
160 KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
161 krb5_principal_get_realm(krb5_context context,
162 krb5_const_principal principal)
164 return princ_realm(principal);
167 KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
168 krb5_principal_get_comp_string(krb5_context context,
169 krb5_const_principal principal,
170 unsigned int component)
172 if(component >= princ_num_comp(principal))
173 return NULL;
174 return princ_ncomp(principal, component);
178 * Get number of component is principal.
180 * @param context Kerberos 5 context
181 * @param principal principal to query
183 * @return number of components in string
185 * @ingroup krb5_principal
188 KRB5_LIB_FUNCTION unsigned int KRB5_LIB_CALL
189 krb5_principal_get_num_comp(krb5_context context,
190 krb5_const_principal principal)
192 return princ_num_comp(principal);
196 * Parse a name into a krb5_principal structure, flags controls the behavior.
198 * @param context Kerberos 5 context
199 * @param name name to parse into a Kerberos principal
200 * @param flags flags to control the behavior
201 * @param principal returned principal, free with krb5_free_principal().
203 * @return An krb5 error code, see krb5_get_error_message().
205 * @ingroup krb5_principal
208 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
209 krb5_parse_name_flags(krb5_context context,
210 const char *name,
211 int flags,
212 krb5_principal *principal)
214 krb5_error_code ret;
215 heim_general_string *comp;
216 heim_general_string realm = NULL;
217 int ncomp;
219 const char *p;
220 char *q;
221 char *s;
222 char *start;
224 int n;
225 char c;
226 int got_realm = 0;
227 int first_at = 1;
228 int no_realm = flags & KRB5_PRINCIPAL_PARSE_NO_REALM;
229 int require_realm = flags & KRB5_PRINCIPAL_PARSE_REQUIRE_REALM;
230 int enterprise = flags & KRB5_PRINCIPAL_PARSE_ENTERPRISE;
231 int ignore_realm = flags & KRB5_PRINCIPAL_PARSE_IGNORE_REALM;
232 int no_def_realm = flags & KRB5_PRINCIPAL_PARSE_NO_DEF_REALM;
234 *principal = NULL;
236 if (no_realm && require_realm) {
237 krb5_set_error_message(context, EINVAL,
238 N_("Can't require both realm and "
239 "no realm at the same time", ""));
240 return EINVAL;
243 /* count number of component,
244 * enterprise names only have one component
246 ncomp = 1;
247 if (!enterprise) {
248 for (p = name; *p; p++) {
249 if (*p=='\\') {
250 if (!p[1]) {
251 krb5_set_error_message(context, KRB5_PARSE_MALFORMED,
252 N_("trailing \\ in principal name", ""));
253 return KRB5_PARSE_MALFORMED;
255 p++;
256 } else if (*p == '/')
257 ncomp++;
258 else if (*p == '@')
259 break;
262 comp = calloc(ncomp, sizeof(*comp));
263 if (comp == NULL)
264 return krb5_enomem(context);
266 n = 0;
267 p = start = q = s = strdup(name);
268 if (start == NULL) {
269 free(comp);
270 return krb5_enomem(context);
272 while (*p) {
273 c = *p++;
274 if (c == '\\') {
275 c = *p++;
276 if (c == 'n')
277 c = '\n';
278 else if (c == 't')
279 c = '\t';
280 else if (c == 'b')
281 c = '\b';
282 else if (c == '0') {
284 * We'll ignore trailing embedded NULs in components and
285 * realms, but can't support any other embedded NULs.
287 while (*p) {
288 if ((*p == '/' || *p == '@') && !got_realm)
289 break;
290 if (*(p++) != '\\' || *(p++) != '0') {
291 ret = KRB5_PARSE_MALFORMED;
292 krb5_set_error_message(context, ret,
293 N_("embedded NULs in principal "
294 "name not supported", ""));
295 goto exit;
298 continue;
299 } else if (c == '\0') {
300 ret = KRB5_PARSE_MALFORMED;
301 krb5_set_error_message(context, ret,
302 N_("trailing \\ in principal name", ""));
303 goto exit;
305 } else if (enterprise && first_at) {
306 if (c == '@')
307 first_at = 0;
308 } else if ((c == '/' && !enterprise) || c == '@') {
309 if (got_realm) {
310 ret = KRB5_PARSE_MALFORMED;
311 krb5_set_error_message(context, ret,
312 N_("part after realm in principal name", ""));
313 goto exit;
314 } else {
315 comp[n] = malloc(q - start + 1);
316 if (comp[n] == NULL) {
317 ret = krb5_enomem(context);
318 goto exit;
320 memcpy(comp[n], start, q - start);
321 comp[n][q - start] = 0;
322 n++;
324 if (c == '@')
325 got_realm = 1;
326 start = q;
327 continue;
329 if (got_realm && (c == '/' || c == '\0')) {
330 ret = KRB5_PARSE_MALFORMED;
331 krb5_set_error_message(context, ret,
332 N_("part after realm in principal name", ""));
333 goto exit;
335 *q++ = c;
337 if (got_realm) {
338 if (no_realm) {
339 ret = KRB5_PARSE_MALFORMED;
340 krb5_set_error_message(context, ret,
341 N_("realm found in 'short' principal "
342 "expected to be without one", ""));
343 goto exit;
345 if (!ignore_realm) {
346 realm = malloc(q - start + 1);
347 if (realm == NULL) {
348 ret = krb5_enomem(context);
349 goto exit;
351 memcpy(realm, start, q - start);
352 realm[q - start] = 0;
354 } else {
355 if (require_realm) {
356 ret = KRB5_PARSE_MALFORMED;
357 krb5_set_error_message(context, ret,
358 N_("realm NOT found in principal "
359 "expected to be with one", ""));
360 goto exit;
361 } else if (no_realm || no_def_realm) {
362 realm = NULL;
363 } else {
364 ret = krb5_get_default_realm(context, &realm);
365 if (ret)
366 goto exit;
369 comp[n] = malloc(q - start + 1);
370 if (comp[n] == NULL) {
371 ret = krb5_enomem(context);
372 goto exit;
374 memcpy(comp[n], start, q - start);
375 comp[n][q - start] = 0;
376 n++;
378 *principal = calloc(1, sizeof(**principal));
379 if (*principal == NULL) {
380 ret = krb5_enomem(context);
381 goto exit;
383 (*principal)->name.name_string.val = comp;
384 princ_num_comp(*principal) = n;
385 (*principal)->realm = realm;
386 if (enterprise)
387 princ_type(*principal) = KRB5_NT_ENTERPRISE_PRINCIPAL;
388 else
389 set_default_princ_type(*principal, KRB5_NT_PRINCIPAL);
390 free(s);
391 return 0;
392 exit:
393 while (n>0) {
394 free(comp[--n]);
396 free(comp);
397 krb5_free_default_realm(context, realm);
398 free(s);
399 return ret;
403 * Parse a name into a krb5_principal structure
405 * @param context Kerberos 5 context
406 * @param name name to parse into a Kerberos principal
407 * @param principal returned principal, free with krb5_free_principal().
409 * @return An krb5 error code, see krb5_get_error_message().
411 * @ingroup krb5_principal
414 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
415 krb5_parse_name(krb5_context context,
416 const char *name,
417 krb5_principal *principal)
419 return krb5_parse_name_flags(context, name, 0, principal);
422 static const char quotable_chars[] = " \n\t\b\\/@";
423 static const char replace_chars[] = " ntb\\/@";
425 #define add_char(BASE, INDEX, LEN, C) do { if((INDEX) < (LEN)) (BASE)[(INDEX)++] = (C); }while(0);
427 static size_t
428 quote_string(const char *s, char *out, size_t idx, size_t len, int display)
430 const char *p, *q;
431 for(p = s; *p && idx < len; p++){
432 q = strchr(quotable_chars, *p);
433 if (q && display) {
434 add_char(out, idx, len, replace_chars[q - quotable_chars]);
435 } else if (q) {
436 add_char(out, idx, len, '\\');
437 add_char(out, idx, len, replace_chars[q - quotable_chars]);
438 }else
439 add_char(out, idx, len, *p);
441 if(idx < len)
442 out[idx] = '\0';
443 return idx;
447 static krb5_error_code
448 unparse_name_fixed(krb5_context context,
449 krb5_const_principal principal,
450 char *name,
451 size_t len,
452 int flags)
454 size_t idx = 0;
455 size_t i;
456 int short_form = (flags & KRB5_PRINCIPAL_UNPARSE_SHORT) != 0;
457 int no_realm = (flags & KRB5_PRINCIPAL_UNPARSE_NO_REALM) != 0;
458 int display = (flags & KRB5_PRINCIPAL_UNPARSE_DISPLAY) != 0;
460 if (!no_realm && princ_realm(principal) == NULL) {
461 krb5_set_error_message(context, ERANGE,
462 N_("Realm missing from principal, "
463 "can't unparse", ""));
464 return ERANGE;
467 for(i = 0; i < princ_num_comp(principal); i++){
468 if(i)
469 add_char(name, idx, len, '/');
470 idx = quote_string(princ_ncomp(principal, i), name, idx, len, display);
471 if(idx == len) {
472 krb5_set_error_message(context, ERANGE,
473 N_("Out of space printing principal", ""));
474 return ERANGE;
477 /* add realm if different from default realm */
478 if(short_form && !no_realm) {
479 krb5_realm r;
480 krb5_error_code ret;
481 ret = krb5_get_default_realm(context, &r);
482 if(ret)
483 return ret;
484 if(strcmp(princ_realm(principal), r) != 0)
485 short_form = 0;
486 krb5_free_default_realm(context, r);
488 if(!short_form && !no_realm) {
489 add_char(name, idx, len, '@');
490 idx = quote_string(princ_realm(principal), name, idx, len, display);
491 if(idx == len) {
492 krb5_set_error_message(context, ERANGE,
493 N_("Out of space printing "
494 "realm of principal", ""));
495 return ERANGE;
498 return 0;
502 * Unparse the principal name to a fixed buffer
504 * @param context A Kerberos context.
505 * @param principal principal to unparse
506 * @param name buffer to write name to
507 * @param len length of buffer
509 * @return An krb5 error code, see krb5_get_error_message().
511 * @ingroup krb5_principal
514 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
515 krb5_unparse_name_fixed(krb5_context context,
516 krb5_const_principal principal,
517 char *name,
518 size_t len)
520 return unparse_name_fixed(context, principal, name, len, 0);
524 * Unparse the principal name to a fixed buffer. The realm is skipped
525 * if its a default realm.
527 * @param context A Kerberos context.
528 * @param principal principal to unparse
529 * @param name buffer to write name to
530 * @param len length of buffer
532 * @return An krb5 error code, see krb5_get_error_message().
534 * @ingroup krb5_principal
537 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
538 krb5_unparse_name_fixed_short(krb5_context context,
539 krb5_const_principal principal,
540 char *name,
541 size_t len)
543 return unparse_name_fixed(context, principal, name, len,
544 KRB5_PRINCIPAL_UNPARSE_SHORT);
548 * Unparse the principal name with unparse flags to a fixed buffer.
550 * @param context A Kerberos context.
551 * @param principal principal to unparse
552 * @param flags unparse flags
553 * @param name buffer to write name to
554 * @param len length of buffer
556 * @return An krb5 error code, see krb5_get_error_message().
558 * @ingroup krb5_principal
561 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
562 krb5_unparse_name_fixed_flags(krb5_context context,
563 krb5_const_principal principal,
564 int flags,
565 char *name,
566 size_t len)
568 return unparse_name_fixed(context, principal, name, len, flags);
571 static krb5_error_code
572 unparse_name(krb5_context context,
573 krb5_const_principal principal,
574 char **name,
575 int flags)
577 size_t len = 0, plen;
578 size_t i;
579 krb5_error_code ret;
580 /* count length */
581 if (princ_realm(principal)) {
582 plen = strlen(princ_realm(principal));
584 if(strcspn(princ_realm(principal), quotable_chars) == plen)
585 len += plen;
586 else
587 len += 2*plen;
588 len++; /* '@' */
590 for(i = 0; i < princ_num_comp(principal); i++){
591 plen = strlen(princ_ncomp(principal, i));
592 if(strcspn(princ_ncomp(principal, i), quotable_chars) == plen)
593 len += plen;
594 else
595 len += 2*plen;
596 len++;
598 len++; /* '\0' */
599 *name = malloc(len);
600 if(*name == NULL)
601 return krb5_enomem(context);
602 ret = unparse_name_fixed(context, principal, *name, len, flags);
603 if(ret) {
604 free(*name);
605 *name = NULL;
607 return ret;
611 * Unparse the Kerberos name into a string
613 * @param context Kerberos 5 context
614 * @param principal principal to query
615 * @param name resulting string, free with krb5_xfree()
617 * @return An krb5 error code, see krb5_get_error_message().
619 * @ingroup krb5_principal
622 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
623 krb5_unparse_name(krb5_context context,
624 krb5_const_principal principal,
625 char **name)
627 return unparse_name(context, principal, name, 0);
631 * Unparse the Kerberos name into a string
633 * @param context Kerberos 5 context
634 * @param principal principal to query
635 * @param flags flag to determine the behavior
636 * @param name resulting string, free with krb5_xfree()
638 * @return An krb5 error code, see krb5_get_error_message().
640 * @ingroup krb5_principal
643 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
644 krb5_unparse_name_flags(krb5_context context,
645 krb5_const_principal principal,
646 int flags,
647 char **name)
649 return unparse_name(context, principal, name, flags);
653 * Unparse the principal name to a allocated buffer. The realm is
654 * skipped if its a default realm.
656 * @param context A Kerberos context.
657 * @param principal principal to unparse
658 * @param name returned buffer, free with krb5_xfree()
660 * @return An krb5 error code, see krb5_get_error_message().
662 * @ingroup krb5_principal
665 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
666 krb5_unparse_name_short(krb5_context context,
667 krb5_const_principal principal,
668 char **name)
670 return unparse_name(context, principal, name, KRB5_PRINCIPAL_UNPARSE_SHORT);
674 * Set a new realm for a principal, and as a side-effect free the
675 * previous realm.
677 * @param context A Kerberos context.
678 * @param principal principal set the realm for
679 * @param realm the new realm to set
681 * @return An krb5 error code, see krb5_get_error_message().
683 * @ingroup krb5_principal
686 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
687 krb5_principal_set_realm(krb5_context context,
688 krb5_principal principal,
689 krb5_const_realm realm)
691 if (princ_realm(principal))
692 free(princ_realm(principal));
694 if (realm == NULL)
695 princ_realm(principal) = NULL;
696 else if ((princ_realm(principal) = strdup(realm)) == NULL)
697 return krb5_enomem(context);
698 return 0;
701 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
702 krb5_principal_set_comp_string(krb5_context context,
703 krb5_principal principal,
704 unsigned int k,
705 const char *component)
707 char *s;
708 size_t i;
710 for (i = princ_num_comp(principal); i <= k; i++)
711 append_component(context, principal, "", 0);
712 s = strdup(component);
713 if (s == NULL)
714 return krb5_enomem(context);
715 free(princ_ncomp(principal, k));
716 princ_ncomp(principal, k) = s;
717 return 0;
720 #ifndef HEIMDAL_SMALLER
722 * Build a principal using vararg style building
724 * @param context A Kerberos context.
725 * @param principal returned principal
726 * @param rlen length of realm
727 * @param realm realm name
728 * @param ... a list of components ended with NULL.
730 * @return An krb5 error code, see krb5_get_error_message().
732 * @ingroup krb5_principal
735 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
736 krb5_build_principal(krb5_context context,
737 krb5_principal *principal,
738 int rlen,
739 krb5_const_realm realm,
740 ...)
742 krb5_error_code ret;
743 va_list ap;
744 va_start(ap, realm);
745 ret = krb5_build_principal_va(context, principal, rlen, realm, ap);
746 va_end(ap);
747 return ret;
749 #endif
752 * Build a principal using vararg style building
754 * @param context A Kerberos context.
755 * @param principal returned principal
756 * @param realm realm name
757 * @param ... a list of components ended with NULL.
759 * @return An krb5 error code, see krb5_get_error_message().
761 * @ingroup krb5_principal
764 /* coverity[+alloc : arg-*1] */
765 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
766 krb5_make_principal(krb5_context context,
767 krb5_principal *principal,
768 krb5_const_realm realm,
769 ...)
771 krb5_error_code ret;
772 krb5_realm r = NULL;
773 va_list ap;
774 if(realm == NULL) {
775 ret = krb5_get_default_realm(context, &r);
776 if(ret)
777 return ret;
778 realm = r;
780 va_start(ap, realm);
781 ret = krb5_build_principal_va(context, principal, strlen(realm), realm, ap);
782 va_end(ap);
783 if(r)
784 krb5_free_default_realm(context, r);
785 return ret;
788 static krb5_error_code
789 append_component(krb5_context context, krb5_principal p,
790 const char *comp,
791 size_t comp_len)
793 heim_general_string *tmp;
794 size_t len = princ_num_comp(p);
796 tmp = realloc(princ_comp(p), (len + 1) * sizeof(*tmp));
797 if(tmp == NULL)
798 return krb5_enomem(context);
799 princ_comp(p) = tmp;
800 princ_ncomp(p, len) = malloc(comp_len + 1);
801 if (princ_ncomp(p, len) == NULL)
802 return krb5_enomem(context);
803 memcpy (princ_ncomp(p, len), comp, comp_len);
804 princ_ncomp(p, len)[comp_len] = '\0';
805 princ_num_comp(p)++;
806 return 0;
809 static krb5_error_code
810 va_ext_princ(krb5_context context, krb5_principal p, va_list ap)
812 krb5_error_code ret = 0;
814 while (1){
815 const char *s;
816 int len;
818 if ((len = va_arg(ap, int)) == 0)
819 break;
820 s = va_arg(ap, const char*);
821 if ((ret = append_component(context, p, s, len)) != 0)
822 break;
824 return ret;
827 static krb5_error_code
828 va_princ(krb5_context context, krb5_principal p, va_list ap)
830 krb5_error_code ret = 0;
832 while (1){
833 const char *s;
835 if ((s = va_arg(ap, const char*)) == NULL)
836 break;
837 if ((ret = append_component(context, p, s, strlen(s))) != 0)
838 break;
840 return ret;
843 static krb5_error_code
844 build_principal(krb5_context context,
845 krb5_principal *principal,
846 int rlen,
847 krb5_const_realm realm,
848 krb5_error_code (*func)(krb5_context, krb5_principal, va_list),
849 va_list ap)
851 krb5_error_code ret;
852 krb5_principal p;
854 *principal = NULL;
855 p = calloc(1, sizeof(*p));
856 if (p == NULL)
857 return krb5_enomem(context);
859 princ_realm(p) = strdup(realm);
860 if (p->realm == NULL) {
861 free(p);
862 return krb5_enomem(context);
865 ret = func(context, p, ap);
866 if (ret == 0) {
867 *principal = p;
868 set_default_princ_type(p, KRB5_NT_PRINCIPAL);
869 } else
870 krb5_free_principal(context, p);
871 return ret;
874 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
875 krb5_build_principal_va(krb5_context context,
876 krb5_principal *principal,
877 int rlen,
878 krb5_const_realm realm,
879 va_list ap)
881 return build_principal(context, principal, rlen, realm, va_princ, ap);
884 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
885 krb5_build_principal_va_ext(krb5_context context,
886 krb5_principal *principal,
887 int rlen,
888 krb5_const_realm realm,
889 va_list ap)
891 return build_principal(context, principal, rlen, realm, va_ext_princ, ap);
895 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
896 krb5_build_principal_ext(krb5_context context,
897 krb5_principal *principal,
898 int rlen,
899 krb5_const_realm realm,
900 ...)
902 krb5_error_code ret;
903 va_list ap;
904 va_start(ap, realm);
905 ret = krb5_build_principal_va_ext(context, principal, rlen, realm, ap);
906 va_end(ap);
907 return ret;
911 * Copy a principal
913 * @param context A Kerberos context.
914 * @param inprinc principal to copy
915 * @param outprinc copied principal, free with krb5_free_principal()
917 * @return An krb5 error code, see krb5_get_error_message().
919 * @ingroup krb5_principal
923 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
924 krb5_copy_principal(krb5_context context,
925 krb5_const_principal inprinc,
926 krb5_principal *outprinc)
928 krb5_principal p = malloc(sizeof(*p));
929 if (p == NULL)
930 return krb5_enomem(context);
931 if(copy_Principal(inprinc, p)) {
932 free(p);
933 return krb5_enomem(context);
935 *outprinc = p;
936 return 0;
940 * Return TRUE iff princ1 == princ2 (without considering the realm)
942 * @param context Kerberos 5 context
943 * @param princ1 first principal to compare
944 * @param princ2 second principal to compare
946 * @return non zero if equal, 0 if not
948 * @ingroup krb5_principal
949 * @see krb5_principal_compare()
950 * @see krb5_realm_compare()
953 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
954 krb5_principal_compare_any_realm(krb5_context context,
955 krb5_const_principal princ1,
956 krb5_const_principal princ2)
958 size_t i;
959 if(princ_num_comp(princ1) != princ_num_comp(princ2))
960 return FALSE;
961 for(i = 0; i < princ_num_comp(princ1); i++){
962 if(strcmp(princ_ncomp(princ1, i), princ_ncomp(princ2, i)) != 0)
963 return FALSE;
965 return TRUE;
968 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
969 _krb5_principal_compare_PrincipalName(krb5_context context,
970 krb5_const_principal princ1,
971 PrincipalName *princ2)
973 size_t i;
974 if (princ_num_comp(princ1) != princ2->name_string.len)
975 return FALSE;
976 for(i = 0; i < princ_num_comp(princ1); i++){
977 if(strcmp(princ_ncomp(princ1, i), princ2->name_string.val[i]) != 0)
978 return FALSE;
980 return TRUE;
985 * Compares the two principals, including realm of the principals and returns
986 * TRUE if they are the same and FALSE if not.
988 * @param context Kerberos 5 context
989 * @param princ1 first principal to compare
990 * @param princ2 second principal to compare
992 * @ingroup krb5_principal
993 * @see krb5_principal_compare_any_realm()
994 * @see krb5_realm_compare()
998 * return TRUE iff princ1 == princ2
1001 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1002 krb5_principal_compare(krb5_context context,
1003 krb5_const_principal princ1,
1004 krb5_const_principal princ2)
1006 if (!krb5_realm_compare(context, princ1, princ2))
1007 return FALSE;
1008 return krb5_principal_compare_any_realm(context, princ1, princ2);
1012 * return TRUE iff realm(princ1) == realm(princ2)
1014 * @param context Kerberos 5 context
1015 * @param princ1 first principal to compare
1016 * @param princ2 second principal to compare
1018 * @ingroup krb5_principal
1019 * @see krb5_principal_compare_any_realm()
1020 * @see krb5_principal_compare()
1023 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1024 krb5_realm_compare(krb5_context context,
1025 krb5_const_principal princ1,
1026 krb5_const_principal princ2)
1028 return strcmp(princ_realm(princ1), princ_realm(princ2)) == 0;
1032 * return TRUE iff princ matches pattern
1034 * @ingroup krb5_principal
1037 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1038 krb5_principal_match(krb5_context context,
1039 krb5_const_principal princ,
1040 krb5_const_principal pattern)
1042 size_t i;
1043 if(princ_num_comp(princ) != princ_num_comp(pattern))
1044 return FALSE;
1045 if(fnmatch(princ_realm(pattern), princ_realm(princ), 0) != 0)
1046 return FALSE;
1047 for(i = 0; i < princ_num_comp(princ); i++){
1048 if(fnmatch(princ_ncomp(pattern, i), princ_ncomp(princ, i), 0) != 0)
1049 return FALSE;
1051 return TRUE;
1055 * This is the original krb5_sname_to_principal(), renamed to be a
1056 * helper of the new one.
1058 static krb5_error_code
1059 krb5_sname_to_principal_old(krb5_context context,
1060 const char *realm,
1061 const char *hostname,
1062 const char *sname,
1063 int32_t type,
1064 krb5_principal *ret_princ)
1066 krb5_error_code ret;
1067 char localhost[MAXHOSTNAMELEN];
1068 char **realms = NULL, *host = NULL;
1070 if(type != KRB5_NT_SRV_HST && type != KRB5_NT_UNKNOWN) {
1071 krb5_set_error_message(context, KRB5_SNAME_UNSUPP_NAMETYPE,
1072 N_("unsupported name type %d", ""),
1073 (int)type);
1074 return KRB5_SNAME_UNSUPP_NAMETYPE;
1076 if(hostname == NULL) {
1077 ret = gethostname(localhost, sizeof(localhost) - 1);
1078 if (ret != 0) {
1079 ret = errno;
1080 krb5_set_error_message(context, ret,
1081 N_("Failed to get local hostname", ""));
1082 return ret;
1084 localhost[sizeof(localhost) - 1] = '\0';
1085 hostname = localhost;
1087 if(sname == NULL)
1088 sname = "host";
1089 if(type == KRB5_NT_SRV_HST) {
1090 if (realm)
1091 ret = krb5_expand_hostname(context, hostname, &host);
1092 else
1093 ret = krb5_expand_hostname_realms(context, hostname,
1094 &host, &realms);
1095 if (ret)
1096 return ret;
1097 strlwr(host);
1098 hostname = host;
1099 if (!realm)
1100 realm = realms[0];
1101 } else if (!realm) {
1102 ret = krb5_get_host_realm(context, hostname, &realms);
1103 if(ret)
1104 return ret;
1105 realm = realms[0];
1108 ret = krb5_make_principal(context, ret_princ, realm, sname,
1109 hostname, NULL);
1110 if(host)
1111 free(host);
1112 if (realms)
1113 krb5_free_host_realm(context, realms);
1114 return ret;
1117 static const struct {
1118 const char *type;
1119 int32_t value;
1120 } nametypes[] = {
1121 { "UNKNOWN", KRB5_NT_UNKNOWN },
1122 { "PRINCIPAL", KRB5_NT_PRINCIPAL },
1123 { "SRV_INST", KRB5_NT_SRV_INST },
1124 { "SRV_HST", KRB5_NT_SRV_HST },
1125 { "SRV_XHST", KRB5_NT_SRV_XHST },
1126 { "UID", KRB5_NT_UID },
1127 { "X500_PRINCIPAL", KRB5_NT_X500_PRINCIPAL },
1128 { "SMTP_NAME", KRB5_NT_SMTP_NAME },
1129 { "ENTERPRISE_PRINCIPAL", KRB5_NT_ENTERPRISE_PRINCIPAL },
1130 { "WELLKNOWN", KRB5_NT_WELLKNOWN },
1131 { "SRV_HST_DOMAIN", KRB5_NT_SRV_HST_DOMAIN },
1132 { "ENT_PRINCIPAL_AND_ID", KRB5_NT_ENT_PRINCIPAL_AND_ID },
1133 { "MS_PRINCIPAL", KRB5_NT_MS_PRINCIPAL },
1134 { "MS_PRINCIPAL_AND_ID", KRB5_NT_MS_PRINCIPAL_AND_ID },
1135 { "SRV_HST_NEEDS_CANON", KRB5_NT_SRV_HST_NEEDS_CANON },
1136 { NULL, 0 }
1140 * Parse nametype string and return a nametype integer
1142 * @ingroup krb5_principal
1145 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1146 krb5_parse_nametype(krb5_context context, const char *str, int32_t *nametype)
1148 size_t i;
1150 for(i = 0; nametypes[i].type; i++) {
1151 if (strcasecmp(nametypes[i].type, str) == 0) {
1152 *nametype = nametypes[i].value;
1153 return 0;
1156 krb5_set_error_message(context, KRB5_PARSE_MALFORMED,
1157 N_("Failed to find name type %s", ""), str);
1158 return KRB5_PARSE_MALFORMED;
1162 * Returns true if name is Kerberos NULL name
1164 * @ingroup krb5_principal
1167 krb5_boolean KRB5_LIB_FUNCTION
1168 krb5_principal_is_null(krb5_context context, krb5_const_principal principal)
1170 if (principal->name.name_type == KRB5_NT_WELLKNOWN &&
1171 principal->name.name_string.len == 2 &&
1172 strcmp(principal->name.name_string.val[0], "WELLKNOWN") == 0 &&
1173 strcmp(principal->name.name_string.val[1], "NULL") == 0)
1174 return TRUE;
1175 return FALSE;
1178 const char _krb5_wellknown_lkdc[] = "WELLKNOWN:COM.APPLE.LKDC";
1179 static const char lkdc_prefix[] = "LKDC:";
1182 * Returns true if name is Kerberos an LKDC realm
1184 * @ingroup krb5_principal
1187 krb5_boolean KRB5_LIB_FUNCTION
1188 krb5_realm_is_lkdc(const char *realm)
1191 return strncmp(realm, lkdc_prefix, sizeof(lkdc_prefix)-1) == 0 ||
1192 strncmp(realm, _krb5_wellknown_lkdc, sizeof(_krb5_wellknown_lkdc) - 1) == 0;
1196 * Returns true if name is Kerberos an LKDC realm
1198 * @ingroup krb5_principal
1201 krb5_boolean KRB5_LIB_FUNCTION
1202 krb5_principal_is_lkdc(krb5_context context, krb5_const_principal principal)
1204 return krb5_realm_is_lkdc(principal->realm);
1208 * Returns true if name is Kerberos an LKDC realm
1210 * @ingroup krb5_principal
1213 krb5_boolean KRB5_LIB_FUNCTION
1214 krb5_principal_is_pku2u(krb5_context context, krb5_const_principal principal)
1216 return strcmp(principal->realm, KRB5_PKU2U_REALM_NAME) == 0;
1220 * Check if the cname part of the principal is a krbtgt principal
1222 * @ingroup krb5_principal
1225 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1226 krb5_principal_is_krbtgt(krb5_context context, krb5_const_principal p)
1228 return p->name.name_string.len == 2 &&
1229 strcmp(p->name.name_string.val[0], KRB5_TGS_NAME) == 0;
1233 * Returns true iff name is an WELLKNOWN:ORG.H5L.HOSTBASED-SERVICE
1235 * @ingroup krb5_principal
1238 krb5_boolean KRB5_LIB_FUNCTION
1239 krb5_principal_is_gss_hostbased_service(krb5_context context,
1240 krb5_const_principal principal)
1242 if (principal == NULL)
1243 return FALSE;
1244 if (principal->name.name_string.len != 2)
1245 return FALSE;
1246 if (strcmp(principal->name.name_string.val[1], KRB5_GSS_HOSTBASED_SERVICE_NAME) != 0)
1247 return FALSE;
1248 return TRUE;
1252 * Check if the cname part of the principal is a initial or renewed krbtgt principal
1254 * @ingroup krb5_principal
1257 krb5_boolean KRB5_LIB_FUNCTION
1258 krb5_principal_is_root_krbtgt(krb5_context context, krb5_const_principal p)
1260 return p->name.name_string.len == 2 &&
1261 strcmp(p->name.name_string.val[0], KRB5_TGS_NAME) == 0 &&
1262 strcmp(p->name.name_string.val[1], p->realm) == 0;
1266 * Returns true iff name is WELLKNOWN/ANONYMOUS
1268 * @ingroup krb5_principal
1271 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1272 krb5_principal_is_anonymous(krb5_context context,
1273 krb5_const_principal p,
1274 unsigned int flags)
1277 * Heimdal versions 7.5 and below left the name-type at KRB5_NT_PRINCIPAL
1278 * even with anonymous pkinit responses. To retain interoperability with
1279 * legacy KDCs, the name-type is not checked by the client after requesting
1280 * a fully anonymous ticket.
1282 if (!(flags & KRB5_ANON_IGNORE_NAME_TYPE) &&
1283 p->name.name_type != KRB5_NT_WELLKNOWN &&
1284 p->name.name_type != KRB5_NT_UNKNOWN)
1285 return FALSE;
1287 if (p->name.name_string.len != 2 ||
1288 strcmp(p->name.name_string.val[0], KRB5_WELLKNOWN_NAME) != 0 ||
1289 strcmp(p->name.name_string.val[1], KRB5_ANON_NAME) != 0)
1290 return FALSE;
1293 * While unauthenticated clients SHOULD get "WELLKNOWN:ANONYMOUS" as their
1294 * realm, Heimdal KDCs prior to 7.0 returned the requested realm. While
1295 * such tickets might lead *servers* to unwittingly grant access to fully
1296 * anonymous clients, trusting that the client was authenticated to the
1297 * realm in question, doing it right is the KDC's job, the client should
1298 * not refuse such a ticket.
1300 * If we ever do decide to enforce WELLKNOWN:ANONYMOUS for unauthenticated
1301 * clients, it is essential that calls that pass KRB5_ANON_MATCH_ANY still
1302 * ignore the realm, as in that case either case matches one of the two
1303 * possible conditions.
1305 if (flags & KRB5_ANON_MATCH_UNAUTHENTICATED)
1306 return TRUE;
1309 * Finally, authenticated clients that asked to be only anonymized do
1310 * legitimately expect a non-anon realm.
1312 return strcmp(p->realm, KRB5_ANON_REALM) != 0;
1316 * Returns true iff name is WELLKNOWN/FEDERATED
1318 * @ingroup krb5_principal
1321 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1322 krb5_principal_is_federated(krb5_context context,
1323 krb5_const_principal p)
1325 if (p->name.name_type != KRB5_NT_WELLKNOWN &&
1326 p->name.name_type != KRB5_NT_UNKNOWN)
1327 return FALSE;
1329 if (p->name.name_string.len != 2 ||
1330 strcmp(p->name.name_string.val[0], KRB5_WELLKNOWN_NAME) != 0 ||
1331 strcmp(p->name.name_string.val[1], KRB5_FEDERATED_NAME) != 0)
1332 return FALSE;
1334 return TRUE;
1337 static int
1338 tolower_ascii(int c)
1340 if (c >= 'A' && c <= 'Z')
1341 return 'a' + (c - 'A');
1342 return c;
1345 typedef enum krb5_name_canon_rule_type {
1346 KRB5_NCRT_BOGUS = 0,
1347 KRB5_NCRT_AS_IS,
1348 KRB5_NCRT_QUALIFY,
1349 KRB5_NCRT_NSS
1350 } krb5_name_canon_rule_type;
1352 #ifdef UINT8_MAX
1353 #define MAXDOTS UINT8_MAX
1354 #else
1355 #define MAXDOTS (255U)
1356 #endif
1357 #ifdef UINT16_MAX
1358 #define MAXORDER UINT16_MAX
1359 #else
1360 #define MAXORDER (65535U)
1361 #endif
1363 struct krb5_name_canon_rule_data {
1364 krb5_name_canon_rule_type type;
1365 krb5_name_canon_rule_options options;
1366 uint8_t mindots; /* match this many dots or more */
1367 uint8_t maxdots; /* match no more than this many dots */
1368 uint16_t explicit_order; /* given order */
1369 uint16_t order; /* actual order */
1370 char *match_domain; /* match this stem */
1371 char *match_realm; /* match this realm */
1372 char *domain; /* qualify with this domain */
1373 char *realm; /* qualify with this realm */
1377 * Create a principal for the given service running on the given
1378 * hostname. If KRB5_NT_SRV_HST is used, the hostname is canonicalized
1379 * according the configured name canonicalization rules, with
1380 * canonicalization delayed in some cases. One rule involves DNS, which
1381 * is insecure unless DNSSEC is used, but we don't use DNSSEC-capable
1382 * resolver APIs here, so that if DNSSEC is used we wouldn't know it.
1384 * Canonicalization is immediate (not delayed) only when there is only
1385 * one canonicalization rule and that rule indicates that we should do a
1386 * host lookup by name (i.e., DNS).
1388 * @param context A Kerberos context.
1389 * @param hostname hostname to use
1390 * @param sname Service name to use
1391 * @param type name type of principal, use KRB5_NT_SRV_HST or KRB5_NT_UNKNOWN.
1392 * @param ret_princ return principal, free with krb5_free_principal().
1394 * @return An krb5 error code, see krb5_get_error_message().
1396 * @ingroup krb5_principal
1399 /* coverity[+alloc : arg-*4] */
1400 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1401 krb5_sname_to_principal(krb5_context context,
1402 const char *hostname,
1403 const char *sname,
1404 int32_t type,
1405 krb5_principal *ret_princ)
1407 char *realm, *remote_host;
1408 krb5_error_code ret;
1409 register char *cp;
1410 char localname[MAXHOSTNAMELEN];
1412 *ret_princ = NULL;
1414 if ((type != KRB5_NT_UNKNOWN) &&
1415 (type != KRB5_NT_SRV_HST))
1416 return KRB5_SNAME_UNSUPP_NAMETYPE;
1418 /* if hostname is NULL, use local hostname */
1419 if (hostname == NULL) {
1420 if (gethostname(localname, MAXHOSTNAMELEN))
1421 return errno;
1422 hostname = localname;
1425 /* if sname is NULL, use "host" */
1426 if (sname == NULL)
1427 sname = "host";
1429 remote_host = strdup(hostname);
1430 if (remote_host == NULL)
1431 return krb5_enomem(context);
1433 if (type == KRB5_NT_SRV_HST) {
1434 krb5_name_canon_rule rules;
1436 /* Lower-case the hostname, because that's the convention */
1437 for (cp = remote_host; *cp; cp++)
1438 if (isupper((int) (*cp)))
1439 *cp = tolower((int) (*cp));
1442 * If there is only one name canon rule and it says to
1443 * canonicalize the old way, do that now, as we used to.
1445 ret = _krb5_get_name_canon_rules(context, &rules);
1446 if (ret) {
1447 _krb5_debug(context, 5, "Failed to get name canon rules: ret = %d",
1448 ret);
1449 free(remote_host);
1450 return ret;
1452 if (rules[0].type == KRB5_NCRT_NSS &&
1453 rules[1].type == KRB5_NCRT_BOGUS) {
1454 _krb5_debug(context, 5, "Using nss for name canon immediately");
1455 ret = krb5_sname_to_principal_old(context, rules[0].realm,
1456 remote_host, sname,
1457 KRB5_NT_SRV_HST, ret_princ);
1458 free(remote_host);
1459 return ret;
1463 /* Remove trailing dots */
1464 if (remote_host[0]) {
1465 for (cp = remote_host + strlen(remote_host)-1;
1466 *cp == '.' && cp > remote_host;
1467 cp--) {
1468 *cp = '\0';
1472 realm = ""; /* "Referral realm" */
1474 ret = krb5_build_principal(context, ret_princ, strlen(realm),
1475 realm, sname, remote_host,
1476 (char *)0);
1478 if (ret == 0 && type == KRB5_NT_SRV_HST) {
1480 * Hostname canonicalization is done elsewhere (in
1481 * krb5_get_credentials() and krb5_kt_get_entry()).
1483 * We overload the name type to indicate to those functions that
1484 * this principal name requires canonicalization.
1486 * We can't use the empty realm to denote the need to
1487 * canonicalize the hostname too: it would mean that users who
1488 * want to assert knowledge of a service's realm must also know
1489 * the canonical hostname, but in practice they don't.
1491 (*ret_princ)->name.name_type = KRB5_NT_SRV_HST_NEEDS_CANON;
1493 _krb5_debug(context, 5, "Building a delayed canon principal for %s/%s@",
1494 sname, remote_host);
1497 free(remote_host);
1498 return ret;
1501 static void
1502 tolower_str(char *s)
1504 for (; *s != '\0'; s++) {
1505 if (isupper(*s))
1506 *s = tolower_ascii(*s);
1510 static krb5_error_code
1511 rule_parse_token(krb5_context context, krb5_name_canon_rule rule,
1512 const char *tok)
1514 long int n;
1515 int needs_type = rule->type == KRB5_NCRT_BOGUS;
1518 * Rules consist of a sequence of tokens, some of which indicate
1519 * what type of rule the rule is, and some of which set rule options
1520 * or ancilliary data. Last rule type token wins.
1523 /* Rule type tokens: */
1524 if (needs_type && strcmp(tok, "as-is") == 0) {
1525 rule->type = KRB5_NCRT_AS_IS;
1526 } else if (needs_type && strcmp(tok, "qualify") == 0) {
1527 rule->type = KRB5_NCRT_QUALIFY;
1528 } else if (needs_type && strcmp(tok, "nss") == 0) {
1529 rule->type = KRB5_NCRT_NSS;
1530 /* Rule options: */
1531 } else if (strcmp(tok, "use_fast") == 0) {
1532 rule->options |= KRB5_NCRO_USE_FAST;
1533 } else if (strcmp(tok, "use_dnssec") == 0) {
1534 rule->options |= KRB5_NCRO_USE_DNSSEC;
1535 } else if (strcmp(tok, "ccache_only") == 0) {
1536 rule->options |= KRB5_NCRO_GC_ONLY;
1537 } else if (strcmp(tok, "no_referrals") == 0) {
1538 rule->options |= KRB5_NCRO_NO_REFERRALS;
1539 } else if (strcmp(tok, "use_referrals") == 0) {
1540 rule->options &= ~KRB5_NCRO_NO_REFERRALS;
1541 if (rule->realm == NULL) {
1542 rule->realm = strdup("");
1543 if (rule->realm == NULL)
1544 return krb5_enomem(context);
1546 } else if (strcmp(tok, "lookup_realm") == 0) {
1547 rule->options |= KRB5_NCRO_LOOKUP_REALM;
1548 free(rule->realm);
1549 rule->realm = NULL;
1550 /* Rule ancilliary data: */
1551 } else if (strncmp(tok, "domain=", strlen("domain=")) == 0) {
1552 free(rule->domain);
1553 rule->domain = strdup(tok + strlen("domain="));
1554 if (rule->domain == NULL)
1555 return krb5_enomem(context);
1556 tolower_str(rule->domain);
1557 } else if (strncmp(tok, "realm=", strlen("realm=")) == 0) {
1558 free(rule->realm);
1559 rule->realm = strdup(tok + strlen("realm="));
1560 if (rule->realm == NULL)
1561 return krb5_enomem(context);
1562 } else if (strncmp(tok, "match_domain=", strlen("match_domain=")) == 0) {
1563 free(rule->match_domain);
1564 rule->match_domain = strdup(tok + strlen("match_domain="));
1565 if (rule->match_domain == NULL)
1566 return krb5_enomem(context);
1567 tolower_str(rule->match_domain);
1568 } else if (strncmp(tok, "match_realm=", strlen("match_realm=")) == 0) {
1569 free(rule->match_realm);
1570 rule->match_realm = strdup(tok + strlen("match_realm="));
1571 if (rule->match_realm == NULL)
1572 return krb5_enomem(context);
1573 } else if (strncmp(tok, "mindots=", strlen("mindots=")) == 0) {
1574 errno = 0;
1575 n = strtol(tok + strlen("mindots="), NULL, 10);
1576 if (errno == 0 && n > 0 && n <= MAXDOTS)
1577 rule->mindots = n;
1578 } else if (strncmp(tok, "maxdots=", strlen("maxdots=")) == 0) {
1579 errno = 0;
1580 n = strtol(tok + strlen("maxdots="), NULL, 10);
1581 if (errno == 0 && n > 0 && n <= MAXDOTS)
1582 rule->maxdots = n;
1583 } else if (strncmp(tok, "order=", strlen("order=")) == 0) {
1584 errno = 0;
1585 n = strtol(tok + strlen("order="), NULL, 10);
1586 if (errno == 0 && n > 0 && n <= MAXORDER)
1587 rule->explicit_order = n;
1588 } else {
1589 _krb5_debug(context, 5,
1590 "Unrecognized name canonicalization rule token %s", tok);
1591 return EINVAL;
1593 return 0;
1596 static int
1597 rule_cmp(const void *a, const void *b)
1599 krb5_const_name_canon_rule left = a;
1600 krb5_const_name_canon_rule right = b;
1602 if (left->type == KRB5_NCRT_BOGUS &&
1603 right->type == KRB5_NCRT_BOGUS)
1604 return 0;
1605 if (left->type == KRB5_NCRT_BOGUS)
1606 return 1;
1607 if (right->type == KRB5_NCRT_BOGUS)
1608 return -1;
1609 if (left->explicit_order < right->explicit_order)
1610 return -1;
1611 if (left->explicit_order > right->explicit_order)
1612 return 1;
1613 return left->order - right->order;
1616 static krb5_error_code
1617 parse_name_canon_rules(krb5_context context, char **rulestrs,
1618 krb5_name_canon_rule *rules)
1620 krb5_error_code ret;
1621 char *tok;
1622 char *cp;
1623 char **cpp;
1624 size_t n;
1625 size_t i, k;
1626 int do_sort = 0;
1627 krb5_name_canon_rule r;
1629 *rules = NULL;
1631 for (n =0, cpp = rulestrs; cpp != NULL && *cpp != NULL; cpp++)
1632 n++;
1634 n += 2; /* Always at least one rule; two for the default case */
1636 if ((r = calloc(n, sizeof (*r))) == NULL)
1637 return krb5_enomem(context);
1639 for (k = 0; k < n; k++) {
1640 r[k].type = KRB5_NCRT_BOGUS;
1641 r[k].match_domain = NULL;
1642 r[k].match_realm = NULL;
1643 r[k].domain = NULL;
1644 r[k].realm = NULL;
1647 for (i = 0, k = 0; i < n && rulestrs != NULL && rulestrs[i] != NULL; i++) {
1648 cp = rulestrs[i];
1649 r[k].explicit_order = MAXORDER; /* mark order, see below */
1650 r[k].maxdots = MAXDOTS;
1651 r[k].order = k; /* default order */
1653 /* Tokenize and parse value */
1654 do {
1655 tok = cp;
1656 cp = strchr(cp, ':'); /* XXX use strtok_r() */
1657 if (cp)
1658 *cp++ = '\0'; /* delimit token */
1659 ret = rule_parse_token(context, &r[k], tok);
1660 if (ret == EINVAL) {
1661 r[k].type = KRB5_NCRT_BOGUS;
1662 break;
1664 if (ret) {
1665 _krb5_free_name_canon_rules(context, r);
1666 return ret;
1668 } while (cp && *cp);
1669 if (r[k].explicit_order != MAXORDER)
1670 do_sort = 1;
1672 /* Validate parsed rule */
1673 if (r[k].type == KRB5_NCRT_BOGUS ||
1674 (r[k].type == KRB5_NCRT_QUALIFY && !r[k].domain) ||
1675 (r[k].type == KRB5_NCRT_NSS && r[k].domain)) {
1676 /* Invalid rule; mark it so and clean up */
1677 r[k].type = KRB5_NCRT_BOGUS;
1678 free(r[k].match_domain);
1679 free(r[k].match_realm);
1680 free(r[k].domain);
1681 free(r[k].realm);
1682 r[k].realm = NULL;
1683 r[k].domain = NULL;
1684 r[k].match_domain = NULL;
1685 r[k].match_realm = NULL;
1686 _krb5_debug(context, 5,
1687 "Ignoring invalid name canonicalization rule %lu",
1688 (unsigned long)i);
1689 continue;
1691 k++; /* good rule */
1694 if (do_sort) {
1696 * Note that we make make this a stable sort by using appareance
1697 * and explicit order.
1699 qsort(r, n, sizeof(r[0]), rule_cmp);
1702 if (r[0].type == KRB5_NCRT_BOGUS) {
1703 /* No rules, or no valid rules */
1704 r[0].type = KRB5_NCRT_NSS;
1707 *rules = r;
1708 return 0; /* We don't communicate bad rule errors here */
1712 * This exists only because the hostname canonicalization behavior in Heimdal
1713 * (and other implementations of Kerberos) has been to use getaddrinfo(),
1714 * unsafe though it is, for ages. We can't fix it in one day.
1716 static void
1717 make_rules_safe(krb5_context context, krb5_name_canon_rule rules)
1720 * If the only rule were to use the name service (getaddrinfo()) then we're
1721 * bound to fail. We could try to convert that rule to an as-is rule, but
1722 * when we do get a validating resolver we'd be unhappy that we did such a
1723 * conversion. Better let the user get failures and make them think about
1724 * their naming rules.
1726 if (rules == NULL)
1727 return;
1728 for (; rules[0].type != KRB5_NCRT_BOGUS; rules++) {
1729 if (rules->type == KRB5_NCRT_NSS)
1730 rules->options |= KRB5_NCRO_USE_DNSSEC;
1731 else
1732 rules->options |= KRB5_NCRO_USE_FAST;
1737 * This function returns an array of host-based service name
1738 * canonicalization rules. The array of rules is organized as a list.
1739 * See the definition of krb5_name_canon_rule.
1741 * @param context A Kerberos context.
1742 * @param rules Output location for array of rules.
1744 KRB5_LIB_FUNCTION krb5_error_code
1745 _krb5_get_name_canon_rules(krb5_context context, krb5_name_canon_rule *rules)
1747 krb5_error_code ret;
1748 char **values = NULL;
1750 *rules = context->name_canon_rules;
1751 if (*rules != NULL)
1752 return 0;
1754 values = krb5_config_get_strings(context, NULL,
1755 "libdefaults", "name_canon_rules", NULL);
1756 ret = parse_name_canon_rules(context, values, rules);
1757 krb5_config_free_strings(values);
1758 if (ret)
1759 return ret;
1761 if (krb5_config_get_bool_default(context, NULL, FALSE,
1762 "libdefaults", "safe_name_canon", NULL))
1763 make_rules_safe(context, *rules);
1765 heim_assert((*rules)[0].type != KRB5_NCRT_BOGUS,
1766 "internal error in parsing principal name "
1767 "canonicalization rules");
1769 /* Memoize */
1770 context->name_canon_rules = *rules;
1772 return 0;
1775 static krb5_error_code
1776 get_host_realm(krb5_context context, const char *hostname, char **realm)
1778 krb5_error_code ret;
1779 char **hrealms = NULL;
1781 *realm = NULL;
1782 ret = krb5_get_host_realm(context, hostname, &hrealms);
1783 if (ret)
1784 return ret;
1785 if (hrealms == NULL)
1786 return KRB5_ERR_HOST_REALM_UNKNOWN; /* krb5_set_error() already done */
1787 if (hrealms[0] == NULL) {
1788 krb5_free_host_realm(context, hrealms);
1789 return KRB5_ERR_HOST_REALM_UNKNOWN; /* krb5_set_error() already done */
1791 *realm = strdup(hrealms[0]);
1792 krb5_free_host_realm(context, hrealms);
1793 if (*realm == NULL)
1794 return krb5_enomem(context);
1795 return 0;
1798 static int
1799 is_domain_suffix(const char *domain, const char *suffix)
1801 size_t dlen = strlen(domain);
1802 size_t slen = strlen(suffix);
1804 if (dlen < slen + 2)
1805 return 0;
1807 if (strcasecmp(domain + (dlen - slen), suffix) != 0)
1808 return 0;
1810 if (domain[(dlen - slen) - 1] != '.')
1811 return 0;
1812 return 1;
1816 * Applies a name canonicalization rule to a principal.
1818 * Returns zero and no out_princ if the rule does not match.
1819 * Returns zero and an out_princ if the rule does match.
1821 static krb5_error_code
1822 apply_name_canon_rule(krb5_context context, krb5_name_canon_rule rules,
1823 size_t rule_idx, krb5_const_principal in_princ,
1824 krb5_principal *out_princ,
1825 krb5_name_canon_rule_options *rule_opts)
1827 krb5_name_canon_rule rule = &rules[rule_idx];
1828 krb5_error_code ret;
1829 unsigned int ndots = 0;
1830 krb5_principal nss = NULL;
1831 const char *sname = NULL;
1832 const char *orig_hostname = NULL;
1833 const char *new_hostname = NULL;
1834 const char *new_realm = NULL;
1835 const char *port = "";
1836 const char *cp;
1837 char *hostname_sans_port = NULL;
1838 char *hostname_with_port = NULL;
1839 char *tmp_hostname = NULL;
1840 char *tmp_realm = NULL;
1842 *out_princ = NULL; /* Signal no match */
1844 if (rule_opts != NULL)
1845 *rule_opts = rule->options;
1847 if (rule->type == KRB5_NCRT_BOGUS)
1848 return 0; /* rule doesn't apply */
1850 sname = krb5_principal_get_comp_string(context, in_princ, 0);
1851 orig_hostname = krb5_principal_get_comp_string(context, in_princ, 1);
1854 * Some apps want to use the very non-standard svc/hostname:port@REALM
1855 * form. We do our best to support that here :(
1857 port = strchr(orig_hostname, ':');
1858 if (port != NULL) {
1859 hostname_sans_port = strndup(orig_hostname, port - orig_hostname);
1860 if (hostname_sans_port == NULL)
1861 return krb5_enomem(context);
1862 orig_hostname = hostname_sans_port;
1865 _krb5_debug(context, 5, N_("Applying a name rule (type %d) to %s", ""),
1866 rule->type, orig_hostname);
1868 if (rule->mindots > 0 || rule->maxdots > 0) {
1869 for (cp = strchr(orig_hostname, '.'); cp && *cp; cp = strchr(cp + 1, '.'))
1870 ndots++;
1872 if (rule->mindots > 0 && ndots < rule->mindots)
1873 return 0;
1874 if (ndots > rule->maxdots)
1875 return 0;
1877 if (rule->match_domain != NULL &&
1878 !is_domain_suffix(orig_hostname, rule->match_domain))
1879 return 0;
1881 if (rule->match_realm != NULL &&
1882 strcmp(rule->match_realm, in_princ->realm) != 0)
1883 return 0;
1885 new_realm = rule->realm;
1886 switch (rule->type) {
1887 case KRB5_NCRT_AS_IS:
1888 break;
1890 case KRB5_NCRT_QUALIFY:
1891 heim_assert(rule->domain != NULL,
1892 "missing domain for qualify name canon rule");
1893 if (asprintf(&tmp_hostname, "%s.%s", orig_hostname,
1894 rule->domain) == -1 || tmp_hostname == NULL) {
1895 ret = krb5_enomem(context);
1896 goto out;
1898 new_hostname = tmp_hostname;
1899 break;
1901 case KRB5_NCRT_NSS:
1902 if ((rule->options & KRB5_NCRO_USE_DNSSEC)) {
1903 ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1904 krb5_set_error_message(context, ret,
1905 "Secure hostname resolution not supported");
1906 goto out;
1908 _krb5_debug(context, 5, "Using name service lookups");
1909 ret = krb5_sname_to_principal_old(context, rule->realm,
1910 orig_hostname, sname,
1911 KRB5_NT_SRV_HST,
1912 &nss);
1913 if (rules[rule_idx + 1].type != KRB5_NCRT_BOGUS &&
1914 (ret == KRB5_ERR_BAD_HOSTNAME ||
1915 ret == KRB5_ERR_HOST_REALM_UNKNOWN)) {
1917 * Bad hostname / realm unknown -> rule inapplicable if
1918 * there's more rules. If it's the last rule then we want
1919 * to return all errors from krb5_sname_to_principal_old()
1920 * here.
1922 ret = 0;
1923 goto out;
1925 if (ret)
1926 goto out;
1928 new_hostname = krb5_principal_get_comp_string(context, nss, 1);
1929 new_realm = krb5_principal_get_realm(context, nss);
1930 break;
1932 default:
1933 /* Can't happen */
1934 ret = 0;
1935 goto out;
1939 * This rule applies.
1941 * Copy in_princ and mutate the copy per the matched rule.
1943 * This way we apply to principals with two or more components, such as
1944 * domain-based names.
1946 ret = krb5_copy_principal(context, in_princ, out_princ);
1947 if (ret)
1948 goto out;
1950 if (new_realm == NULL && (rule->options & KRB5_NCRO_LOOKUP_REALM) != 0) {
1951 ret = get_host_realm(context, new_hostname, &tmp_realm);
1952 if (ret)
1953 goto out;
1954 new_realm = tmp_realm;
1957 /* If we stripped off a :port, add it back in */
1958 if (port != NULL && new_hostname != NULL) {
1959 if (asprintf(&hostname_with_port, "%s%s", new_hostname, port) == -1 ||
1960 hostname_with_port == NULL) {
1961 ret = krb5_enomem(context);
1962 goto out;
1964 new_hostname = hostname_with_port;
1967 if (new_realm != NULL)
1968 krb5_principal_set_realm(context, *out_princ, new_realm);
1969 if (new_hostname != NULL)
1970 krb5_principal_set_comp_string(context, *out_princ, 1, new_hostname);
1971 if (princ_type(*out_princ) == KRB5_NT_SRV_HST_NEEDS_CANON)
1972 princ_type(*out_princ) = KRB5_NT_SRV_HST;
1974 /* Trace rule application */
1976 krb5_error_code ret2;
1977 char *unparsed;
1979 ret2 = krb5_unparse_name(context, *out_princ, &unparsed);
1980 if (ret2) {
1981 _krb5_debug(context, 5,
1982 N_("Couldn't unparse canonicalized princicpal (%d)",
1983 ""),
1984 ret);
1985 } else {
1986 _krb5_debug(context, 5,
1987 N_("Name canon rule application yields %s", ""),
1988 unparsed);
1989 free(unparsed);
1993 out:
1994 free(hostname_sans_port);
1995 free(hostname_with_port);
1996 free(tmp_hostname);
1997 free(tmp_realm);
1998 krb5_free_principal(context, nss);
1999 if (ret)
2000 krb5_set_error_message(context, ret,
2001 N_("Name canon rule application failed", ""));
2002 return ret;
2006 * Free name canonicalization rules
2008 KRB5_LIB_FUNCTION void
2009 _krb5_free_name_canon_rules(krb5_context context, krb5_name_canon_rule rules)
2011 size_t k;
2013 if (rules == NULL)
2014 return;
2016 for (k = 0; rules[k].type != KRB5_NCRT_BOGUS; k++) {
2017 free(rules[k].match_domain);
2018 free(rules[k].match_realm);
2019 free(rules[k].domain);
2020 free(rules[k].realm);
2022 free(rules);
2025 struct krb5_name_canon_iterator_data {
2026 krb5_name_canon_rule rules;
2027 krb5_const_principal in_princ; /* given princ */
2028 krb5_const_principal out_princ; /* princ to be output */
2029 krb5_principal tmp_princ; /* to be freed */
2030 int is_trivial; /* no canon to be done */
2031 int done; /* no more rules to be applied */
2032 size_t cursor; /* current/next rule */
2036 * Initialize name canonicalization iterator.
2038 * @param context Kerberos context
2039 * @param in_princ principal name to be canonicalized OR
2040 * @param iter output iterator object
2042 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
2043 krb5_name_canon_iterator_start(krb5_context context,
2044 krb5_const_principal in_princ,
2045 krb5_name_canon_iterator *iter)
2047 krb5_error_code ret;
2048 krb5_name_canon_iterator state;
2050 *iter = NULL;
2052 state = calloc(1, sizeof (*state));
2053 if (state == NULL)
2054 return krb5_enomem(context);
2055 state->in_princ = in_princ;
2057 if (princ_type(state->in_princ) == KRB5_NT_SRV_HST_NEEDS_CANON) {
2058 ret = _krb5_get_name_canon_rules(context, &state->rules);
2059 if (ret)
2060 goto out;
2061 } else {
2062 /* Name needs no canon -> trivial iterator: in_princ is canonical */
2063 state->is_trivial = 1;
2066 *iter = state;
2067 return 0;
2069 out:
2070 krb5_free_name_canon_iterator(context, state);
2071 return krb5_enomem(context);
2075 * Helper for name canon iteration.
2077 static krb5_error_code
2078 name_canon_iterate(krb5_context context,
2079 krb5_name_canon_iterator *iter,
2080 krb5_name_canon_rule_options *rule_opts)
2082 krb5_error_code ret;
2083 krb5_name_canon_iterator state = *iter;
2085 if (rule_opts)
2086 *rule_opts = 0;
2088 if (state == NULL)
2089 return 0;
2091 if (state->done) {
2092 krb5_free_name_canon_iterator(context, state);
2093 *iter = NULL;
2094 return 0;
2097 if (state->is_trivial && !state->done) {
2098 state->out_princ = state->in_princ;
2099 state->done = 1;
2100 return 0;
2103 heim_assert(state->rules != NULL &&
2104 state->rules[state->cursor].type != KRB5_NCRT_BOGUS,
2105 "Internal error during name canonicalization");
2107 do {
2108 krb5_free_principal(context, state->tmp_princ);
2109 ret = apply_name_canon_rule(context, state->rules, state->cursor,
2110 state->in_princ, &state->tmp_princ, rule_opts);
2111 if (ret) {
2112 krb5_free_name_canon_iterator(context, state);
2113 *iter = NULL;
2114 return ret;
2116 state->cursor++;
2117 } while (state->tmp_princ == NULL &&
2118 state->rules[state->cursor].type != KRB5_NCRT_BOGUS);
2120 if (state->rules[state->cursor].type == KRB5_NCRT_BOGUS)
2121 state->done = 1;
2123 state->out_princ = state->tmp_princ;
2124 if (state->tmp_princ == NULL) {
2125 krb5_free_name_canon_iterator(context, state);
2126 *iter = NULL;
2127 return 0;
2129 return 0;
2133 * Iteratively apply name canon rules, outputing a principal and rule
2134 * options each time. Iteration completes when the @iter is NULL on
2135 * return or when an error is returned. Callers must free the iterator
2136 * if they abandon it mid-way.
2138 * @param context Kerberos context
2139 * @param iter name canon rule iterator (input/output)
2140 * @param try_princ output principal name
2141 * @param rule_opts output rule options
2143 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
2144 krb5_name_canon_iterate(krb5_context context,
2145 krb5_name_canon_iterator *iter,
2146 krb5_const_principal *try_princ,
2147 krb5_name_canon_rule_options *rule_opts)
2149 krb5_error_code ret;
2151 *try_princ = NULL;
2153 ret = name_canon_iterate(context, iter, rule_opts);
2154 if (*iter)
2155 *try_princ = (*iter)->out_princ;
2156 return ret;
2160 * Free a name canonicalization rule iterator.
2162 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
2163 krb5_free_name_canon_iterator(krb5_context context,
2164 krb5_name_canon_iterator iter)
2166 if (iter == NULL)
2167 return;
2168 if (iter->tmp_princ)
2169 krb5_free_principal(context, iter->tmp_princ);
2170 free(iter);