1 /*
2  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 /*
8  * lib/krb5/os/locate_kdc.c
9  *
10  * Copyright 1990,2000,2001,2002 by the Massachusetts Institute of Technology.
11  * All Rights Reserved.
12  *
13  * Export of this software from the United States of America may
14  *   require a specific license from the United States Government.
15  *   It is the responsibility of any person or organization contemplating
16  *   export to obtain such a license before exporting.
17  *
18  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
19  * distribute this software and its documentation for any purpose and
20  * without fee is hereby granted, provided that the above copyright
21  * notice appear in all copies and that both that copyright notice and
22  * this permission notice appear in supporting documentation, and that
23  * the name of M.I.T. not be used in advertising or publicity pertaining
24  * to distribution of the software without specific, written prior
25  * permission.  Furthermore if you modify this software you must label
26  * your software as modified software and not distribute it in such a
27  * fashion that it might be confused with the original M.I.T. software.
28  * M.I.T. makes no representations about the suitability of
29  * this software for any purpose.  It is provided "as is" without express
30  * or implied warranty.
31  *
32  *
33  * get socket addresses for KDC.
34  */
35 
36 #define NEED_SOCKETS
37 #include "fake-addrinfo.h"
38 #include "k5-int.h"
39 #include "os-proto.h"
40 #include <stdio.h>
41 #ifdef KRB5_DNS_LOOKUP
42 #ifdef WSHELPER
43 #include <wshelper.h>
44 #else /* WSHELPER */
45 #include <netinet/in.h>
46 #include <arpa/inet.h>
47 #include <arpa/nameser.h>
48 #include <resolv.h>
49 #include <netdb.h>
50 #endif /* WSHELPER */
51 #ifndef T_SRV
52 #define T_SRV 33
53 #endif /* T_SRV */
54 
55 /* for old Unixes and friends ... */
56 #ifndef MAXHOSTNAMELEN
57 #define MAXHOSTNAMELEN 64
58 #endif
59 
60 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
61 
62 /* Solaris Kerberos: no default dns lookups */
63 #define DEFAULT_LOOKUP_KDC 0
64 #define DEFAULT_LOOKUP_REALM 0
65 
66 #ifdef DEBUG
67 /* Solaris Kerberos: want dbg messages to syslog */
68 
69 #define fprintf krbint_dbg_syslog
70 #include <stdarg.h>
71 #include <com_err.h>
72 static int
73 krbint_dbg_syslog(FILE *file, const char *fmt, ...)
74 {
75     va_list args;
76     char err_str[2048];
77 
78     va_start(args, fmt);
79     vsnprintf(err_str, sizeof (err_str), fmt, args);
80     syslog(LOG_DEBUG, err_str);
81     va_end(args);
82 
83     return (0);
84 }
85 #endif
86 
87 static int
88 maybe_use_dns (krb5_context context, const char *name, int defalt)
89 {
90     krb5_error_code code;
91     char * value = NULL;
92     int use_dns = 0;
93 
94     code = profile_get_string(context->profile, "libdefaults",
95                               name, 0, 0, &value);
96     if (value == 0 && code == 0)
97 	code = profile_get_string(context->profile, "libdefaults",
98 				  "dns_fallback", 0, 0, &value);
99     if (code)
100         return defalt;
101 
102     if (value == 0)
103 	return defalt;
104 
105     use_dns = _krb5_conf_boolean(value);
106     profile_release_string(value);
107     return use_dns;
108 }
109 
110 int
111 _krb5_use_dns_kdc(krb5_context context)
112 {
113     return maybe_use_dns (context, "dns_lookup_kdc", DEFAULT_LOOKUP_KDC);
114 }
115 
116 int
117 _krb5_use_dns_realm(krb5_context context)
118 {
119     return maybe_use_dns (context, "dns_lookup_realm", DEFAULT_LOOKUP_REALM);
120 }
121 
122 #endif /* KRB5_DNS_LOOKUP */
123 
124 /*ARGSUSED*/
125 static int get_port (const char *service, int stream, int defalt)
126 {
127 #if 0 /* Only used for "kerberos" and "kerberos-sec", and we want the
128 	 right port numbers even on the OSes that botch the entries in
129 	 /etc/services.  So don't bother with the lookup, except maybe
130 	 to produce a warning.  */
131     struct addrinfo hints = { 0 };
132     struct addrinfo *ai;
133     int err;
134 
135     hints.ai_family = PF_INET;
136     hints.ai_socktype = stream ? SOCK_STREAM : SOCK_DGRAM;
137     err = getaddrinfo (NULL, service, &hints, &ai);
138     if (err == 0 && ai != 0) {
139 	if (ai->ai_addr->sa_family == AF_INET) {
140 	    int port = ((struct sockaddr_in *)ai->ai_addr)->sin_port;
141 	    freeaddrinfo (ai);
142 	    return port;
143 	}
144 	freeaddrinfo (ai);
145     }
146 #endif
147     /* Any error - don't complain, just use default.  */
148     return htons (defalt);
149 }
150 
151 int
152 krb5int_grow_addrlist (struct addrlist *lp, int nmore)
153 {
154     int i;
155     int newspace = lp->space + nmore;
156     size_t newsize = newspace * sizeof (struct addrlist);
157     struct addrinfo **newaddrs;
158 
159     /* NULL check a concession to SunOS4 compatibility for now; not
160        required for pure ANSI support.  */
161     if (lp->addrs)
162 	newaddrs = realloc (lp->addrs, newsize);
163     else
164 	newaddrs = malloc (newsize);
165 
166     if (newaddrs == NULL)
167 	return errno;
168     for (i = lp->space; i < newspace; i++)
169 	newaddrs[i] = NULL;
170     lp->addrs = newaddrs;
171     lp->space = newspace;
172     return 0;
173 }
174 #define grow_list krb5int_grow_addrlist
175 
176 /* Free up everything pointed to by the addrlist structure, but don't
177    free the structure itself.  */
178 void
179 krb5int_free_addrlist (struct addrlist *lp)
180 {
181     int i;
182     for (i = 0; i < lp->naddrs; i++)
183 	freeaddrinfo (lp->addrs[i]);
184     free (lp->addrs);
185     lp->addrs = NULL;
186     lp->naddrs = lp->space = 0;
187 }
188 #define free_list krb5int_free_addrlist
189 
190 static int translate_ai_error (int err)
191 {
192     switch (err) {
193     case 0:
194 	return 0;
195     case EAI_BADFLAGS:
196     case EAI_FAMILY:
197     case EAI_SOCKTYPE:
198     case EAI_SERVICE:
199 	/* All of these indicate bad inputs to getaddrinfo.  */
200 	return EINVAL;
201     case EAI_AGAIN:
202 	/* Translate to standard errno code.  */
203 	return EAGAIN;
204     case EAI_MEMORY:
205 	/* Translate to standard errno code.  */
206 	return ENOMEM;
207 #ifdef EAI_ADDRFAMILY
208     case EAI_ADDRFAMILY:
209 #endif
210 #if EAI_NODATA != EAI_NONAME
211     case EAI_NODATA:
212 #endif
213     case EAI_NONAME:
214 	/* Name not known or no address data, but no error.  Do
215 	   nothing more.  */
216 	return 0;
217 #ifdef EAI_SYSTEM
218     case EAI_SYSTEM:
219 	/* System error, obviously.  */
220 	return errno;
221 #endif
222     default:
223 	/* An error code we haven't handled?  */
224 	return EINVAL;
225     }
226 }
227 
228 static int add_addrinfo_to_list (struct addrlist *lp, struct addrinfo *a)
229 {
230     int err;
231 
232 #ifdef DEBUG
233     switch (a->ai_socktype) {
234     case SOCK_DGRAM:
235 	fprintf(stderr, "\tdgram\n");
236 	break;
237     case SOCK_STREAM:
238 	fprintf(stderr, "\tstream\n");
239 	break;
240     case SOCK_RAW:
241 	fprintf(stderr, "\traw\n");
242 	break;
243     case 0:
244 	break;
245     default:
246 	fprintf(stderr, "\tsocket type %d\n", a->ai_socktype);
247 	break;
248     }
249 #endif
250 
251     if (lp->naddrs == lp->space) {
252 	err = grow_list (lp, 1);
253 	if (err) {
254 #ifdef DEBUG
255 	    fprintf (stderr, "grow_list failed %d\n", err);
256 #endif
257 	    return err;
258 	}
259     }
260     lp->addrs[lp->naddrs++] = a;
261     a->ai_next = 0;
262 #ifdef DEBUG
263     fprintf (stderr, "count is now %d\n", lp->naddrs);
264 #endif
265     return 0;
266 }
267 
268 #define add_host_to_list krb5int_add_host_to_list
269 
270 int
271 krb5int_add_host_to_list (struct addrlist *lp, const char *hostname,
272 			  int port, int secport,
273 			  int socktype, int family)
274 {
275     struct addrinfo *addrs, *a, *anext, hint;
276     int err;
277     char portbuf[10], secportbuf[10];
278 
279 #ifdef DEBUG
280     fprintf (stderr, "adding hostname %s, ports %d,%d\n", hostname,
281 	     ntohs (port), ntohs (secport));
282 #endif
283 
284     memset(&hint, 0, sizeof(hint));
285     hint.ai_family = family;
286     hint.ai_socktype = socktype;
287     sprintf(portbuf, "%d", ntohs(port));
288     sprintf(secportbuf, "%d", ntohs(secport));
289     err = getaddrinfo (hostname, portbuf, &hint, &addrs);
290     if (err)
291 	return translate_ai_error (err);
292     anext = 0;
293     for (a = addrs; a != 0 && err == 0; a = anext) {
294 	anext = a->ai_next;
295 	err = add_addrinfo_to_list (lp, 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     for (a = addrs; a != 0 && err == 0; a = anext) {
310 	anext = a->ai_next;
311 	err = add_addrinfo_to_list (lp, a);
312     }
313 egress:
314     if (anext)
315 	freeaddrinfo (anext);
316     return err;
317 }
318 
319 /*
320  * returns count of number of addresses found
321  * if master is non-NULL, it is filled in with the index of
322  * the master kdc
323  */
324 
325 static krb5_error_code
326 krb5_locate_srv_conf_1(krb5_context context, const krb5_data *realm,
327 		       const char * name, struct addrlist *addrlist,
328 		       int get_masters, int socktype,
329 		       int udpport, int sec_udpport, int family)
330 {
331     const char	*realm_srv_names[4];
332     char **masterlist, **hostlist, *host, *port, *cp;
333     krb5_error_code code;
334     int i, j, count, ismaster;
335 
336 #ifdef DEBUG
337     fprintf (stderr,
338 	     "looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
339 	     realm->data, name, ntohs (udpport), ntohs (sec_udpport));
340 #endif
341 
342     if ((host = malloc(realm->length + 1)) == NULL)
343 	return ENOMEM;
344 
345     strncpy(host, realm->data, realm->length);
346     host[realm->length] = '\0';
347     hostlist = 0;
348 
349     masterlist = NULL;
350 
351     realm_srv_names[0] = "realms";
352     realm_srv_names[1] = host;
353     realm_srv_names[2] = name;
354     realm_srv_names[3] = 0;
355 
356     code = profile_get_values(context->profile, realm_srv_names, &hostlist);
357 
358     if (code) {
359 #ifdef DEBUG
360 	fprintf (stderr, "config file lookup failed: %s\n",
361 		 error_message(code));
362 #endif
363         if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
364 	    code = KRB5_REALM_UNKNOWN;
365  	krb5_xfree(host);
366   	return code;
367      }
368 
369     count = 0;
370     while (hostlist && hostlist[count])
371 	    count++;
372 #ifdef DEBUG
373     fprintf (stderr, "found %d entries under 'kdc'\n", count);
374 #endif
375 
376     if (count == 0) {
377         profile_free_list(hostlist);
378 	krb5_xfree(host);
379 	addrlist->naddrs = 0;
380 	return 0;
381     }
382 
383     if (get_masters) {
384 	realm_srv_names[0] = "realms";
385 	realm_srv_names[1] = host;
386 	realm_srv_names[2] = "admin_server";
387 	realm_srv_names[3] = 0;
388 
389 	code = profile_get_values(context->profile, realm_srv_names,
390 				  &masterlist);
391 
392 	krb5_xfree(host);
393 
394 	if (code == 0) {
395 	    for (i=0; masterlist[i]; i++) {
396 		host = masterlist[i];
397 
398 		/*
399 		 * Strip off excess whitespace
400 		 */
401 		cp = strchr(host, ' ');
402 		if (cp)
403 		    *cp = 0;
404 		cp = strchr(host, '\t');
405 		if (cp)
406 		    *cp = 0;
407 		cp = strchr(host, ':');
408 		if (cp)
409 		    *cp = 0;
410 	    }
411 	}
412     } else {
413 	krb5_xfree(host);
414     }
415 
416     /* at this point, if master is non-NULL, then either the master kdc
417        is required, and there is one, or the master kdc is not required,
418        and there may or may not be one. */
419 
420 #ifdef HAVE_NETINET_IN_H
421     if (sec_udpport)
422 	    count = count * 2;
423 #endif
424 
425     for (i=0; hostlist[i]; i++) {
426 	int p1, p2;
427 
428 	host = hostlist[i];
429 #ifdef DEBUG
430 	fprintf (stderr, "entry %d is '%s'\n", i, host);
431 #endif
432 	/*
433 	 * Strip off excess whitespace
434 	 */
435 	cp = strchr(host, ' ');
436 	if (cp)
437 	    *cp = 0;
438 	cp = strchr(host, '\t');
439 	if (cp)
440 	    *cp = 0;
441 	port = strchr(host, ':');
442 	if (port) {
443 	    *port = 0;
444 	    port++;
445 	}
446 
447 	ismaster = 0;
448 	if (masterlist) {
449 	    for (j=0; masterlist[j]; j++) {
450 		if (strcasecmp(hostlist[i], masterlist[j]) == 0) {
451 		    ismaster = 1;
452 		}
453 	    }
454 	}
455 
456 	if (get_masters && !ismaster)
457 	    continue;
458 
459 	if (port) {
460 	    unsigned long l;
461 #ifdef HAVE_STROUL
462 	    char *endptr;
463 	    l = strtoul (port, &endptr, 10);
464 	    if (endptr == NULL || *endptr != 0)
465 		return EINVAL;
466 #else
467 	    l = atoi (port);
468 #endif
469 	    /* L is unsigned, don't need to check <0.  */
470 	    if (l > 65535)
471 		return EINVAL;
472 	    p1 = htons (l);
473 	    p2 = 0;
474 	} else {
475 	    p1 = udpport;
476 	    p2 = sec_udpport;
477 	}
478 
479 	if (socktype != 0)
480 	    code = add_host_to_list (addrlist, hostlist[i], p1, p2,
481 				     socktype, family);
482 	else {
483 	    code = add_host_to_list (addrlist, hostlist[i], p1, p2,
484 				     SOCK_DGRAM, family);
485 	    if (code == 0)
486 		code = add_host_to_list (addrlist, hostlist[i], p1, p2,
487 					 SOCK_STREAM, family);
488 	}
489 	if (code) {
490 #ifdef DEBUG
491 	    fprintf (stderr, "error %d returned from add_host_to_list\n", code);
492 #endif
493 	    if (hostlist)
494 		profile_free_list (hostlist);
495 	    if (masterlist)
496 		profile_free_list (masterlist);
497 	    return code;
498 	}
499     }
500 
501     if (hostlist)
502         profile_free_list(hostlist);
503     if (masterlist)
504         profile_free_list(masterlist);
505 
506     return 0;
507 }
508 
509 #ifdef KRB5_DNS_LOOKUP
510 
511 #define make_srv_query_realm krb5int_make_srv_query_realm
512 
513 static krb5_error_code
514 krb5_locate_srv_dns_1 (const krb5_data *realm,
515 		       const char *service,
516 		       const char *protocol,
517 		       struct addrlist *addrlist,
518 		       int family)
519 {
520     struct srv_dns_entry *head = NULL;
521     struct srv_dns_entry *entry = NULL, *next;
522     krb5_error_code code = 0;
523 
524     code = make_srv_query_realm(realm, service, protocol, &head);
525     if (code)
526 	return 0;
527 
528     /*
529      * Okay!  Now we've got a linked list of entries sorted by
530      * priority.  Start looking up A records and returning
531      * addresses.
532      */
533 
534     if (head == NULL)
535 	return 0;
536 
537     /* Check for the "." case indicating no support.  */
538     if (head->next == 0 && head->host[0] == 0) {
539 	free(head->host);
540 	free(head);
541 	return KRB5_ERR_NO_SERVICE;
542     }
543 
544 #ifdef DEBUG
545     fprintf (stderr, "walking answer list:\n");
546 #endif
547     for (entry = head; entry != NULL; entry = next) {
548 #ifdef DEBUG
549 	fprintf (stderr, "\tport=%d host=%s\n", entry->port, entry->host);
550 #endif
551 	next = entry->next;
552 	code = add_host_to_list (addrlist, entry->host, htons (entry->port), 0,
553 				 (strcmp("_tcp", protocol)
554 				  ? SOCK_DGRAM
555 				  : SOCK_STREAM), family);
556 	if (code)
557 	    break;
558 	if (entry == head) {
559 	    free(entry->host);
560 	    free(entry);
561 	    head = next;
562 	    entry = 0;
563 	}
564     }
565 #ifdef DEBUG
566     fprintf (stderr, "[end]\n");
567 #endif
568 
569     krb5int_free_srv_dns_data(head);
570     return code;
571 }
572 #endif /* KRB5_DNS_LOOKUP */
573 
574 /*
575  * Wrapper function for the two backends
576  */
577 
578 krb5_error_code
579 krb5int_locate_server (krb5_context context, const krb5_data *realm,
580 		       struct addrlist *addrlist,
581 		       int get_masters,
582 		       const char *profname, const char *dnsname,
583 		       int socktype,
584 		       /* network order port numbers! */
585 		       int dflport1, int dflport2,
586 		       int family)
587 {
588     krb5_error_code code;
589     struct addrlist al = ADDRLIST_INIT;
590 
591     *addrlist = al;
592 
593     /* Solaris Kerberos: skip local file search if profname == NULL */
594     if (profname != NULL) {
595 	/*
596 	 * We always try the local file first
597 	 */
598 	code = krb5_locate_srv_conf_1(context, realm, profname, &al,
599 	    get_masters, socktype, dflport1, dflport2, family);
600     }
601 
602 #ifdef KRB5_DNS_LOOKUP
603     if (code && dnsname != 0) {
604 	int use_dns = _krb5_use_dns_kdc(context);
605 	if (use_dns) {
606 	    code = 0;
607 	    if (socktype == SOCK_DGRAM || socktype == 0) {
608 		code = krb5_locate_srv_dns_1(realm, dnsname, "_udp",
609 					     &al, family);
610 #ifdef DEBUG
611 		if (code)
612 		    fprintf(stderr, "dns udp lookup returned error %d\n",
613 			    code);
614 #endif
615 	    }
616 	    if ((socktype == SOCK_STREAM || socktype == 0) && code == 0) {
617 		code = krb5_locate_srv_dns_1(realm, dnsname, "_tcp",
618 					     &al, family);
619 #ifdef DEBUG
620 		if (code)
621 		    fprintf(stderr, "dns tcp lookup returned error %d\n",
622 			    code);
623 #endif
624 	    }
625 	}
626     }
627 #endif /* KRB5_DNS_LOOKUP */
628 #ifdef DEBUG
629     if (code == 0)
630 	fprintf (stderr, "krb5int_locate_server found %d addresses\n",
631 		 al.naddrs);
632     else
633 	fprintf (stderr, "krb5int_locate_server returning error code %d\n",
634 		 code);
635 #endif
636     if (code != 0) {
637 	if (al.space)
638 	    free_list (&al);
639 	return code;
640     }
641     if (al.naddrs == 0) {	/* No good servers */
642 	if (al.space)
643 	    free_list (&al);
644 	return KRB5_REALM_CANT_RESOLVE;
645     }
646     *addrlist = al;
647     return 0;
648 }
649 
650 krb5_error_code
651 krb5_locate_kdc(krb5_context context, const krb5_data *realm,
652 		struct addrlist *addrlist,
653 		int get_masters, int socktype, int family)
654 {
655     int udpport, sec_udpport;
656 
657     udpport = get_port (KDC_PORTNAME, 0, KRB5_DEFAULT_PORT);
658     if (socktype == SOCK_STREAM)
659 	sec_udpport = 0;
660     else {
661 	sec_udpport = get_port (KDC_SECONDARY_PORTNAME, 0,
662 				(udpport == htons (KRB5_DEFAULT_PORT)
663 				 ? KRB5_DEFAULT_SEC_PORT
664 				 : KRB5_DEFAULT_PORT));
665 	if (sec_udpport == udpport)
666 	    sec_udpport = 0;
667     }
668 
669     return krb5int_locate_server(context, realm, addrlist, get_masters, "kdc",
670 				 (get_masters
671 				  ? "_kerberos-master"
672 				  : "_kerberos"),
673 				 socktype, udpport, sec_udpport, family);
674 }
675 
676 /*
677  * Solaris Kerberos: for backward compat.  Avoid using this
678  * function!
679  */
680 krb5_error_code
681 krb5_get_servername(krb5_context context,
682     const krb5_data *realm,
683     const char *name, const char *proto,
684     char *srvhost,
685     unsigned short *port)
686 {
687     krb5_error_code code = KRB5_REALM_UNKNOWN;
688 
689 #ifdef KRB5_DNS_LOOKUP
690     {
691 	int use_dns = _krb5_use_dns_kdc(context);
692 
693 	if (use_dns) {
694 	    struct srv_dns_entry *head = NULL;
695 
696 	    code = make_srv_query_realm(realm, name, proto, &head);
697 	    if (code)
698 		return (code);
699 
700 	    if (head == NULL)
701 		return KRB5_REALM_CANT_RESOLVE;
702 
703 	    *port = head->port;
704 	    (void) strlcpy(srvhost, head->host, MAX_DNS_NAMELEN);
705 
706 #ifdef DEBUG
707 	    fprintf (stderr, "krb5_get_servername svrhost %s, port %d\n",
708 		srvhost, *port);
709 #endif
710 	    krb5int_free_srv_dns_data(head);
711 	}
712     }
713 #endif /* KRB5_DNS_LOOKUP */
714 
715     return (code);
716 }
717