/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * This code implements the Starcat Virtual Console host daemon (see cvcd(1M)).
 * It accepts one TCP connection at a time on a well-known port.  Once a
 * connection is accepted, the console redirection driver (cvcdredir(7D)) is
 * opened, and console I/O is routed back and forth between the two file
 * descriptors (network and redirection driver).  Per-socket IPsec is used to
 * secure the connection if it is enabled with the "-a", "-u" and or "-e"
 * command line options.
 */

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>

#include <fcntl.h>
#include <sys/filio.h>		/* Just to get FIONBIO... */
#include <unistd.h>
#include <errno.h>
#include <stropts.h>
#include <signal.h>
#include <syslog.h>
#include <sys/utsname.h>
#include <sys/stat.h>
#include <locale.h>
#include <limits.h>

#include <sys/priocntl.h>
#include <sys/tspriocntl.h>
#include <sys/rtpriocntl.h>

#include <netdb.h>
#include <sys/socket.h>
#include <tiuser.h>

#include <sys/sc_cvcio.h>


/*
 * Header files for per-socket IPsec
 */
#include <netinet/in.h>
#include <net/pfkeyv2.h>

/*
 * The IPsec socket option struct, from ipsec(7P):
 *
 *     typedef struct ipsec_req {
 *         uint_t      ipsr_ah_req;            AH request
 *         uint_t      ipsr_esp_req;           ESP request
 *         uint_t      ipsr_self_encap_req;    Self-Encap request
 *         uint8_t     ipsr_auth_alg;          Auth algs for AH
 *         uint8_t     ipsr_esp_alg;           Encr algs for ESP
 *         uint8_t     ipsr_esp_auth_alg;      Auth algs for ESP
 *     } ipsec_req_t;
 *
 * The -a option sets the ipsr_auth_alg field. Allowable arguments
 * are "none", "md5", or "sha1". The -e option sets the ipsr_esp_alg
 * field. Allowable arguments are "none", "des", or "3des". "none"
 * is the default for both options. The -u option sets ipsr_esp_auth_alg.
 * Allowable arguments are the same as -a.
 *
 * The arguments ("md5", "des", etc.) are named so that they match
 * kmd(1m)'s accepted arguments which are listed on the SC in
 * /etc/opt/SUNWSMS/SMS/config/kmd_policy.cf.
 */
#define	AH_REQ		(IPSEC_PREF_REQUIRED | IPSEC_PREF_UNIQUE)
#define	ESP_REQ		(IPSEC_PREF_REQUIRED | IPSEC_PREF_UNIQUE)
#define	SELF_ENCAP_REQ	0x0

/*
 * A type to hold the command line argument string used to select a
 * particular authentication header (AH) or encapsulating security
 * payload (ESP) algorithm and the ID used for that algorithm when
 * filling the ipsec_req_t structure which is passed to
 * setsockopt(3SOCKET).
 */
typedef struct cvcd_alg {
	char		*arg_name;
	uint8_t		alg_id;
} cvcd_alg_t;

/*
 *  Misc. defines.
 */
#define	NODENAME	"/etc/nodename"
#define	NETWORK_PFD	0
#define	REDIR_PFD	1
#define	LISTEN_PFD	2
#define	NUM_PFDS	3

/*
 * Function prototypes
 */
static void cvcd_set_priority(void);
static int  cvcd_init_host_socket(int port, uint8_t ah_auth_alg,
    uint8_t esp_encr_alg, uint8_t esp_auth_alg);
static void cvcd_do_network_console(void);
static void cvcd_err(int code, char *format, ...);
static void cvcd_usage(void);
static uint8_t cvcd_get_alg(cvcd_alg_t *algs, char *arg);
static boolean_t cvcd_global_policy(void);

/*
 *  Globals
 */
static struct pollfd	pfds[NUM_PFDS];
static char		progname[MAXPATHLEN];
static int		debug = 0;

/*
 * Array of acceptable -a, -u and -e arguments.
 */
