#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-features.h>

#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <stdio.h>
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>
#include <sys/types.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#ifdef WIN32
#include <limits.h>
#endif

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

#include "struct.h"
#include "pass.h"
#include "pass_common.h"
#include "extensible.h"
#include "util_funcs.h"

netsnmp_feature_require(get_exten_instance);
netsnmp_feature_require(parse_miboid);

struct extensible *passthrus = NULL;
int             numpassthrus = 0;

/*
 * the relocatable extensible commands variables 
 */
struct variable2 extensible_passthru_variables[] = {
    /*
     * bogus entry.  Only some of it is actually used. 
     */
    {MIBINDEX, ASN_INTEGER, NETSNMP_OLDAPI_RWRITE,
     var_extensible_pass, 0, {MIBINDEX}},
};



void
init_pass(void)
{
    snmpd_register_config_handler("pass", pass_parse_config,
                                  pass_free_config, "miboid command");
}

void
pass_parse_config(const char *token, char *cptr)
{
    struct extensible **ppass = &passthrus, **etmp, *ptmp;
    char           *tcptr, *endopt;
    int             i;
    unsigned long   priority;

    /*
     * options
     */
    priority = DEFAULT_MIB_PRIORITY;
    while (*cptr == '-') {
      cptr++;
      switch (*cptr) {
      case 'p':
	/* change priority level */
	cptr++;
	cptr = skip_white(cptr);
	if (! isdigit((unsigned char)(*cptr))) {
	  config_perror("priority must be an integer");
	  return;
	}
	priority = strtol((const char*) cptr, &endopt, 0);
	if ((priority == LONG_MIN) || (priority == LONG_MAX)) {
	  config_perror("priority under/overflow");
	  return;
	}
	cptr = endopt;
	cptr = skip_white(cptr);
	break;
      default:
	config_perror("unknown option for pass directive");
	return;
      }
    }

    /*
     * MIB
     */
    if (*cptr == '.')
        cptr++;
    if (!isdigit((unsigned char)(*cptr))) {
        config_perror("second token is not a OID");
        return;
    }
    numpassthrus++;

    while (*ppass != NULL)
        ppass = &((*ppass)->next);
    *ppass = calloc(1, sizeof(**ppass));
    if (*ppass == NULL)
        return;
    (*ppass)->type = PASSTHRU;
    (*ppass)->mibpriority = priority;

    (*ppass)->miblen = parse_miboid(cptr, (*ppass)->miboid);
    while (isdigit((unsigned char)(*cptr)) || *cptr == '.')
        cptr++;
    /*
     * path
     */
    free((*ppass)->command);
    (*ppass)->command = NULL;
    cptr = skip_white(cptr);
    if (cptr == NULL) {
        config_perror("No command specified on pass line");
        if (asprintf(&(*ppass)->command, "%s", "") < 0) {
        }
    } else {
        for (tcptr = cptr; *tcptr != 0 && *tcptr != '#' && *tcptr != ';';
             tcptr++);
        if (asprintf(&(*ppass)->command, "%.*s", (int) (tcptr - cptr), cptr)
            < 0) {
        }
    }
    strlcpy((*ppass)->name, (*ppass)->command, sizeof((*ppass)->name));
    (*ppass)->next = NULL;

    register_mib_priority("pass",
                 (struct variable *) extensible_passthru_variables,
                 sizeof(struct variable2), 1, (*ppass)->miboid,
                 (*ppass)->miblen, (*ppass)->mibpriority);

    /*
     * argggg -- passthrus must be sorted 
     */
    if (numpassthrus > 1) {
        etmp = (struct extensible **)
            malloc(((sizeof(struct extensible *)) * numpassthrus));
        if (etmp == NULL)
            return;

        for (i = 0, ptmp = (struct extensible *) passthrus;
             i < numpassthrus && ptmp != NULL; i++, ptmp = ptmp->next)
            etmp[i] = ptmp;
        qsort(etmp, numpassthrus, sizeof(struct extensible *),
              pass_compare);
        passthrus = (struct extensible *) etmp[0];
        ptmp = (struct extensible *) etmp[0];

        for (i = 0; i < numpassthrus - 1; i++) {
            ptmp->next = etmp[i + 1];
            ptmp = ptmp->next;
        }
        ptmp->next = NULL;
        free(etmp);
    }
}

void
pass_free_config(void)
{
    struct extensible *etmp, *etmp2;

    for (etmp = passthrus; etmp != NULL;) {
        etmp2 = etmp;
        etmp = etmp->next;
        unregister_mib_priority(etmp2->miboid, etmp2->miblen, etmp2->mibpriority);
        free(etmp2->command);
        free(etmp2);
    }
    passthrus = NULL;
    numpassthrus = 0;
}

