/** allows overriding of a given oid with a new type and value */

/* Portions of this file are subject to the following copyright(s).  See
 * the Net-SNMP's COPYING file for more details and other copyrights
 * that may apply:
 */
/*
 * Portions of this file are copyrighted by:
 * Copyright © 2003 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 */

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "override.h"

typedef struct override_data_s {
    int             type;
    void           *value;
    size_t          value_len;
    void           *set_space;
    size_t          set_len;
} override_data;

/** @todo: (optionally) save values persistently when configured for
 *  read-write */
int
override_handler(netsnmp_mib_handler *handler,
                 netsnmp_handler_registration *reginfo,
                 netsnmp_agent_request_info *reqinfo,
                 netsnmp_request_info *requests)
{

    override_data  *data = (override_data*)handler->myvoid;
    void *tmpptr;

    if (!data) {
        netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_GENERR);
        return SNMP_ERR_NOERROR;
    }

    switch (reqinfo->mode) {
    case MODE_GET:
        DEBUGMSGTL(("override", "overriding oid "));
        DEBUGMSGOID(("override", requests->requestvb->name,
                     requests->requestvb->name_length));
        DEBUGMSG(("override", "\n"));
        snmp_set_var_typed_value(requests->requestvb, (u_char)data->type,
                                 (u_char *) data->value, data->value_len);
        break;

#ifndef NETSNMP_NO_WRITE_SUPPORT
    case MODE_SET_RESERVE1:
        if (requests->requestvb->type != data->type)
            netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_WRONGTYPE);
        break;

    case MODE_SET_RESERVE2:
        data->set_space = netsnmp_memdup(requests->requestvb->val.string,
                                         requests->requestvb->val_len);
        if (!data->set_space)
            netsnmp_set_request_error(reqinfo, requests,
                                      SNMP_ERR_RESOURCEUNAVAILABLE);
        break;

    case MODE_SET_FREE:
        SNMP_FREE(data->set_space);
        break;

    case MODE_SET_ACTION:
        /* swap the values in */
        tmpptr = data->value;
        data->value = data->set_space;
        data->set_space = tmpptr;

        /* set the lengths */
        data->set_len = data->value_len;
        data->value_len = requests->requestvb->val_len;
        break;

    case MODE_SET_UNDO:
        SNMP_FREE(data->value);
        data->value = data->set_space;
        data->value_len = data->set_len;
        break;

    case MODE_SET_COMMIT:
        SNMP_FREE(data->set_space);
        break;
#endif /* !NETSNMP_NO_WRITE_SUPPORT */

    default:
        snmp_log(LOG_ERR, "unsupported mode in override handler\n");
        netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_GENERR);
        return SNMP_ERR_GENERR;
    }
    return SNMP_ERR_NOERROR;
}

#define MALLOC_OR_DIE(x) \
  thedata->value = malloc(x); \
  thedata->value_len = x; \
  if (!thedata->value) { \
      free(thedata); \
      config_perror("memory allocation failure"); \
      return; \
  }