static cvcd_alg_t auth_algs_array[] = {
	{ "none",	SADB_AALG_NONE },	/* -a none or -u none */
	{ "md5",	SADB_AALG_MD5HMAC },	/* -a md5  or -u md5 */
	{ "sha1",	SADB_AALG_SHA1HMAC },	/* -a sha1 or -u sha1 */
	{ NULL,		0x0 }
}, esp_algs_array[] = {
	{ "none",	SADB_EALG_NONE },	/* -e none */
	{ "des",	SADB_EALG_DESCBC },	/* -e des  */
	{ "3des",	SADB_EALG_3DESCBC },	/* -e 3des */
	{ NULL,		0x0 }
};


int
main(int argc, char **argv)
{
	int			err;
	int			opt;
	int			tport = 0;
	char			*hostname;
	struct utsname		utsname;
	int			fd;
	int			i;
	struct servent		*se;
	char 			prefix[256];
	uint8_t			ah_auth_alg 	= SADB_AALG_NONE;
	uint8_t			esp_encr_alg  	= SADB_EALG_NONE;
	uint8_t			esp_auth_alg  	= SADB_AALG_NONE;

	(void) setlocale(LC_ALL, "");
	(void) strcpy(progname, argv[0]);

#ifdef DEBUG
	while ((opt = getopt(argc, argv, "a:e:u:dp:")) != EOF) {
#else
	while ((opt = getopt(argc, argv, "a:e:u:")) != EOF) {
#endif
		switch (opt) {
			case 'a' :
			case 'u' :
					if (opt == 'a')
						ah_auth_alg = cvcd_get_alg(
						    auth_algs_array, optarg);
					else
						esp_auth_alg = cvcd_get_alg(
						    auth_algs_array, optarg);
					break;

			case 'e' :	esp_encr_alg = cvcd_get_alg(
					    esp_algs_array, optarg);
					break;
#ifdef DEBUG
			case 'd' :	debug = 1;
					break;

			case 'p' :	tport = atoi(optarg);
					break;
#endif  /* DEBUG */

			default  :	cvcd_usage();
					exit(1);
		}
	}

	if (uname(&utsname) == -1) {
		perror("HOSTNAME not defined");
		exit(1);
	}
	hostname = utsname.nodename;

	/*
	 * hostname may still be NULL, depends on when cvcd was started
	 * in the boot sequence.  If it is NULL, try one more time
	 * to get a hostname -> look in the /etc/nodename file.
	 */
	if (!strlen(hostname)) {
		/*
		 * try to get the hostname from the /etc/nodename file
		 * we reuse the utsname.nodename buffer here!  hostname
		 * already points to it.
		 */
		if ((fd = open(NODENAME, O_RDONLY)) > 0) {
			if ((i = read(fd, utsname.nodename, SYS_NMLN)) <= 0) {
				cvcd_err(LOG_WARNING,
				    "failed to acquire hostname");
			} else {
				utsname.nodename[i-1] = '\0';
			}
			(void) close(fd);
		}
	}
	/*
	 * If all attempts to get the hostname have failed, put something
	 * meaningful in the buffer.
	 */
	if (!strlen(hostname)) {
		(void) strcpy(utsname.nodename, "(unknown)");
	}

	/*
	 * Must be root.
	 */
	if (debug == 0 && geteuid() != 0) {
		fprintf(stderr, "cvcd: Must be root");
		exit(1);
	}

	/*
	 * Daemonize...
	 */
	if (debug == 0) {
		closefrom(0);
		(void) chdir("/");
		(void) umask(0);
		if (fork() != 0) {
			exit(0);
		}
		(void) setpgrp();
		(void) sprintf(prefix, "%s-(HOSTNAME:%s)", progname, hostname);
		openlog(prefix, LOG_CONS | LOG_NDELAY, LOG_LOCAL0);
	}

	/*
	 * Initialize the array of pollfds used to track the listening socket,
	 * the connection to the console redirection driver, and the network
	 * connection.
	 */
	(void) memset((void *)pfds, 0, NUM_PFDS * sizeof (struct pollfd));
	for (i = 0; i < NUM_PFDS; i++) {
		pfds[i].fd = -1;
	}

	/* SPR 94004 */
	(void) sigignore(SIGTERM);

	/*
	 * SPR 83644: cvc and kadb are not compatible under heavy loads.
	 *	Fix: will give cvcd highest TS priority at execution time.
	 */
	cvcd_set_priority();

	/*
	 * If not already determined by a command-line flag, figure out which
	 * port we're supposed to be listening on.
	 */
	if (tport == 0) {
		if ((se = getservbyname(CVCD_SERVICE, "tcp")) == NULL) {
			cvcd_err(LOG_ERR, "getservbyname(%s) not found",
				CVCD_SERVICE);
			exit(1);
		}
		tport = se->s_port;
	}

	if (debug == 1) {
		cvcd_err(LOG_DEBUG, "tport = %d, debug = %d", tport, debug);
	}

	/*
	 * Attempt to initialize the socket we'll use to listen for incoming
	 * connections.  No need to check the return value, as the call will
	 * exit if it fails.
	 */
	pfds[LISTEN_PFD].fd = cvcd_init_host_socket(tport, ah_auth_alg,
	    esp_encr_alg, esp_auth_alg);

	/*
	 * Now that we're all set up, we loop forever waiting for connections
	 * (one at a time) and then driving network console activity over them.
	 */
	for (;;) {
		/*
		 * Start by waiting for an incoming connection.
		 */
		do {
			pfds[LISTEN_PFD].events = POLLIN;
			err = poll(&(pfds[LISTEN_PFD]), 1, -1);
			if (err == -1) {
				cvcd_err(LOG_ERR, "poll: %s", strerror(errno));
				exit(1);
			}
			if ((err > 0) &&
			    (pfds[LISTEN_PFD].revents & POLLIN)) {
				fd = accept(pfds[LISTEN_PFD].fd, NULL, NULL);
				if ((fd == -1) && (errno != EWOULDBLOCK)) {
					cvcd_err(LOG_ERR, "accept: %s",
					    strerror(errno));
					exit(1);
				}
			}
		} while (fd == -1);

		/*
		 * We have a connection.  Set the new socket nonblocking, and
		 * initialize the appropriate pollfd.  In theory, the new socket
		 * is _already_ non-blocking because accept() is supposed to
		 * hand us a socket with the same properties as the socket we're
		 * listening on, but it won't hurt to make sure.
		 */
		opt = 1;
		err = ioctl(fd, FIONBIO, &opt);
		if (err == -1) {
			cvcd_err(LOG_ERR, "ioctl: %s", strerror(errno));
			(void) close(fd);
			continue;
		}
		pfds[NETWORK_PFD].fd = fd;

		/*
		 * Since we're ready to do network console stuff, go ahead and
		 * open the Network Console redirection driver, which will
		 * switch traffic from the IOSRAM path to the network path if
		 * the network path has been selected in cvc.
		 */
		fd = open(CVCREDIR_DEV, O_RDWR|O_NDELAY);
		if (fd == -1) {
			cvcd_err(LOG_ERR, "open(redir): %s", strerror(errno));
			exit(1);
		}
		pfds[REDIR_PFD].fd = fd;

		/*
		 * We have a network connection and we have the redirection
		 * driver open, so drive the network console until something
		 * changes.
		 */
		cvcd_do_network_console();

		/*
		 * cvcd_do_network_console doesn't return until there's a
		 * problem, so we need to close the network connection and the
		 * redirection driver and start the whole loop over again.
		 */
		(void) close(pfds[NETWORK_PFD].fd);
		pfds[NETWORK_PFD].fd = -1;
		(void) close(pfds[REDIR_PFD].fd);
		pfds[REDIR_PFD].fd = -1;
	}

	/* NOTREACHED */
	return (1);
}

/*
 * cvcd_get_alg
 *
 * Returns the ID of the first algorithm found in
 * the 'algs' array with a name matching 'arg'. If
 * there is no matching algorithm, the function does
 * not return. The 'algs' array must be terminated
 * by an entry containing a NULL 'arg_name' field.
 */
static uint8_t
cvcd_get_alg(cvcd_alg_t *algs, char *arg)
{
	cvcd_alg_t *alg;

	for (alg = algs; alg->arg_name != NULL && arg != NULL; alg++) {
		if (strncmp(alg->arg_name, arg, strlen(alg->arg_name) + 1)
		    == 0) {
			return (alg->alg_id);
		}
	}

	cvcd_usage();
	exit(1);
	/* NOTREACHED */
}

/*
 * cvcd_set_priority
 *
 * DESCRIBE
 * SPR 83644: cvc and kadb are not compatible under heavy loads.
 *	Fix: will give cvcd highest TS priority at execution time.
 */
static void
cvcd_set_priority(void)
{
	id_t		pid, tsID;
	pcparms_t	pcparms;
	tsparms_t	*tsparmsp;
	short		tsmaxpri;
	pcinfo_t	info;

	pid = getpid();
	pcparms.pc_cid = PC_CLNULL;
	tsparmsp = (tsparms_t *)pcparms.pc_clparms;

	/* Get scheduler properties for this PID */
	if (priocntl(P_PID, pid, PC_GETPARMS, (caddr_t)&pcparms) == -1L) {
		cvcd_err(LOG_ERR, "Warning: can't set priority.");
		cvcd_err(LOG_ERR, "priocntl(GETPARMS): %s", strerror(errno));
		return;
	}

	/* Get class ID and maximum priority for TS process class */
	(void) strcpy(info.pc_clname, "TS");
	if (priocntl(0L, 0L, PC_GETCID, (caddr_t)&info) == -1L) {
		cvcd_err(LOG_ERR, "Warning: can't set priority.");
		cvcd_err(LOG_ERR, "priocntl(GETCID): %s", strerror(errno));
		return;
	}
	tsmaxpri = ((struct tsinfo *)info.pc_clinfo)->ts_maxupri;
	tsID = info.pc_cid;

	/* Print priority info in debug mode */
	if (debug) {
		if (pcparms.pc_cid == tsID) {
			cvcd_err(LOG_DEBUG,
			    "PID: %d, current priority: %d, Max priority: %d.",
			    pid, tsparmsp->ts_upri, tsmaxpri);
		}
	}
	/* Change proc's priority to maxtspri */
	pcparms.pc_cid = tsID;
	tsparmsp->ts_upri = tsmaxpri;
	tsparmsp->ts_uprilim = tsmaxpri;

	if (priocntl(P_PID, pid, PC_SETPARMS, (caddr_t)&pcparms) == -1L) {
		cvcd_err(LOG_ERR, "Warning: can't set priority.");
		cvcd_err(LOG_ERR, "priocntl(SETPARMS): %s", strerror(errno));
	}

	/* Print new priority info in debug mode */
	if (debug) {
		if (priocntl(P_PID, pid, PC_GETPARMS, (caddr_t)&pcparms) ==
		    -1L) {
			cvcd_err(LOG_ERR, "priocntl(GETPARMS): %s",
			    strerror(errno));
		} else {
			cvcd_err(LOG_DEBUG, "PID: %d, new priority: %d.", pid,
			    tsparmsp->ts_upri);
		}
	}
}


/*
 * cvcd_init_host_socket
 *
 * Given a TCP port number, create and initialize a socket appropriate for
 * accepting incoming connections to that port.
 */
static int
cvcd_init_host_socket(int port, uint8_t ah_auth_alg, uint8_t esp_encr_alg,
	uint8_t esp_auth_alg)
{
	int			err;
	int			fd;
	int			optval;
	int 			optlen = sizeof (optval);
	ipsec_req_t		ipsec_req;	/* For per-socket IPsec */
	struct sockaddr_in6	sin6;		/* IPv6 listen socket   */

	/*
	 * Start by creating the socket, which needs to support IPv6.
	 */
	fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
	if (fd == -1) {
		cvcd_err(LOG_ERR, "socket: %s", strerror(errno));
		exit(1);
	}

	/*
	 * Set the SO_REUSEADDR option, and make the socket non-blocking.
	 */
	optval = 1;
	err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, optlen);
	if (err == -1) {
		cvcd_err(LOG_ERR, "setsockopt: %s", strerror(errno));
		exit(1);
	}

	err = ioctl(fd, FIONBIO, &optval);
	if (err == -1) {
		cvcd_err(LOG_ERR, "ioctl: %s", strerror(errno));
		exit(1);
	}

	/*
	 * Enable per-socket IPsec if the user specified an AH or ESP
	 * algorithm to use and global policy is not in effect.
	 */
	if (!cvcd_global_policy() &&
	    (ah_auth_alg != SADB_AALG_NONE || esp_encr_alg != SADB_EALG_NONE ||
	    esp_auth_alg != SADB_AALG_NONE)) {
		bzero(&ipsec_req, sizeof (ipsec_req));

		/* Hardcoded values */
		ipsec_req.ipsr_self_encap_req	= SELF_ENCAP_REQ;
		/* User defined */
		ipsec_req.ipsr_auth_alg		= ah_auth_alg;
		ipsec_req.ipsr_esp_alg		= esp_encr_alg;
		if (ah_auth_alg != SADB_AALG_NONE)
			ipsec_req.ipsr_ah_req		= AH_REQ;
		if (esp_encr_alg != SADB_EALG_NONE ||
		    esp_auth_alg != SADB_AALG_NONE) {
			ipsec_req.ipsr_esp_req		= ESP_REQ;
			ipsec_req.ipsr_esp_auth_alg	= esp_auth_alg;
		}

		err = setsockopt(fd, IPPROTO_IPV6, IPV6_SEC_OPT,
		    (void *)&ipsec_req, sizeof (ipsec_req));

		if (err == -1) {
			cvcd_err(LOG_ERR, "failed to enable per-socket IPsec");
			cvcd_err(LOG_ERR, "setsockopt: %s", strerror(errno));
			exit(1);
		}
	}

	/*
	 * Bind the socket to our local address and port.
	 */
	bzero(&sin6, sizeof (sin6));
	sin6.sin6_family = AF_INET6;
	sin6.sin6_port = htons(port);
	sin6.sin6_addr = in6addr_any;
	err = bind(fd, (struct sockaddr *)&sin6, sizeof (sin6));
	if (err == -1) {
		cvcd_err(LOG_ERR, "bind: %s", strerror(errno));
		exit(1);
	}

	/*
	 * Indicate that we want to accept connections on this socket.  Since we
	 * only allow one connection at a time anyway, specify a maximum backlog
	 * of 1.
	 */
	err = listen(fd, 1);
	if (err == -1) {
		cvcd_err(LOG_ERR, "listen: %s", strerror(errno));
		exit(1);
	}

	return (fd);
}


