/*
 * tnmSnmpTcl.c --
 *
 *	This module implements the Tcl command interface for the SNMP
 *	protocol engine. The Tcl command interface is based on the
 *	concepts of SNMPv2. The SNMP engine translates SNMPv2 requests
 *	automatically into SNMPv1 requests if necessary.
 *
 * 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: tnmSnmpTcl.c 859 1997-09-22 22:06:30Z schoenw $
 */

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

/*
 * The global variable TnmSnmp list contains all existing
 * session handles.
 */

TnmSnmp *tnmSnmpList = NULL;

int hexdump = 0;

/*
 * Every Tcl interpreter has an associated SnmpControl record. It
 * keeps track of the aliases known by the interpreter. The name
 * tnmSnmpControl is used to get/set the SnmpControl record.
 */

static char tnmSnmpControl[] = "tnmSnmpControl";

typedef struct SnmpControl {
    Tcl_HashTable aliasTable;	/* The hash table with SNMP aliases. */
} SnmpControl;

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

static void
AssocDeleteProc	_ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp));

static void
AsyncCmdProc	_ANSI_ARGS_((TnmSnmp *session, TnmSnmpPdu *pdu, 
			     ClientData clientData));
static void
AsyncWalkProc	_ANSI_ARGS_((TnmSnmp *session, TnmSnmpPdu *pdu, 
			     ClientData clientData));
static void
PduInit		_ANSI_ARGS_((TnmSnmpPdu *pduPtr, TnmSnmp *session, int type));

static void
PduFree		_ANSI_ARGS_((TnmSnmpPdu *pduPtr));

static Tcl_Obj*
GetOption	_ANSI_ARGS_((Tcl_Interp *interp, ClientData object, 
			     int option));
static int
SetOption	_ANSI_ARGS_((Tcl_Interp *interp, ClientData object, 
			     int option, Tcl_Obj *objPtr));
static int
FindSessions	_ANSI_ARGS_((Tcl_Interp *interp, 
			     int objc, Tcl_Obj *CONST objv[]));
static int
GeneratorCmd	_ANSI_ARGS_((ClientData	clientData, Tcl_Interp *interp,
			     int objc, Tcl_Obj *CONST objv[]));
static int
ListenerCmd	_ANSI_ARGS_((ClientData	clientData, Tcl_Interp *interp,
			     int objc, Tcl_Obj *CONST objv[]));
static int
NotifierCmd	_ANSI_ARGS_((ClientData	clientData, Tcl_Interp *interp,
			     int objc, Tcl_Obj *CONST objv[]));
static int
ResponderCmd	_ANSI_ARGS_((ClientData	clientData, Tcl_Interp *interp,
			     int objc, Tcl_Obj *CONST objv[]));
static int 
WaitSession	_ANSI_ARGS_((Tcl_Interp *interp, TnmSnmp *session, int id));

static void
DestroySession	_ANSI_ARGS_((ClientData clientdata));

static int
Notify		_ANSI_ARGS_((Tcl_Interp *interp, TnmSnmp *session, int type,
			     Tcl_Obj *oid, Tcl_Obj *vbl, Tcl_Obj *script));
static int
Request		_ANSI_ARGS_((Tcl_Interp *interp, TnmSnmp *session, int type,
			     int n, int m, Tcl_Obj *vbl, Tcl_Obj *cmd));
static int
AsyncWalk	_ANSI_ARGS_((Tcl_Interp *interp, TnmSnmp *session,
			     Tcl_Obj *vbl, Tcl_Obj *cmd));
static int
SyncWalk	_ANSI_ARGS_((Tcl_Interp *interp, TnmSnmp *session,
			     Tcl_Obj *varName, Tcl_Obj *vbl, Tcl_Obj *cmd));
static int
ExpandTable	_ANSI_ARGS_((Tcl_Interp *interp, 
			     char *tList, Tcl_DString *dst));
static int
ExpandScalars	_ANSI_ARGS_((Tcl_Interp *interp, 
			     char *sList, Tcl_DString *dst));
static int
Table		_ANSI_ARGS_((Tcl_Interp *interp, TnmSnmp *session,
			     char *table, char *arrayName));
static int
Scalars		_ANSI_ARGS_((Tcl_Interp *interp, TnmSnmp *session,
			     char *group, char *arrayName));
static void
ScalarSetVar	_ANSI_ARGS_((Tcl_Interp *interp, char *vbl,
			     char *varName, Tcl_DString *result));

/*
 * The options used to configure snmp session objects.
 */

enum options {
    optAddress, optPort, optVersion, optAlias, optTags, optEnterprise,
    optReadCommunity, optWriteCommunity, 
#ifdef TNM_SNMPv2U
    optUser, optPassword, optContext, 
#endif
    optTimeout, optRetries, optWindow, optDelay,
#ifdef TNM_SNMP_BENCH
    optRtt, optSendSize, optRecvSize
#endif
};

static TnmTable generatorOptionTable[] = {
    { optAddress,	"-address" },
    { optPort,		"-port" },
    { optVersion,	"-version" },
    { optReadCommunity,	"-community" },
    { optWriteCommunity,"-writecommunity" },
#ifdef TNM_SNMPv2U
    { optUser,		"-user" },
    { optPassword,	"-password" },
    { optContext,	"-context" },
#endif
    { optAlias,		"-alias" },
    { optTimeout,	"-timeout" },
    { optRetries,	"-retries" },
    { optWindow,	"-window" },
    { optDelay,		"-delay" },
    { optTags,		"-tags" },
#ifdef TNM_SNMP_BENCH
    { optRtt,		"-rtt" },
    { optSendSize,	"-sendSize" },
    { optRecvSize,	"-recvSize" },
#endif
    { 0, NULL }
};

static TnmConfig generatorConfig = {
    generatorOptionTable,
    SetOption,
    GetOption
};

static TnmTable responderOptionTable[] = {
    { optPort,		"-port" },
    { optVersion,	"-version" },
    { optReadCommunity,	"-community" },
    { optWriteCommunity,"-writecommunity" },
#ifdef TNM_SNMPv2U
    { optUser,		"-user" },
    { optPassword,	"-password" },
    { optContext,	"-context" },
#endif
    { optAlias,		"-alias" },
    { optTimeout,	"-timeout" },
    { optRetries,	"-retries" },
    { optWindow,	"-window" },
    { optDelay,		"-delay" },
    { optTags,		"-tags" },
    { optEnterprise,	"-enterprise" },
    { 0, NULL }
};

static TnmConfig responderConfig = {
    responderOptionTable,
    SetOption,
    GetOption
};

static TnmTable notifierOptionTable[] = {
    { optAddress,	"-address" },
    { optPort,		"-port" },
    { optVersion,	"-version" },
    { optReadCommunity,	"-community" },
#ifdef TNM_SNMPv2U
    { optUser,		"-user" },
    { optPassword,	"-password" },
    { optContext,	"-context" },
#endif
    { optAlias,		"-alias" },
    { optTimeout,	"-timeout" },
    { optRetries,	"-retries" },
    { optWindow,	"-window" },
    { optDelay,		"-delay" },
    { optTags,		"-tags" },
    { 0, NULL }
};

static TnmConfig notifierConfig = {
    notifierOptionTable,
    SetOption,
    GetOption
};

static TnmTable listenerOptionTable[] = {
    { optPort,		"-port" },
    { optVersion,	"-version" },
    { optReadCommunity,	"-community" },
#ifdef TNM_SNMPv2U
    { optUser,		"-user" },
    { optPassword,	"-password" },
    { optContext,	"-context" },
#endif
    { optAlias,		"-alias" },
    { optTags,		"-tags" },
    { 0, NULL }
};

static TnmConfig listenerConfig = {
    listenerOptionTable,
    SetOption,
    GetOption
};

/*
 * The following structure describes a Tcl command that should be
 * evaluated once we receive a response for a SNMP request.
 */

typedef struct EvalCmd {
    Tcl_Interp *interp;
    char *cmd;
    TnmVector vector;
} EvalCmd;

