Blob Blame History Raw

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-features.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <net-snmp/agent/watcher.h>
#include <net-snmp/agent/agent_callbacks.h>

#include "agent/extend.h"
#include "utilities/execute.h"
#include "struct.h"

#ifndef USING_UCD_SNMP_EXTENSIBLE_MODULE
#include "util_funcs/header_simple_table.h"
#include "mibdefs.h"
#define SHELLCOMMAND 3
#endif

netsnmp_feature_require(extract_table_row_data)
netsnmp_feature_require(table_data_delete_table)
#ifndef NETSNMP_NO_WRITE_SUPPORT
netsnmp_feature_require(insert_table_row)
#endif /* NETSNMP_NO_WRITE_SUPPORT */

oid  ns_extend_oid[]    = { 1, 3, 6, 1, 4, 1, 8072, 1, 3, 2 };

typedef struct extend_registration_block_s {
    netsnmp_table_data *dinfo;
    oid                *root_oid;
    size_t              oid_len;
    long                num_entries;
    netsnmp_extend     *ehead;
    netsnmp_handler_registration       *reg[4];
    struct extend_registration_block_s *next;
} extend_registration_block;
extend_registration_block *ereg_head = NULL;


#ifndef USING_UCD_SNMP_EXTENSIBLE_MODULE
typedef struct netsnmp_old_extend_s {
    unsigned int idx;
    netsnmp_extend *exec_entry;
    netsnmp_extend *efix_entry;
} netsnmp_old_extend;

unsigned int             num_compatability_entries = 0;
unsigned int             max_compatability_entries = 50;
netsnmp_old_extend *compatability_entries;

char           *cmdlinebuf;
size_t          cmdlinesize;

WriteMethod fixExec2Error;
FindVarMethod var_extensible_old;
oid  old_extensible_variables_oid[] = { NETSNMP_UCDAVIS_MIB, NETSNMP_SHELLMIBNUM, 1 };
#ifndef NETSNMP_NO_WRITE_SUPPORT
struct variable2 old_extensible_variables[] = {
    {MIBINDEX,     ASN_INTEGER,   NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {MIBINDEX}},
    {ERRORNAME,    ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {ERRORNAME}},
    {SHELLCOMMAND, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {SHELLCOMMAND}},
    {ERRORFLAG,    ASN_INTEGER,   NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {ERRORFLAG}},
    {ERRORMSG,     ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {ERRORMSG}},
    {ERRORFIX,     ASN_INTEGER,  NETSNMP_OLDAPI_RWRITE,
     var_extensible_old, 1, {ERRORFIX}},
    {ERRORFIXCMD,  ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {ERRORFIXCMD}}
};
#else /* !NETSNMP_NO_WRITE_SUPPORT */
struct variable2 old_extensible_variables[] = {
    {MIBINDEX,     ASN_INTEGER,   NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {MIBINDEX}},
    {ERRORNAME,    ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {ERRORNAME}},
    {SHELLCOMMAND, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {SHELLCOMMAND}},
    {ERRORFLAG,    ASN_INTEGER,   NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {ERRORFLAG}},
    {ERRORMSG,     ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {ERRORMSG}},
    {ERRORFIX,     ASN_INTEGER,  NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {ERRORFIX}},
    {ERRORFIXCMD,  ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
     var_extensible_old, 1, {ERRORFIXCMD}}
};
#endif /* !NETSNMP_NO_WRITE_SUPPORT */
#endif


        /*************************
         *
         *  Main initialisation routine
         *
         *************************/

extend_registration_block *
_find_extension_block( oid *name, size_t name_len )
{
    extend_registration_block         *eptr;
    size_t len;
    for ( eptr=ereg_head; eptr; eptr=eptr->next ) {
        len = SNMP_MIN(name_len, eptr->oid_len);
        if (!snmp_oid_compare( name, len, eptr->root_oid, eptr->oid_len))
            return eptr;
    }
    return NULL;
}

extend_registration_block *
_register_extend( oid *base, size_t len )
{
    extend_registration_block         *eptr;
    oid oid_buf[MAX_OID_LEN];

    netsnmp_table_data                *dinfo;
    netsnmp_table_registration_info   *tinfo;
    netsnmp_watcher_info              *winfo;
    netsnmp_handler_registration      *reg = NULL;
    int                                rc;

    for ( eptr=ereg_head; eptr; eptr=eptr->next ) {
        if (!snmp_oid_compare( base, len, eptr->root_oid, eptr->oid_len))
            return eptr;
    }
    if (!eptr) {
        eptr = SNMP_MALLOC_TYPEDEF( extend_registration_block );
        if (!eptr)
            return NULL;
        eptr->root_oid = snmp_duplicate_objid( base, len );
        eptr->oid_len  = len;
        eptr->num_entries = 0;
        eptr->ehead       = NULL;
        eptr->dinfo       = netsnmp_create_table_data( "nsExtendTable" );
        eptr->next        = ereg_head;
        ereg_head         = eptr;
    }

    dinfo = eptr->dinfo;
    memcpy( oid_buf, base, len*sizeof(oid) );

        /*
         * Register the configuration table
         */
    tinfo = SNMP_MALLOC_TYPEDEF( netsnmp_table_registration_info );
    netsnmp_table_helper_add_indexes( tinfo, ASN_OCTET_STR, 0 );
    tinfo->min_column = COLUMN_EXTCFG_FIRST_COLUMN;
    tinfo->max_column = COLUMN_EXTCFG_LAST_COLUMN;
    oid_buf[len] = 2;
#ifndef NETSNMP_NO_WRITE_SUPPORT
    reg   = netsnmp_create_handler_registration(
                "nsExtendConfigTable", handle_nsExtendConfigTable, 
                oid_buf, len+1, HANDLER_CAN_RWRITE);
#else /* !NETSNMP_NO_WRITE_SUPPORT */
    reg   = netsnmp_create_handler_registration(
                "nsExtendConfigTable", handle_nsExtendConfigTable, 
                oid_buf, len+1, HANDLER_CAN_RONLY);
#endif /* !NETSNMP_NO_WRITE_SUPPORT */
    rc = netsnmp_register_table_data( reg, dinfo, tinfo );
    if (rc != SNMPERR_SUCCESS) {
        goto bail;
    }
    netsnmp_handler_owns_table_info(reg->handler->next);
    eptr->reg[0] = reg;

        /*
         * Register the main output table
         *   using the same table_data handle.
         * This is sufficient to link the two tables,
         *   and implement the AUGMENTS behaviour
         */
    tinfo = SNMP_MALLOC_TYPEDEF( netsnmp_table_registration_info );
    netsnmp_table_helper_add_indexes( tinfo, ASN_OCTET_STR, 0 );
    tinfo->min_column = COLUMN_EXTOUT1_FIRST_COLUMN;
    tinfo->max_column = COLUMN_EXTOUT1_LAST_COLUMN;
    oid_buf[len] = 3;
    reg   = netsnmp_create_handler_registration(
                "nsExtendOut1Table", handle_nsExtendOutput1Table, 
                oid_buf, len+1, HANDLER_CAN_RONLY);
    rc = netsnmp_register_table_data( reg, dinfo, tinfo );
    if (rc != SNMPERR_SUCCESS)
        goto bail;
    netsnmp_handler_owns_table_info(reg->handler->next);
    eptr->reg[1] = reg;

        /*
         * Register the multi-line output table
         *   using a simple table helper.
         * This handles extracting the indexes from
         *   the request OID, but leaves most of
         *   the work to our handler routine.
         * Still, it was nice while it lasted...
         */
    tinfo = SNMP_MALLOC_TYPEDEF( netsnmp_table_registration_info );
    netsnmp_table_helper_add_indexes( tinfo, ASN_OCTET_STR, ASN_INTEGER, 0 );
    tinfo->min_column = COLUMN_EXTOUT2_FIRST_COLUMN;
    tinfo->max_column = COLUMN_EXTOUT2_LAST_COLUMN;
    oid_buf[len] = 4;
    reg   = netsnmp_create_handler_registration(
                "nsExtendOut2Table", handle_nsExtendOutput2Table, 
                oid_buf, len+1, HANDLER_CAN_RONLY);
    rc = netsnmp_register_table( reg, tinfo );
    if (rc != SNMPERR_SUCCESS)
        goto bail;
    netsnmp_handler_owns_table_info(reg->handler->next);
    eptr->reg[2] = reg;

        /*
         * Register a watched scalar to keep track of the number of entries
         */
    oid_buf[len] = 1;
    reg   = netsnmp_create_handler_registration(
                "nsExtendNumEntries", NULL, 
                oid_buf, len+1, HANDLER_CAN_RONLY);
    winfo = netsnmp_create_watcher_info(
                &(eptr->num_entries), sizeof(eptr->num_entries),
                ASN_INTEGER, WATCHER_FIXED_SIZE);
    rc = netsnmp_register_watched_scalar2( reg, winfo );
    if (rc != SNMPERR_SUCCESS)
        goto bail;
    eptr->reg[3] = reg;

    return eptr;

bail:
    if (eptr->reg[3])
        netsnmp_unregister_handler(eptr->reg[3]);
    if (eptr->reg[2])
        netsnmp_unregister_handler(eptr->reg[2]);
    if (eptr->reg[1])
        netsnmp_unregister_handler(eptr->reg[1]);
    if (eptr->reg[0])
        netsnmp_unregister_handler(eptr->reg[0]);
    return NULL;
}