u_char         *
var_extensible_pass(struct variable *vp,
                    oid * name,
                    size_t * length,
                    int exact,
                    size_t * var_len, WriteMethod ** write_method)
{
    oid             newname[MAX_OID_LEN];
    int             i, rtest, fd, newlen;
    char            buf[SNMP_MAXBUF];
    static char     buf2[SNMP_MAXBUF];
    struct extensible *passthru;
    FILE           *file;

    for (i = 1; i <= numpassthrus; i++) {
        passthru = get_exten_instance(passthrus, i);
        rtest = snmp_oidtree_compare(name, *length,
                                     passthru->miboid, passthru->miblen);
        if ((exact && rtest == 0) || (!exact && rtest <= 0)) {
            /*
             * setup args 
             */
            if (passthru->miblen >= *length || rtest < 0)
                sprint_mib_oid(buf, passthru->miboid, passthru->miblen);
            else
                sprint_mib_oid(buf, name, *length);
            free(passthru->command);
            passthru->command = NULL;
            if (asprintf(&passthru->command, "%s %s %s", passthru->name,
                         exact ? "-g" : "-n", buf) < 0) {
            }
            DEBUGMSGTL(("ucd-snmp/pass", "pass-running:  %s\n",
                        passthru->command));
            /*
             * valid call.  Exec and get output 
             */
            if ((fd = get_exec_output(passthru)) != -1) {
                file = fdopen(fd, "r");
                if (fgets(buf, sizeof(buf), file) == NULL) {
                    fclose(file);
                    wait_on_exec(passthru);
                    if (exact) {
                        /*
                         * to enable creation
                         */
                        *write_method = setPass;
                        *var_len = 0;
                        return (NULL);
                    }
                    continue;
                }
                newlen = parse_miboid(buf, newname);

                /*
                 * its good, so copy onto name/length 
                 */
                memcpy((char *) name, (char *) newname,
                       (int) newlen * sizeof(oid));
                *length = newlen;

                /*
                 * set up return pointer for setable stuff 
                 */
                *write_method = setPass;

                if (newlen == 0 || fgets(buf, sizeof(buf), file) == NULL
                    || fgets(buf2, sizeof(buf2), file) == NULL) {
                    *var_len = 0;
                    fclose(file);
                    wait_on_exec(passthru);
                    return (NULL);
                }
                fclose(file);
                wait_on_exec(passthru);

                return netsnmp_internal_pass_parse(buf, buf2, var_len, vp);
            }
            *var_len = 0;
            return (NULL);
        }
    }
    if (var_len)
        *var_len = 0;
    *write_method = NULL;
    return (NULL);
}

int
setPass(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)
{
    int             i, rtest;
    struct extensible *passthru;
    char            buf[SNMP_MAXBUF], buf2[SNMP_MAXBUF];

    for (i = 1; i <= numpassthrus; i++) {
        passthru = get_exten_instance(passthrus, i);
        rtest = snmp_oidtree_compare(name, name_len,
                                     passthru->miboid, passthru->miblen);
        if (rtest <= 0) {
            if (action != ACTION)
                return SNMP_ERR_NOERROR;
            /*
             * setup args 
             */
            free(passthru->command);
            passthru->command = NULL;
            if (passthru->miblen >= name_len || rtest < 0)
                sprint_mib_oid(buf, passthru->miboid, passthru->miblen);
            else
                sprint_mib_oid(buf, name, name_len);
            netsnmp_internal_pass_set_format(buf2, var_val, var_val_type,
                                             var_val_len);
            if (asprintf(&passthru->command, "%s -s %s %s", passthru->name, buf,
                         buf2) < 0) {
            }
            DEBUGMSGTL(("ucd-snmp/pass", "pass-running:  %s",
                        passthru->command));
            exec_command(passthru);
            DEBUGMSGTL(("ucd-snmp/pass", "pass-running returned: %s",
                        passthru->output));
            return netsnmp_internal_pass_str_to_errno(passthru->output);
        }
    }
    if (snmp_get_do_debugging()) {
        sprint_mib_oid(buf2, name, name_len);
        DEBUGMSGTL(("ucd-snmp/pass", "pass-notfound:  %s\n", buf2));
    }
    return SNMP_ERR_NOSUCHNAME;
}

int
pass_compare(const void *a, const void *b)
{
    const struct extensible *const *ap = a;
    const struct extensible *const *bp = b;

    return snmp_oid_compare((*ap)->miboid, (*ap)->miblen, (*bp)->miboid,
                            (*bp)->miblen);
}