/*
 * cvcd_do_network_console
 *
 * With established connections to the network and the redirection driver,
 * shuttle data between the two until something goes wrong.
 */
static void
cvcd_do_network_console(void)
{
	int	i;
	int	err;
	int	count;
	short	revents;
	int	input_len = 0;
	int	output_len = 0;
	int	input_off = 0;
	int	output_off = 0;
	char	input_buf[MAXPKTSZ];
	char	output_buf[MAXPKTSZ];

	for (;;) {
		/*
		 * Wait for activity on any of the open file descriptors, which
		 * includes the ability to write data if we have any to write.
		 * If poll() fails, break out of the network console processing
		 * loop.
		 */
		pfds[LISTEN_PFD].events = POLLIN;
		pfds[NETWORK_PFD].events = POLLIN;
		if (output_len != 0) {
			pfds[NETWORK_PFD].events |= POLLOUT;
		}
		pfds[REDIR_PFD].events = POLLIN;
		if (input_len != 0) {
			pfds[REDIR_PFD].events |= POLLOUT;
		}
		err = poll(pfds, NUM_PFDS, -1);
		if (err == -1) {
			cvcd_err(LOG_ERR, "poll: %s", strerror(errno));
			break;
		}

		/*
		 * If any errors or hangups were detected, or one of our file
		 * descriptors is bad, bail out of the network console
		 * processing loop.
		 */
		for (i = 0; i < NUM_PFDS; i++) {
			revents = pfds[i].revents;
			if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
				cvcd_err(LOG_NOTICE,
				    "poll: status on %s fd:%s%s%s",
				    ((i == LISTEN_PFD) ? "listen" :
				    ((i == NETWORK_PFD) ? "network" : "redir")),
				    (revents & POLLERR) ? " error" : "",
				    (revents & POLLHUP) ? " hangup" : "",
				    (revents & POLLNVAL) ? " bad fd" : "");
				goto fail;	/* 'break' wouldn't work here */
			}
		}

		/*
		 * Start by rejecting any connection attempts, since we only
		 * allow one network connection at a time.
		 */
		if (pfds[LISTEN_PFD].revents & POLLIN) {
			int	fd;

			fd = accept(pfds[LISTEN_PFD].fd, NULL, NULL);
			if (fd > 0) {
				(void) close(fd);
			}
		}

		/*
		 * If we have data waiting to be written in one direction or the
		 * other, go ahead and try to send the data on its way.  We're
		 * going to attempt the writes regardless of whether the poll
		 * indicated that the destinations are ready, because we want to
		 * find out if either descriptor has a problem (e.g. broken
		 * network link).
		 * If an "unexpected" error is detected, give up and break out
		 * of the network console processing loop.
		 */
		if (output_len != 0) {
			count = write(pfds[NETWORK_PFD].fd,
			    &(output_buf[output_off]), output_len);
			if ((count == -1) && (errno != EAGAIN)) {
				cvcd_err(LOG_ERR, "write(network): %s",
				    strerror(errno));
				break;
			} else if (count > 0) {
				output_len -= count;
				if (output_len == 0) {
					output_off = 0;
				} else {
					output_off += count;
				}
			}
		}

		if (input_len != 0) {
			count = write(pfds[REDIR_PFD].fd,
			    &(input_buf[input_off]), input_len);
			if ((count == -1) && (errno != EAGAIN)) {
				cvcd_err(LOG_ERR, "write(redir): %s",
				    strerror(errno));
				break;
			} else if (count > 0) {
				input_len -= count;
				if (input_len == 0) {
					input_off = 0;
				} else {
					input_off += count;
				}
			}
		}

		/*
		 * Finally, take a look at each data source and, if there isn't
		 * any residual data from that source still waiting to be
		 * processed, see if more data can be read.  We don't want to
		 * read more data from a source if we haven't finished
		 * processing the last data we read from it because doing so
		 * would maximize the amount of data lost if the network console
		 * failed or was closed.
		 * If an "unexpected" error is detected, give up and break out
		 * of the network console processing loop.
		 * The call to read() appears to be in the habit of returning 0
		 * when you've read all of the data from a stream that has been
		 * hung up, and poll apparently feels that that condition
		 * justifies setting POLLIN, so we're going to treat 0 as an
		 * error return from read().
		 */
		if ((output_len == 0) && (pfds[REDIR_PFD].revents & POLLIN)) {
			count = read(pfds[REDIR_PFD].fd, output_buf, MAXPKTSZ);
			if (count <= 0) {
				/*
				 * Reading 0 simply means there is no data
				 * available, since this is a terminal.
				 */
				if ((count < 0) && (errno != EAGAIN)) {
					cvcd_err(LOG_ERR, "read(redir): %s",
					    strerror(errno));
					break;
				}
			} else {
				output_len = count;
				output_off = 0;
			}
		}

		if ((input_len == 0) && (pfds[NETWORK_PFD].revents & POLLIN)) {
			count = read(pfds[NETWORK_PFD].fd, input_buf, MAXPKTSZ);
			if (count <= 0) {
				/*
				 * Reading 0 here implies a hangup, since this
				 * is a non-blocking socket that poll() reported
				 * as having data available.  This will
				 * typically occur when the console user drops
				 * to OBP or intentially switches to IOSRAM
				 * mode.
				 */
				if (count == 0) {
					cvcd_err(LOG_NOTICE,
					    "read(network): hangup detected");
					break;
				} else if (errno != EAGAIN) {
					cvcd_err(LOG_ERR, "read(network): %s",
					    strerror(errno));
					break;
				}
			} else {
				input_len = count;
				input_off = 0;
			}
		}
	} /* End forever loop */

	/*
	 * If we get here, something bad happened during an attempt to access
	 * either the redirection driver or the network connection.  There
	 * doesn't appear to be any way to avoid the possibility of losing
	 * console input and/or input in that case, so we should at least report
	 * the loss if it happens.
	 * XXX - We could do more, but is it worth the effort?  Logging the
	 *	 lost data would be pretty easy... actually preserving it
	 *	 in the console flow would be a lot harder.  We're more robust
	 *	 than the previous generation at this point, at least, so
	 *	 perhaps that's enough for now?
	 */