/*
 *----------------------------------------------------------------------
 *
 * AssocDeleteProc --
 *
 *	This procedure is called when a Tcl interpreter gets destroyed
 *	so that we can clean up the data associated with this interpreter.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
AssocDeleteProc(clientData, interp)
    ClientData clientData;
    Tcl_Interp *interp;
{
    SnmpControl *control = (SnmpControl *) clientData;
    Tcl_HashEntry *entryPtr;
    Tcl_HashSearch search;

    /*
     * Note, we do not care about job objects since Tcl first
     * removes all commands before calling this delete procedure.
     * However, we have to delete the timer to make sure that
     * no further events are processed for this interpreter.
     */

    if (control) {
	do {
	    entryPtr = Tcl_FirstHashEntry(&control->aliasTable, &search);
	    if (entryPtr) {
		ckfree((char *) Tcl_GetHashValue(entryPtr));
		Tcl_DeleteHashEntry(entryPtr);
	    }
	} while (entryPtr);
	Tcl_DeleteHashTable(&control->aliasTable);
	ckfree((char *) control);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * AsyncCmdProc --
 *
 *	This procedure is called once we have received the response
 *	for an asynchronous SNMP request. It evaluates a Tcl script.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Arbitrary side effects since commands are evaluated.
 *
 *----------------------------------------------------------------------
 */

static void
AsyncCmdProc(session, pdu, clientData)
    TnmSnmp *session;
    TnmSnmpPdu *pdu;
    ClientData clientData;
{
    EvalCmd *ec = (EvalCmd *) clientData;
    TnmSnmpEvalCallback(ec->interp, session, pdu, ec->cmd,
			NULL, NULL, NULL, NULL);
    ckfree((char *) ec);
}

/*
 *----------------------------------------------------------------------
 *
 * AsyncWalkProc --
 *
 *	This procedure is called once we have received the response
 *	during an asynchronous SNMP walk. It evaluates a Tcl script
 *	and starts another SNMP getnext if we did not reach the end
 *	of the MIB view.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Arbitrary side effects since commands are evaluated.
 *
 *----------------------------------------------------------------------
 */

static void
AsyncWalkProc(session, pdu, clientData)
    TnmSnmp *session;
    TnmSnmpPdu *pdu;
    ClientData clientData;
{
    EvalCmd *ec = (EvalCmd *) clientData;
    Tcl_Interp *interp = ec->interp;
    char *command = ec->cmd;
    int i;

    if (pdu->errorStatus == TNM_SNMP_NOERROR) {
	TnmVector vector;
	TnmVectorInit(&vector);
	TnmSnmpVarBindFromString(interp, &vector, 
				 Tcl_DStringValue(&pdu->varbind));
	for (i = 0; i < TnmVectorSize(&vector); i++) {
	    TnmSnmpVarBind *vbPtr = 
		(TnmSnmpVarBind *) TnmVectorGet(&vector, i);
	    TnmSnmpVarBind *stPtr = 
		(TnmSnmpVarBind *) TnmVectorGet(&ec->vector, i);
	    if (TnmSnmpValidException(vbPtr->syntax) 
		|| !TnmOidInTree(&stPtr->oid, &vbPtr->oid)) {
		TnmSnmpVarBindFree(&vector);
		TnmVectorFree(&vector);
		goto done;
	    }
	}
	TnmSnmpVarBindFree(&vector);
	TnmVectorFree(&vector);
	TnmSnmpEvalCallback(interp, session, pdu, command,
			    NULL, NULL, NULL, NULL);
	pdu->type = TNM_SNMP_GETNEXT;
	pdu->requestId = TnmSnmpGetRequestId();
	(void) TnmSnmpEncode(interp, session, pdu, AsyncWalkProc, 
			     (ClientData) ec);
	return;
    }

done:
    TnmSnmpVarBindFree(&ec->vector);
    TnmVectorFree(&ec->vector);
    ckfree((char *) ec);
}

/*
 *----------------------------------------------------------------------
 *
 * PduInit --
 *
 *	This procedure initializes an SNMP PDU.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
PduInit(pduPtr, session, type)
    TnmSnmpPdu *pduPtr;
    TnmSnmp *session;
    int type;
{
    pduPtr->addr = session->maddr;
    pduPtr->type = type;
    pduPtr->requestId = TnmSnmpGetRequestId();
    pduPtr->errorStatus = TNM_SNMP_NOERROR;
    pduPtr->errorIndex = 0;    
    pduPtr->trapOID = NULL;
    Tcl_DStringInit(&pduPtr->varbind);

#ifdef TNM_SNMP_BENCH
    memset((char *) &session->stats, 0, sizeof(session->stats));
#endif
}

/*
 *----------------------------------------------------------------------
 *
 * PduFree --
 *
 *	This procedure frees all data associated with an SNMP PDU.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
PduFree(pduPtr)
    TnmSnmpPdu *pduPtr;
{
    if (pduPtr->trapOID) ckfree(pduPtr->trapOID);
    Tcl_DStringFree(&pduPtr->varbind);
}

/*
 *----------------------------------------------------------------------
 *
 * GetOption --
 *
 *	This procedure retrieves the value of a session option.
 *
 * Results:
 *	A pointer to the value formatted as a string.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Tcl_Obj*
GetOption(interp, object, option)
    Tcl_Interp *interp;
    ClientData object;
    int option;
{
    TnmSnmp *session = (TnmSnmp *) object;

    switch ((enum options) option) {
    case optAddress:
	return Tcl_NewStringObj(inet_ntoa(session->maddr.sin_addr), -1);
    case optPort:
	return Tcl_NewIntObj(ntohs(session->maddr.sin_port));
    case optVersion:
	return Tcl_NewStringObj(TnmGetTableValue(tnmSnmpVersionTable, 
						 session->version), -1);
    case optReadCommunity:
	if (session->version!=TNM_SNMPv1 && session->version!=TNM_SNMPv2C) {
	    return NULL;
	}
	return Tcl_NewStringObj(session->readCommunity, -1);
    case optWriteCommunity:
	if (session->version!=TNM_SNMPv1 && session->version!=TNM_SNMPv2C) {
	    return NULL;
	}
	return Tcl_NewStringObj(session->writeCommunity, -1);
#ifdef TNM_SNMPv2U
    case optUser:
	if (session->version != TNM_SNMPv2U) {
	    return NULL;
	}
	return Tcl_NewStringObj(session->userName, session->userNameLen);
    case optPassword:
	if (session->version != TNM_SNMPv2U) {
	    return NULL;
	}
	return Tcl_NewStringObj(session->password, -1);
    case optContext:
	if (session->version != TNM_SNMPv2U) {
	    return NULL;
	}
	return Tcl_NewStringObj(session->cntxt, session->cntxtLen);
#endif
    case optTimeout:
	return Tcl_NewIntObj(session->timeout);
    case optRetries:
	return Tcl_NewIntObj(session->retries);
    case optWindow:
	return Tcl_NewIntObj(session->window);
    case optDelay:
	return Tcl_NewIntObj(session->delay);
    case optTags:
	return session->tags ? session->tags : Tcl_NewObj();
    case optEnterprise:
	return Tcl_NewStringObj(TnmOidToString(&session->enterpriseOid), -1);
#ifdef TNM_SNMP_BENCH
    case optRtt:
	return Tcl_NewIntObj(
	(session->stats.recvTime.sec - session->stats.sendTime.sec) * 1000000
	+ (session->stats.recvTime.usec - session->stats.sendTime.usec));
    case optSendSize:
	return Tcl_NewIntObj(session->stats.sendSize);
    case optRecvSize:
	return Tcl_NewIntObj(session->stats.recvSize);
#endif
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * SetOption --
 *
 *	This procedure modifies a single option of a session object.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
SetOption(interp, object, option, objPtr)
    Tcl_Interp *interp;
    ClientData object;
    int option;
    Tcl_Obj *objPtr;
{
    TnmSnmp *session = (TnmSnmp *) object;
    int num, len;
    char *val;

    SnmpControl *control = (SnmpControl *) 
	Tcl_GetAssocData(interp, tnmSnmpControl, NULL);

    switch ((enum options) option) {
    case optAddress:
	return TnmSetIPAddress(interp, Tcl_GetStringFromObj(objPtr, NULL),
			       &session->maddr);
    case optPort:
	return TnmSetIPPort(interp, "udp", Tcl_GetStringFromObj(objPtr, NULL),
			    &session->maddr);
    case optVersion:
	num = TnmGetTableKeyFromObj(interp, tnmSnmpVersionTable, 
				    objPtr, "SNMP version");
	if (num == -1) {
	    return TCL_ERROR;
	}
	session->version = num;
	return TCL_OK;
    case optReadCommunity:
#ifdef TNM_SNMPv2U
	if (session->version == TNM_SNMPv2U) {
	    session->version = TNM_SNMPv1;
	}
#endif
	if (session->readCommunity) ckfree(session->readCommunity);
	session->readCommunity = ckstrdup(Tcl_GetStringFromObj(objPtr, NULL));
	return TCL_OK;
    case optWriteCommunity:
#ifdef TNM_SNMPv2U
	if (session->version == TNM_SNMPv2U) {
	    session->version = TNM_SNMPv1;
	}
#endif
	if (session->writeCommunity) ckfree(session->writeCommunity);
	val = Tcl_GetStringFromObj(objPtr, &len);
	session->writeCommunity = len ? ckstrdup(val) : NULL;
	return TCL_OK;
#ifdef TNM_SNMPv2U
    case optUser:
	session->version = TNM_SNMPv2U;
	val = Tcl_GetStringFromObj(objPtr, &len);
	if (len > USEC_MAX_USER) {
	    Tcl_SetResult(interp, "user name too long", TCL_STATIC);
	    return TCL_ERROR;
	}
	session->userNameLen = len;
	memcpy(session->userName, val, session->userNameLen);
	return TCL_OK;
    case optPassword:
	session->version = TNM_SNMPv2U;
	if (session->password) {
	    ckfree(session->password);
	    session->password = NULL;
	}
	val = Tcl_GetStringFromObj(objPtr, &len);
	if (len == 0) {
	    session->qos &= ~ USEC_QOS_AUTH;
	} else {
	    session->password = ckstrdup(val);
	    session->qos |= USEC_QOS_AUTH;
	}
	return TCL_OK;
    case optContext:
	val = Tcl_GetStringFromObj(objPtr, &len);
	if (len > USEC_MAX_CONTEXT) {
	    Tcl_SetResult(interp, "context name too long", TCL_STATIC);
	    return TCL_ERROR;
	}
	session->cntxtLen = len;
	memcpy(session->cntxt, val, session->cntxtLen);
	return TCL_OK;
#endif
    case optAlias: {
	Tcl_HashEntry *entryPtr;
	int code;
	char *alias;
	Tcl_DString dst;
	entryPtr = Tcl_FindHashEntry(&control->aliasTable, 
				     Tcl_GetStringFromObj(objPtr, NULL));
	if (! entryPtr) {
	    Tcl_AppendResult(interp, "unknown alias \"",
			     Tcl_GetStringFromObj(objPtr, NULL),
			     "\"", (char *) NULL);
	    return TCL_ERROR;
	}
	alias = (char *) Tcl_GetHashValue(entryPtr);
	if (! alias) {
	    Tcl_SetResult(interp, "alias loop detected", TCL_STATIC);
	    return TCL_ERROR;
	}
	Tcl_SetHashValue(entryPtr, NULL);
	Tcl_DStringInit(&dst);
	Tcl_DStringAppend(&dst, 
			  Tcl_GetCommandName(interp, session->token), -1);
	Tcl_DStringAppend(&dst, " configure ", -1);
	Tcl_DStringAppend(&dst, alias, -1);
	code = Tcl_Eval(interp, Tcl_DStringValue(&dst));
	Tcl_SetHashValue(entryPtr, alias);
	Tcl_DStringFree(&dst);
	return code;
    }
    case optTimeout:
	if (TnmGetPositiveFromObj(interp, objPtr, &num) != TCL_OK) {
	    return TCL_ERROR;
	}
	session->timeout = num;
	return TCL_OK;
    case optRetries:
	if (TnmGetUnsignedFromObj(interp, objPtr, &num) != TCL_OK) {
	    return TCL_ERROR;
	}
	session->retries = num;
	return TCL_OK;
    case optWindow:
	if (TnmGetUnsignedFromObj(interp, objPtr, &num) != TCL_OK) {
	    return TCL_ERROR;
	}
	session->window = num;
	return TCL_OK;
    case optDelay:
	if (TnmGetUnsignedFromObj(interp, objPtr, &num) != TCL_OK) {
	    return TCL_ERROR;
	}
	session->delay = num;
	return TCL_OK;
    case optTags:
	if (session->tags) {
	    Tcl_DecrRefCount(session->tags);
	}
	session->tags = objPtr;
	Tcl_IncrRefCount(objPtr);
	return TCL_OK;
    case optEnterprise: {
	TnmOid *oidPtr;
	oidPtr = TnmGetOidFromObj(interp, objPtr);
	if (! oidPtr) {
	    return TCL_ERROR;
	}
	TnmOidCopy(&session->enterpriseOid, oidPtr);
	return TCL_OK;
    }
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * FindSessions --
 *
 *	This procedure is invoked to process the "find" command
 *	option of the snmp command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

static int
FindSessions(interp, objc, objv)
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST objv[];
{
    int i, result, type = 0, version = 0;
    TnmSnmp *session;
    Tcl_Obj *listPtr;

    enum options { 
	optAddress, optPort, optTags, optType, optVersion
    } option;

    static char *optionTable[] = {
	"-address", "-port", "-tags", "-type", "-version", (char *) NULL
    };

    if (objc % 2) {
	Tcl_WrongNumArgs(interp, 2, objv, "?option value? ?option value? ...");
	return TCL_ERROR;
    }

    for (i = 2; i < objc; i++) {
	result = Tcl_GetIndexFromObj(interp, objv[i++], optionTable, 
				     "option", TCL_EXACT, (int *) &option);
	if (result != TCL_OK) {
	    return result;
	}
	switch (option) {
	case optType:
	    type = TnmGetTableKeyFromObj(interp, tnmSnmpApplTable, 
					 objv[i], "SNMP application type");
	    if (type == -1) {
		return TCL_ERROR;
	    }
	    break;
	case optVersion:
	    version = TnmGetTableKeyFromObj(interp, tnmSnmpVersionTable,
					    objv[i], "SNMP version");
	    if (version == -1) {
		return TCL_ERROR;
	    }
	    break;
	}
    }

    listPtr = Tcl_GetObjResult(interp);
    for (session = tnmSnmpList; session; session = session->nextPtr) {
	if (type && session->type != type) continue;
	if (version && session->version != version) continue;
	if (session->interp == interp) {
	    char *cmdName = Tcl_GetCommandName(interp, session->token);
	    Tcl_Obj *elemObjPtr = Tcl_NewStringObj(cmdName, -1);
	    Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr);
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Tnm_SnmpCmd --
 *
 *	This procedure is invoked to process the "snmp" command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
Tnm_SnmpObjCmd(clientData, interp, objc, objv)
    ClientData clientData;
    Tcl_Interp *interp;
    int	objc;
    Tcl_Obj *CONST objv[];
{
    static int initialized = 0;
    static unsigned nextId = 0;

    TnmSnmp *session;
    int code, result = TCL_OK;
    Tcl_Obj *listPtr;
    char *name, *pattern;

    SnmpControl *control = (SnmpControl *) 
	Tcl_GetAssocData(interp, tnmSnmpControl, NULL);

    enum commands { 
	cmdAlias, cmdArray, cmdExpand, cmdFind, cmdGenerator, cmdInfo, 
	cmdListener, cmdNotifier, cmdResponder, cmdWait, cmdWatch 
    } cmd;

    static char *cmdTable[] = {
	"alias", "array", "expand", "find", "generator", "info",
	"listener", "notifier", "responder", "wait", "watch",
	(char *) NULL
    };

    enum infos { 
	infoErrors, infoExceptions, infoPDUs, infoTypes, infoVersions 
    } info;

    static char *infoTable[] = {
	"errors", "exceptions", "pdus", "types", "versions", (char *) NULL
    };

    if (! control) {
	control = (SnmpControl *) ckalloc(sizeof(SnmpControl));
	memset((char *) control, 0, sizeof(SnmpControl));
	Tcl_InitHashTable(&control->aliasTable, TCL_STRING_KEYS);
	Tcl_SetAssocData(interp, tnmSnmpControl, AssocDeleteProc, 
			 (ClientData) control);
    }

    if (! initialized) {
	TnmSnmpSysUpTime();
	memset((char *) &tnmSnmpStats, 0, sizeof(TnmSnmpStats));
	srand(time(NULL) * getpid());
	initialized = 1;
    }

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
	return TCL_ERROR;
    }

    code = Tcl_GetIndexFromObj(interp, objv[1], cmdTable, 
			       "option", TCL_EXACT, (int *) &cmd);
    if (code != TCL_OK) {
	return code;
    }

    switch (cmd) {
    case cmdAlias: {
        Tcl_HashEntry *entryPtr;
        if (objc == 2) {
	    Tcl_HashSearch search;
	    entryPtr = Tcl_FirstHashEntry(&control->aliasTable, &search);
	    while (entryPtr) {
	        Tcl_AppendElement(interp,
			  Tcl_GetHashKey(&control->aliasTable, entryPtr));
	        entryPtr = Tcl_NextHashEntry(&search);
	    }
	} else if (objc == 3) {
	    char *name = Tcl_GetStringFromObj(objv[2], NULL);
	    entryPtr = Tcl_FindHashEntry(&control->aliasTable, name);
	    if (entryPtr) {
	        Tcl_SetResult(interp, (char *) Tcl_GetHashValue(entryPtr),
			      TCL_STATIC);
	    }
	} else if (objc == 4) {
	    char *name = Tcl_GetStringFromObj(objv[2], NULL);
	    char *value = Tcl_GetStringFromObj(objv[3], NULL);
	    int isNew;
	    entryPtr = Tcl_CreateHashEntry(&control->aliasTable, name, &isNew);
	    if (!isNew) {
		ckfree((char *) Tcl_GetHashValue(entryPtr));
	    }
	    if (*value == '\0') {
		Tcl_DeleteHashEntry(entryPtr);
	    } else {
		Tcl_SetHashValue(entryPtr, ckstrdup(value));
	    }
	} else {
	    Tcl_WrongNumArgs(interp, 2, objv, "?agent? ?config?");
	    result = TCL_ERROR;
	    break;
	}
	break;
    }

#if 0
    case cmdArray: {
	Tcl_DString ds;
	Tcl_DStringInit(&ds);
	if (TnmMibLoad(interp) != TCL_OK) {
            result = TCL_ERROR;
            break;
        }
        if (argc != 5) {
            TnmWrongNumArgs(interp, 2, argv, "set arrayName varBindList");
            result = TCL_ERROR;
            break;
        }
	Tcl_DStringGetResult(interp, &ds);
	ScalarSetVar(interp, argv[4], argv[3], &ds);
	Tcl_DStringResult(interp, &ds);
	break;
    }

    case cmdExpand: {
	TnmVector vector;
        if (argc != 3) {
	    TnmWrongNumArgs(interp, 2, argv, "varBindList");
	    result = TCL_ERROR;
	    break;
	}
	if (TnmMibLoad(interp) != TCL_OK) {
	    result = TCL_ERROR;
	    break;
	}
	TnmVectorInit(&vector);
	result = TnmSnmpVarBindFromString(interp, &vector, argv[2]);
	if (result == TCL_OK) {
	    Tcl_SetResult(interp, TnmSnmpVarBindToString(&vector), TCL_STATIC);
	}
	TnmSnmpVarBindFree(&vector);
	TnmVectorFree(&vector);
	break;
    }
#endif

    case cmdFind:
	result = FindSessions(interp, objc, objv);
	break;

    case cmdGenerator:

	/* 
	 * Initialize the SNMP manager module by opening a socket for
	 * manager communication. Polulate the MIB module with the
	 * set of default MIB definitions.
	 */

	if (TnmMibLoad(interp) != TCL_OK) {
	    result = TCL_ERROR;
	    break;
	}
	if (TnmSnmpManagerOpen(interp) != TCL_OK) {
	    result = TCL_ERROR;
	    break;
	}
	
	session = TnmSnmpCreateSession(TNM_SNMP_GENERATOR);
	session->interp = interp;
	session->config = &generatorConfig;
	result = TnmSetConfig(interp, session->config,
			      (ClientData) session, objc, objv);
	if (result != TCL_OK) {
	    TnmSnmpDeleteSession(session);
	    break;
	}
#ifdef TNM_SNMPv2U
	if (session->version == TNM_SNMPv2U && session->qos & USEC_QOS_AUTH) {
	    TnmSnmpUsecGetAgentID(session);
	}
#endif

	session->nextPtr = tnmSnmpList;
	tnmSnmpList = session;

	/*
	 * Finally create a Tcl command for this session.
	 */

	name = TnmGetHandle(interp, "snmp", &nextId);
	session->token = Tcl_CreateObjCommand(interp, name, GeneratorCmd,
			  (ClientData) session, DestroySession);
	Tcl_SetStringObj(Tcl_GetObjResult(interp), name, -1);
	break;

    case cmdInfo:
	if (objc < 3 || objc > 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "subject ?pattern?");
	    result = TCL_ERROR;
            break;
	}
	code = Tcl_GetIndexFromObj(interp, objv[2], infoTable, 
				   "option", TCL_EXACT, (int *) &info);
	if (code != TCL_OK) {
	    break;
	}
	pattern = (objc == 4) ? Tcl_GetStringFromObj(objv[3], NULL) : NULL;
	listPtr = Tcl_GetObjResult(interp);
	switch (info) {
	case infoErrors:
	    TnmListFromTable(tnmSnmpErrorTable, listPtr, pattern);
	    break;
	case infoExceptions:
	    TnmListFromTable(tnmSnmpExceptionTable, listPtr, pattern);
	    break;
	case infoPDUs:
	    TnmListFromTable(tnmSnmpPDUTable, listPtr, pattern);
	    break;
	case infoTypes:
	    TnmListFromTable(tnmSnmpTypeTable, listPtr, pattern);
	    break;
	case infoVersions:
	    TnmListFromTable(tnmSnmpVersionTable, listPtr, pattern);
	    break;
	}
	break;

    case cmdListener:
	if (TnmMibLoad(interp) != TCL_OK) {
	    result = TCL_ERROR;
	    break;
	}
	session = TnmSnmpCreateSession(TNM_SNMP_LISTENER);
	session->interp = interp;
	session->config = &listenerConfig;
	result = TnmSetConfig(interp, session->config,
			      (ClientData) session, objc, objv);
	if (result != TCL_OK) {
	    TnmSnmpDeleteSession(session);
	    break;
	}
