//==========================================================================
//
//      ./lib/current/src/snmpusm.c
//
//
//==========================================================================
//####ECOSGPLCOPYRIGHTBEGIN####
// -------------------------------------------
// This file is part of eCos, the Embedded Configurable Operating System.
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
//
// eCos is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 or (at your option) any later version.
//
// eCos is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with eCos; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
//
// As a special exception, if other files instantiate templates or use macros
// or inline functions from this file, or you compile this file and link it
// with other works to produce a work based on this file, this file does not
// by itself cause the resulting work to be covered by the GNU General Public
// License. However the source code for this file must still be made available
// in accordance with section (3) of the GNU General Public License.
//
// This exception does not invalidate any other reasons why a work based on
// this file might be covered by the GNU General Public License.
//
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
// at http://sources.redhat.com/ecos/ecos-license/
// -------------------------------------------
//####ECOSGPLCOPYRIGHTEND####
//####UCDSNMPCOPYRIGHTBEGIN####
//
// -------------------------------------------
//
// Portions of this software may have been derived from the UCD-SNMP
// project,  <http://ucd-snmp.ucdavis.edu/>  from the University of
// California at Davis, which was originally based on the Carnegie Mellon
// University SNMP implementation.  Portions of this software are therefore
// covered by the appropriate copyright disclaimers included herein.
//
// The release used was version 4.1.2 of May 2000.  "ucd-snmp-4.1.2"
// -------------------------------------------
//
//####UCDSNMPCOPYRIGHTEND####
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    hmt
// Contributors: hmt
// Date:         2000-05-30
// Purpose:      Port of UCD-SNMP distribution to eCos.
// Description:  
//              
//
//####DESCRIPTIONEND####
//
//==========================================================================
/********************************************************************
       Copyright 1989, 1991, 1992 by Carnegie Mellon University

			  Derivative Work -
Copyright 1996, 1998, 1999, 2000 The Regents of the University of California

			 All Rights Reserved

Permission to use, copy, modify and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appears in all copies and
that both that copyright notice and this permission notice appear in
supporting documentation, and that the name of CMU and The Regents of
the University of California not be used in advertising or publicity
pertaining to distribution of the software without specific written
permission.

CMU AND THE REGENTS OF THE UNIVERSITY OF CALIFORNIA DISCLAIM ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL CMU OR
THE REGENTS OF THE UNIVERSITY OF CALIFORNIA BE LIABLE FOR ANY SPECIAL,
INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
FROM THE LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*********************************************************************/
/*
 * snmpusm.c
 *
 * Routines to manipulate a information about a "user" as
 * defined by the SNMP-USER-BASED-SM-MIB MIB.
 *
 * All functions usm_set_usmStateReference_*() return 0 on success, -1
 * otherwise.
 *
 * !! Tab stops set to 4 in some parts of this file. !!
 *    (Designated on a per function.)
 */

#include <config.h>

#ifdef CYGPKG_SNMPAGENT_V3_SUPPORT
#include <sys/types.h>
#if HAVE_WINSOCK_H
#include <winsock.h>
#endif
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if TIME_WITH_SYS_TIME
# ifdef WIN32
#  include <sys/timeb.h>
# else
#  include <sys/time.h>
# endif
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#if HAVE_DMALLOC_H
#include <dmalloc.h>
#endif

#include "asn1.h"
#include "snmp_api.h"
#include "snmp_debug.h"
#include "callback.h"
#include "tools.h"
#include "keytools.h"
#include "snmp.h"
#include "read_config.h"
#include "snmpv3.h"
#include "snmp-tc.h"
#include "lcd_time.h"
#include "scapi.h"
#include "callback.h"
#include "default_store.h"
#include "snmpusm.h"

#include "transform_oids.h"

static u_int    dummy_etime, dummy_eboot;	/* For ISENGINEKNOWN(). */

/*
 * Globals.
 */
static u_int salt_integer;
	/* 1/2 of seed for the salt.   Cf. RFC2274, Sect 8.1.1.1.
	 */

int reportErrorOnUnknownID = 0;
	/* Should be determined based on msg type.
	 */

static struct usmUser *initialUser = NULL;
static struct usmUser *noNameUser = NULL;

/*
 * Prototypes
 */
int
usm_check_secLevel_vs_protocols(int level,
                                oid *authProtocol, u_int authProtocolLen,
                                oid *privProtocol, u_int privProtocolLen);
  
/* 
 * Set a given field of the secStateRef.
 *
 * Allocate <len> bytes for type <type> pointed to by ref-><field>.
 * Then copy in <item> and record its length in ref-><field_len>.
 *
 * Return 0 on success, -1 otherwise.
 */
#define MAKE_ENTRY( type, item, len, field, field_len )			\
{									\
	if (ref == NULL)						\
		return -1;						\
	if (ref->field != NULL)	{					\
		SNMP_ZERO(ref->field, ref->field_len);			\
		SNMP_FREE(ref->field);					\
	}								\
	ref->field_len = 0;						\
        if (len == 0 || item == NULL) {					\
		return 0;						\
	}					 			\
	if ((ref->field = (type*) malloc (len * sizeof(type))) == NULL)	\
	{								\
		return -1;						\
	}								\
									\
	memcpy (ref->field, item, len * sizeof(type));			\
	ref->field_len = len;						\
									\
	return 0;							\
}


void
usm_set_reportErrorOnUnknownID (int value)
{
	reportErrorOnUnknownID = value;
}


struct usmStateReference *
usm_malloc_usmStateReference(void)
{
	struct usmStateReference *retval = (struct usmStateReference *)
		calloc(1,sizeof(struct usmStateReference));

	return retval;
}  /* end usm_malloc_usmStateReference() */


void
usm_free_usmStateReference (void *old)
{
	struct usmStateReference *old_ref = (struct usmStateReference *)old;

    if (old_ref) {

	SNMP_FREE(old_ref->usr_name);
	SNMP_FREE(old_ref->usr_engine_id);
	SNMP_FREE(old_ref->usr_auth_protocol);
	SNMP_FREE(old_ref->usr_priv_protocol);

	if (old_ref->usr_auth_key) {
		SNMP_ZERO(old_ref->usr_auth_key, old_ref->usr_auth_key_length);
		SNMP_FREE(old_ref->usr_auth_key);
	}
	if (old_ref->usr_priv_key) {
		SNMP_ZERO(old_ref->usr_priv_key, old_ref->usr_priv_key_length);
		SNMP_FREE(old_ref->usr_priv_key);
	}

	SNMP_ZERO(old_ref, sizeof(*old_ref));
	SNMP_FREE(old_ref);

   }

}  /* end usm_free_usmStateReference() */



int
usm_set_usmStateReference_name (
	struct usmStateReference *ref,
	char *name,
	size_t name_len)
{
	MAKE_ENTRY (char,name,name_len,usr_name,usr_name_length);
}

int
usm_set_usmStateReference_engine_id (
	struct usmStateReference *ref,
	u_char *engine_id,
	size_t engine_id_len)
{
	MAKE_ENTRY (u_char,engine_id,engine_id_len,
		usr_engine_id,usr_engine_id_length);
}

int
usm_set_usmStateReference_auth_protocol (
	struct usmStateReference *ref,
	oid *auth_protocol,
	size_t auth_protocol_len)
{
	MAKE_ENTRY (oid ,auth_protocol,auth_protocol_len,
		usr_auth_protocol,usr_auth_protocol_length);
}

int
usm_set_usmStateReference_auth_key (
	struct usmStateReference *ref,
	u_char *auth_key,
	size_t auth_key_len)
{
	MAKE_ENTRY (u_char,auth_key,auth_key_len,
		usr_auth_key,usr_auth_key_length);
}

int
usm_set_usmStateReference_priv_protocol (
	struct usmStateReference *ref,
	oid *priv_protocol,
	size_t priv_protocol_len)
{
	MAKE_ENTRY (oid,priv_protocol,priv_protocol_len,
		usr_priv_protocol,usr_priv_protocol_length);
}

int
usm_set_usmStateReference_priv_key (
	struct usmStateReference *ref,
	u_char *priv_key,
	size_t priv_key_len)
{
	MAKE_ENTRY (u_char,priv_key,priv_key_len,
		usr_priv_key,usr_priv_key_length);
}

int
usm_set_usmStateReference_sec_level (
	struct usmStateReference *ref,
	int sec_level)
{
	if (ref == NULL) return -1;
	ref->usr_sec_level = sec_level;
	return 0;
}



#ifdef SNMP_TESTING_CODE
/*******************************************************************-o-******
 * emergency_print
 *
 * Parameters:
 *	*field
 *	 length
 *      
 *	This is a print routine that is solely included so that it can be
 *	used in gdb.  Don't use it as a function, it will be pulled before
 *	a real release of the code.
 *
 *	tab stop 4
 *
 *	XXX fflush() only works on FreeBSD; core dumps on Sun OS's
 */
void
emergency_print (u_char *field, u_int length)
{
	int iindex;
	int start=0;
	int stop=25;

	while (start < stop)
	{
		for (iindex = start; iindex < stop; iindex++)
			printf ("%02X ", field[iindex]);

		printf ("\n");
		start = stop;
		stop = stop+25<length?stop+25:length;
	}
	fflush (0);

}  /* end emergency_print() */
#endif /* SNMP_TESTING_CODE */


/*******************************************************************-o-******
 * asn_predict_int_length
 *
 * Parameters:
 *	type	(UNUSED)
 *	number
 *	len
 *      
 * Returns:
 *	Number of bytes necessary to store the ASN.1 encoded value of 'number'.
 *
 *
 *	This gives the number of bytes that the ASN.1 encoder (in asn1.c) will
 *	use to encode a particular integer value.
 *
 *	Returns the length of the integer -- NOT THE HEADER!
 *
 *	Do this the same way as asn_build_int()...
 */
int
asn_predict_int_length (int type, long number, size_t len)
{
	register u_long mask;


	if (len != sizeof (long)) return -1;

	mask = ((u_long) 0x1FF) << ((8 * (sizeof(long) - 1)) - 1);
	/* mask is 0xFF800000 on a big-endian machine */

	while((((number & mask) == 0) || ((number & mask) == mask)) && len > 1)
	{
		len--;
		number <<= 8;
	}

	return len;

}  /* end asn_predict_length() */




/*******************************************************************-o-******
 * asn_predict_length
 *
 * Parameters:
 *	 type
 *	*ptr
 *	 u_char_len
 *      
 * Returns:
 *	Length in bytes:	1 + <n> + <u_char_len>, where
 *
 *		1		For the ASN.1 type.
 *		<n>		# of bytes to store length of data.
 *		<u_char_len>	Length of data associated with ASN.1 type.
 *
 *	This gives the number of bytes that the ASN.1 encoder (in asn1.c) will
 *	use to encode a particular integer value.  This is as broken as the
 *	currently used encoder.
 *
 * XXX	How is <n> chosen, exactly??
 */
