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