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

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


#ifndef lint
static char rcsid_ctl_pkt_c[] = "$Header:ctl_pkt.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 the routines to manage the control connection and
 * deal with control packets.  It includes the routines to parse control
 * packet data.
 */

#include	<stdio.h>
#include	<ctype.h>
#include	<sys/types.h>
#include	<netinet/in.h>

#include	"rvd_types.h"
#include	"logging.h"
#include	"ctl_pkt.h"
#include	"ctl_msgs.h"
#include	"extern.h"

static	char	replbuf[CTLSIZE];	/* buffer for replies */

static struct item ITM_nonce[] = { "nonce", NULL, FALSE };
char *ctl_pmsg();


/* Process the control message contained in the specified buffer.  Parse
 * out the operation field of the message, then look it up in allowable
 * message types list.  Using the skeleton descriptor in the allowable
 * types list, parse the message, then call the appropriate processing
 * routine.  Clean up before leaving.
 */

ctl_domsg(from, buf, buflen)

struct	sockaddr_in	*from;		/* message's sender */
register char	*buf;			/* buffer containing message */
int	buflen;				/* length of buffer in bytes */
{
	register struct	msg	*msgp;	/* temp for scanning msg array */
	int	done;			/* characters parsed so far */
	struct	item	opcode;		/* operation name */
	char	*nonce;			/* optional nonce */

	/* Parse the opcode.
	 */
	if ((done = ctl_parse(buf, buflen, &opcode)) == 0 ||
	    strcmp(opcode.it_key, "operation") != 0) {
		if (loglevel(LOG_CLIENT_ERROR)) {
			syslog(LOG_INFO, "ctl_domsg: no opcode in message from %s",
			  inet_ntoa(from->sin_addr));
		}
		return;
	}

	/* Check for allowable opcode.  If not, the bogus nonce item is
	 * used to parse the message and pick up the nonce for the response.
	 */
	if ((msgp = ctl_msg(opcode.it_val)) == NULL) {
		nonce = ctl_pmsg(&buf[done], (buflen - done), 
				ITM_nonce, nitems(ITM_nonce));
		if (loglevel(LOG_CLIENT_ERROR))
			syslog(LOG_INFO, "unknown operation, (%s)",
				inet_ntoa(from->sin_addr));
		ctl_failure(from, opcode.it_val, nonce, "unknown operation");
		ctl_clean(ITM_nonce, nitems(ITM_nonce));
	}

	/* Parse the message and pick up a pointer to the nonce.
	 */
	nonce = ctl_pmsg(&buf[done], (buflen - done), 
			msgp->ms_desc, msgp->ms_nitem);

	/* Check if we're handling remote requests yet.
	 */
	if (from->sin_addr.s_addr != localhost.s_addr && ! requests_enabled) {
		if (loglevel(LOG_CLIENT_ERROR)) {
			syslog(LOG_INFO, "service not yet available (%s)",
				inet_ntoa(from->sin_addr));
		}
		ctl_failure(from, opcode.it_val, nonce, 
				"service not yet available");
		ctl_clean(msgp->ms_desc, msgp->ms_nitem);
		return;
	}

	/* Make sure all required operands are present.
	 */
	if (!ctl_check(msgp->ms_desc, msgp->ms_nitem)) {
		if (loglevel(LOG_CLIENT_ERROR)) {
			syslog(LOG_INFO, "required operand missing, (%s)",
			  inet_ntoa(from->sin_addr));
		}
		ctl_failure(from, opcode.it_val, nonce, 
			"required operand missing");
		ctl_clean(msgp->ms_desc, msgp->ms_nitem);
		return;
	}

	/* Call the routine for this operation and clean up.
	 */
	(*(msgp->ms_proc))(from, opcode.it_val, msgp->ms_desc, msgp->ms_nitem);
	ctl_clean(msgp->ms_desc, msgp->ms_nitem);
	return;
}


/* Parse the specified control message, filling out the specified skeleton
 * message descriptor.  The message descriptor is an array of message items,
 * each item containing a keyword string and a place to store a pointer to the
 * corresponding value string parsed out of the message buffer.  Each value
 * pointer should initially be null. Skellen is the number of items in the
 * skeleton message descriptor.  Keyword/value pairs found in the message
 * which don't match any keywords in the skeleton descriptor are ignored, as
 * are duplicate keywords.
 * Note that the value pointers will point into the message buffer, so the
 * message descriptor should not be used after the message has been freed.
 */

char *
ctl_pmsg(buf, buflen, skel, skellen)

char	*buf;				/* message buffer pointer */
register int	buflen;			/* number of characters in message */
struct	item	skel[];			/* skeleton message descriptor */
int	skellen;			/* number of items in skeleton */
{
	register char	*next;		/* next character in buf to parse */
	register int	done;		/* number of characters parsed */
	register struct	item	*itp;	/* temp for searching skeleton */
	struct	item	curitm;		/* current item from message */
	register char	*nonce;		/* pointer to nonce string, if any. */

	nonce = (char *)NULL;
	for (next = buf; (done = ctl_parse(next, buflen, &curitm)) != 0;
	     next += done, buflen -= done) {

		ctl_canon(curitm.it_key);
		if ((itp = ctl_item(skel, skellen, curitm.it_key)) != NULL &&
		    itp->it_val == NULL) {
			itp->it_val = curitm.it_val;
			ctl_canon(itp->it_val);
			if (strcmp(itp->it_key, "nonce") == 0)
				nonce = itp->it_val;
		}
	}
	return(nonce);
}