fail:
	if (input_len != 0) {
		cvcd_err(LOG_ERR, "console input lost");
	}
	if (output_len != 0) {
		cvcd_err(LOG_ERR, "console output lost");
	}
}


static void
cvcd_usage()
{
#if defined(DEBUG)
	(void) printf("%s [-d] [-p port] "
	    "[-a none|md5|sha1] [-e none|des|3des] [-u none|md5|sha1]\n",
	    progname);
#else
	(void) printf("%s [-a none|md5|sha1] [-e none|des|3des] "
	    "[-u none|md5|sha1]\n", progname);
#endif  /* DEBUG */
}

/*
 * cvcd_err ()
 *
 * Description:
 * Log messages via syslog daemon.
 *
 * Input:
 * code - logging code
 * format - messages to log
 *
 * Output:
 * void
 *
 */
static void
cvcd_err(int code, char *format, ...)
{
	va_list	varg_ptr;
	char	buf[MAXPKTSZ];

	va_start(varg_ptr, format);
	(void) vsnprintf(buf, MAXPKTSZ, format, varg_ptr);
	va_end(varg_ptr);

	if (debug == 0) {
		syslog(code, buf);
	} else {
		(void) fprintf(stderr, "%s: %s\n", progname, buf);
	}
}

/*
 * has_cvcd_token
 *
 * Look for "?port [cvc_hostd|442]" in input buf.
 * Assume only a single thread calls here.
 */