static void
_unregister_extend(extend_registration_block *eptr)
{
    extend_registration_block *prev;

    netsnmp_assert(eptr);
    for (prev = ereg_head; prev && prev->next != eptr; prev = prev->next)
	;
    if (prev) {
        netsnmp_assert(eptr == prev->next);
	prev->next = eptr->next;
    } else {
        netsnmp_assert(eptr == ereg_head);
	ereg_head = eptr->next;
    }

    netsnmp_table_data_delete_table(eptr->dinfo);
    free(eptr->root_oid);
    free(eptr);
}

int
extend_clear_callback(int majorID, int minorID,
                    void *serverarg, void *clientarg)
{
    extend_registration_block *eptr, *enext = NULL;

    for ( eptr=ereg_head; eptr; eptr=enext ) {
        enext=eptr->next;
        netsnmp_unregister_handler( eptr->reg[0] );
        netsnmp_unregister_handler( eptr->reg[1] );
        netsnmp_unregister_handler( eptr->reg[2] );
        netsnmp_unregister_handler( eptr->reg[3] );
        SNMP_FREE(eptr);
    }
    ereg_head = NULL;
    return 0;
}

void init_extend( void )
{
    snmpd_register_config_handler("extend",    extend_parse_config, NULL, NULL);
    snmpd_register_config_handler("extend-sh", extend_parse_config, NULL, NULL);
    snmpd_register_config_handler("extendfix", extend_parse_config, NULL, NULL);
    snmpd_register_config_handler("exec2", extend_parse_config, NULL, NULL);
    snmpd_register_config_handler("sh2",   extend_parse_config, NULL, NULL);
    snmpd_register_config_handler("execFix2", extend_parse_config, NULL, NULL);
    (void)_register_extend( ns_extend_oid, OID_LENGTH(ns_extend_oid));

#ifndef USING_UCD_SNMP_EXTENSIBLE_MODULE
    snmpd_register_config_handler("exec", extend_parse_config, NULL, NULL);
    snmpd_register_config_handler("sh",   extend_parse_config, NULL, NULL);
    snmpd_register_config_handler("execFix", extend_parse_config, NULL, NULL);
    compatability_entries = (netsnmp_old_extend *)
        calloc( max_compatability_entries, sizeof(netsnmp_old_extend));
    REGISTER_MIB("ucd-extensible", old_extensible_variables,
                 variable2, old_extensible_variables_oid);
#endif

    snmp_register_callback(SNMP_CALLBACK_APPLICATION,
                           SNMPD_CALLBACK_PRE_UPDATE_CONFIG,
                           extend_clear_callback, NULL);
}

void
shutdown_extend(void)
{
#ifndef USING_UCD_SNMP_EXTENSIBLE_MODULE
    free(compatability_entries);
    compatability_entries = NULL;
#endif
    while (ereg_head)
	_unregister_extend(ereg_head);
}

        /*************************
         *
         *  Cached-data hooks
         *  see 'cache_handler' helper
         *
         *************************/

int
extend_load_cache(netsnmp_cache *cache, void *magic)
{
#ifndef USING_UTILITIES_EXECUTE_MODULE
    NETSNMP_LOGONCE((LOG_WARNING,"support for run_exec_command not available\n"));
    return -1;
#else
    int  out_len = 1024*100;
    char out_buf[ 1024*100 ];
    int  cmd_len = 255*2 + 2;	/* 2 * DisplayStrings */
    char cmd_buf[ 255*2 + 2 ];
    int  ret;
    char *cp;
    char *line_buf[ 1024 ];
    netsnmp_extend *extension = (netsnmp_extend *)magic;

    if (!magic)
        return -1;
    DEBUGMSGTL(( "nsExtendTable:cache", "load %s", extension->token ));
    if ( extension->args )
        snprintf( cmd_buf, cmd_len, "%s %s", extension->command, extension->args );
    else 
        snprintf( cmd_buf, cmd_len, "%s", extension->command );
    if ( extension->flags & NS_EXTEND_FLAGS_SHELL )
        ret = run_shell_command( cmd_buf, extension->input, out_buf, &out_len);
    else
        ret = run_exec_command(  cmd_buf, extension->input, out_buf, &out_len);
    DEBUGMSG(( "nsExtendTable:cache", ": %s : %d\n", cmd_buf, ret));
    if (ret >= 0) {
        if (out_buf[   out_len-1 ] == '\n')
            out_buf[ --out_len   ] =  '\0';	/* Stomp on trailing newline */
        extension->output   = strdup( out_buf );
        extension->out_len  = out_len;
        /*
         * Now we need to pick the output apart into separate lines.
         * Start by counting how many lines we've got, and keeping
         * track of where each line starts in a static buffer
         */
        extension->numlines = 1;
        line_buf[ 0 ] = extension->output;
        for (cp=extension->output; *cp; cp++) {
            if (*cp == '\n') {
                line_buf[ extension->numlines++ ] = cp+1;
            }
        }
        if ( extension->numlines > 1 ) {
            extension->lines = (char**)calloc( sizeof(char *), extension->numlines );
            memcpy( extension->lines, line_buf,
                                       sizeof(char *) * extension->numlines );
        } else {
            extension->lines = &extension->output;
        }
    }
    extension->result = ret;
    return ret;
#endif /* !defined(USING_UTILITIES_EXECUTE_MODULE) */
}

