/*
 *   AgentX utility routines
 */

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

#include <stdio.h>
#include <errno.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/types.h>
#if TIME_WITH_SYS_TIME
# ifdef WIN32
#  include <sys/timeb.h>
# else
#  include <sys/time.h>
# endif
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_WINSOCK_H
#include <winsock.h>
#endif

#if HAVE_DMALLOC_H
#include <dmalloc.h>
#endif

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

#include "agentx/protocol.h"
#include "agentx/client.h"
#include "agentx/subagent.h"

extern struct timeval starttime;

        /*
         * AgentX handling utility routines
         *
         * Mostly wrappers round, or re-writes of
         *   the SNMP equivalents
         */

int
agentx_synch_input(int op,
                   netsnmp_session * session,
                   int reqid, netsnmp_pdu *pdu, void *magic)
{
    struct synch_state *state = (struct synch_state *) magic;
    struct timeval  now, diff;

    if (!state || reqid != state->reqid) {
        return handle_agentx_packet(op, session, reqid, pdu, magic);
    }

    DEBUGMSGTL(("agentx/subagent", "synching input, op 0x%02x\n", op));
    state->waiting = 0;
    if (op == NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE) {
        if (pdu->command == AGENTX_MSG_RESPONSE) {
            state->pdu = snmp_clone_pdu(pdu);
            state->status = STAT_SUCCESS;
            session->s_snmp_errno = SNMPERR_SUCCESS;

            /*
             * Synchronise sysUpTime with the master agent
             */
            gettimeofday(&now, NULL);
            now.tv_sec--;
            now.tv_usec += 1000000L;
            diff.tv_sec = pdu->time / 100;
            diff.tv_usec = (pdu->time - (diff.tv_sec * 100)) * 10000;
            starttime.tv_sec = now.tv_sec - diff.tv_sec;
            starttime.tv_usec = now.tv_usec - diff.tv_usec;
            if (starttime.tv_usec > 1000000L) {
                starttime.tv_usec -= 1000000L;
                starttime.tv_sec++;
            }
        }
    } else if (op == NETSNMP_CALLBACK_OP_TIMED_OUT) {
        state->pdu = NULL;
        state->status = STAT_TIMEOUT;
        session->s_snmp_errno = SNMPERR_TIMEOUT;
    } else if (op == NETSNMP_CALLBACK_OP_DISCONNECT) {
        return handle_agentx_packet(op, session, reqid, pdu, magic);
    }

    return 1;
}



int
agentx_synch_response(netsnmp_session * ss, netsnmp_pdu *pdu,
                      netsnmp_pdu **response)
{
    return snmp_synch_response_cb(ss, pdu, response, agentx_synch_input);
}


        /*
         * AgentX PofE convenience functions
         */

int
agentx_open_session(netsnmp_session * ss)
{
    netsnmp_pdu    *pdu, *response;
    extern oid      version_sysoid[];
    extern int      version_sysoid_len;

    DEBUGMSGTL(("agentx/subagent", "opening session \n"));
    if (ss == NULL || !IS_AGENTX_VERSION(ss->version)) {
        return 0;
    }

    pdu = snmp_pdu_create(AGENTX_MSG_OPEN);
    if (pdu == NULL)
        return 0;
    pdu->time = 0;
    snmp_add_var(pdu, version_sysoid, version_sysoid_len,
		 's', "Net-SNMP AgentX sub-agent");

    if (agentx_synch_response(ss, pdu, &response) != STAT_SUCCESS)
        return 0;

    if (!response)
        return 0;

    if (response->errstat != SNMP_ERR_NOERROR) {
        snmp_free_pdu(response);
        return 0;
    }

    ss->sessid = response->sessid;
    snmp_free_pdu(response);

    DEBUGMSGTL(("agentx/subagent", "open \n"));
    return 1;
}

int
agentx_close_session(netsnmp_session * ss, int why)
{
    netsnmp_pdu    *pdu, *response;
    DEBUGMSGTL(("agentx/subagent", "closing session\n"));

    if (ss == NULL || !IS_AGENTX_VERSION(ss->version)) {
        return 0;
    }

    pdu = snmp_pdu_create(AGENTX_MSG_CLOSE);
    if (pdu == NULL)
        return 0;
    pdu->time = 0;
    pdu->errstat = why;
    pdu->sessid = ss->sessid;

    if (agentx_synch_response(ss, pdu, &response) == STAT_SUCCESS)
        snmp_free_pdu(response);
    DEBUGMSGTL(("agentx/subagent", "closed\n"));

    return 1;
}

