/*
 * tnmSnmpUtil.c --
 *
 *	This file contains some utilities to manipulate request lists
 *	that keep track of outstanding requests. It also contains a
 *	wrapper around the MD5 digest algorithm and some code to handle
 *	SNMP bindings.
 *
 * 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.
 *
 * @(#) $Id: tnmSnmpUtil.c 857 1997-09-22 22:03:38Z schoenw $
 */

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

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

extern int hexdump;

/*
 * The queue of active and waiting asynchronous requests.
 */

static TnmSnmpRequest *queueHead = NULL;

/*
 * The following tables are used to map SNMP version numbers,
 * application types, PDU types and errors to strings.
 */

TnmTable tnmSnmpVersionTable[] = {
    { TNM_SNMPv1,	"SNMPv1" },
#ifdef TNM_SNMPv2C
    { TNM_SNMPv2C,	"SNMPv2C" },
#endif
#ifdef TNM_SNMPv2U
    { TNM_SNMPv2U,	"SNMPv2U" },
#endif
    { 0, NULL }
};

TnmTable tnmSnmpApplTable[] = {
    { TNM_SNMP_GENERATOR,	"generator" },
    { TNM_SNMP_RESPONDER,	"responder" },
    { TNM_SNMP_NOTIFIER,	"notifier" },
    { TNM_SNMP_LISTENER,	"listener" },
    { 0, NULL }
};

TnmTable tnmSnmpPDUTable[] = {
    { TNM_SNMP_GET,		"get-request" },
    { TNM_SNMP_GETNEXT,		"get-next-request" },
    { TNM_SNMP_RESPONSE,	"response" },
    { TNM_SNMP_SET,		"set-request" },
    { TNM_SNMPv1_TRAP,		"snmpV1-trap" },
    { TNM_SNMP_GETBULK,		"get-bulk-request" },
    { TNM_SNMP_INFORM,		"inform-request" },
    { TNM_SNMPv2_TRAP,		"snmpV2-trap" },
    { TNM_SNMP_REPORT,		"report" },
    { 0, NULL }
};

/*
 * The table of SNMP error codes is based on RFC 1905, section 3.
 * It also includes some error codes an names that are specific
 * to the Tnm extension (e.g. noResponse).
 */

TnmTable tnmSnmpErrorTable[] =
{
    { TNM_SNMP_NOERROR,			"noError" },
    { TNM_SNMP_TOOBIG,			"tooBig" },
    { TNM_SNMP_NOSUCHNAME,		"noSuchName" },
    { TNM_SNMP_BADVALUE,		"badValue" },
    { TNM_SNMP_READONLY,		"readOnly" },
    { TNM_SNMP_GENERR,			"genErr" },
    { TNM_SNMP_NOACCESS,		"noAccess" },
    { TNM_SNMP_WRONGTYPE,		"wrongType" },
    { TNM_SNMP_WRONGLENGTH,		"wrongLength" },
    { TNM_SNMP_WRONGENCODING,		"wrongEncoding" },
    { TNM_SNMP_WRONGVALUE,		"wrongValue" },
    { TNM_SNMP_NOCREATION,		"noCreation" },
    { TNM_SNMP_INCONSISTENTVALUE,	"inconsistentValue" },
    { TNM_SNMP_RESOURCEUNAVAILABLE,	"resourceUnavailable" },
    { TNM_SNMP_COMMITFAILED,		"commitFailed" },
    { TNM_SNMP_UNDOFAILED,		"undoFailed" },
    { TNM_SNMP_AUTHORIZATIONERROR,	"authorizationError" },
    { TNM_SNMP_NOTWRITABLE,		"notWritable" },
    { TNM_SNMP_INCONSISTENTNAME,	"inconsistentName" },
    { TNM_SNMP_NORESPONSE,		"noResponse" },
    { 0, NULL }
};

/*
 * The following table is used to convert event names to event token.
 */

TnmTable tnmSnmpEventTable[] =
{
   { TNM_SNMP_GET_EVENT,	"get" },
   { TNM_SNMP_SET_EVENT,	"set" },
   { TNM_SNMP_CREATE_EVENT,	"create" },
   { TNM_SNMP_TRAP_EVENT,	"trap" },
   { TNM_SNMP_INFORM_EVENT,	"inform" },
   { TNM_SNMP_CHECK_EVENT,	"check" },
   { TNM_SNMP_COMMIT_EVENT,	"commit" },
   { TNM_SNMP_ROLLBACK_EVENT,	"rollback" },
   { TNM_SNMP_BEGIN_EVENT,	"begin" },
   { TNM_SNMP_END_EVENT,	"end" },
   { TNM_SNMP_RECV_EVENT,	"recv" },
   { TNM_SNMP_REPORT_EVENT,	"report" },
   { TNM_SNMP_SEND_EVENT,	"send" },
   { 0, NULL }
};

#ifdef TNM_SNMPv2U

/*
 * The following structures and procedures are used to keep a list of
 * keys that were computed with the USEC password to key algorithm.
 * This list is needed so that identical session handles don't suffer
 * from repeated slow computations.
 */

typedef struct KeyCacheElem {
    char *password;
    u_char agentID[USEC_MAX_AGENTID];
    u_char authKey[TNM_MD5_SIZE];
    struct KeyCacheElem *nextPtr;
} KeyCacheElem;

static KeyCacheElem *firstKeyCacheElem = NULL;

/*
 * The following structure is used to keep a cache of known agentID,
 * agentBoots and agentTime values. New session handles are initialized
 * using this cache to avoid repeated report PDUs.
 */

typedef struct AgentIDCacheElem {
    struct sockaddr_in addr;
    u_char agentID[USEC_MAX_AGENTID];
    u_int agentBoots;
    u_int agentTime;
    struct AgentIDCacheElem *nextPtr;
} AgentIDCacheElem;

static AgentIDCacheElem *firstAgentIDCacheElem = NULL;

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

static void
SessionDestroyProc	_ANSI_ARGS_((char *memPtr));

static void
RequestDestroyProc	_ANSI_ARGS_((char *memPtr));

static int
FindAuthKey		_ANSI_ARGS_((TnmSnmp *session));

static void
SaveAuthKey		_ANSI_ARGS_((TnmSnmp *session));

static void 
MakeAuthKey		_ANSI_ARGS_((TnmSnmp *session));

