/*
 * tnmSnmpRecv.c --
 *
 *	This file contains all functions that decode a received SNMP
 *	packet and do the appropriate actions. 
 *
 * Copyright (c) 1994-1996 Technical University of Braunschweig.
 * Copyright (c) 1996-1997 University of Twente.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tnmSnmp.h"
#include "tnmMib.h"

/* 
 * Flag that controls hexdump. See the watch command for its use.
 */

extern int hexdump;

/*
 * A structure to keep the important parts of the message header 
 * while processing incoming SNMP messages.
 */

typedef struct Message {
    int version;
    int comLen;
    u_char *com;
    u_char *authDigest;
    int authDigestLen;
#ifdef TNM_SNMPv2U
    u_char qos;
    u_char agentID[USEC_MAX_AGENTID];
    u_int agentBoots;
    u_int agentTime;
    int userNameLen;
    char userName[USEC_MAX_USER];
    int cntxtLen;
    char cntxt[USEC_MAX_CONTEXT];
    int maxSize;
#endif
} Message;

/*
 * Forward declarations for procedures defined later in this file:
 */

static int
Authentic		_ANSI_ARGS_((TnmSnmp *session, Message *msg,
				     u_char *packet, int packetlen,
				     u_int **snmpStatPtr));

static int
DecodeMessage		_ANSI_ARGS_((Tcl_Interp	*interp, 
				     Message *msg, TnmSnmpPdu *pdu,
				     u_char *packet, int packetlen));

#ifdef TNM_SNMPv2U
static int
DecodeUsecParameter	_ANSI_ARGS_((Message *msg));

static void
SendUsecReport		_ANSI_ARGS_((Tcl_Interp *interp, 
				     TnmSnmp *session, 
				     struct sockaddr_in *to, 
				     int reqid, u_int *statPtr));
#endif

static int
DecodePDU		_ANSI_ARGS_((Tcl_Interp *interp, struct TnmSnmpPdu *pdu,
				     u_char *packet, int *packetlen));