void
extend_free_cache(netsnmp_cache *cache, void *magic)
{
    netsnmp_extend *extension = (netsnmp_extend *)magic;
    if (!magic)
        return;

    DEBUGMSGTL(( "nsExtendTable:cache", "free %s\n", extension->token ));
    if (extension->output) {
        SNMP_FREE(extension->output);
        extension->output = NULL;
    }
    if ( extension->numlines > 1 ) {
        SNMP_FREE(extension->lines);
    }
    extension->lines  = NULL;
    extension->out_len  = 0;
    extension->numlines = 0;
}


        /*************************
         *
         *  Utility routines for setting up a new entry
         *  (either via SET requests, or the config file)
         *
         *************************/

void
_free_extension( netsnmp_extend *extension, extend_registration_block *ereg )
{
    netsnmp_extend *eptr  = NULL;
    netsnmp_extend *eprev = NULL;

    if (!extension)
        return;

    if (ereg) {
        /* Unlink from 'ehead' list */
        for (eptr=ereg->ehead; eptr; eptr=eptr->next) {
            if (eptr == extension)
                break;
            eprev = eptr;
        }
        if (!eptr) {
            snmp_log(LOG_ERR,
                     "extend: fell off end of list before finding extension\n");
            return;
        }
        if (eprev)
            eprev->next = eptr->next;
        else
            ereg->ehead = eptr->next;
        netsnmp_table_data_remove_and_delete_row( ereg->dinfo, extension->row);
    }

    SNMP_FREE( extension->token );
    SNMP_FREE( extension->cache );
    SNMP_FREE( extension->command );
    SNMP_FREE( extension->args  );
    SNMP_FREE( extension->input );
    SNMP_FREE( extension );
    return;
}

netsnmp_extend *
_new_extension( char *exec_name, int exec_flags, extend_registration_block *ereg )
{
    netsnmp_extend     *extension;
    netsnmp_table_row  *row;
    netsnmp_extend     *eptr1, *eptr2; 
    netsnmp_table_data *dinfo = ereg->dinfo;

    if (!exec_name)
        return NULL;
    extension = SNMP_MALLOC_TYPEDEF( netsnmp_extend );
    if (!extension)
        return NULL;
    extension->token    = strdup( exec_name );
    extension->flags    = exec_flags;
    extension->cache    = netsnmp_cache_create( 0, extend_load_cache,
                                                   extend_free_cache, NULL, 0 );
    if (extension->cache)
        extension->cache->magic = extension;

    row = netsnmp_create_table_data_row();
    if (!row || !extension->cache) {
        _free_extension( extension, ereg );
        SNMP_FREE( row );
        return NULL;
    }
    row->data = (void *)extension;
    extension->row = row;
    netsnmp_table_row_add_index( row, ASN_OCTET_STR,
                                 exec_name, strlen(exec_name));
    if ( netsnmp_table_data_add_row( dinfo, row) != SNMPERR_SUCCESS ) {
        /* _free_extension( extension, ereg ); */
        SNMP_FREE( extension );  /* Probably not sufficient */
        SNMP_FREE( row );
        return NULL;
    }

    ereg->num_entries++;
        /*
         *  Now add this structure to a private linked list.
         *  We don't need this for the main tables - the
         *   'table_data' helper will take care of those.
         *  But it's probably easier to handle the multi-line
         *  output table ourselves, for which we need access
         *  to the underlying data.
         *   So we'll keep a list internally as well.
         */
    for ( eptr1 = ereg->ehead, eptr2 = NULL;
          eptr1;
          eptr2 = eptr1, eptr1 = eptr1->next ) {

        if (strlen( eptr1->token )  > strlen( exec_name ))
            break;
        if (strlen( eptr1->token ) == strlen( exec_name ) &&
            strcmp( eptr1->token, exec_name ) > 0 )
            break;
    }
    if ( eptr2 )
        eptr2->next = extension;
    else
        ereg->ehead = extension;
    extension->next = eptr1;
    return extension;
}

