xref: /netbsd-src/crypto/external/bsd/heimdal/dist/kdc/connect.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: connect.c,v 1.3 2018/04/29 05:36:04 spz Exp $	*/
2 
3 /*
4  * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
5  * (Royal Institute of Technology, Stockholm, Sweden).
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * 3. Neither the name of the Institute nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include "kdc_locl.h"
37 
38 /*
39  * a tuple describing on what to listen
40  */
41 
42 struct port_desc{
43     int family;
44     int type;
45     int port;
46 };
47 
48 /* the current ones */
49 
50 static struct port_desc *ports;
51 static size_t num_ports;
52 static pid_t bonjour_pid = -1;
53 
54 /*
55  * add `family, port, protocol' to the list with duplicate suppresion.
56  */
57 
58 static void
59 add_port(krb5_context context,
60 	 int family, int port, const char *protocol)
61 {
62     int type;
63     size_t i;
64 
65     if(strcmp(protocol, "udp") == 0)
66 	type = SOCK_DGRAM;
67     else if(strcmp(protocol, "tcp") == 0)
68 	type = SOCK_STREAM;
69     else
70 	return;
71     for(i = 0; i < num_ports; i++){
72 	if(ports[i].type == type
73 	   && ports[i].port == port
74 	   && ports[i].family == family)
75 	    return;
76     }
77     ports = realloc(ports, (num_ports + 1) * sizeof(*ports));
78     if (ports == NULL)
79 	krb5_err (context, 1, errno, "realloc");
80     ports[num_ports].family = family;
81     ports[num_ports].type   = type;
82     ports[num_ports].port   = port;
83     num_ports++;
84 }
85 
86 /*
87  * add a triple but with service -> port lookup
88  * (this prints warnings for stuff that does not exist)
89  */
90 
91 static void
92 add_port_service(krb5_context context,
93 		 int family, const char *service, int port,
94 		 const char *protocol)
95 {
96     port = krb5_getportbyname (context, service, protocol, port);
97     add_port (context, family, port, protocol);
98 }
99 
100 /*
101  * add the port with service -> port lookup or string -> number
102  * (no warning is printed)
103  */
104 
105 static void
106 add_port_string (krb5_context context,
107 		 int family, const char *str, const char *protocol)
108 {
109     struct servent *sp;
110     int port;
111 
112     sp = roken_getservbyname (str, protocol);
113     if (sp != NULL) {
114 	port = sp->s_port;
115     } else {
116 	char *end;
117 
118 	port = htons(strtol(str, &end, 0));
119 	if (end == str)
120 	    return;
121     }
122     add_port (context, family, port, protocol);
123 }
124 
125 /*
126  * add the standard collection of ports for `family'
127  */
128 
129 static void
130 add_standard_ports (krb5_context context,
131 		    krb5_kdc_configuration *config,
132 		    int family)
133 {
134     add_port_service(context, family, "kerberos", 88, "udp");
135     add_port_service(context, family, "kerberos", 88, "tcp");
136     add_port_service(context, family, "kerberos-sec", 88, "udp");
137     add_port_service(context, family, "kerberos-sec", 88, "tcp");
138     if(enable_http)
139 	add_port_service(context, family, "http", 80, "tcp");
140     if(config->enable_kx509) {
141 	add_port_service(context, family, "kca_service", 9878, "udp");
142 	add_port_service(context, family, "kca_service", 9878, "tcp");
143     }
144 
145 }
146 
147 /*
148  * parse the set of space-delimited ports in `str' and add them.
149  * "+" => all the standard ones
150  * otherwise it's port|service[/protocol]
151  */
152 
153 static void
154 parse_ports(krb5_context context,
155 	    krb5_kdc_configuration *config,
156 	    const char *str)
157 {
158     char *pos = NULL;
159     char *p;
160     char *str_copy = strdup (str);
161 
162     p = strtok_r(str_copy, " \t", &pos);
163     while(p != NULL) {
164 	if(strcmp(p, "+") == 0) {
165 #ifdef HAVE_IPV6
166 	    add_standard_ports(context, config, AF_INET6);
167 #endif
168 	    add_standard_ports(context, config, AF_INET);
169 	} else {
170 	    char *q = strchr(p, '/');
171 	    if(q){
172 		*q++ = 0;
173 #ifdef HAVE_IPV6
174 		add_port_string(context, AF_INET6, p, q);
175 #endif
176 		add_port_string(context, AF_INET, p, q);
177 	    }else {
178 #ifdef HAVE_IPV6
179 		add_port_string(context, AF_INET6, p, "udp");
180 		add_port_string(context, AF_INET6, p, "tcp");
181 #endif
182 		add_port_string(context, AF_INET, p, "udp");
183 		add_port_string(context, AF_INET, p, "tcp");
184 	    }
185 	}
186 
187 	p = strtok_r(NULL, " \t", &pos);
188     }
189     free (str_copy);
190 }
191 
192 /*
193  * every socket we listen on
194  */
195 
196 struct descr {
197     krb5_socket_t s;
198     int type;
199     int port;
200     unsigned char *buf;
201     size_t size;
202     size_t len;
203     time_t timeout;
204     struct sockaddr_storage __ss;
205     struct sockaddr *sa;
206     socklen_t sock_len;
207     char addr_string[128];
208 };
209 
210 static void
211 init_descr(struct descr *d)
212 {
213     memset(d, 0, sizeof(*d));
214     d->sa = (struct sockaddr *)&d->__ss;
215     d->s = rk_INVALID_SOCKET;
216 }
217 
218 /*
219  * re-initialize all `n' ->sa in `d'.
220  */
221 
222 static void
223 reinit_descrs (struct descr *d, int n)
224 {
225     int i;
226 
227     for (i = 0; i < n; ++i)
228 	d[i].sa = (struct sockaddr *)&d[i].__ss;
229 }
230 
231 /*
232  * Create the socket (family, type, port) in `d'
233  */
234 
235 static void
236 init_socket(krb5_context context,
237 	    krb5_kdc_configuration *config,
238 	    struct descr *d, krb5_address *a, int family, int type, int port)
239 {
240     krb5_error_code ret;
241     struct sockaddr_storage __ss;
242     struct sockaddr *sa = (struct sockaddr *)&__ss;
243     krb5_socklen_t sa_size = sizeof(__ss);
244 
245     init_descr (d);
246 
247     ret = krb5_addr2sockaddr (context, a, sa, &sa_size, port);
248     if (ret) {
249 	krb5_warn(context, ret, "krb5_addr2sockaddr");
250 	rk_closesocket(d->s);
251 	d->s = rk_INVALID_SOCKET;
252 	return;
253     }
254 
255     if (sa->sa_family != family)
256 	return;
257 
258     d->s = socket(family, type, 0);
259     if(rk_IS_BAD_SOCKET(d->s)){
260 	krb5_warn(context, errno, "socket(%d, %d, 0)", family, type);
261 	d->s = rk_INVALID_SOCKET;
262 	return;
263     }
264     rk_cloexec(d->s);
265 #if defined(HAVE_SETSOCKOPT) && defined(SOL_SOCKET) && defined(SO_REUSEADDR)
266     {
267 	int one = 1;
268 	setsockopt(d->s, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
269     }
270 #endif
271     d->type = type;
272     d->port = port;
273 
274     socket_set_nonblocking(d->s, 1);
275 
276     if(rk_IS_SOCKET_ERROR(bind(d->s, sa, sa_size))){
277 	char a_str[256];
278 	size_t len;
279 
280 	krb5_print_address (a, a_str, sizeof(a_str), &len);
281 	krb5_warn(context, errno, "bind %s/%d", a_str, ntohs(port));
282 	rk_closesocket(d->s);
283 	d->s = rk_INVALID_SOCKET;
284 	return;
285     }
286     if(type == SOCK_STREAM && rk_IS_SOCKET_ERROR(listen(d->s, SOMAXCONN))){
287 	char a_str[256];
288 	size_t len;
289 
290 	krb5_print_address (a, a_str, sizeof(a_str), &len);
291 	krb5_warn(context, errno, "listen %s/%d", a_str, ntohs(port));
292 	rk_closesocket(d->s);
293 	d->s = rk_INVALID_SOCKET;
294 	return;
295     }
296 }
297 
298 /*
299  * Allocate descriptors for all the sockets that we should listen on
300  * and return the number of them.
301  */
302 
303 static int
304 init_sockets(krb5_context context,
305 	     krb5_kdc_configuration *config,
306 	     struct descr **desc)
307 {
308     krb5_error_code ret;
309     size_t i, j;
310     struct descr *d;
311     int num = 0;
312     krb5_addresses addresses;
313 
314     if (explicit_addresses.len) {
315 	addresses = explicit_addresses;
316     } else {
317 	ret = krb5_get_all_server_addrs (context, &addresses);
318 	if (ret)
319 	    krb5_err (context, 1, ret, "krb5_get_all_server_addrs");
320     }
321     parse_ports(context, config, port_str);
322     d = malloc(addresses.len * num_ports * sizeof(*d));
323     if (d == NULL)
324 	krb5_errx(context, 1, "malloc(%lu) failed",
325 		  (unsigned long)num_ports * sizeof(*d));
326 
327     for (i = 0; i < num_ports; i++){
328 	for (j = 0; j < addresses.len; ++j) {
329 	    init_socket(context, config, &d[num], &addresses.val[j],
330 			ports[i].family, ports[i].type, ports[i].port);
331 	    if(d[num].s != rk_INVALID_SOCKET){
332 		char a_str[80];
333 		size_t len;
334 
335 		krb5_print_address (&addresses.val[j], a_str,
336 				    sizeof(a_str), &len);
337 
338 		kdc_log(context, config, 5, "listening on %s port %u/%s",
339 			a_str,
340 			ntohs(ports[i].port),
341 			(ports[i].type == SOCK_STREAM) ? "tcp" : "udp");
342 		/* XXX */
343 		num++;
344 	    }
345 	}
346     }
347     krb5_free_addresses (context, &addresses);
348     d = realloc(d, num * sizeof(*d));
349     if (d == NULL && num != 0)
350 	krb5_errx(context, 1, "realloc(%lu) failed",
351 		  (unsigned long)num * sizeof(*d));
352     reinit_descrs (d, num);
353     *desc = d;
354     return num;
355 }
356 
357 /*
358  *
359  */
360 
361 static const char *
362 descr_type(struct descr *d)
363 {
364     if (d->type == SOCK_DGRAM)
365 	return "udp";
366     else if (d->type == SOCK_STREAM)
367 	return "tcp";
368     return "unknown";
369 }
370 
371 static void
372 addr_to_string(krb5_context context,
373 	       struct sockaddr *addr, size_t addr_len, char *str, size_t len)
374 {
375     krb5_address a;
376     if(krb5_sockaddr2address(context, addr, &a) == 0) {
377 	if(krb5_print_address(&a, str, len, &len) == 0) {
378 	    krb5_free_address(context, &a);
379 	    return;
380 	}
381 	krb5_free_address(context, &a);
382     }
383     snprintf(str, len, "<family=%d>", addr->sa_family);
384 }
385 
386 /*
387  *
388  */
389 
390 static void
391 send_reply(krb5_context context,
392 	   krb5_kdc_configuration *config,
393 	   krb5_boolean prependlength,
394 	   struct descr *d,
395 	   krb5_data *reply)
396 {
397     kdc_log(context, config, 5,
398 	    "sending %lu bytes to %s", (unsigned long)reply->length,
399 	    d->addr_string);
400     if(prependlength){
401 	unsigned char l[4];
402 	l[0] = (reply->length >> 24) & 0xff;
403 	l[1] = (reply->length >> 16) & 0xff;
404 	l[2] = (reply->length >> 8) & 0xff;
405 	l[3] = reply->length & 0xff;
406 	if(rk_IS_SOCKET_ERROR(sendto(d->s, l, sizeof(l), 0, d->sa, d->sock_len))) {
407 	    kdc_log (context, config,
408 		     0, "sendto(%s): %s", d->addr_string,
409 		     strerror(rk_SOCK_ERRNO));
410 	    return;
411 	}
412     }
413     if(rk_IS_SOCKET_ERROR(sendto(d->s, reply->data, reply->length, 0, d->sa, d->sock_len))) {
414 	kdc_log (context, config, 0, "sendto(%s): %s", d->addr_string,
415 		 strerror(rk_SOCK_ERRNO));
416 	return;
417     }
418 }
419 
420 /*
421  * Handle the request in `buf, len' to socket `d'
422  */
423 
424 static void
425 do_request(krb5_context context,
426 	   krb5_kdc_configuration *config,
427 	   void *buf, size_t len, krb5_boolean prependlength,
428 	   struct descr *d)
429 {
430     krb5_error_code ret;
431     krb5_data reply;
432     int datagram_reply = (d->type == SOCK_DGRAM);
433 
434     krb5_kdc_update_time(NULL);
435 
436     krb5_data_zero(&reply);
437     ret = krb5_kdc_process_request(context, config,
438 				   buf, len, &reply, &prependlength,
439 				   d->addr_string, d->sa,
440 				   datagram_reply);
441     if(request_log)
442 	krb5_kdc_save_request(context, request_log, buf, len, &reply, d->sa);
443     if(reply.length){
444 	send_reply(context, config, prependlength, d, &reply);
445 	krb5_data_free(&reply);
446     }
447     if(ret)
448 	kdc_log(context, config, 0,
449 		"Failed processing %lu byte request from %s",
450 		(unsigned long)len, d->addr_string);
451 }
452 
453 /*
454  * Handle incoming data to the UDP socket in `d'
455  */
456 
457 static void
458 handle_udp(krb5_context context,
459 	   krb5_kdc_configuration *config,
460 	   struct descr *d)
461 {
462     unsigned char *buf;
463     ssize_t n;
464 
465     buf = malloc(max_request_udp);
466     if (buf == NULL){
467 	kdc_log(context, config, 0, "Failed to allocate %lu bytes",
468 	        (unsigned long)max_request_udp);
469 	return;
470     }
471 
472     d->sock_len = sizeof(d->__ss);
473     n = recvfrom(d->s, buf, max_request_udp, 0, d->sa, &d->sock_len);
474     if (rk_IS_SOCKET_ERROR(n)) {
475 	if (rk_SOCK_ERRNO != EAGAIN && rk_SOCK_ERRNO != EINTR)
476 	    krb5_warn(context, rk_SOCK_ERRNO, "recvfrom");
477     } else {
478 	addr_to_string (context, d->sa, d->sock_len,
479 			d->addr_string, sizeof(d->addr_string));
480 	if ((size_t)n == max_request_udp) {
481 	    krb5_data data;
482 	    krb5_warn(context, errno,
483 		      "recvfrom: truncated packet from %s, asking for TCP",
484 		      d->addr_string);
485 	    krb5_mk_error(context,
486 			  KRB5KRB_ERR_RESPONSE_TOO_BIG,
487 			  NULL,
488 			  NULL,
489 			  NULL,
490 			  NULL,
491 			  NULL,
492 			  NULL,
493 			  &data);
494 	    send_reply(context, config, FALSE, d, &data);
495 	    krb5_data_free(&data);
496 	} else {
497 	    do_request(context, config, buf, n, FALSE, d);
498 	}
499     }
500     free (buf);
501 }
502 
503 static void
504 clear_descr(struct descr *d)
505 {
506     if(d->buf)
507 	memset(d->buf, 0, d->size);
508     d->len = 0;
509     if(d->s != rk_INVALID_SOCKET)
510 	rk_closesocket(d->s);
511     d->s = rk_INVALID_SOCKET;
512 }
513 
514 
515 /* remove HTTP %-quoting from buf */
516 static int
517 de_http(char *buf)
518 {
519     unsigned char *p, *q;
520     for(p = q = (unsigned char *)buf; *p; p++, q++) {
521 	if(*p == '%' && isxdigit(p[1]) && isxdigit(p[2])) {
522 	    unsigned int x;
523 	    if(sscanf((char *)p + 1, "%2x", &x) != 1)
524 		return -1;
525 	    *q = x;
526 	    p += 2;
527 	} else
528 	    *q = *p;
529     }
530     *q = '\0';
531     return 0;
532 }
533 
534 #define TCP_TIMEOUT 4
535 
536 /*
537  * accept a new TCP connection on `d[parent]' and store it in `d[child]'
538  */
539 
540 static void
541 add_new_tcp (krb5_context context,
542 	     krb5_kdc_configuration *config,
543 	     struct descr *d, int parent, int child)
544 {
545     krb5_socket_t s;
546 
547     if (child == -1)
548 	return;
549 
550     d[child].sock_len = sizeof(d[child].__ss);
551     s = accept(d[parent].s, d[child].sa, &d[child].sock_len);
552     if(rk_IS_BAD_SOCKET(s)) {
553 	if (rk_SOCK_ERRNO != EAGAIN && rk_SOCK_ERRNO != EINTR)
554 	    krb5_warn(context, rk_SOCK_ERRNO, "accept");
555 	return;
556     }
557 
558 #ifdef FD_SETSIZE
559     if (s >= FD_SETSIZE) {
560 	krb5_warnx(context, "socket FD too large");
561 	rk_closesocket (s);
562 	return;
563     }
564 #endif
565 
566     d[child].s = s;
567     d[child].timeout = time(NULL) + TCP_TIMEOUT;
568     d[child].type = SOCK_STREAM;
569     addr_to_string (context,
570 		    d[child].sa, d[child].sock_len,
571 		    d[child].addr_string, sizeof(d[child].addr_string));
572 }
573 
574 /*
575  * Grow `d' to handle at least `n'.
576  * Return != 0 if fails
577  */
578 
579 static int
580 grow_descr (krb5_context context,
581 	    krb5_kdc_configuration *config,
582 	    struct descr *d, size_t n)
583 {
584     if (d->size - d->len < n) {
585 	unsigned char *tmp;
586 	size_t grow;
587 
588 	grow = max(1024, d->len + n);
589 	if (d->size + grow > max_request_tcp) {
590 	    kdc_log(context, config, 0, "Request exceeds max request size (%lu bytes).",
591 		    (unsigned long)d->size + grow);
592 	    clear_descr(d);
593 	    return -1;
594 	}
595 	tmp = realloc (d->buf, d->size + grow);
596 	if (tmp == NULL) {
597 	    kdc_log(context, config, 0, "Failed to re-allocate %lu bytes.",
598 		    (unsigned long)d->size + grow);
599 	    clear_descr(d);
600 	    return -1;
601 	}
602 	d->size += grow;
603 	d->buf = tmp;
604     }
605     return 0;
606 }
607 
608 /*
609  * Try to handle the TCP data at `d->buf, d->len'.
610  * Return -1 if failed, 0 if succesful, and 1 if data is complete.
611  */
612 
613 static int
614 handle_vanilla_tcp (krb5_context context,
615 		    krb5_kdc_configuration *config,
616 		    struct descr *d)
617 {
618     krb5_storage *sp;
619     uint32_t len;
620 
621     sp = krb5_storage_from_mem(d->buf, d->len);
622     if (sp == NULL) {
623 	kdc_log (context, config, 0, "krb5_storage_from_mem failed");
624 	return -1;
625     }
626     krb5_ret_uint32(sp, &len);
627     krb5_storage_free(sp);
628     if(d->len - 4 >= len) {
629 	memmove(d->buf, d->buf + 4, d->len - 4);
630 	d->len -= 4;
631 	return 1;
632     }
633     return 0;
634 }
635 
636 /*
637  * Try to handle the TCP/HTTP data at `d->buf, d->len'.
638  * Return -1 if failed, 0 if succesful, and 1 if data is complete.
639  */
640 
641 static int
642 handle_http_tcp (krb5_context context,
643 		 krb5_kdc_configuration *config,
644 		 struct descr *d)
645 {
646     char *s, *p, *t;
647     void *data;
648     char *proto;
649     int len;
650 
651     s = (char *)d->buf;
652 
653     /* If its a multi line query, truncate off the first line */
654     p = strstr(s, "\r\n");
655     if (p)
656 	*p = 0;
657 
658     p = NULL;
659     t = strtok_r(s, " \t", &p);
660     if (t == NULL) {
661 	kdc_log(context, config, 0,
662 		"Missing HTTP operand (GET) request from %s", d->addr_string);
663 	return -1;
664     }
665 
666     t = strtok_r(NULL, " \t", &p);
667     if(t == NULL) {
668 	kdc_log(context, config, 0,
669 		"Missing HTTP GET data in request from %s", d->addr_string);
670 	return -1;
671     }
672 
673     data = malloc(strlen(t));
674     if (data == NULL) {
675 	kdc_log(context, config, 0, "Failed to allocate %lu bytes",
676 		(unsigned long)strlen(t));
677 	return -1;
678     }
679     if(*t == '/')
680 	t++;
681     if(de_http(t) != 0) {
682 	kdc_log(context, config, 0, "Malformed HTTP request from %s", d->addr_string);
683 	kdc_log(context, config, 5, "HTTP request: %s", t);
684 	free(data);
685 	return -1;
686     }
687     proto = strtok_r(NULL, " \t", &p);
688     if (proto == NULL) {
689 	kdc_log(context, config, 0, "Malformed HTTP request from %s", d->addr_string);
690 	free(data);
691 	return -1;
692     }
693     len = rk_base64_decode(t, data);
694     if(len <= 0){
695 	const char *msg =
696 	    " 404 Not found\r\n"
697 	    "Server: Heimdal/" VERSION "\r\n"
698 	    "Cache-Control: no-cache\r\n"
699 	    "Pragma: no-cache\r\n"
700 	    "Content-type: text/html\r\n"
701 	    "Content-transfer-encoding: 8bit\r\n\r\n"
702 	    "<TITLE>404 Not found</TITLE>\r\n"
703 	    "<H1>404 Not found</H1>\r\n"
704 	    "That page doesn't exist, maybe you are looking for "
705 	    "<A HREF=\"http://www.h5l.org/\">Heimdal</A>?\r\n";
706 	kdc_log(context, config, 0, "HTTP request from %s is non KDC request", d->addr_string);
707 	kdc_log(context, config, 5, "HTTP request: %s", t);
708 	free(data);
709 	if (rk_IS_SOCKET_ERROR(send(d->s, proto, strlen(proto), 0))) {
710 	    kdc_log(context, config, 0, "HTTP write failed: %s: %s",
711 		    d->addr_string, strerror(rk_SOCK_ERRNO));
712 	    return -1;
713 	}
714 	if (rk_IS_SOCKET_ERROR(send(d->s, msg, strlen(msg), 0))) {
715 	    kdc_log(context, config, 0, "HTTP write failed: %s: %s",
716 		    d->addr_string, strerror(rk_SOCK_ERRNO));
717 	    return -1;
718 	}
719 	return -1;
720     }
721     {
722 	const char *msg =
723 	    " 200 OK\r\n"
724 	    "Server: Heimdal/" VERSION "\r\n"
725 	    "Cache-Control: no-cache\r\n"
726 	    "Pragma: no-cache\r\n"
727 	    "Content-type: application/octet-stream\r\n"
728 	    "Content-transfer-encoding: binary\r\n\r\n";
729 	if (rk_IS_SOCKET_ERROR(send(d->s, proto, strlen(proto), 0))) {
730 	    free(data);
731 	    kdc_log(context, config, 0, "HTTP write failed: %s: %s",
732 		    d->addr_string, strerror(rk_SOCK_ERRNO));
733 	    return -1;
734 	}
735 	if (rk_IS_SOCKET_ERROR(send(d->s, msg, strlen(msg), 0))) {
736 	    free(data);
737 	    kdc_log(context, config, 0, "HTTP write failed: %s: %s",
738 		    d->addr_string, strerror(rk_SOCK_ERRNO));
739 	    return -1;
740 	}
741     }
742     if ((size_t)len > d->len)
743         len = d->len;
744     memcpy(d->buf, data, len);
745     d->len = len;
746     free(data);
747     return 1;
748 }
749 
750 /*
751  * Handle incoming data to the TCP socket in `d[index]'
752  */
753 
754 static void
755 handle_tcp(krb5_context context,
756 	   krb5_kdc_configuration *config,
757 	   struct descr *d, int idx, int min_free)
758 {
759     unsigned char buf[1024];
760     int n;
761     int ret = 0;
762 
763     if (d[idx].timeout == 0) {
764 	add_new_tcp (context, config, d, idx, min_free);
765 	return;
766     }
767 
768     n = recvfrom(d[idx].s, buf, sizeof(buf), 0, NULL, NULL);
769     if(rk_IS_SOCKET_ERROR(n)){
770 	krb5_warn(context, rk_SOCK_ERRNO, "recvfrom failed from %s to %s/%d",
771 		  d[idx].addr_string, descr_type(d + idx),
772 		  ntohs(d[idx].port));
773 	return;
774     } else if (n == 0) {
775 	krb5_warnx(context, "connection closed before end of data after %lu "
776 		   "bytes from %s to %s/%d", (unsigned long)d[idx].len,
777 		   d[idx].addr_string, descr_type(d + idx),
778 		   ntohs(d[idx].port));
779 	clear_descr (d + idx);
780 	return;
781     }
782     if (grow_descr (context, config, &d[idx], n))
783 	return;
784     memcpy(d[idx].buf + d[idx].len, buf, n);
785     d[idx].len += n;
786     if(d[idx].len > 4 && d[idx].buf[0] == 0) {
787 	ret = handle_vanilla_tcp (context, config, &d[idx]);
788     } else if(enable_http &&
789 	      d[idx].len >= 4 &&
790 	      strncmp((char *)d[idx].buf, "GET ", 4) == 0 &&
791 	      strncmp((char *)d[idx].buf + d[idx].len - 4,
792 		      "\r\n\r\n", 4) == 0) {
793 
794         /* remove the trailing \r\n\r\n so the string is NUL terminated */
795         d[idx].buf[d[idx].len - 4] = '\0';
796 
797 	ret = handle_http_tcp (context, config, &d[idx]);
798 	if (ret < 0)
799 	    clear_descr (d + idx);
800     } else if (d[idx].len > 4) {
801 	kdc_log (context, config,
802 		 0, "TCP data of strange type from %s to %s/%d",
803 		 d[idx].addr_string, descr_type(d + idx),
804 		 ntohs(d[idx].port));
805 	if (d[idx].buf[0] & 0x80) {
806 	    krb5_data reply;
807 
808 	    kdc_log (context, config, 0, "TCP extension not supported");
809 
810 	    ret = krb5_mk_error(context,
811 				KRB5KRB_ERR_FIELD_TOOLONG,
812 				NULL,
813 				NULL,
814 				NULL,
815 				NULL,
816 				NULL,
817 				NULL,
818 				&reply);
819 	    if (ret == 0) {
820 		send_reply(context, config, TRUE, d + idx, &reply);
821 		krb5_data_free(&reply);
822 	    }
823 	}
824 	clear_descr(d + idx);
825 	return;
826     }
827     if (ret < 0)
828 	return;
829     else if (ret == 1) {
830 	do_request(context, config,
831 		   d[idx].buf, d[idx].len, TRUE, &d[idx]);
832 	clear_descr(d + idx);
833     }
834 }
835 
836 #ifdef HAVE_FORK
837 static void
838 handle_islive(int fd)
839 {
840     char buf;
841     int ret;
842 
843     ret = read(fd, &buf, 1);
844     if (ret != 1)
845 	exit_flag = -1;
846 }
847 #endif
848 
849 krb5_boolean
850 realloc_descrs(struct descr **d, unsigned int *ndescr)
851 {
852     struct descr *tmp;
853     size_t i;
854 
855     tmp = realloc(*d, (*ndescr + 4) * sizeof(**d));
856     if(tmp == NULL)
857         return FALSE;
858 
859     *d = tmp;
860     reinit_descrs (*d, *ndescr);
861     memset(*d + *ndescr, 0, 4 * sizeof(**d));
862     for(i = *ndescr; i < *ndescr + 4; i++)
863         init_descr (*d + i);
864 
865     *ndescr += 4;
866 
867     return TRUE;
868 }
869 
870 int
871 next_min_free(krb5_context context, struct descr **d, unsigned int *ndescr)
872 {
873     size_t i;
874     int min_free;
875 
876     for(i = 0; i < *ndescr; i++) {
877         int s = (*d + i)->s;
878         if(rk_IS_BAD_SOCKET(s))
879             return i;
880     }
881 
882     min_free = *ndescr;
883     if(!realloc_descrs(d, ndescr)) {
884         min_free = -1;
885         krb5_warnx(context, "No memory");
886     }
887 
888     return min_free;
889 }
890 
891 static void
892 loop(krb5_context context, krb5_kdc_configuration *config,
893      struct descr *d, unsigned int ndescr, int islive)
894 {
895 
896     while (exit_flag == 0) {
897 	struct timeval tmout;
898 	fd_set fds;
899 	int min_free = -1;
900 	int max_fd = 0;
901 	size_t i;
902 
903 	FD_ZERO(&fds);
904         if (islive > -1) {
905             FD_SET(islive, &fds);
906             max_fd = islive;
907         }
908 	for (i = 0; i < ndescr; i++) {
909 	    if (!rk_IS_BAD_SOCKET(d[i].s)) {
910 		if (d[i].type == SOCK_STREAM &&
911 		   d[i].timeout && d[i].timeout < time(NULL)) {
912 		    kdc_log(context, config, 1,
913 			    "TCP-connection from %s expired after %lu bytes",
914 			    d[i].addr_string, (unsigned long)d[i].len);
915 		    clear_descr(&d[i]);
916 		    continue;
917 		}
918 #ifndef NO_LIMIT_FD_SETSIZE
919 		if (max_fd < d[i].s)
920 		    max_fd = d[i].s;
921 #ifdef FD_SETSIZE
922 		if (max_fd >= FD_SETSIZE)
923 		    krb5_errx(context, 1, "fd too large");
924 #endif
925 #endif
926 		FD_SET(d[i].s, &fds);
927 	    }
928 	}
929 
930 	tmout.tv_sec = TCP_TIMEOUT;
931 	tmout.tv_usec = 0;
932 	switch(select(max_fd + 1, &fds, 0, 0, &tmout)){
933 	case 0:
934 	    break;
935 	case -1:
936 	    if (errno != EINTR)
937 		krb5_warn(context, rk_SOCK_ERRNO, "select");
938 	    break;
939 	default:
940 #ifdef HAVE_FORK
941 	    if (islive > -1 && FD_ISSET(islive, &fds))
942 		handle_islive(islive);
943 #endif
944 	    for (i = 0; i < ndescr; i++)
945 		if (!rk_IS_BAD_SOCKET(d[i].s) && FD_ISSET(d[i].s, &fds)) {
946 		    min_free = next_min_free(context, &d, &ndescr);
947 
948 		    if (d[i].type == SOCK_DGRAM)
949 			handle_udp(context, config, &d[i]);
950 		    else if (d[i].type == SOCK_STREAM)
951 			handle_tcp(context, config, d, i, min_free);
952 		}
953 	}
954     }
955 
956     switch (exit_flag) {
957     case -1:
958 	kdc_log(context, config, 0,
959                 "KDC worker process exiting because KDC master exited.");
960 	break;
961 #ifdef SIGXCPU
962     case SIGXCPU:
963 	kdc_log(context, config, 0, "CPU time limit exceeded");
964 	break;
965 #endif
966     case SIGINT:
967     case SIGTERM:
968 	kdc_log(context, config, 0, "Terminated");
969 	break;
970     default:
971 	kdc_log(context, config, 0, "Unexpected exit reason: %d", exit_flag);
972 	break;
973     }
974 }
975 
976 #ifdef __APPLE__
977 static void
978 bonjour_kid(krb5_context context, krb5_kdc_configuration *config, const char *argv0, int *islive)
979 {
980     char buf;
981 
982     if (do_bonjour > 0) {
983 	bonjour_announce(context, config);
984 
985 	while (read(0, &buf, 1) == 1)
986 	    continue;
987 	_exit(0);
988     }
989 
990     if ((bonjour_pid = fork()) != 0)
991 	return;
992 
993     close(islive[0]);
994     if (dup2(islive[1], 0) == -1)
995 	err(1, "failed to announce with bonjour (dup)");
996     if (islive[1] != 0)
997         close(islive[1]);
998     execlp(argv0, "kdc", "--bonjour", NULL);
999     err(1, "failed to announce with bonjour (exec)");
1000 }
1001 #endif
1002 
1003 #ifdef HAVE_FORK
1004 static void
1005 kill_kids(pid_t *pids, int max_kids, int sig)
1006 {
1007     int i;
1008 
1009     for (i=0; i < max_kids; i++)
1010 	if (pids[i] > 0)
1011 	    kill(sig, pids[i]);
1012     if (bonjour_pid > 0)
1013         kill(sig, bonjour_pid);
1014 }
1015 
1016 static int
1017 reap_kid(krb5_context context, krb5_kdc_configuration *config,
1018 	 pid_t *pids, int max_kids, int options)
1019 {
1020     pid_t pid;
1021     char *what;
1022     int status;
1023     int i = 0; /* quiet warnings */
1024 
1025     pid = waitpid(-1, &status, options);
1026     if (pid < 1)
1027 	return 0;
1028 
1029     if (pid != bonjour_pid) {
1030         for (i=0; i < max_kids; i++) {
1031             if (pids[i] == pid)
1032                 break;
1033         }
1034 
1035         if (i == max_kids) {
1036             /* XXXrcd: this should not happen, have to do something, though */
1037             return 0;
1038         }
1039     }
1040 
1041     if (pid == bonjour_pid)
1042         what = "bonjour";
1043     else
1044         what = "worker";
1045     if (WIFEXITED(status))
1046         kdc_log(context, config, 0, "KDC reaped %s process: %d, exit status: %d",
1047                 what, (int)pid, WEXITSTATUS(status));
1048     else if (WIFSIGNALED(status))
1049         kdc_log(context, config, 0, "KDC reaped %s process: %d, term signal %d%s",
1050                 what, (int)pid, WTERMSIG(status),
1051                 WCOREDUMP(status) ? " (core dumped)" : "");
1052     else
1053         kdc_log(context, config, 0, "KDC reaped %s process: %d",
1054                 what, (int)pid);
1055     if (pid == bonjour_pid) {
1056         bonjour_pid = (pid_t)-1;
1057         return 0;
1058     } else {
1059         pids[i] = (pid_t)-1;
1060         return 1;
1061     }
1062 }
1063 
1064 static int
1065 reap_kids(krb5_context context, krb5_kdc_configuration *config,
1066 	  pid_t *pids, int max_kids)
1067 {
1068     int reaped = 0;
1069 
1070     for (;;) {
1071 	if (reap_kid(context, config, pids, max_kids, WNOHANG) == 0)
1072 	    break;
1073 	reaped++;
1074     }
1075 
1076     return reaped;
1077 }
1078 
1079 static void
1080 select_sleep(int microseconds)
1081 {
1082     struct timeval tv;
1083 
1084     tv.tv_sec = microseconds / 1000000;
1085     tv.tv_usec = microseconds % 1000000;
1086     select(0, NULL, NULL, NULL, &tv);
1087 }
1088 #endif
1089 
1090 void
1091 start_kdc(krb5_context context,
1092 	  krb5_kdc_configuration *config, const char *argv0)
1093 {
1094     struct timeval tv1;
1095     struct timeval tv2;
1096     struct descr *d;
1097     unsigned int ndescr;
1098     pid_t pid = -1;
1099 #ifdef HAVE_FORK
1100     pid_t *pids;
1101     int max_kdcs = config->num_kdc_processes;
1102     int num_kdcs = 0;
1103     int i;
1104     int islive[2];
1105 #endif
1106 
1107 #ifdef __APPLE__
1108     if (do_bonjour > 0)
1109         bonjour_kid(context, config, argv0, NULL);
1110 #endif
1111 
1112 #ifdef HAVE_FORK
1113 #ifdef _SC_NPROCESSORS_ONLN
1114     if (max_kdcs < 1)
1115 	max_kdcs = sysconf(_SC_NPROCESSORS_ONLN);
1116 #endif
1117 
1118     if (max_kdcs < 1)
1119 	max_kdcs = 1;
1120 
1121     pids = calloc(max_kdcs, sizeof(*pids));
1122     if (!pids)
1123 	krb5_err(context, 1, errno, "malloc");
1124 
1125     /*
1126      * We open a socketpair of which we hand one end to each of our kids.
1127      * When we exit, for whatever reason, the children will notice an EOF
1128      * on their end and be able to cleanly exit.
1129      */
1130 
1131     if (socketpair(PF_LOCAL, SOCK_STREAM, 0, islive) == -1)
1132 	krb5_errx(context, 1, "socketpair");
1133     socket_set_nonblocking(islive[1], 1);
1134 #endif
1135 
1136     ndescr = init_sockets(context, config, &d);
1137     if(ndescr <= 0)
1138 	krb5_errx(context, 1, "No sockets!");
1139 
1140 #ifdef HAVE_FORK
1141 
1142 # ifdef __APPLE__
1143     if (do_bonjour < 0)
1144         bonjour_kid(context, config, argv0, islive);
1145 # endif
1146 
1147     kdc_log(context, config, 0, "KDC started master process pid=%d", getpid());
1148 #else
1149     kdc_log(context, config, 0, "KDC started pid=%d", getpid());
1150 #endif
1151 
1152     roken_detach_finish(NULL, daemon_child);
1153 
1154     tv1.tv_sec  = 0;
1155     tv1.tv_usec = 0;
1156 
1157 #ifdef HAVE_FORK
1158     if (!testing_flag) {
1159         /* Note that we might never execute the body of this loop */
1160         while (exit_flag == 0) {
1161 
1162             /* Slow down the creation of KDCs... */
1163 
1164             gettimeofday(&tv2, NULL);
1165             if (tv1.tv_sec == tv2.tv_sec && tv2.tv_usec - tv1.tv_usec < 25000) {
1166 #if 0	/* XXXrcd: should print a message... */
1167                 kdc_log(context, config, 0, "Spawning KDCs too quickly, "
1168                     "pausing for 50ms");
1169 #endif
1170                 select_sleep(12500);
1171                 continue;
1172             }
1173 
1174             if (num_kdcs >= max_kdcs) {
1175                 num_kdcs -= reap_kid(context, config, pids, max_kdcs, 0);
1176                 continue;
1177             }
1178 
1179             if (num_kdcs > 0)
1180                 num_kdcs -= reap_kids(context, config, pids, max_kdcs);
1181 
1182             pid = fork();
1183             switch (pid) {
1184             case 0:
1185                 close(islive[0]);
1186                 loop(context, config, d, ndescr, islive[1]);
1187                 exit(0);
1188             case -1:
1189                 /* XXXrcd: hmmm, do something useful?? */
1190                 kdc_log(context, config, 0,
1191                         "KDC master process could not fork worker process");
1192                 sleep(10);
1193                 break;
1194             default:
1195                 for (i=0; i < max_kdcs; i++) {
1196                     if (pids[i] < 1) {
1197                         pids[i] = pid;
1198                         break;
1199                     }
1200                 }
1201                 kdc_log(context, config, 0, "KDC worker process started: %d",
1202                         pid);
1203                 num_kdcs++;
1204                 gettimeofday(&tv1, NULL);
1205                 break;
1206             }
1207         }
1208 
1209         /* Closing these sockets should cause the kids to die... */
1210 
1211         close(islive[0]);
1212         close(islive[1]);
1213 
1214         /* Close our listener sockets before terminating workers */
1215         for (i = 0; i < ndescr; ++i)
1216             clear_descr(&d[i]);
1217 
1218         gettimeofday(&tv1, NULL);
1219         tv2 = tv1;
1220 
1221         /* Reap every 10ms, terminate stragglers once a second, give up after 10 */
1222         for (;;) {
1223             struct timeval tv3;
1224             num_kdcs -= reap_kids(context, config, pids, max_kdcs);
1225             if (num_kdcs == 0 && bonjour_pid <= 0)
1226                 goto end;
1227             /*
1228              * Using select to sleep will fail with EINTR if we receive a
1229              * SIGCHLD.  This is desirable.
1230              */
1231             select_sleep(10000);
1232             gettimeofday(&tv3, NULL);
1233             if (tv3.tv_sec - tv1.tv_sec > 10 ||
1234                 (tv3.tv_sec - tv1.tv_sec == 10 && tv3.tv_usec >= tv1.tv_usec))
1235                 break;
1236             if (tv3.tv_sec - tv2.tv_sec > 1 ||
1237                 (tv3.tv_sec - tv2.tv_sec == 1 && tv3.tv_usec >= tv2.tv_usec)) {
1238                 kill_kids(pids, max_kdcs, SIGTERM);
1239                 tv2 = tv3;
1240             }
1241         }
1242 
1243         /* Kill stragglers and reap every 200ms, give up after 15s */
1244         for (;;) {
1245             kill_kids(pids, max_kdcs, SIGKILL);
1246             num_kdcs -= reap_kids(context, config, pids, max_kdcs);
1247             if (num_kdcs == 0 && bonjour_pid <= 0)
1248                 break;
1249             select_sleep(200000);
1250             gettimeofday(&tv2, NULL);
1251             if (tv2.tv_sec - tv1.tv_sec > 15 ||
1252                 (tv2.tv_sec - tv1.tv_sec == 15 && tv2.tv_usec >= tv1.tv_usec))
1253                 break;
1254         }
1255 
1256      end:
1257         kdc_log(context, config, 0, "KDC master process exiting", pid);
1258         free(pids);
1259     } else {
1260         loop(context, config, d, ndescr, -1);
1261         kdc_log(context, config, 0, "KDC exiting", pid);
1262     }
1263 #else
1264     loop(context, config, d, ndescr, -1);
1265     kdc_log(context, config, 0, "KDC exiting", pid);
1266 #endif
1267 
1268     free(d);
1269 }
1270