#ifdef TNM_SNMPv2U
	if (session->version == TNM_SNMPv2U && session->qos & USEC_QOS_AUTH) {
	    TnmSnmpUsecGetAgentID(session);
	}
#endif

	session->nextPtr = tnmSnmpList;
	tnmSnmpList = session;

	/*
	 * Finally create a Tcl command for this session.
	 */
	
	name = TnmGetHandle(interp, "snmp", &nextId);
	session->token = Tcl_CreateObjCommand(interp, name, ListenerCmd,
			  (ClientData) session, DestroySession);
	Tcl_SetStringObj(Tcl_GetObjResult(interp), name, -1);
	break;

    case cmdNotifier:
	if (TnmMibLoad(interp) != TCL_OK) {
	    result = TCL_ERROR;
	    break;
	}
	if (TnmSnmpManagerOpen(interp) != TCL_OK) {
	    result = TCL_ERROR;
	    break;
	}

	session = TnmSnmpCreateSession(TNM_SNMP_NOTIFIER);
	session->interp = interp;
	session->config = &notifierConfig;
	result = TnmSetConfig(interp, session->config,
			      (ClientData) session, objc, objv);
	if (result != TCL_OK) {
	    TnmSnmpDeleteSession(session);
	    break;
	}
#ifdef TNM_SNMPv2U
	if (session->version == TNM_SNMPv2U && session->qos & USEC_QOS_AUTH) {
	    TnmSnmpUsecGetAgentID(session);
	}
