lib/krb5: unparse_name_fixed ERANGE if zero buffer len
[heimdal.git] / lib / krb5 / principal.c
blobf604bbba179b41d992ebe45a58e2b912c7179bda
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 (name == NULL) {
461 krb5_set_error_message(context, EINVAL,
462 N_("Invalid name buffer, "
463 "can't unparse", ""));
464 return EINVAL;
467 if (len == 0) {
468 krb5_set_error_message(context, ERANGE,
469 N_("Invalid name buffer length, "
470 "can't unparse", ""));
471 return ERANGE;
474 name[0] = '\0';
476 if (!no_realm && princ_realm(principal) == NULL) {
477 krb5_set_error_message(context, ERANGE,
478 N_("Realm missing from principal, "
479 "can't unparse", ""));
480 return ERANGE;
483 for(i = 0; i < princ_num_comp(principal); i++){
484 if(i)
485 add_char(name, idx, len, '/');
486 idx = quote_string(princ_ncomp(principal, i), name, idx, len, display);
487 if(idx == len) {
488 krb5_set_error_message(context, ERANGE,
489 N_("Out of space printing principal", ""));
490 return ERANGE;
493 /* add realm if different from default realm */
494 if(short_form && !no_realm) {
495 krb5_realm r;
496 krb5_error_code ret;
497 ret = krb5_get_default_realm(context, &r);
498 if(ret)
499 return ret;
500 if(strcmp(princ_realm(principal), r) != 0)
501 short_form = 0;
502 krb5_free_default_realm(context, r);
504 if(!short_form && !no_realm) {
505 add_char(name, idx, len, '@');
506 idx = quote_string(princ_realm(principal), name, idx, len, display);
507 if(idx == len) {
508 krb5_set_error_message(context, ERANGE,
509 N_("Out of space printing "
510 "realm of principal", ""));
511 return ERANGE;
514 return 0;
518 * Unparse the principal name to a fixed buffer
520 * @param context A Kerberos context.
521 * @param principal principal to unparse
522 * @param name buffer to write name to
523 * @param len length of buffer
525 * @return An krb5 error code, see krb5_get_error_message().
527 * @ingroup krb5_principal
530 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
531 krb5_unparse_name_fixed(krb5_context context,
532 krb5_const_principal principal,
533 char *name,
534 size_t len)
536 return unparse_name_fixed(context, principal, name, len, 0);
540 * Unparse the principal name to a fixed buffer. The realm is skipped
541 * if its a default realm.
543 * @param context A Kerberos context.
544 * @param principal principal to unparse
545 * @param name buffer to write name to
546 * @param len length of buffer
548 * @return An krb5 error code, see krb5_get_error_message().
550 * @ingroup krb5_principal
553 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
554 krb5_unparse_name_fixed_short(krb5_context context,
555 krb5_const_principal principal,
556 char *name,
557 size_t len)
559 return unparse_name_fixed(context, principal, name, len,
560 KRB5_PRINCIPAL_UNPARSE_SHORT);
564 * Unparse the principal name with unparse flags to a fixed buffer.
566 * @param context A Kerberos context.
567 * @param principal principal to unparse
568 * @param flags unparse flags
569 * @param name buffer to write name to
570 * @param len length of buffer
572 * @return An krb5 error code, see krb5_get_error_message().
574 * @ingroup krb5_principal
577 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
578 krb5_unparse_name_fixed_flags(krb5_context context,
579 krb5_const_principal principal,
580 int flags,
581 char *name,
582 size_t len)
584 return unparse_name_fixed(context, principal, name, len, flags);
587 static krb5_error_code
588 unparse_name(krb5_context context,
589 krb5_const_principal principal,
590 char **name,
591 int flags)
593 size_t len = 0, plen;
594 size_t i;
595 krb5_error_code ret;
596 /* count length */
597 if (princ_realm(principal)) {
598 plen = strlen(princ_realm(principal));
600 if(strcspn(princ_realm(principal), quotable_chars) == plen)
601 len += plen;
602 else
603 len += 2*plen;
604 len++; /* '@' */
606 for(i = 0; i < princ_num_comp(principal); i++){
607 plen = strlen(princ_ncomp(principal, i));
608 if(strcspn(princ_ncomp(principal, i), quotable_chars) == plen)
609 len += plen;
610 else
611 len += 2*plen;
612 len++;
614 len++; /* '\0' */
615 *name = malloc(len);
616 if(*name == NULL)
617 return krb5_enomem(context);
618 ret = unparse_name_fixed(context, principal, *name, len, flags);
619 if(ret) {
620 free(*name);
621 *name = NULL;
623 return ret;
627 * Unparse the Kerberos name into a string
629 * @param context Kerberos 5 context
630 * @param principal principal to query
631 * @param name resulting string, free with krb5_xfree()
633 * @return An krb5 error code, see krb5_get_error_message().
635 * @ingroup krb5_principal
638 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
639 krb5_unparse_name(krb5_context context,
640 krb5_const_principal principal,
641 char **name)
643 return unparse_name(context, principal, name, 0);
647 * Unparse the Kerberos name into a string
649 * @param context Kerberos 5 context
650 * @param principal principal to query
651 * @param flags flag to determine the behavior
652 * @param name resulting string, free with krb5_xfree()
654 * @return An krb5 error code, see krb5_get_error_message().
656 * @ingroup krb5_principal
659 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
660 krb5_unparse_name_flags(krb5_context context,
661 krb5_const_principal principal,
662 int flags,
663 char **name)
665 return unparse_name(context, principal, name, flags);
669 * Unparse the principal name to a allocated buffer. The realm is
670 * skipped if its a default realm.
672 * @param context A Kerberos context.
673 * @param principal principal to unparse
674 * @param name returned buffer, free with krb5_xfree()
676 * @return An krb5 error code, see krb5_get_error_message().
678 * @ingroup krb5_principal
681 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
682 krb5_unparse_name_short(krb5_context context,
683 krb5_const_principal principal,
684 char **name)
686 return unparse_name(context, principal, name, KRB5_PRINCIPAL_UNPARSE_SHORT);
690 * Set a new realm for a principal, and as a side-effect free the
691 * previous realm.
693 * @param context A Kerberos context.
694 * @param principal principal set the realm for
695 * @param realm the new realm to set
697 * @return An krb5 error code, see krb5_get_error_message().
699 * @ingroup krb5_principal
702 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
703 krb5_principal_set_realm(krb5_context context,
704 krb5_principal principal,
705 krb5_const_realm realm)
707 if (princ_realm(principal))
708 free(princ_realm(principal));
710 if (realm == NULL)
711 princ_realm(principal) = NULL;
712 else if ((princ_realm(principal) = strdup(realm)) == NULL)
713 return krb5_enomem(context);
714 return 0;
717 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
718 krb5_principal_set_comp_string(krb5_context context,
719 krb5_principal principal,
720 unsigned int k,
721 const char *component)
723 char *s;
724 size_t i;
726 for (i = princ_num_comp(principal); i <= k; i++)
727 append_component(context, principal, "", 0);
728 s = strdup(component);
729 if (s == NULL)
730 return krb5_enomem(context);
731 free(princ_ncomp(principal, k));
732 princ_ncomp(principal, k) = s;
733 return 0;
736 #ifndef HEIMDAL_SMALLER
738 * Build a principal using vararg style building
740 * @param context A Kerberos context.
741 * @param principal returned principal
742 * @param rlen length of realm
743 * @param realm realm name
744 * @param ... a list of components ended with NULL.
746 * @return An krb5 error code, see krb5_get_error_message().
748 * @ingroup krb5_principal
751 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
752 krb5_build_principal(krb5_context context,
753 krb5_principal *principal,
754 int rlen,
755 krb5_const_realm realm,
756 ...)
758 krb5_error_code ret;
759 va_list ap;
760 va_start(ap, realm);
761 ret = krb5_build_principal_va(context, principal, rlen, realm, ap);
762 va_end(ap);
763 return ret;
765 #endif
768 * Build a principal using vararg style building
770 * @param context A Kerberos context.
771 * @param principal returned principal
772 * @param realm realm name
773 * @param ... a list of components ended with NULL.
775 * @return An krb5 error code, see krb5_get_error_message().
777 * @ingroup krb5_principal
780 /* coverity[+alloc : arg-*1] */
781 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
782 krb5_make_principal(krb5_context context,
783 krb5_principal *principal,
784 krb5_const_realm realm,
785 ...)
787 krb5_error_code ret;
788 krb5_realm r = NULL;
789 va_list ap;
790 if(realm == NULL) {
791 ret = krb5_get_default_realm(context, &r);
792 if(ret)
793 return ret;
794 realm = r;
796 va_start(ap, realm);
797 ret = krb5_build_principal_va(context, principal, strlen(realm), realm, ap);
798 va_end(ap);
799 if(r)
800 krb5_free_default_realm(context, r);
801 return ret;
804 static krb5_error_code
805 append_component(krb5_context context, krb5_principal p,
806 const char *comp,
807 size_t comp_len)
809 heim_general_string *tmp;
810 size_t len = princ_num_comp(p);
812 tmp = realloc(princ_comp(p), (len + 1) * sizeof(*tmp));
813 if(tmp == NULL)
814 return krb5_enomem(context);
815 princ_comp(p) = tmp;
816 princ_ncomp(p, len) = malloc(comp_len + 1);
817 if (princ_ncomp(p, len) == NULL)
818 return krb5_enomem(context);
819 memcpy (princ_ncomp(p, len), comp, comp_len);
820 princ_ncomp(p, len)[comp_len] = '\0';
821 princ_num_comp(p)++;
822 return 0;
825 static krb5_error_code
826 va_ext_princ(krb5_context context, krb5_principal p, va_list ap)
828 krb5_error_code ret = 0;
830 while (1){
831 const char *s;
832 int len;
834 if ((len = va_arg(ap, int)) == 0)
835 break;
836 s = va_arg(ap, const char*);
837 if ((ret = append_component(context, p, s, len)) != 0)
838 break;
840 return ret;
843 static krb5_error_code
844 va_princ(krb5_context context, krb5_principal p, va_list ap)
846 krb5_error_code ret = 0;
848 while (1){
849 const char *s;
851 if ((s = va_arg(ap, const char*)) == NULL)
852 break;
853 if ((ret = append_component(context, p, s, strlen(s))) != 0)
854 break;
856 return ret;
859 static krb5_error_code
860 build_principal(krb5_context context,
861 krb5_principal *principal,
862 int rlen,
863 krb5_const_realm realm,
864 krb5_error_code (*func)(krb5_context, krb5_principal, va_list),
865 va_list ap)
867 krb5_error_code ret;
868 krb5_principal p;
870 *principal = NULL;
871 p = calloc(1, sizeof(*p));
872 if (p == NULL)
873 return krb5_enomem(context);
875 princ_realm(p) = strdup(realm);
876 if (p->realm == NULL) {
877 free(p);
878 return krb5_enomem(context);
881 ret = func(context, p, ap);
882 if (ret == 0) {
883 *principal = p;
884 set_default_princ_type(p, KRB5_NT_PRINCIPAL);
885 } else
886 krb5_free_principal(context, p);
887 return ret;
890 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
891 krb5_build_principal_va(krb5_context context,
892 krb5_principal *principal,
893 int rlen,
894 krb5_const_realm realm,
895 va_list ap)
897 return build_principal(context, principal, rlen, realm, va_princ, ap);
900 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
901 krb5_build_principal_va_ext(krb5_context context,
902 krb5_principal *principal,
903 int rlen,
904 krb5_const_realm realm,
905 va_list ap)
907 return build_principal(context, principal, rlen, realm, va_ext_princ, ap);
911 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
912 krb5_build_principal_ext(krb5_context context,
913 krb5_principal *principal,
914 int rlen,
915 krb5_const_realm realm,
916 ...)
918 krb5_error_code ret;
919 va_list ap;
920 va_start(ap, realm);
921 ret = krb5_build_principal_va_ext(context, principal, rlen, realm, ap);
922 va_end(ap);
923 return ret;
927 * Copy a principal
929 * @param context A Kerberos context.
930 * @param inprinc principal to copy
931 * @param outprinc copied principal, free with krb5_free_principal()
933 * @return An krb5 error code, see krb5_get_error_message().
935 * @ingroup krb5_principal
939 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
940 krb5_copy_principal(krb5_context context,
941 krb5_const_principal inprinc,
942 krb5_principal *outprinc)
944 krb5_principal p = malloc(sizeof(*p));
945 if (p == NULL)
946 return krb5_enomem(context);
947 if(copy_Principal(inprinc, p)) {
948 free(p);
949 return krb5_enomem(context);
951 *outprinc = p;
952 return 0;
956 * Return TRUE iff princ1 == princ2 (without considering the realm)
958 * @param context Kerberos 5 context
959 * @param princ1 first principal to compare
960 * @param princ2 second principal to compare
962 * @return non zero if equal, 0 if not
964 * @ingroup krb5_principal
965 * @see krb5_principal_compare()
966 * @see krb5_realm_compare()
969 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
970 krb5_principal_compare_any_realm(krb5_context context,
971 krb5_const_principal princ1,
972 krb5_const_principal princ2)
974 size_t i;
975 if(princ_num_comp(princ1) != princ_num_comp(princ2))
976 return FALSE;
977 for(i = 0; i < princ_num_comp(princ1); i++){
978 if(strcmp(princ_ncomp(princ1, i), princ_ncomp(princ2, i)) != 0)
979 return FALSE;
981 return TRUE;
984 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
985 _krb5_principal_compare_PrincipalName(krb5_context context,
986 krb5_const_principal princ1,
987 PrincipalName *princ2)
989 size_t i;
990 if (princ_num_comp(princ1) != princ2->name_string.len)
991 return FALSE;
992 for(i = 0; i < princ_num_comp(princ1); i++){
993 if(strcmp(princ_ncomp(princ1, i), princ2->name_string.val[i]) != 0)
994 return FALSE;
996 return TRUE;
1001 * Compares the two principals, including realm of the principals and returns
1002 * TRUE if they are the same and FALSE if not.
1004 * @param context Kerberos 5 context
1005 * @param princ1 first principal to compare
1006 * @param princ2 second principal to compare
1008 * @ingroup krb5_principal
1009 * @see krb5_principal_compare_any_realm()
1010 * @see krb5_realm_compare()
1014 * return TRUE iff princ1 == princ2
1017 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1018 krb5_principal_compare(krb5_context context,
1019 krb5_const_principal princ1,
1020 krb5_const_principal princ2)
1022 if (!krb5_realm_compare(context, princ1, princ2))
1023 return FALSE;
1024 return krb5_principal_compare_any_realm(context, princ1, princ2);
1028 * return TRUE iff realm(princ1) == realm(princ2)
1030 * @param context Kerberos 5 context
1031 * @param princ1 first principal to compare
1032 * @param princ2 second principal to compare
1034 * @ingroup krb5_principal
1035 * @see krb5_principal_compare_any_realm()
1036 * @see krb5_principal_compare()
1039 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1040 krb5_realm_compare(krb5_context context,
1041 krb5_const_principal princ1,
1042 krb5_const_principal princ2)
1044 return strcmp(princ_realm(princ1), princ_realm(princ2)) == 0;
1048 * return TRUE iff princ matches pattern
1050 * @ingroup krb5_principal
1053 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1054 krb5_principal_match(krb5_context context,
1055 krb5_const_principal princ,
1056 krb5_const_principal pattern)
1058 size_t i;
1059 if(princ_num_comp(princ) != princ_num_comp(pattern))
1060 return FALSE;
1061 if(fnmatch(princ_realm(pattern), princ_realm(princ), 0) != 0)
1062 return FALSE;
1063 for(i = 0; i < princ_num_comp(princ); i++){
1064 if(fnmatch(princ_ncomp(pattern, i), princ_ncomp(princ, i), 0) != 0)
1065 return FALSE;
1067 return TRUE;
1071 * This is the original krb5_sname_to_principal(), renamed to be a
1072 * helper of the new one.
1074 static krb5_error_code
1075 krb5_sname_to_principal_old(krb5_context context,
1076 const char *realm,
1077 const char *hostname,
1078 const char *sname,
1079 int32_t type,
1080 krb5_principal *ret_princ)
1082 krb5_error_code ret;
1083 char localhost[MAXHOSTNAMELEN];
1084 char **realms = NULL, *host = NULL;
1086 if(type != KRB5_NT_SRV_HST && type != KRB5_NT_UNKNOWN) {
1087 krb5_set_error_message(context, KRB5_SNAME_UNSUPP_NAMETYPE,
1088 N_("unsupported name type %d", ""),
1089 (int)type);
1090 return KRB5_SNAME_UNSUPP_NAMETYPE;
1092 if(hostname == NULL) {
1093 ret = gethostname(localhost, sizeof(localhost) - 1);
1094 if (ret != 0) {
1095 ret = errno;
1096 krb5_set_error_message(context, ret,
1097 N_("Failed to get local hostname", ""));
1098 return ret;
1100 localhost[sizeof(localhost) - 1] = '\0';
1101 hostname = localhost;
1103 if(sname == NULL)
1104 sname = "host";
1105 if(type == KRB5_NT_SRV_HST) {
1106 if (realm)
1107 ret = krb5_expand_hostname(context, hostname, &host);
1108 else
1109 ret = krb5_expand_hostname_realms(context, hostname,
1110 &host, &realms);
1111 if (ret)
1112 return ret;
1113 strlwr(host);
1114 hostname = host;
1115 if (!realm)
1116 realm = realms[0];
1117 } else if (!realm) {
1118 ret = krb5_get_host_realm(context, hostname, &realms);
1119 if(ret)
1120 return ret;
1121 realm = realms[0];
1124 ret = krb5_make_principal(context, ret_princ, realm, sname,
1125 hostname, NULL);
1126 if(host)
1127 free(host);
1128 if (realms)
1129 krb5_free_host_realm(context, realms);
1130 return ret;
1133 static const struct {
1134 const char *type;
1135 int32_t value;
1136 } nametypes[] = {
1137 { "UNKNOWN", KRB5_NT_UNKNOWN },
1138 { "PRINCIPAL", KRB5_NT_PRINCIPAL },
1139 { "SRV_INST", KRB5_NT_SRV_INST },
1140 { "SRV_HST", KRB5_NT_SRV_HST },
1141 { "SRV_XHST", KRB5_NT_SRV_XHST },
1142 { "UID", KRB5_NT_UID },
1143 { "X500_PRINCIPAL", KRB5_NT_X500_PRINCIPAL },
1144 { "SMTP_NAME", KRB5_NT_SMTP_NAME },
1145 { "ENTERPRISE_PRINCIPAL", KRB5_NT_ENTERPRISE_PRINCIPAL },
1146 { "WELLKNOWN", KRB5_NT_WELLKNOWN },
1147 { "SRV_HST_DOMAIN", KRB5_NT_SRV_HST_DOMAIN },
1148 { "ENT_PRINCIPAL_AND_ID", KRB5_NT_ENT_PRINCIPAL_AND_ID },
1149 { "MS_PRINCIPAL", KRB5_NT_MS_PRINCIPAL },
1150 { "MS_PRINCIPAL_AND_ID", KRB5_NT_MS_PRINCIPAL_AND_ID },
1151 { "SRV_HST_NEEDS_CANON", KRB5_NT_SRV_HST_NEEDS_CANON },
1152 { NULL, 0 }
1156 * Parse nametype string and return a nametype integer
1158 * @ingroup krb5_principal
1161 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1162 krb5_parse_nametype(krb5_context context, const char *str, int32_t *nametype)
1164 size_t i;
1166 for(i = 0; nametypes[i].type; i++) {
1167 if (strcasecmp(nametypes[i].type, str) == 0) {
1168 *nametype = nametypes[i].value;
1169 return 0;
1172 krb5_set_error_message(context, KRB5_PARSE_MALFORMED,
1173 N_("Failed to find name type %s", ""), str);
1174 return KRB5_PARSE_MALFORMED;
1178 * Returns true if name is Kerberos NULL name
1180 * @ingroup krb5_principal
1183 krb5_boolean KRB5_LIB_FUNCTION
1184 krb5_principal_is_null(krb5_context context, krb5_const_principal principal)
1186 if (principal->name.name_type == KRB5_NT_WELLKNOWN &&
1187 principal->name.name_string.len == 2 &&
1188 strcmp(principal->name.name_string.val[0], "WELLKNOWN") == 0 &&
1189 strcmp(principal->name.name_string.val[1], "NULL") == 0)
1190 return TRUE;
1191 return FALSE;
1194 const char _krb5_wellknown_lkdc[] = "WELLKNOWN:COM.APPLE.LKDC";
1195 static const char lkdc_prefix[] = "LKDC:";
1198 * Returns true if name is Kerberos an LKDC realm
1200 * @ingroup krb5_principal
1203 krb5_boolean KRB5_LIB_FUNCTION
1204 krb5_realm_is_lkdc(const char *realm)
1207 return strncmp(realm, lkdc_prefix, sizeof(lkdc_prefix)-1) == 0 ||
1208 strncmp(realm, _krb5_wellknown_lkdc, sizeof(_krb5_wellknown_lkdc) - 1) == 0;
1212 * Returns true if name is Kerberos an LKDC realm
1214 * @ingroup krb5_principal
1217 krb5_boolean KRB5_LIB_FUNCTION
1218 krb5_principal_is_lkdc(krb5_context context, krb5_const_principal principal)
1220 return krb5_realm_is_lkdc(principal->realm);
1224 * Returns true if name is Kerberos an LKDC realm
1226 * @ingroup krb5_principal
1229 krb5_boolean KRB5_LIB_FUNCTION
1230 krb5_principal_is_pku2u(krb5_context context, krb5_const_principal principal)
1232 return strcmp(principal->realm, KRB5_PKU2U_REALM_NAME) == 0;
1236 * Check if the cname part of the principal is a krbtgt principal
1238 * @ingroup krb5_principal
1241 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1242 krb5_principal_is_krbtgt(krb5_context context, krb5_const_principal p)
1244 return p->name.name_string.len == 2 &&
1245 strcmp(p->name.name_string.val[0], KRB5_TGS_NAME) == 0;
1249 * Returns true iff name is an WELLKNOWN:ORG.H5L.HOSTBASED-SERVICE
1251 * @ingroup krb5_principal
1254 krb5_boolean KRB5_LIB_FUNCTION
1255 krb5_principal_is_gss_hostbased_service(krb5_context context,
1256 krb5_const_principal principal)
1258 if (principal == NULL)
1259 return FALSE;
1260 if (principal->name.name_string.len != 2)
1261 return FALSE;
1262 if (strcmp(principal->name.name_string.val[1], KRB5_GSS_HOSTBASED_SERVICE_NAME) != 0)
1263 return FALSE;
1264 return TRUE;
1268 * Check if the cname part of the principal is a initial or renewed krbtgt principal
1270 * @ingroup krb5_principal
1273 krb5_boolean KRB5_LIB_FUNCTION
1274 krb5_principal_is_root_krbtgt(krb5_context context, krb5_const_principal p)
1276 return p->name.name_string.len == 2 &&
1277 strcmp(p->name.name_string.val[0], KRB5_TGS_NAME) == 0 &&
1278 strcmp(p->name.name_string.val[1], p->realm) == 0;
1282 * Returns true iff name is WELLKNOWN/ANONYMOUS
1284 * @ingroup krb5_principal
1287 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1288 krb5_principal_is_anonymous(krb5_context context,
1289 krb5_const_principal p,
1290 unsigned int flags)
1293 * Heimdal versions 7.5 and below left the name-type at KRB5_NT_PRINCIPAL
1294 * even with anonymous pkinit responses. To retain interoperability with
1295 * legacy KDCs, the name-type is not checked by the client after requesting
1296 * a fully anonymous ticket.
1298 if (!(flags & KRB5_ANON_IGNORE_NAME_TYPE) &&
1299 p->name.name_type != KRB5_NT_WELLKNOWN &&
1300 p->name.name_type != KRB5_NT_UNKNOWN)
1301 return FALSE;
1303 if (p->name.name_string.len != 2 ||
1304 strcmp(p->name.name_string.val[0], KRB5_WELLKNOWN_NAME) != 0 ||
1305 strcmp(p->name.name_string.val[1], KRB5_ANON_NAME) != 0)
1306 return FALSE;
1309 * While unauthenticated clients SHOULD get "WELLKNOWN:ANONYMOUS" as their
1310 * realm, Heimdal KDCs prior to 7.0 returned the requested realm. While
1311 * such tickets might lead *servers* to unwittingly grant access to fully
1312 * anonymous clients, trusting that the client was authenticated to the
1313 * realm in question, doing it right is the KDC's job, the client should
1314 * not refuse such a ticket.
1316 * If we ever do decide to enforce WELLKNOWN:ANONYMOUS for unauthenticated
1317 * clients, it is essential that calls that pass KRB5_ANON_MATCH_ANY still
1318 * ignore the realm, as in that case either case matches one of the two
1319 * possible conditions.
1321 if (flags & KRB5_ANON_MATCH_UNAUTHENTICATED)
1322 return TRUE;
1325 * Finally, authenticated clients that asked to be only anonymized do
1326 * legitimately expect a non-anon realm.
1328 return strcmp(p->realm, KRB5_ANON_REALM) != 0;
1332 * Returns true iff name is WELLKNOWN/FEDERATED
1334 * @ingroup krb5_principal
1337 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
1338 krb5_principal_is_federated(krb5_context context,
1339 krb5_const_principal p)
1341 if (p->name.name_type != KRB5_NT_WELLKNOWN &&
1342 p->name.name_type != KRB5_NT_UNKNOWN)
1343 return FALSE;
1345 if (p->name.name_string.len != 2 ||
1346 strcmp(p->name.name_string.val[0], KRB5_WELLKNOWN_NAME) != 0 ||
1347 strcmp(p->name.name_string.val[1], KRB5_FEDERATED_NAME) != 0)
1348 return FALSE;
1350 return TRUE;
1353 static int
1354 tolower_ascii(int c)
1356 if (c >= 'A' && c <= 'Z')
1357 return 'a' + (c - 'A');
1358 return c;
1361 typedef enum krb5_name_canon_rule_type {
1362 KRB5_NCRT_BOGUS = 0,
1363 KRB5_NCRT_AS_IS,
1364 KRB5_NCRT_QUALIFY,
1365 KRB5_NCRT_NSS
1366 } krb5_name_canon_rule_type;
1368 #ifdef UINT8_MAX
1369 #define MAXDOTS UINT8_MAX
1370 #else
1371 #define MAXDOTS (255U)
1372 #endif
1373 #ifdef UINT16_MAX
1374 #define MAXORDER UINT16_MAX
1375 #else
1376 #define MAXORDER (65535U)
1377 #endif
1379 struct krb5_name_canon_rule_data {
1380 krb5_name_canon_rule_type type;
1381 krb5_name_canon_rule_options options;
1382 uint8_t mindots; /* match this many dots or more */
1383 uint8_t maxdots; /* match no more than this many dots */
1384 uint16_t explicit_order; /* given order */
1385 uint16_t order; /* actual order */
1386 char *match_domain; /* match this stem */
1387 char *match_realm; /* match this realm */
1388 char *domain; /* qualify with this domain */
1389 char *realm; /* qualify with this realm */
1393 * Create a principal for the given service running on the given
1394 * hostname. If KRB5_NT_SRV_HST is used, the hostname is canonicalized
1395 * according the configured name canonicalization rules, with
1396 * canonicalization delayed in some cases. One rule involves DNS, which
1397 * is insecure unless DNSSEC is used, but we don't use DNSSEC-capable
1398 * resolver APIs here, so that if DNSSEC is used we wouldn't know it.
1400 * Canonicalization is immediate (not delayed) only when there is only
1401 * one canonicalization rule and that rule indicates that we should do a
1402 * host lookup by name (i.e., DNS).
1404 * @param context A Kerberos context.
1405 * @param hostname hostname to use
1406 * @param sname Service name to use
1407 * @param type name type of principal, use KRB5_NT_SRV_HST or KRB5_NT_UNKNOWN.
1408 * @param ret_princ return principal, free with krb5_free_principal().
1410 * @return An krb5 error code, see krb5_get_error_message().
1412 * @ingroup krb5_principal
1415 /* coverity[+alloc : arg-*4] */
1416 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1417 krb5_sname_to_principal(krb5_context context,
1418 const char *hostname,
1419 const char *sname,
1420 int32_t type,
1421 krb5_principal *ret_princ)
1423 char *realm, *remote_host;
1424 krb5_error_code ret;
1425 register char *cp;
1426 char localname[MAXHOSTNAMELEN];
1428 *ret_princ = NULL;
1430 if ((type != KRB5_NT_UNKNOWN) &&
1431 (type != KRB5_NT_SRV_HST))
1432 return KRB5_SNAME_UNSUPP_NAMETYPE;
1434 /* if hostname is NULL, use local hostname */
1435 if (hostname == NULL) {
1436 if (gethostname(localname, MAXHOSTNAMELEN))
1437 return errno;
1438 hostname = localname;
1441 /* if sname is NULL, use "host" */
1442 if (sname == NULL)
1443 sname = "host";
1445 remote_host = strdup(hostname);
1446 if (remote_host == NULL)
1447 return krb5_enomem(context);
1449 if (type == KRB5_NT_SRV_HST) {
1450 krb5_name_canon_rule rules;
1452 /* Lower-case the hostname, because that's the convention */
1453 for (cp = remote_host; *cp; cp++)
1454 if (isupper((int) (*cp)))
1455 *cp = tolower((int) (*cp));
1458 * If there is only one name canon rule and it says to
1459 * canonicalize the old way, do that now, as we used to.
1461 ret = _krb5_get_name_canon_rules(context, &rules);
1462 if (ret) {
1463 _krb5_debug(context, 5, "Failed to get name canon rules: ret = %d",
1464 ret);
1465 free(remote_host);
1466 return ret;
1468 if (rules[0].type == KRB5_NCRT_NSS &&
1469 rules[1].type == KRB5_NCRT_BOGUS) {
1470 _krb5_debug(context, 5, "Using nss for name canon immediately");
1471 ret = krb5_sname_to_principal_old(context, rules[0].realm,
1472 remote_host, sname,
1473 KRB5_NT_SRV_HST, ret_princ);
1474 free(remote_host);
1475 return ret;
1479 /* Remove trailing dots */
1480 if (remote_host[0]) {
1481 for (cp = remote_host + strlen(remote_host)-1;
1482 *cp == '.' && cp > remote_host;
1483 cp--) {
1484 *cp = '\0';
1488 realm = ""; /* "Referral realm" */
1490 ret = krb5_build_principal(context, ret_princ, strlen(realm),
1491 realm, sname, remote_host,
1492 (char *)0);
1494 if (ret == 0 && type == KRB5_NT_SRV_HST) {
1496 * Hostname canonicalization is done elsewhere (in
1497 * krb5_get_credentials() and krb5_kt_get_entry()).
1499 * We overload the name type to indicate to those functions that
1500 * this principal name requires canonicalization.
1502 * We can't use the empty realm to denote the need to
1503 * canonicalize the hostname too: it would mean that users who
1504 * want to assert knowledge of a service's realm must also know
1505 * the canonical hostname, but in practice they don't.
1507 (*ret_princ)->name.name_type = KRB5_NT_SRV_HST_NEEDS_CANON;
1509 _krb5_debug(context, 5, "Building a delayed canon principal for %s/%s@",
1510 sname, remote_host);
1513 free(remote_host);
1514 return ret;
1517 static void
1518 tolower_str(char *s)
1520 for (; *s != '\0'; s++) {
1521 if (isupper(*s))
1522 *s = tolower_ascii(*s);
1526 static krb5_error_code
1527 rule_parse_token(krb5_context context, krb5_name_canon_rule rule,
1528 const char *tok)
1530 long int n;
1531 int needs_type = rule->type == KRB5_NCRT_BOGUS;
1534 * Rules consist of a sequence of tokens, some of which indicate
1535 * what type of rule the rule is, and some of which set rule options
1536 * or ancilliary data. Last rule type token wins.
1539 /* Rule type tokens: */
1540 if (needs_type && strcmp(tok, "as-is") == 0) {
1541 rule->type = KRB5_NCRT_AS_IS;
1542 } else if (needs_type && strcmp(tok, "qualify") == 0) {
1543 rule->type = KRB5_NCRT_QUALIFY;
1544 } else if (needs_type && strcmp(tok, "nss") == 0) {
1545 rule->type = KRB5_NCRT_NSS;
1546 /* Rule options: */
1547 } else if (strcmp(tok, "use_fast") == 0) {
1548 rule->options |= KRB5_NCRO_USE_FAST;
1549 } else if (strcmp(tok, "use_dnssec") == 0) {
1550 rule->options |= KRB5_NCRO_USE_DNSSEC;
1551 } else if (strcmp(tok, "ccache_only") == 0) {
1552 rule->options |= KRB5_NCRO_GC_ONLY;
1553 } else if (strcmp(tok, "no_referrals") == 0) {
1554 rule->options |= KRB5_NCRO_NO_REFERRALS;
1555 } else if (strcmp(tok, "use_referrals") == 0) {
1556 rule->options &= ~KRB5_NCRO_NO_REFERRALS;
1557 if (rule->realm == NULL) {
1558 rule->realm = strdup("");
1559 if (rule->realm == NULL)
1560 return krb5_enomem(context);
1562 } else if (strcmp(tok, "lookup_realm") == 0) {
1563 rule->options |= KRB5_NCRO_LOOKUP_REALM;
1564 free(rule->realm);
1565 rule->realm = NULL;
1566 /* Rule ancilliary data: */
1567 } else if (strncmp(tok, "domain=", strlen("domain=")) == 0) {
1568 free(rule->domain);
1569 rule->domain = strdup(tok + strlen("domain="));
1570 if (rule->domain == NULL)
1571 return krb5_enomem(context);
1572 tolower_str(rule->domain);
1573 } else if (strncmp(tok, "realm=", strlen("realm=")) == 0) {
1574 free(rule->realm);
1575 rule->realm = strdup(tok + strlen("realm="));
1576 if (rule->realm == NULL)
1577 return krb5_enomem(context);
1578 } else if (strncmp(tok, "match_domain=", strlen("match_domain=")) == 0) {
1579 free(rule->match_domain);
1580 rule->match_domain = strdup(tok + strlen("match_domain="));
1581 if (rule->match_domain == NULL)
1582 return krb5_enomem(context);
1583 tolower_str(rule->match_domain);
1584 } else if (strncmp(tok, "match_realm=", strlen("match_realm=")) == 0) {
1585 free(rule->match_realm);
1586 rule->match_realm = strdup(tok + strlen("match_realm="));
1587 if (rule->match_realm == NULL)
1588 return krb5_enomem(context);
1589 } else if (strncmp(tok, "mindots=", strlen("mindots=")) == 0) {
1590 errno = 0;
1591 n = strtol(tok + strlen("mindots="), NULL, 10);
1592 if (errno == 0 && n > 0 && n <= MAXDOTS)
1593 rule->mindots = n;
1594 } else if (strncmp(tok, "maxdots=", strlen("maxdots=")) == 0) {
1595 errno = 0;
1596 n = strtol(tok + strlen("maxdots="), NULL, 10);
1597 if (errno == 0 && n > 0 && n <= MAXDOTS)
1598 rule->maxdots = n;
1599 } else if (strncmp(tok, "order=", strlen("order=")) == 0) {
1600 errno = 0;
1601 n = strtol(tok + strlen("order="), NULL, 10);
1602 if (errno == 0 && n > 0 && n <= MAXORDER)
1603 rule->explicit_order = n;
1604 } else {
1605 _krb5_debug(context, 5,
1606 "Unrecognized name canonicalization rule token %s", tok);
1607 return EINVAL;
1609 return 0;
1612 static int
1613 rule_cmp(const void *a, const void *b)
1615 krb5_const_name_canon_rule left = a;
1616 krb5_const_name_canon_rule right = b;
1618 if (left->type == KRB5_NCRT_BOGUS &&
1619 right->type == KRB5_NCRT_BOGUS)
1620 return 0;
1621 if (left->type == KRB5_NCRT_BOGUS)
1622 return 1;
1623 if (right->type == KRB5_NCRT_BOGUS)
1624 return -1;
1625 if (left->explicit_order < right->explicit_order)
1626 return -1;
1627 if (left->explicit_order > right->explicit_order)
1628 return 1;
1629 return left->order - right->order;
1632 static krb5_error_code
1633 parse_name_canon_rules(krb5_context context, char **rulestrs,
1634 krb5_name_canon_rule *rules)
1636 krb5_error_code ret;
1637 char *tok;
1638 char *cp;
1639 char **cpp;
1640 size_t n;
1641 size_t i, k;
1642 int do_sort = 0;
1643 krb5_name_canon_rule r;
1645 *rules = NULL;
1647 for (n =0, cpp = rulestrs; cpp != NULL && *cpp != NULL; cpp++)
1648 n++;
1650 n += 2; /* Always at least one rule; two for the default case */
1652 if ((r = calloc(n, sizeof (*r))) == NULL)
1653 return krb5_enomem(context);
1655 for (k = 0; k < n; k++) {
1656 r[k].type = KRB5_NCRT_BOGUS;
1657 r[k].match_domain = NULL;
1658 r[k].match_realm = NULL;
1659 r[k].domain = NULL;
1660 r[k].realm = NULL;
1663 for (i = 0, k = 0; i < n && rulestrs != NULL && rulestrs[i] != NULL; i++) {
1664 cp = rulestrs[i];
1665 r[k].explicit_order = MAXORDER; /* mark order, see below */
1666 r[k].maxdots = MAXDOTS;
1667 r[k].order = k; /* default order */
1669 /* Tokenize and parse value */
1670 do {
1671 tok = cp;
1672 cp = strchr(cp, ':'); /* XXX use strtok_r() */
1673 if (cp)
1674 *cp++ = '\0'; /* delimit token */
1675 ret = rule_parse_token(context, &r[k], tok);
1676 if (ret == EINVAL) {
1677 r[k].type = KRB5_NCRT_BOGUS;
1678 break;
1680 if (ret) {
1681 _krb5_free_name_canon_rules(context, r);
1682 return ret;
1684 } while (cp && *cp);
1685 if (r[k].explicit_order != MAXORDER)
1686 do_sort = 1;
1688 /* Validate parsed rule */
1689 if (r[k].type == KRB5_NCRT_BOGUS ||
1690 (r[k].type == KRB5_NCRT_QUALIFY && !r[k].domain) ||
1691 (r[k].type == KRB5_NCRT_NSS && r[k].domain)) {
1692 /* Invalid rule; mark it so and clean up */
1693 r[k].type = KRB5_NCRT_BOGUS;
1694 free(r[k].match_domain);
1695 free(r[k].match_realm);
1696 free(r[k].domain);
1697 free(r[k].realm);
1698 r[k].realm = NULL;
1699 r[k].domain = NULL;
1700 r[k].match_domain = NULL;
1701 r[k].match_realm = NULL;
1702 _krb5_debug(context, 5,
1703 "Ignoring invalid name canonicalization rule %lu",
1704 (unsigned long)i);
1705 continue;
1707 k++; /* good rule */
1710 if (do_sort) {
1712 * Note that we make make this a stable sort by using appareance
1713 * and explicit order.
1715 qsort(r, n, sizeof(r[0]), rule_cmp);
1718 if (r[0].type == KRB5_NCRT_BOGUS) {
1719 /* No rules, or no valid rules */
1720 r[0].type = KRB5_NCRT_NSS;
1723 *rules = r;
1724 return 0; /* We don't communicate bad rule errors here */
1728 * This exists only because the hostname canonicalization behavior in Heimdal
1729 * (and other implementations of Kerberos) has been to use getaddrinfo(),
1730 * unsafe though it is, for ages. We can't fix it in one day.
1732 static void
1733 make_rules_safe(krb5_context context, krb5_name_canon_rule rules)
1736 * If the only rule were to use the name service (getaddrinfo()) then we're
1737 * bound to fail. We could try to convert that rule to an as-is rule, but
1738 * when we do get a validating resolver we'd be unhappy that we did such a
1739 * conversion. Better let the user get failures and make them think about
1740 * their naming rules.
1742 if (rules == NULL)
1743 return;
1744 for (; rules[0].type != KRB5_NCRT_BOGUS; rules++) {
1745 if (rules->type == KRB5_NCRT_NSS)
1746 rules->options |= KRB5_NCRO_USE_DNSSEC;
1747 else
1748 rules->options |= KRB5_NCRO_USE_FAST;
1753 * This function returns an array of host-based service name
1754 * canonicalization rules. The array of rules is organized as a list.
1755 * See the definition of krb5_name_canon_rule.
1757 * @param context A Kerberos context.
1758 * @param rules Output location for array of rules.
1760 KRB5_LIB_FUNCTION krb5_error_code
1761 _krb5_get_name_canon_rules(krb5_context context, krb5_name_canon_rule *rules)
1763 krb5_error_code ret;
1764 char **values = NULL;
1766 *rules = context->name_canon_rules;
1767 if (*rules != NULL)
1768 return 0;
1770 values = krb5_config_get_strings(context, NULL,
1771 "libdefaults", "name_canon_rules", NULL);
1772 ret = parse_name_canon_rules(context, values, rules);
1773 krb5_config_free_strings(values);
1774 if (ret)
1775 return ret;
1777 if (krb5_config_get_bool_default(context, NULL, FALSE,
1778 "libdefaults", "safe_name_canon", NULL))
1779 make_rules_safe(context, *rules);
1781 heim_assert((*rules)[0].type != KRB5_NCRT_BOGUS,
1782 "internal error in parsing principal name "
1783 "canonicalization rules");
1785 /* Memoize */
1786 context->name_canon_rules = *rules;
1788 return 0;
1791 static krb5_error_code
1792 get_host_realm(krb5_context context, const char *hostname, char **realm)
1794 krb5_error_code ret;
1795 char **hrealms = NULL;
1797 *realm = NULL;
1798 ret = krb5_get_host_realm(context, hostname, &hrealms);
1799 if (ret)
1800 return ret;
1801 if (hrealms == NULL)
1802 return KRB5_ERR_HOST_REALM_UNKNOWN; /* krb5_set_error() already done */
1803 if (hrealms[0] == NULL) {
1804 krb5_free_host_realm(context, hrealms);
1805 return KRB5_ERR_HOST_REALM_UNKNOWN; /* krb5_set_error() already done */
1807 *realm = strdup(hrealms[0]);
1808 krb5_free_host_realm(context, hrealms);
1809 if (*realm == NULL)
1810 return krb5_enomem(context);
1811 return 0;
1814 static int
1815 is_domain_suffix(const char *domain, const char *suffix)
1817 size_t dlen = strlen(domain);
1818 size_t slen = strlen(suffix);
1820 if (dlen < slen + 2)
1821 return 0;
1823 if (strcasecmp(domain + (dlen - slen), suffix) != 0)
1824 return 0;
1826 if (domain[(dlen - slen) - 1] != '.')
1827 return 0;
1828 return 1;
1832 * Applies a name canonicalization rule to a principal.
1834 * Returns zero and no out_princ if the rule does not match.
1835 * Returns zero and an out_princ if the rule does match.
1837 static krb5_error_code
1838 apply_name_canon_rule(krb5_context context, krb5_name_canon_rule rules,
1839 size_t rule_idx, krb5_const_principal in_princ,
1840 krb5_principal *out_princ,
1841 krb5_name_canon_rule_options *rule_opts)
1843 krb5_name_canon_rule rule = &rules[rule_idx];
1844 krb5_error_code ret;
1845 unsigned int ndots = 0;
1846 krb5_principal nss = NULL;
1847 const char *sname = NULL;
1848 const char *orig_hostname = NULL;
1849 const char *new_hostname = NULL;
1850 const char *new_realm = NULL;
1851 const char *port = "";
1852 const char *cp;
1853 char *hostname_sans_port = NULL;
1854 char *hostname_with_port = NULL;
1855 char *tmp_hostname = NULL;
1856 char *tmp_realm = NULL;
1858 *out_princ = NULL; /* Signal no match */
1860 if (rule_opts != NULL)
1861 *rule_opts = rule->options;
1863 if (rule->type == KRB5_NCRT_BOGUS)
1864 return 0; /* rule doesn't apply */
1866 sname = krb5_principal_get_comp_string(context, in_princ, 0);
1867 orig_hostname = krb5_principal_get_comp_string(context, in_princ, 1);
1870 * Some apps want to use the very non-standard svc/hostname:port@REALM
1871 * form. We do our best to support that here :(
1873 port = strchr(orig_hostname, ':');
1874 if (port != NULL) {
1875 hostname_sans_port = strndup(orig_hostname, port - orig_hostname);
1876 if (hostname_sans_port == NULL)
1877 return krb5_enomem(context);
1878 orig_hostname = hostname_sans_port;
1881 _krb5_debug(context, 5, N_("Applying a name rule (type %d) to %s", ""),
1882 rule->type, orig_hostname);
1884 if (rule->mindots > 0 || rule->maxdots > 0) {
1885 for (cp = strchr(orig_hostname, '.'); cp && *cp; cp = strchr(cp + 1, '.'))
1886 ndots++;
1888 if (rule->mindots > 0 && ndots < rule->mindots)
1889 return 0;
1890 if (ndots > rule->maxdots)
1891 return 0;
1893 if (rule->match_domain != NULL &&
1894 !is_domain_suffix(orig_hostname, rule->match_domain))
1895 return 0;
1897 if (rule->match_realm != NULL &&
1898 strcmp(rule->match_realm, in_princ->realm) != 0)
1899 return 0;
1901 new_realm = rule->realm;
1902 switch (rule->type) {
1903 case KRB5_NCRT_AS_IS:
1904 break;
1906 case KRB5_NCRT_QUALIFY:
1907 heim_assert(rule->domain != NULL,
1908 "missing domain for qualify name canon rule");
1909 if (asprintf(&tmp_hostname, "%s.%s", orig_hostname,
1910 rule->domain) == -1 || tmp_hostname == NULL) {
1911 ret = krb5_enomem(context);
1912 goto out;
1914 new_hostname = tmp_hostname;
1915 break;
1917 case KRB5_NCRT_NSS:
1918 if ((rule->options & KRB5_NCRO_USE_DNSSEC)) {
1919 ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1920 krb5_set_error_message(context, ret,
1921 "Secure hostname resolution not supported");
1922 goto out;
1924 _krb5_debug(context, 5, "Using name service lookups");
1925 ret = krb5_sname_to_principal_old(context, rule->realm,
1926 orig_hostname, sname,
1927 KRB5_NT_SRV_HST,
1928 &nss);
1929 if (rules[rule_idx + 1].type != KRB5_NCRT_BOGUS &&
1930 (ret == KRB5_ERR_BAD_HOSTNAME ||
1931 ret == KRB5_ERR_HOST_REALM_UNKNOWN)) {
1933 * Bad hostname / realm unknown -> rule inapplicable if
1934 * there's more rules. If it's the last rule then we want
1935 * to return all errors from krb5_sname_to_principal_old()
1936 * here.
1938 ret = 0;
1939 goto out;
1941 if (ret)
1942 goto out;
1944 new_hostname = krb5_principal_get_comp_string(context, nss, 1);
1945 new_realm = krb5_principal_get_realm(context, nss);
1946 break;
1948 default:
1949 /* Can't happen */
1950 ret = 0;
1951 goto out;
1955 * This rule applies.
1957 * Copy in_princ and mutate the copy per the matched rule.
1959 * This way we apply to principals with two or more components, such as
1960 * domain-based names.
1962 ret = krb5_copy_principal(context, in_princ, out_princ);
1963 if (ret)
1964 goto out;
1966 if (new_realm == NULL && (rule->options & KRB5_NCRO_LOOKUP_REALM) != 0) {
1967 ret = get_host_realm(context, new_hostname, &tmp_realm);
1968 if (ret)
1969 goto out;
1970 new_realm = tmp_realm;
1973 /* If we stripped off a :port, add it back in */
1974 if (port != NULL && new_hostname != NULL) {
1975 if (asprintf(&hostname_with_port, "%s%s", new_hostname, port) == -1 ||
1976 hostname_with_port == NULL) {
1977 ret = krb5_enomem(context);
1978 goto out;
1980 new_hostname = hostname_with_port;
1983 if (new_realm != NULL &&
1984 (ret = krb5_principal_set_realm(context, *out_princ, new_realm)))
1985 goto out;
1986 if (new_hostname != NULL &&
1987 (ret = krb5_principal_set_comp_string(context, *out_princ, 1, new_hostname)))
1988 goto out;
1989 if (princ_type(*out_princ) == KRB5_NT_SRV_HST_NEEDS_CANON)
1990 princ_type(*out_princ) = KRB5_NT_SRV_HST;
1992 /* Trace rule application */
1994 krb5_error_code ret2;
1995 char *unparsed;
1997 ret2 = krb5_unparse_name(context, *out_princ, &unparsed);
1998 if (ret2) {
1999 _krb5_debug(context, 5,
2000 N_("Couldn't unparse canonicalized princicpal (%d)",
2001 ""),
2002 ret);
2003 } else {
2004 _krb5_debug(context, 5,
2005 N_("Name canon rule application yields %s", ""),
2006 unparsed);
2007 free(unparsed);
2011 out:
2012 free(hostname_sans_port);
2013 free(hostname_with_port);
2014 free(tmp_hostname);
2015 free(tmp_realm);
2016 krb5_free_principal(context, nss);
2017 if (ret)
2018 krb5_set_error_message(context, ret,
2019 N_("Name canon rule application failed", ""));
2020 return ret;
2024 * Free name canonicalization rules
2026 KRB5_LIB_FUNCTION void
2027 _krb5_free_name_canon_rules(krb5_context context, krb5_name_canon_rule rules)
2029 size_t k;
2031 if (rules == NULL)
2032 return;
2034 for (k = 0; rules[k].type != KRB5_NCRT_BOGUS; k++) {
2035 free(rules[k].match_domain);
2036 free(rules[k].match_realm);
2037 free(rules[k].domain);
2038 free(rules[k].realm);
2040 free(rules);
2043 struct krb5_name_canon_iterator_data {
2044 krb5_name_canon_rule rules;
2045 krb5_const_principal in_princ; /* given princ */
2046 krb5_const_principal out_princ; /* princ to be output */
2047 krb5_principal tmp_princ; /* to be freed */
2048 int is_trivial; /* no canon to be done */
2049 int done; /* no more rules to be applied */
2050 size_t cursor; /* current/next rule */
2054 * Initialize name canonicalization iterator.
2056 * @param context Kerberos context
2057 * @param in_princ principal name to be canonicalized OR
2058 * @param iter output iterator object
2060 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
2061 krb5_name_canon_iterator_start(krb5_context context,
2062 krb5_const_principal in_princ,
2063 krb5_name_canon_iterator *iter)
2065 krb5_error_code ret;
2066 krb5_name_canon_iterator state;
2068 *iter = NULL;
2070 state = calloc(1, sizeof (*state));
2071 if (state == NULL)
2072 return krb5_enomem(context);
2073 state->in_princ = in_princ;
2075 if (princ_type(state->in_princ) == KRB5_NT_SRV_HST_NEEDS_CANON) {
2076 ret = _krb5_get_name_canon_rules(context, &state->rules);
2077 if (ret)
2078 goto out;
2079 } else {
2080 /* Name needs no canon -> trivial iterator: in_princ is canonical */
2081 state->is_trivial = 1;
2084 *iter = state;
2085 return 0;
2087 out:
2088 krb5_free_name_canon_iterator(context, state);
2089 return krb5_enomem(context);
2093 * Helper for name canon iteration.
2095 static krb5_error_code
2096 name_canon_iterate(krb5_context context,
2097 krb5_name_canon_iterator *iter,
2098 krb5_name_canon_rule_options *rule_opts)
2100 krb5_error_code ret;
2101 krb5_name_canon_iterator state = *iter;
2103 if (rule_opts)
2104 *rule_opts = 0;
2106 if (state == NULL)
2107 return 0;
2109 if (state->done) {
2110 krb5_free_name_canon_iterator(context, state);
2111 *iter = NULL;
2112 return 0;
2115 if (state->is_trivial && !state->done) {
2116 state->out_princ = state->in_princ;
2117 state->done = 1;
2118 return 0;
2121 heim_assert(state->rules != NULL &&
2122 state->rules[state->cursor].type != KRB5_NCRT_BOGUS,
2123 "Internal error during name canonicalization");
2125 do {
2126 krb5_free_principal(context, state->tmp_princ);
2127 ret = apply_name_canon_rule(context, state->rules, state->cursor,
2128 state->in_princ, &state->tmp_princ, rule_opts);
2129 if (ret) {
2130 krb5_free_name_canon_iterator(context, state);
2131 *iter = NULL;
2132 return ret;
2134 state->cursor++;
2135 } while (state->tmp_princ == NULL &&
2136 state->rules[state->cursor].type != KRB5_NCRT_BOGUS);
2138 if (state->rules[state->cursor].type == KRB5_NCRT_BOGUS)
2139 state->done = 1;
2141 state->out_princ = state->tmp_princ;
2142 if (state->tmp_princ == NULL) {
2143 krb5_free_name_canon_iterator(context, state);
2144 *iter = NULL;
2145 return 0;
2147 return 0;
2151 * Iteratively apply name canon rules, outputing a principal and rule
2152 * options each time. Iteration completes when the @iter is NULL on
2153 * return or when an error is returned. Callers must free the iterator
2154 * if they abandon it mid-way.
2156 * @param context Kerberos context
2157 * @param iter name canon rule iterator (input/output)
2158 * @param try_princ output principal name
2159 * @param rule_opts output rule options
2161 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
2162 krb5_name_canon_iterate(krb5_context context,
2163 krb5_name_canon_iterator *iter,
2164 krb5_const_principal *try_princ,
2165 krb5_name_canon_rule_options *rule_opts)
2167 krb5_error_code ret;
2169 *try_princ = NULL;
2171 ret = name_canon_iterate(context, iter, rule_opts);
2172 if (*iter)
2173 *try_princ = (*iter)->out_princ;
2174 return ret;
2178 * Free a name canonicalization rule iterator.
2180 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
2181 krb5_free_name_canon_iterator(krb5_context context,
2182 krb5_name_canon_iterator iter)
2184 if (iter == NULL)
2185 return;
2186 if (iter->tmp_princ)
2187 krb5_free_principal(context, iter->tmp_princ);
2188 free(iter);