static int
FindAgentID		_ANSI_ARGS_((TnmSnmp *session));

static void
SaveAgentID		_ANSI_ARGS_((TnmSnmp *session));


/*
 *----------------------------------------------------------------------
 *
 * SessionDestroyProc --
 *
 *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
 *	to clean up the internal structure of a request at a safe time
 *	(when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the request is freed up.
 *
 *----------------------------------------------------------------------
 */

static void
SessionDestroyProc(memPtr)
    char *memPtr;
{
    TnmSnmp *session = (TnmSnmp *) memPtr;
    
    if (session->readCommunity) {
	ckfree(session->readCommunity);
    }
    if (session->writeCommunity) {
	ckfree(session->writeCommunity);
    }
    if (session->tags) {
	Tcl_DecrRefCount(session->tags);
    }
    
    while (session->bindPtr) {
	TnmSnmpBinding *bindPtr = session->bindPtr;	
	session->bindPtr = bindPtr->nextPtr;
	if (bindPtr->command) {
	    ckfree(bindPtr->command);
	}
	ckfree((char *) bindPtr);
    }

    if (session->type == TNM_SNMP_LISTENER) {
	TnmSnmpTrapClose();
    }

#if 0    
    if (session->agentSocket) TnmSnmpAgentClose(session);
#else
    if (session->socket) {
	TnmSnmpClose(session->socket);
    }
#endif
    ckfree((char *) session);
}

/*
 *----------------------------------------------------------------------
 *
 * RequestDestroyProc --
 *
 *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
 *	to clean up the internal structure of a request at a safe time
 *	(when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the request is freed up.
 *
 *----------------------------------------------------------------------
 */

static void
RequestDestroyProc(memPtr)
    char *memPtr;
{
    TnmSnmpRequest *request = (TnmSnmpRequest *) memPtr;

    ckfree((char *) request);
}

/*
 *----------------------------------------------------------------------
 *
 * FindAuthKey --
 *
 *	This procedure searches for an already computed key in the 
 *	list of cached authentication keys.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
FindAuthKey(session)
    TnmSnmp *session;
{
    KeyCacheElem *keyPtr;
    
    for (keyPtr = firstKeyCacheElem; keyPtr; keyPtr = keyPtr->nextPtr) {
	if ((strcmp(session->password, keyPtr->password) == 0) 
	    && (memcmp(session->agentID, keyPtr->agentID, 
		       USEC_MAX_AGENTID) == 0)) {
	    memcpy(session->authKey, keyPtr->authKey, TNM_MD5_SIZE);
	    return TCL_OK;
	}
    }

    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * SaveAuthKey --
 *
 *	This procedure adds a new computed key to the internal
 *	list of cached authentication keys.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
SaveAuthKey(session)
    TnmSnmp *session;
{
    KeyCacheElem *keyPtr;
    
    keyPtr = (KeyCacheElem *) ckalloc(sizeof(KeyCacheElem));
    keyPtr->password = ckstrdup(session->password);
    memcpy(keyPtr->agentID, session->agentID, USEC_MAX_AGENTID);
    memcpy(keyPtr->authKey, session->authKey, TNM_MD5_SIZE);
    keyPtr->nextPtr = firstKeyCacheElem;
    firstKeyCacheElem = keyPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * MakeAuthKey --
 *
 *	This procedure converts a 0 terminated password string into 
 *	a 16 byte MD5 key. This is a slighly modified version taken 
 *	from RFC 1910. We keep a cache of all computes passwords to 
 *	make repeated lookups faster.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A new authentication key is computed and stored in the
 *	SNMP session structure.
 *
 *----------------------------------------------------------------------
 */

static void 
MakeAuthKey(session)
    TnmSnmp *session;
{
    MD5_CTX MD;
    u_char *cp, password_buf[64];
    u_long password_index = 0;
    u_long count = 0, i;
    int found, valid = 0, passwordlen = strlen((char *) session->password);

    /*
     * We simply return if we do not have a password or if the 
     * agentID is zero (which is an initialized agentID value.
     */

    for (i = 0; i < USEC_MAX_AGENTID; i++) {
	if (session->agentID[i] != 0) {
	    valid++;
	    break;
	}
    }
    if (! valid || session->password == NULL) {
	return;
    }

    found = FindAuthKey(session);
    if (found != TCL_OK) {

	TnmMD5Init(&MD);   /* initialize MD5 */

	/* loop until we've done 1 Megabyte */
	while (count < 1048576) {
	    cp = password_buf;
	    for(i = 0; i < 64; i++) {
		*cp++ = session->password[password_index++ % passwordlen];
		/*
		 * Take the next byte of the password, wrapping to the
		 * beginning of the password as necessary.
		 */
	    }
	    
	    TnmMD5Update(&MD, password_buf, 64);
	    
	    /*
	     * 1048576 is divisible by 64, so the last MDupdate will be
	     * aligned as well.
	     */
	    count += 64;
	}
	
	TnmMD5Final(password_buf, &MD);
	memcpy(password_buf+TNM_MD5_SIZE, (char *) session->agentID, 
	       USEC_MAX_AGENTID);
	memcpy(password_buf+TNM_MD5_SIZE+USEC_MAX_AGENTID, password_buf, 
	       TNM_MD5_SIZE);
	TnmMD5Init(&MD);   /* initialize MD5 */
	TnmMD5Update(&MD, password_buf, 
		      TNM_MD5_SIZE+USEC_MAX_AGENTID+TNM_MD5_SIZE);
	TnmMD5Final(session->authKey, &MD);
	SaveAuthKey(session);
    }

    if (hexdump) {
	fprintf(stderr, "MD5 key: ");
	for (i = 0; i < TNM_MD5_SIZE; i++) {
	    fprintf(stderr, "%02x ", session->authKey[i]);
	}
	fprintf(stderr, "\n");
    }
}

