xref: /netbsd-src/crypto/external/bsd/heimdal/dist/kdc/connect.c (revision afab4e300d3a9fb07dd8c80daf53d0feb3345706)
1 /*	$NetBSD: connect.c,v 1.5 2023/06/19 21:41:41 christos 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
add_port(krb5_context context,int family,int port,const char * protocol)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
add_port_service(krb5_context context,int family,const char * service,int port,const char * protocol)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
add_port_string(krb5_context context,int family,const char * str,const char * protocol)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
add_standard_ports(krb5_context context,krb5_kdc_configuration * config,int family)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
parse_ports(krb5_context context,krb5_kdc_configuration * config,const char * str)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
init_descr(struct descr * d)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
reinit_descrs(struct descr * d,int n)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
init_socket(krb5_context context,krb5_kdc_configuration * config,struct descr * d,krb5_address * a,int family,int type,int port)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
init_sockets(krb5_context context,krb5_kdc_configuration * config,struct descr ** desc)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 *
descr_type(struct descr * d)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
addr_to_string(krb5_context context,struct sockaddr * addr,size_t addr_len,char * str,size_t len)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
send_reply(krb5_context context,krb5_kdc_configuration * config,krb5_boolean prependlength,struct descr * d,krb5_data * reply)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
do_request(krb5_context context,krb5_kdc_configuration * config,void * buf,size_t len,krb5_boolean prependlength,struct descr * d)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
handle_udp(krb5_context context,krb5_kdc_configuration * config,struct descr * d)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
clear_descr(struct descr * d)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
de_http(char * buf)517 de_http(char *buf)
518 {
519     unsigned char *p, *q;
520     unsigned int x;
521 
522     for (p = q = (unsigned char *)buf; *p; p++, q++) {
523 	if (*p == '%') {
524 	    if (!(isxdigit(p[1]) && isxdigit(p[2])))
525 		return -1;
526 
527 	    if (sscanf((char *)p + 1, "%2x", &x) != 1)
528 		return -1;
529 
530 	    *q = x;
531 	    p += 2;
532 	} else {
533 	    *q = *p;
534 	}
535     }
536     *q = '\0';
537     return 0;
538 }
539 
540 #define TCP_TIMEOUT 4
541 
542 /*
543  * accept a new TCP connection on `d[parent]' and store it in `d[child]'
544  */
545 
546 static void
add_new_tcp(krb5_context context,krb5_kdc_configuration * config,struct descr * d,int parent,int child)547 add_new_tcp (krb5_context context,
548 	     krb5_kdc_configuration *config,
549 	     struct descr *d, int parent, int child)
550 {
551     krb5_socket_t s;
552 
553     if (child == -1)
554 	return;
555 
556     d[child].sock_len = sizeof(d[child].__ss);
557     s = accept(d[parent].s, d[child].sa, &d[child].sock_len);
558     if(rk_IS_BAD_SOCKET(s)) {
559 	if (rk_SOCK_ERRNO != EAGAIN && rk_SOCK_ERRNO != EINTR)
560 	    krb5_warn(context, rk_SOCK_ERRNO, "accept");
561 	return;
562     }
563 
564 #ifdef FD_SETSIZE
565     if (s >= FD_SETSIZE) {
566 	krb5_warnx(context, "socket FD too large");
567 	rk_closesocket (s);
568 	return;
569     }
570 #endif
571 
572     d[child].s = s;
573     d[child].timeout = time(NULL) + TCP_TIMEOUT;
574     d[child].type = SOCK_STREAM;
575     addr_to_string (context,
576 		    d[child].sa, d[child].sock_len,
577 		    d[child].addr_string, sizeof(d[child].addr_string));
578 }
579 
580 /*
581  * Grow `d' to handle at least `n'.
582  * Return != 0 if fails
583  */
584 
585 static int
grow_descr(krb5_context context,krb5_kdc_configuration * config,struct descr * d,size_t n)586 grow_descr (krb5_context context,
587 	    krb5_kdc_configuration *config,
588 	    struct descr *d, size_t n)
589 {
590     if (d->size - d->len < n) {
591 	unsigned char *tmp;
592 	size_t grow;
593 
594 	grow = max(1024, d->len + n);
595 	if (d->size + grow > max_request_tcp) {
596 	    kdc_log(context, config, 0, "Request exceeds max request size (%lu bytes).",
597 		    (unsigned long)d->size + grow);
598 	    clear_descr(d);
599 	    return -1;
600 	}
601 	tmp = realloc (d->buf, d->size + grow);
602 	if (tmp == NULL) {
603 	    kdc_log(context, config, 0, "Failed to re-allocate %lu bytes.",
604 		    (unsigned long)d->size + grow);
605 	    clear_descr(d);
606 	    return -1;
607 	}
608 	d->size += grow;
609 	d->buf = tmp;
610     }
611     return 0;
612 }
613 
614 /*
615  * Try to handle the TCP data at `d->buf, d->len'.
616  * Return -1 if failed, 0 if succesful, and 1 if data is complete.
617  */
618 
619 static int
handle_vanilla_tcp(krb5_context context,krb5_kdc_configuration * config,struct descr * d)620 handle_vanilla_tcp (krb5_context context,
621 		    krb5_kdc_configuration *config,
622 		    struct descr *d)
623 {
624     krb5_storage *sp;
625     uint32_t len;
626 
627     sp = krb5_storage_from_mem(d->buf, d->len);
628     if (sp == NULL) {
629 	kdc_log (context, config, 0, "krb5_storage_from_mem failed");
630 	return -1;
631     }
632     krb5_ret_uint32(sp, &len);
633     krb5_storage_free(sp);
634     if(d->len - 4 >= len) {
635 	memmove(d->buf, d->buf + 4, d->len - 4);
636 	d->len -= 4;
637 	return 1;
638     }
639     return 0;
640 }
641 
642 /*
643  * Try to handle the TCP/HTTP data at `d->buf, d->len'.
644  * Return -1 if failed, 0 if succesful, and 1 if data is complete.
645  */
646 
647 static int
handle_http_tcp(krb5_context context,krb5_kdc_configuration * config,struct descr * d)648 handle_http_tcp (krb5_context context,
649 		 krb5_kdc_configuration *config,
650 		 struct descr *d)
651 {
652     char *s, *p, *t;
653     void *data;
654     char *proto;
655     int len;
656 
657     s = (char *)d->buf;
658 
659     /* If its a multi line query, truncate off the first line */
660     p = strstr(s, "\r\n");
661     if (p)
662 	*p = 0;
663 
664     p = NULL;
665     t = strtok_r(s, " \t", &p);
666     if (t == NULL) {
667 	kdc_log(context, config, 0,
668 		"Missing HTTP operand (GET) request from %s", d->addr_string);
669 	return -1;
670     }
671 
672     t = strtok_r(NULL, " \t", &p);
673     if(t == NULL) {
674 	kdc_log(context, config, 0,
675 		"Missing HTTP GET data in request from %s", d->addr_string);
676 	return -1;
677     }
678 
679     data = malloc(strlen(t));
680     if (data == NULL) {
681 	kdc_log(context, config, 0, "Failed to allocate %lu bytes",
682 		(unsigned long)strlen(t));
683 	return -1;
684     }
685     if(*t == '/')
686 	t++;
687     if(de_http(t) != 0) {
688 	kdc_log(context, config, 0, "Malformed HTTP request from %s", d->addr_string);
689 	kdc_log(context, config, 5, "HTTP request: %s", t);
690 	free(data);
691 	return -1;
692     }
693     proto = strtok_r(NULL, " \t", &p);
694     if (proto == NULL) {
695 	kdc_log(context, config, 0, "Malformed HTTP request from %s", d->addr_string);
696 	free(data);
697 	return -1;
698     }
699     len = rk_base64_decode(t, data);
700     if(len <= 0){
701 	const char *msg =
702 	    " 404 Not found\r\n"
703 	    "Server: Heimdal/" VERSION "\r\n"
704 	    "Cache-Control: no-cache\r\n"
705 	    "Pragma: no-cache\r\n"
706 	    "Content-type: text/html\r\n"
707 	    "Content-transfer-encoding: 8bit\r\n\r\n"
708 	    "<TITLE>404 Not found</TITLE>\r\n"
709 	    "<H1>404 Not found</H1>\r\n"
710 	    "That page doesn't exist, maybe you are looking for "
711 	    "<A HREF=\"http://www.h5l.org/\">Heimdal</A>?\r\n";
712 	kdc_log(context, config, 0, "HTTP request from %s is non KDC request", d->addr_string);
713 	kdc_log(context, config, 5, "HTTP request: %s", t);
714 	free(data);
715 	if (rk_IS_SOCKET_ERROR(send(d->s, proto, strlen(proto), 0))) {
716 	    kdc_log(context, config, 0, "HTTP write failed: %s: %s",
717 		    d->addr_string, strerror(rk_SOCK_ERRNO));
718 	    return -1;
719 	}
720 	if (rk_IS_SOCKET_ERROR(send(d->s, msg, strlen(msg), 0))) {
721 	    kdc_log(context, config, 0, "HTTP write failed: %s: %s",
722 		    d->addr_string, strerror(rk_SOCK_ERRNO));
723 	    return -1;
724 	}
725 	return -1;
726     }
727     {
728 	const char *msg =
729 	    " 200 OK\r\n"
730 	    "Server: Heimdal/" VERSION "\r\n"
731 	    "Cache-Control: no-cache\r\n"
732 	    "Pragma: no-cache\r\n"
733 	    "Content-type: application/octet-stream\r\n"
734 	    "Content-transfer-encoding: binary\r\n\r\n";
735 	if (rk_IS_SOCKET_ERROR(send(d->s, proto, strlen(proto), 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 	if (rk_IS_SOCKET_ERROR(send(d->s, msg, strlen(msg), 0))) {
742 	    free(data);
743 	    kdc_log(context, config, 0, "HTTP write failed: %s: %s",
744 		    d->addr_string, strerror(rk_SOCK_ERRNO));
745 	    return -1;
746 	}
747     }
748     if ((size_t)len > d->len)
749         len = d->len;
750     memcpy(d->buf, data, len);
751     d->len = len;
752     free(data);
753     return 1;
754 }
755 
756 /*
757  * Handle incoming data to the TCP socket in `d[index]'
758  */
759 
760 static void
handle_tcp(krb5_context context,krb5_kdc_configuration * config,struct descr * d,int idx,int min_free)761 handle_tcp(krb5_context context,
762 	   krb5_kdc_configuration *config,
763 	   struct descr *d, int idx, int min_free)
764 {
765     unsigned char buf[1024];
766     int n;
767     int ret = 0;
768 
769     if (d[idx].timeout == 0) {
770 	add_new_tcp (context, config, d, idx, min_free);
771 	return;
772     }
773 
774     n = recvfrom(d[idx].s, buf, sizeof(buf), 0, NULL, NULL);
775     if(rk_IS_SOCKET_ERROR(n)){
776 	krb5_warn(context, rk_SOCK_ERRNO, "recvfrom failed from %s to %s/%d",
777 		  d[idx].addr_string, descr_type(d + idx),
778 		  ntohs(d[idx].port));
779 	return;
780     } else if (n == 0) {
781 	krb5_warnx(context, "connection closed before end of data after %lu "
782 		   "bytes from %s to %s/%d", (unsigned long)d[idx].len,
783 		   d[idx].addr_string, descr_type(d + idx),
784 		   ntohs(d[idx].port));
785 	clear_descr (d + idx);
786 	return;
787     }
788     if (grow_descr (context, config, &d[idx], n))
789 	return;
790     memcpy(d[idx].buf + d[idx].len, buf, n);
791     d[idx].len += n;
792     if(d[idx].len > 4 && d[idx].buf[0] == 0) {
793 	ret = handle_vanilla_tcp (context, config, &d[idx]);
794     } else if(enable_http &&
795 	      d[idx].len >= 4 &&
796 	      strncmp((char *)d[idx].buf, "GET ", 4) == 0 &&
797 	      strncmp((char *)d[idx].buf + d[idx].len - 4,
798 		      "\r\n\r\n", 4) == 0) {
799 
800         /* remove the trailing \r\n\r\n so the string is NUL terminated */
801         d[idx].buf[d[idx].len - 4] = '\0';
802 
803 	ret = handle_http_tcp (context, config, &d[idx]);
804 	if (ret < 0)
805 	    clear_descr (d + idx);
806     } else if (d[idx].len > 4) {
807 	kdc_log (context, config,
808 		 0, "TCP data of strange type from %s to %s/%d",
809 		 d[idx].addr_string, descr_type(d + idx),
810 		 ntohs(d[idx].port));
811 	if (d[idx].buf[0] & 0x80) {
812 	    krb5_data reply;
813 
814 	    kdc_log (context, config, 0, "TCP extension not supported");
815 
816 	    ret = krb5_mk_error(context,
817 				KRB5KRB_ERR_FIELD_TOOLONG,
818 				NULL,
819 				NULL,
820 				NULL,
821 				NULL,
822 				NULL,
823 				NULL,
824 				&reply);
825 	    if (ret == 0) {
826 		send_reply(context, config, TRUE, d + idx, &reply);
827 		krb5_data_free(&reply);
828 	    }
829 	}
830 	clear_descr(d + idx);
831 	return;
832     }
833     if (ret < 0)
834 	return;
835     else if (ret == 1) {
836 	do_request(context, config,
837 		   d[idx].buf, d[idx].len, TRUE, &d[idx]);
838 	clear_descr(d + idx);
839     }
840 }
841 
842 #ifdef HAVE_FORK
843 static void
handle_islive(int fd)844 handle_islive(int fd)
845 {
846     char buf;
847     int ret;
848 
849     ret = read(fd, &buf, 1);
850     if (ret != 1)
851 	exit_flag = -1;
852 }
853 #endif
854 
855 krb5_boolean
realloc_descrs(struct descr ** d,unsigned int * ndescr)856 realloc_descrs(struct descr **d, unsigned int *ndescr)
857 {
858     struct descr *tmp;
859     size_t i;
860 
861     tmp = realloc(*d, (*ndescr + 4) * sizeof(**d));
862     if(tmp == NULL)
863         return FALSE;
864 
865     *d = tmp;
866     reinit_descrs (*d, *ndescr);
867     memset(*d + *ndescr, 0, 4 * sizeof(**d));
868     for(i = *ndescr; i < *ndescr + 4; i++)
869         init_descr (*d + i);
870 
871     *ndescr += 4;
872 
873     return TRUE;
874 }
875 
876 int
next_min_free(krb5_context context,struct descr ** d,unsigned int * ndescr)877 next_min_free(krb5_context context, struct descr **d, unsigned int *ndescr)
878 {
879     size_t i;
880     int min_free;
881 
882     for(i = 0; i < *ndescr; i++) {
883         int s = (*d + i)->s;
884         if(rk_IS_BAD_SOCKET(s))
885             return i;
886     }
887 
888     min_free = *ndescr;
889     if(!realloc_descrs(d, ndescr)) {
890         min_free = -1;
891         krb5_warnx(context, "No memory");
892     }
893 
894     return min_free;
895 }
896 
897 static void
loop(krb5_context context,krb5_kdc_configuration * config,struct descr * d,unsigned int ndescr,int islive)898 loop(krb5_context context, krb5_kdc_configuration *config,
899      struct descr *d, unsigned int ndescr, int islive)
900 {
901 
902     while (exit_flag == 0) {
903 	struct timeval tmout;
904 	fd_set fds;
905 	int min_free = -1;
906 	int max_fd = 0;
907 	size_t i;
908 
909 	FD_ZERO(&fds);
910         if (islive > -1) {
911             FD_SET(islive, &fds);
912             max_fd = islive;
913         }
914 	for (i = 0; i < ndescr; i++) {
915 	    if (!rk_IS_BAD_SOCKET(d[i].s)) {
916 		if (d[i].type == SOCK_STREAM &&
917 		   d[i].timeout && d[i].timeout < time(NULL)) {
918 		    kdc_log(context, config, 1,
919 			    "TCP-connection from %s expired after %lu bytes",
920 			    d[i].addr_string, (unsigned long)d[i].len);
921 		    clear_descr(&d[i]);
922 		    continue;
923 		}
924 #ifndef NO_LIMIT_FD_SETSIZE
925 		if (max_fd < d[i].s)
926 		    max_fd = d[i].s;
927 #ifdef FD_SETSIZE
928 		if (max_fd >= FD_SETSIZE)
929 		    krb5_errx(context, 1, "fd too large");
930 #endif
931 #endif
932 		FD_SET(d[i].s, &fds);
933 	    }
934 	}
935 
936 	tmout.tv_sec = TCP_TIMEOUT;
937 	tmout.tv_usec = 0;
938 	switch(select(max_fd + 1, &fds, 0, 0, &tmout)){
939 	case 0:
940 	    break;
941 	case -1:
942 	    if (errno != EINTR)
943 		krb5_warn(context, rk_SOCK_ERRNO, "select");
944 	    break;
945 	default:
946 #ifdef HAVE_FORK
947 	    if (islive > -1 && FD_ISSET(islive, &fds))
948 		handle_islive(islive);
949 #endif
950 	    for (i = 0; i < ndescr; i++)
951 		if (!rk_IS_BAD_SOCKET(d[i].s) && FD_ISSET(d[i].s, &fds)) {
952 		    min_free = next_min_free(context, &d, &ndescr);
953 
954 		    if (d[i].type == SOCK_DGRAM)
955 			handle_udp(context, config, &d[i]);
956 		    else if (d[i].type == SOCK_STREAM)
957 			handle_tcp(context, config, d, i, min_free);
958 		}
959 	}
960     }
961 
962     switch (exit_flag) {
963     case -1:
964 	kdc_log(context, config, 0,
965                 "KDC worker process exiting because KDC master exited.");
966 	break;
967 #ifdef SIGXCPU
968     case SIGXCPU:
969 	kdc_log(context, config, 0, "CPU time limit exceeded");
970 	break;
971 #endif
972     case SIGINT:
973     case SIGTERM:
974 	kdc_log(context, config, 0, "Terminated");
975 	break;
976     default:
977 	kdc_log(context, config, 0, "Unexpected exit reason: %d", exit_flag);
978 	break;
979     }
980 }
981 
982 #ifdef __APPLE__
983 static void
bonjour_kid(krb5_context context,krb5_kdc_configuration * config,const char * argv0,int * islive)984 bonjour_kid(krb5_context context, krb5_kdc_configuration *config, const char *argv0, int *islive)
985 {
986     char buf;
987 
988     if (do_bonjour > 0) {
989 	bonjour_announce(context, config);
990 
991 	while (read(0, &buf, 1) == 1)
992 	    continue;
993 	_exit(0);
994     }
995 
996     if ((bonjour_pid = fork()) != 0)
997 	return;
998 
999     close(islive[0]);
1000     if (dup2(islive[1], 0) == -1)
1001 	err(1, "failed to announce with bonjour (dup)");
1002     if (islive[1] != 0)
1003         close(islive[1]);
1004     execlp(argv0, "kdc", "--bonjour", NULL);
1005     err(1, "failed to announce with bonjour (exec)");
1006 }
1007 #endif
1008 
1009 #ifdef HAVE_FORK
1010 static void
kill_kids(pid_t * pids,int max_kids,int sig)1011 kill_kids(pid_t *pids, int max_kids, int sig)
1012 {
1013     int i;
1014 
1015     for (i=0; i < max_kids; i++)
1016 	if (pids[i] > 0)
1017 	    kill(sig, pids[i]);
1018     if (bonjour_pid > 0)
1019         kill(sig, bonjour_pid);
1020 }
1021 
1022 static int
reap_kid(krb5_context context,krb5_kdc_configuration * config,pid_t * pids,int max_kids,int options)1023 reap_kid(krb5_context context, krb5_kdc_configuration *config,
1024 	 pid_t *pids, int max_kids, int options)
1025 {
1026     pid_t pid;
1027     char *what;
1028     int status;
1029     int i = 0; /* quiet warnings */
1030 
1031     pid = waitpid(-1, &status, options);
1032     if (pid < 1)
1033 	return 0;
1034 
1035     if (pid != bonjour_pid) {
1036         for (i=0; i < max_kids; i++) {
1037             if (pids[i] == pid)
1038                 break;
1039         }
1040 
1041         if (i == max_kids) {
1042             /* XXXrcd: this should not happen, have to do something, though */
1043             return 0;
1044         }
1045     }
1046 
1047     if (pid == bonjour_pid)
1048         what = "bonjour";
1049     else
1050         what = "worker";
1051     if (WIFEXITED(status))
1052         kdc_log(context, config, 0, "KDC reaped %s process: %d, exit status: %d",
1053                 what, (int)pid, WEXITSTATUS(status));
1054     else if (WIFSIGNALED(status))
1055         kdc_log(context, config, 0, "KDC reaped %s process: %d, term signal %d%s",
1056                 what, (int)pid, WTERMSIG(status),
1057                 WCOREDUMP(status) ? " (core dumped)" : "");
1058     else
1059         kdc_log(context, config, 0, "KDC reaped %s process: %d",
1060                 what, (int)pid);
1061     if (pid == bonjour_pid) {
1062         bonjour_pid = (pid_t)-1;
1063         return 0;
1064     } else {
1065         pids[i] = (pid_t)0;
1066         return 1;
1067     }
1068 }
1069 
1070 static int
reap_kids(krb5_context context,krb5_kdc_configuration * config,pid_t * pids,int max_kids)1071 reap_kids(krb5_context context, krb5_kdc_configuration *config,
1072 	  pid_t *pids, int max_kids)
1073 {
1074     int reaped = 0;
1075 
1076     for (;;) {
1077 	if (reap_kid(context, config, pids, max_kids, WNOHANG) == 0)
1078 	    break;
1079 	reaped++;
1080     }
1081 
1082     return reaped;
1083 }
1084 
1085 static void
select_sleep(int microseconds)1086 select_sleep(int microseconds)
1087 {
1088     struct timeval tv;
1089 
1090     tv.tv_sec = microseconds / 1000000;
1091     tv.tv_usec = microseconds % 1000000;
1092     select(0, NULL, NULL, NULL, &tv);
1093 }
1094 #endif
1095 
1096 void
start_kdc(krb5_context context,krb5_kdc_configuration * config,const char * argv0)1097 start_kdc(krb5_context context,
1098 	  krb5_kdc_configuration *config, const char *argv0)
1099 {
1100     struct timeval tv1;
1101     struct timeval tv2;
1102     struct descr *d;
1103     unsigned int ndescr;
1104     pid_t pid = -1;
1105 #ifdef HAVE_FORK
1106     pid_t *pids;
1107     int max_kdcs = config->num_kdc_processes;
1108     int num_kdcs = 0;
1109     int i;
1110     int islive[2];
1111 #endif
1112 
1113 #ifdef __APPLE__
1114     if (do_bonjour > 0)
1115         bonjour_kid(context, config, argv0, NULL);
1116 #endif
1117 
1118 #ifdef HAVE_FORK
1119 #ifdef _SC_NPROCESSORS_ONLN
1120     if (max_kdcs < 1)
1121 	max_kdcs = sysconf(_SC_NPROCESSORS_ONLN);
1122 #endif
1123 
1124     if (max_kdcs < 1)
1125 	max_kdcs = 1;
1126 
1127     pids = calloc(max_kdcs, sizeof(*pids));
1128     if (!pids)
1129 	krb5_err(context, 1, errno, "malloc");
1130 
1131     /*
1132      * We open a socketpair of which we hand one end to each of our kids.
1133      * When we exit, for whatever reason, the children will notice an EOF
1134      * on their end and be able to cleanly exit.
1135      */
1136 
1137     if (socketpair(PF_UNIX, SOCK_STREAM, 0, islive) == -1)
1138 	krb5_errx(context, 1, "socketpair");
1139     socket_set_nonblocking(islive[1], 1);
1140 #endif
1141 
1142     ndescr = init_sockets(context, config, &d);
1143     if(ndescr <= 0)
1144 	krb5_errx(context, 1, "No sockets!");
1145 
1146 #ifdef HAVE_FORK
1147 
1148 # ifdef __APPLE__
1149     if (do_bonjour < 0)
1150         bonjour_kid(context, config, argv0, islive);
1151 # endif
1152 
1153     kdc_log(context, config, 0, "KDC started master process pid=%d", getpid());
1154 #else
1155     kdc_log(context, config, 0, "KDC started pid=%d", getpid());
1156 #endif
1157 
1158     roken_detach_finish(NULL, daemon_child);
1159 
1160     tv1.tv_sec  = 0;
1161     tv1.tv_usec = 0;
1162 
1163 #ifdef HAVE_FORK
1164     if (!testing_flag) {
1165         /* Note that we might never execute the body of this loop */
1166         while (exit_flag == 0) {
1167 
1168             /* Slow down the creation of KDCs... */
1169 
1170             gettimeofday(&tv2, NULL);
1171             if (tv1.tv_sec == tv2.tv_sec && tv2.tv_usec - tv1.tv_usec < 25000) {
1172 #if 0	/* XXXrcd: should print a message... */
1173                 kdc_log(context, config, 0, "Spawning KDCs too quickly, "
1174                     "pausing for 50ms");
1175 #endif
1176                 select_sleep(12500);
1177                 continue;
1178             }
1179 
1180             if (num_kdcs >= max_kdcs) {
1181                 num_kdcs -= reap_kid(context, config, pids, max_kdcs, 0);
1182                 continue;
1183             }
1184 
1185             if (num_kdcs > 0)
1186                 num_kdcs -= reap_kids(context, config, pids, max_kdcs);
1187 
1188             pid = fork();
1189             switch (pid) {
1190             case 0:
1191                 close(islive[0]);
1192                 loop(context, config, d, ndescr, islive[1]);
1193                 exit(0);
1194             case -1:
1195                 /* XXXrcd: hmmm, do something useful?? */
1196                 kdc_log(context, config, 0,
1197                         "KDC master process could not fork worker process");
1198                 sleep(10);
1199                 break;
1200             default:
1201                 for (i=0; i < max_kdcs; i++) {
1202                     if (pids[i] < 1) {
1203                         pids[i] = pid;
1204                         break;
1205                     }
1206                 }
1207                 kdc_log(context, config, 0, "KDC worker process started: %d",
1208                         pid);
1209                 num_kdcs++;
1210                 gettimeofday(&tv1, NULL);
1211                 break;
1212             }
1213         }
1214 
1215         /* Closing these sockets should cause the kids to die... */
1216 
1217         close(islive[0]);
1218         close(islive[1]);
1219 
1220         /* Close our listener sockets before terminating workers */
1221         for (i = 0; i < ndescr; ++i)
1222             clear_descr(&d[i]);
1223 
1224         gettimeofday(&tv1, NULL);
1225         tv2 = tv1;
1226 
1227         /* Reap every 10ms, terminate stragglers once a second, give up after 10 */
1228         for (;;) {
1229             struct timeval tv3;
1230             num_kdcs -= reap_kids(context, config, pids, max_kdcs);
1231             if (num_kdcs == 0 && bonjour_pid <= 0)
1232                 goto end;
1233             /*
1234              * Using select to sleep will fail with EINTR if we receive a
1235              * SIGCHLD.  This is desirable.
1236              */
1237             select_sleep(10000);
1238             gettimeofday(&tv3, NULL);
1239             if (tv3.tv_sec - tv1.tv_sec > 10 ||
1240                 (tv3.tv_sec - tv1.tv_sec == 10 && tv3.tv_usec >= tv1.tv_usec))
1241                 break;
1242             if (tv3.tv_sec - tv2.tv_sec > 1 ||
1243                 (tv3.tv_sec - tv2.tv_sec == 1 && tv3.tv_usec >= tv2.tv_usec)) {
1244                 kill_kids(pids, max_kdcs, SIGTERM);
1245                 tv2 = tv3;
1246             }
1247         }
1248 
1249         /* Kill stragglers and reap every 200ms, give up after 15s */
1250         for (;;) {
1251             kill_kids(pids, max_kdcs, SIGKILL);
1252             num_kdcs -= reap_kids(context, config, pids, max_kdcs);
1253             if (num_kdcs == 0 && bonjour_pid <= 0)
1254                 break;
1255             select_sleep(200000);
1256             gettimeofday(&tv2, NULL);
1257             if (tv2.tv_sec - tv1.tv_sec > 15 ||
1258                 (tv2.tv_sec - tv1.tv_sec == 15 && tv2.tv_usec >= tv1.tv_usec))
1259                 break;
1260         }
1261 
1262      end:
1263         kdc_log(context, config, 0, "KDC master process exiting", pid);
1264         free(pids);
1265     } else {
1266         loop(context, config, d, ndescr, -1);
1267         kdc_log(context, config, 0, "KDC exiting", pid);
1268     }
1269 #else
1270     loop(context, config, d, ndescr, -1);
1271     kdc_log(context, config, 0, "KDC exiting", pid);
1272 #endif
1273 }
1274