#endif

	session->nextPtr = tnmSnmpList;
	tnmSnmpList = session;

	/*
	 * Finally create a Tcl command for this session.
	 */
	
	name = TnmGetHandle(interp, "snmp", &nextId);
	session->token = Tcl_CreateObjCommand(interp, name, NotifierCmd,
			  (ClientData) session, DestroySession);
	Tcl_SetStringObj(Tcl_GetObjResult(interp), name, -1);
	break;

    case cmdResponder:
	if (TnmMibLoad(interp) != TCL_OK) {
	    result = TCL_ERROR;
	    break;
	}
	session = TnmSnmpCreateSession(TNM_SNMP_RESPONDER);
	session->interp = interp;
	session->config = &responderConfig;
	result = TnmSetConfig(interp, session->config, 
			      (ClientData) session, objc, objv);
	if (result == TCL_OK) {
	    result = TnmSnmpAgentInit(interp, session);
	}
	if (result != TCL_OK) {
	    TnmSnmpDeleteSession(session);
	    break;
	}
#ifdef TNM_SNMPv2U
	if (session->version == TNM_SNMPv2U && session->qos & USEC_QOS_AUTH) {
	    TnmSnmpUsecGetAgentID(session);
	}
#endif

	session->nextPtr = tnmSnmpList;
	tnmSnmpList = session;

	/*
	 * Finally create a Tcl command for this session.
	 */
	
	name = TnmGetHandle(interp, "snmp", &nextId);
	session->token = Tcl_CreateObjCommand(interp, name, ResponderCmd,
			  (ClientData) session, DestroySession);
	Tcl_SetStringObj(Tcl_GetObjResult(interp), name, -1);
	break;

    case cmdWait:
        if (objc != 2) {
	    Tcl_WrongNumArgs(interp, 2, objv, (char *) NULL);
	    result = TCL_ERROR;
	    break;
	}
      repeat:
	for (session = tnmSnmpList; session; session = session->nextPtr) {
	    if (TnmSnmpQueueRequest(session, NULL)) {
		Tcl_DoOneEvent(0);
		goto repeat;
	    }
	}
	break;

    case cmdWatch:
	if (objc > 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "?bool?");
            result = TCL_ERROR;
	    break;
        }
	if (objc == 3) {
	    result = Tcl_GetBooleanFromObj(interp, objv[2], &hexdump);
	    if (result != TCL_OK) {
		break;
	    }
	}
	Tcl_SetBooleanObj(Tcl_GetObjResult(interp), hexdump);
	break;
    }

    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * GeneratorCmd --
 *
 *	This procedure is invoked to process a manager command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

static int
GeneratorCmd(clientData, interp, objc, objv)
    ClientData clientData;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST objv[];
{
    TnmSnmp *session = (TnmSnmp *) clientData;
    int cmd, code, nonReps, maxReps;

    enum commands {
	cmdCget, cmdConfigure, cmdDestroy, cmdGet, cmdGetBulk, cmdGetNext, 
	cmdScalars, cmdSet, cmdWait, cmdWalk, cmdZZZ
    };

    static char *cmdTable[] = {
	"cget", "configure", "destroy", "get", "getbulk", "getnext",
 	"scalars", "set", "wait", "walk", "zzz", (char *) NULL
    };

    if (objc < 2) {
 	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
	return TCL_ERROR;
    }

    code = Tcl_GetIndexFromObj(interp, objv[1], cmdTable, 
 			       "option", TCL_EXACT, (int *) &cmd);
    if (code != TCL_OK) {
 	return code;
    }

    switch (cmd) {
    case cmdCget:
	return TnmGetConfig(interp, session->config,
			    (ClientData) session, objc, objv);

    case cmdConfigure:

	/*
	 * This call to WaitSession() is needed to ensure that a 
	 * configuration change does not affect queued requests.
	 */

	Tcl_Preserve((ClientData) session);
	WaitSession(interp, session, 0);
 	code = TnmSetConfig(interp, session->config,
			    (ClientData) session, objc, objv);
	if (code != TCL_OK) {
	    Tcl_Release((ClientData) session);
	    return TCL_ERROR;
	}
#ifdef TNM_SNMPv2U
	if (session->version == TNM_SNMPv2U && session->qos & USEC_QOS_AUTH) {
	    TnmSnmpUsecGetAgentID(session);
	}
#endif
	Tcl_Release((ClientData) session);
	return TCL_OK;

    case cmdDestroy:
	if (objc != 2) {
	    Tcl_WrongNumArgs(interp, 2, objv, (char *) NULL);
	    return TCL_ERROR;
	}
	Tcl_DeleteCommandFromToken(interp, session->token);
	return TCL_OK;

    case cmdGet:
	if (objc < 3 || objc > 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "varBindList ?script?");
	    return TCL_ERROR;
	}
	return Request(interp, session, TNM_SNMP_GET, 0, 0,
		       objv[2], (objc == 4) ? objv[3] : NULL);

    case cmdGetNext:
	if (objc < 3 || objc > 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "varBindList ?script?");
	    return TCL_ERROR;
	}
	return Request(interp, session, TNM_SNMP_GETNEXT, 0, 0, 
		       objv[2], (objc == 4) ? objv[3] : NULL);

    case cmdGetBulk:
	if (objc < 5 || objc > 6) {
	    Tcl_WrongNumArgs(interp, 2, objv, 
		    "nonRepeaters maxRepetitions varBindList ?script?");
	    return TCL_ERROR;
	}
	if (TnmGetUnsignedFromObj(interp, objv[2], &nonReps) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (TnmGetPositiveFromObj(interp, objv[3], &maxReps) != TCL_OK) {
	    return TCL_ERROR;
	}
	return Request(interp, session, TNM_SNMP_GETBULK, nonReps, maxReps,
		       objv[4], (objc == 6) ? objv[5] : NULL);

    case cmdSet:
	if (objc < 3 || objc > 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "varBindList ?script?");
	    return TCL_ERROR;
	}
	return Request(interp, session, TNM_SNMP_SET, 0, 0,
		       objv[2], (objc == 4) ? objv[3] : NULL);

    case cmdWait:
	if (objc == 2) {
	    return WaitSession(interp, session, 0);
	} else if (objc == 3) {
	    int request;
	    if (Tcl_GetIntFromObj(interp, objv[2], &request) != TCL_OK) {
		return TCL_ERROR;
	    }
	    return WaitSession(interp, session, request);
	}
	Tcl_WrongNumArgs(interp, 2, objv, "?request?");
	return TCL_ERROR;

    case cmdWalk:
	if (objc != 5) {
	    Tcl_WrongNumArgs(interp, 2, objv, "varName list command");
	    return TCL_ERROR;
	}
	return SyncWalk(interp, session, objv[2], objv[3], objv[4]);

    case cmdZZZ:
	if (objc != 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "list command");
	    return TCL_ERROR;
	}
	return AsyncWalk(interp, session, objv[2], objv[3]);
    }

    return TCL_OK;