/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpDecode --
 *
 *	This procedure decodes a complete SNMP packet and does 
 *	all required actions (mostly executing callbacks or doing 
 *	gets/sets in the agent module).
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TnmSnmpDecode(interp, packet, packetlen, from, session, reqid)
    Tcl_Interp *interp;
    u_char *packet;
    int	packetlen;
    struct sockaddr_in *from;
    TnmSnmp *session;
    int *reqid;
{
    TnmSnmpPdu _pdu, *pdu = &_pdu;
    Message _msg, *msg = &_msg;
    TnmSnmpRequest *request = NULL;
    int code, delivered = 0;

    if (reqid) {
	*reqid = 0;
    }
    memset((char *) msg, 0, sizeof(Message));
    Tcl_DStringInit(&pdu->varbind);
    pdu->addr = *from;

    /*
     * Extract the SNMP message header out of the packet.
     */

    if (*packet == (ASN1_UNIVERSAL | ASN1_CONSTRUCTED | ASN1_SEQUENCE)) {
	tnmSnmpStats.snmpInPkts++;
	code = DecodeMessage(interp, msg, pdu, packet, packetlen);
	if (code == TCL_ERROR) {
	    Tcl_DStringFree(&pdu->varbind);
	    return TCL_CONTINUE;
	}
    } else {
	tnmSnmpStats.snmpInBadVersions++;
	Tcl_SetResult(interp, "received packet with unknown SNMP version",
		      TCL_STATIC);
	Tcl_DStringFree(&pdu->varbind);
	return TCL_CONTINUE;
    }

    /*
     * Show the contents of the PDU - mostly for debugging.
     */

    TnmSnmpDumpPDU(interp, pdu);

#ifdef TNM_SNMPv2U
    /*
     * Do some sanity checks here to detect unfair players
     * that have the report flag set for PDUs that are not
     * allowed to have the report flag turned on. Clear the
     * flag if necessary.
     */

    if (msg->version == TNM_SNMPv2U
	&& msg->qos & USEC_QOS_REPORT
	&& pdu->type != TNM_SNMP_GET 
	&& pdu->type != TNM_SNMP_GETNEXT
	&& pdu->type != TNM_SNMP_GETBULK
	&& pdu->type != TNM_SNMP_SET) {
	    msg->qos &= ~ USEC_QOS_REPORT;
    }

    /*
     * First check for REPORT PDUs. They are used internally to handle
     * SNMPv2 time synchronization (aka maintenance functions).
     */

    if (msg->version == TNM_SNMPv2U && pdu->type == TNM_SNMP_REPORT) {
	TnmSnmp *s = session;
	time_t clock = time((time_t *) NULL);
	request = TnmSnmpFindRequest(pdu->request_id);
	if (request) {
	    s = request->session;
	}
	if (! s) {
	    Tcl_DStringFree(&pdu->varbind);
	    return TCL_CONTINUE;
	}

	TnmSnmpEvalBinding(interp, s, pdu, TNM_SNMP_RECV_EVENT);

	TnmSnmpEvalBinding(interp, s, pdu, TNM_SNMP_REPORT_EVENT);

	/*
	 * Accept new values for agentID, agentBoots and agentTime.
	 * Update the internal cache of agentIDs which makes also
	 * sure that localized authentication keys are re-computed.
	 */

	if (s->qos & USEC_QOS_AUTH) {

	    int update = 0;

	    /*
	     * XXX check the report type (must be usecStatsNotInWindows)
	     * and make sure that the digest is valid.
	     */

	    if (s->agentBoots < msg->agentBoots) {
		s->agentBoots = msg->agentBoots;
		update++;
	    }
	    if (s->agentTime != clock - msg->agentTime) {
		s->agentTime = clock - msg->agentTime;
		update++;
	    }

	    if (memcmp(s->agentID, msg->agentID, USEC_MAX_AGENTID) != 0) {
		memcpy(s->agentID, msg->agentID, USEC_MAX_AGENTID);
		update++;
	    }
	    
#ifdef DEBUG_USEC
	    fprintf(stderr, "check: agentBoots = %u agentTime = %u\n",
		    s->agentBoots, s->agentTime);
#endif

	    if (update) {
		TnmSnmpUsecSetAgentID(s);
	    }

	    /*
	     * Resend the original request (RFC 1910 section 3.2 step 12).
	     */
	    
	    if (request) {
		TnmSnmpUsecAuth(s, request->packet, request->packetlen);
		TnmSnmpDelay(s);
		TnmSnmpSend(interp, request->packet, request->packetlen,
			    &s->maddr, TNM_SNMP_ASYNC);
	    }
	}
	Tcl_DStringFree(&pdu->varbind);
	return TCL_BREAK;
    }
#endif

    /*
     * Next, handle RESPONSES as we should be able to find a session 
     * for the RESPONSE PDU. 
     */


    if (pdu->type == TNM_SNMP_RESPONSE) {

	tnmSnmpStats.snmpInGetResponses++;
    
	/* 
	 * Lookup the request for this response and evaluate the callback
	 * or return the result if we can not find an async request and
	 * we already have the session pointer.
	 */
	
	request = TnmSnmpFindRequest(pdu->request_id);

	if (! request) {
	    if (! session) {
		Tcl_DStringFree(&pdu->varbind);
		return TCL_CONTINUE;
	    }
	    
	    if (reqid) {
		*reqid = pdu->request_id;
	    }

	    if (! Authentic(session, msg, packet, packetlen, NULL)) {
		Tcl_SetResult(interp, "authentication failure", TCL_STATIC);
		Tcl_DStringFree(&pdu->varbind);
		return TCL_CONTINUE;
	    }

	    TnmSnmpEvalBinding(interp, session, pdu, TNM_SNMP_RECV_EVENT);
	    
	    if (pdu->error_status) {
		char buf[20], *name;
		name = TnmGetTableValue(tnmSnmpErrorTable, pdu->error_status);
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, name ? name : "unknown", " ", 
				 (char *) NULL);
		sprintf(buf, "%d ", pdu->error_index);
		Tcl_AppendResult(interp, buf, 
				  Tcl_DStringValue(&pdu->varbind),
				  (char *) NULL);
		Tcl_DStringFree(&pdu->varbind);
		return TCL_ERROR;
	    }
	    Tcl_ResetResult(interp);
	    Tcl_DStringResult(interp, &pdu->varbind);
	    return TCL_OK;

	} else {

	    session = request->session;

	    if (! Authentic(session, msg, packet, packetlen, NULL)) {
		Tcl_SetResult(interp, "authentication failure", TCL_STATIC);
		Tcl_DStringFree(&pdu->varbind);
		return TCL_CONTINUE;
	    }

#ifdef TNM_SNMP_BENCH
	    request->stats.recvSize = tnmSnmpBenchMark.recvSize;
	    request->stats.recvTime = tnmSnmpBenchMark.recvTime;
	    session->stats = request->stats;
#endif

	    TnmSnmpEvalBinding(interp, session, pdu, TNM_SNMP_RECV_EVENT);

	    /* 
	     * Evaluate the callback procedure after we have deleted
	     * the request structure. This strange order makes sure
	     * that calls to Tcl_DoOneEvent() during the callback do
	     * not process this request again.
	     */

	    Tcl_Preserve((ClientData) request);
	    Tcl_Preserve((ClientData) session);
	    TnmSnmpDeleteRequest(request);
	    if (request->proc) {
		(request->proc) (session, pdu, request->clientData);
	    }
	    Tcl_Release((ClientData) session);
	    Tcl_Release((ClientData) request);

	    /*
	     * Free response message structure.
	     */
	    
	    Tcl_DStringFree(&pdu->varbind);
	    return TCL_OK;
	}
    }


    for (session = tnmSnmpList; session; session = session->nextPtr) {

	TnmSnmpBinding *bindPtr = session->bindPtr;

	if (session->version != msg->version) continue;

	switch (pdu->type) {
	  case TNM_SNMPv1_TRAP: 
	    while (bindPtr && bindPtr->event != TNM_SNMP_TRAP_EVENT) {
		bindPtr = bindPtr->nextPtr;
	    }
	    if (session->version == TNM_SNMPv1 && bindPtr && bindPtr->command
		&& ((session->flags & TNM_SNMP_AGENT_ROLE) == 0)
		&& Authentic(session, msg, packet, packetlen, NULL)) {
		delivered++;
		TnmSnmpEvalCallback(interp, session, pdu, bindPtr->command,
				     NULL, NULL, NULL, NULL);
		tnmSnmpStats.snmpInTraps++;
	    }
	    break;
	  case TNM_SNMPv2_TRAP:
	    while (bindPtr && bindPtr->event != TNM_SNMP_TRAP_EVENT) {
		bindPtr = bindPtr->nextPtr;
	    }
	    if ((session->version & TNM_SNMPv2) && bindPtr && bindPtr->command
		&& ((session->flags & TNM_SNMP_AGENT_ROLE) == 0)
		&& Authentic(session, msg, packet, packetlen, NULL)) {
		delivered++;
		TnmSnmpEvalCallback(interp, session, pdu, bindPtr->command,
				     NULL, NULL, NULL, NULL);
		tnmSnmpStats.snmpInTraps++;
	    }
	    break;
	  case TNM_SNMP_INFORM:
	    while (bindPtr && bindPtr->event != TNM_SNMP_INFORM_EVENT) {
                bindPtr = bindPtr->nextPtr;
            }
	    if ((session->version & TNM_SNMPv2) && bindPtr && bindPtr->command
                && Authentic(session, msg, packet, packetlen, NULL)) {
                delivered++;
                TnmSnmpEvalCallback(interp, session, pdu, bindPtr->command, 
				    NULL, NULL, NULL, NULL);
		pdu->type = TNM_SNMP_RESPONSE;
		if (TnmSnmpEncode(interp, session, pdu, NULL, NULL)
		    != TCL_OK) {
		    Tcl_DStringFree(&pdu->varbind);
		    return TCL_ERROR;
		}
            }
	    break;
	  case TNM_SNMP_GETBULK:
	    if (session->version == TNM_SNMPv1) break;
	  case TNM_SNMP_GET:
	  case TNM_SNMP_GETNEXT:
	  case TNM_SNMP_SET: 
	    {
		u_int *statPtr;
		if ((session->flags & TNM_SNMP_AGENT_ROLE) == 0) break;
		if (Authentic(session, msg, packet, packetlen, &statPtr)) {
		    if (TnmSnmpAgentRequest(interp, session, pdu) != TCL_OK) {
			Tcl_DStringFree(&pdu->varbind);
			return TCL_ERROR;
		    }
		    delivered++;
		} else {
#ifdef TNM_SNMPv2U
		    if (session->version == TNM_SNMPv2U 
			&& msg->qos & USEC_QOS_REPORT) {
			SendUsecReport(interp, session, from, 
				       pdu->request_id, statPtr);
		    }
#endif
		}
	    }
	    break;
	}
    }

    if (! delivered && msg->version == TNM_SNMPv1) {
	tnmSnmpStats.snmpInBadCommunityNames++;
    }

    Tcl_DStringFree(&pdu->varbind);
    return TCL_CONTINUE;
}

