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