#if 0
    /*
     * All commands handled below need a well configured session.
     * Check if we have one and return an error if something is
     * still incomplete.
     */

    if (Configured(interp, session) != TCL_OK) {
	return TCL_ERROR;
    }

    switch (cmd) {
    case cmdTbl:
	if (argc != 4) {
	    TnmWrongNumArgs(interp, 2, argv, "table arrayName");
	    return TCL_ERROR;
	}
	return Table(interp, session, argv[2], argv[3]);

    case cmdScalars:
	if (argc != 4) {
	    TnmWrongNumArgs(interp, 2, argv, "group arrayName");
	    return TCL_ERROR;
	}
	return Scalars(interp, session, argv[2], argv[3]);
    }
#endif

#if 0
    } else if (strcmp(argv[1], "bind") == 0) {
	int event;
	TnmSnmpBinding *bindPtr = session->bindPtr;
	if (argc < 4 || argc > 5) {
	    TnmWrongNumArgs(interp, 2, argv, "label event ?command?");
	    return TCL_ERROR;
	}
	event = TnmGetTableKey(tnmSnmpEventTable, argv[3]);
	if (argv[2][0] == '\0') {
	    
	    if (event < 0 || (event & TNM_SNMP_GENERIC_BINDINGS) == 0) {
		Tcl_AppendResult(interp, "unknown event \"", argv[3], 
				 "\": use trap, inform, recv, send, ",
				 "begin, end, or report", (char *) NULL);
		return TCL_ERROR;
	    }
	    if (event & (TNM_SNMP_TRAP_EVENT | TNM_SNMP_INFORM_EVENT)) {
		if (session->type == TNM_SNMP_LISTENER) {
		    if (TnmSnmpTrapOpen(interp) != TCL_OK) {
			return TCL_ERROR;
		    }
		}
	    }
	    while (bindPtr) {
		if (bindPtr->event == event) break;
		bindPtr = bindPtr->nextPtr;
	    }
	    if (argc == 4) {
		if (bindPtr) {
		    Tcl_SetResult(interp, bindPtr->command, TCL_STATIC);
		}
	    } else {
		if (! bindPtr) {
		    bindPtr = (TnmSnmpBinding *) ckalloc(sizeof(TnmSnmpBinding));
		    memset((char *) bindPtr, 0, sizeof(TnmSnmpBinding));
		    bindPtr->event = event;
		    bindPtr->nextPtr = session->bindPtr;
		    session->bindPtr = bindPtr;
		}
		if (bindPtr->command) {
		    ckfree (bindPtr->command);
		}
		bindPtr->command = ckstrdup(argv[4]);
	    }
	} else {

	    char *oidstr = TnmMibGetOid(argv[2]);
	    int code;
	    
	    if (!oidstr) {
		Tcl_AppendResult(interp, "no object \"", argv[2], "\"",
				 (char *) NULL);
		return TCL_ERROR;
	    }
	    
	    if (event < 0 || (event & TNM_SNMP_INSTANCE_BINDINGS) == 0) {
		Tcl_AppendResult(interp, "unknown event \"", argv[3],
				 "\": use get, set, create, check, ",
				 "commit, rollback, format, or scan", 
				 (char *) NULL);
		return TCL_ERROR;
	    }
	    
	    if (argc == 5) {
		TnmOid oid;
		TnmOidInit(&oid);
		code = TnmOidFromString(&oid, oidstr);
		if (code == TCL_OK) {
		    code = TnmSnmpSetNodeBinding(session, &oid, event, 
						 argv[4]);
		}
		TnmOidFree(&oid);
		if (code != TCL_OK) {
		    Tcl_AppendResult(interp, "unknown instance \"",
				     argv[2], "\"", (char *) NULL);
		    return TCL_ERROR;
		}
	    } else {
		char *cmd = NULL;
		TnmOid oid;
		TnmOidInit(&oid);
		code = TnmOidFromString(&oid, oidstr);
		if (code == TCL_OK) {
		    cmd = TnmSnmpGetNodeBinding(session, &oid, event);
		}
		TnmOidFree(&oid);
		Tcl_SetResult(interp, (cmd) ? cmd : "", TCL_STATIC);
	    }
	}
	return TCL_OK;

    } else if (strcmp(argv[1], "instance") == 0) {
	int code;
        if (argc < 4 || argc > 5) {
	    TnmWrongNumArgs(interp, 2, argv, "oid varName ?defval?");
	    return TCL_ERROR;
	}
#endif

#if 0
	
        if (! session->agentInterp) {
	    char *name = Tcl_GetCommandName(interp, session->token);
	    Tcl_AppendResult(interp, "invalid agent session \"", 
			     name, "\"", (char *) NULL);
	    return TCL_ERROR;
	}
	code = TnmSnmpCreateNode(session->agentInterp, argv[2], argv[3],
				 (argc > 4) ? argv[4] : "");
	if (code != TCL_OK) {
	    if (interp != session->agentInterp) {
		Tcl_SetResult(interp, session->agentInterp->result, 
			      TCL_VOLATILE);
		Tcl_ResetResult(session->agentInterp);
	    }
	    return code;
	}
	return TCL_OK;
    }

    Tcl_AppendResult(interp, "bad option \"", argv[1], "\": should be ",
		     "configure, cget, wait, destroy, ",
		     "get, getnext, getbulk, set, trap, inform, walk, ",
		     "scalars, table, instance, or bind", (char *) NULL);
    return TCL_ERROR;
#endif
}

/*
 *----------------------------------------------------------------------
 *
 * ListenerCmd --
 *
 *	This procedure is invoked to process a listener command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

static int
ListenerCmd(clientData, interp, objc, objv)
    ClientData clientData;
    Tcl_Interp *interp;
    int	objc;
    Tcl_Obj *CONST objv[];
{
    TnmSnmp *session = (TnmSnmp *) clientData;
    int code;

    enum commands {
	cmdCget, cmdConfigure, cmdDestroy, cmdWait
    } cmd;

    static char * cmdTable[] = {
	"cget", "configure", "destroy", "wait",
	(char *) NULL
    };

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
        return TCL_ERROR;
    }

    code = Tcl_GetIndexFromObj(interp, objv[1], cmdTable, 
			       "option", TCL_EXACT, (int *) &cmd);
    if (code != TCL_OK) {
	return code;
    }

    switch (cmd) {
    case cmdCget:
	return TnmGetConfig(interp, session->config,
			    (ClientData) session, objc, objv);

    case cmdConfigure:

	/*
	 * This call to WaitSession() is needed to ensure that a 
	 * configuration change does not affect queued requests.
	 */

	Tcl_Preserve((ClientData) session);
	WaitSession(interp, session, 0);
 	code = TnmSetConfig(interp, session->config,
			    (ClientData) session, objc, objv);
	if (code != TCL_OK) {
	    Tcl_Release((ClientData) session);
	    return TCL_ERROR;
	}
#ifdef TNM_SNMPv2U
	if (session->version == TNM_SNMPv2U && session->qos & USEC_QOS_AUTH) {
	    TnmSnmpUsecGetAgentID(session);
	}
#endif
	Tcl_Release((ClientData) session);
	return TCL_OK;

    case cmdDestroy:
	if (objc != 2) {
	    Tcl_WrongNumArgs(interp, 2, objv, (char *) NULL);
	    return TCL_ERROR;
	}
	Tcl_DeleteCommandFromToken(interp, session->token);
	return TCL_OK;

    case cmdWait:
	if (objc == 2) {
	    return WaitSession(interp, session, 0);
	} else if (objc == 3) {
	    int request;
	    if (Tcl_GetIntFromObj(interp, objv[2], &request) != TCL_OK) {
		return TCL_ERROR;
	    }
	    return WaitSession(interp, session, request);
	}
	Tcl_WrongNumArgs(interp, 2, objv, "?request?");
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NotifierCmd --
 *
 *	This procedure is invoked to process a notifier command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

