/*
 * snmpTargetAddrEntry MIB
 *
 * This file was created to separate notification data storage from
 * the MIB implementation.
 *
 * Portions of this file are copyrighted by:
 * Copyright (c) 2016 VMware, 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>
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include <stdlib.h>
#include <ctype.h>

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

#include "snmpTargetAddrEntry_data.h"
#include "util_funcs/header_generic.h"

netsnmp_feature_require(container_compare_mem);

static struct targetAddrTable_struct *aAddrTable = NULL;
static int _active = 0;

/*
 * Utility routines
 */
static int store_snmpTargetAddrEntry(int majorID, int minorID, void *serverarg,
                                     void *clientarg);
static void snmpd_parse_config_targetAddr(const char *token, char *char_ptr);

struct targetAddrTable_struct *
get_addrTable(void)
{
    return aAddrTable;
}

struct targetAddrTable_struct *
get_addrForName2(const char *name, size_t nameLen)
{
    struct targetAddrTable_struct *ptr;
    for (ptr = aAddrTable; ptr != NULL; ptr = ptr->next) {
        if (ptr->nameLen == nameLen && ptr->nameData &&
            memcmp(ptr->nameData, name, nameLen) == 0)
            return ptr;
    }
    return NULL;
}


/*
 * TargetAddrTable_create creates and returns a pointer
 * to a targetAddrTable_struct with default values set
 */
struct targetAddrTable_struct *
snmpTargetAddrTable_create(void)
{
    struct targetAddrTable_struct *newEntry;

    newEntry = (struct targetAddrTable_struct *)
        calloc(1, sizeof(struct targetAddrTable_struct));

    if (newEntry) {
        ++_active;
        newEntry->timeout = 1500;
        newEntry->retryCount = 3;

        newEntry->tagListData = strdup("");
        newEntry->tagListLen = 0;

        newEntry->storageType = SNMP_STORAGE_NONVOLATILE;
        newEntry->rowStatus = SNMP_ROW_NONEXISTENT;
    }

    return newEntry;
}                               /* snmpTargetAddrTable_create */


/*
 * TargetAddrTable_dispose frees the space allocated to a
 * targetAddrTable_struct
 */
void
snmpTargetAddrTable_dispose(struct targetAddrTable_struct *reaped)
{
    if (NULL == reaped)
        return;

    if (reaped->sess)
        snmp_close(reaped->sess);
    else
        SNMP_FREE(reaped->tAddress);
    SNMP_FREE(reaped->nameData);
    SNMP_FREE(reaped->tagListData);
    SNMP_FREE(reaped->paramsData);

    SNMP_FREE(reaped);
    --_active;
}                               /* snmpTargetAddrTable_dispose  */

/*
 * snmpTargetAddrTable_addToList adds a targetAddrTable_struct
 * to a list passed in. The list is assumed to be in a sorted order,
 * low to high and this procedure inserts a new struct in the proper
 * location. Sorting uses OID values based on name. A new equal value
 * overwrites a current one.
 */
void
snmpTargetAddrTable_addToList(struct targetAddrTable_struct *newEntry,
                              struct targetAddrTable_struct **listPtr)
{
    static struct targetAddrTable_struct *curr_struct, *prev_struct;
    int             i;

    /*
     * if the list is empty, add the new entry to the top
     */
    if ((prev_struct = curr_struct = *listPtr) == NULL) {
        *listPtr = newEntry;
        return;
    } else {
        /*
         * search through the list for an equal or greater OID value
         */
        while (curr_struct != NULL) {
            i = netsnmp_compare_mem(newEntry->nameData,
                                    newEntry->nameLen,
                                    curr_struct->nameData,
                                    curr_struct->nameLen);
            if (i == 0) {       /* Exact match, overwrite with new struct */
                newEntry->next = curr_struct->next;
                /*
                 * if curr_struct is the top of the list
                 */
                if (*listPtr == curr_struct)
                    *listPtr = newEntry;
                else
                    prev_struct->next = newEntry;
                snmpTargetAddrTable_dispose(curr_struct);
                return;
            } else if (i < 0) { /* Found a greater OID, insert struct in front of it. */
                newEntry->next = curr_struct;
                /*
                 * if curr_struct is the top of the list
                 */
                if (*listPtr == curr_struct)
                    *listPtr = newEntry;
                else
                    prev_struct->next = newEntry;
                return;
            }
            prev_struct = curr_struct;
            curr_struct = curr_struct->next;
        }
    }
    /*
     * if we're here, no larger OID was ever found, insert on end of list
     */
    prev_struct->next = newEntry;
}                               /* snmpTargeAddrTable_addToList  */