static boolean_t
has_cvcd_token(char *buf)
{
	char 		*token;
	char		*delims = "{} \t\n";
	boolean_t 	port = B_FALSE;

	while ((token = strtok(buf, delims)) != NULL) {
		buf = NULL;
		if (port == B_TRUE) {
			if (strcmp(token, "cvc_hostd") == 0 ||
			    strcmp(token, "442") == 0) {
				return (B_TRUE);
			} else {
				return (B_FALSE);
			}
		}
		if (strlen(token) == 5) {
			token++;
			if (strcmp(token, "port") == 0) {
				port = B_TRUE;
				continue;
			}
		}
	}
	return (B_FALSE);
}

/*
 * cvcd_global_policy
 *
 * Check global policy file for cvcd entry. Just covers common cases.
 */
static boolean_t
cvcd_global_policy()
{
	FILE		*fp;
	char		buf[256];
	boolean_t	rv = B_FALSE;

	fp = fopen("/etc/inet/ipsecinit.conf", "r");
	if (fp == NULL)
		return (B_FALSE);
	while (fgets(buf, sizeof (buf), fp) != NULL) {
		if (buf[0] == '#')
			continue;
		if (has_cvcd_token(buf)) {
			rv = B_TRUE;
			cvcd_err(LOG_NOTICE, "cvcd using global policy");
			break;
		}
	}
	(void) fclose(fp);
	return (rv);
}