int
asn_predict_length (int type, u_char *ptr, size_t u_char_len)
{

	if (type & ASN_SEQUENCE) return 1+3+u_char_len;

	if (type &  ASN_INTEGER)
	{
		u_long value;
		memcpy (&value, ptr, u_char_len);
		u_char_len = asn_predict_int_length (type, value, u_char_len);
	}

	if (u_char_len < 0x80)
		return 1+1+u_char_len;
	else if (u_char_len < 0xFF)
		return 1+2+u_char_len;
	else
		return 1+3+u_char_len;

}  /* end asn_predict_length() */




/*******************************************************************-o-******
 * usm_calc_offsets
 *
 * Parameters:
 *	(See list below...)
 *      
 * Returns:
 *	0	On success,
 *	-1	Otherwise.
 *
 *
 *	This routine calculates the offsets into an outgoing message buffer
 *	for the necessary values.  The outgoing buffer will generically
 *	look like this:
 *
 *	SNMPv3 Message
 *	SEQ len[11]
 *		INT len version
 *	Header
 *		SEQ len
 *			INT len MsgID
 *			INT len msgMaxSize
 *			OST len msgFlags (OST = OCTET STRING)
 *			INT len msgSecurityModel
 *	MsgSecurityParameters
 *		[1] OST len[2]
 *			SEQ len[3]
 *				OST len msgAuthoritativeEngineID
 *				INT len msgAuthoritativeEngineBoots
 *				INT len msgAuthoritativeEngineTime
 *				OST len msgUserName
 *				OST len[4] [5] msgAuthenticationParameters
 *				OST len[6] [7] msgPrivacyParameters
 *	MsgData
 *		[8] OST len[9] [10] encryptedPDU
 *		or
 *		[8,10] SEQUENCE len[9] scopedPDU
 *	[12]
 *
 *	The bracketed points will be needed to be identified ([x] is an index
 *	value, len[x] means a length value).  Here is a semantic guide to them:
 *
 *	[1] = globalDataLen (input)
 *	[2] = otstlen
 *	[3] = seq_len
 *	[4] = msgAuthParmLen (may be 0 or 12)
 *	[5] = authParamsOffset
 *	[6] = msgPrivParmLen (may be 0 or 8)
 *	[7] = privParamsOffset
 *	[8] = globalDataLen + msgSecParmLen
 *	[9] = datalen
 *	[10] = dataOffset
 *	[11] = theTotalLength - the length of the header itself
 *	[12] = theTotalLength
 */
int
usm_calc_offsets (
	size_t  globalDataLen,	/* SNMPv3Message + HeaderData */
	int     secLevel,
	size_t  secEngineIDLen,
	size_t  secNameLen,
	size_t  scopedPduLen,	/* An BER encoded sequence. */
	u_long  engineboots,	/* XXX (asn1.c works in long, not int.) */
	long    engine_time,	/* XXX (asn1.c works in long, not int.) */

	size_t *theTotalLength,	 /* globalDataLen + msgSecurityP. + msgData */
	size_t *authParamsOffset,/* Distance to auth bytes.                 */
	size_t *privParamsOffset,/* Distance to priv bytes.                 */
	size_t *dataOffset,	 /* Distance to scopedPdu SEQ  -or-  the
				  *   crypted (data) portion of msgData.    */

	size_t *datalen,	/* Size of msgData OCTET STRING encoding.  */
	size_t *msgAuthParmLen,	/* Size of msgAuthenticationParameters.    */
	size_t *msgPrivParmLen,	/* Size of msgPrivacyParameters.           */
	size_t *otstlen,	/* Size of msgSecurityP. O.S. encoding.    */
	size_t *seq_len,	/* Size of msgSecurityP. SEQ data.         */
	size_t *msgSecParmLen)	/* Size of msgSecurityP. SEQ.              */
{
	int	engIDlen,	/* Sizes of OCTET STRING and SEQ encodings */
		engBtlen,	/*   for fields within                     */
		engTmlen,	/*   msgSecurityParameters portion of      */
		namelen,	/*   SNMPv3Message.                        */
		authlen,
		privlen;

	/* 
	 * If doing authentication, msgAuthParmLen = 12 else msgAuthParmLen = 0.
	 * If doing encryption,     msgPrivParmLen = 8  else msgPrivParmLen = 0.
	 */
	*msgAuthParmLen = (secLevel == SNMP_SEC_LEVEL_AUTHNOPRIV
		|| secLevel == SNMP_SEC_LEVEL_AUTHPRIV)?12:0;

	*msgPrivParmLen = (secLevel == SNMP_SEC_LEVEL_AUTHPRIV)?8:0;


	/* 
	 * Calculate lengths.
	 */
	if ( (engIDlen = asn_predict_length(ASN_OCTET_STR,
				0, secEngineIDLen)) == -1 )
	{
		return -1;
	}

	if ( (engBtlen = asn_predict_length (ASN_INTEGER,
				(u_char*)&engineboots,sizeof(long))) == -1 )
	{
		return -1;
	}

	if ( (engTmlen = asn_predict_length (ASN_INTEGER,
				(u_char*)&engine_time,sizeof(long))) == -1 )
	{
		return -1;
	}

	if ( (namelen = asn_predict_length (ASN_OCTET_STR,0,secNameLen))==-1 )
	{
		return -1;
	}

	if ( (authlen = asn_predict_length (ASN_OCTET_STR,
				0,*msgAuthParmLen)) == -1 )
	{
		return -1;
	}

	if ( (privlen = asn_predict_length (ASN_OCTET_STR,
				0,*msgPrivParmLen)) == -1 )
	{
		return -1;
	}

	*seq_len = engIDlen + engBtlen + engTmlen + namelen + authlen + privlen;

	if ( (*otstlen = asn_predict_length (ASN_SEQUENCE,
				0, *seq_len)) == -1 )
	{
		return -1;
	}

	if ( (*msgSecParmLen = asn_predict_length (ASN_OCTET_STR,
				0,*otstlen)) == -1 )
	{
		return -1;
	}

	*authParamsOffset =	globalDataLen +
		+ (*msgSecParmLen - *seq_len)
		+ engIDlen + engBtlen + engTmlen + namelen
		+ (authlen - *msgAuthParmLen);

	*privParamsOffset =	*authParamsOffset + *msgAuthParmLen
		+ (privlen - *msgPrivParmLen);


	/*
	 * Compute the size of the plaintext.  Round up to account for cipher
	 * block size, if necessary.
	 *
	 * XXX  This is hardwired for 1DES... If scopedPduLen is already
	 *	a multiple of 8, then *add* 8 more; otherwise, round up
	 *	to the next multiple of 8.
	 *
	 * FIX  Calculation of encrypted portion of msgData and consequent
	 *	setting and sanity checking of theTotalLength, et al. should
	 *	occur *after* encryption has taken place.
	 */
	if (secLevel == SNMP_SEC_LEVEL_AUTHPRIV)
	{
		scopedPduLen = ( scopedPduLen % 8 )
					? ROUNDUP8(scopedPduLen)
					: scopedPduLen + 8;

		if ((*datalen = 
			asn_predict_length (ASN_OCTET_STR,0,scopedPduLen))==-1)
		{
			return -1;
		}
	}
	else
	{
		*datalen = scopedPduLen;
	}

	*dataOffset	= globalDataLen + *msgSecParmLen +
						(*datalen - scopedPduLen);
	*theTotalLength = globalDataLen + *msgSecParmLen + *datalen;

	return 0;

}  /* end usm_calc_offsets() */





/*******************************************************************-o-******
 * usm_set_salt
 *
 * Parameters:
 *	*iv		  (O)   Buffer to contain IV.
 *	*iv_length	  (O)   Length of iv.
 *	*priv_salt	  (I)   Salt portion of private key.
 *	 priv_salt_length (I)   Length of priv_salt.
 *	*msgSalt	  (I/O) Pointer salt portion of outgoing msg buffer.
 *      
 * Returns:
 *	0	On success,
 *	-1	Otherwise.
 *
 *	Determine the initialization vector for the DES-CBC encryption.
 *	(Cf. RFC 2274, 8.1.1.1.)
 *
 *	iv is defined as the concatenation of engineBoots and the
 *		salt integer.
 *	The salt integer is incremented.
 *	The resulting salt is copied into the msgSalt buffer.
 *	The result of the concatenation is then XORed with the salt
 *		portion of the private key (last 8 bytes).
 *	The IV result is returned individually for further use.
 */
int
usm_set_salt (	u_char		*iv,
		size_t		*iv_length,
		u_char		*priv_salt,
		size_t		 priv_salt_length,
		u_char		*msgSalt)
{
	size_t propersize_salt     = BYTESIZE(USM_MAX_SALT_LENGTH);
	int net_boots;
        int net_salt_int;
		/* net_* should be encoded in network byte order.  XXX  Why?
		 */
	int iindex;


	/*
	 * Sanity check.
	 */
	if ( !iv || !iv_length || !priv_salt || !msgSalt
		|| (*iv_length != propersize_salt)
             	|| (priv_salt_length < propersize_salt) )
	{
        	return -1;
	}


	net_boots 	= htonl(snmpv3_local_snmpEngineBoots());
        net_salt_int	= htonl(salt_integer);

	salt_integer += 1;

	memcpy(iv,			&net_boots,	propersize_salt/2);
	memcpy(iv+(propersize_salt/2), &net_salt_int,	propersize_salt/2);

	memcpy(msgSalt, iv, propersize_salt);


	/* 
	 * Turn the salt into an IV: XOR <boots, salt_int> with salt
	 * portion of priv_key.
	 */
	for (iindex = 0; iindex < (int)propersize_salt; iindex++)
		iv[iindex] ^= priv_salt[iindex];


	return 0;

}  /* end usm_set_salt() */




/*******************************************************************-o-******
 * usm_generate_out_msg
 *
 * Parameters:
 *	(See list below...)
 *      
 * Returns:
 *	SNMPERR_SUCCESS			On success.
 *	SNMPERR_USM_AUTHENTICATIONFAILURE
 *	SNMPERR_USM_ENCRYPTIONERROR
 *	SNMPERR_USM_GENERICERROR
 *	SNMPERR_USM_UNKNOWNSECURITYNAME
 *	SNMPERR_USM_GENERICERROR
 *	SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL
 *	
 *
 * Generates an outgoing message.
 *
 * XXX	Beware of misnomers!
 */