void
snmpTargetAddrTable_add(struct targetAddrTable_struct *newEntry)
{
    snmpTargetAddrTable_addToList(newEntry, &aAddrTable);
}


/*
 * snmpTargetAddrTable_remFromList removes a targetAddrTable_struct
 * from the list passed in
 */
void
snmpTargetAddrTable_remFromList(struct targetAddrTable_struct *oldEntry,
                                struct targetAddrTable_struct **listPtr)
{
    struct targetAddrTable_struct *tptr;

    if ((tptr = *listPtr) == NULL)
        return;
    else if (tptr == oldEntry) {
        *listPtr = (*listPtr)->next;
        snmpTargetAddrTable_dispose(tptr);
        return;
    } else {
        while (tptr->next != NULL) {
            if (tptr->next == oldEntry) {
                tptr->next = tptr->next->next;
                snmpTargetAddrTable_dispose(oldEntry);
                return;
            }
            tptr = tptr->next;
        }
    }
}                               /* snmpTargetAddrTable_remFromList  */

void
snmpTargetAddrTable_remove(struct targetAddrTable_struct *entry)
{
    snmpTargetAddrTable_remFromList(entry, &aAddrTable);
}

/*
 * lookup OID in the link list of Addr Table Entries
 */
struct targetAddrTable_struct *
search_snmpTargetAddrTable(oid * baseName,
                           size_t baseNameLen,
                           oid * name, size_t * length, int exact)
{
    static struct targetAddrTable_struct *temp_struct;
    int             i;
    size_t          myOIDLen = 0;
    oid             newNum[128];

    /*
     * lookup entry in addrTable linked list, Get Current MIB ID
     */
    memcpy(newNum, baseName, baseNameLen * sizeof(oid));

    for (temp_struct = aAddrTable; temp_struct != NULL;
         temp_struct = temp_struct->next) {
        for (i = 0; i < temp_struct->nameLen; i++) {
            newNum[baseNameLen + i] = temp_struct->nameData[i];
        }
        myOIDLen = baseNameLen + i;
        i = snmp_oid_compare(name, *length, newNum, myOIDLen);
        /*
         * Assumes that the linked list sorted by OID, low to high
         */
        if ((i == 0 && exact != 0) || (i < 0 && exact == 0)) {
            if (exact == 0) {
                memcpy(name, newNum, myOIDLen * sizeof(oid));
                *length = myOIDLen;
            }
            return temp_struct;
        }
    }
    return NULL;
}                               /* search_snmpTargetAddrTable  */


/*
 * Init routines
 */

void
init_snmpTargetAddrEntry_data(void)
{
    static int done = 0;

    if (++done != 1)
        return;

    snmpd_register_config_handler("targetAddr",
                                  snmpd_parse_config_targetAddr,
                                  (void (*)(void))0, NULL);

    /*
     * we need to be called back later 
     */
    snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_STORE_DATA,
                           store_snmpTargetAddrEntry, NULL);

}                               /* init_snmpTargetAddrEntry */


/*
 * Shutdown routines
 */

void
shutdown_snmpTargetAddrEntry_data(void)
{
    struct targetAddrTable_struct *ptr;
    struct targetAddrTable_struct *next;

    snmp_unregister_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_STORE_DATA,
                             store_snmpTargetAddrEntry, NULL, FALSE);

    DEBUGMSGTL(("trap:targetAddr:shutdown", "clearing %d object(s)\n",
                _active));
    for (ptr = aAddrTable; ptr; ptr = next) {
        next = ptr->next;
        snmpTargetAddrTable_dispose(ptr);
    }
    aAddrTable = NULL;

    DEBUGMSGTL(("trap:targetAddr:shutdown", "active count %d\n",_active));
    if (_active != 0) {
        DEBUGMSGTL(("trap:targetAddr:shutdown",
                    "unexpected count %d after cleanup!\n", _active));
        snmp_log(LOG_WARNING, "targetAddr count %d, not 0, after shutdown.\n",
                 _active);
    }
}

