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