xref: /illumos-gate/usr/src/cmd/krb5/krb5kdc/network.c (revision 4eaa471005973e11a6110b69fe990530b3b95a38)
1 /*
2  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*
7  * kdc/network.c
8  *
9  * Copyright 1990,2000 by the Massachusetts Institute of Technology.
10  *
11  * Export of this software from the United States of America may
12  *   require a specific license from the United States Government.
13  *   It is the responsibility of any person or organization contemplating
14  *   export to obtain such a license before exporting.
15  *
16  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
17  * distribute this software and its documentation for any purpose and
18  * without fee is hereby granted, provided that the above copyright
19  * notice appear in all copies and that both that copyright notice and
20  * this permission notice appear in supporting documentation, and that
21  * the name of M.I.T. not be used in advertising or publicity pertaining
22  * to distribution of the software without specific, written prior
23  * permission.  Furthermore if you modify this software you must label
24  * your software as modified software and not distribute it in such a
25  * fashion that it might be confused with the original M.I.T. software.
26  * M.I.T. makes no representations about the suitability of
27  * this software for any purpose.  It is provided "as is" without express
28  * or implied warranty.
29  *
30  *
31  * Network code for Kerberos v5 KDC.
32  */
33 
34 #include "k5-int.h"
35 #include "com_err.h"
36 #include "kdc_util.h"
37 #include "extern.h"
38 #include "kdc5_err.h"
39 #include "adm_proto.h"
40 #include <sys/ioctl.h>
41 #include <syslog.h>
42 
43 #include <stddef.h>
44 #include <ctype.h>
45 #include "port-sockets.h"
46 /* #include "socket-utils.h" */
47 
48 #ifdef HAVE_NETINET_IN_H
49 #include <sys/types.h>
50 #include <netinet/in.h>
51 #include <sys/socket.h>
52 #ifdef HAVE_SYS_SOCKIO_H
53 /* for SIOCGIFCONF, etc. */
54 #include <sys/sockio.h>
55 #endif
56 #include <sys/time.h>
57 #include <libintl.h>
58 
59 #if HAVE_SYS_SELECT_H
60 #include <sys/select.h>
61 #endif
62 #include <arpa/inet.h>
63 #include <inet/ip.h>
64 #include <inet/ip6.h>
65 
66 #ifndef ARPHRD_ETHER /* OpenBSD breaks on multiple inclusions */
67 #include <net/if.h>
68 #endif
69 
70 #ifdef HAVE_SYS_FILIO_H
71 #include <sys/filio.h>		/* FIONBIO */
72 #endif
73 
74 #include "fake-addrinfo.h"
75 
76 /* Misc utility routines.  */
77 static void
78 set_sa_port(struct sockaddr *addr, int port)
79 {
80     switch (addr->sa_family) {
81     case AF_INET:
82 	sa2sin(addr)->sin_port = port;
83 	break;
84 #ifdef KRB5_USE_INET6
85     case AF_INET6:
86 	sa2sin6(addr)->sin6_port = port;
87 	break;
88 #endif
89     default:
90 	break;
91     }
92 }
93 
94 static int ipv6_enabled()
95 {
96 #ifdef KRB5_USE_INET6
97     static int result = -1;
98     if (result == -1) {
99 	int s;
100 	s = socket(AF_INET6, SOCK_STREAM, 0);
101 	if (s >= 0) {
102 	    result = 1;
103 	    close(s);
104 	} else
105 	    result = 0;
106     }
107     return result;
108 #else
109     return 0;
110 #endif
111 }
112 
113 static int
114 setreuseaddr(int sock, int value)
115 {
116     return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
117 }
118 
119 #if defined(KRB5_USE_INET6) && defined(IPV6_V6ONLY)
120 static int
121 setv6only(int sock, int value)
122 {
123     return setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value));
124 }
125 #endif
126 
127 
128 static const char *paddr (struct sockaddr *sa)
129 {
130     static char buf[100];
131     char portbuf[10];
132     if (getnameinfo(sa, socklen(sa),
133 		    buf, sizeof(buf), portbuf, sizeof(portbuf),
134 		    NI_NUMERICHOST|NI_NUMERICSERV))
135 	strcpy(buf, "<unprintable>");
136     else {
137 	unsigned int len = sizeof(buf) - strlen(buf);
138 	char *p = buf + strlen(buf);
139 	if (len > 2+strlen(portbuf)) {
140 	    *p++ = '.';
141 	    len--;
142 	    strncpy(p, portbuf, len);
143 	}
144     }
145     return buf;
146 }
147 
148 /* KDC data.  */
149 
150 enum kdc_conn_type { CONN_UDP, CONN_TCP_LISTENER, CONN_TCP };
151 
152 /* Per-connection info.  */
153 struct connection {
154     int fd;
155     enum kdc_conn_type type;
156     void (*service)(struct connection *, const char *, int);
157     /* Solaris Kerberos: for auditing */
158     in_port_t port; /* local port */
159     union {
160 	/* Type-specific information.  */
161 	struct {
162 	    int x;
163 	} udp;
164 	struct {
165 	    int x;
166 	} tcp_listener;
167 	struct {
168 	    /* connection */
169 	    struct sockaddr_storage addr_s;
170 	    socklen_t addrlen;
171 	    char addrbuf[56];
172 	    krb5_fulladdr faddr;
173 	    krb5_address kaddr;
174 	    /* incoming */
175 	    size_t bufsiz;
176 	    size_t offset;
177 	    char *buffer;
178 	    size_t msglen;
179 	    /* outgoing */
180 	    krb5_data *response;
181 	    unsigned char lenbuf[4];
182 	    sg_buf sgbuf[2];
183 	    sg_buf *sgp;
184 	    int sgnum;
185 	    /* crude denial-of-service avoidance support */
186 	    time_t start_time;
187 	} tcp;
188     } u;
189 };
190 
191 
192 #define SET(TYPE) struct { TYPE *data; int n, max; }
193 
194 /* Start at the top and work down -- this should allow for deletions
195    without disrupting the iteration, since we delete by overwriting
196    the element to be removed with the last element.  */
197 #define FOREACH_ELT(set,idx,vvar) \
198   for (idx = set.n-1; idx >= 0 && (vvar = set.data[idx], 1); idx--)
199 
200 #define GROW_SET(set, incr, tmpptr) \
201   (((int)(set.max + incr) < set.max					\
202     || (((size_t)((int)(set.max + incr) * sizeof(set.data[0]))		\
203 	 / sizeof(set.data[0]))						\
204 	!= (set.max + incr)))						\
205    ? 0				/* overflow */				\
206    : ((tmpptr = realloc(set.data,					\
207 			(int)(set.max + incr) * sizeof(set.data[0])))	\
208       ? (set.data = tmpptr, set.max += incr, 1)				\
209       : 0))
210 
211 /* 1 = success, 0 = failure */
212 #define ADD(set, val, tmpptr) \
213   ((set.n < set.max || GROW_SET(set, 10, tmpptr))			\
214    ? (set.data[set.n++] = val, 1)					\
215    : 0)
216 
217 #define DEL(set, idx) \
218   (set.data[idx] = set.data[--set.n], 0)
219 
220 #define FREE_SET_DATA(set) if(set.data) free(set.data);                 \
221    (set.data = 0, set.max = 0)
222 
223 
224 /* Set<struct connection *> connections; */
225 static SET(struct connection *) connections;
226 #define n_sockets	connections.n
227 #define conns		connections.data
228 
229 /* Set<u_short> udp_port_data, tcp_port_data; */
230 static SET(u_short) udp_port_data, tcp_port_data;
231 
232 #include "cm.h"
233 
234 static struct select_state sstate;
235 
236 static krb5_error_code add_udp_port(int port)
237 {
238     int	i;
239     void *tmp;
240     u_short val;
241     u_short s_port = port;
242 
243     if (s_port != port)
244 	return EINVAL;
245 
246     FOREACH_ELT (udp_port_data, i, val)
247 	if (s_port == val)
248 	    return 0;
249     if (!ADD(udp_port_data, s_port, tmp))
250 	return ENOMEM;
251     return 0;
252 }
253 
254 static krb5_error_code add_tcp_port(int port)
255 {
256     int	i;
257     void *tmp;
258     u_short val;
259     u_short s_port = port;
260 
261     if (s_port != port)
262 	return EINVAL;
263 
264     FOREACH_ELT (tcp_port_data, i, val)
265 	if (s_port == val)
266 	    return 0;
267     if (!ADD(tcp_port_data, s_port, tmp))
268 	return ENOMEM;
269     return 0;
270 }
271 
272 
273 #define USE_AF AF_INET
274 #define USE_TYPE SOCK_DGRAM
275 #define USE_PROTO 0
276 #define SOCKET_ERRNO errno
277 #include "foreachaddr.h"
278 
279 struct socksetup {
280     const char *prog;
281     krb5_error_code retval;
282 };
283 
284 static struct connection *
285 add_fd (struct socksetup *data, int sock, enum kdc_conn_type conntype,
286 	void (*service)(struct connection *, const char *, int))
287 {
288     struct connection *newconn;
289     void *tmp;
290 
291     newconn = malloc(sizeof(*newconn));
292     if (newconn == 0) {
293 	data->retval = errno;
294 	com_err(data->prog, errno,
295 		gettext("cannot allocate storage for connection info"));
296 	return 0;
297     }
298     if (!ADD(connections, newconn, tmp)) {
299 	data->retval = errno;
300 	com_err(data->prog, data->retval, gettext("cannot save socket info"));
301 	free(newconn);
302 	return 0;
303     }
304 
305     memset(newconn, 0, sizeof(*newconn));
306     newconn->type = conntype;
307     newconn->fd = sock;
308     newconn->service = service;
309     return newconn;
310 }
311 
312 static void process_packet(struct connection *, const char *, int);
313 static void accept_tcp_connection(struct connection *, const char *, int);
314 static void process_tcp_connection(struct connection *, const char *, int);
315 
316 static struct connection *
317 add_udp_fd (struct socksetup *data, int sock)
318 {
319     return add_fd(data, sock, CONN_UDP, process_packet);
320 }
321 
322 static struct connection *
323 add_tcp_listener_fd (struct socksetup *data, int sock)
324 {
325     return add_fd(data, sock, CONN_TCP_LISTENER, accept_tcp_connection);
326 }
327 
328 static struct connection *
329 add_tcp_data_fd (struct socksetup *data, int sock)
330 {
331     return add_fd(data, sock, CONN_TCP, process_tcp_connection);
332 }
333 
334 static void
335 delete_fd (struct connection *xconn)
336 {
337     struct connection *conn;
338     int i;
339 
340     FOREACH_ELT(connections, i, conn)
341 	if (conn == xconn) {
342 	    DEL(connections, i);
343 	    break;
344 	}
345     free(xconn);
346 }
347 
348 static int
349 setnbio(int sock)
350 {
351     static const int one = 1;
352     return ioctlsocket(sock, FIONBIO, (const void *)&one);
353 }
354 
355 static int
356 setnolinger(int s)
357 {
358     static const struct linger ling = { 0, 0 };
359     return setsockopt(s, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
360 }
361 
362 /* Returns -1 or socket fd.  */
363 static int
364 setup_a_tcp_listener(struct socksetup *data, struct sockaddr *addr)
365 {
366     int sock;
367 
368     sock = socket(addr->sa_family, SOCK_STREAM, 0);
369     if (sock == -1) {
370 	com_err(data->prog, errno,
371 		gettext("Cannot create TCP server socket on %s"),
372 		paddr(addr));
373 	return -1;
374     }
375     /*
376      * Solaris Kerberos: noticed that there where bind problems for tcp sockets
377      * if kdc restarted quickly.  Setting SO_REUSEADDR allowed binds to succeed.
378      */
379     if (setreuseaddr(sock, 1) < 0) {
380 	com_err(data->prog, errno,
381 		gettext("enabling SO_REUSEADDR on TCP socket"));
382 	close(sock);
383 	return -1;
384     }
385     if (bind(sock, addr, socklen(addr)) == -1) {
386 	com_err(data->prog, errno,
387 		gettext("Cannot bind TCP server socket on %s"), paddr(addr));
388 	close(sock);
389 	return -1;
390     }
391     if (listen(sock, 5) < 0) {
392 	com_err(data->prog, errno,
393 		gettext("Cannot listen on TCP server socket on %s"),
394 		paddr(addr));
395 	close(sock);
396 	return -1;
397     }
398     if (setnbio(sock)) {
399 	com_err(data->prog, errno,
400 		gettext("cannot set listening tcp socket on %s non-blocking"),
401 		paddr(addr));
402 	close(sock);
403 	return -1;
404     }
405     if (setnolinger(sock)) {
406 	com_err(data->prog, errno,
407 		gettext("disabling SO_LINGER on TCP socket on %s"),
408 		paddr(addr));
409 	close(sock);
410 	return -1;
411     }
412     return sock;
413 }
414 
415 static int
416 setup_tcp_listener_ports(struct socksetup *data)
417 {
418     struct sockaddr_in sin4;
419 #ifdef KRB5_USE_INET6
420     struct sockaddr_in6 sin6;
421 #endif
422     int i, port;
423 
424     memset(&sin4, 0, sizeof(sin4));
425     sin4.sin_family = AF_INET;
426 #ifdef HAVE_SA_LEN
427     sin4.sin_len = sizeof(sin4);
428 #endif
429     sin4.sin_addr.s_addr = INADDR_ANY;
430 
431 #ifdef KRB5_USE_INET6
432     memset(&sin6, 0, sizeof(sin6));
433     sin6.sin6_family = AF_INET6;
434 #ifdef SIN6_LEN
435     sin6.sin6_len = sizeof(sin6);
436 #endif
437     sin6.sin6_addr = in6addr_any;
438 #endif
439 
440     FOREACH_ELT (tcp_port_data, i, port) {
441 	int s4, s6;
442 
443 	set_sa_port((struct sockaddr *)&sin4, htons(port));
444 	if (!ipv6_enabled()) {
445 	    s4 = setup_a_tcp_listener(data, (struct sockaddr *)&sin4);
446 	    if (s4 < 0)
447 		return -1;
448 	    s6 = -1;
449 	} else {
450 #ifndef KRB5_USE_INET6
451 	    abort();
452 #else
453 	    s4 = s6 = -1;
454 
455 	    set_sa_port((struct sockaddr *)&sin6, htons(port));
456 
457 	    s6 = setup_a_tcp_listener(data, (struct sockaddr *)&sin6);
458 	    if (s6 < 0)
459 		return -1;
460 #ifdef IPV6_V6ONLY
461 	    if (setv6only(s6, 0))
462 		com_err(data->prog, errno,
463 		       	gettext("setsockopt(IPV6_V6ONLY,0) failed"));
464 #endif
465 
466 	    s4 = setup_a_tcp_listener(data, (struct sockaddr *)&sin4);
467 #endif /* KRB5_USE_INET6 */
468 	}
469 
470 	/* Sockets are created, prepare to listen on them.  */
471 	if (s4 >= 0) {
472 	    FD_SET(s4, &sstate.rfds);
473 	    if (s4 >= sstate.max)
474 		sstate.max = s4 + 1;
475 	    if (add_tcp_listener_fd(data, s4) == 0)
476 		close(s4);
477 	    else
478 		krb5_klog_syslog(LOG_INFO, "listening on fd %d: tcp %s",
479 				 s4, paddr((struct sockaddr *)&sin4));
480 	}
481 #ifdef KRB5_USE_INET6
482 	if (s6 >= 0) {
483 	    FD_SET(s6, &sstate.rfds);
484 	    if (s6 >= sstate.max)
485 		sstate.max = s6 + 1;
486 	    if (add_tcp_listener_fd(data, s6) == 0) {
487 		close(s6);
488 		s6 = -1;
489 	    } else
490 		krb5_klog_syslog(LOG_INFO, "listening on fd %d: tcp %s",
491 				 s6, paddr((struct sockaddr *)&sin6));
492 	    if (s4 < 0)
493 		krb5_klog_syslog(LOG_INFO,
494 				 "assuming IPv6 socket accepts IPv4");
495 	}
496 #endif
497     }
498     return 0;
499 }
500 
501 static int
502 setup_udp_port(void *P_data, struct sockaddr *addr)
503 {
504     struct socksetup *data = P_data;
505     int sock = -1, i;
506     char haddrbuf[NI_MAXHOST];
507     int err;
508     u_short port;
509 
510     err = getnameinfo(addr, socklen(addr), haddrbuf, sizeof(haddrbuf),
511 		      0, 0, NI_NUMERICHOST);
512     if (err)
513 	strcpy(haddrbuf, "<unprintable>");
514 
515     switch (addr->sa_family) {
516     case AF_INET:
517 	break;
518 #ifdef AF_INET6
519     case AF_INET6:
520 #ifdef KRB5_USE_INET6
521 	break;
522 #else
523 	{
524 	    static int first = 1;
525 	    if (first) {
526 		krb5_klog_syslog (LOG_INFO, "skipping local ipv6 addresses");
527 		first = 0;
528 	    }
529 	    return 0;
530 	}
531 #endif
532 #endif
533 #ifdef AF_LINK /* some BSD systems, AIX */
534     case AF_LINK:
535 	return 0;
536 #endif
537 #ifdef AF_DLI /* Direct Link Interface - DEC Ultrix/OSF1 link layer? */
538     case AF_DLI:
539 	return 0;
540 #endif
541     default:
542 	krb5_klog_syslog (LOG_INFO,
543 			  "skipping unrecognized local address family %d",
544 			  addr->sa_family);
545 	return 0;
546     }
547 
548     FOREACH_ELT (udp_port_data, i, port) {
549 	sock = socket (addr->sa_family, SOCK_DGRAM, 0);
550 	if (sock == -1) {
551 	    data->retval = errno;
552 	    com_err(data->prog, data->retval,
553 		    gettext("Cannot create server socket for port %d address %s"),
554 		    port, haddrbuf);
555 	    return 1;
556 	}
557 	set_sa_port(addr, htons(port));
558 	if (bind (sock, (struct sockaddr *)addr, socklen (addr)) == -1) {
559 	    data->retval = errno;
560 	    com_err(data->prog, data->retval,
561 		    gettext("Cannot bind server socket to port %d address %s"),
562 		    port, haddrbuf);
563 	    return 1;
564 	}
565 	FD_SET (sock, &sstate.rfds);
566 	if (sock >= sstate.max)
567 	    sstate.max = sock + 1;
568 	krb5_klog_syslog (LOG_INFO, "listening on fd %d: udp %s", sock,
569 			  paddr((struct sockaddr *)addr));
570 	if (add_udp_fd (data, sock) == 0)
571 	    return 1;
572     }
573     return 0;
574 }
575 
576 #if 1
577 static void klog_handler(const void *data, size_t len)
578 {
579     static char buf[BUFSIZ];
580     static int bufoffset;
581     void *p;
582 
583 #define flush_buf() \
584   (bufoffset						\
585    ? (((buf[0] == 0 || buf[0] == '\n')			\
586        ? (fork()==0?abort():(void)0)			\
587        : (void)0),					\
588       krb5_klog_syslog(LOG_INFO, "%s", buf),		\
589       memset(buf, 0, sizeof(buf)),			\
590       bufoffset = 0)					\
591    : 0)
592 
593     p = memchr(data, 0, len);
594     if (p)
595 	len = (const char *)p - (const char *)data;
596 scan_for_newlines:
597     if (len == 0)
598 	return;
599     p = memchr(data, '\n', len);
600     if (p) {
601 	if (p != data)
602 	    klog_handler(data, (size_t)((const char *)p - (const char *)data));
603 	flush_buf();
604 	len -= ((const char *)p - (const char *)data) + 1;
605 	data = 1 + (const char *)p;
606 	goto scan_for_newlines;
607     } else if (len > sizeof(buf) - 1 || len + bufoffset > sizeof(buf) - 1) {
608 	size_t x = sizeof(buf) - len - 1;
609 	klog_handler(data, x);
610 	flush_buf();
611 	len -= x;
612 	data = (const char *)data + x;
613 	goto scan_for_newlines;
614     } else {
615 	memcpy(buf + bufoffset, data, len);
616 	bufoffset += len;
617     }
618 }
619 #endif
620 
621 /* XXX */
622 extern int krb5int_debug_sendto_kdc;
623 extern void (*krb5int_sendtokdc_debug_handler)(const void*, size_t);
624 
625 krb5_error_code
626 setup_network(const char *prog)
627 {
628     struct socksetup setup_data;
629     krb5_error_code retval;
630     char *cp;
631     int i, port;
632 
633     FD_ZERO(&sstate.rfds);
634     FD_ZERO(&sstate.wfds);
635     FD_ZERO(&sstate.xfds);
636     sstate.max = 0;
637 
638 /*    krb5int_debug_sendto_kdc = 1; */
639     krb5int_sendtokdc_debug_handler = klog_handler;
640 
641     /* Handle each realm's ports */
642     for (i=0; i<kdc_numrealms; i++) {
643 	cp = kdc_realmlist[i]->realm_ports;
644 	while (cp && *cp) {
645 	    if (*cp == ',' || isspace((int) *cp)) {
646 		cp++;
647 		continue;
648 	    }
649 	    port = strtol(cp, &cp, 10);
650 	    if (cp == 0)
651 		break;
652 	    retval = add_udp_port(port);
653 	    if (retval)
654 		return retval;
655 	}
656 
657 	cp = kdc_realmlist[i]->realm_tcp_ports;
658 	while (cp && *cp) {
659 	    if (*cp == ',' || isspace((int) *cp)) {
660 		cp++;
661 		continue;
662 	    }
663 	    port = strtol(cp, &cp, 10);
664 	    if (cp == 0)
665 		break;
666 	    retval = add_tcp_port(port);
667 	    if (retval)
668 		return retval;
669 	}
670     }
671 
672     setup_data.prog = prog;
673     setup_data.retval = 0;
674     krb5_klog_syslog (LOG_INFO, "setting up network...");
675     /* To do: Use RFC 2292 interface (or follow-on) and IPV6_PKTINFO,
676        so we might need only one UDP socket; fall back to binding
677        sockets on each address only if IPV6_PKTINFO isn't
678        supported.  */
679     if (foreach_localaddr (&setup_data, setup_udp_port, 0, 0)) {
680 	return setup_data.retval;
681     }
682     setup_tcp_listener_ports(&setup_data);
683     krb5_klog_syslog (LOG_INFO, "set up %d sockets", n_sockets);
684     if (n_sockets == 0) {
685 	com_err(prog, 0, gettext("no sockets set up?"));
686 	exit (1);
687     }
688 
689     return 0;
690 }
691 
692 static void init_addr(krb5_fulladdr *faddr, struct sockaddr *sa)
693 {
694     switch (sa->sa_family) {
695     case AF_INET:
696 	faddr->address->addrtype = ADDRTYPE_INET;
697 	faddr->address->length = IPV4_ADDR_LEN;
698 	faddr->address->contents = (krb5_octet *) &sa2sin(sa)->sin_addr;
699 	faddr->port = ntohs(sa2sin(sa)->sin_port);
700 	break;
701 #ifdef KRB5_USE_INET6
702     case AF_INET6:
703 	if (IN6_IS_ADDR_V4MAPPED(&sa2sin6(sa)->sin6_addr)) {
704 	    faddr->address->addrtype = ADDRTYPE_INET;
705 	    faddr->address->length = IPV4_ADDR_LEN;
706 	    /* offset to RAM address of ipv4 part of ipv6 address */
707 	    faddr->address->contents = (IPV6_ADDR_LEN - IPV4_ADDR_LEN) +
708 		(krb5_octet *) &sa2sin6(sa)->sin6_addr;
709 	} else {
710 	    faddr->address->addrtype = ADDRTYPE_INET6;
711 	    faddr->address->length = IPV6_ADDR_LEN;
712 	    faddr->address->contents = (krb5_octet *) &sa2sin6(sa)->sin6_addr;
713 	}
714 	faddr->port = ntohs(sa2sin6(sa)->sin6_port);
715 	break;
716 #endif
717     default:
718 	faddr->address->addrtype = -1;
719 	faddr->address->length = 0;
720 	faddr->address->contents = 0;
721 	faddr->port = 0;
722 	break;
723     }
724 }
725 
726 static void process_packet(struct connection *conn, const char *prog,
727 			   int selflags)
728 {
729     int cc;
730     socklen_t saddr_len;
731     krb5_fulladdr faddr;
732     krb5_error_code retval;
733     struct sockaddr_storage saddr;
734     krb5_address addr;
735     krb5_data request;
736     krb5_data *response;
737     char pktbuf[MAX_DGRAM_SIZE];
738     int port_fd = conn->fd;
739 
740     response = NULL;
741     saddr_len = sizeof(saddr);
742     cc = recvfrom(port_fd, pktbuf, sizeof(pktbuf), 0,
743 		  (struct sockaddr *)&saddr, &saddr_len);
744     if (cc == -1) {
745 	if (errno != EINTR
746 	    /* This is how Linux indicates that a previous
747 	       transmission was refused, e.g., if the client timed out
748 	       before getting the response packet.  */
749 	    && errno != ECONNREFUSED
750 	    )
751 	    com_err(prog, errno, gettext("while receiving from network"));
752 	return;
753     }
754     if (!cc)
755 	return;		/* zero-length packet? */
756 
757     request.length = cc;
758     request.data = pktbuf;
759     faddr.address = &addr;
760     init_addr(&faddr, ss2sa(&saddr));
761     /* this address is in net order */
762     if ((retval = dispatch(&request, &faddr, &response))) {
763 	com_err(prog, retval, gettext("while dispatching (udp)"));
764 	return;
765     }
766     cc = sendto(port_fd, response->data, (socklen_t) response->length, 0,
767 		(struct sockaddr *)&saddr, saddr_len);
768     if (cc == -1) {
769 	char addrbuf[46];
770         krb5_free_data(kdc_context, response);
771 	if (inet_ntop(((struct sockaddr *)&saddr)->sa_family,
772 		      addr.contents, addrbuf, sizeof(addrbuf)) == 0) {
773 	    strcpy(addrbuf, "?");
774 	}
775 	com_err(prog, errno, gettext("while sending reply to %s/%d"),
776 		addrbuf, faddr.port);
777 	return;
778     }
779     if (cc != response->length) {
780 	krb5_free_data(kdc_context, response);
781 	com_err(prog, 0, gettext("short reply write %d vs %d\n"),
782 		response->length, cc);
783 	return;
784     }
785     krb5_free_data(kdc_context, response);
786     return;
787 }
788 
789 static int tcp_data_counter;
790 /* Solaris kerberos: getting this value from elsewhere */
791 extern int max_tcp_data_connections;
792 
793 static void kill_tcp_connection(struct connection *);
794 
795 static void accept_tcp_connection(struct connection *conn, const char *prog,
796 				  int selflags)
797 {
798     int s;
799     struct sockaddr_storage addr_s;
800     struct sockaddr *addr = (struct sockaddr *)&addr_s;
801     socklen_t addrlen = sizeof(addr_s);
802     struct socksetup sockdata;
803     struct connection *newconn;
804     char tmpbuf[10];
805 
806     s = accept(conn->fd, addr, &addrlen);
807     if (s < 0)
808 	return;
809     setnbio(s), setnolinger(s);
810 
811     sockdata.prog = prog;
812     sockdata.retval = 0;
813 
814     newconn = add_tcp_data_fd(&sockdata, s);
815     if (newconn == 0)
816 	return;
817 
818     if (getnameinfo((struct sockaddr *)&addr_s, addrlen,
819 		    newconn->u.tcp.addrbuf, sizeof(newconn->u.tcp.addrbuf),
820 		    tmpbuf, sizeof(tmpbuf),
821 		    NI_NUMERICHOST | NI_NUMERICSERV))
822 	strcpy(newconn->u.tcp.addrbuf, "???");
823     else {
824 	char *p, *end;
825 	p = newconn->u.tcp.addrbuf;
826 	end = p + sizeof(newconn->u.tcp.addrbuf);
827 	p += strlen(p);
828 	if (end - p > 2 + strlen(tmpbuf)) {
829 	    *p++ = '.';
830 	    strcpy(p, tmpbuf);
831 	}
832     }
833 #if 0
834     krb5_klog_syslog(LOG_INFO, "accepted TCP connection on socket %d from %s",
835 		     s, newconn->u.tcp.addrbuf);
836 #endif
837 
838     newconn->u.tcp.addr_s = addr_s;
839     newconn->u.tcp.addrlen = addrlen;
840     newconn->u.tcp.bufsiz = 1024 * 1024;
841     newconn->u.tcp.buffer = malloc(newconn->u.tcp.bufsiz);
842     newconn->u.tcp.start_time = time(0);
843 
844     if (++tcp_data_counter > max_tcp_data_connections) {
845 	struct connection *oldest_tcp = NULL;
846 	struct connection *c;
847 	int i;
848 
849 	krb5_klog_syslog(LOG_INFO, "too many connections");
850 
851 	FOREACH_ELT (connections, i, c) {
852 	    if (c->type != CONN_TCP)
853 		continue;
854 	    if (c == newconn)
855 		continue;
856 #if 0
857 	    krb5_klog_syslog(LOG_INFO, "fd %d started at %ld", c->fd,
858 			     c->u.tcp.start_time);
859 #endif
860 	    if (oldest_tcp == NULL
861 		|| oldest_tcp->u.tcp.start_time > c->u.tcp.start_time)
862 		oldest_tcp = c;
863 	}
864 	if (oldest_tcp != NULL) {
865 	    krb5_klog_syslog(LOG_INFO, "dropping tcp fd %d from %s",
866 			     oldest_tcp->fd, oldest_tcp->u.tcp.addrbuf);
867 	    kill_tcp_connection(oldest_tcp);
868 	    oldest_tcp = NULL;
869 	}
870     }
871     if (newconn->u.tcp.buffer == 0) {
872 	com_err(prog, errno, gettext("allocating buffer for new TCP session from %s"),
873 		newconn->u.tcp.addrbuf);
874 	delete_fd(newconn);
875 	close(s);
876 	tcp_data_counter--;
877 	return;
878     }
879     newconn->u.tcp.offset = 0;
880     newconn->u.tcp.faddr.address = &newconn->u.tcp.kaddr;
881     init_addr(&newconn->u.tcp.faddr, ss2sa(&newconn->u.tcp.addr_s));
882     SG_SET(&newconn->u.tcp.sgbuf[0], newconn->u.tcp.lenbuf, 4);
883     SG_SET(&newconn->u.tcp.sgbuf[1], 0, 0);
884 
885     FD_SET(s, &sstate.rfds);
886     if (sstate.max <= s)
887 	sstate.max = s + 1;
888 }
889 
890 static void
891 kill_tcp_connection(struct connection *conn)
892 {
893     if (conn->u.tcp.response)
894 	krb5_free_data(kdc_context, conn->u.tcp.response);
895     if (conn->u.tcp.buffer)
896 	free(conn->u.tcp.buffer);
897     FD_CLR(conn->fd, &sstate.rfds);
898     FD_CLR(conn->fd, &sstate.wfds);
899     if (sstate.max == conn->fd + 1)
900 	while (sstate.max > 0
901 	       && ! FD_ISSET(sstate.max-1, &sstate.rfds)
902 	       && ! FD_ISSET(sstate.max-1, &sstate.wfds)
903 	       /* && ! FD_ISSET(sstate.max-1, &sstate.xfds) */
904 	    )
905 	    sstate.max--;
906     close(conn->fd);
907     conn->fd = -1;
908     delete_fd(conn);
909     tcp_data_counter--;
910 }
911 
912 static krb5_error_code
913 make_toolong_error (krb5_data **out)
914 {
915     krb5_error errpkt;
916     krb5_error_code retval;
917     krb5_data *scratch;
918 
919     retval = krb5_us_timeofday(kdc_context, &errpkt.stime, &errpkt.susec);
920     if (retval)
921 	return retval;
922     errpkt.error = KRB_ERR_FIELD_TOOLONG;
923     errpkt.server = tgs_server;
924     errpkt.client = NULL;
925     errpkt.cusec = 0;
926     errpkt.ctime = 0;
927     errpkt.text.length = 0;
928     errpkt.text.data = 0;
929     errpkt.e_data.length = 0;
930     errpkt.e_data.data = 0;
931     scratch = malloc(sizeof(*scratch));
932     if (scratch == NULL)
933 	return ENOMEM;
934     retval = krb5_mk_error(kdc_context, &errpkt, scratch);
935     if (retval) {
936 	free(scratch);
937 	return retval;
938     }
939 
940     *out = scratch;
941     return 0;
942 }
943 
944 static void
945 process_tcp_connection(struct connection *conn, const char *prog, int selflags)
946 {
947     if (selflags & SSF_WRITE) {
948 	ssize_t nwrote;
949 	SOCKET_WRITEV_TEMP tmp;
950 
951 	nwrote = SOCKET_WRITEV(conn->fd, conn->u.tcp.sgp, conn->u.tcp.sgnum,
952 			       tmp);
953 	if (nwrote < 0) {
954 	    goto kill_tcp_connection;
955 	}
956 	if (nwrote == 0)
957 	    /* eof */
958 	    goto kill_tcp_connection;
959 	while (nwrote) {
960 	    sg_buf *sgp = conn->u.tcp.sgp;
961 	    if (nwrote < SG_LEN(sgp)) {
962 		SG_ADVANCE(sgp, nwrote);
963 		nwrote = 0;
964 	    } else {
965 		nwrote -= SG_LEN(sgp);
966 		conn->u.tcp.sgp++;
967 		conn->u.tcp.sgnum--;
968 		if (conn->u.tcp.sgnum == 0 && nwrote != 0)
969 		    abort();
970 	    }
971 	}
972 	if (conn->u.tcp.sgnum == 0) {
973 	    /* finished sending */
974 	    /* We should go back to reading, though if we sent a
975 	       FIELD_TOOLONG error in reply to a length with the high
976 	       bit set, RFC 4120 says we have to close the TCP
977 	       stream.  */
978 	    goto kill_tcp_connection;
979 	}
980     } else if (selflags & SSF_READ) {
981 	/* Read message length and data into one big buffer, already
982 	   allocated at connect time.  If we have a complete message,
983 	   we stop reading, so we should only be here if there is no
984 	   data in the buffer, or only an incomplete message.  */
985 	size_t len;
986 	ssize_t nread;
987 	if (conn->u.tcp.offset < 4) {
988 	    /* msglen has not been computed */
989 	    /* XXX Doing at least two reads here, letting the kernel
990 	       worry about buffering.  It'll be faster when we add
991 	       code to manage the buffer here.  */
992 	    len = 4 - conn->u.tcp.offset;
993 	    nread = SOCKET_READ(conn->fd,
994 				conn->u.tcp.buffer + conn->u.tcp.offset, len);
995 	    if (nread < 0)
996 		/* error */
997 		goto kill_tcp_connection;
998 	    if (nread == 0)
999 		/* eof */
1000 		goto kill_tcp_connection;
1001 	    conn->u.tcp.offset += nread;
1002 	    if (conn->u.tcp.offset == 4) {
1003 		unsigned char *p = (unsigned char *)conn->u.tcp.buffer;
1004 		conn->u.tcp.msglen = ((p[0] << 24)
1005 				      | (p[1] << 16)
1006 				      | (p[2] <<  8)
1007 				      | p[3]);
1008 		if (conn->u.tcp.msglen > conn->u.tcp.bufsiz - 4) {
1009 		    krb5_error_code err;
1010 		    /* message too big */
1011 		    krb5_klog_syslog(LOG_ERR, "TCP client %s wants %lu bytes, cap is %lu",
1012 				     conn->u.tcp.addrbuf, (unsigned long) conn->u.tcp.msglen,
1013 				     (unsigned long) conn->u.tcp.bufsiz - 4);
1014 		    /* XXX Should return an error.  */
1015 		    err = make_toolong_error (&conn->u.tcp.response);
1016 		    if (err) {
1017 			krb5_klog_syslog(LOG_ERR,
1018 					 "error constructing KRB_ERR_FIELD_TOOLONG error! %s",
1019 					 error_message(err));
1020 			goto kill_tcp_connection;
1021 		    }
1022 		    goto have_response;
1023 		}
1024 	    }
1025 	} else {
1026 	    /* msglen known */
1027 	    krb5_data request;
1028 	    krb5_error_code err;
1029 
1030 	    len = conn->u.tcp.msglen - (conn->u.tcp.offset - 4);
1031 	    nread = SOCKET_READ(conn->fd,
1032 				conn->u.tcp.buffer + conn->u.tcp.offset, len);
1033 	    if (nread < 0)
1034 		/* error */
1035 		goto kill_tcp_connection;
1036 	    if (nread == 0)
1037 		/* eof */
1038 		goto kill_tcp_connection;
1039 	    conn->u.tcp.offset += nread;
1040 	    if (conn->u.tcp.offset < conn->u.tcp.msglen + 4)
1041 		return;
1042 	    /* have a complete message, and exactly one message */
1043 	    request.length = conn->u.tcp.msglen;
1044 	    request.data = conn->u.tcp.buffer + 4;
1045 	    err = dispatch(&request, &conn->u.tcp.faddr,
1046 			   &conn->u.tcp.response);
1047 	    if (err) {
1048 		com_err(prog, err, gettext("while dispatching (tcp)"));
1049 		goto kill_tcp_connection;
1050 	    }
1051 	have_response:
1052 	    conn->u.tcp.lenbuf[0] = 0xff & (conn->u.tcp.response->length >> 24);
1053 	    conn->u.tcp.lenbuf[1] = 0xff & (conn->u.tcp.response->length >> 16);
1054 	    conn->u.tcp.lenbuf[2] = 0xff & (conn->u.tcp.response->length >> 8);
1055 	    conn->u.tcp.lenbuf[3] = 0xff & (conn->u.tcp.response->length >> 0);
1056 	    SG_SET(&conn->u.tcp.sgbuf[1], conn->u.tcp.response->data,
1057 		   conn->u.tcp.response->length);
1058 	    conn->u.tcp.sgp = conn->u.tcp.sgbuf;
1059 	    conn->u.tcp.sgnum = 2;
1060 	    FD_CLR(conn->fd, &sstate.rfds);
1061 	    FD_SET(conn->fd, &sstate.wfds);
1062 	}
1063     } else
1064 	abort();
1065 
1066     return;
1067 
1068 kill_tcp_connection:
1069     kill_tcp_connection(conn);
1070 }
1071 
1072 static void service_conn(struct connection *conn, const char *prog,
1073 			 int selflags)
1074 {
1075     conn->service(conn, prog, selflags);
1076 }
1077 
1078 krb5_error_code
1079 listen_and_process(const char *prog)
1080 {
1081     int			nfound;
1082     /* This struct contains 3 fd_set objects; on some platforms, they
1083        can be rather large.  Making this static avoids putting all
1084        that junk on the stack.  */
1085     static struct select_state sout;
1086     int			i, sret;
1087     krb5_error_code	err;
1088 
1089     if (conns == (struct connection **) NULL)
1090 	return KDC5_NONET;
1091 
1092     while (!signal_requests_exit) {
1093 	if (signal_requests_hup) {
1094 	    krb5_klog_reopen(kdc_context);
1095 	    signal_requests_hup = 0;
1096 	}
1097 	sstate.end_time.tv_sec = sstate.end_time.tv_usec = 0;
1098 	err = krb5int_cm_call_select(&sstate, &sout, &sret);
1099 	if (err) {
1100 	    com_err(prog, err, gettext("while selecting for network input(1)"));
1101 	    continue;
1102 	}
1103 	if (sret == -1) {
1104 	    if (errno != EINTR)
1105 		com_err(prog, errno, gettext("while selecting for network input(2)"));
1106 	    continue;
1107 	}
1108 	nfound = sret;
1109 	for (i=0; i<n_sockets && nfound > 0; i++) {
1110 	    int sflags = 0;
1111 	    if (conns[i]->fd < 0)
1112 		abort();
1113 	    if (FD_ISSET(conns[i]->fd, &sout.rfds))
1114 		sflags |= SSF_READ, nfound--;
1115 	    if (FD_ISSET(conns[i]->fd, &sout.wfds))
1116 		sflags |= SSF_WRITE, nfound--;
1117 	    if (sflags)
1118 		service_conn(conns[i], prog, sflags);
1119 	}
1120     }
1121     return 0;
1122 }
1123 
1124 krb5_error_code
1125 closedown_network(const char *prog)
1126 {
1127     int i;
1128     struct connection *conn;
1129 
1130     if (conns == (struct connection **) NULL)
1131 	return KDC5_NONET;
1132 
1133     FOREACH_ELT (connections, i, conn) {
1134 	if (conn->fd >= 0)
1135 	    (void) close(conn->fd);
1136 	DEL (connections, i);
1137 	/* There may also be per-connection data in the tcp structure
1138 	   (tcp.buffer, tcp.response) that we're not freeing here.
1139 	   That should only happen if we quit with a connection in
1140 	   progress.  */
1141 	free(conn);
1142     }
1143     FREE_SET_DATA(connections);
1144     FREE_SET_DATA(udp_port_data);
1145     FREE_SET_DATA(tcp_port_data);
1146 
1147     return 0;
1148 }
1149 
1150 #endif /* INET */
1151