/*
 * store_snmpTargetAddrEntry handles the persistent storage proccess 
 * for this MIB table. It writes out all the non-volatile rows 
 * to permanent storage on a shutdown  
 */
static int
store_snmpTargetAddrEntry(int majorID, int minorID, void *serverarg,
                          void *clientarg)
{
    const struct targetAddrTable_struct *curr_struct;
    char            line[1024], *ep = line + sizeof(line);
    int             i;

    curr_struct = aAddrTable;
    while (curr_struct != NULL) {
        if ((curr_struct->storageType == SNMP_STORAGE_NONVOLATILE ||
             curr_struct->storageType == SNMP_STORAGE_PERMANENT) &&
            (curr_struct->rowStatus == SNMP_ROW_ACTIVE ||
             curr_struct->rowStatus == SNMP_ROW_NOTINSERVICE)) {
            char *cur;

            cur = line + snprintf(line, sizeof(line), "targetAddr ");
            cur = read_config_save_octet_string(
                 cur, (const u_char*)curr_struct->nameData,
                 curr_struct->nameLen);
            *cur++ = ' ';
            for (i = 0; i < curr_struct->tDomainLen; i++) {
                cur += snprintf(cur, ep - cur, ".%i",
                                (int) curr_struct->tDomain[i]);
            }
            *cur++ = ' ';
            cur = read_config_save_octet_string(
                cur, curr_struct->tAddress, curr_struct->tAddressLen);
            snprintf(cur, ep - cur, " %i %i \"%s\" %s %i %i",
                     curr_struct->timeout,
                     curr_struct->retryCount, curr_struct->tagListData,
                     curr_struct->paramsData, curr_struct->storageType,
                     curr_struct->rowStatus);

            /*
             * store to file
             */
            snmpd_store_config(line);
        }

        curr_struct = curr_struct->next;
    }

    return SNMPERR_SUCCESS;
}                               /*  store_snmpTargetAddrEntry  */

static int
snmpTargetAddr_addTDomain(struct targetAddrTable_struct *entry, char *cptr)
{
    size_t          len = 128;

    if (cptr == NULL) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: no tDomain in config string\n"));
        return (0);
    }

    if (!read_objid(cptr, entry->tDomain, &len)) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: tDomain unreadable in config string\n"));
        return (0);
    }

    /*
     * spec check for oid 1-128 
     */
    if (len < 1 || len > 128) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: tDomain out of range in config string\n"));
        return (0);
    }

    entry->tDomainLen = len;
    return (1);
}                               /* snmpTargetAddr_addTDomain */


static int
snmpTargetAddr_addTimeout(struct targetAddrTable_struct *entry, char *cptr)
{
    if (cptr == NULL) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetParamsEntry: no Timeout in config string\n"));
        return (0);
    } else if (!(isdigit((unsigned char)(*cptr)))) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargeParamsEntry: Timeout is not a digit in config string\n"));
        return (0);
    }
    /*
     * check Timeout >= 0 
     */
    else if ((entry->timeout = (int) strtol(cptr, (char **) NULL, 0)) < 0) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargeParamsEntry: Timeout out of range in config string\n"));
        return (0);
    }
    return (1);
}                               /* snmpTargetAddr_addTimeout  */


static int
snmpTargetAddr_addRetryCount(struct targetAddrTable_struct *entry,
                             char *cptr)
{
    if (cptr == NULL) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetParamsEntry: no Retry Count in config string\n"));
        return (0);
    } else if (!(isdigit((unsigned char)(*cptr)))) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargeParamsEntry: Retry Count is not a digit in config string\n"));
        return (0);
    }
    /*
     * spec check 0..255 
     */
    else {
        entry->retryCount = (int) strtol(cptr, (char **) NULL, 0);
        if ((entry->retryCount < 0) || (entry->retryCount > 255)) {
            DEBUGMSGTL(("snmpTargetAddrEntry",
                        "ERROR snmpTargeParamsEntry: Retry Count is out of range in config string\n"));
            return (0);
        }
    }
    return (1);
}                               /* snmpTargetAddr_addRetryCount  */