int
agentx_register(netsnmp_session * ss, oid start[], size_t startlen,
                int priority, int range_subid, oid range_ubound,
                int timeout, u_char flags, const char *contextName)
{
    netsnmp_pdu    *pdu, *response;

    DEBUGMSGTL(("agentx/subagent", "registering: "));
    DEBUGMSGOIDRANGE(("agentx/subagent", start, startlen, range_subid,
                      range_ubound));
    DEBUGMSG(("agentx/subagent", "\n"));

    if (ss == NULL || !IS_AGENTX_VERSION(ss->version)) {
        return 0;
    }

    pdu = snmp_pdu_create(AGENTX_MSG_REGISTER);
    if (pdu == NULL) {
        return 0;
    }
    pdu->time = timeout;
    pdu->priority = priority;
    pdu->sessid = ss->sessid;
    pdu->range_subid = range_subid;
    if (contextName) {
        pdu->flags |= AGENTX_MSG_FLAG_NON_DEFAULT_CONTEXT;
        pdu->community = strdup(contextName);
        pdu->community_len = strlen(contextName);
    }

    if (flags & FULLY_QUALIFIED_INSTANCE) {
        pdu->flags |= AGENTX_MSG_FLAG_INSTANCE_REGISTER;
    }

    if (range_subid) {
        snmp_pdu_add_variable(pdu, start, startlen, ASN_OBJECT_ID,
                              (u_char *) start, startlen * sizeof(oid));
        pdu->variables->val.objid[range_subid - 1] = range_ubound;
    } else {
        snmp_add_null_var(pdu, start, startlen);
    }

    if (agentx_synch_response(ss, pdu, &response) != STAT_SUCCESS) {
        DEBUGMSGTL(("agentx/subagent", "registering failed!\n"));
        return 0;
    }

    if (response->errstat != SNMP_ERR_NOERROR) {
        snmp_log(LOG_ERR,"registering pdu failed: %ld!\n", response->errstat);
        snmp_free_pdu(response);
        return 0;
    }

    snmp_free_pdu(response);
    DEBUGMSGTL(("agentx/subagent", "registered\n"));
    return 1;
}

int
agentx_unregister(netsnmp_session * ss, oid start[], size_t startlen,
                  int priority, int range_subid, oid range_ubound,
                  const char *contextName)
{
    netsnmp_pdu    *pdu, *response;

    if (ss == NULL || !IS_AGENTX_VERSION(ss->version)) {
        return 0;
    }

    DEBUGMSGTL(("agentx/subagent", "unregistering: "));
    DEBUGMSGOIDRANGE(("agentx/subagent", start, startlen, range_subid,
                      range_ubound));
    DEBUGMSG(("agentx/subagent", "\n"));
    pdu = snmp_pdu_create(AGENTX_MSG_UNREGISTER);
    if (pdu == NULL) {
        return 0;
    }
    pdu->time = 0;
    pdu->priority = priority;
    pdu->sessid = ss->sessid;
    pdu->range_subid = range_subid;
    if (contextName) {
        pdu->flags |= AGENTX_MSG_FLAG_NON_DEFAULT_CONTEXT;
        pdu->community = strdup(contextName);
        pdu->community_len = strlen(contextName);
    }

    if (range_subid) {
        snmp_pdu_add_variable(pdu, start, startlen, ASN_OBJECT_ID,
                              (u_char *) start, startlen * sizeof(oid));
        pdu->variables->val.objid[range_subid - 1] = range_ubound;
    } else {
        snmp_add_null_var(pdu, start, startlen);
    }

    if (agentx_synch_response(ss, pdu, &response) != STAT_SUCCESS)
        return 0;

    if (response->errstat != SNMP_ERR_NOERROR) {
        snmp_free_pdu(response);
        return 0;
    }

    snmp_free_pdu(response);
    DEBUGMSGTL(("agentx/subagent", "unregistered\n"));
    return 1;
}

netsnmp_variable_list *
agentx_register_index(netsnmp_session * ss,
                      netsnmp_variable_list * varbind, int flags)
{
    netsnmp_pdu    *pdu, *response;
    netsnmp_variable_list *varbind2;

    if (ss == NULL || !IS_AGENTX_VERSION(ss->version)) {
        return NULL;
    }

    /*
     * Make a copy of the index request varbind
     *    for the AgentX request PDU
     *    (since the pdu structure will be freed)
     */
    varbind2 =
        (netsnmp_variable_list *) malloc(sizeof(netsnmp_variable_list));
    if (varbind2 == NULL)
        return NULL;
    if (snmp_clone_var(varbind, varbind2)) {
        snmp_free_varbind(varbind2);
        return NULL;
    }
    if (varbind2->val.string == NULL)
        varbind2->val.string = varbind2->buf;   /* ensure it points somewhere */

    pdu = snmp_pdu_create(AGENTX_MSG_INDEX_ALLOCATE);
    if (pdu == NULL) {
        snmp_free_varbind(varbind2);
        return NULL;
    }
    pdu->time = 0;
    pdu->sessid = ss->sessid;
    if (flags == ALLOCATE_ANY_INDEX)
        pdu->flags |= AGENTX_MSG_FLAG_ANY_INSTANCE;
    if (flags == ALLOCATE_NEW_INDEX)
        pdu->flags |= AGENTX_MSG_FLAG_NEW_INSTANCE;

    /*
     *  Just send a single index request varbind.
     *  Although the AgentX protocol supports
     *    multiple index allocations in a single
     *    request, the model used in the net-snmp agent
     *    doesn't currently take advantage of this.
     *  I believe this is our prerogative - just as
     *    long as the master side Index request handler
     *    can cope with multiple index requests.
     */
    pdu->variables = varbind2;

    if (agentx_synch_response(ss, pdu, &response) != STAT_SUCCESS)
        return NULL;

    if (response->errstat != SNMP_ERR_NOERROR) {
        snmp_free_pdu(response);
        return NULL;
    }

    /*
     * Unlink the (single) response varbind to return
     *  to the main driving index request routine.
     *
     * This is a memory leak, as nothing will ever
     *  release this varbind.  If this becomes a problem,
     *  we'll need to keep a list of these here, and
     *  free the memory in the "index release" routine.
     * But the master side never frees these either (by
     *  design, since it still needs them), so expecting
     *  the subagent to is discrimination, pure & simple :-)
     */
    varbind2 = response->variables;
    response->variables = NULL;
    snmp_free_pdu(response);
    return varbind2;
}