void
extend_parse_config(const char *token, char *cptr)
{
    netsnmp_extend *extension;
    char exec_name[STRMAX];
    char exec_name2[STRMAX];     /* For use with UCD execFix directive */
    char exec_command[STRMAX];
    oid  oid_buf[MAX_OID_LEN];
    size_t oid_len;
    extend_registration_block *eptr;
    int  flags;
    int cache_timeout = 0;
    int exec_type = NS_EXTEND_ETYPE_EXEC;

    cptr = copy_nword(cptr, exec_name, sizeof(exec_name));
    if (strcmp(exec_name, "-cacheTime") == 0) {
        char cache_timeout_str[32];

        cptr = copy_nword(cptr, cache_timeout_str, sizeof(cache_timeout_str));
        /* If atoi can't do the conversion, it returns 0 */
        cache_timeout = atoi(cache_timeout_str);
        cptr = copy_nword(cptr, exec_name, sizeof(exec_name));
    }
    if (strcmp(exec_name, "-execType") == 0) {
        char exec_type_str[16];

        cptr = copy_nword(cptr, exec_type_str, sizeof(exec_type_str));
        if (strcmp(exec_type_str, "sh") == 0)
            exec_type = NS_EXTEND_ETYPE_SHELL;
        else
            exec_type = NS_EXTEND_ETYPE_EXEC;
        cptr = copy_nword(cptr, exec_name, sizeof(exec_name));
    }
    if ( *exec_name == '.' ) {
        oid_len = MAX_OID_LEN - 2;
        if (0 == read_objid( exec_name, oid_buf, &oid_len )) {
            config_perror("ERROR: Unrecognised OID" );
            return;
        }
        cptr = copy_nword(cptr, exec_name,    sizeof(exec_name));
        if (!strcmp( token, "sh"   ) ||
            !strcmp( token, "exec" )) {
            config_perror("ERROR: This output format has been deprecated - Please use the 'extend' directive instead" );
            return;
        }
    } else {
        memcpy( oid_buf, ns_extend_oid, sizeof(ns_extend_oid));
        oid_len = OID_LENGTH(ns_extend_oid);
    }
    cptr = copy_nword(cptr, exec_command, sizeof(exec_command));
    /* XXX - check 'exec_command' exists & is executable */
    flags = (NS_EXTEND_FLAGS_ACTIVE | NS_EXTEND_FLAGS_CONFIG);
    if (!strcmp( token, "sh"        ) ||
        !strcmp( token, "extend-sh" ) ||
        !strcmp( token, "sh2") ||
        exec_type == NS_EXTEND_ETYPE_SHELL)
        flags |= NS_EXTEND_FLAGS_SHELL;
    if (!strcmp( token, "execFix"   ) ||
        !strcmp( token, "extendfix" ) ||
        !strcmp( token, "execFix2" )) {
        strcpy( exec_name2, exec_name );
        strcat( exec_name, "Fix" );
        flags |= NS_EXTEND_FLAGS_WRITEABLE;
        /* XXX - Check for shell... */
    }

    eptr      = _register_extend( oid_buf, oid_len );
    if (!eptr) {
        snmp_log(LOG_ERR, "Failed to register extend entry '%s' - possibly duplicate name.\n", exec_name );
        return;
    }
    extension = _new_extension( exec_name, flags, eptr );
    if (extension) {
        extension->command  = strdup( exec_command );
        if (cptr)
            extension->args = strdup( cptr );
        if (cache_timeout != 0)
            extension->cache->timeout = cache_timeout;
    } else {
        snmp_log(LOG_ERR, "Failed to register extend entry '%s' - possibly duplicate name.\n", exec_name );
        return;
    }

#ifndef USING_UCD_SNMP_EXTENSIBLE_MODULE
    /*
     *  Compatability with the UCD extTable
     */
    if (!strcmp( token, "execFix"  )) {
        int  i;
        for ( i=0; i < num_compatability_entries; i++ ) {
            if (!strcmp( exec_name2,
                    compatability_entries[i].exec_entry->token))
                break;
        }
        if ( i == num_compatability_entries )
            config_perror("No matching exec entry" );
        else
            compatability_entries[ i ].efix_entry = extension;
            
    } else if (!strcmp( token, "sh"   ) ||
               !strcmp( token, "exec" )) {
        if ( num_compatability_entries == max_compatability_entries ) {
            /* XXX - should really use dynamic allocation */
            netsnmp_old_extend *new_compatability_entries;
            new_compatability_entries = realloc(compatability_entries,
                             max_compatability_entries*2*sizeof(netsnmp_old_extend));
            if (!new_compatability_entries)
                config_perror("No further UCD-compatible entries" );
            else {
                memset(new_compatability_entries+num_compatability_entries, 0,
                        sizeof(netsnmp_old_extend)*max_compatability_entries);
                max_compatability_entries *= 2;
                compatability_entries = new_compatability_entries;
            }
        }
        if (num_compatability_entries != max_compatability_entries)
            compatability_entries[
                num_compatability_entries++ ].exec_entry = extension;
    }
#endif
}

        /*************************
         *
         *  Main table handlers
         *  Most of the work is handled
         *   by the 'table_data' helper.
         *
         *************************/

