/*
 * 5799-WZQ (C) COPYRIGHT = NONE
 * LICENSED MATERIALS - PROPERTY OF IBM
 */
/* $Header:conn.c 12.0$ */
/* $ACIS:conn.c 12.0$ */
/* $Source: /ibm/acis/usr/src/ibm/rvd/server/RCS/conn.c,v $ */

#ifndef lint
static char *rcsid = "$Header:conn.c 12.0$";
#endif


#ifndef lint
static char rcsid_conn_c[] = "$Header:conn.c 12.0$";
#endif lint

/* Copyright 1984 by the Massachusetts Institute of Technology */
/* See permission and disclaimer notice in the file "notice.h" */
#include "notice.h"

/* Routines for manipulating the per-connection information.  There
 * is one instance of a conn structure for each spun-up virtual disk.
 * The conn structures are pointed to by an array of pointers; the index
 * of the conn structure in the array is passed to the user end as a hint.
 * This speeds demultiplexing of incoming packets to connections.
 * As packets arrive, they are sorted into the read and write queues
 * for the correct connection.  Whenever there are packets on a
 * connection's read and write queues, the connection block is itself
 * enqueued on a list of connections having work.
 */

#include	<sys/types.h>
#include	<stdio.h>
#include	<sys/param.h>
#include	<sys/time.h>
#include	<netinet/in.h>
#include	<netinet/in_systm.h>
#include	<netinet/ip.h>
#include	<netinet/rvd.h>
#include	<machineio/vdconst.h>
#ifdef	KERBEROS
#include	<krb.h>
#endif	KERBEROS

#include	"rvd_types.h"
#include	"ctl_msgs.h"
#include	"rvdadd.h"
#include	"custom.h"
#include	"logging.h"
#include	"ctl_pkt.h"
#include	"obj.h"
#include	"queue.h"
#include	"packet.h"
#include	"physd.h"
#include	"virtd.h"
#include	"conn.h"
#include	"extern.h"

#define	MAXDISP		200		/* max length of display_virtual
					 * connections=\n\\(this stuff) */
#define	MORE		"more=true\n"
#define	NUMBER		"\nnumber=%D\n"

#define	Length(s)	(sizeof(s) - 1)

char	*cn_vsum();

extern struct physd_q	all_physds;
static	struct	conn	**all_conns;	/* array of pointers to conns */
static	int		nconn;		/* current size of all_conns
					 * array in elements.
					 */

struct	conn_stats {			/* connection statistics */
	int	cn_ovflo;		/* times all_conns had to expand */
	int	cn_dupl;		/* times cn_unique failed */
	int	cn_badidx;		/* times index hint failed */
} conn_stats;

/* Initialize the connection information.  Create the all_conns array and
 * initialize to show no current connections.
 * This routine depends on calloc zeroing memory.
 */

cn_init()

{
	all_conns = (struct conn **)calloc(NCONN, sizeof(struct conn *));
	nconn = NCONN;
}

/* Create a new connection structure, initialized with the specified
 * arguments, and add it to the all_conns array.  This routine does
 * no argument validation; it assumes that the cn_unique routine
 * has already been called to insure that the specified arguments
 * describe a unique connection.  Returns the newly created connection
 * structure.
 */

struct conn *
cn_new(fhost, drive, nonce, rbfactor, vd, mode)

