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