/* Attempt to parse out the next 'keyword=value' item from the specified
 * character buffer.  The keyword and value strings will be null-terminated
 * in the buffer, and the key and val fields of the specified item descriptor
 * will be filled in to point to them.  The strings are not canonicallized
 * by this routine; you must use ctl_canon() below.  The len argument is the
 * size of the buffer in bytes.  Returns the total number of characters which
 * were processed in this call; this is useful in parsing multiple items out of
 * a buffer.  Returns 0 if no item was found.
 */

ctl_parse(buf, buflen, itp)

register char	*buf;			/* buffer containing string to parse */
int	buflen;				/* length of buffer in bytes */
register struct	item	*itp;		/* pointer to place to put item */
{
	register int	len;		/* temp for remaining length */
	register int	quoted;		/* last char was quote flag */

	len = buflen;

	while (len > 0 && issep(*buf)) { /* skip leading blanks */
		buf++;
		len--;
	}
	if (len <= 0)			/* not found */
		return(0);

	itp->it_key = buf;		/* save start of keyword */
	buf++;				/* don't bother to test first char */
	len--;

	for (quoted = FALSE; len > 0 && (quoted || *buf != ENDKEY);
	     buf++, len--) {
		if (quoted)		/* last char was quote */
			quoted = FALSE;
		else if (*buf == QUOTE)	/* this char is quote */
			quoted = TRUE;
		else if (issep(*buf))	/* unquoted separator - error */
			return(0);	/* show we lost */
	}
	if (len <= 0)			/* ran out of room */
		return(0);

	*buf++ = '\0';			/* null-terminate keyword */
	len--;				/* skip the equals sign */
	itp->it_val = buf;		/* save start of value */

	for (quoted = FALSE; len > 0 && (quoted || !issep(*buf));
	     buf++, len--) {
		if (quoted)		/* last char was quote */
			quoted = FALSE;
		else if (*buf == QUOTE)	/* this char is quote */
			quoted = TRUE;
		else if (*buf == ENDKEY) /* unquoted equals sign - error */
			return(0);	/* show we lost */
	}

	*buf = '\0';			/* null-terminate value */
	if (--len < 0)			/* account for separator if any */
		len = 0;
	return(buflen - len);
}


/* Canonicallize in place the specified string.  The canonicallization
 * performed just consists of eliminating quote characters from the string.
 */

ctl_canon(str)

register char	*str;			/* string to be canonicallized */
{
	register char	*to;		/* temp for character moves */
	register int	quoted;		/* last char was quote flag */

	for (to = str, quoted = FALSE; *str != '\0' || quoted; str++) {
		if (quoted) {		/* last char was quote */
			quoted = FALSE;	/* turn it off */
			*to++ = *str;	/* move on down */
		} else if (*str == QUOTE) { /* this char is quote */
			quoted = TRUE;
		} else {
			*to++ = *str;	/* move one */
		}
	}
	*to = '\0';
}


/* Insure that all the required items in the specified item list are in
 * fact present; that is, non-NULL.
 */

boolean
ctl_check(itmlist, nitem)

register struct	item	*itmlist;	/* array of items to check */
register int	nitem;			/* number of items in list */
{
	for ( ; nitem-- > 0; itmlist++)
		if (itmlist->it_req && (itmlist->it_val == NULL))
			return(FALSE);
	return(TRUE);
}


/* Clean up the specified item list by nulling out the value fields of
 * all the items.
 */

ctl_clean(itmlist, nitem)

register struct	item	*itmlist;	/* array of items to clean */
register int	nitem;			/* number of items in list */
{
	while (nitem-- > 0)
		itmlist++->it_val = NULL;
}


/* Return a success indication for the specified operation code to the
 * specified IP address/socket number.
 */

ctl_success(from, opcode, nonce)

struct	sockaddr_in	*from;		/* addr of requester */
char	*opcode;			/* string name of operation done */
char	*nonce;				/* pointer to nonce string */
{
	register char	*p;		/* temp pointer */

	(void)sprintf(replbuf, "success=%s\n", opcode);
	if (nonce != (char *)NULL) {
		p = replbuf + strlen(replbuf);
		(void)sprintf(p, "nonce=%s\n", nonce);
	}
	ctl_send(from, replbuf, strlen(replbuf));
}


/* Return a failure indication for the specified operation code to the
 * specified IP address/socket number.  If non-null, errcode is an
 * error message string.  The string will be quoted as needed.
 */

ctl_failure(from, opcode, nonce, errcode)

struct	sockaddr_in	*from;		/* addr of requester */
char	*opcode;			/* string name of operation done */
char	*nonce;				/* pointer to nonce string */
char	*errcode;			/* error message string if non-null */
{
	register char	*p;		/* temp pointer */

	(void)sprintf(replbuf, "failure=%s\n", opcode);
	p = replbuf + strlen(replbuf);
	if (nonce != (char *)NULL) {
		(void)sprintf(p, "nonce=%s\n", nonce);
		p += strlen(p);
	}
	if (errcode != NULL)
		csprintf(p, "error=%s\n", errcode);

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