static int
NotifierCmd(clientData, interp, objc, objv)
    ClientData clientData;
    Tcl_Interp *interp;
    int	objc;
    Tcl_Obj *CONST objv[];
{
    TnmSnmp *session = (TnmSnmp *) clientData;
    int code;

    enum commands {
	cmdCget, cmdConfigure, cmdDestroy, cmdInform, cmdTrap, cmdWait
    } cmd;

    static char *cmdTable[] = {
	"cget", "configure", "destroy", "inform", "trap", "wait", 
	(char *) NULL
    };

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
        return TCL_ERROR;
    }

    code = Tcl_GetIndexFromObj(interp, objv[1], cmdTable, 
			       "option", TCL_EXACT, (int *) &cmd);
    if (code != TCL_OK) {
	return code;
    }

    switch (cmd) {
    case cmdCget:
	return TnmGetConfig(interp, session->config,
			    (ClientData) session, objc, objv);

    case cmdConfigure:

	/*
	 * This call to WaitSession() is needed to ensure that a 
	 * configuration change does not affect queued requests.
	 */

	Tcl_Preserve((ClientData) session);
	WaitSession(interp, session, 0);
 	code = TnmSetConfig(interp, session->config,
			    (ClientData) session, objc, objv);
	if (code != TCL_OK) {
	    Tcl_Release((ClientData) session);
	    return TCL_ERROR;
	}
#ifdef TNM_SNMPv2U
	if (session->version == TNM_SNMPv2U && session->qos & USEC_QOS_AUTH) {
	    TnmSnmpUsecGetAgentID(session);
	}
#endif
	Tcl_Release((ClientData) session);
	return TCL_OK;

    case cmdDestroy:
	if (objc != 2) {
	    Tcl_WrongNumArgs(interp, 2, objv, (char *) NULL);
	    return TCL_ERROR;
	}
	Tcl_DeleteCommandFromToken(interp, session->token);
	return TCL_OK;

    case cmdInform:
	if (objc < 4 || objc > 5) {
            Tcl_WrongNumArgs(interp, 2, objv,
			     "snmpTrapOID varBindList ?script?");
            return TCL_ERROR;
        }
	return Notify(interp, session, TNM_SNMP_INFORM, objv[2],
		      objv[3], (objc == 5) ? objv[4] : NULL);

    case cmdTrap:
	if (objc != 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "snmpTrapOID varBindList");
	    return TCL_ERROR;
	}
	return Notify(interp, session, TNM_SNMPv2_TRAP, objv[2],
		      objv[3], NULL);

    case cmdWait:
	if (objc == 2) {
	    return WaitSession(interp, session, 0);
	} else if (objc == 3) {
	    int request;
	    if (Tcl_GetIntFromObj(interp, objv[2], &request) != TCL_OK) {
		return TCL_ERROR;
	    }
	    return WaitSession(interp, session, request);
	}
	Tcl_WrongNumArgs(interp, 2, objv, "?request?");
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ResponderCmd --
 *
 *	This procedure is invoked to process a agent command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