static int
snmpTargetAddr_addTagList(struct targetAddrTable_struct *entry, char *cptr)
{
    if (cptr == NULL) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: no tag list in config string\n"));
        return (0);
    } else {
        size_t len = strlen(cptr);
        /*
         * spec check for string 0-255 
         */
        if (len > 255) {
            DEBUGMSGTL(("snmpTargetAddrEntry",
                        "ERROR snmpTargetAddrEntry: tag list out of range in config string\n"));
            return (0);
        }
        SNMP_FREE(entry->tagListData);
        entry->tagListData = strdup(cptr);
        entry->tagListLen = strlen(cptr);
    }
    return (1);
}                               /* snmpTargetAddr_addTagList */


static int
snmpTargetAddr_addParams(struct targetAddrTable_struct *entry, char *cptr)
{
    size_t          len;
    if (cptr == NULL) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: no params in config string\n"));
        return (0);
    } else {
        len = strlen(cptr);
        /*
         * spec check for string 1-32 
         */
        if (len < 1 || len > 32) {
            DEBUGMSGTL(("snmpTargetAddrEntry",
                        "ERROR snmpTargetAddrEntry: params out of range in config string\n"));
            return (0);
        }
        entry->paramsData = strdup(cptr);
        entry->paramsLen = strlen(cptr);
    }
    return (1);
}                               /* snmpTargetAddr_addParams */


static int
snmpTargetAddr_addStorageType(struct targetAddrTable_struct *entry,
                              char *cptr)
{
    if (cptr == NULL) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: no storage type in config "
                    "string\n"));
        return (0);
    } else if (!(isdigit((unsigned char)(*cptr)))) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: storage type is not a digit "
                    "in config string\n"));
        return (0);
    }
    /*
     * check that storage type is a possible value 
     */
    else if (((entry->storageType = (int) strtol(cptr, (char **) NULL, 0))
              != SNMP_STORAGE_OTHER) &&
             (entry->storageType != SNMP_STORAGE_VOLATILE) &&
             (entry->storageType != SNMP_STORAGE_NONVOLATILE) &&
             (entry->storageType != SNMP_STORAGE_PERMANENT) &&
             (entry->storageType != SNMP_STORAGE_READONLY)) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: storage type not a valid "
                    "value of other(%d), volatile(%d), nonvolatile(%d), "
                    "permanent(%d), or readonly(%d) in config string.\n",
                    SNMP_STORAGE_OTHER, SNMP_STORAGE_VOLATILE,
                    SNMP_STORAGE_NONVOLATILE, SNMP_STORAGE_PERMANENT,
                    SNMP_STORAGE_READONLY));
        return (0);
    }
    return (1);
}                               /* snmpTargetAddr_addStorageType */


static int
snmpTargetAddr_addRowStatus(struct targetAddrTable_struct *entry,
                            char *cptr)
{
    if (cptr == NULL) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: no Row Status in config "
                    "string\n"));
        return (0);
    } else if (!(isdigit((unsigned char)(*cptr)))) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: Row Status is not a digit in "
                    "config string\n"));
        return (0);
    }
    /*
     * check that row status is a valid value 
     */
    else if (((entry->rowStatus = (int) strtol(cptr, (char **) NULL, 0))
              != SNMP_ROW_ACTIVE) &&
             (entry->rowStatus != SNMP_ROW_NOTINSERVICE) &&
             (entry->rowStatus != SNMP_ROW_NOTREADY)) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: Row Status is not a valid "
                    "value of active(%d), notinservice(%d), or notready(%d) "
                    "in config string.\n",
                    SNMP_ROW_ACTIVE, SNMP_ROW_NOTINSERVICE, SNMP_ROW_NOTREADY));
        return (0);
    }
    return (1);
}                               /* snmpTargetAddr_addRowStatus  */