/*
 *----------------------------------------------------------------------
 *
 * Authentic --
 *
 *	This procedure verifies the authentication information 
 *	contained in the message header against the information 
 *	associated with the given session handle.
 *
 * Results:
 *	1 on success and 0 if the authentication process failed.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
Authentic(session, msg, packet, packetlen, snmpStatPtr)
    TnmSnmp *session;
    Message *msg;
    u_char *packet;
    int packetlen;
    u_int **snmpStatPtr;
{
    u_char recvDigest[16], md5Digest[16];
    int authentic = 0;
    char *community = session->readCommunity;

    if (msg->version != session->version) {
	return authentic;
    }

    switch (msg->version) {
	
    case TNM_SNMPv1:
#ifdef TNM_SNMPv2C
    case TNM_SNMPv2C:
#endif
	authentic = ((strlen(community) == msg->comLen) &&
		     (memcmp(community, msg->com, msg->comLen) == 0));

	/*
	 * XXX
	 * This is not really correct since we should only check the
	 * writeCommunity string for responses to a set operations.
	 */

	if (! authentic && session->writeCommunity) {
	    community = session->writeCommunity;
	    authentic = ((strlen(community) == msg->comLen) &&
			 (memcmp(community, msg->com, msg->comLen) == 0));
	}
	break;

#ifdef TNM_SNMPv2U
    case TNM_SNMPv2U:
	
	authentic = 1;
	if (snmpStatPtr) {
	    *snmpStatPtr = NULL;
	}

	/*
	 * Check the user name (RFC 1910 section 3.2 step 7).
	 */

	if (msg->userNameLen != session->userNameLen ||
	    memcmp(msg->userName, session->userName, msg->userNameLen) != 0) {
	    tnmSnmpStats.usecStatsUnknownUserNames++;
	    if (snmpStatPtr) {
		*snmpStatPtr = &tnmSnmpStats.usecStatsUnknownUserNames;
	    }
	    authentic = 0;
	    break;
	}

	/*
	 * Check the qos (RFC 1910 section 3.2 step 8).
	 */

	if (msg->qos & USEC_QOS_PRIV 
	    || (msg->qos & ~USEC_QOS_REPORT) != session->qos) {
	    tnmSnmpStats.usecStatsUnsupportedQoS++;
	    if (snmpStatPtr) {
		*snmpStatPtr = &tnmSnmpStats.usecStatsUnsupportedQoS;
	    }
	    authentic = 0;
	    break;
	}

	/*
	 * Check if the context matches (RFC 1910 section 3.2 step 6)
	 */

	if (msg->cntxtLen != session->cntxtLen
            || memcmp(msg->cntxt, session->cntxt, msg->cntxtLen) != 0) {
	    tnmSnmpStats.usecStatsUnknownContexts++;
	    if (snmpStatPtr) {
		*snmpStatPtr = &tnmSnmpStats.usecStatsUnknownContexts;
	    }
            authentic = 0;
            break;
        }
	
	if (msg->qos & USEC_QOS_AUTH) {

	    /*
	     * Check if the agentID matches (RFC 1910 section 3.2 step 6)
	     */
	    
	    if (memcmp(msg->agentID, session->agentID,USEC_MAX_AGENTID) != 0) {
		tnmSnmpStats.usecStatsUnknownContexts++;
		if (snmpStatPtr) {
		    *snmpStatPtr = &tnmSnmpStats.usecStatsUnknownContexts;
		}
		authentic = 0;
		break;
	    }
	    
	    if (session->flags & TNM_SNMP_AGENT_ROLE) {
		int clock = time((time_t *) NULL) - session->agentTime;
		if (msg->agentBoots != session->agentBoots
		    || (int) msg->agentTime < clock - 150
		    || (int) msg->agentTime > clock + 150
		    || session->agentBoots == 0xffffffff) {
		    tnmSnmpStats.usecStatsNotInWindows++;
		    if (snmpStatPtr) {
			*snmpStatPtr = &tnmSnmpStats.usecStatsNotInWindows;
		    }
		    authentic = 0;
		    break;
		}
	    }

	    if (msg->authDigestLen != TNM_MD5_SIZE) {
		tnmSnmpStats.usecStatsWrongDigestValues++;
		if (snmpStatPtr) {
		    *snmpStatPtr = &tnmSnmpStats.usecStatsWrongDigestValues;
		}
		authentic = 0;
		break;
	    }

	    memcpy(recvDigest, msg->authDigest, TNM_MD5_SIZE);
	    memcpy(msg->authDigest, session->authKey, TNM_MD5_SIZE);
	    TnmSnmpMD5Digest(packet, packetlen, session->authKey, md5Digest);
	    if (memcmp(recvDigest, md5Digest, TNM_MD5_SIZE) != 0) {
#ifdef DEBUG_USEC
	        int i;

	        fprintf(stdout, "received digest:");
		for (i = 0; i < 16; i++) 
		    fprintf(stdout, " %02x", recvDigest[i]);
		fprintf(stdout,"\n");
#endif
		tnmSnmpStats.usecStatsWrongDigestValues++;
		if (snmpStatPtr) {
		    *snmpStatPtr = &tnmSnmpStats.usecStatsWrongDigestValues;
		}
		authentic = 0;
	    }
	    memcpy(msg->authDigest, recvDigest, TNM_MD5_SIZE);
	    break;
	}