int
usm_generate_out_msg (
     int      msgProcModel,	/* (UNUSED) */

     u_char  *globalData,	/* IN */
		/* Pointer to msg header data will point to the beginning
		 * of the entire packet buffer to be transmitted on wire,
		 * memory will be contiguous with secParams, typically
		 * this pointer will be passed back as beginning of
		 * wholeMsg below.  asn seq. length is updated w/ new length.
		 *
		 * While this points to a buffer that should be big enough
		 * for the whole message, only the first two parts
		 * of the message are completed, namely SNMPv3Message and
		 * HeaderData.  globalDataLen (next parameter) represents
		 * the length of these two completed parts.
		 */

     size_t   globalDataLen,	/* IN - Length of msg header data.	*/
     int      maxMsgSize,	/* (UNUSED) */
     int      secModel,		/* (UNUSED) */
     u_char  *secEngineID,	/* IN - Pointer snmpEngineID.		*/
     size_t   secEngineIDLen,	/* IN - SnmpEngineID length.		*/
     char    *secName,		/* IN - Pointer to securityName.	*/
     size_t   secNameLen,	/* IN - SecurityName length.		*/
     int      secLevel,		/* IN - AuthNoPriv, authPriv etc.	*/

     u_char  *scopedPdu,	/* IN */
		/* Pointer to scopedPdu will be encrypted by USM if needed
		 * and written to packet buffer immediately following
		 * securityParameters, entire msg will be authenticated by
		 * USM if needed.
		 */

     size_t   scopedPduLen,	/* IN - scopedPdu length. */

     void    *secStateRef,	/* IN */
		/* secStateRef, pointer to cached info provided only for
		 * Response, otherwise NULL.
		 */

     u_char  *secParams,	/* OUT */
		/* BER encoded securityParameters pointer to offset within
		 * packet buffer where secParams should be written, the
		 * entire BER encoded OCTET STRING (including header) is
		 * written here by USM secParams = globalData +
		 * globalDataLen.
		 */

     size_t  *secParamsLen,	/* IN/OUT - Len available, len returned. */

     u_char **wholeMsg,         /* OUT */
		/* Complete authenticated/encrypted message - typically
		 * the pointer to start of packet buffer provided in
		 * globalData is returned here, could also be a separate
		 * buffer.
		 */

     size_t *wholeMsgLen)          /* IN/OUT - Len available, len returned. */
{
	size_t otstlen;
	size_t seq_len;
	size_t msgAuthParmLen;
	size_t msgPrivParmLen;
	size_t msgSecParmLen;
	size_t authParamsOffset;
	size_t privParamsOffset;
	size_t datalen;
	size_t dataOffset;
	size_t theTotalLength;

	u_char         *ptr;
	size_t          ptr_len;
	size_t          remaining;
	size_t          offSet;
	u_int           boots_uint;
	u_int           time_uint;
	long            boots_long;
	long            time_long;

	/*
		Indirection because secStateRef values override parameters.

		None of these are to be free'd - they are either pointing to
		what's in the secStateRef or to something either in the
		actual prarmeter list or the user list.
	*/

	char   *theName		 	= NULL;
	u_int   theNameLength		= 0;
	u_char *theEngineID		= NULL;
	u_int   theEngineIDLength	= 0;
	u_char *theAuthKey		= NULL;
	u_int   theAuthKeyLength	= 0;
	oid    *theAuthProtocol		= NULL;
	u_int   theAuthProtocolLength	= 0;
	u_char *thePrivKey		= NULL;
	u_int   thePrivKeyLength	= 0;
	oid    *thePrivProtocol		= NULL;
	u_int   thePrivProtocolLength	= 0;
	int     theSecLevel		= 0;	/* No defined const for bad
						 * value (other then err).
						 */


	DEBUGMSGTL(("usm","USM processing has begun.\n"));

	if (secStateRef != NULL)
	{
		/* To hush the compiler for now.  XXX */
		struct usmStateReference *ref
				= (struct usmStateReference *)secStateRef;

		theName		 	= ref->usr_name;
		theNameLength		= ref->usr_name_length;
		theEngineID		= ref->usr_engine_id;
		theEngineIDLength	= ref->usr_engine_id_length;

		if (!theEngineIDLength) {
		  theEngineID		= secEngineID;
		  theEngineIDLength	= secEngineIDLen;
		}

		theAuthProtocol		= ref->usr_auth_protocol;
		theAuthProtocolLength	= ref->usr_auth_protocol_length;
		theAuthKey		= ref->usr_auth_key;
		theAuthKeyLength	= ref->usr_auth_key_length;
		thePrivProtocol		= ref->usr_priv_protocol;
		thePrivProtocolLength	= ref->usr_priv_protocol_length;
		thePrivKey		= ref->usr_priv_key;
		thePrivKeyLength	= ref->usr_priv_key_length;
		theSecLevel		= ref->usr_sec_level;
	}

	/* 
	 * Identify the user record.
	 */
	else
	{
		struct usmUser *user;

                /* we do allow an unknown user name for
                   unauthenticated requests. */
		if ( (user = 
			usm_get_user(secEngineID, secEngineIDLen, secName))
				== NULL &&
                     			secLevel != SNMP_SEC_LEVEL_NOAUTH)
		{
			DEBUGMSGTL(("usm","Unknown User\n"));
			usm_free_usmStateReference (secStateRef);
			return SNMPERR_USM_UNKNOWNSECURITYNAME;
		}

		theName		 	= secName;
		theNameLength		= secNameLen;
		theEngineID		= secEngineID;
                theSecLevel		= secLevel;
		theEngineIDLength	= secEngineIDLen;
                if (user) {
                  theAuthProtocol	= user->authProtocol;
                  theAuthProtocolLength	= user->authProtocolLen;
                  theAuthKey		= user->authKey;
                  theAuthKeyLength	= user->authKeyLen;
                  thePrivProtocol	= user->privProtocol;
                  thePrivProtocolLength	= user->privProtocolLen;
                  thePrivKey		= user->privKey;
                  thePrivKeyLength	= user->privKeyLen;
                } else {
                  /* unknown users can not do authentication (obviously) */
                  theAuthProtocol	= usmNoAuthProtocol;
                  theAuthProtocolLength	= sizeof(usmNoAuthProtocol)/sizeof(oid);
                  theAuthKey		= NULL;
                  theAuthKeyLength	= 0;
                  thePrivProtocol	= usmNoPrivProtocol;
                  thePrivProtocolLength	= sizeof(usmNoPrivProtocol)/sizeof(oid);
                  thePrivKey		= NULL;
                  thePrivKeyLength	= 0;
                }
	}  /* endif -- secStateRef==NULL */


	/*
		From here to the end of the function, avoid reference to
		secName, secEngineID, secLevel, and associated lengths.
	*/


	/* 
	 * Check to see if the user can use the requested sec services.
	 */
	if (usm_check_secLevel_vs_protocols(
		theSecLevel,
		theAuthProtocol, theAuthProtocolLength,
		theAuthProtocol, theAuthProtocolLength) == 1)
	{
		DEBUGMSGTL(("usm","Unsupported Security Level\n"));
		usm_free_usmStateReference (secStateRef);
		return SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL;
	}


	/* 
	 * Retrieve the engine information.
	 *
	 * XXX	No error is declared in the EoP when sending messages to
         * 	unknown engines, processing continues w/ boots/time == (0,0).
	 */
	if (get_enginetime (theEngineID, theEngineIDLength, 
			    &boots_uint, &time_uint, FALSE) == -1)
	{
		DEBUGMSGTL(("usm","%s\n", "Failed to find engine data."));
	}

	boots_long = boots_uint;
	time_long  = time_uint;
	

	/* 
	 * Set up the Offsets.
	 */
	if (usm_calc_offsets (globalDataLen, theSecLevel, theEngineIDLength,
		theNameLength, scopedPduLen, boots_long, time_long,
		&theTotalLength, &authParamsOffset,
		&privParamsOffset, &dataOffset, &datalen,
		&msgAuthParmLen, &msgPrivParmLen,
		&otstlen, &seq_len, &msgSecParmLen) == -1)
	{
		DEBUGMSGTL(("usm","Failed calculating offsets.\n"));
		usm_free_usmStateReference (secStateRef);
		return SNMPERR_USM_GENERICERROR;
	}

	/*
		So, we have the offsets for the three parts that need to be
		determined, and an overall length.  Now we need to make
		sure all of this would fit in the outgoing buffer, and
		whether or not we need to make a new buffer, etc.
	*/


	/* 
	 * Set wholeMsg as a pointer to globalData.  Sanity check for
	 * the proper size.
	 * 
	 * Mark workspace in the message with bytes of all 1's to make it
	 * easier to find mistakes in raw message dumps.
	 */
	ptr = *wholeMsg = globalData;
	if (theTotalLength > *wholeMsgLen)
	{
		DEBUGMSGTL(("usm","Message won't fit in buffer.\n"));
		usm_free_usmStateReference (secStateRef);
		return SNMPERR_USM_GENERICERROR;
	}

	ptr_len = *wholeMsgLen = theTotalLength;

#ifdef SNMP_TESTING_CODE
	memset (&ptr[globalDataLen], 0xFF, theTotalLength-globalDataLen);
#endif /* SNMP_TESTING_CODE */


	/* 
	 * Do the encryption.
	 */
	if (theSecLevel == SNMP_SEC_LEVEL_AUTHPRIV)
	{
		size_t	encrypted_length = theTotalLength - dataOffset;
		size_t	salt_length	 = BYTESIZE(USM_MAX_SALT_LENGTH);
                u_char	salt[BYTESIZE(USM_MAX_SALT_LENGTH)];

		/* XXX  Hardwired to seek into a 1DES private key!
		 */
		if ( usm_set_salt(	salt,		&salt_length,
					thePrivKey+8,	thePrivKeyLength-8,
                        		&ptr[privParamsOffset])
						== -1 )
		{
			DEBUGMSGTL(("usm","Can't set DES-CBC salt.\n"));
			usm_free_usmStateReference (secStateRef);
			return SNMPERR_USM_GENERICERROR;
		}

		if ( sc_encrypt(
			 thePrivProtocol,	 thePrivProtocolLength,
			 thePrivKey,		 thePrivKeyLength,
                         salt,			 salt_length,
			 scopedPdu,		 scopedPduLen,
			&ptr[dataOffset],	&encrypted_length)
							!= SNMP_ERR_NOERROR )
		{
			DEBUGMSGTL(("usm","DES-CBC error.\n"));
			usm_free_usmStateReference (secStateRef);
			return SNMPERR_USM_ENCRYPTIONERROR;
		}


#ifdef SNMP_TESTING_CODE
		if ( debug_is_token_registered("usm/dump") == SNMPERR_SUCCESS) {
			dump_chunk("usm/dump", "This data was encrypted:",
					scopedPdu, scopedPduLen);
			dump_chunk("usm/dump", "salt + Encrypted form:",
					salt, salt_length);
			dump_chunk("usm/dump", NULL,
					&ptr[dataOffset], encrypted_length);
			dump_chunk("usm/dump", "*wholeMsg:",
					*wholeMsg, theTotalLength);
		}
#endif


		ptr 	= *wholeMsg;
		ptr_len = *wholeMsgLen = theTotalLength;


		/* 
		 * XXX  Sanity check for salt length should be moved up
		 *	under usm_calc_offsets() or tossed.
		 */
		if ( (encrypted_length != (theTotalLength - dataOffset))
				|| (salt_length != msgPrivParmLen) )
		{
			DEBUGMSGTL(("usm","DES-CBC length error.\n"));
			usm_free_usmStateReference (secStateRef);
			return SNMPERR_USM_ENCRYPTIONERROR;
		}

		DEBUGMSGTL(("usm","Encryption successful.\n"));
	}

	/* 
	 * No encryption for you!
	 */
	else
	{
		memcpy( &ptr[dataOffset], scopedPdu, scopedPduLen );
	}



	/* 
	 * Start filling in the other fields (in prep for authentication).
	 * 
	 * offSet is an octet string header, which is different from all
	 * the other headers.
	 */
	remaining = ptr_len - globalDataLen;

	offSet =  ptr_len - remaining;
	asn_build_header (&ptr[offSet], &remaining, 
		(u_char)(ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR), otstlen);

	offSet = ptr_len - remaining;
	asn_build_sequence (&ptr[offSet], &remaining, 
		(u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), seq_len);
	
	offSet = ptr_len - remaining;
	asn_build_string (&ptr[offSet], &remaining,
		(u_char)(ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR),
		theEngineID, theEngineIDLength);
	
	offSet = ptr_len - remaining;
	asn_build_int (&ptr[offSet], &remaining,
		(u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
		&boots_long, sizeof(long));
	
	offSet = ptr_len - remaining;
	asn_build_int (&ptr[offSet], &remaining,
		(u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
		&time_long, sizeof(long));
	
	offSet = ptr_len - remaining;
	asn_build_string (&ptr[offSet], &remaining,
		(u_char)(ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR),
		(u_char *)theName, theNameLength);


	/*
		Note: if there is no authentication being done,
		msgAuthParmLen is 0, and there is no effect (other than
		inserting a zero-length header) of the following
		statements.
	*/

	offSet = ptr_len - remaining;
	asn_build_header(
			&ptr[offSet],
			&remaining,
			(u_char)(ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR),
			msgAuthParmLen);

	if (theSecLevel == SNMP_SEC_LEVEL_AUTHNOPRIV
		|| theSecLevel == SNMP_SEC_LEVEL_AUTHPRIV)
	{
		offSet = ptr_len - remaining;
		memset (&ptr[offSet],0,msgAuthParmLen);
	}

	remaining -= msgAuthParmLen;


	/*
		Note: if there is no encryption being done, msgPrivParmLen
		is 0, and there is no effect (other than inserting a
		zero-length header) of the following statements.
	*/

	offSet = ptr_len - remaining;
	asn_build_header(
		&ptr[offSet],
		&remaining,
		(u_char)(ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR),
		msgPrivParmLen);

	remaining -= msgPrivParmLen;	/* Skipping the IV already there. */


	/* 
	 * For privacy, need to add the octet string header for it.
	 */
	if (theSecLevel==SNMP_SEC_LEVEL_AUTHPRIV)
	{
		offSet = ptr_len - remaining;
		asn_build_header(
			&ptr[offSet],
			&remaining,
			(u_char)(ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR),
			theTotalLength - dataOffset );
	}


	/* 
	 * Adjust overall length and store it as the first SEQ length
	 * of the SNMPv3Message.
	 *
	 * FIX	4 is a magic number!
	 */
	remaining = theTotalLength;
	asn_build_sequence (ptr, &remaining, 
		(u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), theTotalLength-4);


	/* 
	 * Now, time to consider / do authentication.
	 */
	if (theSecLevel == SNMP_SEC_LEVEL_AUTHNOPRIV
		|| theSecLevel == SNMP_SEC_LEVEL_AUTHPRIV)
	{
		size_t	temp_sig_len	= msgAuthParmLen;
		u_char *temp_sig	= (u_char *) malloc (temp_sig_len);

		if (temp_sig == NULL)
		{
			DEBUGMSGTL(("usm","Out of memory.\n"));
			usm_free_usmStateReference (secStateRef);
			return SNMPERR_USM_GENERICERROR;
		}

		if ( sc_generate_keyed_hash (
			theAuthProtocol,	 theAuthProtocolLength,
			theAuthKey,		 theAuthKeyLength,
			ptr,			 ptr_len,
			temp_sig,		&temp_sig_len)
							!= SNMP_ERR_NOERROR )
		{
			/* FIX temp_sig_len defined?!
			 */
			SNMP_ZERO(temp_sig, temp_sig_len);
			SNMP_FREE(temp_sig);
			DEBUGMSGTL(("usm","Signing failed.\n"));
			usm_free_usmStateReference (secStateRef);
			return SNMPERR_USM_AUTHENTICATIONFAILURE;
		}

		if (temp_sig_len != msgAuthParmLen)
		{
			SNMP_ZERO(temp_sig, temp_sig_len);
			SNMP_FREE(temp_sig);
			DEBUGMSGTL(("usm","Signing lengths failed.\n"));
			usm_free_usmStateReference (secStateRef);
			return SNMPERR_USM_AUTHENTICATIONFAILURE;
		}

		memcpy (&ptr[authParamsOffset], temp_sig, msgAuthParmLen);

		SNMP_ZERO(temp_sig, temp_sig_len);
		SNMP_FREE(temp_sig);

	}  /* endif -- create keyed hash */


	usm_free_usmStateReference (secStateRef);

	DEBUGMSGTL(("usm","USM processing completed.\n"));
	
	return SNMPERR_SUCCESS;

}  /* end usm_generate_out_msg() */




/*******************************************************************-o-******
 * usm_parse_security_parameters
 *
 * Parameters:
 *	(See list below...)
 *      
 * Returns:
 *	0	On success,
 *	-1	Otherwise.
 *
 *	tab stop 4
 *
 *	Extracts values from the security header and data portions of the
 *	incoming buffer.
 */
int
usm_parse_security_parameters (
	u_char  *secParams,
	size_t   remaining,
	u_char  *secEngineID,
	size_t  *secEngineIDLen,
	u_int   *boots_uint,
	u_int   *time_uint,
	char    *secName,
	size_t  *secNameLen,
	u_char  *signature,
	size_t  *signature_length,
	u_char  *salt,
	size_t  *salt_length,
	u_char **data_ptr)
{
	u_char  *parse_ptr = secParams;
	u_char  *value_ptr;
	u_char  *next_ptr;
	u_char   type_value;

	size_t   octet_string_length = remaining;
	size_t   sequence_length;
	size_t   remaining_bytes;

	long     boots_long;
	long     time_long;

	u_int    origNameLen;


	/* 
	 * Eat the first octet header.
	 */
	if ((value_ptr = asn_parse_sequence (parse_ptr, &octet_string_length,
		&type_value,
		(ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR),
		"usm first octet")) == NULL)
	{
		/* RETURN parse error */ return -1;
	}


	/* 
	 * Eat the sequence header.
	 */
	parse_ptr 	= value_ptr;
	sequence_length = octet_string_length;

	if ((value_ptr = asn_parse_sequence (parse_ptr, &sequence_length,
		&type_value,
		(ASN_SEQUENCE | ASN_CONSTRUCTOR),
		"usm sequence")) == NULL)
	{
		/* RETURN parse error */ return -1;
	}


	/*
	 * Retrieve the engineID.
	 */
	parse_ptr 	= value_ptr;
	remaining_bytes = sequence_length;

        DEBUGDUMPHEADER("dump_recv", "Parsing msgAuthoritativeEngineID\n");
	if ( (next_ptr
		= asn_parse_string (parse_ptr, &remaining_bytes, &type_value,
			secEngineID, secEngineIDLen)) == NULL )
	{
		DEBUGINDENTLESS();
                /* RETURN parse error */ return -1;
	}
        DEBUGINDENTLESS();

	if (type_value != (u_char) (ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR))
	{
		/* RETURN parse error */ return -1;
	}


	/* 
	 * Retrieve the engine boots, notice switch in the way next_ptr and
	 * remaining_bytes are used (to accomodate the asn code).
	 */
        DEBUGDUMPHEADER("dump_recv", "Parsing msgAuthoritativeEngineBoots\n");
	if ((next_ptr = asn_parse_int (next_ptr, &remaining_bytes, &type_value,
		&boots_long, sizeof(long))) == NULL)
	{
		DEBUGINDENTLESS();
                /* RETURN parse error */ return -1;
	}
        DEBUGINDENTLESS();

	if (type_value != (u_char) (ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_INTEGER))
	{
		DEBUGINDENTLESS();
                /* RETURN parse error */ return -1;
	}

	*boots_uint = (u_int) boots_long;


	/* 
	 * Retrieve the time value.
	 */
        DEBUGDUMPHEADER("dump_recv", "Parsing msgAuthoritativeEngineTime\n");
	if ((next_ptr = asn_parse_int (next_ptr, &remaining_bytes, &type_value,
		&time_long, sizeof(long))) == NULL)
	{
		/* RETURN parse error */ return -1;
	}

	if (type_value != (u_char) (ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_INTEGER))
	{
		  DEBUGINDENTLESS();
                  /* RETURN parse error */ return -1;
	}

	*time_uint = (u_int) time_long;


	/* 
	 * Retrieve the secName.
	 */
	origNameLen = *secNameLen;

        DEBUGDUMPHEADER("dump_recv", "Parsing msgUserName\n");
	if ( (next_ptr
		= asn_parse_string (next_ptr, &remaining_bytes, &type_value,
			(u_char *)secName, secNameLen)) == NULL )
	{
                DEBUGINDENTLESS();
		/* RETURN parse error */ return -1;
	}
        DEBUGINDENTLESS();

	/* FIX -- doesn't this also indicate a buffer overrun?
	 */
	if ((int)origNameLen < *secNameLen + 1)
	{
		/* RETURN parse error, but it's really a parameter error */
		return -1;
	}

	secName[*secNameLen] = '\0';

	if (type_value != (u_char) (ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR))
	{
		/* RETURN parse error */ return -1;
	}


	/* 
	 * Retrieve the signature and blank it if there.
	 */
        DEBUGDUMPHEADER("dump_recv", "Parsing msgAuthenticationParameters\n");
	if ( (next_ptr
		= asn_parse_string (next_ptr, &remaining_bytes, &type_value,
			signature, signature_length)) == NULL )
	{
                DEBUGINDENTLESS();
		/* RETURN parse error */ return -1;
	}
        DEBUGINDENTLESS();

	if (type_value != (u_char) (ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR))
	{
		/* RETURN parse error */ return -1;
	}

	if (*signature_length != 0) /* Blanking for authentication step later */
	{
		memset (next_ptr-(u_long)*signature_length,
						0, *signature_length);
	}


	/* 
	 * Retrieve the salt.
	 *
	 * Note that the next ptr is where the data section starts.
	 */
        DEBUGDUMPHEADER("dump_recv", "Parsing msgPrivacyParameters\n");
	if ( (*data_ptr
		= asn_parse_string (next_ptr, &remaining_bytes, &type_value,
			salt, salt_length)) == NULL )
	{
		DEBUGINDENTLESS();
                /* RETURN parse error */ return -1;
	}
        DEBUGINDENTLESS();

	if (type_value != (u_char) (ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR))
	{
		/* RETURN parse error */ return -1;
	}

	return 0;

}  /* end usm_parse_security_parameters() */




/*******************************************************************-o-******
 * usm_check_and_update_timeliness
 *
 * Parameters:
 *	*secEngineID
 *	 secEngineIDen
 *	 boots_uint
 *	 time_uint
 *	*error
 *      
 * Returns:
 *	0	On success,
 *	-1	Otherwise.
 *	
 *
 * Performs the incoming timeliness checking and setting.
 */
int
usm_check_and_update_timeliness(
	u_char *secEngineID,
	size_t  secEngineIDLen,
	u_int   boots_uint,
	u_int   time_uint,
	int    *error)
{
	u_char	myID[USM_MAX_ID_LENGTH];
	int	myIDLength = snmpv3_get_engineID(myID, USM_MAX_ID_LENGTH);
	u_int	myBoots;
	u_int	myTime;



	if ( (myIDLength > USM_MAX_ID_LENGTH) || (myIDLength < 0) )
	{
		/* We're probably already screwed...buffer overwrite.  XXX? */
		DEBUGMSGTL(("usm","Buffer overflow.\n"));
		*error = SNMPERR_USM_GENERICERROR;
		return -1;
	}

	myBoots = snmpv3_local_snmpEngineBoots();
	myTime  = snmpv3_local_snmpEngineTime();


        /*
         * IF the time involved is local
	 *     Make sure  message is inside the time window 
         * ELSE 
         *      IF boots is higher or boots is the same and time is higher
         *              remember this new data
         *      ELSE
         *      	IF !(boots same and time within USM_TIME_WINDOW secs)
         *          		Message is too old 
         *      	ELSE    
         *          		Message is ok, but don't take time
	 *		ENDIF
	 *	ENDIF
	 * ENDIF
         */

	/*
	 * This is a local reference.
	 */
	if ( (int)secEngineIDLen == myIDLength
		&& memcmp (secEngineID, myID, myIDLength) == 0 )
	{
		u_int time_difference = myTime > time_uint ?
			myTime - time_uint : time_uint - myTime;

		if (boots_uint == ENGINEBOOT_MAX 
			|| boots_uint != myBoots
			|| time_difference > USM_TIME_WINDOW) 
		{
			if ( snmp_increment_statistic(
					STAT_USMSTATSNOTINTIMEWINDOWS) == 0 )
			{
				DEBUGMSGTL(("usm","%s\n",
					"Failed to increment statistic."));
			}

			DEBUGMSGTL(("usm","%s\n", "Not in local time window."));
			*error = SNMPERR_USM_NOTINTIMEWINDOW;
			return -1;
		}

		*error = SNMPERR_SUCCESS;
		return 0;
	}

	/* 
	 * This is a remote reference.
	 */
	else
	{
		u_int	theirBoots,
			theirTime,
			theirLastTime;
		u_int	time_difference;

		if ( get_enginetime_ex(	secEngineID,	secEngineIDLen,
					&theirBoots,	&theirTime,
					&theirLastTime,
					TRUE)
							!= SNMPERR_SUCCESS)
		{
			DEBUGMSGTL(("usm","%s\n",
				"Failed to get remote engine's times."));

			*error = SNMPERR_USM_GENERICERROR;
			return -1;
		}

		time_difference = theirTime > time_uint ?
			theirTime - time_uint : time_uint - theirTime;


		/* 
		 * XXX	Contrary to the pseudocode:
		 *	See if boots is invalid first.
		 */
		if (theirBoots == ENGINEBOOT_MAX || theirBoots > boots_uint)
		{
			DEBUGMSGTL(("usm","%s\n", "Remote boot count invalid."));

			*error = SNMPERR_USM_NOTINTIMEWINDOW;
			return -1;
		}


		/* 
		 * Boots is ok, see if the boots is the same but the time
		 * is old.
		 */
		if (theirBoots == boots_uint && time_uint < theirLastTime)
		{
			if(time_difference > USM_TIME_WINDOW)
			{
				DEBUGMSGTL(("usm","%s\n", "Message too old."));
				*error = SNMPERR_USM_NOTINTIMEWINDOW;
				return -1;
			}

			else		/* Old, but acceptable */
			{
				*error = SNMPERR_SUCCESS;
				return 0;
			}
		}


		/*
			Message is ok, either boots has been advanced, or
			time is greater than before with the same boots.
		*/

		if ( set_enginetime(	secEngineID,	secEngineIDLen,
					boots_uint,	time_uint,
					TRUE)
							!= SNMPERR_SUCCESS)
		{
			DEBUGMSGTL(("usm","%s\n", "Failed updating remote boot/time."));
			*error = SNMPERR_USM_GENERICERROR;
			return -1;
		}

		*error = SNMPERR_SUCCESS;
		return 0;		/* Fresh message and time updated */

	}  /* endif -- local or remote time reference. */


}  /* end usm_check_and_update_timeliness() */




/*******************************************************************-o-******
 * usm_process_in_msg
 *
 * Parameters:
 *	(See list below...)
 *      
 * Returns:
 *	SNMPERR_SUCCESS			On success.
 *	SNMPERR_USM_AUTHENTICATIONFAILURE
 *	SNMPERR_USM_DECRYPTIONERROR
 *	SNMPERR_USM_GENERICERROR
 *	SNMPERR_USM_PARSEERROR
 *	SNMPERR_USM_UNKNOWNENGINEID
 *	SNMPERR_USM_PARSEERROR
 *	SNMPERR_USM_UNKNOWNSECURITYNAME
 *	SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL
 *
 *
 * ASSUMES size of decrypt_buf will always be >= size of encrypted sPDU.
 *
 * FIX  Memory leaks if secStateRef is allocated and a return occurs
 *	without cleaning up.  May contain secrets...
 */
int
usm_process_in_msg (
	int      msgProcModel,	   /* (UNUSED) */
	size_t   maxMsgSize,	   /* IN     - Used to calc maxSizeResponse.  */

	u_char  *secParams,	   /* IN     - BER encoded securityParameters.*/
	int      secModel,	   /* (UNUSED) */
	int      secLevel,	   /* IN     - AuthNoPriv, authPriv etc.      */

	u_char  *wholeMsg,	   /* IN     - Original v3 message.           */
	size_t   wholeMsgLen,	   /* IN     - Msg length.                    */

	u_char  *secEngineID,	   /* OUT    - Pointer snmpEngineID.          */
	size_t  *secEngineIDLen,   /* IN/OUT - Len available, len returned.   */
	                           /*   NOTE: Memory provided by caller.      */

	char *secName,             /* OUT    - Pointer to securityName.       */
	size_t  *secNameLen,	   /* IN/OUT - Len available, len returned.   */

	u_char **scopedPdu,        /* OUT    - Pointer to plaintext scopedPdu.*/
	size_t  *scopedPduLen,	   /* IN/OUT - Len available, len returned.   */

	size_t  *maxSizeResponse,  /* OUT    - Max size of Response PDU.      */
	void   **secStateRf)	   /* OUT    - Ref to security state.         */
{
	size_t   remaining = wholeMsgLen
				- (u_int)
					((u_long)*secParams-(u_long)*wholeMsg);
	u_int   boots_uint;
	u_int   time_uint;
	u_char  signature[BYTESIZE(USM_MAX_KEYEDHASH_LENGTH)];
	size_t  signature_length = BYTESIZE(USM_MAX_KEYEDHASH_LENGTH);
	u_char  salt[BYTESIZE(USM_MAX_SALT_LENGTH)];
	size_t  salt_length = BYTESIZE(USM_MAX_SALT_LENGTH);
	u_char  iv[BYTESIZE(USM_MAX_SALT_LENGTH)];
	u_int   iv_length = BYTESIZE(USM_MAX_SALT_LENGTH);
	u_char *data_ptr;
	u_char *value_ptr;
	u_char  type_value;
	u_char *end_of_overhead;
	int     error;
        int     i;
	struct usmStateReference **secStateRef = (struct usmStateReference **)secStateRf;

	struct usmUser *user;


	DEBUGMSGTL(("usm","USM processing begun...\n"));


	if (secStateRef)		/* FIX -- huh?  destroy it? */
	{
		*secStateRef = usm_malloc_usmStateReference();
		if (*secStateRef == NULL)
		{
			DEBUGMSGTL(("usm", "Out of memory.\n"));
			return SNMPERR_USM_GENERICERROR;
		}
	}


	/* 
	 * Make sure the *secParms is an OCTET STRING.
	 * Extract the user name, engine ID, and security level.
	 */
	if ( usm_parse_security_parameters (
		 secParams,		 remaining,
		 secEngineID,		 secEngineIDLen,
		&boots_uint,		&time_uint,
		 secName,		 secNameLen,
		 signature,		&signature_length,
		 salt,			&salt_length,
		 &data_ptr)
			== -1 )
	{
		DEBUGMSGTL(("usm","Parsing failed.\n"));
		if (snmp_increment_statistic (STAT_SNMPINASNPARSEERRS)==0)
		{
			DEBUGMSGTL(("usm","%s\n", "Failed to increment statistic."));
		}
		return SNMPERR_USM_PARSEERROR;
	}


	if (secStateRef)
	{
		/* Cache the name, engine ID, and security level,
		 * per step 2 (section 3.2)
		 */
		if ( usm_set_usmStateReference_name (
				*secStateRef, secName, *secNameLen) == -1 )
		{
			DEBUGMSGTL(("usm","%s\n", "Couldn't cache name."));
			return SNMPERR_USM_GENERICERROR;
		}

		if ( usm_set_usmStateReference_engine_id (
			*secStateRef, secEngineID, *secEngineIDLen) == -1 )
		{
			DEBUGMSGTL(("usm","%s\n", "Couldn't cache engine id."));
			return SNMPERR_USM_GENERICERROR;
		}

		if ( usm_set_usmStateReference_sec_level (
					*secStateRef, secLevel) == -1 )
		{
			DEBUGMSGTL(("usm","%s\n", "Couldn't cache security level."));
			return SNMPERR_USM_GENERICERROR;
		}
	}
	

	/* 
	 * Locate the engine ID record.
	 * If it is unknown, then either create one or note this as an error.
	 */
	if (reportErrorOnUnknownID)
	{
		if (ISENGINEKNOWN(secEngineID, *secEngineIDLen)==FALSE)
		{
			DEBUGMSGTL(("usm","Unknown Engine ID.\n"));
			if (snmp_increment_statistic (
					STAT_USMSTATSUNKNOWNENGINEIDS)==0)
			{
				DEBUGMSGTL(("usm","%s\n",
					"Failed to increment statistic."));
			}
			return SNMPERR_USM_UNKNOWNENGINEID;
		}
	}
	else
	{
		if ( ENSURE_ENGINE_RECORD(secEngineID,*secEngineIDLen)
							!= SNMPERR_SUCCESS )
		{
			DEBUGMSGTL(("usm","%s\n", "Couldn't ensure engine record."));
			return SNMPERR_USM_GENERICERROR;
		}
		
	}


	/* 
	 * Locate the User record.
	 * If the user/engine ID is unknown, report this as an error.
	 */
	if ( (user = 
		usm_get_user(secEngineID, *secEngineIDLen, secName))
			== NULL )
	{
		DEBUGMSGTL(("usm","Unknown User.\n"));
		if (snmp_increment_statistic (STAT_USMSTATSUNKNOWNUSERNAMES)==0)
		{
			DEBUGMSGTL(("usm","%s\n", "Failed to increment statistic."));
		}
		return SNMPERR_USM_UNKNOWNSECURITYNAME;
	}


	/* 
	 * Make sure the security level is appropriate.
	 */
	if (usm_check_secLevel(secLevel, user) == 1)
	{
		DEBUGMSGTL(("usm","Unsupported Security Level.\n"));
		if (snmp_increment_statistic
					(STAT_USMSTATSUNSUPPORTEDSECLEVELS)==0)
		{
			DEBUGMSGTL(("usm","%s\n", "Failed to increment statistic."));
		}
		return SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL;
	}


	/* 
	 * Check the authentication credentials of the message.
	 */
	if (secLevel == SNMP_SEC_LEVEL_AUTHNOPRIV
		|| secLevel == SNMP_SEC_LEVEL_AUTHPRIV)
	{
		if ( sc_check_keyed_hash (
			user->authProtocol,	user->authProtocolLen,
			user->authKey,		user->authKeyLen,
			wholeMsg,		wholeMsgLen,
			signature,		signature_length)
							!= SNMP_ERR_NOERROR )
		{
			DEBUGMSGTL(("usm","Verification failed.\n"));
			if (snmp_increment_statistic
					(STAT_USMSTATSWRONGDIGESTS)==0)
			{
				DEBUGMSGTL(("usm","%s\n",
				    "Failed to increment statistic."));
			}
			return SNMPERR_USM_AUTHENTICATIONFAILURE;
		}

		DEBUGMSGTL(("usm","Verification succeeded.\n"));
	}


	/* 
	 * Steps 10-11  user is already set - relocated before timeliness 
	 * check in case it fails - still save user data for response.
	 *
	 * Cache the keys and protocol oids, per step 11 (s3.2).
	 */
	if (secStateRef)
	{
		if (usm_set_usmStateReference_auth_protocol (*secStateRef,
			user->authProtocol, user->authProtocolLen) ==-1)
		{
			DEBUGMSGTL(("usm","%s\n",
				"Couldn't cache authentication protocol."));
			return SNMPERR_USM_GENERICERROR;
		}

		if (usm_set_usmStateReference_auth_key (*secStateRef,
			user->authKey, user->authKeyLen) == -1)
		{
			DEBUGMSGTL(("usm","%s\n", "Couldn't cache authentiation key."));
			return SNMPERR_USM_GENERICERROR;
		}

		if (usm_set_usmStateReference_priv_protocol (*secStateRef,
			user->privProtocol, user->privProtocolLen) ==-1)
		{
			DEBUGMSGTL(("usm","%s\n", "Couldn't cache privacy protocol."));
			return SNMPERR_USM_GENERICERROR;
		}

		if (usm_set_usmStateReference_priv_key (*secStateRef,
			user->privKey, user->privKeyLen) == -1)
		{
			DEBUGMSGTL(("usm","%s\n", "Couldn't cache privacy key."));
			return SNMPERR_USM_GENERICERROR;
		}
	}


	/* 
	 * Perform the timeliness/time manager functions.
	 */
	if (secLevel == SNMP_SEC_LEVEL_AUTHNOPRIV
			|| secLevel == SNMP_SEC_LEVEL_AUTHPRIV)
	{
		if ( usm_check_and_update_timeliness (
			secEngineID, *secEngineIDLen,
			boots_uint, time_uint, &error) == -1 )
		{
			return error;
		}
	}

#ifdef							LCD_TIME_SYNC_OPT	
	/* 
	 * Cache the unauthenticated time to use in case we don't have
	 * anything better - this guess will be no worse than (0,0)
	 * that we normally use.
	 */
        else 
        {
		set_enginetime(secEngineID, *secEngineIDLen, 
			 			boots_uint, time_uint, FALSE);
        }
#endif							/* LCD_TIME_SYNC_OPT */


	/* 
	 * If needed, decrypt the scoped PDU.
	 */
	if (secLevel == SNMP_SEC_LEVEL_AUTHPRIV)
	{
		remaining = wholeMsgLen - (data_ptr - wholeMsg);

		if ((value_ptr = asn_parse_sequence (data_ptr, &remaining,
			&type_value,
			(ASN_UNIVERSAL|ASN_PRIMITIVE|ASN_OCTET_STR),
			"encrypted sPDU")) == NULL)
		{
			DEBUGMSGTL(("usm","%s\n",
				"Failed while parsing encrypted sPDU."));
			if (snmp_increment_statistic
						(STAT_SNMPINASNPARSEERRS)==0)
			{
				DEBUGMSGTL(("usm","%s\n",
					"Failed increment statistic."));
			}
			return SNMPERR_USM_PARSEERROR;
		}

		end_of_overhead = value_ptr;

                /* 
                 * XOR the salt with the last (iv_length) bytes
                 * of the priv_key to obtain the IV.
                 */
                for (i = 0; i < (int)iv_length; i++)
                  iv[i] = salt[i] ^ user->privKey[iv_length + i];
                
		if (sc_decrypt (
			 user->privProtocol,	user->privProtocolLen,
			 user->privKey,		user->privKeyLen,
			 iv,			iv_length,
			 value_ptr,		remaining,
			*scopedPdu,		scopedPduLen) 
							!= SNMP_ERR_NOERROR)
		{
			DEBUGMSGTL(("usm","%s\n", "Failed decryption."));
			if (snmp_increment_statistic
					(STAT_USMSTATSDECRYPTIONERRORS)==0)
			{
				DEBUGMSGTL(("usm","%s\n",
					"Failed increment statistic."));
			}
			return SNMPERR_USM_DECRYPTIONERROR;
		}

#ifdef SNMP_TESTING_CODE
		if ( debug_is_token_registered("usm/dump") == SNMPERR_SUCCESS) {
			dump_chunk("usm/dump", "Decrypted chunk:",
						*scopedPdu, *scopedPduLen);
			dump_chunk("usm/dump", "IV + Encrypted form:",
						salt, salt_length);
			dump_chunk("usm/dump", NULL,
						value_ptr, remaining);
		}
#endif
	}

	/* 
	 * sPDU is plaintext.
	 */
	else
	{
		*scopedPdu	= data_ptr;
		*scopedPduLen	= wholeMsgLen - (data_ptr - wholeMsg);
		end_of_overhead	= data_ptr;

	}  /* endif -- PDU decryption */


	/* 
	 * Calculate the biggest sPDU for the response (i.e., whole - ovrhd).
	 *
	 * FIX  Correct? 
	 */
	*maxSizeResponse = maxMsgSize - (int)
				((u_long)end_of_overhead - (u_long)wholeMsg);


	DEBUGMSGTL(("usm","USM processing completed.\n"));

	return SNMPERR_SUCCESS;

}  /* end usm_process_in_msg() */

void
init_usm(void) {
  snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_POST_READ_CONFIG,
                         init_usm_post_config, NULL);
}

/* 
 * initializations for the USM.
 *
 * Should be called after the configuration files have been read.
 *
 * Set "arbitrary" portion of salt to a random number.
 */
int
init_usm_post_config(int majorid, int minorid, void *serverarg,
                     void *clientarg) {
  size_t	salt_integer_len = sizeof(salt_integer);

  initialUser = usm_create_initial_user("initial", usmHMACMD5AuthProtocol,
                                        USM_LENGTH_OID_TRANSFORM,
                                        usmDESPrivProtocol,
                                        USM_LENGTH_OID_TRANSFORM);
  SNMP_FREE(initialUser->engineID);
  initialUser->engineIDLen = 0;

  if ( sc_random((u_char *) &salt_integer, &salt_integer_len) != SNMPERR_SUCCESS )
  {
	DEBUGMSGTL(("usm","sc_random() failed: using time() as salt.\n"));
	salt_integer	 = (u_int) time(NULL);
	salt_integer_len = sizeof(salt_integer);
  }

  noNameUser = usm_create_initial_user("", usmHMACMD5AuthProtocol,
                                        USM_LENGTH_OID_TRANSFORM,
                                        usmDESPrivProtocol,
                                        USM_LENGTH_OID_TRANSFORM);
  SNMP_FREE(noNameUser->engineID);
  noNameUser->engineIDLen = 0;

  return SNMPERR_SUCCESS;
}  /* end init_usm_post_config() */
 

/* 
 * Local storage (LCD) of the default user list.
 */
static struct usmUser *userList=NULL;

struct usmUser *
usm_get_userList(void)
{
  return userList;
}



/*******************************************************************-o-******
 * usm_check_secLevel
 *
 * Parameters:
 *	 level
 *	*user
 *      
 * Returns:
 *	0	On success,
 *	-1	Otherwise.
 *
 * Checks that a given security level is valid for a given user.
 */
int
usm_check_secLevel(int level, struct usmUser *user)
{

  if ( level == SNMP_SEC_LEVEL_AUTHPRIV
	&& (snmp_oid_compare(user->privProtocol, user->privProtocolLen,
		usmNoPrivProtocol, sizeof(usmNoPrivProtocol)/sizeof(oid))==0) )
  {
    return 1;
  } 
  if ( (level == SNMP_SEC_LEVEL_AUTHPRIV || level == SNMP_SEC_LEVEL_AUTHNOPRIV)
	&& (snmp_oid_compare(user->authProtocol, user->authProtocolLen,
		usmNoAuthProtocol, sizeof(usmNoAuthProtocol)/sizeof(oid))==0) )
  {
    return 1;
  }

  return 0;

}  /* end usm_check_secLevel() */




/*******************************************************************-o-******
 * usm_check_secLevel_vs_protocols
 *
 * Parameters:
 *	 level
 *	*authProtocol
 *	 authProtocolLen
 *	*privProtocol
 *	 privProtocolLen
 *      
 * Returns:
 *	0	On success,
 *	-1	Otherwise.
 *
 * Same as above but with explicitly named transform types instead of taking
 * from the usmUser structure.
 */
int
usm_check_secLevel_vs_protocols(int level,
	oid *authProtocol, u_int authProtocolLen,
	oid *privProtocol, u_int privProtocolLen)
{

  if ( level == SNMP_SEC_LEVEL_AUTHPRIV
	&& (snmp_oid_compare(privProtocol, privProtocolLen, usmNoPrivProtocol,
              			sizeof(usmNoPrivProtocol)/sizeof(oid))==0) )
  {
    return 1;
  }
  if ( (level == SNMP_SEC_LEVEL_AUTHPRIV || level == SNMP_SEC_LEVEL_AUTHNOPRIV)
	&& (snmp_oid_compare(authProtocol, authProtocolLen, usmNoAuthProtocol,
              			sizeof(usmNoAuthProtocol)/sizeof(oid))==0) )
  {
    return 1;
  }

  return 0;

}  /* end usm_check_secLevel_vs_protocols() */


/* usm_update_engine_time(): Updates engine_time for all registered users. 
 * This function would be useful for systems that start up with default time
 * settings and then update their timing reference using NTP at a later stage
 */
void usm_update_engine_time(void) {

  struct usmUser *ptr;
  long            boots_long;
  long            time_long;

  boots_long = snmpv3_local_snmpEngineBoots();
  time_long  = snmpv3_local_snmpEngineTime();

  for (ptr = userList; ptr != NULL; ptr = ptr->next) {
    set_enginetime( ptr->engineID, ptr->engineIDLen, 
                    boots_long, time_long, TRUE );
  }
}


/* usm_get_user(): Returns a user from userList based on the engineID,
   engineIDLen and name of the requested user. */

struct usmUser *
usm_get_user(u_char *engineID, size_t engineIDLen, char *name)
{
  DEBUGMSGTL(("usm","getting user %s\n", name));
  return usm_get_user_from_list(engineID, engineIDLen, name, userList, 1);
}

struct usmUser *
usm_get_user_from_list(u_char *engineID, size_t engineIDLen,
                       char *name, struct usmUser *puserList, int use_default)
{
  struct usmUser *ptr;
  char noName[] = "";
  if (name == NULL)
    name = noName;
  for (ptr = puserList; ptr != NULL; ptr = ptr->next) {
    if (!strcmp(ptr->name, name) &&
        ptr->engineIDLen == engineIDLen &&
        ((ptr->engineID == NULL && engineID == NULL) ||
         (ptr->engineID != NULL && engineID != NULL &&
          memcmp(ptr->engineID, engineID, engineIDLen) == 0)))
      return ptr;
  }
  /* return "" user used to facilitate engineID discovery */
  if (use_default && !strcmp(name, "")) return noNameUser;
  /* this next line may be vestigial from when the draft used 'initial'
     to discover engineID, also did not remove creation if 'inital' user
     -gsm 2/6/99 */
  if (use_default && !strcmp(name, "initial")) return initialUser;
  return NULL;
}

/* usm_add_user(): Add's a user to the userList, sorted by the
   engineIDLength then the engineID then the name length then the name
   to facilitate getNext calls on a usmUser table which is indexed by
   these values.

   Note: userList must not be NULL (obviously), as thats a rather trivial
   addition and is left to the API user.

   returns the head of the list (which could change due to this add).
*/

struct usmUser *
usm_add_user(struct usmUser *user)
{
  struct usmUser *uptr;
  uptr = usm_add_user_to_list(user, userList);
  if (uptr != NULL)
    userList = uptr;
  return uptr;
}

struct usmUser *
usm_add_user_to_list(struct usmUser *user,
                                     struct usmUser *puserList)
{
  struct usmUser *nptr, *pptr;

  /* loop through puserList till we find the proper, sorted place to
     insert the new user */
  for (nptr = puserList, pptr = NULL; nptr != NULL;
       pptr = nptr, nptr = nptr->next) {
    if (nptr->engineIDLen > user->engineIDLen)
      break;

    if (user->engineID == NULL && nptr->engineID != NULL)
      break;
    
    if (nptr->engineIDLen == user->engineIDLen &&
        (nptr->engineID != NULL && user->engineID != NULL &&
         memcmp(nptr->engineID, user->engineID, user->engineIDLen) > 0))
      break;

    if (!(nptr->engineID == NULL && user->engineID != NULL)) {
      if (nptr->engineIDLen == user->engineIDLen &&
          ((nptr->engineID == NULL && user->engineID == NULL) ||
           memcmp(nptr->engineID, user->engineID, user->engineIDLen) == 0) &&
          strlen(nptr->name) > strlen(user->name))
        break;

      if (nptr->engineIDLen == user->engineIDLen &&
          ((nptr->engineID == NULL && user->engineID == NULL) ||
           memcmp(nptr->engineID, user->engineID, user->engineIDLen) == 0) &&
          strlen(nptr->name) == strlen(user->name) &&
          strcmp(nptr->name, user->name) > 0)
        break;

      if (nptr->engineIDLen == user->engineIDLen &&
          ((nptr->engineID == NULL && user->engineID == NULL) ||
           memcmp(nptr->engineID, user->engineID, user->engineIDLen) == 0) &&
          strlen(nptr->name) == strlen(user->name) &&
          strcmp(nptr->name, user->name) == 0)
        /* the user is an exact match of a previous entry.  Bail */
        return NULL;
    }
  }

  /* nptr should now point to the user that we need to add ourselves
     in front of, and pptr should be our new 'prev'. */

  /* change our pointers */
  user->prev = pptr;
  user->next = nptr;

  /* change the next's prev pointer */
  if (user->next)
    user->next->prev = user;

  /* change the prev's next pointer */
  if (user->prev)
    user->prev->next = user;

  /* rewind to the head of the list and return it (since the new head
     could be us, we need to notify the above routine who the head now is. */
  for(pptr = user; pptr->prev != NULL; pptr = pptr->prev);
  return pptr;
}

/* usm_remove_user(): finds and removes a user from a list */
struct usmUser *
usm_remove_user(struct usmUser *user)
{
  return usm_remove_user_from_list(user, &userList);
}

struct usmUser *
usm_remove_user_from_list(struct usmUser *user,
                                          struct usmUser **ppuserList)
{
  struct usmUser *nptr, *pptr;

  /* NULL pointers aren't allowed */
  if (ppuserList == NULL)
    return NULL;

  /* find the user in the list */
  for (nptr = *ppuserList, pptr = NULL; nptr != NULL;
       pptr = nptr, nptr = nptr->next) {
    if (nptr == user)
      break;
  }

  if (nptr) {
    /* remove the user from the linked list */
    if (pptr) {
      pptr->next = nptr->next;
    }
    if (nptr->next) {
      nptr->next->prev = pptr;
    }
  } else {
    /* user didn't exit */
    return NULL;
  }
  if (nptr == *ppuserList) /* we're the head of the list, need to change
                            the head to the next user */
    *ppuserList = nptr->next;
  return *ppuserList;
}  /* end usm_remove_user_from_list() */




/* usm_free_user():  calls free() on all needed parts of struct usmUser and
   the user himself.

   Note: This should *not* be called on an object in a list (IE,
   remove it from the list first, and set next and prev to NULL), but
   will try to reconnect the list pieces again if it is called this
   way.  If called on the head of the list, the entire list will be
   lost. */
struct usmUser *
usm_free_user(struct usmUser *user)
{
  if (user == NULL)
    return NULL;

  SNMP_FREE(user->engineID);
  SNMP_FREE(user->name);
  SNMP_FREE(user->secName);
  SNMP_FREE(user->cloneFrom);
  SNMP_FREE(user->userPublicString);
  SNMP_FREE(user->authProtocol);
  SNMP_FREE(user->privProtocol);

  if (user->authKey != NULL) {
    SNMP_ZERO(user->authKey, user->authKeyLen);
    SNMP_FREE(user->authKey);
  }

  if (user->privKey != NULL) {
    SNMP_ZERO(user->privKey, user->privKeyLen);
    SNMP_FREE(user->privKey);
  }


  /* FIX  Why not put this check *first?*
   */
  if (user->prev != NULL) { /* ack, this shouldn't happen */
    user->prev->next = user->next;
  }
  if (user->next != NULL) {
    user->next->prev = user->prev;
    if (user->prev != NULL) /* ack this is really bad, because it means
                              we'll loose the head of some structure tree */
      DEBUGMSGTL(("usm","Severe: Asked to free the head of a usmUser tree somewhere."));
  }


  SNMP_ZERO(user, sizeof(*user));
  SNMP_FREE(user);

  return NULL;  /* for convenience to returns from calling functions */

}  /* end usm_free_user() */




/* take a given user and clone the security info into another */
struct usmUser *
usm_cloneFrom_user(struct usmUser *from, struct usmUser *to)
{
  /* copy the authProtocol oid row pointer */
  SNMP_FREE(to->authProtocol);

  if ((to->authProtocol =
       snmp_duplicate_objid(from->authProtocol,from->authProtocolLen)) != NULL)
    to->authProtocolLen = from->authProtocolLen;
  else
    to->authProtocolLen = 0;


  /* copy the authKey */
  SNMP_FREE(to->authKey);

  if (from->authKeyLen > 0 &&
      (to->authKey = (u_char *) malloc(from->authKeyLen))
      != NULL) {
    to->authKeyLen = from->authKeyLen;
    memcpy(to->authKey, from->authKey, to->authKeyLen);
  } else {
    to->authKey = NULL;
    to->authKeyLen = 0;
  }


  /* copy the privProtocol oid row pointer */
  SNMP_FREE(to->privProtocol);

  if ((to->privProtocol =
       snmp_duplicate_objid(from->privProtocol,from->privProtocolLen)) != NULL)
    to->privProtocolLen = from->privProtocolLen;
  else
    to->privProtocolLen = 0;

  /* copy the privKey */
  SNMP_FREE(to->privKey);

  if (from->privKeyLen > 0 &&
      (to->privKey = (u_char *) malloc(from->privKeyLen))
      != NULL) {
    to->privKeyLen = from->privKeyLen;
    memcpy(to->privKey, from->privKey, to->privKeyLen);
  } else {
    to->privKey = NULL;
    to->privKeyLen = 0;
  }
  return to;
}

/* usm_create_user(void):
     create a default empty user, instantiating only the auth/priv
     protocols to noAuth and noPriv OID pointers
*/
struct usmUser *
usm_create_user(void)
{
  struct usmUser *newUser;

  /* create the new user */
  newUser = (struct usmUser *) calloc(1,sizeof(struct usmUser));
  if (newUser == NULL)
    return NULL;

  /* fill the auth/priv protocols */
  if ((newUser->authProtocol =
       snmp_duplicate_objid(usmNoAuthProtocol,
                            sizeof(usmNoAuthProtocol)/sizeof(oid))) == NULL)
    return usm_free_user(newUser);
  newUser->authProtocolLen = sizeof(usmNoAuthProtocol)/sizeof(oid);

  if ((newUser->privProtocol =
       snmp_duplicate_objid(usmNoPrivProtocol,
                            sizeof(usmNoPrivProtocol)/sizeof(oid))) == NULL)
    return usm_free_user(newUser);
  newUser->privProtocolLen = sizeof(usmNoPrivProtocol)/sizeof(oid);

  /* set the storage type to nonvolatile, and the status to ACTIVE */
  newUser->userStorageType = ST_NONVOLATILE;
  newUser->userStatus = RS_ACTIVE;
  return newUser;

}  /* end usm_clone_user() */




/* usm_create_initial_user(void):
   creates an initial user, filled with the defaults defined in the
   USM document.
*/
struct usmUser *
usm_create_initial_user(const char *name, oid *authProtocol, size_t authProtocolLen,
                        oid *privProtocol, size_t privProtocolLen)
{
  struct usmUser *newUser  = usm_create_user();
  if (newUser == NULL)
    return NULL;

  if ((newUser->name = strdup(name)) == NULL)
    return usm_free_user(newUser);

  if ((newUser->secName = strdup(name)) == NULL)
    return usm_free_user(newUser);

  if ((newUser->engineID = snmpv3_generate_engineID(&newUser->engineIDLen)) == NULL)
    return usm_free_user(newUser); 

  if ((newUser->cloneFrom = (oid *) malloc(sizeof(oid)*2)) == NULL)
    return usm_free_user(newUser);
  newUser->cloneFrom[0] = 0;
  newUser->cloneFrom[1] = 0;
  newUser->cloneFromLen = 2;

  SNMP_FREE(newUser->privProtocol);
  if ((newUser->privProtocol = (oid *) malloc(privProtocolLen*sizeof(oid)))
      == NULL)
    return usm_free_user(newUser);
  newUser->privProtocolLen = privProtocolLen;
  memcpy(newUser->privProtocol, privProtocol, privProtocolLen*sizeof(oid));

  SNMP_FREE(newUser->authProtocol);
  if ((newUser->authProtocol = (oid *) malloc(authProtocolLen*sizeof(oid)))
      == NULL)
    return usm_free_user(newUser);
  newUser->authProtocolLen = authProtocolLen;
  memcpy(newUser->authProtocol, authProtocol, authProtocolLen*sizeof(oid));

  newUser->userStatus = RS_ACTIVE;
  newUser->userStorageType = ST_READONLY;
  
  return newUser;
}

/* this is a callback that can store all known users based on a
   previously registered application ID */
int
usm_store_users(int majorID, int minorID, void *serverarg, void *clientarg)
{
  /* figure out our application name */
  char *appname = (char *) clientarg;
  if (appname == NULL)
      appname = ds_get_string(DS_LIBRARY_ID, DS_LIB_APPTYPE);

  /* save the user base */
  usm_save_users("usmUser", appname);

  /* never fails */
  return SNMPERR_SUCCESS;
}


/* usm_save_users(): saves a list of users to the persistent cache */
void
usm_save_users(const char *token, const char *type)
{
  usm_save_users_from_list(userList, token, type);
}

void
usm_save_users_from_list(struct usmUser *puserList, const char *token,
                         const char *type)
{
  struct usmUser *uptr;
  for (uptr = puserList; uptr != NULL; uptr = uptr->next) {
    if (uptr->userStorageType == ST_NONVOLATILE)
      usm_save_user(uptr, token, type);
  }
}

/* usm_save_user(): saves a user to the persistent cache */
void
usm_save_user(struct usmUser *user, const char *token, const char *type)
{
  char line[4096];
  char *cptr;

  memset(line, 0, sizeof(line));

  sprintf(line, "%s %d %d ", token, user->userStatus, user->userStorageType);
  cptr = &line[strlen(line)]; /* the NULL */
  cptr = read_config_save_octet_string(cptr, user->engineID, user->engineIDLen);
  *cptr++ = ' ';
  cptr = read_config_save_octet_string(cptr, (u_char *)user->name,
                                       (user->name == NULL) ? 0 :
                                       strlen(user->name)+1);
  *cptr++ = ' ';
  cptr = read_config_save_octet_string(cptr, (u_char *)user->secName,
                                       (user->secName == NULL) ? 0 :
                                       strlen(user->secName)+1);
  *cptr++ = ' ';
  cptr = read_config_save_objid(cptr, user->cloneFrom, user->cloneFromLen);
  *cptr++ = ' ';
  cptr = read_config_save_objid(cptr, user->authProtocol,
                                user->authProtocolLen);
  *cptr++ = ' ';
  cptr = read_config_save_octet_string(cptr, user->authKey, user->authKeyLen);
  *cptr++ = ' ';
  cptr = read_config_save_objid(cptr, user->privProtocol,
                                user->privProtocolLen);
  *cptr++ = ' ';
  cptr = read_config_save_octet_string(cptr, user->privKey, user->privKeyLen);
  *cptr++ = ' ';
  cptr = read_config_save_octet_string(cptr, user->userPublicString,
                                       (user->userPublicString == NULL) ? 0 :
                                       strlen((char *)user->userPublicString)+1);
  read_config_store(type, line);
}

/* usm_parse_user(): reads in a line containing a saved user profile
   and returns a pointer to a newly created struct usmUser. */
struct usmUser *
usm_read_user(char *line)
{
  struct usmUser *user;
  size_t len;

  user = usm_create_user();
  if (user == NULL)
    return NULL;
  
  user->userStatus = atoi(line);
  line = skip_token(line);
  user->userStorageType = atoi(line);
  line = skip_token(line);
  line = read_config_read_octet_string(line, &user->engineID,
                                       &user->engineIDLen);

  /* set the lcd entry for this engineID to the minimum boots/time
     values so that its a known engineid and won't return a report pdu.
     This is mostly important when receiving v3 traps so that the usm
     will at least continue processing them. */
  set_enginetime(user->engineID, user->engineIDLen, 1, 0, 0);

  line = read_config_read_octet_string(line, (u_char **)&user->name,
                                       &len);
  line = read_config_read_octet_string(line, (u_char **)&user->secName,
                                       &len);
  SNMP_FREE(user->cloneFrom);
  user->cloneFromLen = 0;

  line = read_config_read_objid(line, &user->cloneFrom, &user->cloneFromLen);

  SNMP_FREE(user->authProtocol);
  user->authProtocolLen = 0;

  line = read_config_read_objid(line, &user->authProtocol,
                                &user->authProtocolLen);
  line = read_config_read_octet_string(line, &user->authKey,
                                       &user->authKeyLen);
  SNMP_FREE(user->privProtocol);
  user->privProtocolLen = 0;

  line = read_config_read_objid(line, &user->privProtocol,
                                &user->privProtocolLen);
  line = read_config_read_octet_string(line, &user->privKey,
                                       &user->privKeyLen);
  line = read_config_read_octet_string(line, &user->userPublicString,
                                       &len);
  return user;
}

/* snmpd.conf parsing routines */
void
usm_parse_config_usmUser(const char *token, char *line)
{
  struct usmUser *uptr;

  uptr = usm_read_user(line);
  usm_add_user(uptr);
}




/*******************************************************************-o-******
 * usm_set_password
 *
 * Parameters:
 *	*token
 *	*line
 *      
 *
 * format: userSetAuthPass     secname engineIDLen engineID pass
 *     or: userSetPrivPass     secname engineIDLen engineID pass 
 *     or: userSetAuthKey      secname engineIDLen engineID KuLen Ku
 *     or: userSetPrivKey      secname engineIDLen engineID KuLen Ku 
 *     or: userSetAuthLocalKey secname engineIDLen engineID KulLen Kul
 *     or: userSetPrivLocalKey secname engineIDLen engineID KulLen Kul 
 *
 * type is:	1=passphrase; 2=Ku; 3=Kul.
 *
 *
 * ASSUMES  Passwords are null-terminated printable strings.
 */
void
usm_set_password(const char *token, char *line)
{
  char		 *cp;
  char		  nameBuf[SNMP_MAXBUF];
  u_char	 *engineID;
  size_t	  engineIDLen;
  struct usmUser *user;

  cp = copy_word(line, nameBuf);
  if (cp == NULL) {
    config_perror("invalid name specifier");
    return;
  }
    
  DEBUGMSGTL(("usm", "comparing: %s and %s\n", cp, WILDCARDSTRING));
  if (strncmp(cp, WILDCARDSTRING, strlen(WILDCARDSTRING)) == 0) {
    /* match against all engineIDs we know about */
    cp = skip_token(cp);
    for(user = userList; user != NULL; user = user->next) {
      if (strcmp(user->secName, nameBuf) == 0) {
        usm_set_user_password(user, token, cp);
      }
    }
  } else {
    cp = read_config_read_octet_string(cp, &engineID, &engineIDLen);
    if (cp == NULL) {
      config_perror("invalid engineID specifier");
      return;
    }

    user = usm_get_user(engineID, engineIDLen, nameBuf);
    if (user == NULL) {
      config_perror("not a valid user/engineID pair");
      return;
    }
    usm_set_user_password(user, token, cp);
  }
}

/* uses the rest of LINE to configure USER's password of type TOKEN */
void
usm_set_user_password(struct usmUser *user, const char *token, char *line)
{
  char	 *cp = line;
  u_char	 *engineID = user->engineID;
  size_t	  engineIDLen = user->engineIDLen;

  u_char	**key;
  size_t	 *keyLen;
  u_char	  userKey[SNMP_MAXBUF_SMALL];
  size_t	  userKeyLen = SNMP_MAXBUF_SMALL;
  int		  type, ret;

  /*
   * Retrieve the "old" key and set the key type.
   */
  if (strcmp(token, "userSetAuthPass") == 0) {
    key = &user->authKey;
    keyLen = &user->authKeyLen;
    type = 0;
  } else if (strcmp(token, "userSetPrivPass") == 0) {
    key = &user->privKey;
    keyLen = &user->privKeyLen;
    type = 0;
  } else if (strcmp(token, "userSetAuthKey") == 0) {
    key = &user->authKey;
    keyLen = &user->authKeyLen;
    type = 1;
  } else if (strcmp(token, "userSetPrivKey") == 0) {
    key = &user->privKey;
    keyLen = &user->privKeyLen;
    type = 1;
  } else if (strcmp(token, "userSetAuthLocalKey") == 0) {
    key = &user->authKey;
    keyLen = &user->authKeyLen;
    type = 2;
  } else if (strcmp(token, "userSetPrivLocalKey") == 0) {
    key = &user->privKey;
    keyLen = &user->privKeyLen;
    type = 2;
  } else {
    /* no old key, or token was not recognized */
    return;
  }

  if (*key) {
    /* (destroy and) free the old key */
    memset(*key, 0, *keyLen);
    free(*key);
  }

  if (type == 0) {
    /* convert the password into a key 
     */
    ret = generate_Ku(	user->authProtocol, user->authProtocolLen,
			(u_char *)cp, strlen(cp),
			userKey, &userKeyLen );
  
    if (ret != SNMPERR_SUCCESS) {
      config_perror("setting key failed (in sc_genKu())");
      return;
    }
  } else if (type == 1) {
    cp = read_config_read_octet_string(cp, (u_char **) &userKey, &userKeyLen);
    
    if (cp == NULL) {
      config_perror("invalid user key");
      return;
    }
  }
  
  if (type < 2) {
    *key = (u_char *)malloc(SNMP_MAXBUF_SMALL);
    *keyLen = SNMP_MAXBUF_SMALL;
    ret = generate_kul(	user->authProtocol, user->authProtocolLen,
			engineID, engineIDLen,
			userKey, userKeyLen,
			*key, keyLen );
    if (ret != SNMPERR_SUCCESS) {
      config_perror("setting key failed (in generate_kul())");
      return;
    }
  
    /* (destroy and) free the old key */
    memset(userKey, 0, sizeof(userKey));

  } else {
    /* the key is given, copy it in */
    cp = read_config_read_octet_string(cp, key, keyLen);
    
    if (cp == NULL) {
      config_perror("invalid localized user key");
      return;
    }
  }
}  /* end usm_set_password() */

#endif /* CYGPKG_SNMPAGENT_V3_SUPPORT */