static void
snmpd_parse_config_targetAddr(const char *token, char *char_ptr)
{
    const char     *cptr = char_ptr;
    char            buff[1024], *bptr;
    struct targetAddrTable_struct *newEntry;
    int             i;
    size_t          bufl;

    newEntry = snmpTargetAddrTable_create();

    cptr = skip_white_const(cptr);
    if (cptr == NULL) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: no name in config string\n"));
        snmpTargetAddrTable_dispose(newEntry);
        return;
    }

    bufl = 0;
    cptr = read_config_read_octet_string_const(cptr,
                                               (u_char**)&newEntry->nameData,
                                               &bufl);
    if (bufl < 1 || bufl > 32) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: name out of range in config "
                    "string\n"));
        snmpTargetAddrTable_dispose(newEntry);
        return;
    }
    newEntry->nameLen = bufl;

    cptr = copy_nword_const(cptr, buff, sizeof(buff));
    if (snmpTargetAddr_addTDomain(newEntry, buff) == 0) {
        snmpTargetAddrTable_dispose(newEntry);
        return;
    }
    cptr =
        read_config_read_octet_string_const(cptr,
                                      (u_char **) & newEntry->tAddress,
                                      &newEntry->tAddressLen);
    if (!cptr || !newEntry->tAddress || (newEntry->tAddressLen < 1) ||
        (newEntry->tAddressLen > 32)) {
        DEBUGMSGTL(("snmpTargetAddrEntry",
                    "ERROR snmpTargetAddrEntry: no/bd TAddress in config string\n"));
        snmpTargetAddrTable_dispose(newEntry);
        return;
    }
    cptr = copy_nword_const(cptr, buff, sizeof(buff));
    if (snmpTargetAddr_addTimeout(newEntry, buff) == 0) {
        snmpTargetAddrTable_dispose(newEntry);
        return;
    }
    cptr = copy_nword_const(cptr, buff, sizeof(buff));
    if (snmpTargetAddr_addRetryCount(newEntry, buff) == 0) {
        snmpTargetAddrTable_dispose(newEntry);
        return;
    }
    cptr = copy_nword_const(cptr, buff, sizeof(buff));
    if (snmpTargetAddr_addTagList(newEntry, buff) == 0) {
        snmpTargetAddrTable_dispose(newEntry);
        return;
    }
    cptr = copy_nword_const(cptr, buff, sizeof(buff));
    if (snmpTargetAddr_addParams(newEntry, buff) == 0) {
        snmpTargetAddrTable_dispose(newEntry);
        return;
    }
    cptr = copy_nword_const(cptr, buff, sizeof(buff));
    if (snmpTargetAddr_addStorageType(newEntry, buff) == 0) {
        snmpTargetAddrTable_dispose(newEntry);
        return;
    }
    cptr = copy_nword_const(cptr, buff, sizeof(buff));
    if (snmpTargetAddr_addRowStatus(newEntry, buff) == 0) {
        snmpTargetAddrTable_dispose(newEntry);
        return;
    }
    bptr = buff;
    bptr += sprintf(bptr, "snmp_parse_config_targetAddr, read: ");
    bptr = read_config_save_octet_string(bptr, (u_char*)newEntry->nameData,
                                         newEntry->nameLen);
    *bptr++ = '\n';
    for (i = 0; i < newEntry->tDomainLen; i++) {
        bptr += snprintf(bptr, buff + sizeof(buff) - bptr,
                         ".%d", (int) newEntry->tDomain[i]);
    }
    for (i = 0; i < newEntry->tAddressLen; i++)
        bptr += snprintf(bptr, buff + sizeof(buff) - bptr, " %02x",
                         ((u_char *)newEntry->tAddress)[i]);
    bptr += snprintf(bptr, buff + sizeof(buff) - bptr,
                     " %d %d %s %s %d %d\n", newEntry->timeout,
                     newEntry->retryCount, newEntry->tagListData,
                     newEntry->paramsData, newEntry->storageType,
                     newEntry->rowStatus);
    buff[ sizeof(buff) - 1 ] = 0;
    DEBUGMSGTL(("snmpTargetAddrEntry", "%s", buff));

    snmpTargetAddrTable_addToList(newEntry, &aAddrTable);
}                               /* snmpd_parse_config_target */

