xref: /titanic_52/usr/src/cmd/cvcd/sparc/sun4u/starcat/cvcd.c (revision 749f21d359d8fbd020c974a1a5227316221bfc9c)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * This code implements the Starcat Virtual Console host daemon (see cvcd(1M)).
31  * It accepts one TCP connection at a time on a well-known port.  Once a
32  * connection is accepted, the console redirection driver (cvcdredir(7D)) is
33  * opened, and console I/O is routed back and forth between the two file
34  * descriptors (network and redirection driver).  Per-socket IPsec is used to
35  * secure the connection if it is enabled with the "-a", "-u" and or "-e"
36  * command line options.
37  */
38 
39 #include <stdio.h>
40 #include <stdarg.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <strings.h>
44 
45 #include <fcntl.h>
46 #include <sys/filio.h>		/* Just to get FIONBIO... */
47 #include <unistd.h>
48 #include <errno.h>
49 #include <stropts.h>
50 #include <signal.h>
51 #include <syslog.h>
52 #include <sys/utsname.h>
53 #include <sys/stat.h>
54 #include <locale.h>
55 #include <limits.h>
56 
57 #include <sys/priocntl.h>
58 #include <sys/tspriocntl.h>
59 #include <sys/rtpriocntl.h>
60 
61 #include <netdb.h>
62 #include <sys/socket.h>
63 #include <tiuser.h>
64 
65 #include <sys/sc_cvcio.h>
66 
67 
68 /*
69  * Header files for per-socket IPsec
70  */
71 #include <netinet/in.h>
72 #include <net/pfkeyv2.h>
73 
74 /*
75  * The IPsec socket option struct, from ipsec(7P):
76  *
77  *     typedef struct ipsec_req {
78  *         uint_t      ipsr_ah_req;            AH request
79  *         uint_t      ipsr_esp_req;           ESP request
80  *         uint_t      ipsr_self_encap_req;    Self-Encap request
81  *         uint8_t     ipsr_auth_alg;          Auth algs for AH
82  *         uint8_t     ipsr_esp_alg;           Encr algs for ESP
83  *         uint8_t     ipsr_esp_auth_alg;      Auth algs for ESP
84  *     } ipsec_req_t;
85  *
86  * The -a option sets the ipsr_auth_alg field. Allowable arguments
87  * are "none", "md5", or "sha1". The -e option sets the ipsr_esp_alg
88  * field. Allowable arguments are "none", "des", or "3des". "none"
89  * is the default for both options. The -u option sets ipsr_esp_auth_alg.
90  * Allowable arguments are the same as -a.
91  *
92  * The arguments ("md5", "des", etc.) are named so that they match
93  * kmd(1m)'s accepted arguments which are listed on the SC in
94  * /etc/opt/SUNWSMS/SMS/config/kmd_policy.cf.
95  */
96 #define	AH_REQ		(IPSEC_PREF_REQUIRED | IPSEC_PREF_UNIQUE)
97 #define	ESP_REQ		(IPSEC_PREF_REQUIRED | IPSEC_PREF_UNIQUE)
98 #define	SELF_ENCAP_REQ	0x0
99 
100 /*
101  * A type to hold the command line argument string used to select a
102  * particular authentication header (AH) or encapsulating security
103  * payload (ESP) algorithm and the ID used for that algorithm when
104  * filling the ipsec_req_t structure which is passed to
105  * setsockopt(3SOCKET).
106  */
107 typedef struct cvcd_alg {
108 	char		*arg_name;
109 	uint8_t		alg_id;
110 } cvcd_alg_t;
111 
112 /*
113  *  Misc. defines.
114  */
115 #define	NODENAME	"/etc/nodename"
116 #define	NETWORK_PFD	0
117 #define	REDIR_PFD	1
118 #define	LISTEN_PFD	2
119 #define	NUM_PFDS	3
120 
121 /*
122  * Function prototypes
123  */
124 static void cvcd_set_priority(void);
125 static int  cvcd_init_host_socket(int port, uint8_t ah_auth_alg,
126     uint8_t esp_encr_alg, uint8_t esp_auth_alg);
127 static void cvcd_do_network_console(void);
128 static void cvcd_err(int code, char *format, ...);
129 static void cvcd_usage(void);
130 static uint8_t cvcd_get_alg(cvcd_alg_t *algs, char *arg);
131 
132 /*
133  *  Globals
134  */
135 static struct pollfd	pfds[NUM_PFDS];
136 static char		progname[MAXPATHLEN];
137 static int		debug = 0;
138 
139 /*
140  * Array of acceptable -a, -u and -e arguments.
141  */
142 static cvcd_alg_t auth_algs_array[] = {
143 	{ "none",	SADB_AALG_NONE },	/* -a none or -u none */
144 	{ "md5",	SADB_AALG_MD5HMAC },	/* -a md5  or -u md5 */
145 	{ "sha1",	SADB_AALG_SHA1HMAC },	/* -a sha1 or -u sha1 */
146 	{ NULL,		0x0 }
147 }, esp_algs_array[] = {
148 	{ "none",	SADB_EALG_NONE },	/* -e none */
149 	{ "des",	SADB_EALG_DESCBC },	/* -e des  */
150 	{ "3des",	SADB_EALG_3DESCBC },	/* -e 3des */
151 	{ NULL,		0x0 }
152 };
153 
154 
155 int
156 main(int argc, char **argv)
157 {
158 	int			err;
159 	int			opt;
160 	int			tport = 0;
161 	char			*hostname;
162 	struct utsname		utsname;
163 	int			fd;
164 	int			i;
165 	struct servent		*se;
166 	char 			prefix[256];
167 	uint8_t			ah_auth_alg 	= SADB_AALG_NONE;
168 	uint8_t			esp_encr_alg  	= SADB_EALG_NONE;
169 	uint8_t			esp_auth_alg  	= SADB_AALG_NONE;
170 
171 	(void) setlocale(LC_ALL, "");
172 	(void) strcpy(progname, argv[0]);
173 
174 #ifdef DEBUG
175 	while ((opt = getopt(argc, argv, "a:e:u:dp:")) != EOF) {
176 #else
177 	while ((opt = getopt(argc, argv, "a:e:u:")) != EOF) {
178 #endif
179 		switch (opt) {
180 			case 'a' :
181 			case 'u' :
182 					if (opt == 'a')
183 						ah_auth_alg = cvcd_get_alg(
184 						    auth_algs_array, optarg);
185 					else
186 						esp_auth_alg = cvcd_get_alg(
187 						    auth_algs_array, optarg);
188 					break;
189 
190 			case 'e' :	esp_encr_alg = cvcd_get_alg(
191 					    esp_algs_array, optarg);
192 					break;
193 #ifdef DEBUG
194 			case 'd' :	debug = 1;
195 					break;
196 
197 			case 'p' :	tport = atoi(optarg);
198 					break;
199 #endif  /* DEBUG */
200 
201 			default  :	cvcd_usage();
202 					exit(1);
203 		}
204 	}
205 
206 	if (uname(&utsname) == -1) {
207 		perror("HOSTNAME not defined");
208 		exit(1);
209 	}
210 	hostname = utsname.nodename;
211 
212 	/*
213 	 * hostname may still be NULL, depends on when cvcd was started
214 	 * in the boot sequence.  If it is NULL, try one more time
215 	 * to get a hostname -> look in the /etc/nodename file.
216 	 */
217 	if (!strlen(hostname)) {
218 		/*
219 		 * try to get the hostname from the /etc/nodename file
220 		 * we reuse the utsname.nodename buffer here!  hostname
221 		 * already points to it.
222 		 */
223 		if ((fd = open(NODENAME, O_RDONLY)) > 0) {
224 			if ((i = read(fd, utsname.nodename, SYS_NMLN)) <= 0) {
225 				cvcd_err(LOG_WARNING,
226 				    "failed to acquire hostname");
227 			} else {
228 				utsname.nodename[i-1] = '\0';
229 			}
230 			(void) close(fd);
231 		}
232 	}
233 	/*
234 	 * If all attempts to get the hostname have failed, put something
235 	 * meaningful in the buffer.
236 	 */
237 	if (!strlen(hostname)) {
238 		(void) strcpy(utsname.nodename, "(unknown)");
239 	}
240 
241 	/*
242 	 * Must be root.
243 	 */
244 	if (debug == 0 && geteuid() != 0) {
245 		fprintf(stderr, "cvcd: Must be root");
246 		exit(1);
247 	}
248 
249 	/*
250 	 * Daemonize...
251 	 */
252 	if (debug == 0) {
253 		closefrom(0);
254 		(void) chdir("/");
255 		(void) umask(0);
256 		if (fork() != 0) {
257 			exit(0);
258 		}
259 		(void) setpgrp();
260 		(void) sprintf(prefix, "%s-(HOSTNAME:%s)", progname, hostname);
261 		openlog(prefix, LOG_CONS | LOG_NDELAY, LOG_LOCAL0);
262 	}
263 
264 	/*
265 	 * Initialize the array of pollfds used to track the listening socket,
266 	 * the connection to the console redirection driver, and the network
267 	 * connection.
268 	 */
269 	(void) memset((void *)pfds, 0, NUM_PFDS * sizeof (struct pollfd));
270 	for (i = 0; i < NUM_PFDS; i++) {
271 		pfds[i].fd = -1;
272 	}
273 
274 	/* SPR 94004 */
275 	(void) sigignore(SIGTERM);
276 
277 	/*
278 	 * SPR 83644: cvc and kadb are not compatible under heavy loads.
279 	 *	Fix: will give cvcd highest TS priority at execution time.
280 	 */
281 	cvcd_set_priority();
282 
283 	/*
284 	 * If not already determined by a command-line flag, figure out which
285 	 * port we're supposed to be listening on.
286 	 */
287 	if (tport == 0) {
288 		if ((se = getservbyname(CVCD_SERVICE, "tcp")) == NULL) {
289 			cvcd_err(LOG_ERR, "getservbyname(%s) not found",
290 				CVCD_SERVICE);
291 			exit(1);
292 		}
293 		tport = se->s_port;
294 	}
295 
296 	if (debug == 1) {
297 		cvcd_err(LOG_DEBUG, "tport = %d, debug = %d", tport, debug);
298 	}
299 
300 	/*
301 	 * Attempt to initialize the socket we'll use to listen for incoming
302 	 * connections.  No need to check the return value, as the call will
303 	 * exit if it fails.
304 	 */
305 	pfds[LISTEN_PFD].fd = cvcd_init_host_socket(tport, ah_auth_alg,
306 	    esp_encr_alg, esp_auth_alg);
307 
308 	/*
309 	 * Now that we're all set up, we loop forever waiting for connections
310 	 * (one at a time) and then driving network console activity over them.
311 	 */
312 	for (;;) {
313 		/*
314 		 * Start by waiting for an incoming connection.
315 		 */
316 		do {
317 			pfds[LISTEN_PFD].events = POLLIN;
318 			err = poll(&(pfds[LISTEN_PFD]), 1, -1);
319 			if (err == -1) {
320 				cvcd_err(LOG_ERR, "poll: %s", strerror(errno));
321 				exit(1);
322 			}
323 			if ((err > 0) &&
324 			    (pfds[LISTEN_PFD].revents & POLLIN)) {
325 				fd = accept(pfds[LISTEN_PFD].fd, NULL, NULL);
326 				if ((fd == -1) && (errno != EWOULDBLOCK)) {
327 					cvcd_err(LOG_ERR, "accept: %s",
328 					    strerror(errno));
329 					exit(1);
330 				}
331 			}
332 		} while (fd == -1);
333 
334 		/*
335 		 * We have a connection.  Set the new socket nonblocking, and
336 		 * initialize the appropriate pollfd.  In theory, the new socket
337 		 * is _already_ non-blocking because accept() is supposed to
338 		 * hand us a socket with the same properties as the socket we're
339 		 * listening on, but it won't hurt to make sure.
340 		 */
341 		opt = 1;
342 		err = ioctl(fd, FIONBIO, &opt);
343 		if (err == -1) {
344 			cvcd_err(LOG_ERR, "ioctl: %s", strerror(errno));
345 			(void) close(fd);
346 			continue;
347 		}
348 		pfds[NETWORK_PFD].fd = fd;
349 
350 		/*
351 		 * Since we're ready to do network console stuff, go ahead and
352 		 * open the Network Console redirection driver, which will
353 		 * switch traffic from the IOSRAM path to the network path if
354 		 * the network path has been selected in cvc.
355 		 */
356 		fd = open(CVCREDIR_DEV, O_RDWR|O_NDELAY);
357 		if (fd == -1) {
358 			cvcd_err(LOG_ERR, "open(redir): %s", strerror(errno));
359 			exit(1);
360 		}
361 		pfds[REDIR_PFD].fd = fd;
362 
363 		/*
364 		 * We have a network connection and we have the redirection
365 		 * driver open, so drive the network console until something
366 		 * changes.
367 		 */
368 		cvcd_do_network_console();
369 
370 		/*
371 		 * cvcd_do_network_console doesn't return until there's a
372 		 * problem, so we need to close the network connection and the
373 		 * redirection driver and start the whole loop over again.
374 		 */
375 		(void) close(pfds[NETWORK_PFD].fd);
376 		pfds[NETWORK_PFD].fd = -1;
377 		(void) close(pfds[REDIR_PFD].fd);
378 		pfds[REDIR_PFD].fd = -1;
379 	}
380 
381 	/* NOTREACHED */
382 	return (1);
383 }
384 
385 /*
386  * cvcd_get_alg
387  *
388  * Returns the ID of the first algorithm found in
389  * the 'algs' array with a name matching 'arg'. If
390  * there is no matching algorithm, the function does
391  * not return. The 'algs' array must be terminated
392  * by an entry containing a NULL 'arg_name' field.
393  */
394 static uint8_t
395 cvcd_get_alg(cvcd_alg_t *algs, char *arg)
396 {
397 	cvcd_alg_t *alg;
398 
399 	for (alg = algs; alg->arg_name != NULL && arg != NULL; alg++) {
400 		if (strncmp(alg->arg_name, arg, strlen(alg->arg_name) + 1)
401 		    == 0) {
402 			return (alg->alg_id);
403 		}
404 	}
405 
406 	cvcd_usage();
407 	exit(1);
408 	/* NOTREACHED */
409 }
410 
411 /*
412  * cvcd_set_priority
413  *
414  * DESCRIBE
415  * SPR 83644: cvc and kadb are not compatible under heavy loads.
416  *	Fix: will give cvcd highest TS priority at execution time.
417  */
418 static void
419 cvcd_set_priority(void)
420 {
421 	id_t		pid, tsID;
422 	pcparms_t	pcparms;
423 	tsparms_t	*tsparmsp;
424 	short		tsmaxpri;
425 	pcinfo_t	info;
426 
427 	pid = getpid();
428 	pcparms.pc_cid = PC_CLNULL;
429 	tsparmsp = (tsparms_t *)pcparms.pc_clparms;
430 
431 	/* Get scheduler properties for this PID */
432 	if (priocntl(P_PID, pid, PC_GETPARMS, (caddr_t)&pcparms) == -1L) {
433 		cvcd_err(LOG_ERR, "Warning: can't set priority.");
434 		cvcd_err(LOG_ERR, "priocntl(GETPARMS): %s", strerror(errno));
435 		return;
436 	}
437 
438 	/* Get class ID and maximum priority for TS process class */
439 	(void) strcpy(info.pc_clname, "TS");
440 	if (priocntl(0L, 0L, PC_GETCID, (caddr_t)&info) == -1L) {
441 		cvcd_err(LOG_ERR, "Warning: can't set priority.");
442 		cvcd_err(LOG_ERR, "priocntl(GETCID): %s", strerror(errno));
443 		return;
444 	}
445 	tsmaxpri = ((struct tsinfo *)info.pc_clinfo)->ts_maxupri;
446 	tsID = info.pc_cid;
447 
448 	/* Print priority info in debug mode */
449 	if (debug) {
450 		if (pcparms.pc_cid == tsID) {
451 			cvcd_err(LOG_DEBUG,
452 			    "PID: %d, current priority: %d, Max priority: %d.",
453 			    pid, tsparmsp->ts_upri, tsmaxpri);
454 		}
455 	}
456 	/* Change proc's priority to maxtspri */
457 	pcparms.pc_cid = tsID;
458 	tsparmsp->ts_upri = tsmaxpri;
459 	tsparmsp->ts_uprilim = tsmaxpri;
460 
461 	if (priocntl(P_PID, pid, PC_SETPARMS, (caddr_t)&pcparms) == -1L) {
462 		cvcd_err(LOG_ERR, "Warning: can't set priority.");
463 		cvcd_err(LOG_ERR, "priocntl(SETPARMS): %s", strerror(errno));
464 	}
465 
466 	/* Print new priority info in debug mode */
467 	if (debug) {
468 		if (priocntl(P_PID, pid, PC_GETPARMS, (caddr_t)&pcparms) ==
469 		    -1L) {
470 			cvcd_err(LOG_ERR, "priocntl(GETPARMS): %s",
471 			    strerror(errno));
472 		} else {
473 			cvcd_err(LOG_DEBUG, "PID: %d, new priority: %d.", pid,
474 			    tsparmsp->ts_upri);
475 		}
476 	}
477 }
478 
479 
480 /*
481  * cvcd_init_host_socket
482  *
483  * Given a TCP port number, create and initialize a socket appropriate for
484  * accepting incoming connections to that port.
485  */
486 static int
487 cvcd_init_host_socket(int port, uint8_t ah_auth_alg, uint8_t esp_encr_alg,
488 	uint8_t esp_auth_alg)
489 {
490 	int			err;
491 	int			fd;
492 	int			optval;
493 	int 			optlen = sizeof (optval);
494 	ipsec_req_t		ipsec_req;	/* For per-socket IPsec */
495 	struct sockaddr_in6	sin6;		/* IPv6 listen socket   */
496 
497 	/*
498 	 * Start by creating the socket, which needs to support IPv6.
499 	 */
500 	fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
501 	if (fd == -1) {
502 		cvcd_err(LOG_ERR, "socket: %s", strerror(errno));
503 		exit(1);
504 	}
505 
506 	/*
507 	 * Set the SO_REUSEADDR option, and make the socket non-blocking.
508 	 */
509 	optval = 1;
510 	err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, optlen);
511 	if (err == -1) {
512 		cvcd_err(LOG_ERR, "setsockopt: %s", strerror(errno));
513 		exit(1);
514 	}
515 
516 	err = ioctl(fd, FIONBIO, &optval);
517 	if (err == -1) {
518 		cvcd_err(LOG_ERR, "ioctl: %s", strerror(errno));
519 		exit(1);
520 	}
521 
522 	/*
523 	 * Enable per-socket IPsec if the user specified an AH or ESP
524 	 * algorithm to use.
525 	 */
526 	if (ah_auth_alg != SADB_AALG_NONE || esp_encr_alg != SADB_EALG_NONE ||
527 	    esp_auth_alg != SADB_AALG_NONE) {
528 		bzero(&ipsec_req, sizeof (ipsec_req));
529 
530 		/* Hardcoded values */
531 		ipsec_req.ipsr_self_encap_req	= SELF_ENCAP_REQ;
532 		/* User defined */
533 		ipsec_req.ipsr_auth_alg		= ah_auth_alg;
534 		ipsec_req.ipsr_esp_alg		= esp_encr_alg;
535 		if (ah_auth_alg != SADB_AALG_NONE)
536 			ipsec_req.ipsr_ah_req		= AH_REQ;
537 		if (esp_encr_alg != SADB_EALG_NONE ||
538 		    esp_auth_alg != SADB_AALG_NONE) {
539 			ipsec_req.ipsr_esp_req		= ESP_REQ;
540 			ipsec_req.ipsr_esp_auth_alg	= esp_auth_alg;
541 		}
542 
543 		err = setsockopt(fd, IPPROTO_IPV6, IPV6_SEC_OPT,
544 		    (void *)&ipsec_req, sizeof (ipsec_req));
545 
546 		if (err == -1) {
547 			cvcd_err(LOG_ERR, "failed to enable per-socket IPsec");
548 			cvcd_err(LOG_ERR, "setsockopt: %s", strerror(errno));
549 			exit(1);
550 		}
551 	}
552 
553 	/*
554 	 * Bind the socket to our local address and port.
555 	 */
556 	bzero(&sin6, sizeof (sin6));
557 	sin6.sin6_family = AF_INET6;
558 	sin6.sin6_port = htons(port);
559 	sin6.sin6_addr = in6addr_any;
560 	err = bind(fd, (struct sockaddr *)&sin6, sizeof (sin6));
561 	if (err == -1) {
562 		cvcd_err(LOG_ERR, "bind: %s", strerror(errno));
563 		exit(1);
564 	}
565 
566 	/*
567 	 * Indicate that we want to accept connections on this socket.  Since we
568 	 * only allow one connection at a time anyway, specify a maximum backlog
569 	 * of 1.
570 	 */
571 	err = listen(fd, 1);
572 	if (err == -1) {
573 		cvcd_err(LOG_ERR, "listen: %s", strerror(errno));
574 		exit(1);
575 	}
576 
577 	return (fd);
578 }
579 
580 
581 /*
582  * cvcd_do_network_console
583  *
584  * With established connections to the network and the redirection driver,
585  * shuttle data between the two until something goes wrong.
586  */
587 static void
588 cvcd_do_network_console(void)
589 {
590 	int	i;
591 	int	err;
592 	int	count;
593 	short	revents;
594 	int	input_len = 0;
595 	int	output_len = 0;
596 	int	input_off = 0;
597 	int	output_off = 0;
598 	char	input_buf[MAXPKTSZ];
599 	char	output_buf[MAXPKTSZ];
600 
601 	for (;;) {
602 		/*
603 		 * Wait for activity on any of the open file descriptors, which
604 		 * includes the ability to write data if we have any to write.
605 		 * If poll() fails, break out of the network console processing
606 		 * loop.
607 		 */
608 		pfds[LISTEN_PFD].events = POLLIN;
609 		pfds[NETWORK_PFD].events = POLLIN;
610 		if (output_len != 0) {
611 			pfds[NETWORK_PFD].events |= POLLOUT;
612 		}
613 		pfds[REDIR_PFD].events = POLLIN;
614 		if (input_len != 0) {
615 			pfds[REDIR_PFD].events |= POLLOUT;
616 		}
617 		err = poll(pfds, NUM_PFDS, -1);
618 		if (err == -1) {
619 			cvcd_err(LOG_ERR, "poll: %s", strerror(errno));
620 			break;
621 		}
622 
623 		/*
624 		 * If any errors or hangups were detected, or one of our file
625 		 * descriptors is bad, bail out of the network console
626 		 * processing loop.
627 		 */
628 		for (i = 0; i < NUM_PFDS; i++) {
629 			revents = pfds[i].revents;
630 			if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
631 				cvcd_err(LOG_NOTICE,
632 				    "poll: status on %s fd:%s%s%s",
633 				    ((i == LISTEN_PFD) ? "listen" :
634 				    ((i == NETWORK_PFD) ? "network" : "redir")),
635 				    (revents & POLLERR) ? " error" : "",
636 				    (revents & POLLHUP) ? " hangup" : "",
637 				    (revents & POLLNVAL) ? " bad fd" : "");
638 				goto fail;	/* 'break' wouldn't work here */
639 			}
640 		}
641 
642 		/*
643 		 * Start by rejecting any connection attempts, since we only
644 		 * allow one network connection at a time.
645 		 */
646 		if (pfds[LISTEN_PFD].revents & POLLIN) {
647 			int	fd;
648 
649 			fd = accept(pfds[LISTEN_PFD].fd, NULL, NULL);
650 			if (fd > 0) {
651 				(void) close(fd);
652 			}
653 		}
654 
655 		/*
656 		 * If we have data waiting to be written in one direction or the
657 		 * other, go ahead and try to send the data on its way.  We're
658 		 * going to attempt the writes regardless of whether the poll
659 		 * indicated that the destinations are ready, because we want to
660 		 * find out if either descriptor has a problem (e.g. broken
661 		 * network link).
662 		 * If an "unexpected" error is detected, give up and break out
663 		 * of the network console processing loop.
664 		 */
665 		if (output_len != 0) {
666 			count = write(pfds[NETWORK_PFD].fd,
667 			    &(output_buf[output_off]), output_len);
668 			if ((count == -1) && (errno != EAGAIN)) {
669 				cvcd_err(LOG_ERR, "write(network): %s",
670 				    strerror(errno));
671 				break;
672 			} else if (count > 0) {
673 				output_len -= count;
674 				if (output_len == 0) {
675 					output_off = 0;
676 				} else {
677 					output_off += count;
678 				}
679 			}
680 		}
681 
682 		if (input_len != 0) {
683 			count = write(pfds[REDIR_PFD].fd,
684 			    &(input_buf[input_off]), input_len);
685 			if ((count == -1) && (errno != EAGAIN)) {
686 				cvcd_err(LOG_ERR, "write(redir): %s",
687 				    strerror(errno));
688 				break;
689 			} else if (count > 0) {
690 				input_len -= count;
691 				if (input_len == 0) {
692 					input_off = 0;
693 				} else {
694 					input_off += count;
695 				}
696 			}
697 		}
698 
699 		/*
700 		 * Finally, take a look at each data source and, if there isn't
701 		 * any residual data from that source still waiting to be
702 		 * processed, see if more data can be read.  We don't want to
703 		 * read more data from a source if we haven't finished
704 		 * processing the last data we read from it because doing so
705 		 * would maximize the amount of data lost if the network console
706 		 * failed or was closed.
707 		 * If an "unexpected" error is detected, give up and break out
708 		 * of the network console processing loop.
709 		 * The call to read() appears to be in the habit of returning 0
710 		 * when you've read all of the data from a stream that has been
711 		 * hung up, and poll apparently feels that that condition
712 		 * justifies setting POLLIN, so we're going to treat 0 as an
713 		 * error return from read().
714 		 */
715 		if ((output_len == 0) && (pfds[REDIR_PFD].revents & POLLIN)) {
716 			count = read(pfds[REDIR_PFD].fd, output_buf, MAXPKTSZ);
717 			if (count <= 0) {
718 				/*
719 				 * Reading 0 simply means there is no data
720 				 * available, since this is a terminal.
721 				 */
722 				if ((count < 0) && (errno != EAGAIN)) {
723 					cvcd_err(LOG_ERR, "read(redir): %s",
724 					    strerror(errno));
725 					break;
726 				}
727 			} else {
728 				output_len = count;
729 				output_off = 0;
730 			}
731 		}
732 
733 		if ((input_len == 0) && (pfds[NETWORK_PFD].revents & POLLIN)) {
734 			count = read(pfds[NETWORK_PFD].fd, input_buf, MAXPKTSZ);
735 			if (count <= 0) {
736 				/*
737 				 * Reading 0 here implies a hangup, since this
738 				 * is a non-blocking socket that poll() reported
739 				 * as having data available.  This will
740 				 * typically occur when the console user drops
741 				 * to OBP or intentially switches to IOSRAM
742 				 * mode.
743 				 */
744 				if (count == 0) {
745 					cvcd_err(LOG_NOTICE,
746 					    "read(network): hangup detected");
747 					break;
748 				} else if (errno != EAGAIN) {
749 					cvcd_err(LOG_ERR, "read(network): %s",
750 					    strerror(errno));
751 					break;
752 				}
753 			} else {
754 				input_len = count;
755 				input_off = 0;
756 			}
757 		}
758 	} /* End forever loop */
759 
760 	/*
761 	 * If we get here, something bad happened during an attempt to access
762 	 * either the redirection driver or the network connection.  There
763 	 * doesn't appear to be any way to avoid the possibility of losing
764 	 * console input and/or input in that case, so we should at least report
765 	 * the loss if it happens.
766 	 * XXX - We could do more, but is it worth the effort?  Logging the
767 	 *	 lost data would be pretty easy... actually preserving it
768 	 *	 in the console flow would be a lot harder.  We're more robust
769 	 *	 than the previous generation at this point, at least, so
770 	 *	 perhaps that's enough for now?
771 	 */
772 fail:
773 	if (input_len != 0) {
774 		cvcd_err(LOG_ERR, "console input lost");
775 	}
776 	if (output_len != 0) {
777 		cvcd_err(LOG_ERR, "console output lost");
778 	}
779 }
780 
781 
782 static void
783 cvcd_usage()
784 {
785 #if defined(DEBUG)
786 	(void) printf("%s [-d] [-p port] "
787 	    "[-a none|md5|sha1] [-e none|des|3des] [-u none|md5|sha1]\n",
788 	    progname);
789 #else
790 	(void) printf("%s [-a none|md5|sha1] [-e none|des|3des] "
791 	    "[-u none|md5|sha1]\n", progname);
792 #endif  /* DEBUG */
793 }
794 
795 /*
796  * cvcd_err ()
797  *
798  * Description:
799  * Log messages via syslog daemon.
800  *
801  * Input:
802  * code - logging code
803  * format - messages to log
804  *
805  * Output:
806  * void
807  *
808  */
809 static void
810 cvcd_err(int code, char *format, ...)
811 {
812 	va_list	varg_ptr;
813 	char	buf[MAXPKTSZ];
814 
815 	va_start(varg_ptr, format);
816 	(void) vsnprintf(buf, MAXPKTSZ, format, varg_ptr);
817 	va_end(varg_ptr);
818 
819 	if (debug == 0) {
820 		syslog(code, buf);
821 	} else {
822 		(void) fprintf(stderr, "%s: %s\n", progname, buf);
823 	}
824 }
825