struct	in_addr	fhost;			/* foreign host address */
u_long		drive;			/* foreign drive number */
u_long		nonce;			/* spinup nonce */
u_char		rbfactor;		/* read blocking factor */
struct	virtd	*vd;			/* virtual disk */
u_char		mode;			/* spinup mode */
{
	register struct	conn	*cn;	/* newly allocated conn */
	register int	i;		/* index in all_conns array */
	struct	conn	**new_all_conns; /* new array if neded */

	cn = (struct conn *)obj_alloc(sizeof(struct conn), CONN_TYPE);

	cn->cn_fhost = fhost;		/* store the arguments */
	cn->cn_drive = drive;
	cn->cn_nonce = nonce;
	cn->cn_rbfactor = MIN(rbfactor, RVDMXBPP);
	cn->cn_virtd = vd;
	q_init(&cn->cn_reads);		/* circularize read queue */
	q_init(&cn->cn_writes);		/* and write queue */


	for (i = 0; i < nconn; i++) {	/* find a free slot */
		if (all_conns[i] == NULL) { /* found one */
			all_conns[i] = cn;  /* fill it in */
			cn->cn_index = i;
			vd_increment(vd, mode);	/* incr virt disk ref count */
			return(cn);
		}
	}


/* No space in all_conns.  Allocate a new one that is NCONN larger, copy
 * the old all_conns array into the new space and free up the old space.
 * (This depends on calloc zeroing storage.)  Null is returned if there
 * is not enough free memory to extend the connection array.
 */

	conn_stats.cn_ovflo++;
	new_all_conns = (struct conn **)calloc(NCONN, sizeof(struct conn *));
	if (new_all_conns == NULL) {
		obj_free((char *)cn);
		return((struct conn *)NULL);
	}
	bcopy((char *)all_conns, (char *)new_all_conns, nconn *
	    sizeof(struct conn *) );
	free((char *)all_conns);
	all_conns = new_all_conns;
	all_conns[nconn] = cn;		/* we know this one's free */
	cn->cn_index = nconn;
	vd_increment(vd, mode);		/* incr virt disk ref count */
	nconn += NCONN;			/* show new size */
	return(cn);
}


/* Insure that there is presently no connection open with the specified
 * foreign host/drive number.  If there is no such connection return
 * NULL; if there is such a connection, return a pointer to it.
 * An opportunity exists here to speed things up a little by
 * caching as a hint the index of a free slot in the all_conns array.
 * cn_new could use the hint to find a place to insert the new
 * connection and hence not have to scan the entire array again.
 */


struct conn *
cn_unique(fhost, drive)

struct	in_addr	fhost;			/* foreign host */
u_long	drive;				/* drive number */
{
	register int	i;		/* index in all_conns array */
	register struct	conn	*cn;	/* temp conn pointer */

	for (i = 0; i < nconn; i++) {
		if ((cn = all_conns[i]) != NULL
		  && cn->cn_fhost.s_addr == fhost.s_addr
		  && cn->cn_drive == drive) { /* match */
			conn_stats.cn_dupl++;
			return(cn);	/* return it */
		}
	}
	return(NULL);			/* not found */
}


/* Look up the specified foreign host/drive/index combination in the
 * all_conns array.  The index, which is a hint, is checked first:
 * if it's in bounds and all_conns[i] matches the foreign host and
 * drive, you win.  Otherwise the entire array is searched for a match.
 * Returns the connection, if found, or NULL otherwise.
 */


struct conn *
cn_lookup(fhost, drive, index)

struct	in_addr	fhost;			/* foreign host */
u_long	drive;				/* foreign drive number */
register u_long	index;			/* hinted index */
{
	register int	i;		/* temp index */
	register struct	conn	*cn;	/* connection to return */

	/* Check to see if the hinted index is valid; if so, return the
	   connection. */

	if (index < nconn && (cn = all_conns[index]) != NULL
	  && cn->cn_fhost.s_addr == fhost.s_addr && cn->cn_drive == drive) {
		return(cn);
	}

	conn_stats.cn_badidx++;

	/* Otherwise, search the connection table for the required drive. */

	for (i = 0; i < nconn; i++) {
		if ((cn = all_conns[i]) != NULL
		  && cn->cn_fhost.s_addr == fhost.s_addr
		  && cn->cn_drive == drive) {
		  	if (loglevel(LOG_CLIENT_ERROR))
				syslog(LOG_INFO,
				   "cn_lookup: drive %D pack %s bad index %d (%s)",
				   drive, cn->cn_virtd->vd_pack, index,
				   inet_ntoa(fhost));
			return(cn);
		}
	}

	/* If we can't find it at all, return NULL. */
	return(NULL);
}


/* Terminate the specified connection.  Remove it from the all_conns list,
 * null out its entry, and free its storage.  If there are any packets
 * on its read or write queues, free them also.
 */

cn_end(cn)

