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