xref: /titanic_44/usr/src/lib/gss_mechs/mech_krb5/krb5/os/locate_kdc.c (revision 6a634c9dca3093f3922e4b7ab826d7bdf17bf78e)
1 /*
2  * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
3  */
4 
5 /*
6  * lib/krb5/os/locate_kdc.c
7  *
8  * Copyright 1990,2000,2001,2002,2003,2004,2006 Massachusetts Institute of Technology.
9  * All Rights Reserved.
10  *
11  * Export of this software from the United States of America may
12  *   require a specific license from the United States Government.
13  *   It is the responsibility of any person or organization contemplating
14  *   export to obtain such a license before exporting.
15  *
16  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
17  * distribute this software and its documentation for any purpose and
18  * without fee is hereby granted, provided that the above copyright
19  * notice appear in all copies and that both that copyright notice and
20  * this permission notice appear in supporting documentation, and that
21  * the name of M.I.T. not be used in advertising or publicity pertaining
22  * to distribution of the software without specific, written prior
23  * permission.  Furthermore if you modify this software you must label
24  * your software as modified software and not distribute it in such a
25  * fashion that it might be confused with the original M.I.T. software.
26  * M.I.T. makes no representations about the suitability of
27  * this software for any purpose.  It is provided "as is" without express
28  * or implied warranty.
29  *
30  *
31  * get socket addresses for KDC.
32  */
33 
34 /*
35  * Solaris Kerberos
36  * Re-factored the following routines to get a clear separation of locating
37  * KDC entries (krb5.conf/DNS-SRVrecs) versus mapping them to net addresses
38  * to allow us to output better error msgs:
39  *   krb5int_locate_server
40  *   prof_locate_server
41  *   dns_locate_server
42  *   krb5_locate_srv_conf_1 (removed)
43  *   krb5_locate_srv_dns_1  (removed)
44  *   prof_hostnames2netaddrs (new)
45  *   hostlist2str (new)
46  *   dns_hostnames2netaddrs (new)
47  *   dnslist2str (new)
48  * Also, for the profile get_master==1 case, the algorithm has been
49  * simplified to just do a profile_get_values on "admin_server" and
50  * not try to match those against "kdc" entries (does not seem necessary
51  * and the DNS-SRVrecs code does not do that).
52  */
53 
54 #include "fake-addrinfo.h"
55 #include "k5-int.h"
56 #include "os-proto.h"
57 #include <stdio.h>
58 #ifdef KRB5_DNS_LOOKUP
59 #ifdef WSHELPER
60 #include <wshelper.h>
61 #else /* WSHELPER */
62 #include <netinet/in.h>
63 #include <arpa/inet.h>
64 #include <arpa/nameser.h>
65 #include <resolv.h>
66 #include <netdb.h>
67 #endif /* WSHELPER */
68 #ifndef T_SRV
69 #define T_SRV 33
70 #endif /* T_SRV */
71 #include <syslog.h>
72 #include <locale.h>
73 
74 /* for old Unixes and friends ... */
75 #ifndef MAXHOSTNAMELEN
76 #define MAXHOSTNAMELEN 64
77 #endif
78 
79 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
80 
81 /* Solaris Kerberos: default to dns lookup for the KDC but not the realm */
82 #define DEFAULT_LOOKUP_KDC 1
83 #define DEFAULT_LOOKUP_REALM 0
84 
85 static int
maybe_use_dns(krb5_context context,const char * name,int defalt)86 maybe_use_dns (krb5_context context, const char *name, int defalt)
87 {
88     krb5_error_code code;
89     char * value = NULL;
90     int use_dns = 0;
91 
92     code = profile_get_string(context->profile, "libdefaults",
93                               name, 0, 0, &value);
94     if (value == 0 && code == 0)
95 	code = profile_get_string(context->profile, "libdefaults",
96 				  "dns_fallback", 0, 0, &value);
97     if (code)
98         return defalt;
99 
100     if (value == 0)
101 	return defalt;
102 
103     use_dns = _krb5_conf_boolean(value);
104     profile_release_string(value);
105     return use_dns;
106 }
107 
108 int
_krb5_use_dns_kdc(krb5_context context)109 _krb5_use_dns_kdc(krb5_context context)
110 {
111     return maybe_use_dns (context, "dns_lookup_kdc", DEFAULT_LOOKUP_KDC);
112 }
113 
114 int
_krb5_use_dns_realm(krb5_context context)115 _krb5_use_dns_realm(krb5_context context)
116 {
117     return maybe_use_dns (context, "dns_lookup_realm", DEFAULT_LOOKUP_REALM);
118 }
119 
120 #endif /* KRB5_DNS_LOOKUP */
121 
122 int
krb5int_grow_addrlist(struct addrlist * lp,int nmore)123 krb5int_grow_addrlist (struct addrlist *lp, int nmore)
124 {
125     int i;
126     int newspace = lp->space + nmore;
127     size_t newsize = newspace * sizeof (*lp->addrs);
128     void *newaddrs;
129 
130     newaddrs = realloc (lp->addrs, newsize);
131     if (newaddrs == NULL)
132 	return errno;
133     lp->addrs = newaddrs;
134     for (i = lp->space; i < newspace; i++) {
135 	lp->addrs[i].ai = NULL;
136 	lp->addrs[i].freefn = NULL;
137 	lp->addrs[i].data = NULL;
138     }
139     lp->space = newspace;
140     return 0;
141 }
142 #define grow_list krb5int_grow_addrlist
143 
144 /* Free up everything pointed to by the addrlist structure, but don't
145    free the structure itself.  */
146 void
krb5int_free_addrlist(struct addrlist * lp)147 krb5int_free_addrlist (struct addrlist *lp)
148 {
149     int i;
150     for (i = 0; i < lp->naddrs; i++)
151 	if (lp->addrs[i].freefn)
152 	    (lp->addrs[i].freefn)(lp->addrs[i].data);
153     free (lp->addrs);
154     lp->addrs = NULL;
155     lp->naddrs = lp->space = 0;
156 }
157 #define free_list krb5int_free_addrlist
158 
translate_ai_error(int err)159 static int translate_ai_error (int err)
160 {
161     switch (err) {
162     case 0:
163 	return 0;
164     case EAI_BADFLAGS:
165     case EAI_FAMILY:
166     case EAI_SOCKTYPE:
167     case EAI_SERVICE:
168 	/* All of these indicate bad inputs to getaddrinfo.  */
169 	return EINVAL;
170     case EAI_AGAIN:
171 	/* Translate to standard errno code.  */
172 	return EAGAIN;
173     case EAI_MEMORY:
174 	/* Translate to standard errno code.  */
175 	return ENOMEM;
176 #ifdef EAI_ADDRFAMILY
177     case EAI_ADDRFAMILY:
178 #endif
179 #if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME
180     case EAI_NODATA:
181 #endif
182     case EAI_NONAME:
183 	/* Name not known or no address data, but no error.  Do
184 	   nothing more.  */
185 	return 0;
186 #ifdef EAI_OVERFLOW
187     case EAI_OVERFLOW:
188 	/* An argument buffer overflowed.  */
189 	return EINVAL;		/* XXX */
190 #endif
191 #ifdef EAI_SYSTEM
192     case EAI_SYSTEM:
193 	/* System error, obviously.  */
194 	return errno;
195 #endif
196     default:
197 	/* An error code we haven't handled?  */
198 	return EINVAL;
199     }
200 }
201 
202 /* Solaris Kerberos: want dbg messages to syslog */
203 #include <stdarg.h>
Tprintf(const char * fmt,...)204 static inline void Tprintf(const char *fmt, ...)
205 {
206 #ifdef TEST
207     va_list ap;
208     char err_str[2048];
209 
210     va_start(ap, fmt);
211     vsnprintf(err_str, sizeof (err_str), fmt, args);
212     syslog(LOG_DEBUG, err_str);
213     va_end(ap);
214 #endif
215 }
216 
217 #if 0
218 extern void krb5int_debug_fprint(const char *, ...);
219 #define dprint krb5int_debug_fprint
220 #define print_addrlist krb5int_print_addrlist
221 extern void print_addrlist (const struct addrlist *a);
222 #else
dprint(const char * fmt,...)223 static inline void dprint(const char *fmt, ...) { }
print_addrlist(const struct addrlist * a)224 static inline void print_addrlist(const struct addrlist *a) { }
225 #endif
226 
add_addrinfo_to_list(struct addrlist * lp,struct addrinfo * a,void (* freefn)(void *),void * data)227 static int add_addrinfo_to_list (struct addrlist *lp, struct addrinfo *a,
228 				 void (*freefn)(void *), void *data)
229 {
230     int err;
231 
232     dprint("\tadding %p=%A to %p (naddrs=%d space=%d)\n", a, a, lp,
233 	   lp->naddrs, lp->space);
234 
235     if (lp->naddrs == lp->space) {
236 	err = grow_list (lp, 1);
237 	if (err) {
238 	    Tprintf ("grow_list failed %d\n", err);
239 	    return err;
240 	}
241     }
242     Tprintf("setting element %d\n", lp->naddrs);
243     lp->addrs[lp->naddrs].ai = a;
244     lp->addrs[lp->naddrs].freefn = freefn;
245     lp->addrs[lp->naddrs].data = data;
246     lp->naddrs++;
247     Tprintf ("\tcount is now %d: ", lp->naddrs);
248     print_addrlist(lp);
249     Tprintf("\n");
250     return 0;
251 }
252 
253 #define add_host_to_list krb5int_add_host_to_list
254 
call_freeaddrinfo(void * data)255 static void call_freeaddrinfo(void *data)
256 {
257     /* Strict interpretation of the C standard says we can't assume
258        that the ABI for f(void*) and f(struct foo *) will be
259        compatible.  Use this stub just to be paranoid.  */
260     freeaddrinfo(data);
261 }
262 
263 int
krb5int_add_host_to_list(struct addrlist * lp,const char * hostname,int port,int secport,int socktype,int family)264 krb5int_add_host_to_list (struct addrlist *lp, const char *hostname,
265 			  int port, int secport,
266 			  int socktype, int family)
267 {
268     struct addrinfo *addrs, *a, *anext, hint;
269     int err;
270     char portbuf[10], secportbuf[10];
271     void (*freefn)(void *);
272 
273     Tprintf ("adding hostname %s, ports %d,%d, family %d, socktype %d\n",
274 	     hostname, ntohs (port), ntohs (secport),
275 	     family, socktype);
276 
277     memset(&hint, 0, sizeof(hint));
278     hint.ai_family = family;
279     hint.ai_socktype = socktype;
280 #ifdef AI_NUMERICSERV
281     hint.ai_flags = AI_NUMERICSERV;
282 #endif
283     sprintf(portbuf, "%d", ntohs(port));
284     sprintf(secportbuf, "%d", ntohs(secport));
285     err = getaddrinfo (hostname, portbuf, &hint, &addrs);
286     if (err) {
287 	Tprintf ("\tgetaddrinfo(\"%s\", \"%s\", ...)\n\treturns %d: %s\n",
288 		 hostname, portbuf, err, gai_strerror (err));
289 	return translate_ai_error (err);
290     }
291     freefn = call_freeaddrinfo;
292     anext = 0;
293     for (a = addrs; a != 0 && err == 0; a = anext, freefn = 0) {
294 	anext = a->ai_next;
295 	err = add_addrinfo_to_list (lp, a, freefn, a);
296     }
297     if (err || secport == 0)
298 	goto egress;
299     if (socktype == 0)
300 	socktype = SOCK_DGRAM;
301     else if (socktype != SOCK_DGRAM)
302 	goto egress;
303     hint.ai_family = AF_INET;
304     err = getaddrinfo (hostname, secportbuf, &hint, &addrs);
305     if (err) {
306 	err = translate_ai_error (err);
307 	goto egress;
308     }
309     freefn = call_freeaddrinfo;
310     for (a = addrs; a != 0 && err == 0; a = anext, freefn = 0) {
311 	anext = a->ai_next;
312 	err = add_addrinfo_to_list (lp, a, freefn, a);
313     }
314 egress:
315     /* Solaris Kerberos */
316     if (anext)
317 	freeaddrinfo (anext);
318     return err;
319 }
320 
321 #include <locate_plugin.h>
322 
323 #if TARGET_OS_MAC
324 static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR, LIBDIR "/krb5/plugins/libkrb5", NULL }; /* should be a list */
325 #else
326 static const char *objdirs[] = { LIBDIR "/krb5/plugins/libkrb5", NULL };
327 #endif
328 
329 struct module_callback_data {
330     int out_of_mem;
331     struct addrlist *lp;
332 };
333 
334 static int
module_callback(void * cbdata,int socktype,struct sockaddr * sa)335 module_callback (void *cbdata, int socktype, struct sockaddr *sa)
336 {
337     struct module_callback_data *d = cbdata;
338     struct {
339 	struct addrinfo ai;
340 	union {
341 	    struct sockaddr_in sin;
342 	    struct sockaddr_in6 sin6;
343 	} u;
344     } *x;
345 
346     if (socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
347 	return 0;
348     if (sa->sa_family != AF_INET && sa->sa_family != AF_INET6)
349 	return 0;
350     x = malloc (sizeof (*x));
351     if (x == 0) {
352 	d->out_of_mem = 1;
353 	return 1;
354     }
355     memset(x, 0, sizeof (*x));
356     x->ai.ai_addr = (struct sockaddr *) &x->u;
357     x->ai.ai_socktype = socktype;
358     x->ai.ai_family = sa->sa_family;
359     if (sa->sa_family == AF_INET) {
360 	x->u.sin = *(struct sockaddr_in *)sa;
361 	x->ai.ai_addrlen = sizeof(struct sockaddr_in);
362     }
363     if (sa->sa_family == AF_INET6) {
364 	x->u.sin6 = *(struct sockaddr_in6 *)sa;
365 	x->ai.ai_addrlen = sizeof(struct sockaddr_in6);
366     }
367     if (add_addrinfo_to_list (d->lp, &x->ai, free, x) != 0) {
368 	/* Assumes only error is ENOMEM.  */
369 	d->out_of_mem = 1;
370 	return 1;
371     }
372     return 0;
373 }
374 
375 static krb5_error_code
module_locate_server(krb5_context ctx,const krb5_data * realm,struct addrlist * addrlist,enum locate_service_type svc,int socktype,int family)376 module_locate_server (krb5_context ctx, const krb5_data *realm,
377 		      struct addrlist *addrlist,
378 		      enum locate_service_type svc, int socktype, int family)
379 {
380     struct krb5plugin_service_locate_result *res = NULL;
381     krb5_error_code code;
382     struct krb5plugin_service_locate_ftable *vtbl = NULL;
383     void **ptrs;
384     int i;
385     struct module_callback_data cbdata = { 0, };
386 
387     Tprintf("in module_locate_server\n");
388     cbdata.lp = addrlist;
389     if (!PLUGIN_DIR_OPEN (&ctx->libkrb5_plugins)) {
390 
391 	code = krb5int_open_plugin_dirs (objdirs, NULL, &ctx->libkrb5_plugins,
392 					 &ctx->err);
393 	if (code)
394 	    return KRB5_PLUGIN_NO_HANDLE;
395     }
396 
397     code = krb5int_get_plugin_dir_data (&ctx->libkrb5_plugins,
398 					"service_locator", &ptrs, &ctx->err);
399     if (code) {
400 	Tprintf("error looking up plugin symbols: %s\n",
401 		krb5_get_error_message(ctx, code));
402 	return KRB5_PLUGIN_NO_HANDLE;
403     }
404 
405     for (i = 0; ptrs[i]; i++) {
406 	void *blob;
407 
408 	vtbl = ptrs[i];
409 	Tprintf("element %d is %p\n", i, ptrs[i]);
410 
411 	/* For now, don't keep the plugin data alive.  For long-lived
412 	   contexts, it may be desirable to change that later.  */
413 	code = vtbl->init(ctx, &blob);
414 	if (code)
415 	    continue;
416 
417 	code = vtbl->lookup(blob, svc, realm->data, socktype, family,
418 			    module_callback, &cbdata);
419 	vtbl->fini(blob);
420 	if (code == KRB5_PLUGIN_NO_HANDLE) {
421 	    /* Module passes, keep going.  */
422 	    /* XXX */
423 	    Tprintf("plugin doesn't handle this realm (KRB5_PLUGIN_NO_HANDLE)\n");
424 	    continue;
425 	}
426 	if (code != 0) {
427 	    /* Module encountered an actual error.  */
428 	    Tprintf("plugin lookup routine returned error %d: %s\n",
429 		    code, error_message(code));
430 	    krb5int_free_plugin_dir_data (ptrs);
431 	    return code;
432 	}
433 	break;
434     }
435     if (ptrs[i] == NULL) {
436 	Tprintf("ran off end of plugin list\n");
437 	krb5int_free_plugin_dir_data (ptrs);
438 	return KRB5_PLUGIN_NO_HANDLE;
439     }
440     Tprintf("stopped with plugin #%d, res=%p\n", i, res);
441 
442     /* Got something back, yippee.  */
443     Tprintf("now have %d addrs in list %p\n", addrlist->naddrs, addrlist);
444     print_addrlist(addrlist);
445     krb5int_free_plugin_dir_data (ptrs);
446     return 0;
447 }
448 
449 static krb5_error_code
prof_locate_server(krb5_context context,const krb5_data * realm,char *** hostlist,enum locate_service_type svc)450 prof_locate_server (krb5_context context, const krb5_data *realm,
451 		    char ***hostlist,
452 		    enum locate_service_type svc)
453 {
454     const char	*realm_srv_names[4];
455     char **hl, *host, *profname;
456     krb5_error_code code;
457     int i, j, count;
458 
459     *hostlist = NULL;  /* default - indicate no KDCs found */
460 
461     switch (svc) {
462     case locate_service_kdc:
463 	profname = "kdc";
464 	break;
465     case locate_service_master_kdc:
466         profname = "master_kdc";
467 	break;
468     case locate_service_kadmin:
469 	profname = "admin_server";
470 	break;
471     case locate_service_krb524:
472 	profname = "krb524_server";
473 	break;
474     case locate_service_kpasswd:
475 	profname = "kpasswd_server";
476 	break;
477     default:
478 	return EINVAL;
479     }
480 
481     if ((host = malloc(realm->length + 1)) == NULL)
482 	return ENOMEM;
483 
484     (void) strncpy(host, realm->data, realm->length);
485     host[realm->length] = '\0';
486     hl = 0;
487 
488     realm_srv_names[0] = "realms";
489     realm_srv_names[1] = host;
490     realm_srv_names[2] = profname;
491     realm_srv_names[3] = 0;
492 
493     code = profile_get_values(context->profile, realm_srv_names, &hl);
494     if (code) {
495 	Tprintf ("config file lookup failed: %s\n",
496 		 error_message(code));
497         if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
498 	    code = KRB5_REALM_UNKNOWN;
499  	krb5_xfree(host);
500   	return code;
501      }
502     krb5_xfree(host);
503 
504     *hostlist = hl;
505 
506     return 0;
507 }
508 
509 static krb5_error_code
dns_locate_server(krb5_context context,const krb5_data * realm,struct srv_dns_entry ** dns_list_head,enum locate_service_type svc,int socktype,int family)510 dns_locate_server (krb5_context context, const krb5_data *realm,
511 		struct srv_dns_entry **dns_list_head,
512 		enum locate_service_type svc, int socktype, int family)
513 {
514     const char *dnsname;
515     int use_dns = _krb5_use_dns_kdc(context);
516     krb5_error_code code;
517     struct srv_dns_entry *head = NULL;
518 
519     *dns_list_head = NULL; /* default: indicate we have found no KDCs */
520 
521     if (!use_dns)
522 	return KRB5_PLUGIN_NO_HANDLE;
523 
524     switch (svc) {
525     case locate_service_kdc:
526 	dnsname = "_kerberos";
527 	break;
528     case locate_service_master_kdc:
529 	dnsname = "_kerberos-master";
530 	break;
531     case locate_service_kadmin:
532 	dnsname = "_kerberos-adm";
533 	break;
534     case locate_service_krb524:
535 	dnsname = "_krb524";
536 	break;
537     case locate_service_kpasswd:
538 	dnsname = "_kpasswd";
539 	break;
540     default:
541 	return KRB5_PLUGIN_NO_HANDLE;
542     }
543 
544     code = 0;
545     if (socktype == SOCK_DGRAM || socktype == 0) {
546 	code = krb5int_make_srv_query_realm(realm, dnsname, "_udp", &head);
547 	if (code)
548 	    Tprintf("dns udp lookup returned error %d\n", code);
549     }
550     if ((socktype == SOCK_STREAM || socktype == 0) && code == 0) {
551 	code = krb5int_make_srv_query_realm(realm, dnsname, "_tcp", &head);
552 	if (code)
553 	    Tprintf("dns tcp lookup returned error %d\n", code);
554     }
555 
556     if (head == NULL)
557 	return 0;
558 
559     /* Check for the "." case indicating no support.  */
560     if (head->next == 0 && head->host[0] == 0) {
561 	free(head->host);
562 	free(head);
563 	return KRB5_ERR_NO_SERVICE;
564     }
565 
566     /*
567      * Okay!  Now we've got a linked list of entries sorted by
568      * priority.  Return it so later we can map hostnames to net addresses.
569      */
570     *dns_list_head = head;
571 
572     return 0;
573 }
574 
575 /*
576  * Given the list of hostnames of KDCs found in DNS SRV recs, lets go
577  * thru NSS (name svc switch) to get the net addrs.
578  */
579 static krb5_error_code
dns_hostnames2netaddrs(struct srv_dns_entry * head,enum locate_service_type svc,int socktype,int family,struct addrlist * addrlist)580 dns_hostnames2netaddrs(
581 	struct srv_dns_entry *head,
582 	enum locate_service_type svc,
583 	int socktype,
584 	int family,
585 	struct addrlist *addrlist)
586 {
587     struct srv_dns_entry *entry = NULL, *next;
588     krb5_error_code code;
589 
590     Tprintf ("walking answer list:\n");
591     for (entry = head; entry != NULL; entry = entry->next) {
592 	code = 0;
593 	if (socktype)
594 	    code = add_host_to_list (addrlist, entry->host,
595 				    htons (entry->port), 0,
596 				    socktype, family);
597 	else {
598 	    (void) add_host_to_list (addrlist, entry->host,
599 				    htons (entry->port), 0,
600 				    SOCK_DGRAM, family);
601 
602 	    code = add_host_to_list (addrlist, entry->host,
603 				    htons (entry->port), 0,
604 				    SOCK_STREAM, family);
605 	}
606         if (code) {
607 	    Tprintf("  fail add_host code=%d %s\n", code, entry->host);
608         }
609     }
610     Tprintf ("[end]\n");
611 
612     return code;
613 }
614 
615 /*
616  * Given the DNS SRV recs list, return a string of all the hosts like so:
617  *     "fqdn0[,fqdn1][,fqdnN]"
618  */
619 static char *
dnslist2str(struct srv_dns_entry * dns_list_head)620 dnslist2str(struct srv_dns_entry *dns_list_head)
621 {
622 	struct srv_dns_entry *head = dns_list_head;
623 	struct srv_dns_entry *entry = NULL, *next;
624 	unsigned int size = 0, c = 0, buf_size;
625 	char *s = NULL;
626 
627 	for (entry = head; entry; entry = entry->next, c++) {
628 		size += strlen(entry->host);
629 	}
630 	if (!c)
631 		return NULL;
632 
633 	/* hostnames + commas + NULL */
634 	buf_size = size + (c - 1) + 1;
635 	s = malloc(buf_size);
636 	if (!s)
637 		return NULL;
638 
639 	(void) strlcpy(s, head->host, buf_size);
640 	for (entry = head->next; entry; entry = entry->next) {
641 	    (void) strlcat(s, ",", buf_size);
642 	    (void) strlcat(s, entry->host, buf_size);
643 	}
644 
645 	return s;
646 }
647 
648 /*
649  * Given the profile hostlist, return a string of all the hosts like so:
650  *     "fqdn0[,fqdn1][,fqdnN]"
651  */
652 static char *
hostlist2str(char ** hostlist)653 hostlist2str(char **hostlist)
654 {
655 	unsigned int c = 0, size = 0, buf_size;
656 	char **hl = hostlist, *s = NULL;
657 
658 	while (hl && *hl) {
659 	    size += strlen(*hl);
660 	    hl++;
661 	    c++;
662 	}
663 	if (!c)
664 	    return NULL;
665 
666 	/* hostnames + commas + NULL */
667 	buf_size = size + (c - 1) + 1;
668 	s = malloc(buf_size);
669 	if (!s)
670 	    return NULL;
671 
672 	hl = hostlist;
673 	(void) strlcpy(s, *hl, buf_size);
674 	hl++;
675 	while (hl && *hl) {
676 	    (void) strlcat(s, ",", buf_size);
677 	    (void) strlcat(s, *hl, buf_size);
678 	    hl++;
679 	}
680 
681 	return s;
682 }
683 
684 /*
685  * Take the profile KDC list and return a list of net addrs.
686  */
687 static krb5_error_code
prof_hostnames2netaddrs(char ** hostlist,enum locate_service_type svc,int socktype,int family,struct addrlist * addrlist)688 prof_hostnames2netaddrs(
689 	char **hostlist,
690 	enum locate_service_type svc,
691 	int socktype,
692 	int family,
693 	struct addrlist *addrlist) /* output */
694 {
695 	int udpport  = 0 , sec_udpport = 0;
696 	int code, i;
697 	struct servent *serv;
698 
699 	int count = 0;
700 	while (hostlist && hostlist[count])
701 		count++;
702 	if (count == 0) {
703 		return 0;
704 	}
705 
706     switch (svc) {
707     case locate_service_kdc:
708     case locate_service_master_kdc:
709 	/* We used to use /etc/services for these, but enough systems
710 	   have old, crufty, wrong settings that this is probably
711 	   better.  */
712 	udpport = htons(KRB5_DEFAULT_PORT);
713 	sec_udpport = htons(KRB5_DEFAULT_SEC_PORT);
714 	break;
715     case locate_service_kadmin:
716 	udpport = htons(DEFAULT_KADM5_PORT);
717 	break;
718     case locate_service_krb524:
719 	serv = getservbyname(KRB524_SERVICE, "udp");
720 	udpport = serv ? serv->s_port : htons (KRB524_PORT);
721 	break;
722     case locate_service_kpasswd:
723 	udpport = htons(DEFAULT_KPASSWD_PORT);
724 	break;
725     default:
726 	return EINVAL;
727     }
728 
729     for (i=0; hostlist[i]; i++) {
730 	int p1, p2;
731 	char *cp, *port, *host;
732 
733 	host = hostlist[i];
734 	/*
735 	 * Strip off excess whitespace
736 	 */
737 	cp = strchr(host, ' ');
738 	if (cp)
739 	    *cp = 0;
740 	cp = strchr(host, '\t');
741 	if (cp)
742 	    *cp = 0;
743 	port = strchr(host, ':');
744 	if (port) {
745 	    *port = 0;
746 	    port++;
747 	}
748 
749 	if (port) {
750 	    unsigned long l;
751 #ifdef HAVE_STROUL
752 	    char *endptr;
753 	    l = strtoul (port, &endptr, 10);
754 	    if (endptr == NULL || *endptr != 0)
755 		return EINVAL;
756 #else
757 	    l = atoi (port);
758 #endif
759 	    /* L is unsigned, don't need to check <0.  */
760 	    if (l == 0 || l > 65535)
761 		return EINVAL;
762 	    p1 = htons (l);
763 	    p2 = 0;
764 	} else {
765 	    p1 = udpport;
766 	    p2 = sec_udpport;
767 	}
768 
769 
770 	if (socktype != 0) {
771 	    code = add_host_to_list (addrlist, hostlist[i], p1, p2,
772 				     socktype, family);
773 	} else {
774 	    code = add_host_to_list (addrlist, hostlist[i], p1, p2,
775 				     SOCK_DGRAM, family);
776 	    if (code == 0)
777 		code = add_host_to_list (addrlist, hostlist[i], p1, p2,
778 					 SOCK_STREAM, family);
779 	}
780     }
781 
782     return code;
783 }
784 
785 /*
786  * Wrapper function for the various backends
787  */
788 
789 krb5_error_code
krb5int_locate_server(krb5_context context,const krb5_data * realm,struct addrlist * addrlist,enum locate_service_type svc,int socktype,int family)790 krb5int_locate_server (krb5_context context, const krb5_data *realm,
791 		       struct addrlist *addrlist,
792 		       enum locate_service_type svc,
793 		       int socktype, int family)
794 {
795     krb5_error_code code;
796     struct addrlist al = ADDRLIST_INIT;
797     char **hostlist = NULL;
798     struct srv_dns_entry *dns_list_head = NULL;
799 
800     *addrlist = al;
801 
802     code = module_locate_server(context, realm, &al, svc, socktype, family);
803     Tprintf("module_locate_server returns %d\n", code);
804     if (code == KRB5_PLUGIN_NO_HANDLE) {
805 	/*
806 	 * We always try the local file before DNS.  Note that there
807 	 * is no way to indicate "service not available" via the
808 	 * config file.
809 	 */
810 	code = prof_locate_server(context, realm, &hostlist, svc);
811 
812 	/*
813 	 * Solaris Kerberos:
814 	 * If kpasswd_server has not been configured and dns_lookup_kdc -
815 	 * dns_fallback are not configured then admin_server should
816 	 * be inferred, per krb5.conf(4).
817 	 */
818 	if (code && svc == locate_service_kpasswd &&
819 	    !maybe_use_dns(context, "dns_lookup_kdc", 0)) {
820 		code = prof_locate_server(context, realm, &hostlist,
821 			locate_service_kadmin);
822 	}
823 
824 #ifdef KRB5_DNS_LOOKUP
825 	/*
826 	 * Solaris Kerberos:
827 	 * There is no point in trying to locate the KDC in DNS if "realm"
828 	 * is empty.
829 	 */
830 	/* Try DNS for all profile errors?  */
831 	if (code && !krb5_is_referral_realm(realm)) {
832 	    krb5_error_code code2;
833 	    code2 = dns_locate_server(context, realm, &dns_list_head,
834 				    svc, socktype, family);
835 
836 	    if (code2 != KRB5_PLUGIN_NO_HANDLE)
837 		code = code2;
838 	}
839 #endif /* KRB5_DNS_LOOKUP */
840 
841 	/* We could put more heuristics here, like looking up a hostname
842 	   of "kerberos."+REALM, etc.  */
843     }
844 
845     if (code != 0) {
846 	if (al.space)
847 	    free_list (&al);
848 	if (hostlist)
849 	    profile_free_list(hostlist);
850 	if (dns_list_head)
851 	    krb5int_free_srv_dns_data(dns_list_head);
852 
853 	return code;
854     }
855 
856     /*
857      * At this point we have no errors, let's check to see if we have
858      * any KDC entries from krb5.conf or DNS.
859      */
860     if (!hostlist && !dns_list_head) {
861 	switch(svc) {
862 	case locate_service_master_kdc:
863 	    krb5_set_error_message(context,
864 				KRB5_REALM_CANT_RESOLVE,
865 				dgettext(TEXT_DOMAIN,
866 					"Cannot find a master KDC entry in krb5.conf(4) or DNS Service Location records for realm '%.*s'"),
867 				realm->length, realm->data);
868 	    break;
869 	case locate_service_kadmin:
870 	    krb5_set_error_message(context,
871 				KRB5_REALM_CANT_RESOLVE,
872 				dgettext(TEXT_DOMAIN,
873 					"Cannot find a kadmin KDC entry in krb5.conf(4) or DNS Service Location records for realm '%.*s'"),
874 				realm->length, realm->data);
875 	    break;
876 	case locate_service_kpasswd:
877 	    krb5_set_error_message(context,
878 				KRB5_REALM_CANT_RESOLVE,
879 				dgettext(TEXT_DOMAIN,
880 					"Cannot find a kpasswd KDC entry in krb5.conf(4) or DNS Service Location records for realm '%.*s'"),
881 				realm->length, realm->data);
882 	    break;
883 	default: 	  /*  locate_service_kdc: */
884 		krb5_set_error_message(context,
885 				    KRB5_REALM_CANT_RESOLVE,
886 				    dgettext(TEXT_DOMAIN,
887 					    "Cannot find any KDC entries in krb5.conf(4) or DNS Service Location records for realm '%.*s'"),
888 				    realm->length, realm->data);
889 
890 	}
891 	return KRB5_REALM_CANT_RESOLVE;
892     }
893 
894     /* We have KDC entries, let see if we can get their net addrs. */
895     if (hostlist)
896 	code = prof_hostnames2netaddrs(hostlist, svc,
897 				    socktype, family, &al);
898     else if (dns_list_head)
899 	code = dns_hostnames2netaddrs(dns_list_head, svc,
900 				    socktype, family, &al);
901     if (code) {
902 	if (hostlist)
903 	    profile_free_list(hostlist);
904 	if (dns_list_head)
905 	    krb5int_free_srv_dns_data(dns_list_head);
906 	return code;
907     }
908 
909     /*
910      * Solaris Kerberos:
911      * If an entry for _kerberos-master. does not exist (checked for
912      * above) but _kpasswd. does then treat that as an entry for the
913      * master KDC (but use port 88 not the kpasswd port). MS AD creates
914      * kpasswd entries by default in DNS.
915      */
916     if (!dns_list_head && svc == locate_service_master_kdc &&
917 	al.naddrs == 0) {
918 
919 	/* Look for _kpasswd._tcp|udp */
920 	code = dns_locate_server(context, realm, &dns_list_head,
921 				locate_service_kpasswd, socktype, family);
922 
923 	if (code == 0 && dns_list_head) {
924 	    int i;
925 	    struct addrinfo *a;
926 
927 	    code = dns_hostnames2netaddrs(dns_list_head, svc,
928 					socktype, family, &al);
929 
930 	    /* Set the port to 88 instead of the kpasswd port */
931 	    if (code == 0 && al.naddrs > 0) {
932 		for (i = 0; i < al.naddrs; i++) {
933 		    if (al.addrs[i].ai->ai_family == AF_INET)
934 			for (a = al.addrs[i].ai; a != NULL; a = a->ai_next)
935 			    ((struct sockaddr_in *)a->ai_addr)->sin_port =
936 				htons(KRB5_DEFAULT_PORT);
937 
938 		    if (al.addrs[i].ai->ai_family == AF_INET6)
939 			for (a = al.addrs[i].ai; a != NULL; a = a->ai_next)
940 			     ((struct sockaddr_in6 *)a->ai_addr)->sin6_port =
941 				    htons(KRB5_DEFAULT_PORT);
942 		}
943 	    }
944 	}
945     }
946 
947     /* No errors so far, lets see if we have KDC net addrs */
948     if (al.naddrs == 0) {
949 	char *hostlist_str = NULL, *dnslist_str  = NULL;
950 	if (al.space)
951 	    free_list (&al);
952 
953 	if (hostlist) {
954 	    hostlist_str = hostlist2str(hostlist);
955 	    krb5_set_error_message(context, KRB5_REALM_CANT_RESOLVE,
956 				dgettext(TEXT_DOMAIN,
957 					"Cannot resolve network address for KDCs '%s' specified in krb5.conf(4) for realm %.*s"),
958 				hostlist_str ? hostlist_str : "unknown",
959 				realm->length, realm->data);
960 	    if (hostlist_str)
961 		free(hostlist_str);
962 	} else if (dns_list_head) {
963 	    dnslist_str = dnslist2str(dns_list_head);
964 	    krb5_set_error_message(context, KRB5_REALM_CANT_RESOLVE,
965 				dgettext(TEXT_DOMAIN,
966 					"Cannot resolve network address for KDCs '%s' discovered via DNS Service Location records for realm '%.*s'"),
967 				dnslist_str ? dnslist_str : "unknown",
968 				realm->length, realm->data);
969 	    if (dnslist_str)
970 		    free(dnslist_str);
971 	}
972 
973 	if (hostlist)
974 	    profile_free_list(hostlist);
975 	if (dns_list_head)
976 	    krb5int_free_srv_dns_data(dns_list_head);
977 
978 	return KRB5_REALM_CANT_RESOLVE;
979     }
980 
981     if (hostlist)
982 	    profile_free_list(hostlist);
983     if (dns_list_head)
984 	    krb5int_free_srv_dns_data(dns_list_head);
985 
986     *addrlist = al;
987     return 0;
988 }
989 
990 krb5_error_code
krb5_locate_kdc(krb5_context context,const krb5_data * realm,struct addrlist * addrlist,int get_masters,int socktype,int family)991 krb5_locate_kdc(krb5_context context, const krb5_data *realm,
992 		struct addrlist *addrlist,
993 		int get_masters, int socktype, int family)
994 {
995     return krb5int_locate_server(context, realm, addrlist,
996 				 (get_masters
997 				  ? locate_service_master_kdc
998 				  : locate_service_kdc),
999 				 socktype, family);
1000 }
1001 
1002 /*
1003  * Solaris Kerberos: for backward compat.  Avoid using this
1004  * function!
1005  */
1006 krb5_error_code
krb5_get_servername(krb5_context context,const krb5_data * realm,const char * name,const char * proto,char * srvhost,unsigned short * port)1007 krb5_get_servername(krb5_context context,
1008     const krb5_data *realm,
1009     const char *name, const char *proto,
1010     char *srvhost,
1011     unsigned short *port)
1012 {
1013     krb5_error_code code = KRB5_REALM_UNKNOWN;
1014 
1015 #ifdef KRB5_DNS_LOOKUP
1016     {
1017 	int use_dns = _krb5_use_dns_kdc(context);
1018 
1019 	if (use_dns) {
1020 	    struct srv_dns_entry *head = NULL;
1021 
1022 	    code = krb5int_make_srv_query_realm(realm, name, proto, &head);
1023 	    if (code)
1024 		return (code);
1025 
1026 	    if (head == NULL)
1027 		return KRB5_REALM_CANT_RESOLVE;
1028 
1029 	    *port = head->port;
1030 	    (void) strlcpy(srvhost, head->host, MAX_DNS_NAMELEN);
1031 
1032 #ifdef DEBUG
1033 	    fprintf (stderr, "krb5_get_servername svrhost %s, port %d\n",
1034 		srvhost, *port);
1035 #endif
1036 	    krb5int_free_srv_dns_data(head);
1037 	}
1038     }
1039 #endif /* KRB5_DNS_LOOKUP */
1040 
1041     return (code);
1042 }
1043