#endif
    }

    return authentic;
}

/*
 *----------------------------------------------------------------------
 *
 * DecodeMessage --
 *
 *	This procedure takes a serialized packet and decodes the 
 *	SNMPv1 (RFC 1157) and SNMPv2 (RFC 1901) message header.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
DecodeMessage(interp, msg, pdu, packet, packetlen)
    Tcl_Interp *interp;
    Message *msg;
    TnmSnmpPdu *pdu;
    u_char *packet;
    int packetlen;
{
    int version;
    int buflen = 0;
    u_int msglen = 0;
    u_char *p = packet;
    
    /*
     * Decode "Packet Header" header ( SEQUENCE 0x30 msglen )
     */
    
    if (*p++ != (ASN1_UNIVERSAL | ASN1_CONSTRUCTED | ASN1_SEQUENCE)) {
	sprintf(interp->result,
		"Message header: invalid value 0x%.2x; expecting 0x%.2x",
		*--p, (ASN1_UNIVERSAL | ASN1_CONSTRUCTED | ASN1_SEQUENCE));
	tnmSnmpStats.snmpInASNParseErrs++;
	return TCL_ERROR;
    }
    buflen += 1;
    
    p = TnmBerDecLength(p, &buflen, &msglen);
    if (p == NULL) goto asn1Error;
    
    if ((buflen + msglen) != packetlen) {
	interp->result = "invalid length field in message header";
	return TCL_ERROR;
    }
    
    buflen = 0;		/* buflen from here must be the same as msglen */

    /*
     * Decode Version field of this message ( must be 0 for RFC1157, 1
     * for community based SNMPv2 or 2 for USEC ).
     */
    
    p = TnmBerDecInt(p, &buflen, ASN1_INTEGER, &version);
    if (p == NULL) goto asn1Error;

    switch (version) {
      case 0:
	msg->version = TNM_SNMPv1;
	break;
#ifdef TNM_SNMPv2C
      case 1:
	msg->version = TNM_SNMPv2C;
	break;
#endif
#ifdef TNM_SNMPv2U
      case 2:
	msg->version = TNM_SNMPv2U;
	break;
#endif
      default:
	tnmSnmpStats.snmpInBadVersions++;
	sprintf(interp->result,
		"received packet with unknown SNMP version %d", version);
        return TCL_ERROR;
    }
    
    /*
     * Decode "community" string.
     */

    if (*p != ASN1_OCTET_STRING) {
        sprintf(interp->result,
		"Parameter string: invalid value 0x%.2x; expecting 0x%.2x",
		*p, ASN1_OCTET_STRING);
	tnmSnmpStats.snmpInASNParseErrs++;
	return TCL_ERROR;
    }
    p = TnmBerDecOctetString(p, &buflen, ASN1_OCTET_STRING,
			     (char **) &msg->com, &msg->comLen);
    if (p == NULL) goto asn1Error;

#ifdef TNM_SNMPv2U
    if (version == 2) {
	if (DecodeUsecParameter(msg) != TCL_OK) {
	    Tcl_SetResult(interp, "encoding error in USEC parameter", 
			  TCL_STATIC);
	    return TCL_ERROR;
	}
    }
#endif

    /*
     * Decode PDU and validate total message length.
     */

    if (DecodePDU(interp, pdu, p, &buflen) != TCL_OK) {
	return TCL_ERROR;
    }

    if (buflen != msglen) {
	sprintf(interp->result,
		"Message sequence length (%d) differs from real length (%d).",
		buflen, (int) msglen);
	tnmSnmpStats.snmpInASNParseErrs++;
	return TCL_ERROR;
    }

    return TCL_OK;

  asn1Error:
    Tcl_SetResult(interp, TnmBerError(), TCL_STATIC);
    tnmSnmpStats.snmpInASNParseErrs++;
    return TCL_ERROR;
}
#ifdef TNM_SNMPv2U