/*
 *----------------------------------------------------------------------
 *
 * FindAgentID --
 *
 *	This procedure searches for an already known agentID in the list
 *	of cached agentIDs.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
FindAgentID(session)
    TnmSnmp *session;
{
    AgentIDCacheElem *idPtr;
    
    for (idPtr = firstAgentIDCacheElem; idPtr; idPtr = idPtr->nextPtr) {
	if (memcmp(&session->maddr, &idPtr->addr, 
		   sizeof(struct sockaddr_in)) == 0) {
	    memcpy(session->agentID, idPtr->agentID, USEC_MAX_AGENTID);
	    session->agentBoots = idPtr->agentBoots;
	    session->agentTime = idPtr->agentTime;
	    return TCL_OK;
	}
    }

    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * SaveAgentID --
 *
 *	This procedure adds a new agentID to the internal list of 
 *	cached agentIDs. It also caches the agentBoots and agentTime
 *	values.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
SaveAgentID(session)
    TnmSnmp *session;
{
    AgentIDCacheElem *idPtr;
    
    for (idPtr = firstAgentIDCacheElem; idPtr; idPtr = idPtr->nextPtr) {
	if (memcmp(&session->maddr, &idPtr->addr,
		   sizeof(struct sockaddr_in)) == 0) {
	    memcpy(idPtr->agentID, session->agentID, USEC_MAX_AGENTID);
	    idPtr->agentBoots = session->agentBoots;
	    idPtr->agentTime = session->agentTime;
	    return;
	}
    }

    idPtr = (AgentIDCacheElem *) ckalloc(sizeof(AgentIDCacheElem));
    memcpy(&idPtr->addr, &session->maddr, sizeof(struct sockaddr_in));
    memcpy(idPtr->agentID, session->agentID, USEC_MAX_AGENTID);
    idPtr->agentBoots = session->agentBoots;
    idPtr->agentTime = session->agentTime;
    idPtr->nextPtr = firstAgentIDCacheElem;
    firstAgentIDCacheElem = idPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpUsecSetAgentID --
 *
 *	This procedure re-computes localized authentication keys and
 *	should be called whenever the agentID of a session is changed.
 *	It also caches agentBoots and agentTime and hence it should
 *	also be called when these two parameters change.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TnmSnmpUsecSetAgentID(session)
    TnmSnmp *session;
{
    if (session->qos & USEC_QOS_AUTH && session->password) {
	MakeAuthKey(session);
    }
    SaveAgentID(session);
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpUsecGetAgentID --
 *
 *	This procedure tries to find an already known agentID for the
 *	SNMP session. It uses the internal cache of agentIDs. The 
 *	authentication key is re-computed if an agentID is found.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A new authentication key and new agentID, agentBoots and agentTime
 *	values are stored in the SNMP session structure.
 *
 *----------------------------------------------------------------------
 */

void
TnmSnmpUsecGetAgentID(session)
    TnmSnmp *session;
{
    int found;

    found = FindAgentID(session);
    if (found == TCL_OK) {
	if (session->qos & USEC_QOS_AUTH) {
	    MakeAuthKey(session);
	}
    }
}

#endif

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpEvalCallback --
 *
 *	This procedure evaluates a Tcl callback. The command string is
 *	modified according to the % escapes before evaluation.  The
 *	list of supported escapes is %R = request id, %S = session
 *	name, %E = error status, %I = error index, %V = varbindlist
 *	and %A the agent address. There are three more escapes for
 *	instance bindings: %o = object identifier of instance, %i =
 *	instance identifier, %v = value, %p = previous value during
 *	set processing.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Tcl commands are evaluated which can have all kind of effects.
 *
 *----------------------------------------------------------------------
 */