void
netsnmp_parse_override(const char *token, char *line)
{
    char           *cp;
    char            buf[SNMP_MAXBUF], namebuf[SNMP_MAXBUF];
    int             readwrite = 0;
    oid             oidbuf[MAX_OID_LEN];
    size_t          oidbuf_len = MAX_OID_LEN;
    int             type;
    override_data  *thedata;
    netsnmp_handler_registration *the_reg;

    cp = copy_nword(line, namebuf, sizeof(namebuf) - 1);
    if (strcmp(namebuf, "-rw") == 0) {
        readwrite = 1;
        cp = copy_nword(cp, namebuf, sizeof(namebuf) - 1);
    }

    if (!cp) {
        config_perror("no oid specified");
        return;
    }

    if (!snmp_parse_oid(namebuf, oidbuf, &oidbuf_len)) {
        config_perror("illegal oid");
        return;
    }
    cp = copy_nword(cp, buf, sizeof(buf) - 1);

    if (!cp && strcmp(buf, "null") != 0) {
        config_perror("no variable value specified");
        return;
    }

    {
        struct { const char* key; int value; } const strings[] = {
            { "counter", ASN_COUNTER },
            { "counter64", ASN_COUNTER64 },
            { "integer", ASN_INTEGER },
            { "ipaddress", ASN_IPADDRESS },
            { "nsap", ASN_NSAP },
            { "null", ASN_NULL },
            { "object_id", ASN_OBJECT_ID },
            { "octet_str", ASN_OCTET_STR },
            { "opaque", ASN_OPAQUE },
#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES
            { "opaque_counter64", ASN_OPAQUE_COUNTER64 },
            { "opaque_double", ASN_OPAQUE_DOUBLE },
            { "opaque_float", ASN_OPAQUE_FLOAT },
            { "opaque_i64", ASN_OPAQUE_I64 },
            { "opaque_u64", ASN_OPAQUE_U64 },
#endif
            { "timeticks", ASN_TIMETICKS },
            { "uinteger", ASN_GAUGE },
            { "unsigned", ASN_UNSIGNED },
            { NULL, 0 }
        }, * run;
        for(run = strings; run->key && strcasecmp(run->key, buf) < 0; ++run);
        if(run->key && strcasecmp(run->key, buf) == 0)
            type = run->value;
        else {
            config_perror("unknown type specified");
            return;
        }
    }

    if (cp)
        copy_nword(cp, buf, sizeof(buf) - 1);
    else
        buf[0] = 0;

    thedata = SNMP_MALLOC_TYPEDEF(override_data);
    if (!thedata) {
        config_perror("memory allocation failure");
        return;
    }
    thedata->type = type;

    switch (type) {
    case ASN_INTEGER:
        MALLOC_OR_DIE(sizeof(long));
        *((long *) thedata->value) = strtol(buf, NULL, 0);
        break;

    case ASN_COUNTER:
    case ASN_TIMETICKS:
    case ASN_UNSIGNED:
        MALLOC_OR_DIE(sizeof(u_long));
        *((u_long *) thedata->value) = strtoul(buf, NULL, 0);
        break;

    case ASN_OCTET_STR:
    case ASN_BIT_STR:
        if (buf[0] == '0' && buf[1] == 'x') {
            /*
             * hex 
             */
            thedata->value_len =
                hex_to_binary2((u_char *)(buf + 2), strlen(buf) - 2,
                               (char **) &thedata->value);
        } else {
            thedata->value = strdup(buf);
            thedata->value_len = strlen(buf);
        }
        break;

    case ASN_OBJECT_ID:
        read_config_read_objid(buf, (oid **) & thedata->value,
                               &thedata->value_len);
        /* We need the size of the value in bytes, not in oids */
        thedata->value_len *= sizeof(oid);
        break;

    case ASN_NULL:
        thedata->value_len = 0;
        break;

    default:
        SNMP_FREE(thedata);
        config_perror("illegal/unsupported type specified");
        return;
    }

    if (!thedata->value && thedata->type != ASN_NULL) {
        config_perror("memory allocation failure");
        free(thedata);
        return;
    }

    the_reg = SNMP_MALLOC_TYPEDEF(netsnmp_handler_registration);
    if (!the_reg) {
        config_perror("memory allocation failure");
        free(thedata);
        return;
    }

    the_reg->handlerName = strdup(namebuf);
    the_reg->priority = 255;
    the_reg->modes = (readwrite) ? HANDLER_CAN_RWRITE : HANDLER_CAN_RONLY;
    the_reg->handler =
        netsnmp_create_handler("override", override_handler);
    the_reg->rootoid = snmp_duplicate_objid(oidbuf, oidbuf_len);
    the_reg->rootoid_len = oidbuf_len;
    if (!the_reg->rootoid || !the_reg->handler || !the_reg->handlerName) {
        if (the_reg->handler)
            SNMP_FREE(the_reg->handler->handler_name);
        SNMP_FREE(the_reg->handler);
        SNMP_FREE(the_reg->handlerName);
        SNMP_FREE(the_reg);
        config_perror("memory allocation failure");
        free(thedata);
        return;
    }
    the_reg->handler->myvoid = thedata;

    if (netsnmp_register_instance(the_reg)) {
        config_perror("oid registration failed within the agent");
        SNMP_FREE(thedata->value);
        free(thedata);
        return;
    }
}


void
init_override(void)
{

    snmpd_register_config_handler("override", netsnmp_parse_override, NULL,     /* XXX: free func */
                                  "[-rw] mibnode type value");
}
