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

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


#ifndef lint
static char rcsid_virtd_c[] = "$Header:virtd.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"

/* This file contains all the general-purpose routines for managing
 * virtual disk descriptors.  This includes the routines to read in
 * the virtual disk descriptor data base, look up existing virtual disks
 * on a spinup, manage the virtual disk reference counts, and handle
 * creating, modifying, and deleting virtual disks on the fly.
 */

#include	<sys/types.h>
#include	<stdio.h>
#include	<errno.h>
#include	<netinet/in.h>
#include	<machineio/vdconst.h>
#ifdef	KERBEROS
#include	<krb.h>
#endif	KERBEROS

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

#undef	q_head
#define	q_head(p,t)	((t)((struct qelem *)p)->q_forw)

extern struct physd_q	all_physds;

#ifdef	__UNDEF__
/* Read in the virtual disk database from the specified file descriptor.
 * Ndisks is the number of virtual disk descriptor records to read.
 * Each record is read into an allocated virtual disk descriptor, the
 * remaining fields are initialized, and the descriptor is chained into
 * the list of all virtual disks. The phys argument is a pointer to the
 * physical disk descriptor of the physical disk containing this virtual disk.
 * The file described by vdfd is assumed to be open and positioned at the
 * start of the virtual disk descriptors.
 * Returns TRUE if the file is successfully read, or FALSE otherwise.
 */

boolean
vd_input(vdfd, phys, ndisks)

register FILE	*vdfd;			/* fd for database file */
struct	physd	*phys;			/* containing physical disk */
register int	ndisks;			/* number of vd's to read */
{
	register int	i;		/* temp counter */
	register struct	virtd	*vd;	/* newly created virtual disk */

	for (i = 0;i < ndisks; i++) {	/* read ndisks records */
		vd = q_alloc(struct virtd, VIRTD_TYPE);
		q_init(vd);
/*!!*/		if (fread((char *)vd->vd_desc[0], PACKINFSIZE, 1, vdfd) !=
		    1) {
			if (ferror(vdfd))
				syslog(LOG_ERR, "vd_input: %m");
			else
				syslog(LOG_ERR, 
				       "vd_input: unexpected end-of-file");
			clearerr(vdfd);		/* so caller isn't confused */
			obj_free((char *)vd);
			return(FALSE);
		}

		vd->vd_mode = RVDMNONE;		/* unopened */
		vd->vd_mode_orig = RVDMNONE;	/* unopened */
		vd->vd_links = 0;		/* no owners */
		vd->pd_forw = phys;		/* point to phys disk */

/*		ins_q_tail(vd, &all_virtds);	/* chain it in */
	}
	return(TRUE);
}
#endif	__UNDEF__

/* Look up the virtual disk with the specified name and return a pointer to
 * its virtual disk descriptor.  Returns NULL if no such disk exists.
 */

struct virtd *
vd_lookup(vdname)
register char	*vdname;		/* name to look up */

{
	struct physd	*td, *pd;

	td = (struct physd *) &(all_physds.pq_forw);
	pd = q_head(td, struct physd *);

	while (pd != td) {
		struct virtd	*td, *vd;

		td = (struct virtd *) &(pd->vd_forw);
		vd = q_head(td, struct virtd *);

		while (vd != td) {
			if (! strcmp(vdname, vd->vd_pack))
				return(vd);
			vd = q_head(vd, struct virtd *);
		}
		pd = q_head(pd, struct physd *);
	}
	return(NULL);			/* no such disk */
}

/* Look up the virtual disk with the specified uid and return a pointer to
 * its virtual disk descriptor.  Returns NULL if no such disk exists.
 */

struct virtd *
vd_look_uid(vduid)
register u_long	vduid;		/* uid to look up */

{
	struct physd	*td, *pd;

	td = (struct physd *) &(all_physds.pq_forw);
	pd = q_head(td, struct physd *);

	while (pd != td) {
		struct virtd	*td, *vd;

		td = (struct virtd *) &(pd->vd_forw);
		vd = q_head(td, struct virtd *);

		while (vd != td) {
			if (vduid == vd->vd_uid)
				return(vd);
			vd = q_head(vd, struct virtd *);
		}
		pd = q_head(pd, struct physd *);
	}
	return(NULL);			/* no such disk */
}

/* Increment the spinup reference count for the specified virtual disk,
 * spun up in the specified mode.  This sets the current open mode to
 * mode if it's not already set.
 */