int
TnmSnmpEvalCallback(interp, session, pdu, cmd, instance, oid, value, last)
    Tcl_Interp *interp;
    TnmSnmp *session;
    TnmSnmpPdu *pdu;
    char *cmd;
    char *instance;
    char *oid;
    char *value;
    char *last;
{
    char buf[20];
    int	code;
    Tcl_DString tclCmd;
    char *startPtr, *scanPtr, *name;

    Tcl_DStringInit(&tclCmd);
    startPtr = cmd;
    for (scanPtr = startPtr; *scanPtr != '\0'; scanPtr++) {
	if (*scanPtr != '%') {
	    continue;
	}
	Tcl_DStringAppend(&tclCmd, startPtr, scanPtr - startPtr);
	scanPtr++;
	startPtr = scanPtr + 1;
	switch (*scanPtr) {
	  case 'R':  
	    sprintf(buf, "%d", pdu->requestId);
	    Tcl_DStringAppend(&tclCmd, buf, -1);
	    break;
	  case 'S':
	    if (session && session->interp && session->token) {
		Tcl_DStringAppend(&tclCmd, 
		  Tcl_GetCommandName(session->interp, session->token), -1);
	    }
	    break;
	  case 'V':
	    Tcl_DStringAppend(&tclCmd, Tcl_DStringValue(&pdu->varbind), -1);
	    break;
	  case 'E':
	    name = TnmGetTableValue(tnmSnmpErrorTable, pdu->errorStatus);
	    if (name == NULL) {
		name = "unknown";
	    }
	    Tcl_DStringAppend(&tclCmd, name, -1);
	    break;
	  case 'I':
	    sprintf(buf, "%d", pdu->errorIndex - 1);
	    Tcl_DStringAppend(&tclCmd, buf, -1);
	    break;
	  case 'A':
	    Tcl_DStringAppend(&tclCmd, inet_ntoa(pdu->addr.sin_addr), -1);
	    break;
	  case 'P':
	    sprintf(buf, "%u", ntohs(pdu->addr.sin_port));
	    Tcl_DStringAppend(&tclCmd, buf, -1);
	    break;
	  case 'T':
	    name = TnmGetTableValue(tnmSnmpPDUTable, pdu->type);
	    if (name == NULL) {
		name = "unknown";
	    }
	    Tcl_DStringAppend(&tclCmd, name, -1);
            break;
	  case 'o':
	    if (instance) {
		Tcl_DStringAppend(&tclCmd, instance, -1);
	    }
	    break;
	  case 'i':
	    if (oid) {
		Tcl_DStringAppend(&tclCmd, oid, -1);
	    }
	    break;
	  case 'v':
	    if (value) {
		Tcl_DStringAppend(&tclCmd, value, -1);
	    }
	    break;
	  case 'p':
	    if (last) {
		Tcl_DStringAppend(&tclCmd, last, -1);
	    }
	    break;
	  case '%':
	    Tcl_DStringAppend(&tclCmd, "%", -1);
	    break;
	  default:
	    sprintf(buf, "%%%c", *scanPtr);
	    Tcl_DStringAppend(&tclCmd, buf, -1);
	}
    }
    Tcl_DStringAppend(&tclCmd, startPtr, scanPtr - startPtr);
    
    /*
     * Now evaluate the callback function and issue a background
     * error if the callback fails for some reason. Return the
     * original error message and code to the caller.
     */
    
    Tcl_AllowExceptions(interp);
    code = Tcl_GlobalEval(interp, Tcl_DStringValue(&tclCmd));
    Tcl_DStringFree(&tclCmd);

    /*
     * Call the usual error handling proc if we have evaluated
     * a binding not bound to a specific instance. Bindings 
     * bound to an instance are usually called during PDU 
     * processing where it is important to get the error message
     * back.
     */

    if (code == TCL_ERROR && oid == NULL) {
	char *errorMsg = ckstrdup(interp->result);
	Tcl_AddErrorInfo(interp, "\n    (snmp callback)");
	Tcl_BackgroundError(interp);
	Tcl_SetResult(interp, errorMsg, TCL_DYNAMIC);
    }
    
    return code;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpEvalBinding --
 *
 *	This procedure checks for events that are not bound to an
 *	instance, such as TNM_SNMP_BEGIN_EVENT and TNM_SNMP_END_EVENT.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Tcl commands are evaluated which can have all kind of effects.
 *
 *----------------------------------------------------------------------
 */

int
TnmSnmpEvalBinding(interp, session, pdu, event)
    Tcl_Interp *interp;
    TnmSnmp *session;
    TnmSnmpPdu *pdu;
    int event;
{
    int code = TCL_OK;
    TnmSnmpBinding *bindPtr = session->bindPtr;
    
    while (bindPtr) {
	if (bindPtr->event == event) break;
	bindPtr = bindPtr->nextPtr;
    }

    if (bindPtr && bindPtr->command) {
	Tcl_Preserve((ClientData) session);
	code = TnmSnmpEvalCallback(interp, session, pdu, bindPtr->command,
				   NULL, NULL, NULL, NULL);
	Tcl_Release((ClientData) session);
    }

    return code;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpDumpPDU --
 *
 *	This procedure dumps the contents of a pdu to standard output. 
 *	This is just a debugging aid.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TnmSnmpDumpPDU(interp, pdu)
    Tcl_Interp *interp;
    TnmSnmpPdu *pdu;
{
    if (hexdump) {

        int i, code, argc;
	char **argv;
	char *name, *status;

	name = TnmGetTableValue(tnmSnmpPDUTable, pdu->type);
	if (name == NULL) {
	    name = "unknown";
	}

	status = TnmGetTableValue(tnmSnmpErrorTable, pdu->errorStatus);
	if (status == NULL) {
	    status = "unknown";
	}
	
	if (pdu->type == TNM_SNMP_GETBULK) {
	    printf("%s %d non-repeaters %d max-repetitions %d\n", 
		   name, pdu->requestId,
		   pdu->errorStatus, pdu->errorIndex);
	} else if (pdu->type == TNM_SNMPv1_TRAP) {
	    printf("%s\n", name);
	} else if (pdu->errorStatus == TNM_SNMP_NOERROR) {
	    printf("%s %d %s\n", name, pdu->requestId, status);
	} else {
	    printf("%s %d %s at %d\n", 
		   name, pdu->requestId, status, pdu->errorIndex);
	}

	code = Tcl_SplitList(interp, Tcl_DStringValue(&pdu->varbind), 
			     &argc, &argv);
	if (code == TCL_OK) {
	    for (i = 0; i < argc; i++) {
		printf("%4d.\t%s\n", i+1, argv[i]);
	    }
	    ckfree((char *) argv);
	}
	Tcl_ResetResult(interp);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpMD5Digest --
 *
 *	This procedure computes the message digest of the given packet.
 *	It is based on the MD5 implementation of RFC 1321. We compute a
 *	keyed MD5 digest if the key parameter is not a NULL pointer.
 *
 * Results:
 *	The digest is written into the digest array.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TnmSnmpMD5Digest(packet, length, key, digest)
    u_char *packet;
    int length;
    u_char *key;
    u_char *digest;
{
    MD5_CTX MD;

    TnmMD5Init(&MD);   /* initialize MD5 */
    TnmMD5Update(&MD, (char *) packet, length);
    if (key) {
	TnmMD5Update(&MD, (char *) key, TNM_MD5_SIZE);
    }
    TnmMD5Final(digest, &MD);

    if (hexdump) {
	int i;
	if (key) {
	    fprintf(stderr, "MD5    key: ");
	    for (i = 0; i < TNM_MD5_SIZE; i++) {
		fprintf(stderr, "%02x ", key[i]);
	    }
	    fprintf(stdout, "\n");
	}
	fprintf(stderr, "MD5 digest: ");
	for (i = 0; i < TNM_MD5_SIZE; i++) {
	    fprintf(stderr, "%02x ", digest[i]);
	}
	fprintf(stderr, "\n");
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpCreateSession --
 *
 *	This procedure allocates and initializes a TnmSnmp 
 *	structure.
 *
 * Results:
 *	A pointer to the new TnmSnmp structure.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

TnmSnmp*
TnmSnmpCreateSession(type)
    char type;
{
    TnmSnmp *session;
    
    session = (TnmSnmp *) ckalloc(sizeof(TnmSnmp));
    memset((char *) session, 0, sizeof(TnmSnmp));

    session->maddr.sin_family = AF_INET;
    session->maddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    if (type == TNM_SNMP_LISTENER || type == TNM_SNMP_NOTIFIER) {
	session->maddr.sin_port = htons(TNM_SNMP_TRAPPORT);
    } else {
	session->maddr.sin_port = htons(TNM_SNMP_PORT);
    }
    session->version = TNM_SNMPv1;
    session->type    = type;
    session->readCommunity = ckstrdup("public");
    session->writeCommunity = ckstrdup("private");
#ifdef TNM_SNMPv2U
    strcpy(session->userName, "public");
    session->userNameLen = strlen(session->userName);
    session->maxSize = TNM_SNMP_MAXSIZE;
#endif
    session->retries = TNM_SNMP_RETRIES;
    session->timeout = TNM_SNMP_TIMEOUT;
    session->window  = TNM_SNMP_WINDOW;
    session->delay   = TNM_SNMP_DELAY;

    TnmOidInit(&session->enterpriseOid);
    TnmOidFromString(&session->enterpriseOid, "1.3.6.1.4.1.1575");

    return session;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpDeleteSession --
 *
 *	This procedure frees the memory allocated by a TnmSnmp
 *	and all it's associated structures.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TnmSnmpDeleteSession(session)
    TnmSnmp *session;
{
    TnmSnmpRequest **rPtrPtr;

    if (! session) return;

    rPtrPtr = &queueHead;
    while (*rPtrPtr) {
	if ((*rPtrPtr)->session == session) {
	    TnmSnmpRequest *request = *rPtrPtr;
	    *rPtrPtr = (*rPtrPtr)->nextPtr;
	    if (request->timer) {
	        Tcl_DeleteTimerHandler(request->timer);
	    }
	    Tcl_EventuallyFree((ClientData) request, RequestDestroyProc);
	} else {
	    rPtrPtr = &(*rPtrPtr)->nextPtr;
	}
    }

    Tcl_EventuallyFree((ClientData) session, SessionDestroyProc);
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpCreateRequest --
 *
 *	This procedure creates an entry in the request list and
 *	saves this packet together with it's callback function.
 *	The callback is executed when the response packet is
 *	received from the agent.
 *
 * Results:
 *	A pointer to the new TnmSnmpRequest structure.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

TnmSnmpRequest*
TnmSnmpCreateRequest(id, packet, packetlen, proc, clientData, interp)
    int id;
    u_char *packet;
    int packetlen;
    TnmSnmpRequestProc *proc;
    ClientData clientData;
    Tcl_Interp *interp;
{
    TnmSnmpRequest *request;

    /*
     * Allocate a TnmSnmpRequest structure together with some space to
     * hold the encoded packet. Allocating this in one ckalloc call 
     * simplifies and thus speeds up memory management.
     */

    request = (TnmSnmpRequest *) ckalloc(sizeof(TnmSnmpRequest) + packetlen);
    memset((char *) request, 0, sizeof(TnmSnmpRequest));
    request->packet = (u_char *) request + sizeof(TnmSnmpRequest);
    request->id = id;
    memcpy(request->packet, packet, packetlen);
    request->packetlen = packetlen;
    request->proc = proc;
    request->clientData = clientData;
    request->interp = interp;
    return request;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpFindRequest --
 *
 *	This procedure scans through the request lists of all open
 *	sessions and tries to find the request for a given request
 *	id.
 *
 * Results:
 *	A pointer to the request structure or NULL if the request
 *	id is not in the request list.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

TnmSnmpRequest*
TnmSnmpFindRequest(id)
    int id;
{
    TnmSnmpRequest *rPtr;

    for (rPtr = queueHead; rPtr; rPtr = rPtr->nextPtr) {
        if (rPtr->id == id) break;
    }
    return rPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpQueueRequest --
 *
 *	This procedure queues a request into the wait queue or checks 
 *	if queued requests should be activated. The queue is processed
 *	in FIFO order with the following constraints:
 *
 *	1. The number of active requests per session is smaller than
 *	   the window size of this session.
 *
 *	2. The total number of active requests is smaller than the
 *	   window size of this session.
 *
 *	The second rule makes sure that you can't flood a network by 
 *	e.g. creating thousand sessions all with a small window size
 *	sending one request. If the parameter which specifies the
 *	new request is NULL, only queue processing will take place.
 *
 * Results:
 *	The number of requests queued for this SNMP session.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TnmSnmpQueueRequest(session, request)
    TnmSnmp *session;
    TnmSnmpRequest *request;
{
    int waiting = 0, active = 0;
    TnmSnmpRequest *rPtr, *lastPtr = NULL;

    /*
     * Walk through the queue and count active and waiting requests.
     * Keep a pointer to the last request in the queue.
     */

    for (rPtr = queueHead; rPtr; rPtr = rPtr->nextPtr) {
	if (rPtr->sends) {
	    active++;
	} else {
	    waiting++;
	}
	if (request) {
	    lastPtr = rPtr;
	}
    }

    /*
     * Append the new request (if we have one).
     */

    if (request) {
	request->session = session;
	session->waiting++;
	waiting++;
	if (! queueHead) {
	    queueHead = request;
	} else {
	    lastPtr->nextPtr = request;
	}
    }

    /*
     * Try to activate new requests if there are some waiting and
     * if the total number of active requests is smaller than the
     * window of the current session.
     */

    for (rPtr = queueHead; rPtr && waiting; rPtr = rPtr->nextPtr) {
        if (session->window && active >= session->window) break;
	if (! rPtr->sends && (rPtr->session->active < rPtr->session->window 
			      || rPtr->session->window == 0)) {
	    TnmSnmpTimeoutProc((ClientData) rPtr);
	    active++;
	    waiting--;
	    rPtr->session->active++;
	    rPtr->session->waiting--;
	}
    }

    return (session->active + session->waiting);
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpDeleteRequest --
 *
 *	This procedure deletes a request from the list of known 
 *	requests. This will also free all resources and event
 *	handlers for this request.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TnmSnmpDeleteRequest(request)
    TnmSnmpRequest *request;
{
    TnmSnmpRequest *rPtr, **rPtrPtr;
    TnmSnmp *session;

    /*
     * Check whether the request still exists. It may have been
     * removed because the session for this request has been 
     * destroyed during callback processing.
     */

    for (rPtr = queueHead; rPtr; rPtr = rPtr->nextPtr) {
	if (rPtr == request) break;
    }
    if (! rPtr) return;
    
    /* 
     * Check whether the session is still in the session list.
     * We sometimes get called when the session has already been
     * destroyed as a side effect of evaluating callbacks.
     */
    
    for (session = tnmSnmpList; session; session = session->nextPtr) {
	if (session == request->session) break;
    }

    if (session) {
	if (request->sends) {
	    session->active--;
	} else {
	    session->waiting--;
	}
    }
    
    /*
     * Remove the request from the list of outstanding requests.
     * and free the resources allocated for this request.
     */

    rPtrPtr = &queueHead;
    while (*rPtrPtr && *rPtrPtr != request) {
	rPtrPtr = &(*rPtrPtr)->nextPtr;
    }
    if (*rPtrPtr) {
	*rPtrPtr = request->nextPtr;
	if (request->timer) {
	    Tcl_DeleteTimerHandler(request->timer);
	    request->timer = NULL;
	}
	Tcl_EventuallyFree((ClientData) request, RequestDestroyProc);
    }

    /*
     * Update the request queue. This will activate async requests
     * that have been queued because of the window size.
     */
     
    if (session) {
	TnmSnmpQueueRequest(session, NULL);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpGetRequestId --
 *
 *	This procedure generates an unused request identifier.
 *
 * Results:
 *	The request identifier.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TnmSnmpGetRequestId()
{
    int id;
    TnmSnmpRequest *rPtr = queueHead;

    do {
	id = rand();
	for (rPtr = queueHead; rPtr && rPtr->id != id; rPtr = rPtr->nextPtr) {
	    /* empty body */
	}
    } while (rPtr);

    return id;
}

/*
 *----------------------------------------------------------------------
 *
 * Tnm_SnmpSplitVBList --
 *
 *	This procedure splits a list of Tcl lists containing
 *	varbinds (again Tcl lists) into an array of SNMP_VarBind
 *	structures.
 *
 * Results:
 *	The standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Tnm_SnmpSplitVBList(interp, list, varBindSizePtr, varBindPtrPtr)
    Tcl_Interp *interp;
    char *list;
    int *varBindSizePtr;
    SNMP_VarBind **varBindPtrPtr;
{
    int code, vblc, i;
    char **vblv;
    int varBindSize;
    SNMP_VarBind *varBindPtr;

    code = Tcl_SplitList(interp, list, &vblc, &vblv);
    if (code != TCL_OK) {
        return TCL_ERROR;
    }

    /*
     * Allocate space for the varbind table. Note, we could reuse space
     * allocated from previous runs to avoid all the malloc and free
     * operations. For now, we go the simple way.
     */

    varBindSize = vblc;
    varBindPtr = (SNMP_VarBind *) ckalloc(varBindSize * sizeof(SNMP_VarBind));
    memset((char *) varBindPtr, 0, varBindSize * sizeof(SNMP_VarBind));

    for (i = 0; i < varBindSize; i++) {
        int vbc;
        char **vbv;
        code = Tcl_SplitList(interp, vblv[i], &vbc, &vbv);
	if (code != TCL_OK) {
	    Tnm_SnmpFreeVBList(varBindSize, varBindPtr);
	    ckfree((char *) vblv);
	    return TCL_ERROR;
	}
	if (vbc > 0) {
	    varBindPtr[i].soid = vbv[0];
	    if (vbc > 1) {
		varBindPtr[i].syntax = vbv[1];
		if (vbc > 2) {
		    varBindPtr[i].value = vbv[2];
		}
	    }
	}
	varBindPtr[i].freePtr = (char *) vbv;
    }

    *varBindSizePtr = varBindSize;
    *varBindPtrPtr = varBindPtr;
    ckfree((char *) vblv);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Tnm_SnmpMergeVBList --
 *
 *	This procedure merges the contents of a SNMP_VarBind
 *	structure into a Tcl list of Tcl lists.
 *
 * Results:
 *	A pointer to a malloced buffer.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char*
Tnm_SnmpMergeVBList(varBindSize, varBindPtr)
    int varBindSize;
    SNMP_VarBind *varBindPtr;
{
    static Tcl_DString list;
    int i;

    Tcl_DStringInit(&list);

    for (i = 0; i < varBindSize; i++) {
        Tcl_DStringStartSublist(&list);
	Tcl_DStringAppendElement(&list, 
			     varBindPtr[i].soid ? varBindPtr[i].soid : "");
	Tcl_DStringAppendElement(&list, 
			     varBindPtr[i].syntax ? varBindPtr[i].syntax : "");
	Tcl_DStringAppendElement(&list, 
			     varBindPtr[i].value ? varBindPtr[i].value : "");
	Tcl_DStringEndSublist(&list);
    }

    return ckstrdup(Tcl_DStringValue(&list));
}

/*
 *----------------------------------------------------------------------
 *
 * Tnm_SnmpFreeVBList --
 *
 *	This procedure frees the array of SNMP_VarBind structures
 *	and all the varbinds stored in the array.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Tnm_SnmpFreeVBList(varBindSize, varBindPtr)
    int varBindSize;
    SNMP_VarBind *varBindPtr;
{
    int i;
    
    for (i = 0; i < varBindSize; i++) {
	if (varBindPtr[i].freePtr) {
	    ckfree(varBindPtr[i].freePtr);
	}
    }

    ckfree((char *) varBindPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpSysUpTime --
 *
 *	This procedure returns the uptime of this SNMP enitity (agent) 
 *	in hundreds of seconds. Should be initialized when registering 
 *	the SNMP extension.
 *
 * Results:
 *	The uptime in hundreds of seconds.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TnmSnmpSysUpTime()
{
    static Tcl_Time bootTime = { 0, 0 };
    Tcl_Time currentTime;
    int delta = 0;

    TnmGetTime(&currentTime);
    if (bootTime.sec == 0 && bootTime.usec == 0) {
	bootTime = currentTime;
    } else {
	delta = (currentTime.sec - bootTime.sec) * 100
	    + (currentTime.usec - bootTime.usec) / 10000;
    }
    return delta;
}

/* ===================================================================== */

static TnmSnmpVarBind *vbFreeList = NULL;

TnmSnmpVarBind*
TnmSnmpCreateVarBind()
{
    TnmSnmpVarBind *vbPtr;

    if (! vbFreeList) {
	vbPtr = (TnmSnmpVarBind *) ckalloc(sizeof(TnmSnmpVarBind));
	memset((char *) vbPtr, 0, sizeof(TnmSnmpVarBind));
	TnmOidInit(&vbPtr->oid);
    } else {
	vbPtr = vbFreeList;
	vbFreeList = vbFreeList->value.nextPtr;
    }
    return vbPtr;
}

void
TnmSnmpFreeVarBind(vbPtr)
    TnmSnmpVarBind *vbPtr;
{
    if (vbPtr) {
	TnmOidFree(&vbPtr->oid);
	switch (vbPtr->syntax) {
	case ASN1_OBJECT_IDENTIFIER:
	    if (vbPtr->value.oidValue) {
#if 1
		TnmOidFree(vbPtr->value.oidValue);
		ckfree((char *) vbPtr->value.oidValue);
		vbPtr->value.oidValue = 0;
#endif
	    }
	    break;
	}
	vbPtr->value.nextPtr = vbFreeList;
	vbFreeList = vbPtr;
    }
}

void
TnmSnmpVarBindFree(vector)
    TnmVector *vector;
{
    int i;

    for (i = 0; i < TnmVectorSize(vector); i++) {
	TnmSnmpFreeVarBind((TnmSnmpVarBind *) TnmVectorGet(vector, i));
	TnmVectorSet(vector, i, NULL);
    }
}

static int
SetOid(interp, vbPtr, label)
    Tcl_Interp *interp;
    TnmSnmpVarBind *vbPtr;
    char *label;
{
    int code;

    code = TnmOidFromString(&vbPtr->oid, label);
    if (code != TCL_OK) {
	char *tmp = TnmMibGetOid(label);
	if (tmp) {
	    code = TnmOidFromString(&vbPtr->oid, tmp);
	}
    }
    if (code != TCL_OK) {
	Tcl_AppendResult(interp, "invalid object identifier \"",
			 label, "\"", (char *) NULL);
	return TCL_ERROR;
    }

    return TCL_OK;
}

char*
TnmSnmpOidToString(vbPtr)
    TnmSnmpVarBind *vbPtr;
{
    return TnmOidToString(&vbPtr->oid);
}

static int
SetType(interp, vbPtr, type)
    Tcl_Interp *interp;
    TnmSnmpVarBind *vbPtr;
    char *type;
{
    if (! type) {
	vbPtr->syntax = TnmMibGetBaseSyntax(TnmOidToString(&vbPtr->oid));
    } else {
	vbPtr->syntax = TnmGetTableKey(tnmSnmpExceptionTable, type);
	if (vbPtr->syntax < 0) {
	    vbPtr->syntax = TnmGetTableKey(tnmSnmpTypeTable, type);
	    if (vbPtr->syntax < 0) {
		vbPtr->syntax = TnmMibGetBaseSyntax(
		    TnmOidToString(&vbPtr->oid));
	    }
	}
    }

    if (!TnmSnmpValidType(vbPtr->syntax)
	&& !TnmSnmpValidException(vbPtr->syntax)) {
	vbPtr->syntax = ASN1_NULL;
    }

    return TCL_OK;
}

char*
TnmSnmpTypeToString(vbPtr)
    TnmSnmpVarBind *vbPtr;
{
    char *result;

    result = TnmGetTableValue(tnmSnmpExceptionTable, vbPtr->syntax);
    if (! result) {
	result = TnmGetTableValue(tnmSnmpTypeTable, vbPtr->syntax);
    }
    return result;
}

static int
SetValue(interp, vbPtr, value)
    Tcl_Interp *interp;
    TnmSnmpVarBind *vbPtr;
    char *value;
{
    int rc;

    if (! value) {
	return TCL_OK;
    }

    switch (vbPtr->syntax) {
    case ASN1_INTEGER:
    case ASN1_COUNTER32:
    case ASN1_GAUGE32: {
	rc = Tcl_GetInt(interp, value, &vbPtr->value.intValue);
	if (rc != TCL_OK) {
	    char *tmp = TnmMibScan(TnmOidToString(&vbPtr->oid), 0, value);
	    if (tmp && *tmp) {
		Tcl_ResetResult(interp);
		rc = Tcl_GetInt(interp, tmp, &vbPtr->value.intValue);
	    }
	    if (rc != TCL_OK) {
		return TCL_ERROR;
	    }
	}
	break;
    }
#if 0
    case ASN1_COUNTER64: {
	int int_val, rc;
	if (sizeof(int) >= 8) {
	    rc = Tcl_GetInt(interp, value, &int_val);
	    if (rc != TCL_OK) {
		char *tmp = TnmMibScan(vbv[0], 0, value);
		if (tmp && *tmp) {
		    Tcl_ResetResult(interp);
		    rc = Tcl_GetInt(interp, tmp, &int_val);
		}
		if (rc != TCL_OK) return NULL;
	    }
	    packet = TnmBerEncInt(packet, packetlen,
				  ASN1_COUNTER64, int_val);
	} else {
	    double d;
	    rc = Tcl_GetDouble(interp, value, &d);
	    if (rc != TCL_OK) {
		return NULL;
	    }
	    if (d < 0) {
		Tcl_SetResult(interp, "negativ counter value",
			      TCL_STATIC);
		return NULL;
	    }
	    packet = TnmBerEncCounter64(packet, packetlen, d);
	}
	break;
    }
#endif
    case ASN1_BIT_STRING:
	panic("bit string value not implemented!");
	break;
    case ASN1_TIMETICKS: {
	int d, h, m, s, f, n;
	n = sscanf(value, "%dd %d:%d:%d.%d", &d, &h, &m, &s, &f);
	if (n == 5) {
	    vbPtr->value.intValue 
		= d * 8640000 + h * 360000 + m * 6000 + s * 100 + f;
	} else {
	    vbPtr->value.intValue = 0;
	    while (isdigit(*value)) {
		vbPtr->value.intValue = 10 * vbPtr->value.intValue
		    + *value - '0';
		value++;
	    }
	}
	break;
    }
    case ASN1_IPADDRESS: {
	struct sockaddr_in addr;
	rc = TnmSetIPAddress(interp, value, &addr);
	if (rc != TCL_OK) {
	    return TCL_ERROR;
	}
	vbPtr->value.ipValue = addr.sin_addr;
	break;
    }
#if 0
    case ASN1_OCTET_STRING:
    {   char *hex = value;
    int len;
    static char *bin = NULL;
    static int binLen = 0;
    char *scan = TnmMibScan(vbv[0], 0, value);
    if (scan) hex = scan;
    if (*hex) {
	len = strlen(hex);
	if (binLen < len + 1) {
	    if (bin) ckfree(bin);
	    binLen = len + 1;
	    bin = ckalloc(binLen);
	}
	if (TnmHexDec(hex, bin, &len) < 0) {
	    Tcl_SetResult(interp, 
			  "illegal octet string value",
			  TCL_STATIC);
	    return NULL;
	}
    } else {
	len = 0;
    }
    packet = TnmBerEncOctetString(packet, packetlen,
				  ASN1_OCTET_STRING, bin, len);
    }
#endif
    break;
    case ASN1_OBJECT_IDENTIFIER:
	vbPtr->value.oidValue = (TnmOid *) ckalloc(sizeof(TnmOid));
	memset((char *) vbPtr->value.oidValue, 0, sizeof(TnmOid));
	TnmOidInit(vbPtr->value.oidValue);
	rc = TnmOidFromString(vbPtr->value.oidValue, value);
	if (rc != TCL_OK) {
	    char *tmp = TnmMibGetOid(value);
	    if (tmp) {
		rc = TnmOidFromString(vbPtr->value.oidValue, tmp);
	    }
	}
	if (rc != TCL_OK) {
	    Tcl_AppendResult(interp, "illegal object identifier \"",
			     value, "\"", (char *) NULL);
	    return TCL_ERROR;
	}
	break;
    }

    return TCL_OK;
}

char*
TnmSnmpValueToString(vbPtr)
    TnmSnmpVarBind *vbPtr;
{
    static char buffer[40];
    char *result;

    switch (vbPtr->syntax) {
    case ASN1_COUNTER32:
    case ASN1_GAUGE32:
	sprintf(buffer, "%u", vbPtr->value.intValue);
	result = buffer;
	break;
    case ASN1_INTEGER: {
	char *tmp;
	sprintf(buffer, "%d", vbPtr->value.intValue);
	tmp = TnmMibFormat(TnmOidToString(&vbPtr->oid), 0, buffer);
	result = tmp ? tmp : buffer;
	break;
    }
    case ASN1_TIMETICKS: {  
	int d, h, m, s, f;
	d = vbPtr->value.intValue;
	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(buffer, "%dd %2d:%02d:%02d.%02d", d, h, m, s, f);
	result = buffer;
        break;
    }
    case ASN1_COUNTER64:
	result = TnmPrintCounter64(vbPtr->value.c64Value.high, 
				   vbPtr->value.c64Value.low);
	break;
    case ASN1_NULL:
	result = "";
	break;
    case ASN1_OBJECT_IDENTIFIER: {
	if (vbPtr->value.oidValue) {
	    char *soid = TnmOidToString(vbPtr->value.oidValue);
	    char *tmp = TnmMibFormat(TnmOidToString(&vbPtr->oid), 0, soid);
	    result = tmp ? tmp : soid;
	} else {
	    result = "";
	}
	break;
    }
    case ASN1_IPADDRESS: {
	result = inet_ntoa(vbPtr->value.ipValue);
	break;
    }
#if 0
    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;
#endif
    default:
	result = NULL;
    }

    return result;
}

int
TnmSnmpVarBindFromString(interp, vector, string)
    Tcl_Interp *interp;
    TnmVector *vector;
    char *string;
{
    int code, result = TCL_OK;
    int vblc, vbc, i;
    char **vblv = NULL, **vbv = NULL;
    char *name, *type, *value;
    TnmSnmpVarBind *vbPtr = NULL;

    code = Tcl_SplitList(interp, string, &vblc, &vblv);
    if (code != TCL_OK) {
        return TCL_ERROR;
    }

    TnmVectorFree(vector);
    for (i = 0; i < vblc; i++) {

	/*
	 * Break the Tcl varbind into pieces and assign the elements
	 * to local variables so that we do not have to deal with
	 * ugly names later on. Return an error right away if the
	 * list can't be valid.
	 */

        code = Tcl_SplitList(interp, vblv[i], &vbc, &vbv);
	if (code != TCL_OK) {
	    result = code;
	    break;
	}
	if (vbc < 1 || vbc > 3) {
	    Tcl_AppendResult(interp, "illegal number of elements in ",
			     "varbind list element", (char *) NULL);
	    result = TCL_ERROR;
	    break;
	}
	name = vbv[0], type = NULL, value = NULL;
	if (vbc == 2) value = vbv[1];
	if (vbc == 3) type = vbv[1], value = vbv[2];

	vbPtr = TnmSnmpCreateVarBind();

	/*
	 * Get the object identifier value. We might have to call the
	 * MIB module in order to convert a name into an object
	 * identifier. Return an error if it is not possible to resolve
	 * the string into an object identifier value.
	 */

	/*
	 * Convert the SNMP type value. We first check for an SNMP
	 * exception, followed by a test for an SNMP type, followed
	 * by a MIB lookup for the given object identifier.
	 */

	code = SetOid(interp, vbPtr, name);
	if (code == TCL_OK) {
	    code = SetType(interp, vbPtr, type);
	    if (code == TCL_OK) {
		code = SetValue(interp, vbPtr, value);
	    }
	}
	if (code != TCL_OK) {
	    TnmSnmpFreeVarBind(vbPtr);
	    result = TCL_ERROR;
	    break;
	}

	/*
	 * Put the new varbind into the vector.
	 */

	TnmVectorAdd(vector, (ClientData) vbPtr);
	ckfree((char *) vbv);
	vbv = NULL;
    }

    if (result != TCL_OK) {
	TnmSnmpVarBindFree(vector);
    }
    if (vblv) ckfree((char *) vblv);
    if (vbv) ckfree((char *) vbv);
    return result;
}

char*
TnmSnmpVarBindToString(vector)
    TnmVector *vector;
{
    int i;
    static Tcl_DString *dstPtr = NULL;
    TnmSnmpVarBind *vbPtr;

    if (! dstPtr) {
	dstPtr = (Tcl_DString *) ckalloc(sizeof(Tcl_DString));
	Tcl_DStringInit(dstPtr);
    } else {
	Tcl_DStringFree(dstPtr);
    }

    for (i = 0; i < TnmVectorSize(vector); i++) {
	vbPtr = (TnmSnmpVarBind *) TnmVectorGet(vector, i);
	Tcl_DStringStartSublist(dstPtr);
	Tcl_DStringAppendElement(dstPtr, TnmSnmpOidToString(vbPtr));
	Tcl_DStringAppendElement(dstPtr, TnmSnmpTypeToString(vbPtr));
	Tcl_DStringAppendElement(dstPtr, TnmSnmpValueToString(vbPtr));
	Tcl_DStringEndSublist(dstPtr);
    }

    return Tcl_DStringValue(dstPtr);
}