int
handle_nsExtendConfigTable(netsnmp_mib_handler          *handler,
                     netsnmp_handler_registration *reginfo,
                     netsnmp_agent_request_info   *reqinfo,
                     netsnmp_request_info         *requests)
{
    netsnmp_request_info       *request;
    netsnmp_table_request_info *table_info;
    netsnmp_extend             *extension;
    extend_registration_block  *eptr;
    int  i;
    int  need_to_validate = 0;

    for ( request=requests; request; request=request->next ) {
        if (request->processed)
            continue;
        table_info = netsnmp_extract_table_info( request );
        extension  = (netsnmp_extend*)netsnmp_extract_table_row_data( request );

        DEBUGMSGTL(( "nsExtendTable:config", "varbind: "));
        DEBUGMSGOID(("nsExtendTable:config", request->requestvb->name,
                                             request->requestvb->name_length));
        DEBUGMSG((   "nsExtendTable:config", " (%s)\n",
                      se_find_label_in_slist("agent_mode", reqinfo->mode)));

        switch (reqinfo->mode) {
        case MODE_GET:
            switch (table_info->colnum) {
            case COLUMN_EXTCFG_COMMAND:
                snmp_set_var_typed_value(
                     request->requestvb, ASN_OCTET_STR,
                     extension->command,
                    (extension->command)?strlen(extension->command):0);
                break;
            case COLUMN_EXTCFG_ARGS:
                snmp_set_var_typed_value(
                     request->requestvb, ASN_OCTET_STR,
                     extension->args,
                    (extension->args)?strlen(extension->args):0);
                break;
            case COLUMN_EXTCFG_INPUT:
                snmp_set_var_typed_value(
                     request->requestvb, ASN_OCTET_STR,
                     extension->input,
                    (extension->input)?strlen(extension->input):0);
                break;
            case COLUMN_EXTCFG_CACHETIME:
                snmp_set_var_typed_value(
                     request->requestvb, ASN_INTEGER,
                    (u_char*)&extension->cache->timeout, sizeof(int));
                break;
            case COLUMN_EXTCFG_EXECTYPE:
                i = ((extension->flags & NS_EXTEND_FLAGS_SHELL) ?
                                         NS_EXTEND_ETYPE_SHELL :
                                         NS_EXTEND_ETYPE_EXEC);
                snmp_set_var_typed_value(
                     request->requestvb, ASN_INTEGER,
                    (u_char*)&i, sizeof(i));
                break;
            case COLUMN_EXTCFG_RUNTYPE:
                i = ((extension->flags & NS_EXTEND_FLAGS_WRITEABLE) ?
                                         NS_EXTEND_RTYPE_RWRITE :
                                         NS_EXTEND_RTYPE_RONLY);
                snmp_set_var_typed_value(
                     request->requestvb, ASN_INTEGER,
                    (u_char*)&i, sizeof(i));
                break;

            case COLUMN_EXTCFG_STORAGE:
                i = ((extension->flags & NS_EXTEND_FLAGS_CONFIG) ?
                                         ST_PERMANENT : ST_VOLATILE);
                snmp_set_var_typed_value(
                     request->requestvb, ASN_INTEGER,
                    (u_char*)&i, sizeof(i));
                break;
            case COLUMN_EXTCFG_STATUS:
                i = ((extension->flags & NS_EXTEND_FLAGS_ACTIVE) ?
                                         RS_ACTIVE :
                                         RS_NOTINSERVICE);
                snmp_set_var_typed_value(
                     request->requestvb, ASN_INTEGER,
                    (u_char*)&i, sizeof(i));
                break;

            default:
                netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
                continue;
            }
            break;

        /**********
         *
         * Start of SET handling
         *
         *   All config objects are potentially writable except
         *     nsExtendStorage which is fixed as either 'permanent'
         *     (if read from a config file) or 'volatile' (if set via SNMP)
         *   The string-based settings of a 'permanent' entry cannot 
         *     be changed - neither can the execution or run type.
         *   Such entries can be (temporarily) marked as inactive,
         *     and the cache timeout adjusted, but these changes are
         *     not persistent.
         *
         **********/

#ifndef NETSNMP_NO_WRITE_SUPPORT
        case MODE_SET_RESERVE1:
            /*
             * Validate the new assignments
             */
            switch (table_info->colnum) {
            case COLUMN_EXTCFG_COMMAND:
                if (request->requestvb->type != ASN_OCTET_STR) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGTYPE);
                    return SNMP_ERR_WRONGTYPE;
                }
                /*
                 * Must have a full path to the command
                 * XXX - Assumes Unix-style paths
                 */
                if (request->requestvb->val_len == 0 ||
                    request->requestvb->val.string[0] != '/') {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGVALUE);
                    return SNMP_ERR_WRONGVALUE;
                }
                /*
                 * XXX - need to check this file exists
                 *       (and is executable)
                 */

                if (extension && extension->flags & NS_EXTEND_FLAGS_CONFIG) {
                    /*
                     * config entries are "permanent" so can't be changed
                     */
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_NOTWRITABLE);
                    return SNMP_ERR_NOTWRITABLE;
                }
                break;

            case COLUMN_EXTCFG_ARGS:
            case COLUMN_EXTCFG_INPUT:
                if (request->requestvb->type != ASN_OCTET_STR) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGTYPE);
                    return SNMP_ERR_WRONGTYPE;
                }

                if (extension && extension->flags & NS_EXTEND_FLAGS_CONFIG) {
                    /*
                     * config entries are "permanent" so can't be changed
                     */
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_NOTWRITABLE);
                    return SNMP_ERR_NOTWRITABLE;
                }
                break;

            case COLUMN_EXTCFG_CACHETIME:
                if (request->requestvb->type != ASN_INTEGER) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGTYPE);
                    return SNMP_ERR_WRONGTYPE;
                }
                i = *request->requestvb->val.integer;
                /*
                 * -1 is a special value indicating "don't cache"
                 *    [[ XXX - should this be 0 ?? ]]
                 * Otherwise, cache times must be non-negative
                 */
                if (i < -1 ) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGVALUE);
                    return SNMP_ERR_WRONGVALUE;
                }
                break;

            case COLUMN_EXTCFG_EXECTYPE:
                if (request->requestvb->type != ASN_INTEGER) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGTYPE);
                    return SNMP_ERR_WRONGTYPE;
                }
                i = *request->requestvb->val.integer;
                if (i<1 || i>2) {  /* 'exec(1)' or 'shell(2)' only */
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGVALUE);
                    return SNMP_ERR_WRONGVALUE;
                }
                if (extension && extension->flags & NS_EXTEND_FLAGS_CONFIG) {
                    /*
                     * config entries are "permanent" so can't be changed
                     */
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_NOTWRITABLE);
                    return SNMP_ERR_NOTWRITABLE;
                }
                break;

            case COLUMN_EXTCFG_RUNTYPE:
                if (request->requestvb->type != ASN_INTEGER) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGTYPE);
                    return SNMP_ERR_WRONGTYPE;
                }
                /*
                 * 'run-on-read(1)', 'run-on-set(2)'
                 *  or 'run-command(3)' only
                 */
                i = *request->requestvb->val.integer;
                if (i<1 || i>3) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGVALUE);
                    return SNMP_ERR_WRONGVALUE;
                }
                /*
                 * 'run-command(3)' can only be used with
                 *  a pre-existing 'run-on-set(2)' entry.
                 */
                if (i==3 && !(extension && (extension->flags & NS_EXTEND_FLAGS_WRITEABLE))) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_INCONSISTENTVALUE);
                    return SNMP_ERR_INCONSISTENTVALUE;
                }
                /*
                 * 'run-command(3)' is the only valid assignment
                 *  for permanent (i.e. config) entries
                 */
                if ((extension && extension->flags & NS_EXTEND_FLAGS_CONFIG)
                    && i!=3 ) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_INCONSISTENTVALUE);
                    return SNMP_ERR_INCONSISTENTVALUE;
                }
                break;

            case COLUMN_EXTCFG_STATUS:
                if (request->requestvb->type != ASN_INTEGER) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGTYPE);
                    return SNMP_ERR_WRONGTYPE;
                }
                i = *request->requestvb->val.integer;
                switch (i) {
                case RS_ACTIVE:
                case RS_NOTINSERVICE:
                    if (!extension) {
                        /* Must be used with existing rows */
                        netsnmp_set_request_error(reqinfo, request,
                                                  SNMP_ERR_INCONSISTENTVALUE);
                        return SNMP_ERR_INCONSISTENTVALUE;
                    }
                    break;    /* OK */
                case RS_CREATEANDGO:
                case RS_CREATEANDWAIT:
                    if (extension) {
                        /* Can only be used to create new rows */
                        netsnmp_set_request_error(reqinfo, request,
                                                  SNMP_ERR_INCONSISTENTVALUE);
                        return SNMP_ERR_INCONSISTENTVALUE;
                    }
                    break;
                case RS_DESTROY:
                    break;
                default:
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGVALUE);
                    return SNMP_ERR_WRONGVALUE;
                }
                break;

            default:
                netsnmp_set_request_error(reqinfo, request,
                                          SNMP_ERR_NOTWRITABLE);
                return SNMP_ERR_NOTWRITABLE;
            }
            break;

        case MODE_SET_RESERVE2:
            switch (table_info->colnum) {
            case COLUMN_EXTCFG_STATUS:
                i = *request->requestvb->val.integer;
                switch (i) {
                case RS_CREATEANDGO:
                case RS_CREATEANDWAIT:
                    eptr = _find_extension_block( request->requestvb->name,
                                                  request->requestvb->name_length );
                    extension = _new_extension( (char *) table_info->indexes->val.string,
                                                0, eptr );
                    if (!extension) {  /* failed */
                        netsnmp_set_request_error(reqinfo, request,
                                                  SNMP_ERR_RESOURCEUNAVAILABLE);
                        return SNMP_ERR_RESOURCEUNAVAILABLE;
                    }
                    netsnmp_insert_table_row( request, extension->row );
                }
            }
            break;

        case MODE_SET_FREE:
            switch (table_info->colnum) {
            case COLUMN_EXTCFG_STATUS:
                i = *request->requestvb->val.integer;
                switch (i) {
                case RS_CREATEANDGO:
                case RS_CREATEANDWAIT:
                    eptr = _find_extension_block( request->requestvb->name,
                                                  request->requestvb->name_length );
                    _free_extension( extension, eptr );
                }
            }
            break;

        case MODE_SET_ACTION:
            switch (table_info->colnum) {
            case COLUMN_EXTCFG_COMMAND:
                extension->old_command = extension->command;
                extension->command = netsnmp_strdup_and_null(
                    request->requestvb->val.string,
                    request->requestvb->val_len);
                break;
            case COLUMN_EXTCFG_ARGS:
                extension->old_args = extension->args;
                extension->args = netsnmp_strdup_and_null(
                    request->requestvb->val.string,
                    request->requestvb->val_len);
                break;
            case COLUMN_EXTCFG_INPUT:
                extension->old_input = extension->input;
                extension->input = netsnmp_strdup_and_null(
                    request->requestvb->val.string,
                    request->requestvb->val_len);
                break;
            case COLUMN_EXTCFG_STATUS:
                i = *request->requestvb->val.integer;
                switch (i) {
                case RS_ACTIVE:
                case RS_CREATEANDGO:
                    need_to_validate = 1;
                }
                break;
            }
            break;

        case MODE_SET_UNDO:
            switch (table_info->colnum) {
            case COLUMN_EXTCFG_COMMAND:
                if ( extension && extension->old_command ) {
                    SNMP_FREE(extension->command);
                    extension->command     = extension->old_command;
                    extension->old_command = NULL;
                }
                break;
            case COLUMN_EXTCFG_ARGS:
                if ( extension && extension->old_args ) {
                    SNMP_FREE(extension->args);
                    extension->args     = extension->old_args;
                    extension->old_args = NULL;
                }
                break;
            case COLUMN_EXTCFG_INPUT:
                if ( extension && extension->old_input ) {
                    SNMP_FREE(extension->input);
                    extension->input     = extension->old_input;
                    extension->old_input = NULL;
                }
                break;
            case COLUMN_EXTCFG_STATUS:
                i = *request->requestvb->val.integer;
                switch (i) {
                case RS_CREATEANDGO:
                case RS_CREATEANDWAIT:
                    eptr = _find_extension_block( request->requestvb->name,
                                                  request->requestvb->name_length );
                    _free_extension( extension, eptr );
                }
                break;
            }
            break;

        case MODE_SET_COMMIT:
            switch (table_info->colnum) {
            case COLUMN_EXTCFG_CACHETIME:
                i = *request->requestvb->val.integer;
                extension->cache->timeout = i;
                break;

            case COLUMN_EXTCFG_RUNTYPE:
                i = *request->requestvb->val.integer;
                switch (i) {
                case 1:
                    extension->flags &= ~NS_EXTEND_FLAGS_WRITEABLE;
                    break;
                case 2:
                    extension->flags |=  NS_EXTEND_FLAGS_WRITEABLE;
                    break;
                case 3:
                    (void)netsnmp_cache_check_and_reload( extension->cache );
                    break;
                }
                break;

            case COLUMN_EXTCFG_EXECTYPE:
                i = *request->requestvb->val.integer;
                if ( i == NS_EXTEND_ETYPE_SHELL )
                    extension->flags |=  NS_EXTEND_FLAGS_SHELL;
                else
                    extension->flags &= ~NS_EXTEND_FLAGS_SHELL;
                break;

            case COLUMN_EXTCFG_STATUS:
                i = *request->requestvb->val.integer;
                switch (i) {
                case RS_ACTIVE:
                case RS_CREATEANDGO:
                    extension->flags |= NS_EXTEND_FLAGS_ACTIVE;
                    break;
                case RS_NOTINSERVICE:
                case RS_CREATEANDWAIT:
                    extension->flags &= ~NS_EXTEND_FLAGS_ACTIVE;
                    break;
                case RS_DESTROY:
                    eptr = _find_extension_block( request->requestvb->name,
                                                  request->requestvb->name_length );
                    _free_extension( extension, eptr );
                    break;
                }
            }
            break;