vd_increment(vd, mode)

register struct	virtd	*vd;		/* disk to increment */
register u_char	mode;			/* spinup mode */
{
	if (vd->vd_links != 0 && vd->vd_mode != mode)
		bughalt("vd_increment: mode mismatch");
	vd->vd_mode = mode;
	vd->vd_links++;
	vd->vd_accessed = now;
}


/* Delete the specified virtual disk - it must not be spun up.
 */

vd_delete(vd)

register struct	virtd	*vd;		/* disk to delete */
{
	if (vd->vd_links != 0 || vd->vd_mode != RVDMNONE) /* sanity */
		bughalt("vd_delete: inconsistent vd mode & ref count");

	pd_vddel(vd->pd_forw, vd);
	obj_free((char *) vd);	/* punt it */
}


/* Decrement the specified disk's reference count.  If the count goes to
 * zero, clear its open mode.
 */

vd_decrement(vd)

register struct	virtd	*vd;		/* disk to decrement */
{
	if (--(vd->vd_links) < 0)	/* sanity check */
		bughalt("vd_decrement: negative reference count");
	else if (vd->vd_links == 0)  /* last spindown */
		vd->vd_mode = RVDMNONE;	/* show not open */
}


/* Add a new virtual disk, described by the item list.
 */

/*ARGSUSED*/
vd_add(fhost, op, desc, nitem)
	struct	sockaddr_in	*fhost;	/* foreign host making request */
	char	*op;			/* operation name */
	struct	item	desc[];		/* items describing disk */
	int	nitem;			/* size of item list */
{
	register struct	virtd	*vd;	/* virt disk descriptor */
	register struct	physd	*pd;	/* phys disk descriptor */
	int	modes;			/* allowable spinup modes */
	int	offset;			/* offset on physical disk */
	int	created;		/* create date */
	int	modified;		/* modification date */
	u_long	blocks;			/* size in blocks */
	u_long	uid;			/* pack unique ID */
	char	*pw;			/* password string */
	char	*uname;			/* Authenticated user name. */
	char	*nonce;			/* nonce string */
	boolean	authent_ok;		/* Authenticator OK flag. */

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

	/* If an authenticator is present, is the user on
	 * the operations or administration list?
	 */
#ifdef	KERBEROS
    {
	KTEXT	authent;

	if ((authent = (struct ktext *)desc[ITM_VADD_AUTH].it_val) != NULL) {
		if ((uname = auth_to_user(authent, fhost->sin_addr)) != NULL) {
			if (priv_user(uname, FALSE, FALSE))
				authent_ok = TRUE;
		}
	}
    }
#endif	KERBEROS

	/* If no authenticator, check for the operations or admin password.
	 */
	if (!authent_ok) {
		if (!authorized(pw, TRUE, FALSE)) {
			if (loglevel(LOG_CLIENT_ERROR))
			    syslog(LOG_INFO,
				"add virtual: bad password on %s by %s",
				desc[ITM_VADD_NAME].it_val, 
				inet_ntoa(fhost->sin_addr));
			ctl_failure(fhost, op, nonce, "unauthorized operation");
			return;
		}
	}


	if (sscanf(desc[ITM_VADD_MODES].it_val, "%D", &modes) != 1 ||
	    sscanf(desc[ITM_VADD_OFF].it_val, "%D", &offset) != 1 ||
	    sscanf(desc[ITM_VADD_BLOCKS].it_val, "%D", &blocks) != 1 ||
	    sscanf(desc[ITM_VADD_UID].it_val, "%D", &uid) != 1) {
		if (loglevel(LOG_CLIENT_ERROR)) {
		    syslog(LOG_INFO,
			"add virtual: invalid numeric field for %s, (%s)",
			desc[ITM_VADD_NAME].it_val,
			inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "invalid numeric field");
		return;
	}

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

	if ((vd = vd_look_uid(uid)) != NULL) {
		if (loglevel(LOG_CLIENT_ERROR))
			syslog(LOG_INFO, "add virtual: duplicate uid %d, (%s)",
		       uid, inet_ntoa(fhost->sin_addr));
		ctl_failure(fhost, op, nonce, "duplicate uid");
		return;
	}

	if ((pd = pd_lookup(desc[ITM_VADD_PHYS].it_val)) == NULL) {
		if (loglevel(LOG_CLIENT_ERROR)) {
		    syslog(LOG_INFO,
			"add virtual: no such physical disk %s, (%s)",
			desc[ITM_VADD_PHYS].it_val,
			inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "no such physical disk");
		return;
	}

	if ((offset < 0) || ((long)blocks <= 0) || 
	    (blocks + offset) > pd->pd_blocks)      {
		if (loglevel(LOG_CLIENT_ERROR)) {
		    syslog(LOG_INFO,
			"add virtual: invalid offset or size for %s, (%s)",
			desc[ITM_VADD_NAME].it_val,
			inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "invalid offset or size");
		return;
	}

	if (sscanf(desc[ITM_VADD_CREATED].it_val, "%D", &created) != 1)
		(void)time(&created);
	if (sscanf(desc[ITM_VADD_MODIFIED].it_val, "%D", &modified) != 1)
		(void)time(&modified);

	vd = q_alloc(struct virtd, VIRTD_TYPE);
	q_init(vd);
	(void)strncpy(vd->vd_desc, desc[ITM_VADD_OWN].it_val,
		sizeof(vd->vd_desc) - 1);
	(void)strncpy(vd->vd_pack, desc[ITM_VADD_NAME].it_val,
		sizeof(vd->vd_pack) - 1);
	(void)strncpy(vd->vd_ropasswd, desc[ITM_VADD_ROCAP].it_val,
		sizeof(vd->vd_ropasswd) - 1);
	(void)strncpy(vd->vd_expasswd, desc[ITM_VADD_EXCAP].it_val,
		sizeof(vd->vd_expasswd) - 1);
	(void)strncpy(vd->vd_shpasswd, desc[ITM_VADD_SHCAP].it_val,
		sizeof(vd->vd_shpasswd) - 1);
	vd->vd_uid = uid;
	vd->vd_modes = modes;
	vd->vd_modes_orig = modes;
	vd->vd_offset = offset;
	vd->vd_blocks = blocks;
	vd->vd_created = created;
	vd->vd_modified = modified;
	vd->pd_back = vd->pd_forw = pd;
/*	vd->vd_flags = 0;	*/
	vd->vd_mode = RVDMNONE;
	vd->vd_links = 0;

	if (desc[ITM_VADD_OWNH].it_val != NULL &&
	    strcmp(desc[ITM_VADD_OWNH].it_val, "") != 0)
		vd->vd_host.s_addr = inet_addr(desc[ITM_VADD_OWNH].it_val);
	else
		vd->vd_host.s_addr = 0;

	pd_vdadd(pd, vd);


	if (loglevel(LOG_SPINS)) {
		if (uname != NULL) {
		    syslog(LOG_INFO,
			"add virtual: virtual disk %s on device %s added by %s from %s",
			desc[ITM_VADD_NAME].it_val,
			desc[ITM_VADD_PHYS].it_val,
			uname, inet_ntoa(fhost->sin_addr));
		} else {
		    syslog(LOG_INFO,
			"add virtual: virtual disk %s on device %s added from %s",
			desc[ITM_VADD_NAME].it_val,
			desc[ITM_VADD_PHYS].it_val,
			inet_ntoa(fhost->sin_addr));
		}
	}

	ctl_success(fhost, op, nonce);
}


/* Delete the virtual disk specified by the descriptor.
 */

/*ARGSUSED*/
vd_del(fhost, op, desc, nitem)
	struct	sockaddr_in	*fhost;		/* requesting foreign host */
	char	*op;				/* operation name */
	register struct	item	desc[];		/* operation descriptor */
	int	nitem;				/* number of items in desc */
{
	struct virtd	*vd1 = 0;
	struct virtd	*vd2 = 0;
	u_long	uid;
	char	*pw;			/* password string */
	char	*uname;			/* Authenticated user name. */
	char	*nonce;			/* nonce string */
	boolean	authent_ok;		/* Authenticator OK flag. */

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

	/* If an authenticator is present, is the user on the
	 * operations or the administration list?
	 */
#ifdef	KERBEROS
    {
	KTEXT	authent;

	if ((authent = (struct ktext *)desc[ITM_VDEL_AUTH].it_val) != NULL) {
		if ((uname = auth_to_user(authent, fhost->sin_addr)) != NULL) {
			if (priv_user(uname, TRUE, FALSE))
				authent_ok = TRUE;
		}
	}
    }
#endif	KERBEROS

	/* If no authenticator, check for the operations or admin password.
	 */
	if (!authent_ok) {
		if (!authorized(pw, TRUE, FALSE)) {
			if (loglevel(LOG_CLIENT_ERROR))
			    syslog(LOG_INFO,
				"delete virtual: bad password on %s by %s",
				desc[ITM_VDEL_NAME].it_val, 
				inet_ntoa(fhost->sin_addr));
			ctl_failure(fhost, op, nonce, "unauthorized operation");
			return;
		}
	}

	
	if (desc[ITM_VDEL_NAME].it_val != NULL &&
		(vd1 = vd_lookup(desc[ITM_VDEL_NAME].it_val)) == NULL) {

		if (loglevel(LOG_CLIENT_ERROR)) {
		    syslog(LOG_INFO,
			"delete virtual: no such virtual disk name = %s, (%s)",
			desc[ITM_VDEL_NAME].it_val,
			inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "no such virtual disk name");
		return;
	}

	if ( desc[ITM_VDEL_UID].it_val != NULL &&
		((sscanf(desc[ITM_VDEL_UID].it_val, "%D", &uid) != 1) ||
		(vd2 = vd_look_uid(uid)) == NULL)) {

		if (loglevel(LOG_CLIENT_ERROR)) {
		    syslog(LOG_INFO,
			"delete virtual: no such virtual disk uid = %s, (%s)",
				desc[ITM_VDEL_UID].it_val,
				inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "no such virtual disk uid");
		return;
	}
	if (vd2) {
		if (vd1 && vd1 != vd2) {
			if (loglevel(LOG_CLIENT_ERROR)) {
			    syslog(LOG_INFO,
				"delete virtual: virtual disk name = %s does not match uid = %s, (%s)",
				desc[ITM_VDEL_NAME].it_val,
				desc[ITM_VDEL_UID].it_val,
				inet_ntoa(fhost->sin_addr));
			}
			ctl_failure(fhost, op, nonce, "no such virtual disk");
			return;
		}
		else
			vd1 = vd2;
	}
	else if (vd1 == NULL) {
		if (loglevel(LOG_CLIENT_ERROR)) {
			syslog(LOG_INFO,
				"delete virtual: need name or uid, (%s)",
				inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "need name or uid");
		return;
	}
			
	if (vd1->vd_links != 0) {	/* currently spun up */
		if (loglevel(LOG_CLIENT_ERROR))
			syslog(LOG_INFO,
				"delete virtual: %*s still spun up, (%s)",
				sizeof(vd1->vd_pack), vd1->vd_pack,
				inet_ntoa(fhost->sin_addr));
		ctl_failure(fhost, op, nonce, "virtual disk still spun up");
		return;
	}

	if (loglevel(LOG_SPINS)) {
		if (uname != NULL) {
		    syslog(LOG_INFO,
			"delete virtual: virtual disk %s deleted by %s (%s)",
			vd1->vd_pack, uname, inet_ntoa(fhost->sin_addr));
		} else {
		    syslog(LOG_INFO,
			"delete virtual: virtual disk %s deleted from %s",
			vd1->vd_pack, inet_ntoa(fhost->sin_addr));
		}
	}


	if (loglevel(LOG_SPINS)) {
	}

	vd_delete(vd1);
	ctl_success(fhost, op, nonce);
}


/* Modify an existing virtual disk, as described by the item list.
 */

/*ARGSUSED*/
vd_mod(fhost, op, desc, nitem)
	struct	sockaddr_in	*fhost;	/* foreign host making request */
	char	*op;			/* operation name */
	struct	item	desc[];		/* items describing disk */
	int	nitem;			/* size of item list */
{
	register struct	virtd	*vd;	/* virt disk descriptor */
	int	modes;			/* allowable spinup modes */
	u_long	blocks;			/* size in blocks */
	u_long	uid;			/* pack unique ID */
	char	*pw;			/* password string */
	char	*uname;			/* Authenticated user name. */
	char	*nonce;			/* nonce string */
	boolean	authent_ok;		/* Authenticator OK flag. */

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

	/* If an authenticator is present, is the user 
	 * on the operations or administration lists?
	 */
#ifdef	KERBEROS
    {
	KTEXT	authent;

	if ((authent = (struct ktext *)desc[ITM_VMOD_AUTH].it_val) != NULL) {
		if ((uname = auth_to_user(authent, fhost->sin_addr)) != NULL) {
			if (priv_user(uname, FALSE, FALSE))
				authent_ok = TRUE;
		}
	}
    }
#endif	KERBEROS

	/* If no authenticator, check for the operations password.
	 */
	if (!authent_ok) {
		if (!authorized(pw, TRUE, FALSE)) {
			if (loglevel(LOG_CLIENT_ERROR))
			    syslog(LOG_INFO,
				"modify virtual: bad password on %s by %s",
				desc[ITM_VMOD_NAME].it_val, 
				inet_ntoa(fhost->sin_addr));
			ctl_failure(fhost, op, nonce, "unauthorized operation");
			return;
		}
	}


	/* If a uid was given, look up the virtual drive by uid
	 * and make sure no other pack has a duplicate name.
	 * (It's ok for the same pack to have the same name.)
	 */
	if (desc[ITM_VMOD_UID].it_val != NULL) {
		if (sscanf(desc[ITM_VMOD_UID].it_val, "%D", &uid) != 1 ||
			(vd = vd_look_uid(uid)) == NULL) {

			if (loglevel(LOG_CLIENT_ERROR)) {
			  syslog(LOG_INFO,
			  "modify virtual: no such virtual disk uid = %s, (%s)",
				desc[ITM_VMOD_UID].it_val,
				inet_ntoa(fhost->sin_addr));
			}
			ctl_failure(fhost, op, nonce,
				"no such virtual disk uid");
			return;
		}
		if (vd_lookup(desc[ITM_VMOD_NAME].it_val) != NULL &&
		    strncmp(vd->vd_pack, desc[ITM_VMOD_NAME].it_val, 
						sizeof(vd->vd_pack)) != 0) {
			if (loglevel(LOG_CLIENT_ERROR)) {
			    syslog(LOG_INFO,
				"modify virtual: nonunique disk name %s, (%s)",
				   desc[ITM_VMOD_NAME].it_val,
				   inet_ntoa(fhost->sin_addr));
			}
			ctl_failure(fhost, op, nonce, "nonunique disk name");
			return;
		}
	} else if ((vd = vd_lookup(desc[ITM_VMOD_NAME].it_val)) == NULL) {
		if (loglevel(LOG_CLIENT_ERROR)) {
		    syslog(LOG_INFO,
			"modify virtual: no such virtual disk name %s, (%s)",
			desc[ITM_VMOD_NAME].it_val,
			inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "no such virtual disk name");
		return;
	}

	if (desc[ITM_VMOD_OWN].it_val != NULL)
		(void)strncpy(vd->vd_desc, desc[ITM_VMOD_OWN].it_val, NDESC);
	if (desc[ITM_VMOD_ROCAP].it_val != NULL)
		(void)strncpy(vd->vd_ropasswd, desc[ITM_VMOD_ROCAP].it_val,
		  sizeof(vd->vd_ropasswd) - 1);
	if (desc[ITM_VMOD_EXCAP].it_val != NULL)
		(void)strncpy(vd->vd_expasswd, desc[ITM_VMOD_EXCAP].it_val,
		  sizeof(vd->vd_expasswd) - 1);
	if (desc[ITM_VMOD_SHCAP].it_val != NULL)
		(void)strncpy(vd->vd_shpasswd, desc[ITM_VMOD_SHCAP].it_val,
		  sizeof(vd->vd_shpasswd) - 1);
	if (desc[ITM_VMOD_MODES].it_val != NULL) {
		if (sscanf(desc[ITM_VMOD_MODES].it_val, "%D", &modes) == 1) {
			vd->vd_modes = modes;
			vd->vd_modes_orig = modes;
		} else {
			if (loglevel(LOG_CLIENT_ERROR))
				syslog(LOG_INFO,"modify virtual: invalid modes %D, (%s)",
			       desc[ITM_VMOD_MODES].it_val, inet_ntoa(fhost->sin_addr));
			ctl_failure(fhost, op, nonce, "invalid modes");
			return;
		}
	}
	/*
	 * not a good idea here
	 */
	if (desc[ITM_VMOD_BLOCKS].it_val != NULL) {
		if ((sscanf(desc[ITM_VMOD_BLOCKS].it_val, "%D", &blocks) == 1) &&
		    ((long)blocks > 0) && ((long)blocks <= vd->vd_blocks)) {
			vd->vd_blocks = blocks;
		} else {
			if (loglevel(LOG_CLIENT_ERROR))
				syslog(LOG_INFO,"modify virtual: invalid block count %D, (%s)",
			       blocks, inet_ntoa(fhost->sin_addr));
			ctl_failure(fhost, op, nonce, "invalid block count");
			return;
		}
	}
	if (desc[ITM_VMOD_OWNH].it_val != NULL) {
		vd->vd_host.s_addr = inet_addr(desc[ITM_VMOD_OWNH].it_val);
	}

	if (loglevel(LOG_SPINS)) {
		if (uname != NULL) {
		    syslog(LOG_INFO,
		       "modify virtual: virtual disk %s modified by %s from %s",
			desc[ITM_VMOD_NAME].it_val,
			uname, inet_ntoa(fhost->sin_addr));
		} else {
		    syslog(LOG_INFO,
		       "modify virtual: virtual disk %s modified from %s",
			desc[ITM_VMOD_NAME].it_val,
			inet_ntoa(fhost->sin_addr));
		}
	}

	ctl_success(fhost, op, nonce);
}


/* Exchange the names of the two virtual disks named in the specified item
 * list.
 */

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

struct	sockaddr_in	*fhost;		/* foreign host making request */
	char	*op;			/* operation name */
	struct	item	desc[];		/* operation descriptor */
	int	nitem;			/* number of items in desc */
{
	struct	virtd	*vd1;		/* virtual disk 1 */
	struct	virtd	*vd2;		/* virtual disk 2 */
	u_long	uid1;			/* unique id 1 */
	u_long	uid2;			/* unique id 2 */
	char	*pw1;			/* password 1 */
	char	*pw2;			/* password 2 */
	char	*nonce;			/* nonce string */
	char	*uname;			/* Authenticated user name. */
	boolean	authent_ok;		/* Authenticator OK flag. */

	authent_ok = FALSE;
	nonce = desc[ITM_VDEX_NONCE].it_val;
	if ((pw1 = desc[ITM_VDEX_PASSW1].it_val) == NULL)
		pw1 = "";
	if ((pw2 = desc[ITM_VDEX_PASSW2].it_val) == NULL)
		pw2 = "";


	/* Make sure uid1 is a number and that there is a virtual disk for it.
	 */
	if (sscanf(desc[ITM_VDEX_UID1].it_val, "%D", &uid1) != 1 ||
					(vd1 = vd_look_uid(uid1)) == NULL) {
		if (loglevel(LOG_CLIENT_ERROR)) {
		    syslog(LOG_INFO,
			"exchange names: no such virtual disk uid = %s, (%s)",
			desc[ITM_VDEX_UID1].it_val,
			inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "no such virtual disk uid");
		return;
	}

	/* Make sure uid2 is a number and that there is a virtual disk for it.
	 */
	if (sscanf(desc[ITM_VDEX_UID2].it_val, "%D", &uid2) != 1 ||
		(vd2 = vd_look_uid(uid2)) == NULL) {

		if (loglevel(LOG_CLIENT_ERROR)) {
		    syslog(LOG_INFO,
			"exchange names: no such virtual disk uid = %s, (%s)",
			desc[ITM_VDEX_UID2].it_val,
			inet_ntoa(fhost->sin_addr));
		}
		ctl_failure(fhost, op, nonce, "no such virtual disk uid");
		return;
	}

	/* If there is an authenticator, allow the exchange if 
	 * 	- the user is on the operations or admin list, OR 
	 *	- the user owns both packs, OR
	 *	- the user is a member of the exclusive mode list of both packs.
	 */
#ifdef	KERBEROS
    {
	KTEXT	authent;

	if ((authent = (struct ktext *)desc[ITM_VDEX_AUTH].it_val) != NULL &&
	    (uname = auth_to_user(authent, fhost->sin_addr)) != NULL) {
		if (priv_user(uname, TRUE, FALSE))
			authent_ok = TRUE;
		if (!authent_ok) {
			if ((strncmp(uname, vd1->vd_desc, NDESC+1) == 0) && 
			    (strncmp(uname, vd2->vd_desc, NDESC+1) == 0))
				authent_ok = TRUE;
		}
		if (!authent_ok) {
			if (is_aclname(vd1->vd_expasswd) &&
			    is_aclname(vd2->vd_expasswd) &&
			    ismember(uname, vd1->vd_expasswd, FALSE) &&
			    ismember(uname, vd2->vd_expasswd, FALSE)) {
				authent_ok = TRUE;
			}
		}
	}
    }
#endif	KERBEROS

	/* Allow the exchange if either password is the operations or
	 * administration password OR if the exclusive password for
	 * each pack has been given.
	 */
	if (!authent_ok) {
	    if (authorized(pw1,TRUE,FALSE) == FALSE &&
	        authorized(pw2,TRUE,FALSE) == FALSE) {

		if (is_aclname(vd1->vd_expasswd) ||
		    is_aclname(vd2->vd_expasswd)) {
			if (loglevel(LOG_CLIENT_ERROR)) {
			    syslog(LOG_INFO,
				"exchange names: %s or %s use access lists. (%s)",
				desc[ITM_VDEX_NAME1].it_val,
				desc[ITM_VDEX_NAME2].it_val,
				inet_ntoa(fhost->sin_addr));
			}
			ctl_failure(fhost, op, nonce, "unauthorized operation");
			return;
		}

		if (strncmp(vd1->vd_expasswd,pw1,sizeof(vd1->vd_expasswd)-1)) {
			if (loglevel(LOG_CLIENT_ERROR)) {
			    syslog(LOG_INFO,
				"exchange names: bad password on %s by %s",
				desc[ITM_VDEX_NAME1].it_val,
				inet_ntoa(fhost->sin_addr));
			}
			ctl_failure(fhost, op, nonce, "unauthorized operation");
			return;
		}

		if (strncmp(vd2->vd_expasswd,pw2,sizeof(vd2->vd_expasswd)-1)) {
			if (loglevel(LOG_CLIENT_ERROR)) {
			    syslog(LOG_INFO,
				"exchange names: bad password on %s by %s",
				desc[ITM_VDEX_NAME2].it_val,
				inet_ntoa(fhost->sin_addr));
			}
			ctl_failure(fhost, op, nonce, "unauthorized operation");
			return;
		}
	    }
	}


	/* Make sure the disk names match their respective ids for each pack.
	 */
	if (strncmp(desc[ITM_VDEX_NAME1].it_val,
		vd1->vd_pack, sizeof(vd1->vd_pack)) || uid1 != vd1->vd_uid) {
		if (loglevel(LOG_CLIENT_ERROR)) {
		    syslog(LOG_INFO,
			"exchange names: mismatched pack name/uids by %s (%s)",
			uname, inet_ntoa(fhost->sin_addr));
		    syslog(LOG_INFO,
			"  given  pack name/uid: %s, %D",
			desc[ITM_VDEX_NAME1].it_val, uid1);
		    syslog(LOG_INFO,
			"  actual pack name/uid: %s, %D",
			vd1->vd_pack, vd1->vd_uid);
		}
		ctl_failure(fhost, op, nonce, "nonmatching uid/name");
		return;
	}
	if (strncmp(desc[ITM_VDEX_NAME2].it_val,
		vd2->vd_pack, sizeof(vd2->vd_pack)) || uid2 != vd2->vd_uid) {
		if (loglevel(LOG_CLIENT_ERROR)) {
		    syslog(LOG_INFO,
			"exchange names: mismatched pack name/uids by %s (%s)",
			uname, inet_ntoa(fhost->sin_addr));
		    syslog(LOG_INFO,
			"  given  pack name/uid: %s, %D",
			desc[ITM_VDEX_NAME2].it_val, uid2);
		    syslog(LOG_INFO,
			"  actual pack name/uid: %s, %D",
			vd2->vd_pack, vd2->vd_uid);
		}
		ctl_failure(fhost, op, nonce, "nonmatching uid/name");
		return;
	}


	/* At last, copy packname2 to packname1.
	 */
	(void)strncpy(vd1->vd_pack, desc[ITM_VDEX_NAME2].it_val,
		sizeof(vd1->vd_pack) - 1);
	(void)strncpy(vd2->vd_pack, desc[ITM_VDEX_NAME1].it_val,
		sizeof(vd2->vd_pack) - 1);

	/* Tell the world.
	 */
	if (loglevel(LOG_SPINS)) {
		syslog(LOG_INFO,
		    "exchange names: %s exchanged with %s by %s (%s)",
			desc[ITM_VDEX_NAME1].it_val,
			desc[ITM_VDEX_NAME2].it_val,
			uname, inet_ntoa(fhost->sin_addr));
	}

	ctl_success(fhost, op, nonce);
}