/*
 *----------------------------------------------------------------------
 *
 * DecodeUsecParameter --
 *
 *	This procedure decodes the USEC parameter field and updates
 *	the message structure pointed to by msg. See RFC 1910 for
 *	details about the parameter field.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
DecodeUsecParameter(msg)
    Message *msg;
{
    u_char *p = msg->com;

    /*
     * Check the model number and get the qos value.
     */

    if (*p++ != USEC_MODEL) {
	tnmSnmpStats.usecStatsBadParameters++;
	return TCL_ERROR;
    }
    msg->qos = *p++;

    /*
     * Copy the agentID, the agentBoots and the agentTime into 
     * the message structure.
     */

    memcpy(msg->agentID, p, USEC_MAX_AGENTID);
    p += USEC_MAX_AGENTID;
	
    msg->agentBoots = *p++;
    msg->agentBoots = (msg->agentBoots << 8) + *p++;
    msg->agentBoots = (msg->agentBoots << 8) + *p++;
    msg->agentBoots = (msg->agentBoots << 8) + *p++;

    msg->agentTime = *p++;
    msg->agentTime = (msg->agentTime << 8) + *p++;
    msg->agentTime = (msg->agentTime << 8) + *p++;
    msg->agentTime = (msg->agentTime << 8) + *p++;

    msg->maxSize = *p++;
    msg->maxSize = (msg->maxSize << 8) + *p++;
    if (msg->maxSize < USEC_MIN_MMS || msg->maxSize > USEC_MAX_MMS) {
	tnmSnmpStats.usecStatsBadParameters++;
        return TCL_ERROR;
    }

#ifdef DEBUG_USEC
    fprintf(stderr, 
	    "decode: agentBoots = %u agentTime = %u maxSize = %u\n", 
	    msg->agentBoots, msg->agentTime, msg->maxSize);
#endif

    /*
     * Get the user name, the authentication digest, the max message 
     * size and finally the context identifier.
     */
	
    msg->userNameLen = *p++;
    if (msg->userNameLen > USEC_MAX_USER) {
	tnmSnmpStats.usecStatsBadParameters++;
	return TCL_ERROR;
    }
    memcpy(msg->userName, p, msg->userNameLen);
    p += msg->userNameLen;
    
    msg->authDigestLen = *p++;
    msg->authDigest = (u_char *) p;
    p += msg->authDigestLen;
    
    msg->cntxtLen = msg->comLen - (p - msg->com);
    if (msg->cntxtLen < 0 || msg->cntxtLen > USEC_MAX_CONTEXT) {
	tnmSnmpStats.usecStatsBadParameters++;
	return TCL_ERROR;
    }
    memcpy(msg->cntxt, p, msg->comLen - (p - msg->com));

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SendUsecReport --
 *
 *	This procedure sends a report PDU to let the receiver
 *	synchronize itself. Make sure that we never send a report PDU
 *	if we are not acting in an agent role. (We could get into nasty
 *	loops otherwise.) We create a copy of the session and adjust
 *	it to become the well-known usec user.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A report PDU is generated and send to the SNMP peer.
 *
 *----------------------------------------------------------------------
 */

