1/*
2 * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "kdc_locl.h"
35
36/* Should we enable the HTTP hack? */
37int enable_http = -1;
38
39/* Log over requests to the KDC */
40const char *request_log;
41
42/* A string describing on what ports to listen */
43const char *port_str;
44
45krb5_addresses explicit_addresses;
46
47size_t max_request_udp;
48size_t max_request_tcp;
49
50/*
51 * a tuple describing on what to listen
52 */
53
54struct port_desc{
55    int family;
56    int type;
57    int port;
58};
59
60/* the current ones */
61
62static struct port_desc *ports;
63static size_t num_ports;
64
65/*
66 * add `family, port, protocol' to the list with duplicate suppresion.
67 */
68
69static void
70add_port(krb5_context context,
71	 int family, int port, const char *protocol)
72{
73    int type;
74    size_t i;
75
76    if(strcmp(protocol, "udp") == 0)
77	type = SOCK_DGRAM;
78    else if(strcmp(protocol, "tcp") == 0)
79	type = SOCK_STREAM;
80    else
81	return;
82    for(i = 0; i < num_ports; i++){
83	if(ports[i].type == type
84	   && ports[i].port == port
85	   && ports[i].family == family)
86	    return;
87    }
88    ports = realloc(ports, (num_ports + 1) * sizeof(*ports));
89    if (ports == NULL)
90	krb5_err (context, 1, errno, "realloc");
91    ports[num_ports].family = family;
92    ports[num_ports].type   = type;
93    ports[num_ports].port   = port;
94    num_ports++;
95}
96
97/*
98 * add a triple but with service -> port lookup
99 * (this prints warnings for stuff that does not exist)
100 */
101
102static void
103add_port_service(krb5_context context,
104		 int family, const char *service, int port,
105		 const char *protocol)
106{
107    port = krb5_getportbyname (context, service, protocol, port);
108    add_port (context, family, port, protocol);
109}
110
111/*
112 * add the port with service -> port lookup or string -> number
113 * (no warning is printed)
114 */
115
116static void
117add_port_string (krb5_context context,
118		 int family, const char *str, const char *protocol)
119{
120    struct servent *sp;
121    int port;
122
123    sp = roken_getservbyname (str, protocol);
124    if (sp != NULL) {
125	port = sp->s_port;
126    } else {
127	char *end;
128
129	port = htons(strtol(str, &end, 0));
130	if (end == str)
131	    return;
132    }
133    add_port (context, family, port, protocol);
134}
135
136/*
137 * add the standard collection of ports for `family'
138 */
139
140static void
141add_standard_ports (krb5_context context,
142		    krb5_kdc_configuration *config,
143		    int family)
144{
145    add_port_service(context, family, "kerberos", 88, "udp");
146    add_port_service(context, family, "kerberos", 88, "tcp");
147    add_port_service(context, family, "kerberos-sec", 88, "udp");
148    add_port_service(context, family, "kerberos-sec", 88, "tcp");
149    if(enable_http)
150	add_port_service(context, family, "http", 80, "tcp");
151    if(config->enable_kx509) {
152	add_port_service(context, family, "kca_service", 9878, "udp");
153	add_port_service(context, family, "kca_service", 9878, "tcp");
154    }
155
156}
157
158/*
159 * parse the set of space-delimited ports in `str' and add them.
160 * "+" => all the standard ones
161 * otherwise it's port|service[/protocol]
162 */
163
164static void
165parse_ports(krb5_context context,
166	    krb5_kdc_configuration *config,
167	    const char *str)
168{
169    char *pos = NULL;
170    char *p;
171    char *str_copy = strdup (str);
172
173    p = strtok_r(str_copy, " \t", &pos);
174    while(p != NULL) {
175	if(strcmp(p, "+") == 0) {
176#ifdef HAVE_IPV6
177	    add_standard_ports(context, config, AF_INET6);
178#endif
179	    add_standard_ports(context, config, AF_INET);
180	} else {
181	    char *q = strchr(p, '/');
182	    if(q){
183		*q++ = 0;
184#ifdef HAVE_IPV6
185		add_port_string(context, AF_INET6, p, q);
186#endif
187		add_port_string(context, AF_INET, p, q);
188	    }else {
189#ifdef HAVE_IPV6
190		add_port_string(context, AF_INET6, p, "udp");
191		add_port_string(context, AF_INET6, p, "tcp");
192#endif
193		add_port_string(context, AF_INET, p, "udp");
194		add_port_string(context, AF_INET, p, "tcp");
195	    }
196	}
197
198	p = strtok_r(NULL, " \t", &pos);
199    }
200    free (str_copy);
201}
202
203/*
204 * every socket we listen on
205 */
206
207struct descr {
208    krb5_socket_t s;
209    int type;
210    int port;
211    unsigned char *buf;
212    size_t size;
213    size_t len;
214    time_t timeout;
215    struct sockaddr_storage __ss;
216    struct sockaddr *sa;
217    socklen_t sock_len;
218    char addr_string[128];
219};
220
221static void
222init_descr(struct descr *d)
223{
224    memset(d, 0, sizeof(*d));
225    d->sa = (struct sockaddr *)&d->__ss;
226    d->s = rk_INVALID_SOCKET;
227}
228
229/*
230 * re-initialize all `n' ->sa in `d'.
231 */
232
233static void
234reinit_descrs (struct descr *d, int n)
235{
236    int i;
237
238    for (i = 0; i < n; ++i)
239	d[i].sa = (struct sockaddr *)&d[i].__ss;
240}
241
242/*
243 * Create the socket (family, type, port) in `d'
244 */
245
246static void
247init_socket(krb5_context context,
248	    krb5_kdc_configuration *config,
249	    struct descr *d, krb5_address *a, int family, int type, int port)
250{
251    krb5_error_code ret;
252    struct sockaddr_storage __ss;
253    struct sockaddr *sa = (struct sockaddr *)&__ss;
254    krb5_socklen_t sa_size = sizeof(__ss);
255
256    init_descr (d);
257
258    ret = krb5_addr2sockaddr (context, a, sa, &sa_size, port);
259    if (ret) {
260	krb5_warn(context, ret, "krb5_addr2sockaddr");
261	rk_closesocket(d->s);
262	d->s = rk_INVALID_SOCKET;
263	return;
264    }
265
266    if (sa->sa_family != family)
267	return;
268
269    d->s = socket(family, type, 0);
270    if(rk_IS_BAD_SOCKET(d->s)){
271	krb5_warn(context, errno, "socket(%d, %d, 0)", family, type);
272	d->s = rk_INVALID_SOCKET;
273	return;
274    }
275#if defined(HAVE_SETSOCKOPT) && defined(SOL_SOCKET) && defined(SO_REUSEADDR)
276    {
277	int one = 1;
278	setsockopt(d->s, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
279    }
280#endif
281    d->type = type;
282    d->port = port;
283
284    if(rk_IS_SOCKET_ERROR(bind(d->s, sa, sa_size))){
285	char a_str[256];
286	size_t len;
287
288	krb5_print_address (a, a_str, sizeof(a_str), &len);
289	krb5_warn(context, errno, "bind %s/%d", a_str, ntohs(port));
290	rk_closesocket(d->s);
291	d->s = rk_INVALID_SOCKET;
292	return;
293    }
294    if(type == SOCK_STREAM && rk_IS_SOCKET_ERROR(listen(d->s, SOMAXCONN))){
295	char a_str[256];
296	size_t len;
297
298	krb5_print_address (a, a_str, sizeof(a_str), &len);
299	krb5_warn(context, errno, "listen %s/%d", a_str, ntohs(port));
300	rk_closesocket(d->s);
301	d->s = rk_INVALID_SOCKET;
302	return;
303    }
304}
305
306/*
307 * Allocate descriptors for all the sockets that we should listen on
308 * and return the number of them.
309 */
310
311static int
312init_sockets(krb5_context context,
313	     krb5_kdc_configuration *config,
314	     struct descr **desc)
315{
316    krb5_error_code ret;
317    size_t i, j;
318    struct descr *d;
319    int num = 0;
320    krb5_addresses addresses;
321
322    if (explicit_addresses.len) {
323	addresses = explicit_addresses;
324    } else {
325	ret = krb5_get_all_server_addrs (context, &addresses);
326	if (ret)
327	    krb5_err (context, 1, ret, "krb5_get_all_server_addrs");
328    }
329    parse_ports(context, config, port_str);
330    d = malloc(addresses.len * num_ports * sizeof(*d));
331    if (d == NULL)
332	krb5_errx(context, 1, "malloc(%lu) failed",
333		  (unsigned long)num_ports * sizeof(*d));
334
335    for (i = 0; i < num_ports; i++){
336	for (j = 0; j < addresses.len; ++j) {
337	    init_socket(context, config, &d[num], &addresses.val[j],
338			ports[i].family, ports[i].type, ports[i].port);
339	    if(d[num].s != rk_INVALID_SOCKET){
340		char a_str[80];
341		size_t len;
342
343		krb5_print_address (&addresses.val[j], a_str,
344				    sizeof(a_str), &len);
345
346		kdc_log(context, config, 5, "listening on %s port %u/%s",
347			a_str,
348			ntohs(ports[i].port),
349			(ports[i].type == SOCK_STREAM) ? "tcp" : "udp");
350		/* XXX */
351		num++;
352	    }
353	}
354    }
355    krb5_free_addresses (context, &addresses);
356    d = realloc(d, num * sizeof(*d));
357    if (d == NULL && num != 0)
358	krb5_errx(context, 1, "realloc(%lu) failed",
359		  (unsigned long)num * sizeof(*d));
360    reinit_descrs (d, num);
361    *desc = d;
362    return num;
363}
364
365/*
366 *
367 */
368
369static const char *
370descr_type(struct descr *d)
371{
372    if (d->type == SOCK_DGRAM)
373	return "udp";
374    else if (d->type == SOCK_STREAM)
375	return "tcp";
376    return "unknown";
377}
378
379static void
380addr_to_string(krb5_context context,
381	       struct sockaddr *addr, size_t addr_len, char *str, size_t len)
382{
383    krb5_address a;
384    if(krb5_sockaddr2address(context, addr, &a) == 0) {
385	if(krb5_print_address(&a, str, len, &len) == 0) {
386	    krb5_free_address(context, &a);
387	    return;
388	}
389	krb5_free_address(context, &a);
390    }
391    snprintf(str, len, "<family=%d>", addr->sa_family);
392}
393
394/*
395 *
396 */
397
398static void
399send_reply(krb5_context context,
400	   krb5_kdc_configuration *config,
401	   krb5_boolean prependlength,
402	   struct descr *d,
403	   krb5_data *reply)
404{
405    kdc_log(context, config, 5,
406	    "sending %lu bytes to %s", (unsigned long)reply->length,
407	    d->addr_string);
408    if(prependlength){
409	unsigned char l[4];
410	l[0] = (reply->length >> 24) & 0xff;
411	l[1] = (reply->length >> 16) & 0xff;
412	l[2] = (reply->length >> 8) & 0xff;
413	l[3] = reply->length & 0xff;
414	if(rk_IS_SOCKET_ERROR(sendto(d->s, l, sizeof(l), 0, d->sa, d->sock_len))) {
415	    kdc_log (context, config,
416		     0, "sendto(%s): %s", d->addr_string,
417		     strerror(rk_SOCK_ERRNO));
418	    return;
419	}
420    }
421    if(rk_IS_SOCKET_ERROR(sendto(d->s, reply->data, reply->length, 0, d->sa, d->sock_len))) {
422	kdc_log (context, config, 0, "sendto(%s): %s", d->addr_string,
423		 strerror(rk_SOCK_ERRNO));
424	return;
425    }
426}
427
428/*
429 * Handle the request in `buf, len' to socket `d'
430 */
431
432static void
433do_request(krb5_context context,
434	   krb5_kdc_configuration *config,
435	   void *buf, size_t len, krb5_boolean prependlength,
436	   struct descr *d)
437{
438    krb5_error_code ret;
439    krb5_data reply;
440    int datagram_reply = (d->type == SOCK_DGRAM);
441
442    krb5_kdc_update_time(NULL);
443
444    krb5_data_zero(&reply);
445    ret = krb5_kdc_process_request(context, config,
446				   buf, len, &reply, &prependlength,
447				   d->addr_string, d->sa,
448				   datagram_reply);
449    if(request_log)
450	krb5_kdc_save_request(context, request_log, buf, len, &reply, d->sa);
451    if(reply.length){
452	send_reply(context, config, prependlength, d, &reply);
453	krb5_data_free(&reply);
454    }
455    if(ret)
456	kdc_log(context, config, 0,
457		"Failed processing %lu byte request from %s",
458		(unsigned long)len, d->addr_string);
459}
460
461/*
462 * Handle incoming data to the UDP socket in `d'
463 */
464
465static void
466handle_udp(krb5_context context,
467	   krb5_kdc_configuration *config,
468	   struct descr *d)
469{
470    unsigned char *buf;
471    ssize_t n;
472
473    buf = malloc(max_request_udp);
474    if(buf == NULL){
475	kdc_log(context, config, 0, "Failed to allocate %lu bytes", (unsigned long)max_request_udp);
476	return;
477    }
478
479    d->sock_len = sizeof(d->__ss);
480    n = recvfrom(d->s, buf, max_request_udp, 0, d->sa, &d->sock_len);
481    if(rk_IS_SOCKET_ERROR(n))
482	krb5_warn(context, rk_SOCK_ERRNO, "recvfrom");
483    else {
484	addr_to_string (context, d->sa, d->sock_len,
485			d->addr_string, sizeof(d->addr_string));
486	if ((size_t)n == max_request_udp) {
487	    krb5_data data;
488	    krb5_warn(context, errno,
489		      "recvfrom: truncated packet from %s, asking for TCP",
490		      d->addr_string);
491	    krb5_mk_error(context,
492			  KRB5KRB_ERR_RESPONSE_TOO_BIG,
493			  NULL,
494			  NULL,
495			  NULL,
496			  NULL,
497			  NULL,
498			  NULL,
499			  &data);
500	    send_reply(context, config, FALSE, d, &data);
501	    krb5_data_free(&data);
502	} else {
503	    do_request(context, config, buf, n, FALSE, d);
504	}
505    }
506    free (buf);
507}
508
509static void
510clear_descr(struct descr *d)
511{
512    if(d->buf)
513	memset(d->buf, 0, d->size);
514    d->len = 0;
515    if(d->s != rk_INVALID_SOCKET)
516	rk_closesocket(d->s);
517    d->s = rk_INVALID_SOCKET;
518}
519
520
521/* remove HTTP %-quoting from buf */
522static int
523de_http(char *buf)
524{
525    unsigned char *p, *q;
526    for(p = q = (unsigned char *)buf; *p; p++, q++) {
527	if(*p == '%' && isxdigit(p[1]) && isxdigit(p[2])) {
528	    unsigned int x;
529	    if(sscanf((char *)p + 1, "%2x", &x) != 1)
530		return -1;
531	    *q = x;
532	    p += 2;
533	} else
534	    *q = *p;
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
546static void
547add_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	krb5_warn(context, rk_SOCK_ERRNO, "accept");
560	return;
561    }
562
563#ifdef FD_SETSIZE
564    if (s >= FD_SETSIZE) {
565	krb5_warnx(context, "socket FD too large");
566	rk_closesocket (s);
567	return;
568    }
569#endif
570
571    d[child].s = s;
572    d[child].timeout = time(NULL) + TCP_TIMEOUT;
573    d[child].type = SOCK_STREAM;
574    addr_to_string (context,
575		    d[child].sa, d[child].sock_len,
576		    d[child].addr_string, sizeof(d[child].addr_string));
577}
578
579/*
580 * Grow `d' to handle at least `n'.
581 * Return != 0 if fails
582 */
583
584static int
585grow_descr (krb5_context context,
586	    krb5_kdc_configuration *config,
587	    struct descr *d, size_t n)
588{
589    if (d->size - d->len < n) {
590	unsigned char *tmp;
591	size_t grow;
592
593	grow = max(1024, d->len + n);
594	if (d->size + grow > max_request_tcp) {
595	    kdc_log(context, config, 0, "Request exceeds max request size (%lu bytes).",
596		    (unsigned long)d->size + grow);
597	    clear_descr(d);
598	    return -1;
599	}
600	tmp = realloc (d->buf, d->size + grow);
601	if (tmp == NULL) {
602	    kdc_log(context, config, 0, "Failed to re-allocate %lu bytes.",
603		    (unsigned long)d->size + grow);
604	    clear_descr(d);
605	    return -1;
606	}
607	d->size += grow;
608	d->buf = tmp;
609    }
610    return 0;
611}
612
613/*
614 * Try to handle the TCP data at `d->buf, d->len'.
615 * Return -1 if failed, 0 if succesful, and 1 if data is complete.
616 */
617
618static int
619handle_vanilla_tcp (krb5_context context,
620		    krb5_kdc_configuration *config,
621		    struct descr *d)
622{
623    krb5_storage *sp;
624    uint32_t len;
625
626    sp = krb5_storage_from_mem(d->buf, d->len);
627    if (sp == NULL) {
628	kdc_log (context, config, 0, "krb5_storage_from_mem failed");
629	return -1;
630    }
631    krb5_ret_uint32(sp, &len);
632    krb5_storage_free(sp);
633    if(d->len - 4 >= len) {
634	memmove(d->buf, d->buf + 4, d->len - 4);
635	d->len -= 4;
636	return 1;
637    }
638    return 0;
639}
640
641/*
642 * Try to handle the TCP/HTTP data at `d->buf, d->len'.
643 * Return -1 if failed, 0 if succesful, and 1 if data is complete.
644 */
645
646static int
647handle_http_tcp (krb5_context context,
648		 krb5_kdc_configuration *config,
649		 struct descr *d)
650{
651    char *s, *p, *t;
652    void *data;
653    char *proto;
654    int len;
655
656    s = (char *)d->buf;
657
658    /* If its a multi line query, truncate off the first line */
659    p = strstr(s, "\r\n");
660    if (p)
661	*p = 0;
662
663    p = NULL;
664    t = strtok_r(s, " \t", &p);
665    if (t == NULL) {
666	kdc_log(context, config, 0,
667		"Missing HTTP operand (GET) request from %s", d->addr_string);
668	return -1;
669    }
670
671    t = strtok_r(NULL, " \t", &p);
672    if(t == NULL) {
673	kdc_log(context, config, 0,
674		"Missing HTTP GET data in request from %s", d->addr_string);
675	return -1;
676    }
677
678    data = malloc(strlen(t));
679    if (data == NULL) {
680	kdc_log(context, config, 0, "Failed to allocate %lu bytes",
681		(unsigned long)strlen(t));
682	return -1;
683    }
684    if(*t == '/')
685	t++;
686    if(de_http(t) != 0) {
687	kdc_log(context, config, 0, "Malformed HTTP request from %s", d->addr_string);
688	kdc_log(context, config, 5, "HTTP request: %s", t);
689	free(data);
690	return -1;
691    }
692    proto = strtok_r(NULL, " \t", &p);
693    if (proto == NULL) {
694	kdc_log(context, config, 0, "Malformed HTTP request from %s", d->addr_string);
695	free(data);
696	return -1;
697    }
698    len = base64_decode(t, data);
699    if(len <= 0){
700	const char *msg =
701	    " 404 Not found\r\n"
702	    "Server: Heimdal/" VERSION "\r\n"
703	    "Cache-Control: no-cache\r\n"
704	    "Pragma: no-cache\r\n"
705	    "Content-type: text/html\r\n"
706	    "Content-transfer-encoding: 8bit\r\n\r\n"
707	    "<TITLE>404 Not found</TITLE>\r\n"
708	    "<H1>404 Not found</H1>\r\n"
709	    "That page doesn't exist, maybe you are looking for "
710	    "<A HREF=\"http://www.h5l.org/\">Heimdal</A>?\r\n";
711	kdc_log(context, config, 0, "HTTP request from %s is non KDC request", d->addr_string);
712	kdc_log(context, config, 5, "HTTP request: %s", t);
713	free(data);
714	if (rk_IS_SOCKET_ERROR(send(d->s, proto, strlen(proto), 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	if (rk_IS_SOCKET_ERROR(send(d->s, msg, strlen(msg), 0))) {
720	    kdc_log(context, config, 0, "HTTP write failed: %s: %s",
721		    d->addr_string, strerror(rk_SOCK_ERRNO));
722	    return -1;
723	}
724	return -1;
725    }
726    {
727	const char *msg =
728	    " 200 OK\r\n"
729	    "Server: Heimdal/" VERSION "\r\n"
730	    "Cache-Control: no-cache\r\n"
731	    "Pragma: no-cache\r\n"
732	    "Content-type: application/octet-stream\r\n"
733	    "Content-transfer-encoding: binary\r\n\r\n";
734	if (rk_IS_SOCKET_ERROR(send(d->s, proto, strlen(proto), 0))) {
735	    free(data);
736	    kdc_log(context, config, 0, "HTTP write failed: %s: %s",
737		    d->addr_string, strerror(rk_SOCK_ERRNO));
738	    return -1;
739	}
740	if (rk_IS_SOCKET_ERROR(send(d->s, msg, strlen(msg), 0))) {
741	    free(data);
742	    kdc_log(context, config, 0, "HTTP write failed: %s: %s",
743		    d->addr_string, strerror(rk_SOCK_ERRNO));
744	    return -1;
745	}
746    }
747    if ((size_t)len > d->len)
748        len = d->len;
749    memcpy(d->buf, data, len);
750    d->len = len;
751    free(data);
752    return 1;
753}
754
755/*
756 * Handle incoming data to the TCP socket in `d[index]'
757 */
758
759static void
760handle_tcp(krb5_context context,
761	   krb5_kdc_configuration *config,
762	   struct descr *d, int idx, int min_free)
763{
764    unsigned char buf[1024];
765    int n;
766    int ret = 0;
767
768    if (d[idx].timeout == 0) {
769	add_new_tcp (context, config, d, idx, min_free);
770	return;
771    }
772
773    n = recvfrom(d[idx].s, buf, sizeof(buf), 0, NULL, NULL);
774    if(rk_IS_SOCKET_ERROR(n)){
775	krb5_warn(context, rk_SOCK_ERRNO, "recvfrom failed from %s to %s/%d",
776		  d[idx].addr_string, descr_type(d + idx),
777		  ntohs(d[idx].port));
778	return;
779    } else if (n == 0) {
780	krb5_warnx(context, "connection closed before end of data after %lu "
781		   "bytes from %s to %s/%d", (unsigned long)d[idx].len,
782		   d[idx].addr_string, descr_type(d + idx),
783		   ntohs(d[idx].port));
784	clear_descr (d + idx);
785	return;
786    }
787    if (grow_descr (context, config, &d[idx], n))
788	return;
789    memcpy(d[idx].buf + d[idx].len, buf, n);
790    d[idx].len += n;
791    if(d[idx].len > 4 && d[idx].buf[0] == 0) {
792	ret = handle_vanilla_tcp (context, config, &d[idx]);
793    } else if(enable_http &&
794	      d[idx].len >= 4 &&
795	      strncmp((char *)d[idx].buf, "GET ", 4) == 0 &&
796	      strncmp((char *)d[idx].buf + d[idx].len - 4,
797		      "\r\n\r\n", 4) == 0) {
798
799        /* remove the trailing \r\n\r\n so the string is NUL terminated */
800        d[idx].buf[d[idx].len - 4] = '\0';
801
802	ret = handle_http_tcp (context, config, &d[idx]);
803	if (ret < 0)
804	    clear_descr (d + idx);
805    } else if (d[idx].len > 4) {
806	kdc_log (context, config,
807		 0, "TCP data of strange type from %s to %s/%d",
808		 d[idx].addr_string, descr_type(d + idx),
809		 ntohs(d[idx].port));
810	if (d[idx].buf[0] & 0x80) {
811	    krb5_data reply;
812
813	    kdc_log (context, config, 0, "TCP extension not supported");
814
815	    ret = krb5_mk_error(context,
816				KRB5KRB_ERR_FIELD_TOOLONG,
817				NULL,
818				NULL,
819				NULL,
820				NULL,
821				NULL,
822				NULL,
823				&reply);
824	    if (ret == 0) {
825		send_reply(context, config, TRUE, d + idx, &reply);
826		krb5_data_free(&reply);
827	    }
828	}
829	clear_descr(d + idx);
830	return;
831    }
832    if (ret < 0)
833	return;
834    else if (ret == 1) {
835	do_request(context, config,
836		   d[idx].buf, d[idx].len, TRUE, &d[idx]);
837	clear_descr(d + idx);
838    }
839}
840
841void
842loop(krb5_context context,
843     krb5_kdc_configuration *config)
844{
845    struct descr *d;
846    unsigned int ndescr;
847
848    ndescr = init_sockets(context, config, &d);
849    if(ndescr <= 0)
850	krb5_errx(context, 1, "No sockets!");
851    kdc_log(context, config, 0, "KDC started");
852    while(exit_flag == 0){
853	struct timeval tmout;
854	fd_set fds;
855	int min_free = -1;
856	int max_fd = 0;
857	size_t i;
858
859	FD_ZERO(&fds);
860	for(i = 0; i < ndescr; i++) {
861	    if(!rk_IS_BAD_SOCKET(d[i].s)){
862		if(d[i].type == SOCK_STREAM &&
863		   d[i].timeout && d[i].timeout < time(NULL)) {
864		    kdc_log(context, config, 1,
865			    "TCP-connection from %s expired after %lu bytes",
866			    d[i].addr_string, (unsigned long)d[i].len);
867		    clear_descr(&d[i]);
868		    continue;
869		}
870#ifndef NO_LIMIT_FD_SETSIZE
871		if(max_fd < d[i].s)
872		    max_fd = d[i].s;
873#ifdef FD_SETSIZE
874		if (max_fd >= FD_SETSIZE)
875		    krb5_errx(context, 1, "fd too large");
876#endif
877#endif
878		FD_SET(d[i].s, &fds);
879	    } else if(min_free < 0 || i < (size_t)min_free)
880		min_free = i;
881	}
882	if(min_free == -1){
883	    struct descr *tmp;
884	    tmp = realloc(d, (ndescr + 4) * sizeof(*d));
885	    if(tmp == NULL)
886		krb5_warnx(context, "No memory");
887	    else {
888		d = tmp;
889		reinit_descrs (d, ndescr);
890		memset(d + ndescr, 0, 4 * sizeof(*d));
891		for(i = ndescr; i < ndescr + 4; i++)
892		    init_descr (&d[i]);
893		min_free = ndescr;
894		ndescr += 4;
895	    }
896	}
897
898	tmout.tv_sec = TCP_TIMEOUT;
899	tmout.tv_usec = 0;
900	switch(select(max_fd + 1, &fds, 0, 0, &tmout)){
901	case 0:
902	    break;
903	case -1:
904	    if (errno != EINTR)
905		krb5_warn(context, rk_SOCK_ERRNO, "select");
906	    break;
907	default:
908	    for(i = 0; i < ndescr; i++)
909		if(!rk_IS_BAD_SOCKET(d[i].s) && FD_ISSET(d[i].s, &fds)) {
910		    if(d[i].type == SOCK_DGRAM)
911			handle_udp(context, config, &d[i]);
912		    else if(d[i].type == SOCK_STREAM)
913			handle_tcp(context, config, d, i, min_free);
914		}
915	}
916    }
917    if (0);
918#ifdef SIGXCPU
919    else if(exit_flag == SIGXCPU)
920	kdc_log(context, config, 0, "CPU time limit exceeded");
921#endif
922    else if(exit_flag == SIGINT || exit_flag == SIGTERM)
923	kdc_log(context, config, 0, "Terminated");
924    else
925	kdc_log(context, config, 0, "Unexpected exit reason: %d", exit_flag);
926    free (d);
927}
928