int
agentx_unregister_index(netsnmp_session * ss,
                        netsnmp_variable_list * varbind)
{
    netsnmp_pdu    *pdu, *response;
    netsnmp_variable_list *varbind2;

    if (ss == NULL || !IS_AGENTX_VERSION(ss->version)) {
        return -1;
    }

    /*
     * Make a copy of the index request varbind
     *    for the AgentX request PDU
     *    (since the pdu structure will be freed)
     */
    varbind2 =
        (netsnmp_variable_list *) malloc(sizeof(netsnmp_variable_list));
    if (varbind2 == NULL)
        return -1;
    if (snmp_clone_var(varbind, varbind2)) {
        snmp_free_varbind(varbind2);
        return -1;
    }

    pdu = snmp_pdu_create(AGENTX_MSG_INDEX_DEALLOCATE);
    if (pdu == NULL) {
        snmp_free_varbind(varbind2);
        return -1;
    }
    pdu->time = 0;
    pdu->sessid = ss->sessid;

    /*
     *  Just send a single index release varbind.
     *      (as above)
     */
    pdu->variables = varbind2;

    if (agentx_synch_response(ss, pdu, &response) != STAT_SUCCESS)
        return -1;

    if (response->errstat != SNMP_ERR_NOERROR) {
        snmp_free_pdu(response);
        return -1;              /* XXX - say why */
    }

    snmp_free_pdu(response);
    return SNMP_ERR_NOERROR;
}

int
agentx_add_agentcaps(netsnmp_session * ss,
                     oid * agent_cap, size_t agent_caplen,
                     const char *descr)
{
    netsnmp_pdu    *pdu, *response;

    if (ss == NULL || !IS_AGENTX_VERSION(ss->version)) {
        return 0;
    }

    pdu = snmp_pdu_create(AGENTX_MSG_ADD_AGENT_CAPS);
    if (pdu == NULL)
        return 0;
    pdu->time = 0;
    pdu->sessid = ss->sessid;
    snmp_add_var(pdu, agent_cap, agent_caplen, 's', descr);

    if (agentx_synch_response(ss, pdu, &response) != STAT_SUCCESS)
        return 0;

    if (response->errstat != SNMP_ERR_NOERROR) {
        snmp_free_pdu(response);
        return 0;
    }

    snmp_free_pdu(response);
    return 1;
}

int
agentx_remove_agentcaps(netsnmp_session * ss,
                        oid * agent_cap, size_t agent_caplen)
{
    netsnmp_pdu    *pdu, *response;

    if (ss == NULL || !IS_AGENTX_VERSION(ss->version)) {
        return 0;
    }

    pdu = snmp_pdu_create(AGENTX_MSG_REMOVE_AGENT_CAPS);
    if (pdu == NULL)
        return 0;
    pdu->time = 0;
    pdu->sessid = ss->sessid;
    snmp_add_null_var(pdu, agent_cap, agent_caplen);

    if (agentx_synch_response(ss, pdu, &response) != STAT_SUCCESS)
        return 0;

    if (response->errstat != SNMP_ERR_NOERROR) {
        snmp_free_pdu(response);
        return 0;
    }

    snmp_free_pdu(response);
    return 1;
}

int
agentx_send_ping(netsnmp_session * ss)
{
    netsnmp_pdu    *pdu, *response;

    if (ss == NULL || !IS_AGENTX_VERSION(ss->version)) {
        return 0;
    }

    pdu = snmp_pdu_create(AGENTX_MSG_PING);
    if (pdu == NULL)
        return 0;
    pdu->time = 0;
    pdu->sessid = ss->sessid;

    if (agentx_synch_response(ss, pdu, &response) != STAT_SUCCESS)
        return 0;

    if (response->errstat != SNMP_ERR_NOERROR) {
        snmp_free_pdu(response);
        return 0;
    }

    snmp_free_pdu(response);
    return 1;
}