static void
SendUsecReport(interp, session, to, reqid, statPtr)
    Tcl_Interp *interp;
    TnmSnmp *session;
    struct sockaddr_in *to;
    int reqid;
    u_int *statPtr;
{
    TnmSnmpPdu _pdu, *pdu = &_pdu;
    char varbind[80];
    int qos;

    /*
     * Make sure we are in an agent role and we actually have
     * something to report.
     */

    if (session->flags & TNM_SNMP_AGENT_ROLE || ! statPtr) {
	return;
    }

    /*
     * Create a report PDU and send it to the SNMP peer.
     */

    pdu->addr = *to;
    pdu->type = TNM_SNMP_REPORT;
    pdu->request_id = reqid;
    pdu->error_status = TNM_SNMP_NOERROR;
    pdu->error_index = 0;    
    pdu->trapOID = NULL;
    Tcl_DStringInit(&pdu->varbind);
    
    if (statPtr > &tnmSnmpStats.usecStatsUnsupportedQoS) {
	sprintf(varbind, "{1.3.6.1.6.3.6.1.2.%d %u}", 
	    statPtr - &tnmSnmpStats.usecStatsUnsupportedQoS + 1,  *statPtr);
    } else if (statPtr > &tnmSnmpStats.snmpStatsPackets) {
	sprintf(varbind, "{1.3.6.1.6.3.1.1.1.%d %u}", 
	    statPtr -  &tnmSnmpStats.snmpStatsPackets + 1, *statPtr);
    } else {
	*varbind = '\0';
    }

    /*
     * Report PDUs are only authenticated if we are sending an 
     * usecStatsNotInWindows report (see RFC 1910).
     */

    qos = session->qos;
    if (statPtr != &tnmSnmpStats.usecStatsNotInWindows) {
	session->qos = USEC_QOS_NULL;
    }

    Tcl_DStringAppend(&pdu->varbind, varbind, -1);
    TnmSnmpEncode(interp, session, pdu, NULL, NULL);
    TnmSnmpEvalBinding(interp, session, pdu, TNM_SNMP_REPORT_EVENT);
    Tcl_DStringFree(&pdu->varbind);

    session->qos = qos;
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * DecodePDU --
 *
 *	This procedure takes a serialized packet and decodes the PDU. 
 *	The result is written to the pdu structure and varbind list 
 *	is converted to a TCL list contained in pdu->varbind.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
DecodePDU(interp, pdu, packet, packetlen)
    Tcl_Interp *interp;
    TnmSnmpPdu *pdu;
    u_char *packet;
    int *packetlen;
{
    int oidlen	= 0,		/* */
        pdulen	= 0;		/* # of bytes read parsing the PDU */
    
    u_int asnlen = 0,		/* asn sequence length		   */
          deflen = 0;		/* pdu total length without header */
    
    Tnm_Oid oid[TNM_OID_MAX_SIZE];
    int int_val;
    char buf[20];
    char *exception, *freeme;
    static char *vboid;
    static int vboidLen = 0;
    char *snmpTrapEnterprise = NULL;

    Tcl_DStringInit(&pdu->varbind);

    /*
     * first decode PDU tag -- let's see what we have to deal with
     */
    
    switch (*packet++) {
      case ASN1_CONTEXT | ASN1_CONSTRUCTED | TNM_SNMP_RESPONSE:
	pdu->type = TNM_SNMP_RESPONSE;
	break;
      case ASN1_CONTEXT | ASN1_CONSTRUCTED | TNM_SNMP_GET:
	pdu->type = TNM_SNMP_GET;
	break;
      case ASN1_CONTEXT | ASN1_CONSTRUCTED | TNM_SNMP_GETNEXT:
	pdu->type = TNM_SNMP_GETNEXT;
	break;
      case ASN1_CONTEXT | ASN1_CONSTRUCTED | TNM_SNMP_GETBULK:
	pdu->type = TNM_SNMP_GETBULK;
	break;
      case ASN1_CONTEXT | ASN1_CONSTRUCTED | TNM_SNMP_SET:
	pdu->type = TNM_SNMP_SET;
	break;
      case ASN1_CONTEXT | ASN1_CONSTRUCTED | TNM_SNMP_INFORM:
	pdu->type = TNM_SNMP_INFORM;
	break;
      case ASN1_CONTEXT | ASN1_CONSTRUCTED | TNM_SNMPv1_TRAP:
	pdu->type = TNM_SNMPv1_TRAP;
	break;
      case ASN1_CONTEXT | ASN1_CONSTRUCTED | TNM_SNMPv2_TRAP:
	pdu->type = TNM_SNMPv2_TRAP;
	break;
      case ASN1_CONTEXT | ASN1_CONSTRUCTED | TNM_SNMP_REPORT:
	pdu->type = TNM_SNMP_REPORT;
	break;
      default:
	sprintf(interp->result,
		"Response-PDU: invalid tag 0x%.2x.", *--packet);
	tnmSnmpStats.snmpInASNParseErrs++;
	return TCL_ERROR;
    }
    pdulen = 1;

    packet = TnmBerDecLength(packet, &pdulen, &deflen);
    if (packet == NULL) goto asn1Error;
    
    *packetlen += pdulen;
    pdulen = 0;

    if (pdu->type == TNM_SNMPv1_TRAP) {

	int generic, specific;
	char *toid = NULL;

	/*
	 * V1 trap PDUs require special attention - sad story
	 */

	pdu->request_id = 0;
	pdu->error_status = 0;
	pdu->error_index = 0;

	packet = TnmBerDecOID(packet, &pdulen, oid, &oidlen);
        if (packet == NULL) goto asn1Error;

	/*
	 * Save the enterprise object identifier so we can add it
	 * at the end of the varbind list. See the definition of
	 * snmpTrapEnterprise for details.
	 */

	{
	    char *tmp;
	    snmpTrapEnterprise = TnmOidToStr(oid, oidlen);
	    tmp = TnmMibGetName(snmpTrapEnterprise, 0);
	    if (tmp) {
		snmpTrapEnterprise = ckstrdup(tmp);
	    } else {
		snmpTrapEnterprise = ckstrdup(snmpTrapEnterprise);
	    }
	}

	/*
	 * We use the IP address in the SNMPv1_TRAP packet as a from
	 * address since this allows some people to fake traps. This
	 * was requested by jsong@nlc.com (James Song).
	 */

	packet = TnmBerDecOctetString(packet, &pdulen, ASN1_IPADDRESS, 
				      (char **) &freeme, &int_val);
	if (packet == NULL) goto asn1Error;
#if 1
	if (int_val != 4) goto asn1Error;
	memcpy(&pdu->addr.sin_addr, freeme, 4);
#endif

	packet = TnmBerDecInt(packet, &pdulen, ASN1_INTEGER, &generic);
        if (packet == NULL) goto asn1Error;

	packet = TnmBerDecInt(packet, &pdulen, ASN1_INTEGER, &specific);
        if (packet == NULL) goto asn1Error;

	/* 
	 * Ignore errors here to accept bogus trap messages.
	 */

	packet = TnmBerDecInt(packet, &pdulen, ASN1_TIMETICKS, &int_val);
	if (packet == NULL) goto asn1Error;
	{   u_int d, h, m, s, f;
	    d = int_val;
	    f = d % 100; d = d /100;
	    s = d % 60;  d = d / 60;
	    m = d % 60;  d = d / 60;
	    h = d % 24;  d = d / 24;
	    sprintf(buf, "%dd %2d:%02d:%02d.%02d", d, h, m, s, f);
	    Tcl_DStringStartSublist(&pdu->varbind);
	    Tcl_DStringAppendElement(&pdu->varbind, "1.3.6.1.2.1.1.3.0");
	    Tcl_DStringAppendElement(&pdu->varbind, "TimeTicks");
	    Tcl_DStringAppendElement(&pdu->varbind, buf);
	    Tcl_DStringEndSublist(&pdu->varbind);
	}

	switch (generic) {
	  case 0:				/* coldStart*/
	    toid = "1.3.6.1.6.3.1.1.5.1";
	    break;
	  case 1:				/* warmStart */
	    toid = "1.3.6.1.6.3.1.1.5.2";
	    break;
	  case 2:				/* linkDown */
	    toid = "1.3.6.1.6.3.1.1.5.3";
	    break;
	  case 3:				/* linkUp */
	    toid = "1.3.6.1.6.3.1.1.5.4";
	    break;
	  case 4:				/* authenticationFailure */
	    toid = "1.3.6.1.6.3.1.1.5.5";
	    break;
	  case 5:				/* egpNeighborLoss */
	    toid = "1.3.6.1.6.3.1.1.5.6";
	    break;
	  default:
	    oid[oidlen++] = 0;
	    oid[oidlen++] = specific;		/* enterpriseSpecific */
	    toid = ckstrdup(TnmOidToStr(oid, oidlen));
	    break;
	}

	Tcl_DStringStartSublist(&pdu->varbind);
	Tcl_DStringAppendElement(&pdu->varbind, "1.3.6.1.6.3.1.1.4.1.0");
	Tcl_DStringAppendElement(&pdu->varbind, "OBJECT IDENTIFIER");
	{
#if 1
	    char *tmp = TnmMibFormat("1.3.6.1.6.3.1.1.4.1.0", 0, toid);
	    if (tmp) {
		Tcl_DStringAppendElement(&pdu->varbind, tmp);
	    } else {
		Tcl_DStringAppendElement(&pdu->varbind, toid);
	    }
#else
	    char *tmp = TnmMibGetName(toid, 0);
	    if (tmp) {
		Tcl_DStringAppendElement(&pdu->varbind, tmp);
	    } else {
		Tcl_DStringAppendElement(&pdu->varbind, toid);
	    }
#endif
	}

	if (((generic < 0) || (generic > 5)) && toid) {
	    ckfree(toid);
	    toid = NULL;
	}
	Tcl_DStringEndSublist(&pdu->varbind);

	if (packet == NULL) {
	    goto trapError;
	}

    } else {

	/*
	 * decode "request-id", "error-status", & "error index" field
	 */
	
	packet = TnmBerDecInt(packet, &pdulen, ASN1_INTEGER, 
			      &pdu->request_id);
	if (packet == NULL) goto asn1Error;
	
	packet = TnmBerDecInt(packet, &pdulen, 
			      ASN1_INTEGER, &pdu->error_status);
	if (packet == NULL) goto asn1Error;

	switch (pdu->error_status) {
	  case TNM_SNMP_TOOBIG:
	      tnmSnmpStats.snmpInTooBigs++;
	      break;
	  case TNM_SNMP_NOSUCHNAME:
	      tnmSnmpStats.snmpInNoSuchNames++;
	      break;
	  case TNM_SNMP_BADVALUE:
	      tnmSnmpStats.snmpInBadValues++;
	      break;
	  case TNM_SNMP_READONLY:
	      tnmSnmpStats.snmpInReadOnlys++;
	      break;
	  case TNM_SNMP_GENERR:
	      tnmSnmpStats.snmpInGenErrs++;
	      break;
	}
	
	packet = TnmBerDecInt(packet, &pdulen, ASN1_INTEGER, 
			      &pdu->error_index);
	if (packet == NULL) goto asn1Error;
	
    }
    
    /*
     * decode "VarBindList" header ( 0x30 asnlen )
     */
    
    if (*packet++ != (ASN1_UNIVERSAL | ASN1_CONSTRUCTED | ASN1_SEQUENCE)) {
	if (pdu->type == TNM_SNMPv1_TRAP) {
	    goto trapError;
	}
	sprintf(interp->result,
		"VarBindList: invalid tag 0x%.2x; expecting 0x%.2x",
		*--packet, (ASN1_UNIVERSAL | ASN1_CONSTRUCTED | ASN1_SEQUENCE));
	tnmSnmpStats.snmpInASNParseErrs++;
	return TCL_ERROR;
    }
    pdulen += 1;
    
    packet = TnmBerDecLength(packet, &pdulen, &asnlen);
    if (packet == NULL) goto asn1Error;
	
    if ((pdulen + asnlen) != deflen) {
	interp->result = "VarBindList: invalid length field";
	tnmSnmpStats.snmpInASNParseErrs++;
	return TCL_ERROR;
    }

    /* 
     * decode each "VarBind" 
     */

    while (packet && pdulen < deflen) {
	
	/*
	 * decode "VarBind" header ( 0x30 asnlen )
	 */
	
	if (*packet++ !=  (ASN1_UNIVERSAL | ASN1_CONSTRUCTED | ASN1_SEQUENCE)) {
	    sprintf(interp->result,
		    "VarBind: invalid tag 0x%.2x; expecting 0x%.2x",
		    *--packet, (ASN1_UNIVERSAL | ASN1_CONSTRUCTED | ASN1_SEQUENCE));
	    tnmSnmpStats.snmpInASNParseErrs++;
	    return TCL_ERROR;
	}
	pdulen += 1;
	
	packet = TnmBerDecLength(packet, &pdulen, &asnlen);
	if (packet == NULL) goto asn1Error;
	
	/* Start a new element in the VarBindList */

	Tcl_DStringStartSublist(&pdu->varbind);
	
	/*
	 * decode OBJECT-IDENTIFIER
	 */
	
	packet = TnmBerDecOID(packet, &pdulen, oid, &oidlen);
	if (packet == NULL) goto asn1Error;

	{
	    char *soid = TnmOidToStr(oid, oidlen);
	    int len = strlen(soid);
	    if (vboidLen < len + 1)  {
		if (vboid) ckfree(vboid);
		vboidLen = len + 1;
		vboid = ckstrdup(soid);
	    } else {
		strcpy(vboid, soid);
	    }
	    Tcl_DStringAppendElement(&pdu->varbind, vboid);
	}

	/*
	 * Handle exceptions that are coded in the SNMP varbind. We
	 * should create a type conforming null value if possible.
	 */

	exception = TnmGetTableValue(tnmSnmpExceptionTable, *packet);
	if (exception) {
	    Tcl_DStringAppendElement(&pdu->varbind, exception);
	    Tcl_DStringAppendElement(&pdu->varbind, 
				     TnmMibGetBaseSyntax(vboid)
				     == ASN1_OCTET_STRING ? "" : "0");
	    packet = TnmBerDecNull(packet, &pdulen, *packet);
	    goto nextVarBind;
	}

	/*
	 * Decode the ASN.1 type.
	 */

	{
	    char *syntax = TnmGetTableValue(tnmSnmpTypeTable, *packet);
	    Tcl_DStringAppendElement(&pdu->varbind, syntax ? syntax : "");
	}

	/*
	 * decode value for object
	 */

	switch (*packet) {
	  case ASN1_COUNTER32:
	  case ASN1_GAUGE32:
            packet = TnmBerDecInt(packet, &pdulen, *packet, &int_val);
            if (packet == NULL) goto asn1Error;
	    sprintf(buf, "%u", int_val);
            Tcl_DStringAppendElement(&pdu->varbind, buf);
            break;
	  case ASN1_INTEGER:
            packet = TnmBerDecInt(packet, &pdulen, *packet, &int_val);
            if (packet == NULL) goto asn1Error;
	    {   char *tmp;
		sprintf(buf, "%d", int_val);
		tmp = TnmMibFormat(vboid, 0, buf);
		if (tmp) {
		    Tcl_DStringAppendElement(&pdu->varbind, tmp);
		} else {
		    Tcl_DStringAppendElement(&pdu->varbind, buf);
		}
	    }
            break;
	  case ASN1_TIMETICKS:
	    {  
		u_int d, h, m, s, f;
		packet = TnmBerDecInt(packet, &pdulen, ASN1_TIMETICKS, 
				      (int *) &d);
		if (packet == NULL) goto asn1Error;
		f = d % 100; d = d /100;
		s = d % 60;  d = d / 60;
		m = d % 60;  d = d / 60;
		h = d % 24;  d = d / 24;
		sprintf(buf, "%dd %2d:%02d:%02d.%02d", d, h, m, s, f);
		Tcl_DStringAppendElement(&pdu->varbind, buf);
            }
            break;
	  case ASN1_COUNTER64:
	    {
		u_int high, low;
		packet = TnmBerDecCounter64(packet, &pdulen, &high, &low);
		if (packet == NULL) goto asn1Error;
		Tcl_DStringAppendElement(&pdu->varbind, 
					 TnmPrintCounter64(high, low));
	    }
	    break;
	  case ASN1_NULL:
            packet += 2;
            pdulen += 2;
	    Tcl_DStringAppendElement(&pdu->varbind, "");
            break;
	  case ASN1_OBJECT_IDENTIFIER:
            packet = TnmBerDecOID(packet, &pdulen, oid, &oidlen);
            if (packet == NULL) goto asn1Error;
#if 1
	    {   char *soid = TnmOidToStr(oid, oidlen);
		char *tmp = TnmMibFormat(vboid, 0, soid);
		if (tmp) {
		    Tcl_DStringAppendElement(&pdu->varbind, tmp);
		} else {
		    Tcl_DStringAppendElement(&pdu->varbind, soid);
		}
	    }
#else
	    Tcl_DStringAppendElement(&pdu->varbind, TnmOidToStr(oid, oidlen));
#endif
            break;
	  case ASN1_IPADDRESS:
            packet = TnmBerDecOctetString(packet, &pdulen, ASN1_IPADDRESS, 
					  (char **) &freeme, &int_val);
            if (packet == NULL) goto asn1Error;
	    if (int_val != 4) goto asn1Error;
            Tcl_DStringAppend(&pdu->varbind, " ", 1);
	    {
		struct sockaddr_in addr;
		memcpy(&addr.sin_addr, freeme, 4);
		Tcl_DStringAppendElement(&pdu->varbind, 
					 inet_ntoa(addr.sin_addr));
	    }
            break;
	  case ASN1_OCTET_STRING:
            packet = TnmBerDecOctetString(packet, &pdulen, ASN1_OCTET_STRING, 
					  (char **) &freeme, &int_val);
            if (packet == NULL) goto asn1Error;
	    {   char *tmp;
		static char *hex = NULL;
		static int hexLen = 0;
		if (hexLen < int_val * 5 + 1) {
		    if (hex) ckfree(hex);
		    hexLen = int_val * 5 + 1;
		    hex = ckalloc(hexLen);
		}
		TnmHexEnc(freeme, int_val, hex);
		tmp = TnmMibFormat(vboid, 0, hex);
		if (tmp) {
		    Tcl_DStringAppendElement(&pdu->varbind, tmp);
		} else {
		    Tcl_DStringAppendElement(&pdu->varbind, hex);
		}
	    }
            break;
	  default:
            sprintf(interp->result, "unknown asn1 type 0x%.2x", *packet);
	    tnmSnmpStats.snmpInASNParseErrs++;
            return TCL_ERROR;
	}
	
      nextVarBind:
	Tcl_DStringEndSublist(&pdu->varbind);
    }

    /*
     * Add the enterprise object identifier to the varbind list.
     * See the definition of snmpTrapEnterprise of details.
     */

    if (pdu->type == TNM_SNMPv1_TRAP && snmpTrapEnterprise) {
	Tcl_DStringStartSublist(&pdu->varbind);
	Tcl_DStringAppendElement(&pdu->varbind, "1.3.6.1.6.3.1.1.4.3.0");
        Tcl_DStringAppendElement(&pdu->varbind, "OBJECT IDENTIFIER");
	Tcl_DStringAppendElement(&pdu->varbind, snmpTrapEnterprise);
	Tcl_DStringEndSublist(&pdu->varbind);
	ckfree(snmpTrapEnterprise);
    }
    
    *packetlen += pdulen;
    
    if (pdulen != deflen) {
	sprintf(interp->result,
		"PDU sequence length (%d) differs from real length (%d).",
		pdulen, (int) deflen);
	tnmSnmpStats.snmpInASNParseErrs++;
	return TCL_ERROR;
    }

    return TCL_OK;
    
  asn1Error:
    Tcl_SetResult(interp, TnmBerError(), TCL_STATIC);
    tnmSnmpStats.snmpInASNParseErrs++;
    return TCL_ERROR;

  trapError:
    return TCL_OK;
}