#endif /* !NETSNMP_NO_WRITE_SUPPORT */ 

        default:
            netsnmp_set_request_error(reqinfo, request, SNMP_ERR_GENERR);
            return SNMP_ERR_GENERR;
        }
    }

#ifndef NETSNMP_NO_WRITE_SUPPORT
    /*
     * If we're marking a given row as active,
     *  then we need to check that it's ready.
     */
    if (need_to_validate) {
        for ( request=requests; request; request=request->next ) {
            if (request->processed)
                continue;
            table_info = netsnmp_extract_table_info( request );
            extension  = (netsnmp_extend*)netsnmp_extract_table_row_data( request );
            switch (table_info->colnum) {
            case COLUMN_EXTCFG_STATUS:
                i = *request->requestvb->val.integer;
                if (( i == RS_ACTIVE || i == RS_CREATEANDGO ) &&
                    !(extension && extension->command &&
                      extension->command[0] == '/' /* &&
                      is_executable(extension->command) */)) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_INCONSISTENTVALUE);
                    return SNMP_ERR_INCONSISTENTVALUE;
                }
            }
        }
    }
#endif /* !NETSNMP_NO_WRITE_SUPPORT */
    
    return SNMP_ERR_NOERROR;
}


int
handle_nsExtendOutput1Table(netsnmp_mib_handler          *handler,
                     netsnmp_handler_registration *reginfo,
                     netsnmp_agent_request_info   *reqinfo,
                     netsnmp_request_info         *requests)
{
    netsnmp_request_info       *request;
    netsnmp_table_request_info *table_info;
    netsnmp_extend             *extension;
    int len;

    for ( request=requests; request; request=request->next ) {
        if (request->processed)
            continue;
        table_info = netsnmp_extract_table_info( request );
        extension  = (netsnmp_extend*)netsnmp_extract_table_row_data( request );

        DEBUGMSGTL(( "nsExtendTable:output1", "varbind: "));
        DEBUGMSGOID(("nsExtendTable:output1", request->requestvb->name,
                                              request->requestvb->name_length));
        DEBUGMSG((   "nsExtendTable:output1", "\n"));

        switch (reqinfo->mode) {
        case MODE_GET:
            if (!extension || !(extension->flags & NS_EXTEND_FLAGS_ACTIVE)) {
                /*
                 * If this row is inactive, then skip it.
                 */
                netsnmp_set_request_error(reqinfo, request,
                                          SNMP_NOSUCHINSTANCE);
                continue;
            }
            if (!(extension->flags & NS_EXTEND_FLAGS_WRITEABLE) &&
                (netsnmp_cache_check_and_reload( extension->cache ) < 0 )) {
                /*
                 * If reloading the output cache of a 'run-on-read'
                 * entry fails, then skip it.
                 */
                netsnmp_set_request_error(reqinfo, request,
                                          SNMP_NOSUCHINSTANCE);
                continue;
            }
            if ((extension->flags & NS_EXTEND_FLAGS_WRITEABLE) &&
                (netsnmp_cache_check_expired( extension->cache ) == 1 )) {
                /*
                 * If the output cache of a 'run-on-write'
                 * entry has expired, then skip it.
                 */
                netsnmp_set_request_error(reqinfo, request,
                                          SNMP_NOSUCHINSTANCE);
                continue;
            }

            switch (table_info->colnum) {
            case COLUMN_EXTOUT1_OUTLEN:
                snmp_set_var_typed_value(
                     request->requestvb, ASN_INTEGER,
                    (u_char*)&extension->out_len, sizeof(int));
                break;
            case COLUMN_EXTOUT1_OUTPUT1:
                /* 
                 * If we've got more than one line,
                 * find the length of the first one.
                 * Otherwise find the length of the whole string.
                 */
                if (extension->numlines > 1) {
                    len = (extension->lines[1])-(extension->output) -1;
                } else if (extension->output) {
                    len = strlen(extension->output);
                } else {
                    len = 0;
                }
                snmp_set_var_typed_value(
                     request->requestvb, ASN_OCTET_STR,
                     extension->output, len);
                break;
            case COLUMN_EXTOUT1_OUTPUT2:
                snmp_set_var_typed_value(
                     request->requestvb, ASN_OCTET_STR,
                     extension->output,
                    (extension->output)?extension->out_len:0);
                break;
            case COLUMN_EXTOUT1_NUMLINES:
                snmp_set_var_typed_value(
                     request->requestvb, ASN_INTEGER,
                    (u_char*)&extension->numlines, sizeof(int));
                break;
            case COLUMN_EXTOUT1_RESULT:
                snmp_set_var_typed_value(
                     request->requestvb, ASN_INTEGER,
                    (u_char*)&extension->result, sizeof(int));
                break;
            default:
                netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
                continue;
            }
            break;
        default:
            netsnmp_set_request_error(reqinfo, request, SNMP_ERR_GENERR);
            return SNMP_ERR_GENERR;
        }
    }
    return SNMP_ERR_NOERROR;
}


        /*************************
         *
         *  Multi-line output table handler
         *  Most of the work is handled here.
         *
         *************************/


