2 * Copyright (c) 2001 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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
34 #include "krb5_locl.h"
37 RCSID("$Id: krbhst.c,v 1.43.2.1 2003/04/22 15:00:38 lha Exp $");
40 string_to_proto(const char *string
)
42 if(strcasecmp(string
, "udp") == 0)
43 return KRB5_KRBHST_UDP
;
44 else if(strcasecmp(string
, "tcp") == 0)
45 return KRB5_KRBHST_TCP
;
46 else if(strcasecmp(string
, "http") == 0)
47 return KRB5_KRBHST_HTTP
;
52 * set `res' and `count' to the result of looking up SRV RR in DNS for
53 * `proto', `proto', `realm' using `dns_type'.
54 * if `port' != 0, force that port number
57 static krb5_error_code
58 srv_find_realm(krb5_context context
, krb5_krbhst_info
***res
, int *count
,
59 const char *realm
, const char *dns_type
,
60 const char *proto
, const char *service
, int port
)
64 struct resource_record
*rr
;
69 proto_num
= string_to_proto(proto
);
71 krb5_set_error_string(context
, "unknown protocol `%s'", proto
);
75 if(proto_num
== KRB5_KRBHST_HTTP
)
76 def_port
= ntohs(krb5_getportbyname (context
, "http", "tcp", 80));
78 def_port
= ntohs(krb5_getportbyname (context
, service
, proto
, 88));
82 snprintf(domain
, sizeof(domain
), "_%s._%s.%s.", service
, proto
, realm
);
84 r
= dns_lookup(domain
, dns_type
);
88 return KRB5_KDC_UNREACH
;
91 for(num_srv
= 0, rr
= r
->head
; rr
; rr
= rr
->next
)
95 *res
= malloc(num_srv
* sizeof(**res
));
98 krb5_set_error_string(context
, "malloc: out of memory");
104 for(num_srv
= 0, rr
= r
->head
; rr
; rr
= rr
->next
)
105 if(rr
->type
== T_SRV
) {
106 krb5_krbhst_info
*hi
;
107 size_t len
= strlen(rr
->u
.srv
->target
);
109 hi
= calloc(1, sizeof(*hi
) + len
);
112 while(--num_srv
>= 0)
113 free((*res
)[num_srv
]);
117 (*res
)[num_srv
++] = hi
;
119 hi
->proto
= proto_num
;
121 hi
->def_port
= def_port
;
125 hi
->port
= rr
->u
.srv
->port
;
127 strlcpy(hi
->hostname
, rr
->u
.srv
->target
, len
+ 1);
137 struct krb5_krbhst_data
{
141 int port
; /* hardwired port number if != 0 */
145 #define KD_SRV_HTTP 8
146 #define KD_FALLBACK 16
147 #define KD_CONFIG_EXISTS 32
149 krb5_error_code (*get_next
)(krb5_context
, struct krb5_krbhst_data
*,
152 unsigned int fallback_count
;
154 struct krb5_krbhst_info
*hosts
, **index
, **end
;
158 krbhst_empty(const struct krb5_krbhst_data
*kd
)
160 return kd
->index
== &kd
->hosts
;
164 * parse `spec' into a krb5_krbhst_info, defaulting the port to `def_port'
165 * and forcing it to `port' if port != 0
168 static struct krb5_krbhst_info
*
169 parse_hostspec(krb5_context context
, const char *spec
, int def_port
, int port
)
171 const char *p
= spec
;
172 struct krb5_krbhst_info
*hi
;
174 hi
= calloc(1, sizeof(*hi
) + strlen(spec
));
178 hi
->proto
= KRB5_KRBHST_UDP
;
180 if(strncmp(p
, "http://", 7) == 0){
181 hi
->proto
= KRB5_KRBHST_HTTP
;
183 } else if(strncmp(p
, "http/", 5) == 0) {
184 hi
->proto
= KRB5_KRBHST_HTTP
;
186 def_port
= ntohs(krb5_getportbyname (context
, "http", "tcp", 80));
187 }else if(strncmp(p
, "tcp/", 4) == 0){
188 hi
->proto
= KRB5_KRBHST_TCP
;
190 } else if(strncmp(p
, "udp/", 4) == 0) {
194 if(strsep_copy(&p
, ":", hi
->hostname
, strlen(spec
) + 1) < 0) {
198 /* get rid of trailing /, and convert to lower case */
199 hi
->hostname
[strcspn(hi
->hostname
, "/")] = '\0';
200 strlwr(hi
->hostname
);
202 hi
->port
= hi
->def_port
= def_port
;
205 hi
->port
= strtol(p
, &end
, 0);
217 free_krbhst_info(krb5_krbhst_info
*hi
)
220 freeaddrinfo(hi
->ai
);
225 append_host_hostinfo(struct krb5_krbhst_data
*kd
, struct krb5_krbhst_info
*host
)
227 struct krb5_krbhst_info
*h
;
229 for(h
= kd
->hosts
; h
; h
= h
->next
)
230 if(h
->proto
== host
->proto
&&
231 h
->port
== host
->port
&&
232 strcmp(h
->hostname
, host
->hostname
) == 0) {
233 free_krbhst_info(host
);
237 kd
->end
= &host
->next
;
240 static krb5_error_code
241 append_host_string(krb5_context context
, struct krb5_krbhst_data
*kd
,
242 const char *host
, int def_port
, int port
)
244 struct krb5_krbhst_info
*hi
;
246 hi
= parse_hostspec(context
, host
, def_port
, port
);
250 append_host_hostinfo(kd
, hi
);
255 * return a readable representation of `host' in `hostname, hostlen'
259 krb5_krbhst_format_string(krb5_context context
, const krb5_krbhst_info
*host
,
260 char *hostname
, size_t hostlen
)
262 const char *proto
= "";
263 char portstr
[7] = "";
264 if(host
->proto
== KRB5_KRBHST_TCP
)
266 else if(host
->proto
== KRB5_KRBHST_HTTP
)
268 if(host
->port
!= host
->def_port
)
269 snprintf(portstr
, sizeof(portstr
), ":%d", host
->port
);
270 snprintf(hostname
, hostlen
, "%s%s%s", proto
, host
->hostname
, portstr
);
275 * create a getaddrinfo `hints' based on `proto'
279 make_hints(struct addrinfo
*hints
, int proto
)
281 memset(hints
, 0, sizeof(*hints
));
282 hints
->ai_family
= AF_UNSPEC
;
284 case KRB5_KRBHST_UDP
:
285 hints
->ai_socktype
= SOCK_DGRAM
;
287 case KRB5_KRBHST_HTTP
:
288 case KRB5_KRBHST_TCP
:
289 hints
->ai_socktype
= SOCK_STREAM
;
295 * return an `struct addrinfo *' in `ai' corresponding to the information
296 * in `host'. free:ing is handled by krb5_krbhst_free.
300 krb5_krbhst_get_addrinfo(krb5_context context
, krb5_krbhst_info
*host
,
301 struct addrinfo
**ai
)
303 struct addrinfo hints
;
304 char portstr
[NI_MAXSERV
];
307 if (host
->ai
== NULL
) {
308 make_hints(&hints
, host
->proto
);
309 snprintf (portstr
, sizeof(portstr
), "%d", host
->port
);
310 ret
= getaddrinfo(host
->hostname
, portstr
, &hints
, &host
->ai
);
312 return krb5_eai_to_heim_errno(ret
, errno
);
319 get_next(struct krb5_krbhst_data
*kd
, krb5_krbhst_info
**host
)
321 struct krb5_krbhst_info
*hi
= *kd
->index
;
324 kd
->index
= &(*kd
->index
)->next
;
331 srv_get_hosts(krb5_context context
, struct krb5_krbhst_data
*kd
,
332 const char *proto
, const char *service
)
334 krb5_krbhst_info
**res
;
337 srv_find_realm(context
, &res
, &count
, kd
->realm
, "SRV", proto
, service
,
339 for(i
= 0; i
< count
; i
++)
340 append_host_hostinfo(kd
, res
[i
]);
345 * read the configuration for `conf_string', defaulting to kd->def_port and
346 * forcing it to `kd->port' if kd->port != 0
350 config_get_hosts(krb5_context context
, struct krb5_krbhst_data
*kd
,
351 const char *conf_string
)
356 hostlist
= krb5_config_get_strings(context
, NULL
,
357 "realms", kd
->realm
, conf_string
, NULL
);
361 kd
->flags
|= KD_CONFIG_EXISTS
;
362 for(i
= 0; hostlist
&& hostlist
[i
] != NULL
; i
++)
363 append_host_string(context
, kd
, hostlist
[i
], kd
->def_port
, kd
->port
);
365 krb5_config_free_strings(hostlist
);
369 * as a fallback, look for `serv_string.kd->realm' (typically
370 * kerberos.REALM, kerberos-1.REALM, ...
371 * `port' is the default port for the service, and `proto' the
375 static krb5_error_code
376 fallback_get_hosts(krb5_context context
, struct krb5_krbhst_data
*kd
,
377 const char *serv_string
, int port
, int proto
)
382 struct addrinfo hints
;
383 char portstr
[NI_MAXSERV
];
385 if(kd
->fallback_count
== 0)
386 asprintf(&host
, "%s.%s.", serv_string
, kd
->realm
);
388 asprintf(&host
, "%s-%d.%s.",
389 serv_string
, kd
->fallback_count
, kd
->realm
);
394 make_hints(&hints
, proto
);
395 snprintf(portstr
, sizeof(portstr
), "%d", port
);
396 ret
= getaddrinfo(host
, portstr
, &hints
, &ai
);
398 /* no more hosts, so we're done here */
400 kd
->flags
|= KD_FALLBACK
;
402 struct krb5_krbhst_info
*hi
;
403 size_t hostlen
= strlen(host
);
405 hi
= calloc(1, sizeof(*hi
) + hostlen
);
412 hi
->port
= hi
->def_port
= port
;
414 memmove(hi
->hostname
, host
, hostlen
- 1);
415 hi
->hostname
[hostlen
- 1] = '\0';
417 append_host_hostinfo(kd
, hi
);
418 kd
->fallback_count
++;
423 static krb5_error_code
424 kdc_get_next(krb5_context context
,
425 struct krb5_krbhst_data
*kd
,
426 krb5_krbhst_info
**host
)
430 if((kd
->flags
& KD_CONFIG
) == 0) {
431 config_get_hosts(context
, kd
, "kdc");
432 kd
->flags
|= KD_CONFIG
;
433 if(get_next(kd
, host
))
437 if (kd
->flags
& KD_CONFIG_EXISTS
)
438 return KRB5_KDC_UNREACH
; /* XXX */
440 if(context
->srv_lookup
) {
441 if((kd
->flags
& KD_SRV_UDP
) == 0) {
442 srv_get_hosts(context
, kd
, "udp", "kerberos");
443 kd
->flags
|= KD_SRV_UDP
;
444 if(get_next(kd
, host
))
448 if((kd
->flags
& KD_SRV_TCP
) == 0) {
449 srv_get_hosts(context
, kd
, "tcp", "kerberos");
450 kd
->flags
|= KD_SRV_TCP
;
451 if(get_next(kd
, host
))
454 if((kd
->flags
& KD_SRV_HTTP
) == 0) {
455 srv_get_hosts(context
, kd
, "http", "kerberos");
456 kd
->flags
|= KD_SRV_HTTP
;
457 if(get_next(kd
, host
))
462 while((kd
->flags
& KD_FALLBACK
) == 0) {
463 ret
= fallback_get_hosts(context
, kd
, "kerberos",
464 kd
->def_port
, KRB5_KRBHST_UDP
);
467 if(get_next(kd
, host
))
471 return KRB5_KDC_UNREACH
; /* XXX */
474 static krb5_error_code
475 admin_get_next(krb5_context context
,
476 struct krb5_krbhst_data
*kd
,
477 krb5_krbhst_info
**host
)
481 if((kd
->flags
& KD_CONFIG
) == 0) {
482 config_get_hosts(context
, kd
, "admin_server");
483 kd
->flags
|= KD_CONFIG
;
484 if(get_next(kd
, host
))
488 if (kd
->flags
& KD_CONFIG_EXISTS
)
489 return KRB5_KDC_UNREACH
; /* XXX */
491 if(context
->srv_lookup
) {
492 if((kd
->flags
& KD_SRV_TCP
) == 0) {
493 srv_get_hosts(context
, kd
, "tcp", "kerberos-adm");
494 kd
->flags
|= KD_SRV_TCP
;
495 if(get_next(kd
, host
))
501 && (kd
->flags
& KD_FALLBACK
) == 0) {
502 ret
= fallback_get_hosts(context
, kd
, "kerberos",
503 kd
->def_port
, KRB5_KRBHST_UDP
);
506 kd
->flags
|= KD_FALLBACK
;
507 if(get_next(kd
, host
))
511 return KRB5_KDC_UNREACH
; /* XXX */
514 static krb5_error_code
515 kpasswd_get_next(krb5_context context
,
516 struct krb5_krbhst_data
*kd
,
517 krb5_krbhst_info
**host
)
521 if((kd
->flags
& KD_CONFIG
) == 0) {
522 config_get_hosts(context
, kd
, "kpasswd_server");
523 if(get_next(kd
, host
))
527 if (kd
->flags
& KD_CONFIG_EXISTS
)
528 return KRB5_KDC_UNREACH
; /* XXX */
530 if(context
->srv_lookup
) {
531 if((kd
->flags
& KD_SRV_UDP
) == 0) {
532 srv_get_hosts(context
, kd
, "udp", "kpasswd");
533 kd
->flags
|= KD_SRV_UDP
;
534 if(get_next(kd
, host
))
539 /* no matches -> try admin */
541 if (krbhst_empty(kd
)) {
543 kd
->port
= kd
->def_port
;
544 kd
->get_next
= admin_get_next
;
545 ret
= (*kd
->get_next
)(context
, kd
, host
);
547 (*host
)->proto
= KRB5_KRBHST_UDP
;
551 return KRB5_KDC_UNREACH
; /* XXX */
554 static krb5_error_code
555 krb524_get_next(krb5_context context
,
556 struct krb5_krbhst_data
*kd
,
557 krb5_krbhst_info
**host
)
559 if((kd
->flags
& KD_CONFIG
) == 0) {
560 config_get_hosts(context
, kd
, "krb524_server");
561 if(get_next(kd
, host
))
563 kd
->flags
|= KD_CONFIG
;
566 if (kd
->flags
& KD_CONFIG_EXISTS
)
567 return KRB5_KDC_UNREACH
; /* XXX */
569 if(context
->srv_lookup
) {
570 if((kd
->flags
& KD_SRV_UDP
) == 0) {
571 srv_get_hosts(context
, kd
, "udp", "krb524");
572 kd
->flags
|= KD_SRV_UDP
;
573 if(get_next(kd
, host
))
577 if((kd
->flags
& KD_SRV_TCP
) == 0) {
578 srv_get_hosts(context
, kd
, "tcp", "krb524");
579 kd
->flags
|= KD_SRV_TCP
;
580 if(get_next(kd
, host
))
585 /* no matches -> try kdc */
587 if (krbhst_empty(kd
)) {
589 kd
->port
= kd
->def_port
;
590 kd
->get_next
= kdc_get_next
;
591 return (*kd
->get_next
)(context
, kd
, host
);
594 return KRB5_KDC_UNREACH
; /* XXX */
597 static struct krb5_krbhst_data
*
598 common_init(krb5_context context
,
601 struct krb5_krbhst_data
*kd
;
603 if((kd
= calloc(1, sizeof(*kd
))) == NULL
)
606 if((kd
->realm
= strdup(realm
)) == NULL
) {
611 kd
->end
= kd
->index
= &kd
->hosts
;
616 * initialize `handle' to look for hosts of type `type' in realm `realm'
620 krb5_krbhst_init(krb5_context context
,
623 krb5_krbhst_handle
*handle
)
625 struct krb5_krbhst_data
*kd
;
626 krb5_error_code (*get_next
)(krb5_context
, struct krb5_krbhst_data
*,
627 krb5_krbhst_info
**);
631 case KRB5_KRBHST_KDC
:
632 get_next
= kdc_get_next
;
633 def_port
= ntohs(krb5_getportbyname (context
, "kerberos", "udp", 88));
635 case KRB5_KRBHST_ADMIN
:
636 get_next
= admin_get_next
;
637 def_port
= ntohs(krb5_getportbyname (context
, "kerberos-adm",
640 case KRB5_KRBHST_CHANGEPW
:
641 get_next
= kpasswd_get_next
;
642 def_port
= ntohs(krb5_getportbyname (context
, "kpasswd", "udp",
645 case KRB5_KRBHST_KRB524
:
646 get_next
= krb524_get_next
;
647 def_port
= ntohs(krb5_getportbyname (context
, "krb524", "udp", 4444));
650 krb5_set_error_string(context
, "unknown krbhst type (%u)", type
);
653 if((kd
= common_init(context
, realm
)) == NULL
)
655 kd
->get_next
= get_next
;
656 kd
->def_port
= def_port
;
662 * return the next host information from `handle' in `host'
666 krb5_krbhst_next(krb5_context context
,
667 krb5_krbhst_handle handle
,
668 krb5_krbhst_info
**host
)
670 if(get_next(handle
, host
))
673 return (*handle
->get_next
)(context
, handle
, host
);
677 * return the next host information from `handle' as a host name
678 * in `hostname' (or length `hostlen)
682 krb5_krbhst_next_as_string(krb5_context context
,
683 krb5_krbhst_handle handle
,
688 krb5_krbhst_info
*host
;
689 ret
= krb5_krbhst_next(context
, handle
, &host
);
692 return krb5_krbhst_format_string(context
, host
, hostname
, hostlen
);
697 krb5_krbhst_reset(krb5_context context
, krb5_krbhst_handle handle
)
699 handle
->index
= &handle
->hosts
;
703 krb5_krbhst_free(krb5_context context
, krb5_krbhst_handle handle
)
705 krb5_krbhst_info
*h
, *next
;
710 for (h
= handle
->hosts
; h
!= NULL
; h
= next
) {
719 /* backwards compatibility ahead */
721 static krb5_error_code
722 gethostlist(krb5_context context
, const char *realm
,
723 unsigned int type
, char ***hostlist
)
727 krb5_krbhst_handle handle
;
728 char host
[MAXHOSTNAMELEN
];
729 krb5_krbhst_info
*hostinfo
;
731 ret
= krb5_krbhst_init(context
, realm
, type
, &handle
);
735 while(krb5_krbhst_next(context
, handle
, &hostinfo
) == 0)
738 return KRB5_KDC_UNREACH
;
739 *hostlist
= calloc(nhost
+ 1, sizeof(**hostlist
));
740 if(*hostlist
== NULL
) {
741 krb5_krbhst_free(context
, handle
);
745 krb5_krbhst_reset(context
, handle
);
747 while(krb5_krbhst_next_as_string(context
, handle
,
748 host
, sizeof(host
)) == 0) {
749 if(((*hostlist
)[nhost
++] = strdup(host
)) == NULL
) {
750 krb5_free_krbhst(context
, *hostlist
);
751 krb5_krbhst_free(context
, handle
);
755 (*hostlist
)[nhost
++] = NULL
;
756 krb5_krbhst_free(context
, handle
);
761 * return an malloced list of kadmin-hosts for `realm' in `hostlist'
765 krb5_get_krb_admin_hst (krb5_context context
,
766 const krb5_realm
*realm
,
769 return gethostlist(context
, *realm
, KRB5_KRBHST_ADMIN
, hostlist
);
773 * return an malloced list of changepw-hosts for `realm' in `hostlist'
777 krb5_get_krb_changepw_hst (krb5_context context
,
778 const krb5_realm
*realm
,
781 return gethostlist(context
, *realm
, KRB5_KRBHST_CHANGEPW
, hostlist
);
785 * return an malloced list of 524-hosts for `realm' in `hostlist'
789 krb5_get_krb524hst (krb5_context context
,
790 const krb5_realm
*realm
,
793 return gethostlist(context
, *realm
, KRB5_KRBHST_KRB524
, hostlist
);
798 * return an malloced list of KDC's for `realm' in `hostlist'
802 krb5_get_krbhst (krb5_context context
,
803 const krb5_realm
*realm
,
806 return gethostlist(context
, *realm
, KRB5_KRBHST_KDC
, hostlist
);
810 * free all the memory allocated in `hostlist'
814 krb5_free_krbhst (krb5_context context
,
819 for (p
= hostlist
; *p
; ++p
)