static int
ResponderCmd(clientData, interp, objc, objv)
    ClientData clientData;
    Tcl_Interp *interp;
    int	objc;
    Tcl_Obj *CONST objv[];
{
    TnmSnmp *session = (TnmSnmp *) clientData;
    int code;

    enum commands {
	cmdCget, cmdConfigure, cmdDestroy
    } cmd;

    static char *cmdTable[] = {
	"cget", "configure", "destroy",
	(char *) NULL
    };

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
        return TCL_ERROR;
    }

    code = Tcl_GetIndexFromObj(interp, objv[1], cmdTable, 
			       "option", TCL_EXACT, (int *) &cmd);
    if (code != TCL_OK) {
	return code;
    }

    switch (cmd) {
    case cmdCget:
	return TnmGetConfig(interp, session->config,
			    (ClientData) session, objc, objv);

    case cmdConfigure:

	/*
	 * This call to WaitSession() is needed to ensure that a 
	 * configuration change does not affect queued requests.
	 */

	Tcl_Preserve((ClientData) session);
	WaitSession(interp, session, 0);
 	code = TnmSetConfig(interp, session->config,
			    (ClientData) session, objc, objv);
	if (code == TCL_OK) {
	    code = TnmSnmpAgentInit(interp, session);
	}
	if (code != TCL_OK) {
	    Tcl_Release((ClientData) session);
	    return TCL_ERROR;
	}
#ifdef TNM_SNMPv2U
	if (session->version == TNM_SNMPv2U && session->qos & USEC_QOS_AUTH) {
	    TnmSnmpUsecGetAgentID(session);
	}
#endif
	Tcl_Release((ClientData) session);
	break;

    case cmdDestroy:
	if (objc != 2) {
	    Tcl_WrongNumArgs(interp, 2, objv, (char *) NULL);
	    return TCL_ERROR;
	}
	Tcl_DeleteCommandFromToken(interp, session->token);
	break;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * WaitSession --
 *
 *	This procedure processes events until either the list of
 *	outstanding requests is empty or until the given request
 *	is no longer in the list of outstanding requests.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Tcl events are processed which can cause arbitrary side effects.
 *
 *----------------------------------------------------------------------
 */

static int
WaitSession(interp, session, request)
    Tcl_Interp *interp;
    TnmSnmp *session;
    int request;
{
    char *name = Tcl_GetCommandName(interp, session->token);

    if (! name) {
	return TCL_OK;
    }

    /*
     * Do not use the session pointer! We have to search for the
     * session name after each single event because the session
     * may be deleted as a side effect of the event.
     */

    name = ckstrdup(name);
  repeat:
    for (session = tnmSnmpList; session; session = session->nextPtr) {
	char *thisName = Tcl_GetCommandName(interp, session->token);
	if (strcmp(thisName, name) != 0) continue;
	if (! request) {
	    if (TnmSnmpQueueRequest(session, NULL)) {
		Tcl_DoOneEvent(0);
		goto repeat;
	    }
	} else {
	    if (TnmSnmpFindRequest(request)) {
		Tcl_DoOneEvent(0);
		goto repeat;
	    }
	}
    }
    
    ckfree(name);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroySession --
 *
 *	This procedure is invoked when a session handle is deleted.
 *	It frees the associated session structure and de-installs all
 *	pending events. If it is the last session, we also close the
 *	socket for manager communication.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
DestroySession(clientData)
    ClientData clientData;
{
    TnmSnmp **sPtrPtr, *session = (TnmSnmp *) clientData;

    sPtrPtr = &tnmSnmpList;
    while (*sPtrPtr && (*sPtrPtr) != session) {
	sPtrPtr = &(*sPtrPtr)->nextPtr;
    }

    if (*sPtrPtr) {
	(*sPtrPtr) = session->nextPtr;
    }

    TnmSnmpDeleteSession(session);

    if (tnmSnmpList == NULL) {
	TnmSnmpManagerClose();
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Notify --
 *
 *	This procedure sends out a notification (trap or inform).
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
Notify(interp, session, type, oid, vbl, script)
    Tcl_Interp *interp;
    TnmSnmp *session;
    int type;
    Tcl_Obj *oid;
    Tcl_Obj *vbl;
    Tcl_Obj *script;
{
    TnmSnmpPdu pdu;
    char *trapOid;
    
    PduInit(&pdu, session, type);
    trapOid = Tcl_GetStringFromObj(oid, NULL);
    if (TnmIsOid(trapOid)) {
	pdu.trapOID = ckstrdup(trapOid);
    } else {
	char *tmp = TnmMibGetOid(trapOid);
	if (! tmp) {
	    Tcl_AppendResult(interp, "unknown notification \"", 
			     trapOid, "\"", (char *) NULL);
	    PduFree(&pdu);
	    return TCL_ERROR;
	}
	pdu.trapOID = ckstrdup(tmp);
    }
    Tcl_DStringAppend(&pdu.varbind, Tcl_GetStringFromObj(vbl, NULL), -1);
    if (TnmSnmpEncode(interp, session, &pdu, NULL, NULL) != TCL_OK) {
	PduFree(&pdu);
	return TCL_ERROR;
    }
    PduFree(&pdu);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Request --
 *
 *	This procedure creates a pdu structure and calls TnmSnmpEncode
 *	to send the packet to the destination.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
Request(interp, session, type, non, max, vblObj, cmdObj)
    Tcl_Interp *interp;
    TnmSnmp *session;
    int type;
    int non;
    int max;
    Tcl_Obj *vblObj;
    Tcl_Obj *cmdObj;
{
    TnmSnmpPdu pdu;
    int code = TCL_OK;
    char *vbl = Tcl_GetStringFromObj(vblObj, NULL);
    char *cmd = cmdObj ? Tcl_GetStringFromObj(cmdObj, NULL) : NULL;

    PduInit(&pdu, session, type);
    if (type == TNM_SNMP_GETBULK) {
	pdu.errorStatus = non > 0 ? non : 0;
	pdu.errorIndex = max > 0 ? max : 0;
    }
    Tcl_DStringAppend(&pdu.varbind, vbl, -1);

    if (cmd) {
	EvalCmd *ec = (EvalCmd *) ckalloc(sizeof(EvalCmd) + strlen(cmd) + 1);
	ec->interp = interp;
	ec->cmd = (char *) ec + sizeof(EvalCmd);
	strcpy(ec->cmd, cmd);
	code = TnmSnmpEncode(interp, session, &pdu, 
			     AsyncCmdProc, (ClientData) ec);
    } else {
	code = TnmSnmpEncode(interp, session, &pdu, NULL, NULL);
    }

    PduFree(&pdu);
    return code;
}

/*
 *----------------------------------------------------------------------
 *
 * AsyncWalk --
 *
 *	This procedure walks a MIB tree. It evaluates the given Tcl
 *	command foreach varbind retrieved using getbulk requests.
 *	First, all variables contained in the list argument are
 *	converted to their OIDs. Then we start an asynchronous loop
 *	using gebulk requests until we get an error or until one
 *	returned variable starts with an OID not being a valid prefix.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
AsyncWalk(interp, session, vblObj, cmdObj)
    Tcl_Interp *interp;
    TnmSnmp *session;
    Tcl_Obj *vblObj;
    Tcl_Obj *cmdObj;
{
    TnmSnmpPdu pdu;
    int code;
    EvalCmd *ecPtr;
    char *vbl = Tcl_GetStringFromObj(vblObj, NULL);
    char *cmd = Tcl_GetStringFromObj(cmdObj, NULL);

    /*
     * The structure where we keep all information about this
     * asynchronous walk.
     */

    ecPtr = (EvalCmd *) ckalloc(sizeof(EvalCmd) + strlen(cmd) + 1);
    TnmVectorInit(&ecPtr->vector);
    code = TnmSnmpVarBindFromString(interp, &ecPtr->vector, vbl);
    if (code != TCL_OK) {
	ckfree((char *) ecPtr);
	return TCL_ERROR;
    }

    PduInit(&pdu, session, TNM_SNMP_GETNEXT);
    Tcl_DStringAppend(&pdu.varbind, vbl, -1);

    ecPtr->interp = interp;
    ecPtr->cmd = (char *) ecPtr + sizeof(EvalCmd);
    strcpy(ecPtr->cmd, cmd);
    code = TnmSnmpEncode(interp, session, &pdu, 
			 AsyncWalkProc, (ClientData) ecPtr);
    PduFree(&pdu);
    return code;
}

/*
 *----------------------------------------------------------------------
 *
 * SyncWalk --
 *
 *	This procedure walks a MIB tree. It evaluates the given Tcl
 *	command foreach varbind retrieved using getbulk requests.
 *	First, all variables contained in the list argument are
 *	converted to their OIDs. Then we loop using gebulk requests
 *	until we get an error or until one returned variable starts
 *	with an OID not being a valid prefix.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
SyncWalk(interp, session, varNameObj, vblObj, cmdObj)
    Tcl_Interp *interp;
    TnmSnmp *session;
    Tcl_Obj *varNameObj;
    Tcl_Obj *vblObj;
    Tcl_Obj *cmdObj;
{
    int i, j, k, result;
    int oidc, respc;
    char **oidv = NULL, **respv = NULL;
    TnmSnmpPdu pdu;
    int numRepeaters = 0;
    char *varName = Tcl_GetStringFromObj(varNameObj, NULL);
    char *vbl = Tcl_GetStringFromObj(vblObj, NULL);
    char *cmd = Tcl_GetStringFromObj(cmdObj, NULL);
    
    PduInit(&pdu, session, TNM_SNMP_GETBULK);

    /*
     * Save the oid prefix contained in list in oidv and oidc.
     */
    
    result = Tcl_SplitList(interp, vbl, &oidc, &oidv);
    if (result != TCL_OK) {
	return result;
    }
    if (oidc == 0) {
	result = TCL_OK;
	goto loopDone;
    }
    
    for (i = 0; i < oidc; i++) {
	char *tmp = TnmMibGetOid(oidv[i]);
	if (!tmp) {
	    Tcl_AppendResult(interp,  "no object \"", oidv[i], "\"",
			     (char *) NULL);
	    ckfree((char *) oidv);
	    PduFree(&pdu);
            return TCL_ERROR;
	}
	oidv[i] = ckalloc(strlen(tmp) + 2);
	strcpy(oidv[i], tmp);
	strcat(oidv[i], ".");
	Tcl_DStringAppendElement(&pdu.varbind, tmp);
    }

    while (1) {

	pdu.type        = TNM_SNMP_GETBULK;
	pdu.requestId   = TnmSnmpGetRequestId();

	/* 
	 * Set the non-repeaters and the max-repetitions for the getbulk
	 * operation. We use the sequence 8 16 24 32 40 48 to increase
	 * the `warp' factor with every repetition.
	 *
	 * Some measurements show some real bad effects. If you increase
	 * the warp factor too much, you will risk timeouts because the
	 * agent might need a lot of time to build the response. If the
	 * agent does not cache response packets, you will get very bad 
	 * performance. Therefore, I have limited the `warp' factor to
	 * 24 which works well with scotty's default parameters on an
	 * Ethernet. (This number should not be hard coded but perhaps
	 * it is better so because everyone should read this comment
	 * before playing with these parameters.)
	 */

	if (numRepeaters < 24 ) {
	    numRepeaters += 8;
	}

	pdu.errorStatus = 0;
	pdu.errorIndex  = (numRepeaters / oidc > 0) 
	    ? numRepeaters / oidc : 1;

	result = TnmSnmpEncode(interp, session, &pdu, NULL, NULL);
	if (result == TCL_ERROR 
	    && (strncmp(interp->result, "noSuchName ", 11) == 0)) {
	    result = TCL_OK;
	    goto loopDone;
	}
	if (result != TCL_OK) {
            break;
        }
	
	if (respv) ckfree((char *) respv);
	result = Tcl_SplitList(interp, interp->result, &respc, &respv);
	if (result != TCL_OK) {
	    goto loopDone;
	}

	if (respc < oidc) {
	    Tcl_SetResult(interp, "response with wrong # of varbinds",
			  TCL_STATIC);
	    result = TCL_ERROR;
	    goto loopDone;
	}

	for (j = 0; j < respc / oidc; j++) {

	    for (i = 0; i < oidc; i++) {
		if (strncmp(oidv[i], respv[j * oidc + i], 
			    strlen(oidv[i])) != 0) {
		    result = TCL_OK;
		    goto loopDone;
		}
	    }

	    PduFree(&pdu);
	    for (k = j * oidc; k < (j+1) * oidc; k++) {
		int vbc;
		char **vbv;
		result = Tcl_SplitList(interp, respv[k], &vbc, &vbv);
		if (result != TCL_OK) {
		    goto loopDone;
		}
		if (strcmp(vbv[1], "endOfMibView") == 0) {
		    ckfree((char *) vbv);
		    result = TCL_OK;
		    goto loopDone;
		}
		ckfree((char *) vbv);
		Tcl_DStringAppendElement(&pdu.varbind, respv[k]);
	    }

	    if (Tcl_SetVar(interp, varName, Tcl_DStringValue(&pdu.varbind),
			   TCL_LEAVE_ERR_MSG) == NULL) {
		result = TCL_ERROR;
		goto loopDone;
	    }

	    result = Tcl_Eval(interp, cmd);
	    if (result != TCL_OK) {
		if (result == TCL_CONTINUE) {
		    result = TCL_OK;
		} else if (result == TCL_BREAK) {
		    result = TCL_OK;
		    goto loopDone;
		} else if (result == TCL_ERROR) {
		    char msg[100];
		    sprintf(msg, "\n    (\"%s walk\" body line %d)",
			    Tcl_GetCommandName(interp, session->token),
			    interp->errorLine);
		    Tcl_AddErrorInfo(interp, msg);
		    goto loopDone;
		} else {
		    goto loopDone;
		}
	    }
	}
    }

  loopDone:
    for (i = 0; i < oidc; i++) {
	ckfree(oidv[i]);
    }
    ckfree((char *) oidv);
    if (respv) ckfree((char *) respv);
    PduFree(&pdu);

    /*
     * noSuchName errors mark the end of a SNMPv1 MIB view and hence 
     * they are no real errors. So we ignore them here.
     */

    if (result == TCL_ERROR 
	&& (strncmp(interp->result, "noSuchName", 10) == 0)) {
	result = TCL_OK;
    }

    if (result == TCL_OK) {
	Tcl_ResetResult(interp);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ExpandTable --
 *
 *	This procedure expands the list of table variables or a single
 *	table name into a list of MIB instances.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
ExpandTable(interp, tList, dst)
    Tcl_Interp *interp;
    char *tList;
    Tcl_DString *dst;
{
    int i, argc, code;
    char **argv = NULL;
    TnmMibNode *nodePtr, *entryPtr = NULL, *tablePtr = NULL;
    
    code = Tcl_SplitList(interp, tList, &argc, &argv);
    if (code != TCL_OK) {
	return TCL_ERROR;
    }

    for (i = 0; i < argc; i++) {

	/*
	 * Lookup the given object.
	 */

	nodePtr = TnmMibFindNode(argv[i], NULL, 0);
	if (! nodePtr) {
	    Tcl_AppendResult(interp, "unknown mib table \"", argv[i], "\"",
			     (char *) NULL);
	    ckfree((char *) argv);
	    return TCL_ERROR;
	}

	/*
	 * Locate the entry (SEQUENCE) that contains this object.
	 */

	switch (nodePtr->syntax) {
	  case ASN1_SEQUENCE:
	    entryPtr = nodePtr;
	    break;
	  case ASN1_SEQUENCE_OF: 
	    if (nodePtr->childPtr) {
		entryPtr = nodePtr->childPtr;
	    }
	    break;
	  default:
	    if (nodePtr->parentPtr && nodePtr->childPtr == NULL
		&& nodePtr->parentPtr->syntax == ASN1_SEQUENCE) {
		entryPtr = nodePtr->parentPtr;
	    } else {
	    unknownTable:
		Tcl_AppendResult(interp, "not a table \"", argv[i], "\"",
				 (char *) NULL);
		ckfree((char *) argv);
		return TCL_ERROR;
	    }
	}

	/*
	 * Check whether all objects belong to the same table.
	 */

	if (entryPtr == NULL || entryPtr->parentPtr == NULL) {
	    goto unknownTable;
	}

	if (tablePtr == NULL) {
	    tablePtr = entryPtr->parentPtr;
	}
	if (tablePtr != entryPtr->parentPtr) {
	    Tcl_AppendResult(interp, "instances not in the same table",
			     (char *) NULL);
	    ckfree((char *) argv);
	    return TCL_ERROR;
	}

	/*
	 * Now add the nodes to the list. Expand SEQUENCE nodes to
	 * include all child nodes. Check the access mode which must
	 * allow at least read access.
	 */

	if (nodePtr == entryPtr || nodePtr == tablePtr) {
	    TnmMibNode *nPtr;
	    for (nPtr = entryPtr->childPtr; nPtr; nPtr=nPtr->nextPtr) {
		if (nPtr->access != TNM_MIB_NOACCESS) {
		    Tcl_DStringAppendElement(dst, nPtr->label);
		}
	    }
	} else {
	    if (nodePtr->access != TNM_MIB_NOACCESS) {
		Tcl_DStringAppendElement(dst, nodePtr->label);
	    }
	}
    }

    ckfree((char *) argv);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Table --
 *
 *	This procedure retrieves a conceptual SNMP table and stores
 *	the values in a Tcl array.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Tcl variables are modified.
 *
 *----------------------------------------------------------------------
 */

static int
Table(interp, session, table, arrayName)
    Tcl_Interp *interp;
    TnmSnmp *session;
    char *table;
    char *arrayName;
{
    int i, largc, code;
    TnmSnmpPdu _pdu, *pdu = &_pdu;
    Tcl_DString varList;
    char **largv;
    
    /*
     * A special hack to make sure that the given array name 
     * is actually known as an array.
     */

    Tcl_SetVar2(interp, arrayName, "foo", "", 0);
    Tcl_UnsetVar(interp, arrayName, 0);

    /*
     * Initialize the PDU.
     */

    pdu->addr        = session->maddr;
    pdu->type        = TNM_SNMP_GETBULK;
    pdu->requestId   = TnmSnmpGetRequestId();
    pdu->errorStatus = TNM_SNMP_NOERROR;
    pdu->errorIndex  = 0;    
    pdu->trapOID     = NULL;
    Tcl_DStringInit(&pdu->varbind);
    Tcl_DStringInit(&varList);

    /*
     * Expand the given table list to create the complete getnext varbind.
     */

    code = ExpandTable(interp, table, &varList);
    if (code != TCL_OK) {
        return TCL_ERROR;
    }

    if (Tcl_DStringLength(&varList) == 0) {
	return TCL_OK;
    }

    /*
     *
     */

    code = Tcl_SplitList(interp, Tcl_DStringValue(&varList),
			 &largc, &largv);
    if (code != TCL_OK) {
	Tcl_DStringFree(&varList);
	return TCL_ERROR;
    }

    for (i = 0; i < largc; i++) {
	TnmWriteMessage(largv[i]);
	TnmWriteMessage("\n");
    }

    ckfree((char *) largv);
    Tcl_DStringFree(&varList);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ExpandScalars --
 *
 *	This procedure expands the list of scalar or group names
 *	into a list of MIB instances.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
ExpandScalars(interp, sList, dst)
    Tcl_Interp *interp;
    char *sList;
    Tcl_DString *dst;
{
    int argc, code, i;
    char **argv = NULL;
    TnmMibNode *nodePtr;
    TnmOid oid;

    code = Tcl_SplitList(interp, sList, &argc, &argv);
    if (code != TCL_OK) {
	return TCL_ERROR;
    }

    TnmOidInit(&oid);
    for (i = 0; i < argc; i++) {

	nodePtr = TnmMibFindNode(argv[i], NULL, 0);
	if (nodePtr == NULL) {
	    Tcl_AppendResult(interp, "unknown mib object \"", argv[i], "\"",
			     (char *) NULL);
	    ckfree((char *) argv);
	    return TCL_ERROR;
	}

	/*
	 * Skip the node if it is a table or an entry node.
	 */

	if (nodePtr->syntax == ASN1_SEQUENCE 
	    || nodePtr->syntax == ASN1_SEQUENCE_OF) {
	    continue;
	}

	/*
	 * Try to expand to child nodes if the node has child nodes, 
	 * Ignore all nodes which itsef have childs and which are
	 * not accessible.
	 */

	if (nodePtr->childPtr) {
	    for (nodePtr = nodePtr->childPtr; 
		 nodePtr; nodePtr=nodePtr->nextPtr) {
		if (nodePtr->access == TNM_MIB_NOACCESS || nodePtr->childPtr) {
		    continue;
		}
		TnmMibNodeToOid(nodePtr, &oid);
		TnmOidAppend(&oid, 0);
		Tcl_DStringAppendElement(dst, TnmOidToString(&oid));
		TnmOidFree(&oid);
	    }

	} else if (nodePtr->access != TNM_MIB_NOACCESS) {
	    TnmMibNodeToOid(nodePtr, &oid);
	    TnmOidAppend(&oid, 0);
	    Tcl_DStringAppendElement(dst, TnmOidToString(&oid));
	    TnmOidFree(&oid);

	} else {
	    Tcl_AppendResult(interp, "object \"", argv[0],
			     "\" not accessible", (char *) NULL);
	    ckfree((char *) argv);
	    return TCL_ERROR;
	}
    }

    ckfree((char *) argv);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Scalars --
 *
 *	This procedure extracts the variables and values contained in
 *	the varbindlist vbl and set corresponding array Tcl variables.
 *	The list of array names modified is appended to result.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Tcl variables are modified.
 *
 *----------------------------------------------------------------------
 */

static int
Scalars(interp, session, group, arrayName)
    Tcl_Interp *interp;
    TnmSnmp *session;
    char *group;
    char *arrayName;
{
    int i, largc, code;
    TnmSnmpPdu _pdu, *pdu = &_pdu;
    Tcl_DString varList;
    Tcl_DString result;
    char **largv;
    
    /*
     * A special hack to make sure that the given array name 
     * is actually known as an array.
     */

    Tcl_SetVar2(interp, arrayName, "foo", "", 0);
    Tcl_UnsetVar(interp, arrayName, 0);

    /*
     * Initialize the PDU.
     */

    pdu->addr        = session->maddr;
    pdu->type        = TNM_SNMP_GET;
    pdu->requestId   = TnmSnmpGetRequestId();
    pdu->errorStatus = TNM_SNMP_NOERROR;
    pdu->errorIndex  = 0;    
    pdu->trapOID     = NULL;
    Tcl_DStringInit(&pdu->varbind);
    Tcl_DStringInit(&varList);
    Tcl_DStringInit(&result);

    /*
     * Expand the given scalar list to create the complete get varbind.
     */

    code = ExpandScalars(interp, group, &varList);
    if (code != TCL_OK) {
        return TCL_ERROR;
    }

    if (Tcl_DStringLength(&varList) == 0) {
	return TCL_OK;
    }

    /*
     * First try to retrieve all variables in one get request. This
     * may fail because the PDU causes a tooBig error or the agent
     * responds to missing variables with a noSuchName error.
     */

    Tcl_DStringAppend(&pdu->varbind, Tcl_DStringValue(&varList),
		      Tcl_DStringLength(&varList));
    code = TnmSnmpEncode(interp, session, pdu, NULL, NULL);
    if (code == TCL_OK) {	
	ScalarSetVar(interp, interp->result, arrayName, &result);
	Tcl_DStringFree(&varList);
	Tcl_DStringResult(interp, &result);
	return TCL_OK;
    }

    /*
     * Stop if we got no response since the agent is not
     * talking to us. This saves some time-outs.
     */

    if (strcmp(interp->result, "noResponse") == 0) {
	return TCL_ERROR;
    }

    /*
     * If we had no success, try every single varbind with one
     * request. Ignore errors so we just collect existing variables.
     */

    code = Tcl_SplitList(interp, Tcl_DStringValue(&varList),
			 &largc, &largv);
    if (code != TCL_OK) {
	Tcl_DStringFree(&varList);
	return TCL_ERROR;
    }

    for (i = 0; i < largc; i++) {

	pdu->type        = TNM_SNMP_GET;
	pdu->requestId   = TnmSnmpGetRequestId();
	pdu->errorStatus = TNM_SNMP_NOERROR;
	pdu->errorIndex  = 0;    
	Tcl_DStringInit(&pdu->varbind);
	Tcl_DStringAppend(&pdu->varbind, largv[i], -1);

	code = TnmSnmpEncode(interp, session, pdu, NULL, NULL);
	if (code != TCL_OK) {
	    continue;
	}

	ScalarSetVar(interp, interp->result, arrayName, &result);
    }
    ckfree((char *) largv);
    Tcl_DStringFree(&varList);
    Tcl_DStringResult(interp, &result);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ScalarSetVar --
 *
 *	This procedure extracts the variables and values contained in
 *	the varbindlist vbl and set corresponding array Tcl variables.
 *	The list of array names modified is appended to result.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Tcl variables are modified.
 *
 *----------------------------------------------------------------------
 */

static void
ScalarSetVar(interp, vbl, varName, result)
    Tcl_Interp *interp;
    char *vbl;
    char *varName;
    Tcl_DString *result;
{
    int i, code;
    char *name;
    TnmSnmpVarBind *vbPtr;
    TnmMibNode *nodePtr;
    TnmVector vector;

    TnmVectorInit(&vector);
    code = TnmSnmpVarBindFromString(interp, &vector, vbl);
    if (code != TCL_OK) {
	return;
    }
    
    for (i = 0; i < TnmVectorSize(&vector); i++) {
	vbPtr = (TnmSnmpVarBind *) TnmVectorGet(&vector, i);
	if (TnmSnmpValidException(vbPtr->syntax)) {
	    continue;
	}
	name = TnmOidToString(&vbPtr->oid);
	nodePtr = TnmMibFindNode(name, NULL, 0);
	if (nodePtr) {
	    name = nodePtr->label;
	}
	Tcl_SetVar2(interp, varName, name, TnmSnmpValueToString(vbPtr), 0);
	Tcl_DStringAppendElement(result, name);
    }

    TnmSnmpVarBindFree(&vector);
    TnmVectorFree(&vector);
}