register struct conn	*cn;		/* connection to end */
{
	if (loglevel(LOG_SPINS))
		log_down(cn);		/* log disconnection */

	rem_q_elem(cn);			/* dequeue it if enqueued */

	all_conns[cn->cn_index] = NULL;	/* null out conn table entry */

	pkt_q_free(&cn->cn_reads);	/* free any pending read */
	pkt_q_free(&cn->cn_writes);	/* and write packets */

	vd_decrement(cn->cn_virtd);	/* dec virt. disk ref count */

	obj_free((char *)cn);
}


/* Process a "spindown_virtual" control message.  The item descriptor list
 * contains the virtual disk name and exclusive-mode password.  Find all
 * connections involving the specified virtual disk and terminate them.
 * Possible errors are: no such disk; bad password; no such connections.
 */

/*ARGSUSED*/
cn_vdown(fhost, op, desc, nitem)

struct	sockaddr_in	*fhost;		/* host performing request */
	char	*op;			/* control operation name */
	struct	item	desc[];		/* item list */
	int	nitem;			/* number of items in list */
{
	struct	virtd	*vd;	/* virtual disk desc. for disk */
	struct	conn	*cn;	/* temp for connections */
	int	i;		/* temp for finding connections */
	char	*pw;		/* Pointer to exclusive mode password. */
	char	*nonce;		/* nonce string */
	char	*uname;		/* Authenticated user name. */
	boolean	conn_found;	/* Flag is TRUE if connection was found. */
	boolean vdown_ok;	/* Flag is true if user is authorized. */

	uname = NULL;
	nonce = desc[ITM_VDOWN_NONCE].it_val;
	if ((pw = desc[ITM_VDOWN_PASSW].it_val) == NULL)
		pw = "";

	if ((vd = vd_lookup(desc[ITM_VDOWN_NAME].it_val)) == NULL) {
		if (loglevel(LOG_CLIENT_ERROR))
			syslog(LOG_INFO,
			    "spindown virtual: virtual disk %s unknown (%s)",
			    desc[ITM_VDOWN_NAME].it_val,
			    inet_ntoa(fhost->sin_addr));
		ctl_failure(fhost, op, nonce, "virtual disk unknown");
		return;
	}

	/* Permission is granted if:
	 *	- the operations password has been given.
	 *	- the exclusive mode capability uses an access control list
	 *	  and an authenticated name is the member of that list.
	 *	- the exclusive mode capability uses a password and the
	 *	  given password matches.
	 * (The strange use of "vdown_ok" allows the authenticated 
	 * piece to be ifdef'd out.)
	 */
	vdown_ok = FALSE;
	if (authorized(pw,FALSE,FALSE))
		vdown_ok = TRUE;

#ifdef	KERBEROS
    {
	KTEXT	authent;

	if (!vdown_ok && is_aclname(vd->vd_expasswd)) {
		authent = (struct ktext *)desc[ITM_VDOWN_AUTH].it_val;
		if (authent == NULL) {
			if (loglevel(LOG_CLIENT_ERROR)) {
			    syslog(LOG_INFO,
				"spindown virtual:no authenticator for %s (%s)",
				desc[ITM_VDOWN_NAME].it_val,
				inet_ntoa(fhost->sin_addr));
			}
		        ctl_failure(fhost, op, nonce, "no authenticator");
		        return;
		}
		uname = auth_to_user(authent, fhost->sin_addr);
		if (uname == NULL) {
			if (loglevel(LOG_CLIENT_ERROR)) {
			    syslog(LOG_INFO,
				"spindown virtual: authentication failed for");
			    syslog(LOG_INFO,
				"spindown virtual: pack %s (%s)",
				desc[ITM_VDOWN_NAME].it_val,
				inet_ntoa(fhost->sin_addr));
			}
		        ctl_failure(fhost, op, nonce, "authentication failed");
			return;
		}
		if (!ismember(uname, vd->vd_expasswd, FALSE)) {
			if (loglevel(LOG_CLIENT_ERROR)) {
			    syslog(LOG_INFO,
				"spindown virtual: %s is not member of list %s",
				uname, vd->vd_expasswd);
			    syslog(LOG_INFO,
				"spindown virtual: failed on pack %s, (%s)",
				desc[ITM_VDOWN_NAME].it_val,
				inet_ntoa(fhost->sin_addr));
			}
		        ctl_failure(fhost, op, nonce, "not list member");
		        return;
		}
		vdown_ok = TRUE;
	}
    }
#endif	KERBEROS


	if (!vdown_ok && !is_aclname(vd->vd_expasswd)) {
		if (strncmp(vd->vd_expasswd, pw,
					sizeof(vd->vd_expasswd) - 1) != 0) {
			if (loglevel(LOG_CLIENT_ERROR)) {
			    syslog(LOG_INFO,
				"spindown virtual: bad password on %s by %s",
				desc[ITM_VDOWN_NAME].it_val,
				inet_ntoa(fhost->sin_addr));
			}
			ctl_failure(fhost, op, nonce, "unauthorized operation");
			return;
		}
		vdown_ok = TRUE;
	}

	/* At this point the only reason why it's not Ok is because
	 * the server does not support Kerberos but the data base has
	 * specified an access control list.  Log it.
	 */
	if (!vdown_ok) {
		if (loglevel(LOG_ERROR)) {
		    syslog(LOG_INFO,
			"spindown virtual: incompatible excap for %s, (%s)",
			desc[ITM_VDOWN_NAME].it_val,
			inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "unauthorized operation");
		return;
	}
/* End of authorization/authentication checking code. User checks out OK.
 */


	/* Remove all connections to this pack.
	 */
	conn_found = FALSE;
	for (i = 0; i < nconn; i++) {
		if ((cn = all_conns[i]) != NULL && cn->cn_virtd == vd) {
			if (loglevel(LOG_SPINS))
				syslog(LOG_INFO, "spindown virtual: %s (%s)",
					vd->vd_pack,
					inet_ntoa(fhost->sin_addr));
			cn_end(cn);
			conn_found = TRUE;
		}
	}

	/* Return success if at least one connection was found.
	 */
	if (conn_found == TRUE) {
		ctl_success(fhost, op, nonce);
		return;
	}

	/*
	 * Here if the connection was not found.
	 */
	if (loglevel(LOG_CLIENT_ERROR))
		syslog(LOG_INFO,
			"spindown virtual: no connections to pack %s for %s",
			desc[ITM_VDOWN_NAME].it_val,
			inet_ntoa(fhost->sin_addr));
	ctl_failure(fhost, op, nonce, "no connections to pack");
	return;
}


/* Process a "spindown_host" control message.  The item descriptor list
 * contains the host and possibly an authorization password (if the
 * request comes from the specified host no password is needed).  Find all
 * connections involving the specified host and terminate them.
 * Possible errors are: bad password; no such connections.
 */

/*ARGSUSED*/
cn_hdown(fhost, op, desc, nitem)
	struct	sockaddr_in	*fhost;	/* host performing request */
	char	*op;			/* control operation name */
	struct	item	desc[];		/* item list */
	int	nitem;			/* number of items in list */
{
	struct	conn	*cn;		/* temp for connections */
	int	i;			/* temp for finding connections */
	struct	in_addr	forhost;	/* host to be spun down */
	char	*pw;			/* password */
	char	*nonce;			/* nonce string */
	boolean	found;			/* was a connection found? */
	char	*uname;			/* Authenticated user name. */
	boolean	authent_ok;		/* Authenticator OK flag. */

	uname = NULL;
	authent_ok = FALSE;
	nonce = desc[ITM_HDOWN_NONCE].it_val;
	forhost.s_addr = inet_addr(desc[ITM_HDOWN_NAME].it_val);
	if ((pw = desc[ITM_HDOWN_PASSW].it_val) == NULL)
		pw = "";

	if (forhost.s_addr != fhost->sin_addr.s_addr &&
	    !authorized(pw,FALSE,FALSE)) {
#ifdef	KERBEROS
    {
		KTEXT	authent;
		
		authent = (struct ktext *)desc[ITM_HDOWN_AUTH].it_val;
		if (authent != NULL) {
			uname = auth_to_user(authent, fhost->sin_addr);
			if (uname != NULL) {
				if (priv_user(uname, FALSE, FALSE))
					authent_ok = TRUE;
			}
		}
    }
#endif	KERBEROS
		if (!authent_ok) {
			if (loglevel(LOG_CLIENT_ERROR))
			    syslog(LOG_INFO,
				"spindown host: bad password on %s by %s",
				desc[ITM_HDOWN_NAME].it_val,
				inet_ntoa(fhost->sin_addr));
			ctl_failure(fhost, op, nonce, "unauthorized operation");
			return;
		}
	}

	if (loglevel(LOG_SPINS)) {
		syslog(LOG_INFO, "spindown host: %s (%s)",
			desc[ITM_HDOWN_NAME].it_val,inet_ntoa(fhost->sin_addr));
	}

	for (i = 0, found = FALSE; i < nconn; i++) {
		if ((cn = all_conns[i]) != NULL
		  && cn->cn_fhost.s_addr == forhost.s_addr) {
			found = TRUE;
			cn_end(cn);
		}
	}

	if (found) {
		ctl_success(fhost, op, nonce);
	} else {
		if (loglevel(LOG_CLIENT_ERROR)) {
			syslog(LOG_INFO,
				"spindown host: no connections to %s for %s",
				desc[ITM_HDOWN_NAME].it_val,
				inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "no connections from host");
	}
}


/* Display all connections for a specified virtual disk.
 */

/*ARGSUSED*/
cn_vdisp(fhost, op, desc, nitem)
	struct	sockaddr_in	*fhost;	/* foreign host making request */
	char	*op;			/* operation */
	struct	item	desc[];		/* request descriptor */
	int	nitem;			/* number of items in desc */
{
	register struct	virtd	*vd;	/* virtual disk descriptor */
	register struct	conn	*cn;	/* connection being processed */
	register char	*p;		/* temporary pointer */
	register char	*nonce;		/* nonce string */
	struct in_addr	host;
	int	start, i, j, len, count;/* temp for loop */
	char	*pw;			/* password string */
	static	char	replbuf[CTLSIZE]; /* buffer for reply */
	static	char	buf1[MAXDISP],
			buf2[MAXDISP];
	char	*uname;			/* Authenticated user name. */
	boolean	authent_ok;		/* Authenticator OK flag. */

	vd = NULL;
	uname = NULL;
	authent_ok = FALSE;
	nonce = desc[ITM_VDISP_NONCE].it_val;

	/*
	 * Get the virtd pointer, if the disk is named and it's in use.
	 * No error is logged if the named virtual disk is not found.
	 */
	if (desc[ITM_VDISP_NAME].it_val != NULL
	&& ((vd = vd_lookup(desc[ITM_VDISP_NAME].it_val)) == NULL
	||  vd->vd_mode == RVDMNONE)) {
		ctl_failure(fhost, op, nonce, "no such connections");
		return;
	}

	/*
	 * get hostname address, if given
	 */
	host.s_addr = 0;
	if (desc[ITM_VDISP_HOST].it_val != NULL
	&& (host.s_addr = inet_addr(desc[ITM_VDISP_HOST].it_val)) == -1) {
		if (loglevel(LOG_CLIENT_ERROR)) {
		    syslog(LOG_INFO,
			"display virtual: malformed host address %s by %s",
			desc[ITM_VDISP_HOST].it_val,
			inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "malformed address");
		return;
	}

	/*
	 * get start into connection table
	 */
	if (desc[ITM_VDISP_START].it_val == NULL
	|| sscanf(desc[ITM_VDISP_START].it_val, "%D", &start) != 1)
		start = 1;

	/*
	 * get password, if given
	 */
	if ((pw = desc[ITM_VDISP_PASSW].it_val) == NULL)
		pw = "";

	/*
	 * check privacy of request:
	 *	one host may not request a list of the spinups of another
	 *	host without the operations or administration passwords.
	 */
	if (host.s_addr && host.s_addr != fhost->sin_addr.s_addr) {
#ifdef	KERBEROS
	    {
		KTEXT	authent;

		authent = (struct ktext *)desc[ITM_VDISP_AUTH].it_val;
		if (authent != NULL) {
			uname = auth_to_user(authent, fhost->sin_addr);
			if ((uname != NULL) && priv_user(uname, TRUE, FALSE))
				authent_ok = TRUE;
		}
	    }
#endif	KERBEROS
		if (!authent_ok) {
			if (!authorized(pw,TRUE,FALSE)) {
				if (loglevel(LOG_CLIENT_ERROR)) {
				    syslog(LOG_INFO,
					"display virtual: bad password on pack %s by %s",
					desc[ITM_VDISP_NAME].it_val,
					inet_ntoa(fhost->sin_addr));
				}
				ctl_failure(fhost, op, nonce, 
					"unauthorized operation");
				return;
			}
		}
	}

	/*
	 * header of successful response packet
	 */
	p = replbuf;
	(void)sprintf(p, "success=%s\n", op);
	p += strlen(p);
	if (nonce != (char *)NULL) {
		(void)sprintf(p, "nonce=%s\n", nonce);
		p += strlen(p);
	}
	(void)sprintf(p, "time=%U\nconnections=", now + LONGTIME);
	p  += strlen(p);
	len = strlen(replbuf);

	/*
	 * scan connection table for qualified connections
	 */
	for (count = i = j = 0; i < nconn; i++) {
		if ((cn = all_conns[i]) != NULL) {
			/*
			 * if not this vd, or not this host, or
			 * beyond this offset, skip
			 */
			if ((vd && vd != cn->cn_virtd)
			|| (host.s_addr && host.s_addr != cn->cn_fhost.s_addr)
			|| ++count < start)
				continue;
			/*
			 * concatenate it onto the buffer
			 */
			csprintf(buf1, "\npack=%s host=%s drive=%D mode=%D",
			    cn->cn_virtd->vd_pack, inet_ntoa(cn->cn_fhost),
			    cn->cn_drive, cn->cn_virtd->vd_mode);
			csprintf(buf2, "%s", buf1);
			len = strlen(buf2);
			/*
			 * if it won't leave enough room for `more=true',
			 * then don't append it
			 */
			if (p - replbuf + len + Length(MORE) + Length(NUMBER)
			  > CTLSIZE)
				continue;
			(void)strcpy(p, buf2);
			p += len;
			++j;
		}
	}
	/*
	 * no matches.  failure.
	 */
	if (count == 0) {
		if (loglevel(LOG_CLIENT_ERROR))
			syslog(LOG_INFO,
				"display virtual: no connection to pack %s for %s",
				desc[ITM_VDISP_NAME].it_val,
				inet_ntoa(fhost->sin_addr));
		ctl_failure(fhost, op, nonce, "no such connections");
		return;
	}
	/*
	 * add number=%d (matches)
	 */
	csprintf(buf1, NUMBER, count);
	(void)strcpy(p, buf1);
	p += (len = strlen(p));
	/*
	 * add `more=true' message if applicable
	 */
	if (start + j <= count) {	/* almost hit a fence-post */
		(void)strcpy(p, MORE);
		p += (len = Length(MORE));
	}
/**	fwrite(replbuf, sizeof(char), p - replbuf, stderr);	*/
	ctl_send(fhost, replbuf, p - replbuf);
}


/* Display all connections for a specified active physical disk.
 */

/*ARGSUSED*/
cn_adisp(fhost, op, desc, nitem)

struct	sockaddr_in	*fhost;		/* foreign host making request */
char	*op;				/* operation */
struct	item	desc[];			/* request descriptor */
int	nitem;				/* number of items in desc */
{
	register struct physd	*pd, *td;/* physical disk descriptor */
	register char	*p;		/* temporary pointer */
	int	start, j, len, count;	/* temp for loop */
	char	*file, *s;		/* password string */
	register char	*nonce;		/* nonce string */
	static	char	replbuf[CTLSIZE]; /* buffer for reply */
	static	char	buf[MAXDISP];

	/*
	 * get physd pointer, is disk is named
	 */
	pd = NULL;
	nonce = desc[ITM_ADISP_NONCE].it_val;
	if ((file = desc[ITM_ADISP_FILE].it_val) != NULL
	&&  (pd = pd_lookup(file)) == NULL) {
		if (loglevel(LOG_CLIENT_ERROR))
			syslog(LOG_INFO,
				"display active: physical disk %s unknown (%s)",
				desc[ITM_ADISP_FILE].it_val,
				inet_ntoa(fhost->sin_addr));
		ctl_failure(fhost, op, nonce, "physical disk unknown");
		return;
	}

	/*
	 * get start into connection table
	 */
	if (desc[ITM_ADISP_START].it_val == NULL
	|| sscanf(desc[ITM_ADISP_START].it_val, "%D", &start) != 1)
		start = 1;

	/*
	 * header of successful response packet
	 */
	p = replbuf;
	(void)sprintf(p, "success=%s\n", op);
	p += strlen(p);
	if (nonce != (char *)NULL) {
		(void)sprintf(p, "nonce=%s\n", nonce);
		p += strlen(p);
	}
	(void)sprintf(p, "time=%U\nactivity=", now + LONGTIME);
	p  += strlen(p);
	len = strlen(replbuf);

	if (! pd) {
		td = (struct physd *) &(all_physds.pq_forw);
		pd = q_head(td, struct physd *);
	}
	else
		td = NULL;

	/*
	 * The outer loop runs through the queue of physical disk descriptors.
	 * If td = NULL then there is only one pass of the outer loop.
	 */
	count = j = 0;
	while(pd != td) {
		struct virtd	*vd, *sd;

		sd = (struct virtd *) &(pd->vd_forw);

		for (vd = q_head(sd, struct virtd *); vd != sd; 
				vd = q_head(vd, struct virtd *)) {
			/*
			 * disk is live
			 */
			if (vd->vd_links) {
				if (++count < start)
					continue;
				s = cn_vsum(vd);
				csprintf(buf, "%s", s);
				len = strlen(buf);
				/*
				 * if it won't leave enough room for
				 * `more=true' then don't append it.
				 */
				if (p - replbuf + len + Length(MORE)
				  + Length(NUMBER) > CTLSIZE)
					continue;
				(void)strcpy(p, buf);
				p += len;
				++j;
			}
		}
		/*
		 * Break out of the outer loop if the filename option
		 * was used.  (Here, td is being used as a flag.)
		 */
		if (! td)	/* ... this is such a kludge */
			break;
		pd = q_head(pd, struct physd *);
	}
	/*
	 * no matches.  failure.
	 */
	if (count == 0) {
		if (loglevel(LOG_CLIENT_ERROR))
			syslog(LOG_INFO,
				"display active: no connections for device %s (%s)",
				desc[ITM_ADISP_FILE].it_val,
				inet_ntoa(fhost->sin_addr));
		ctl_failure(fhost, op, nonce, "no such connections");
		return;
	}
	/*
	 * add number=%D (count of matches)
	 */
	csprintf(buf, NUMBER, count);
	(void)strcpy(p, buf);
	p += (len = strlen(p));
	/*
	 * add `more=true' message if applicable
	 */
	if (start + j <= count) {	/* almost hit another fence-post */
		(void)strcpy(p, MORE);
		p += (len = Length(MORE));
	}
/**	fwrite(replbuf, sizeof(char), p - replbuf, stderr);	*/
	ctl_send(fhost, replbuf, p - replbuf);
}


/* Get summary usage information for a specified virtd
 */

char *
cn_vsum(vd)

struct virtd	*vd;

{
static char	buf[1000];
	

	csprintf(buf,
		"\npartition=%s pack=%s uid=%D mode=%D connections=%d idle=%d",
		vd->pd_forw->pd_file, vd->vd_pack, vd->vd_uid,
		(int) vd->vd_mode, vd->vd_links, now - vd->vd_accessed);
	return(buf);
}


/* Set modes for future spinups
 */

/*ARGSUSED*/
cn_modes(fhost, op, desc, nitem)
	struct	sockaddr_in	*fhost;	/* foreign host making request */
	char	*op;			/* operation */
	struct	item	desc[];		/* request descriptor */
	int	nitem;			/* number of items in desc */
{
	register struct virtd	*vd = NULL;
	int	oldmode, newmode;
	char	*pw;
	char	*nonce = desc[ITM_SMOD_NONCE].it_val; /* nonce string */
	static char	replbuf[80];
	char	*uname;			/* Authenticated user name. */
	boolean	authent_ok;		/* Authenticator OK flag. */

	if (sscanf(desc[ITM_SMOD_MODES].it_val, "%D", &newmode) != 1) {
		if (loglevel(LOG_CLIENT_ERROR))
			syslog(LOG_INFO,"allow spinups: illegal mode %D by %s",
				desc[ITM_SMOD_MODES].it_val,
				inet_ntoa(fhost->sin_addr));
		ctl_failure(fhost, op, nonce, "illegal mode");
		return;
	}

	uname = NULL;
	authent_ok = FALSE;
	if ((pw = desc[ITM_SMOD_PASSW].it_val) == NULL)
		pw = "";

#ifdef	KERBEROS
    { 
	KTEXT	authent;

	authent = (struct ktext *)desc[ITM_SMOD_AUTH].it_val;
	if (authent != NULL)
		uname = auth_to_user(authent, fhost->sin_addr);
    }
#endif	KERBEROS

	if (desc[ITM_SMOD_NAME].it_val != NULL) {
		if ((vd = vd_lookup(desc[ITM_SMOD_NAME].it_val)) == NULL) {
			if (loglevel(LOG_CLIENT_ERROR))
			    syslog(LOG_INFO,
				"allow spinups: unknown virtual disk %s, (%s)",
				desc[ITM_SMOD_NAME].it_val,
				inet_ntoa(fhost->sin_addr));
			ctl_failure(fhost, op, nonce, "virtual disk unknown");
			return;
		}

		/* If the password is an access list file name then see if
		 * the authenticated user is a member of the list or a member
		 * of the operations list.  Otherwise, check the  password.
		 */
		if (is_aclname(vd->vd_expasswd)) {
			if (uname != NULL && (priv_user(uname, FALSE, FALSE) ||
				     ismember(uname, vd->vd_expasswd, FALSE)))
				authent_ok = TRUE;
		} else {
			if (strcmp(vd->vd_expasswd, pw) == 0)
				authent_ok = TRUE;
		}

		/* Return an error if this is unauthorized.
		 */
		if (!authent_ok) {
			if (loglevel(LOG_CLIENT_ERROR))
			    syslog(LOG_INFO,
				"allow spinups: unauthorized operation by %s",
				inet_ntoa(fhost->sin_addr));
			ctl_failure(fhost, op, nonce, "unauthorized operation");
			return;
		}

		oldmode = vd->vd_modes;
		vd->vd_modes = newmode;
	} else {
		if ((uname != NULL) && priv_user(uname, FALSE, FALSE))
			authent_ok = TRUE;

		if (!authent_ok && !authorized(pw, FALSE, FALSE)) {
			if (loglevel(LOG_CLIENT_ERROR))
			    syslog(LOG_INFO,
				"allow spinups: unauthorized operation by %s",
				inet_ntoa(fhost->sin_addr));
			ctl_failure(fhost, op, nonce, "unauthorized operation");
			return;
		}
		oldmode = gmodes;
		gmodes = newmode;
		if (oldmode == 0)
			gmodes_orig = gmodes;
	}


	/* The operation is authorized and the modes have been changed.
	 * Create the successful reply buffer, with or without the nonce
	 * field, and send it.
	 */
	if (nonce != (char *)NULL) {
		csprintf(replbuf, "success=%s\n", op);
		pw = replbuf + strlen(replbuf);
		(void)sprintf(pw, "nonce=%s\n", nonce);
		pw += strlen(pw);
		csprintf(pw, "oldmode=%D\n", oldmode);
	} else
		csprintf(replbuf, "success=%s\noldmode=%D\n", op, oldmode);

	ctl_send(fhost, replbuf, strlen(replbuf));
}


/* Show connection statistics in log.
 */

cn_show()

{
	syslog(LOG_INFO, "Connection statistics:");
	syslog(LOG_INFO, " %8d    connection table overflows",
	    conn_stats.cn_ovflo);
	syslog(LOG_INFO, " %8d    duplicate opens", conn_stats.cn_dupl);
	syslog(LOG_INFO, " %8d    incorrect indices", conn_stats.cn_badidx);
}