/*
 *  Locate the appropriate entry for a given request
 */
netsnmp_extend *
_extend_find_entry( netsnmp_request_info       *request,
                    netsnmp_table_request_info *table_info,
                    int mode  )
{
    netsnmp_extend            *eptr;
    extend_registration_block *ereg;
    unsigned int line_idx;
    oid oid_buf[MAX_OID_LEN];
    int oid_len;
    int i;
    char  *token;
    size_t token_len;

    if (!request || !table_info || !table_info->indexes
                 || !table_info->indexes->next_variable) {
        DEBUGMSGTL(( "nsExtendTable:output2", "invalid invocation\n"));
        return NULL;
    }

    ereg = _find_extension_block( request->requestvb->name,
                                  request->requestvb->name_length );

    /***
     *  GET handling - find the exact entry being requested
     ***/
    if ( mode == MODE_GET ) {
        DEBUGMSGTL(( "nsExtendTable:output2", "GET: %s / %ld\n ",
                      table_info->indexes->val.string,
                     *table_info->indexes->next_variable->val.integer));
        for ( eptr = ereg->ehead; eptr; eptr = eptr->next ) {
            if ( !strcmp( eptr->token, (char *) table_info->indexes->val.string ))
                break;
        }

        if ( eptr ) {
            /*
             * Ensure the output is available...
             */
            if (!(eptr->flags & NS_EXTEND_FLAGS_ACTIVE) ||
               (netsnmp_cache_check_and_reload( eptr->cache ) < 0 ))
                return NULL;

            /*
             * ...and check the line requested is valid
             */
            line_idx = *table_info->indexes->next_variable->val.integer;
            if (line_idx < 1 || line_idx > eptr->numlines)
                return NULL;
        }
    }

        /***
         *  GETNEXT handling - find the first suitable entry
         ***/
    else {
        if (!table_info->indexes->val_len ) {
            DEBUGMSGTL(( "nsExtendTable:output2", "GETNEXT: first entry\n"));
            /*
             * Beginning of the table - find the first active
             *  (and successful) entry, and use the first line of it
             */
            for (eptr = ereg->ehead; eptr; eptr = eptr->next ) {
                if ((eptr->flags & NS_EXTEND_FLAGS_ACTIVE) &&
                    (netsnmp_cache_check_and_reload( eptr->cache ) >= 0 )) {
                    line_idx = 1;
                    break;
                }
            }
        } else {
            token     =  (char *) table_info->indexes->val.string;
            token_len =  table_info->indexes->val_len;
            line_idx  = *table_info->indexes->next_variable->val.integer;
            DEBUGMSGTL(( "nsExtendTable:output2", "GETNEXT: %s / %d\n ",
                          token, line_idx ));
            /*
             * Otherwise, find the first entry not earlier
             * than the requested token...
             */
            for (eptr = ereg->ehead; eptr; eptr = eptr->next ) {
                if ( strlen(eptr->token) > token_len )
                    break;
                if ( strlen(eptr->token) == token_len &&
                     strcmp(eptr->token, token) >= 0 )
                    break;
            }
            if (!eptr)
                return NULL;    /* (assuming there is one) */

            /*
             * ... and make sure it's active & the output is available
             * (or use the first following entry that is)
             */
            for (    ; eptr; eptr = eptr->next ) {
                if ((eptr->flags & NS_EXTEND_FLAGS_ACTIVE) &&
                    (netsnmp_cache_check_and_reload( eptr->cache ) >= 0 )) {
                    break;
                }
                line_idx = 1;
            }

            if (!eptr)
                return NULL;    /* (assuming there is one) */

            /*
             *  If we're working with the same entry that was requested,
             *  see whether we've reached the end of the output...
             */
            if (!strcmp( eptr->token, token )) {
                if ( eptr->numlines <= line_idx ) {
                    /*
                     * ... and if so, move on to the first line
                     * of the next (active and successful) entry.
                     */
                    line_idx = 1;
                    for (eptr = eptr->next ; eptr; eptr = eptr->next ) {
                        if ((eptr->flags & NS_EXTEND_FLAGS_ACTIVE) &&
                            (netsnmp_cache_check_and_reload( eptr->cache ) >= 0 )) {
                            break;
                        }
                    }
                } else {
                    /*
                     * Otherwise just use the next line of this entry.
                     */
                    line_idx++;
                }
            }
            else {
                /*
                 * If this is not the same entry that was requested,
                 * then we should return the first line.
                 */
                line_idx = 1;
            }
        }
        if (eptr) {
            DEBUGMSGTL(( "nsExtendTable:output2", "GETNEXT -> %s / %d\n ",
                          eptr->token, line_idx));
            /*
             * Since we're processing a GETNEXT request,
             * now we've found the appropriate entry (and line),
             * we need to update the varbind OID ...
             */
            memset(oid_buf, 0, sizeof(oid_buf));
            oid_len = ereg->oid_len;
            memcpy( oid_buf, ereg->root_oid, oid_len*sizeof(oid));
            oid_buf[ oid_len++ ] = 4;    /* nsExtendOutput2Table */
            oid_buf[ oid_len++ ] = 1;    /* nsExtendOutput2Entry */
            oid_buf[ oid_len++ ] = COLUMN_EXTOUT2_OUTLINE;
                                         /* string token index */
            oid_buf[ oid_len++ ] = strlen(eptr->token);
            for ( i=0; i < (int)strlen(eptr->token); i++ )
                oid_buf[ oid_len+i ] = eptr->token[i];
            oid_len += strlen( eptr->token );
                                         /* plus line number */
            oid_buf[ oid_len++ ] = line_idx;
            snmp_set_var_objid( request->requestvb, oid_buf, oid_len );
            /*
             * ... and index values to match.
             */
            snmp_set_var_value( table_info->indexes,
                                eptr->token, strlen(eptr->token));
            snmp_set_var_value( table_info->indexes->next_variable,
                                (const u_char*)&line_idx, sizeof(line_idx));
        }
    }
    return eptr;  /* Finally, signal success */
}

/*
 *  Multi-line output handler
 *  Locate the appropriate entry (using _extend_find_entry)
 *  and return the appropriate output line
 */
int
handle_nsExtendOutput2Table(netsnmp_mib_handler          *handler,
                     netsnmp_handler_registration *reginfo,
                     netsnmp_agent_request_info   *reqinfo,
                     netsnmp_request_info         *requests)
{
    netsnmp_request_info       *request;
    netsnmp_table_request_info *table_info;
    netsnmp_extend             *extension;
    char *cp;
    unsigned int line_idx;
    int len;

    for ( request=requests; request; request=request->next ) {
        if (request->processed)
            continue;

        table_info = netsnmp_extract_table_info( request );
        extension  = _extend_find_entry( request, table_info, reqinfo->mode );

        DEBUGMSGTL(( "nsExtendTable:output2", "varbind: "));
        DEBUGMSGOID(("nsExtendTable:output2", request->requestvb->name,
                                              request->requestvb->name_length));
        DEBUGMSG((   "nsExtendTable:output2", " (%s)\n",
                                    (extension) ? extension->token : "[none]"));

        if (!extension) {
            if (reqinfo->mode == MODE_GET)
                netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHINSTANCE);
            else
                netsnmp_set_request_error(reqinfo, request, SNMP_ENDOFMIBVIEW);
            continue;
        }

        switch (reqinfo->mode) {
        case MODE_GET:
        case MODE_GETNEXT:
            switch (table_info->colnum) {
            case COLUMN_EXTOUT2_OUTLINE:
                /* 
                 * Determine which line we've been asked for....
                 */
                line_idx = *table_info->indexes->next_variable->val.integer;
                if (line_idx < 1 || line_idx > extension->numlines) {
                    netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHINSTANCE);
                    continue;
                }
                cp  = extension->lines[line_idx-1];

                /* 
                 * ... and how long it is.
                 */
                if ( extension->numlines > line_idx )
                    len = (extension->lines[line_idx])-cp -1;
                else if (cp)
                    len = strlen(cp);
                else
                    len = 0;

                snmp_set_var_typed_value( request->requestvb,
                                          ASN_OCTET_STR, cp, len );
                break;
            default:
                netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
                continue;
            }
            break;
        default:
            netsnmp_set_request_error(reqinfo, request, SNMP_ERR_GENERR);
            return SNMP_ERR_GENERR;
        }
    }
    return SNMP_ERR_NOERROR;
}

#ifndef USING_UCD_SNMP_EXTENSIBLE_MODULE
        /*************************
         *
         *  Compatability with the UCD extTable
         *
         *************************/

char * _get_cmdline(netsnmp_extend *extend)
{
    size_t          size;
    char           *newbuf;
    const char     *args = extend->args;

    if (args == NULL)
        /* Use empty string for processes without arguments. */
        args = "";

    size = strlen(extend->command) + strlen(args) + 2;
    if (size > cmdlinesize) {
        newbuf = realloc(cmdlinebuf, size);
        if (!newbuf) {
            free(cmdlinebuf);
            cmdlinebuf = NULL;
            cmdlinesize = 0;
            return NULL;
        }
        cmdlinebuf = newbuf;
        cmdlinesize = size;
    }
    sprintf(cmdlinebuf, "%s %s", extend->command, args);
    return cmdlinebuf;
}

u_char *
var_extensible_old(struct variable * vp,
                     oid * name,
                     size_t * length,
                     int exact,
                     size_t * var_len, WriteMethod ** write_method)
{
    netsnmp_old_extend *exten = NULL;
    static long     long_ret;
    unsigned int idx;
    char         *cmdline;

    if (header_simple_table
        (vp, name, length, exact, var_len, write_method, num_compatability_entries))
        return (NULL);

    idx = name[*length-1] -1;
	if (idx > max_compatability_entries)
		return NULL;
    exten = &compatability_entries[ idx ];
    if (exten) {
        switch (vp->magic) {
        case MIBINDEX:
            long_ret = name[*length - 1];
            return ((u_char *) (&long_ret));
        case ERRORNAME:        /* name defined in config file */
            *var_len = strlen(exten->exec_entry->token);
            return ((u_char *) (exten->exec_entry->token));
        case SHELLCOMMAND:
            cmdline = _get_cmdline(exten->exec_entry);
            if (cmdline)
                *var_len = strlen(cmdline);
            return ((u_char *) cmdline);
        case ERRORFLAG:        /* return code from the process */
            netsnmp_cache_check_and_reload( exten->exec_entry->cache );
            long_ret = exten->exec_entry->result;
            return ((u_char *) (&long_ret));
        case ERRORMSG:         /* first line of text returned from the process */
            netsnmp_cache_check_and_reload( exten->exec_entry->cache );
            if (exten->exec_entry->numlines > 1) {
                *var_len = (exten->exec_entry->lines[1])-
                           (exten->exec_entry->output) -1;
            } else if (exten->exec_entry->output) {
                *var_len = strlen(exten->exec_entry->output);
            } else {
                *var_len = 0;
            }
            return ((u_char *) (exten->exec_entry->output));
        case ERRORFIX:
            *write_method = fixExec2Error;
            long_return = 0;
            return ((u_char *) &long_return);

        case ERRORFIXCMD:
            if (exten->efix_entry) {
                cmdline = _get_cmdline(exten->efix_entry);
		if (cmdline)
                    *var_len = strlen(cmdline);
                return ((u_char *) cmdline);
            } else {
                *var_len = 0;
                return ((u_char *) &long_return);  /* Just needs to be non-null! */
            }
        }
        return NULL;
    }
    return NULL;
}


int
fixExec2Error(int action,
             u_char * var_val,
             u_char var_val_type,
             size_t var_val_len,
             u_char * statP, oid * name, size_t name_len)
{
    netsnmp_old_extend *exten = NULL;
    unsigned int idx;

    idx = name[name_len-1] -1;
    exten = &compatability_entries[ idx ];

#ifndef NETSNMP_NO_WRITE_SUPPORT
    switch (action) {
    case MODE_SET_RESERVE1:
        if (var_val_type != ASN_INTEGER) {
            snmp_log(LOG_ERR, "Wrong type != int\n");
            return SNMP_ERR_WRONGTYPE;
        }
        idx = *((long *) var_val);
        if (idx != 1) {
            snmp_log(LOG_ERR, "Wrong value != 1\n");
            return SNMP_ERR_WRONGVALUE;
        }
        if (!exten || !exten->efix_entry) {
            snmp_log(LOG_ERR, "No command to run\n");
            return SNMP_ERR_GENERR;
        }
        return SNMP_ERR_NOERROR;

    case MODE_SET_COMMIT:
        netsnmp_cache_check_and_reload( exten->efix_entry->cache );
    }
#endif /* !NETSNMP_NO_WRITE_SUPPORT */
    return SNMP_ERR_NOERROR;
}
#endif /* USING_UCD_SNMP_EXTENSIBLE_MODULE */