/*
 * Smux module authored by Rohit Dube.
 * Rewritten by Nick Amato <naamato@merit.net>.
 */

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

#if HAVE_IO_H                   /* win32 */
#include <io.h>
#endif
#include <stdio.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
#if HAVE_ERR_H
#include <err.h>
#endif
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#include <errno.h>
#if HAVE_NETDB_H
#include <netdb.h>
#endif

#include <sys/stat.h>
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#if HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif

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

#if HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#if HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif

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

#include "smux.h"
#include "snmpd.h"

netsnmp_feature_require(snprint_objid);

long            smux_long;
u_long          smux_ulong;
struct sockaddr_in smux_sa;
struct counter64 smux_counter64;
oid             smux_objid[MAX_OID_LEN];
u_char          smux_str[SMUXMAXSTRLEN];
int             smux_listen_sd = -1;

static struct timeval smux_rcv_timeout;
static long   smux_reqid;

void            init_smux(void);
static u_char  *smux_open_process(int, u_char *, size_t *, int *);
static u_char  *smux_rreq_process(int, u_char *, size_t *);
static u_char  *smux_close_process(int, u_char *, size_t *);
static u_char  *smux_trap_process(u_char *, size_t *);
static u_char  *smux_parse(u_char *, oid *, size_t *, size_t *, u_char *);
static u_char  *smux_parse_var(u_char *, size_t *, oid *, size_t *,
                               size_t *, u_char *);
static void     smux_send_close(int, int);
static void     smux_list_detach(smux_reg **, smux_reg *);
static void     smux_replace_active(smux_reg *, smux_reg *);
static void     smux_peer_cleanup(int);
static int      smux_auth_peer(oid *, size_t, char *, int);
static int      smux_build(u_char, long, oid *,
                           size_t *, u_char, u_char *, size_t, u_char *,
                           size_t *);
static int      smux_list_add(smux_reg **, smux_reg *);
static int      smux_pdu_process(int, u_char *, size_t);
static int      smux_send_rrsp(int, int);
static smux_reg *smux_find_match(smux_reg *, int, oid *, size_t, long);
static smux_reg *smux_find_replacement(oid *, size_t);
u_char         *var_smux_get(oid *, size_t, oid *, size_t *, int, size_t *,
                               u_char *);
int             var_smux_write(int, u_char *, u_char, size_t, oid *, size_t);

static smux_reg *ActiveRegs;    /* Active registrations                 */
static smux_reg *PassiveRegs;   /* Currently unused registrations       */

static smux_peer_auth *Auths[SMUX_MAX_PEERS];   /* Configured peers */
static int      nauths, npeers = 0;



void
smux_parse_smux_socket(const char *token, char *cptr)
{
    DEBUGMSGTL(("smux", "port spec: %s\n", cptr));
    netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_SMUX_SOCKET, cptr);
}

void
smux_parse_peer_auth(const char *token, char *cptr)
{
    smux_peer_auth *aptr;
    char           *password_cptr;
    int             rv;

    if ((aptr =
         (smux_peer_auth *) calloc(1, sizeof(smux_peer_auth))) == NULL) {
        snmp_log_perror("smux_parse_peer_auth: malloc");
        return;
    }
    if (nauths == SMUX_MAX_PEERS) {
	config_perror("Too many smuxpeers");
	free(aptr);
	return;
    }

    password_cptr = strchr(cptr, ' ');
    if (password_cptr)
        *(password_cptr++) = '\0';

    /*
     * oid 
     */
    aptr->sa_active_fd = -1;
    aptr->sa_oid_len = MAX_OID_LEN;
    rv = read_objid( cptr, aptr->sa_oid, &aptr->sa_oid_len );
    DEBUGMSGTL(("smux_conf", "parsing registration for: %s\n", cptr));
    if (!rv)
        config_perror("Error parsing smux oid");

    if (password_cptr != NULL) {    /* Do we have a password or not? */
	    DEBUGMSGTL(("smux_conf", "password is: %s\n",
	                SNMP_STRORNULL(password_cptr)));

        /*
         * password 
         */
        if (*password_cptr)
            strlcpy(aptr->sa_passwd, password_cptr, sizeof(aptr->sa_passwd));
    } else {
        /*
         * null passwords OK 
         */
        DEBUGMSGTL(("smux_conf", "null password\n"));
    }

    Auths[nauths++] = aptr;
    return;
}

void
smux_free_peer_auth(void)
{
    int             i;

    for (i = 0; i < nauths; i++) {
        free(Auths[i]);
        Auths[i] = NULL;
    }
    nauths = 0;
}

void
init_smux(void)
{
    snmpd_register_config_handler("smuxpeer", smux_parse_peer_auth,
                                  smux_free_peer_auth,
                                  "OID-IDENTITY PASSWORD");
    snmpd_register_config_handler("smuxsocket",
                                  smux_parse_smux_socket, NULL,
                                  "SMUX bind address");
}

void
real_init_smux(void)
{
    struct sockaddr_in lo_socket;
    char           *smux_socket;
    int             one = 1;

    if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE) == SUB_AGENT) {
        smux_listen_sd = -1;
        return;
    }

    /*
     * Reqid 
     */
    smux_reqid = 0;
    smux_listen_sd = -1;

    /*
     * Receive timeout 
     */
    smux_rcv_timeout.tv_sec = 0;
    smux_rcv_timeout.tv_usec = 500000;

    /*
     * Get ready to listen on the SMUX port
     */
    memset(&lo_socket, (0), sizeof(lo_socket));
    lo_socket.sin_family = AF_INET;

    smux_socket = netsnmp_ds_get_string(NETSNMP_DS_APPLICATION_ID, 
					NETSNMP_DS_SMUX_SOCKET);
#ifdef NETSNMP_ENABLE_LOCAL_SMUX
    if (!smux_socket)
        smux_socket = "127.0.0.1";   /* By default, listen on localhost only */
#endif
    netsnmp_sockaddr_in( &lo_socket, smux_socket, SMUXPORT );

    if ((smux_listen_sd = (int) socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        snmp_log_perror("[init_smux] socket failed");
        return;
    }
#ifdef SO_REUSEADDR
    /*
     * At least on Linux, when the master agent terminates, any
     * TCP connections for SMUX peers are put in the TIME_WAIT
     * state for about 60 seconds. If the master agent is started
     * during this time, the bind for the listening socket will
     * fail because the SMUX port is in use.
     */
    if (setsockopt(smux_listen_sd, SOL_SOCKET, SO_REUSEADDR, (char *) &one,
                   sizeof(one)) < 0) {
        snmp_log_perror("[init_smux] setsockopt(SO_REUSEADDR) failed");
    }
#endif                          /* SO_REUSEADDR */

    if (bind(smux_listen_sd, (struct sockaddr *) &lo_socket,
             sizeof(lo_socket)) < 0) {
        snmp_log_perror("[init_smux] bind failed");
        close(smux_listen_sd);
        smux_listen_sd = -1;
        return;
    }
#ifdef	SO_KEEPALIVE
    if (setsockopt(smux_listen_sd, SOL_SOCKET, SO_KEEPALIVE, (char *) &one,
                   sizeof(one)) < 0) {
        snmp_log_perror("[init_smux] setsockopt(SO_KEEPALIVE) failed");
        close(smux_listen_sd);
        smux_listen_sd = -1;
        return;
    }
#endif                          /* SO_KEEPALIVE */

    if (listen(smux_listen_sd, SOMAXCONN) == -1) {
        snmp_log_perror("[init_smux] listen failed");
        close(smux_listen_sd);
        smux_listen_sd = -1;
        return;
    }

    DEBUGMSGTL(("smux_init",
                "[smux_init] done; smux listen sd is %d, smux port is %d\n",
                smux_listen_sd, ntohs(lo_socket.sin_port)));
}

static int
smux_handler(netsnmp_mib_handler *handler,
                netsnmp_handler_registration *reginfo,
                netsnmp_agent_request_info *reqinfo,
                netsnmp_request_info *requests)
{
    u_char *access = NULL;
    size_t var_len;
    int exact = 1;
    int status = 0;
    u_char var_type;
    static long old_reqid = -1;
    static long old_sessid = -1;
    long new_reqid, new_sessid;

    /* Increment the reqid of outgoing SMUX messages only when processing
     * new incoming SNMP message, i.e. when reqid or session id chamges */
    new_reqid = reqinfo->asp->pdu->reqid;
    new_sessid = reqinfo->asp->session->sessid;
    DEBUGMSGTL(("smux", "smux_handler: incoming reqid=%ld, sessid=%ld\n",
            new_reqid, new_sessid));
    if (old_reqid != new_reqid || old_sessid != new_sessid) {
        smux_reqid++;
        old_reqid = new_reqid;
	old_sessid = new_sessid;
    }

    switch (reqinfo->mode) {
    case MODE_GETNEXT:
    case MODE_GETBULK:
        exact = 0;
    }

    for (; requests; requests = requests->next) {
        switch(reqinfo->mode) {
        case MODE_GET:
        case MODE_GETNEXT:
        case MODE_SET_RESERVE1:
            access = var_smux_get(reginfo->rootoid,
                    reginfo->rootoid_len,
                    requests->requestvb->name,
                    &requests->requestvb->name_length,
                    exact,
                    &var_len,
                    &var_type);
            if (access)
                if (reqinfo->mode != MODE_SET_RESERVE1)
                    snmp_set_var_typed_value(requests->requestvb,
                            var_type, access, var_len);
            if (reqinfo->mode != MODE_SET_RESERVE1)
                break;
            /* fall through if MODE_SET_RESERVE1 */
	    /* FALL THROUGH */

        default:
            /* SET processing */
            status = var_smux_write(reqinfo->mode,
                    requests->requestvb->val.string,
                    requests->requestvb->type,
                    requests->requestvb->val_len,
                    requests->requestvb->name,
                    requests->requestvb->name_length);
            if (status != SNMP_ERR_NOERROR) {
                netsnmp_set_request_error(reqinfo, requests, status);
            }
        }
    }
    return SNMP_ERR_NOERROR;
}

u_char         *
var_smux_get(oid *root, size_t root_len,
         oid * name, size_t * length,
         int exact, size_t * var_len, u_char *var_type)
{
    u_char         *valptr;
    smux_reg       *rptr;

    /*
     * search the active registration list 
     */
    for (rptr = ActiveRegs; rptr; rptr = rptr->sr_next) {
        if (0 >= snmp_oidtree_compare(root, root_len, rptr->sr_name,
                                      rptr->sr_name_len))
            break;
    }
    if (rptr == NULL)
        return NULL;
    else if (exact && (*length < rptr->sr_name_len))
        return NULL;

    valptr = smux_snmp_process(exact, name, length,
                               var_len, var_type, rptr->sr_fd);

    if (valptr == NULL)
        return NULL;

    if ((snmp_oidtree_compare(name, *length, rptr->sr_name,
                              rptr->sr_name_len)) != 0) {
        /*
         * the peer has returned a value outside
         * * of the registered tree
         */
        return NULL;
    } else {
        return valptr;
    }
}

int
var_smux_write(int action,
               u_char * var_val,
               u_char var_val_type,
               size_t var_val_len,
               oid * name, size_t name_len)
{
    smux_reg       *rptr;
    u_char          buf[SMUXMAXPKTSIZE], *ptr, sout[3], type;
    int             reterr;
    size_t          var_len, datalen, name_length, packet_len;
    size_t          len;
    ssize_t         tmp_len;
    long            reqid, errsts, erridx;
    u_char          *dataptr;

    DEBUGMSGTL(("smux", "[var_smux_write] entering var_smux_write\n"));

    len = SMUXMAXPKTSIZE;
    reterr = SNMP_ERR_NOERROR;
    var_len = var_val_len;
    name_length = name_len;

    /*
     * XXX find the descriptor again 
     */
    for (rptr = ActiveRegs; rptr; rptr = rptr->sr_next) {
        if (!snmp_oidtree_compare(name, name_len, rptr->sr_name,
                                  rptr->sr_name_len))
            break;
    }

    if (!rptr) {
        DEBUGMSGTL(("smux", "[var_smux_write] unknown registration\n"));
        return SNMP_ERR_GENERR;
    }

    switch (action) {
    case RESERVE1:
        DEBUGMSGTL(("smux", "[var_smux_write] entering RESERVE1\n"));

        /*
         * length might be long 
         */
        var_len += (*(var_val + 1) & ASN_LONG_LEN) ?
            var_len + ((*(var_val + 1) & 0x7F) + 2) : 2;

        switch (var_val_type) {
        case ASN_INTEGER:
        case ASN_OCTET_STR:
        case ASN_COUNTER:
        case ASN_GAUGE:
        case ASN_TIMETICKS:
        case ASN_UINTEGER:
        case ASN_COUNTER64:
        case ASN_IPADDRESS:
        case ASN_OPAQUE:
        case ASN_NSAP:
        case ASN_OBJECT_ID:
        case ASN_BIT_STR:
            datalen = var_val_len;
            dataptr = var_val;
            break;
        case SNMP_NOSUCHOBJECT:
        case SNMP_NOSUCHINSTANCE:
        case SNMP_ENDOFMIBVIEW:
        case ASN_NULL:
        default:
            DEBUGMSGTL(("smux",
                        "[var_smux_write] variable not supported\n"));
            return SNMP_ERR_GENERR;
            break;
        }

        if ((smux_build((u_char) SMUX_SET, smux_reqid,
                        name, &name_length, var_val_type, dataptr,
                        datalen, buf, &len)) < 0) {
            DEBUGMSGTL(("smux", "[var_smux_write] smux build failed\n"));
            return SNMP_ERR_GENERR;
        }

        if (sendto(rptr->sr_fd, (void *) buf, len, 0, NULL, 0) < 0) {
            DEBUGMSGTL(("smux", "[var_smux_write] send failed\n"));
            return SNMP_ERR_GENERR;
        }

        while (1) {
            /*
             * peek at what's received 
             */
            if ((len = recvfrom(rptr->sr_fd, (void *) buf,
                            SMUXMAXPKTSIZE, MSG_PEEK, NULL, NULL)) <= 0) {
                if ((len == -1) && ((errno == EINTR) || (errno == EAGAIN)))
                {
                   continue;
                }
                DEBUGMSGTL(("smux",
                            "[var_smux_write] peek failed or timed out\n"));
                /*
                 * do we need to do a peer cleanup in this case?? 
                 */
                smux_peer_cleanup(rptr->sr_fd);
                smux_snmp_select_list_del(rptr->sr_fd);
                return SNMP_ERR_GENERR;
            }

            DEBUGMSGTL(("smux", "[var_smux_write] Peeked at %" NETSNMP_PRIz
                        "d bytes\n", len));
            DEBUGDUMPSETUP("var_smux_write", buf, len);

            /*
             * determine if we received more than one packet 
             */
            packet_len = len;
            ptr = asn_parse_header(buf, &packet_len, &type);
            if (ptr == NULL)
                return SNMP_ERR_GENERR;
            packet_len += (ptr - buf);
            if (len > (ssize_t)packet_len) {
                /*
                 * set length to receive only the first packet 
                 */
                len = packet_len;
            }

            /*
             * receive the first packet 
             */
            tmp_len = len;
            do
            {
               len = tmp_len;
               len = recvfrom(rptr->sr_fd, (void *) buf, len, 0, NULL, NULL);
            }
            while((len == -1) && ((errno == EINTR) || (errno == EAGAIN)));

            if (len <= 0) {
                DEBUGMSGTL(("smux",
                            "[var_smux_write] recv failed or timed out\n"));
                smux_peer_cleanup(rptr->sr_fd);
                smux_snmp_select_list_del(rptr->sr_fd);
                return SNMP_ERR_GENERR;
            }

            DEBUGMSGTL(("smux", "[var_smux_write] Received %" NETSNMP_PRIz
                        "d bytes\n", len));

            if (buf[0] == SMUX_TRAP) {
                DEBUGMSGTL(("smux", "[var_smux_write] Received trap\n"));
                DEBUGMSGTL(("smux", "Got trap from peer on fd %d\n",
                         rptr->sr_fd));
                ptr = asn_parse_header(buf, &len, &type);
                if (ptr == NULL)
                    return SNMP_ERR_GENERR;
                smux_trap_process(ptr, &len);


                /*
                 * go and peek at received data again 
                 */
                /*
                 * we could receive the reply or another trap 
                 */
            } else {
                ptr = buf;
                ptr = asn_parse_header(ptr, &len, &type);
                if ((ptr == NULL) || type != SNMP_MSG_RESPONSE)
                    return SNMP_ERR_GENERR;

                ptr =
                    asn_parse_int(ptr, &len, &type, &reqid, sizeof(reqid));
                if ((ptr == NULL) || type != ASN_INTEGER)
                    return SNMP_ERR_GENERR;

                ptr =
                    asn_parse_int(ptr, &len, &type, &errsts,
                                  sizeof(errsts));
                if ((ptr == NULL) || type != ASN_INTEGER)
                    return SNMP_ERR_GENERR;

                if (errsts) {
                    DEBUGMSGTL(("smux",
                                "[var_smux_write] errsts returned\n"));
                    return (errsts);
                }

                ptr =
                    asn_parse_int(ptr, &len, &type, &erridx,
                                  sizeof(erridx));
                if ((ptr == NULL) || type != ASN_INTEGER)
                    return SNMP_ERR_GENERR;

                reterr = SNMP_ERR_NOERROR;
                break;
            }
        }                       /* while (1) */
        break;                  /* case Action == RESERVE1 */

    case RESERVE2:
        DEBUGMSGTL(("smux", "[var_smux_write] entering RESERVE2\n"));
        reterr = SNMP_ERR_NOERROR;
        break;                  /* case Action == RESERVE2 */

    case FREE:
    case COMMIT:
        ptr = sout;
        *(ptr++) = (u_char) SMUX_SOUT;
        *(ptr++) = (u_char) 1;
        if (action == FREE) {
            *ptr = (u_char) 1;  /* rollback */
            DEBUGMSGTL(("smux",
                        "[var_smux_write] entering FREE - sending RollBack \n"));
        } else {
            *ptr = (u_char) 0;  /* commit */
            DEBUGMSGTL(("smux",
                        "[var_smux_write] entering FREE - sending Commit \n"));
        }

        if ((sendto(rptr->sr_fd, (void *) sout, 3, 0, NULL, 0)) < 0) {
            DEBUGMSGTL(("smux",
                        "[var_smux_write] send rollback/commit failed\n"));
            return SNMP_ERR_GENERR;
        }

        reterr = SNMP_ERR_NOERROR;
        break;                  /* case Action == COMMIT */

    default:
        break;
    }
    return reterr;
}


int
smux_accept(int sd)
{
    u_char          data[SMUXMAXPKTSIZE], *ptr, type;
    struct sockaddr_in in_socket;
    struct timeval  tv;
    int             fail, fd;
    socklen_t       alen;
    int             length;
    size_t          len;

    alen = sizeof(struct sockaddr_in);
    /*
     * this may be too high 
     */
    tv.tv_sec = 5;
    tv.tv_usec = 0;

    /*
     * connection request 
     */
    DEBUGMSGTL(("smux", "[smux_accept] Calling accept()\n"));
    errno = 0;
    if ((fd = (int) accept(sd, (struct sockaddr *) &in_socket, &alen)) < 0) {
        snmp_log_perror("[smux_accept] accept failed");
        return -1;
    } else {
        DEBUGMSGTL(("smux", "[smux_accept] accepted fd %d from %s:%d\n",
                 fd, inet_ntoa(in_socket.sin_addr),
                 ntohs(in_socket.sin_port)));
        if (npeers + 1 == SMUXMAXPEERS) {
            snmp_log(LOG_ERR,
                     "[smux_accept] denied peer on fd %d, limit %d reached",
                     fd, SMUXMAXPEERS);
            close(fd);
            return -1;
        }

        /*
         * now block for an OpenPDU 
         */
        do
        {
           length = recvfrom(fd, (char *) data, SMUXMAXPKTSIZE, 0, NULL, NULL);
        }
        while((length == -1) && ((errno == EINTR) || (errno == EAGAIN)));

        if (length <= 0) {
            DEBUGMSGTL(("smux",
                        "[smux_accept] peer on fd %d died or timed out\n",
                        fd));
            close(fd);
            return -1;
        }
        /*
         * try to authorize him 
         */
        ptr = data;
        len = length;
        if ((ptr = asn_parse_header(ptr, &len, &type)) == NULL) {
            smux_send_close(fd, SMUXC_PACKETFORMAT);
            close(fd);
            DEBUGMSGTL(("smux", "[smux_accept] peer on %d sent bad open", fd));
            return -1;
        } else if (type != (u_char) SMUX_OPEN) {
            smux_send_close(fd, SMUXC_PROTOCOLERROR);
            close(fd);
            DEBUGMSGTL(("smux",
                        "[smux_accept] peer on %d did not send open: (%d)\n",
                        fd, type));
            return -1;
        }
        ptr = smux_open_process(fd, ptr, &len, &fail);
        if (fail) {
            smux_send_close(fd, SMUXC_AUTHENTICATIONFAILURE);
            close(fd);
            DEBUGMSGTL(("smux",
                        "[smux_accept] peer on %d failed authentication\n",
                        fd));
            return -1;
        }

        /*
         * he's OK 
         */
#ifdef SO_RCVTIMEO
        if (setsockopt
            (fd, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof(tv)) < 0) {
            DEBUGMSGTL(("smux",
                        "[smux_accept] setsockopt(SO_RCVTIMEO) failed fd %d\n",
                        fd));
            snmp_log_perror("smux_accept: setsockopt SO_RCVTIMEO");
        }
#endif
        npeers++;
        DEBUGMSGTL(("smux", "[smux_accept] fd %d\n", fd));

        /*
         * Process other PDUs already read, e.g. a registerRequest. 
         */
        len = length - (ptr - data);
        if (smux_pdu_process(fd, ptr, len) < 0) {
            /*
             * Easy come, easy go.  Clean-up is already done. 
             */
            return -1;
        }
    }
    return fd;
}

int
smux_process(int fd)
{
    int             length, tmp_length;
    u_char          data[SMUXMAXPKTSIZE];
    u_char          type, *ptr;
    size_t          packet_len;

    do
    {
       length = recvfrom(fd, (char *) data, SMUXMAXPKTSIZE, MSG_PEEK, NULL,
                         NULL);
    }
    while((length == -1) && ((errno == EINTR) || (errno == EAGAIN)));

    if (length <= 0)
    {
       if (length < 0)
           snmp_log_perror("[smux_process] peek failed");
       smux_peer_cleanup(fd);
       return -1;
    }

    /*
     * determine if we received more than one packet 
     */
    packet_len = length;
    ptr = asn_parse_header(data, &packet_len, &type);
    if (ptr == NULL)
        return -1;
    packet_len += (ptr - data);
    if (length > packet_len) {
        /*
         * set length to receive only the first packet 
         */
        length = packet_len;
    }

    tmp_length = length;
    do
    {
       length = tmp_length;
       length = recvfrom(fd, (char *) data, length, 0, NULL, NULL);
    }
    while((length == -1) && ((errno == EINTR) || (errno == EAGAIN)));

    if (length <= 0) {
        /*
         * the peer went away, close this descriptor 
         * * and delete it from the list
         */
        DEBUGMSGTL(("smux",
                    "[smux_process] peer on fd %d died or timed out\n",
                    fd));
        smux_peer_cleanup(fd);
        return -1;
    }

    return smux_pdu_process(fd, data, length);
}

static int
smux_pdu_process(int fd, u_char * data, size_t length)
{
    int             error;
    size_t          len;
    u_char         *ptr, type;

    DEBUGMSGTL(("smux", "[smux_pdu_process] Processing %" NETSNMP_PRIz
                "d bytes\n", length));

    error = 0;
    ptr = data;
    while (error == 0 && ptr != NULL && ptr < data + length) {
        len = length - (ptr - data);
        ptr = asn_parse_header(ptr, &len, &type);
        if (ptr == NULL) {
            DEBUGMSGTL(("smux", "[smux_pdu_process] cannot parse header\n"));
            break;
        }
        DEBUGMSGTL(("smux", "[smux_pdu_process] type is %d\n",
                    (int) type));
        switch (type) {
        case SMUX_OPEN:
            smux_send_close(fd, SMUXC_PROTOCOLERROR);
            DEBUGMSGTL(("smux",
                        "[smux_pdu_process] peer on fd %d sent duplicate open?\n",
                        fd));
            smux_peer_cleanup(fd);
            error = -1;
            break;
        case SMUX_CLOSE:
            ptr = smux_close_process(fd, ptr, &len);
            smux_peer_cleanup(fd);
            error = -1;
            break;
        case SMUX_RREQ:
            ptr = smux_rreq_process(fd, ptr, &len);
            break;
        case SMUX_RRSP:
            error = -1;
            smux_send_close(fd, SMUXC_PROTOCOLERROR);
            smux_peer_cleanup(fd);
            DEBUGMSGTL(("smux",
                        "[smux_pdu_process] peer on fd %d sent RRSP!\n",
                        fd));
            break;
        case SMUX_SOUT:
            error = -1;
            smux_send_close(fd, SMUXC_PROTOCOLERROR);
            smux_peer_cleanup(fd);
            DEBUGMSGTL(("smux", "This shouldn't have happened!\n"));
            break;
        case SMUX_TRAP:
            DEBUGMSGTL(("smux", "Got trap from peer on fd %d\n", fd));
            if (ptr)
            {
               DEBUGMSGTL(("smux", "[smux_pdu_process] call smux_trap_process.\n"));
               ptr = smux_trap_process(ptr, &len);
            }
            else
            {
               DEBUGMSGTL(("smux", "[smux_pdu_process] smux_trap_process not called: ptr=NULL.\n"));
               DEBUGMSGTL(("smux", "[smux_pdu_process] Error: \n%s\n", snmp_api_errstring(0)));
            }
            /*
             * watch out for close on top of this...should return correct end 
             */
            break;
        default:
            smux_send_close(fd, SMUXC_PACKETFORMAT);
            smux_peer_cleanup(fd);
            DEBUGMSGTL(("smux", "[smux_pdu_process] Wrong type %d\n",
                        (int) type));
            error = -1;
            break;
        }
    }
    return error;
}

static u_char  *
smux_open_process(int fd, u_char * ptr, size_t * len, int *fail)
{
    u_char          type;
    long            version;
    oid             oid_name[MAX_OID_LEN];
    char            passwd[SMUXMAXSTRLEN];
    char            descr[SMUXMAXSTRLEN];
    char            oid_print[SMUXMAXSTRLEN];
    int             i;
    size_t          oid_name_len, string_len;

    if (!(ptr = asn_parse_int(ptr, len, &type, &version, sizeof(version)))) {
        DEBUGMSGTL(("smux", "[smux_open_process] version parse failed\n"));
        *fail = TRUE;
        return ((ptr += *len));
    }
    DEBUGMSGTL(("smux",
                "[smux_open_process] version %ld, len %" NETSNMP_PRIz
                "u, type %d\n", version, *len, (int) type));

    oid_name_len = MAX_OID_LEN;
    if ((ptr = asn_parse_objid(ptr, len, &type, oid_name,
                               &oid_name_len)) == NULL) {
        DEBUGMSGTL(("smux", "[smux_open_process] oid parse failed\n"));
        *fail = TRUE;
        return ((ptr += *len));
    }
    snprint_objid(oid_print, sizeof(oid_print), oid_name, oid_name_len);

    if (snmp_get_do_debugging()) {
        DEBUGMSGTL(("smux", "[smux_open_process] smux peer: %s\n",
                    oid_print));
        DEBUGMSGTL(("smux", "[smux_open_process] len %" NETSNMP_PRIz
                    "u, type %d\n", *len, (int) type));
    }

    string_len = SMUXMAXSTRLEN;
    if ((ptr = asn_parse_string(ptr, len, &type, (u_char *) descr,
                                &string_len)) == NULL) {
        DEBUGMSGTL(("smux", "[smux_open_process] descr parse failed\n"));
        *fail = TRUE;
        return ((ptr += *len));
    }

    if (snmp_get_do_debugging()) {
        DEBUGMSGTL(("smux", "[smux_open_process] smux peer descr: "));
        for (i = 0; i < (int) string_len; i++)
            DEBUGMSG(("smux", "%c", descr[i]));
        DEBUGMSG(("smux", "\n"));
        DEBUGMSGTL(("smux", "[smux_open_process] len %" NETSNMP_PRIz
                    "u, type %d\n", *len, (int) type));
    }
    descr[string_len] = 0;

    string_len = SMUXMAXSTRLEN;
    if ((ptr = asn_parse_string(ptr, len, &type, (u_char *) passwd,
                                &string_len)) == NULL) {
        DEBUGMSGTL(("smux", "[smux_open_process] passwd parse failed\n"));
        *fail = TRUE;
        return ((ptr += *len));
    }

    if (snmp_get_do_debugging()) {
        DEBUGMSGTL(("smux", "[smux_open_process] smux peer passwd: "));
        for (i = 0; i < (int) string_len; i++)
            DEBUGMSG(("smux", "%c", passwd[i]));
        DEBUGMSG(("smux", "\n"));
        DEBUGMSGTL(("smux", "[smux_open_process] len %" NETSNMP_PRIz
                    "u, type %d\n", *len, (int) type));
    }
    passwd[string_len] = '\0';
    if (!smux_auth_peer(oid_name, oid_name_len, passwd, fd)) {
        snmp_log(LOG_WARNING,
                 "refused smux peer: oid %s, descr %s\n",
                 oid_print, descr);
        *fail = TRUE;
        return ptr;
    }
    DEBUGMSGTL(("smux",
             "accepted smux peer: oid %s, descr %s\n",
             oid_print, descr));
    *fail = FALSE;
    return ptr;
}

static void
smux_send_close(int fd, int reason)
{
    u_char          outpacket[3], *ptr;

    ptr = outpacket;

    *(ptr++) = (u_char) SMUX_CLOSE;
    *(ptr++) = (u_char) 1;
    *ptr = (u_char) (reason & 0xFF);

    if (snmp_get_do_debugging())
        DEBUGMSGTL(("smux",
                    "[smux_close] sending close to fd %d, reason %d\n", fd,
                    reason));

    /*
     * send a response back 
     */
    if (sendto(fd, (char *) outpacket, 3, 0, NULL, 0) < 0) {
        snmp_log_perror("[smux_snmp_close] send failed");
    }
}


static int
smux_auth_peer(oid * name, size_t namelen, char *passwd, int fd)
{
    int             i;
    char            oid_print[SMUXMAXSTRLEN];

    if (snmp_get_do_debugging()) {
        snprint_objid(oid_print, sizeof(oid_print), name, namelen);
        DEBUGMSGTL(("smux:auth", "[smux_auth_peer] Authorizing: %s, %s\n",
                    oid_print, passwd));
    }

    for (i = 0; i < nauths; i++) {
        if (snmp_get_do_debugging()) {
            snprint_objid(oid_print, sizeof(oid_print),
                          Auths[i]->sa_oid, Auths[i]->sa_oid_len);
            DEBUGMSGTL(("smux:auth", "[smux_auth_peer] Checking OID: %s (%d)\n",
                    oid_print, i));
        }
        if (snmp_oid_compare(Auths[i]->sa_oid, Auths[i]->sa_oid_len,
                             name, namelen) == 0) {
            if (snmp_get_do_debugging()) {
                DEBUGMSGTL(("smux:auth", "[smux_auth_peer] Checking P/W: %s (%d)\n",
                        Auths[i]->sa_passwd, Auths[i]->sa_active_fd));
            }
            if (!(strcmp(Auths[i]->sa_passwd, passwd)) &&
                (Auths[i]->sa_active_fd == -1)) {
                /*
                 * matched, mark the auth 
                 */
                Auths[i]->sa_active_fd = fd;
                return 1;
            }
        }
    }
    /*
     * did not match oid and passwd 
     */
    return 0;
}


/*
 * XXX - Bells and Whistles:
 * Need to catch signal when snmpd goes down and send close pdu to gated 
 */
static u_char  *
smux_close_process(int fd, u_char * ptr, size_t * len)
{
    long            down = 0;
    int             length = *len;

    /*
     * This is the integer part of the close pdu 
     */
    while (length--) {
        down = (down << 8) | (long) *ptr;
        ptr++;
    }

    DEBUGMSGTL(("smux",
                "[smux_close_process] close from peer on fd %d reason %ld\n",
                fd, down));
    smux_peer_cleanup(fd);

    return NULL;
}

static u_char  *
smux_rreq_process(int sd, u_char * ptr, size_t * len)
{
    long            priority, rpriority;
    long            operation;
    oid             oid_name[MAX_OID_LEN];
    size_t          oid_name_len;
    int             i, result;
    u_char          type;
    smux_reg       *rptr, *nrptr;
    netsnmp_handler_registration *reg;

    oid_name_len = MAX_OID_LEN;
    ptr = asn_parse_objid(ptr, len, &type, oid_name, &oid_name_len);

    DEBUGMSGTL(("smux", "[smux_rreq_process] smux subtree: "));
    DEBUGMSGOID(("smux", oid_name, oid_name_len));
    DEBUGMSG(("smux", "\n"));

    if ((ptr = asn_parse_int(ptr, len, &type, &priority,
                             sizeof(priority))) == NULL) {
        DEBUGMSGTL(("smux",
                    "[smux_rreq_process] priority parse failed\n"));
        smux_send_rrsp(sd, -1);
        return NULL;
    }
    DEBUGMSGTL(("smux", "[smux_rreq_process] priority %ld\n", priority));

    if ((ptr = asn_parse_int(ptr, len, &type, &operation,
                             sizeof(operation))) == NULL) {
        DEBUGMSGTL(("smux",
                    "[smux_rreq_process] operation parse failed\n"));
        smux_send_rrsp(sd, -1);
        return NULL;
    }
    DEBUGMSGTL(("smux", "[smux_rreq_process] operation %ld\n", operation));

    if (operation == SMUX_REGOP_DELETE) {
        /*
         * search the active list for this registration 
         */
        rptr =
            smux_find_match(ActiveRegs, sd, oid_name, oid_name_len,
                            priority);
        if (rptr) {
            rpriority = rptr->sr_priority;
            /*
             * unregister the mib 
             */
            unregister_mib(rptr->sr_name, rptr->sr_name_len);
            /*
             * find a replacement 
             */
            nrptr =
                smux_find_replacement(rptr->sr_name, rptr->sr_name_len);
            if (nrptr) {
                /*
                 * found one 
                 */
                smux_replace_active(rptr, nrptr);
            } else {
                /*
                 * no replacement found 
                 */
                smux_list_detach(&ActiveRegs, rptr);
                free(rptr);
            }
            smux_send_rrsp(sd, rpriority);
            return ptr;
        }
        /*
         * search the passive list for this registration 
         */
        rptr =
            smux_find_match(PassiveRegs, sd, oid_name, oid_name_len,
                            priority);
        if (rptr) {
            rpriority = rptr->sr_priority;
            smux_list_detach(&PassiveRegs, rptr);
            free(rptr);
            smux_send_rrsp(sd, rpriority);
            return ptr;
        }
        /*
         * This peer cannot unregister the tree, it does not
         * * belong to him.  Send him an error.
         */
        smux_send_rrsp(sd, -1);
        return ptr;

    } else if ((operation == SMUX_REGOP_REGISTER_RO) ||
               (operation == SMUX_REGOP_REGISTER_RW)) {
        if (priority < -1) {
            DEBUGMSGTL(("smux",
                        "[smux_rreq_process] peer fd %d invalid priority %ld",
                        sd, priority));
            smux_send_rrsp(sd, -1);
            return NULL;
        }
        if ((nrptr = malloc(sizeof(smux_reg))) == NULL) {
            snmp_log_perror("[smux_rreq_process] malloc");
            smux_send_rrsp(sd, -1);
            return NULL;
        }
        nrptr->sr_priority = priority;
        nrptr->sr_name_len = oid_name_len;
        nrptr->sr_fd = sd;
        for (i = 0; i < (int) oid_name_len; i++)
            nrptr->sr_name[i] = oid_name[i];

        /*
         * See if this tree matches or scopes any of the
         * * active trees.
         */
        for (rptr = ActiveRegs; rptr; rptr = rptr->sr_next) {
            result =
                snmp_oid_compare(oid_name, oid_name_len, rptr->sr_name,
                                 rptr->sr_name_len);
            if (result == 0) {
                if (oid_name_len == rptr->sr_name_len) {
                    if (nrptr->sr_priority == -1) {
                        nrptr->sr_priority = rptr->sr_priority;
                        do {
                            nrptr->sr_priority++;
                        } while (smux_list_add(&PassiveRegs, nrptr));
                        goto done;
                    } else if (nrptr->sr_priority < rptr->sr_priority) {
                        /*
                         * Better priority.  There are no better
                         * * priorities for this tree in the passive list,
                         * * so replace the current active tree.
                         */
                        smux_replace_active(rptr, nrptr);
                        goto done;
                    } else {
                        /*
                         * Equal or worse priority 
                         */
                        do {
                            nrptr->sr_priority++;
                        } while (smux_list_add(&PassiveRegs, nrptr) == -1);
                        goto done;
                    }
                } else if (oid_name_len < rptr->sr_name_len) {
                    /*
                     * This tree scopes a current active
                     * * tree.  Replace the current active tree.
                     */
                    smux_replace_active(rptr, nrptr);
                    goto done;
                } else {        /* oid_name_len > rptr->sr_name_len */
                    /*
                     * This tree is scoped by a current
                     * * active tree.  
                     */
                    do {
                        nrptr->sr_priority++;
                    } while (smux_list_add(&PassiveRegs, nrptr) == -1);
                    goto done;
                }
            }
        }
        /*
         * We didn't find it in the active list.  Add it at
         * * the requested priority.
         */
        if (nrptr->sr_priority == -1)
            nrptr->sr_priority = 0;

        reg = netsnmp_create_handler_registration("smux",
                smux_handler,
                nrptr->sr_name,
                nrptr->sr_name_len,
                HANDLER_CAN_RWRITE);
        if (reg == NULL) {
            snmp_log(LOG_ERR, "SMUX: cannot create new smux peer "
                    "registration\n");
            smux_send_rrsp(sd, -1);
            free(nrptr);
            return NULL;
        }
        if (netsnmp_register_handler(reg) != MIB_REGISTERED_OK) {
            snmp_log(LOG_ERR, "SMUX: cannot register new smux peer\n");
            smux_send_rrsp(sd, -1);
            free(nrptr);
            return NULL;
        }
        nrptr->reginfo = reg;
        smux_list_add(&ActiveRegs, nrptr);

      done:
        smux_send_rrsp(sd, nrptr->sr_priority);
        return ptr;
    } else {
        DEBUGMSGTL(("smux", "[smux_rreq_process] unknown operation\n"));
        smux_send_rrsp(sd, -1);
        return NULL;
    }
}

/*
 * Find the registration with a matching descriptor, OID and priority.  If
 * the priority is -1 then find a registration with a matching descriptor,
 * a matching OID, and the highest priority.
 */
static smux_reg *
smux_find_match(smux_reg * regs, int sd, oid * oid_name,
                size_t oid_name_len, long priority)
{
    smux_reg       *rptr, *bestrptr;

    bestrptr = NULL;
    for (rptr = regs; rptr; rptr = rptr->sr_next) {
        if (rptr->sr_fd != sd)
            continue;
        if (snmp_oid_compare
            (rptr->sr_name, rptr->sr_name_len, oid_name, oid_name_len))
            continue;
        if (rptr->sr_priority == priority)
            return rptr;
        if (priority != -1)
            continue;
        if (bestrptr) {
            if (bestrptr->sr_priority > rptr->sr_priority)
                bestrptr = rptr;
        } else {
            bestrptr = rptr;
        }
    }
    return bestrptr;
}

static void
smux_replace_active(smux_reg * actptr, smux_reg * pasptr)
{
    netsnmp_handler_registration *reg;

    smux_list_detach(&ActiveRegs, actptr);
    if (actptr->reginfo) {
        netsnmp_unregister_handler(actptr->reginfo);
        actptr->reginfo = NULL;
    }

    smux_list_detach(&PassiveRegs, pasptr);

    (void) smux_list_add(&ActiveRegs, pasptr);
    free(actptr);

    reg = netsnmp_create_handler_registration("smux",
            smux_handler,
            pasptr->sr_name,
            pasptr->sr_name_len,
            HANDLER_CAN_RWRITE);
    if (reg == NULL) {
        snmp_log(LOG_ERR, "SMUX: cannot create new smux peer registration\n");
        pasptr->reginfo = NULL;
        return;
    }
    if (netsnmp_register_handler(reg) != MIB_REGISTERED_OK) {
        snmp_log(LOG_ERR, "SMUX: cannot register new smux peer\n");
        pasptr->reginfo = NULL;
        return;
    }
    pasptr->reginfo = reg;
}

static void
smux_list_detach(smux_reg ** head, smux_reg * m_remove)
{
    smux_reg       *rptr, *rptr2;

    if (*head == NULL) {
        DEBUGMSGTL(("smux", "[smux_list_detach] Ouch!"));
        return;
    }
    if (*head == m_remove) {
        *head = (*head)->sr_next;
        return;
    }
    for (rptr = *head, rptr2 = rptr->sr_next; rptr2;
         rptr2 = rptr2->sr_next, rptr = rptr->sr_next) {
        if (rptr2 == m_remove) {
            rptr->sr_next = rptr2->sr_next;
            return;
        }
    }
}

/*
 * Attempt to add a registration (in order) to a list.  If the
 * add fails (because of an existing registration with equal
 * priority) return -1.
 */
static int
smux_list_add(smux_reg ** head, smux_reg * add)
{
    smux_reg       *rptr, *prev;
    int             result;

    if (*head == NULL) {
        *head = add;
        (*head)->sr_next = NULL;
        return 0;
    }
    prev = NULL;
    for (rptr = *head; rptr; rptr = rptr->sr_next) {
        result = snmp_oid_compare(add->sr_name, add->sr_name_len,
                                  rptr->sr_name, rptr->sr_name_len);
        if (result == 0) {
            /*
             * Same tree...
             */
            if (add->sr_priority == rptr->sr_priority) {
                /*
                 * ... same pri : nope 
                 */
                return -1;
            } else if (add->sr_priority < rptr->sr_priority) {
                /*
                 * ... lower pri : insert and return
                 */
                add->sr_next = rptr;
                if ( prev ) { prev->sr_next = add; }
                else        {         *head = add; }
                return 0;
#ifdef XXX
            } else {
                /*
                 * ... higher pri : put after 
                 */
                add->sr_next  = rptr->sr_next;
                rptr->sr_next = add;
#endif
            }
        } else if (result < 0) {
            /*
             * Earlier tree : insert and return
             */
            add->sr_next = rptr;
            if ( prev ) { prev->sr_next = add; }
            else        {         *head = add; }
            return 0;
#ifdef XXX
        } else  {
            /*
             * Later tree : put after
             */
            add->sr_next = rptr->sr_next;
            rptr->sr_next = add;
            return 0;
#endif
        }
        prev = rptr;
    }
    /*
     * Otherwise, this entry must come last
     */
    if ( prev ) { prev->sr_next = add; }
    else        {         *head = add; }
    add->sr_next = NULL;
    return 0;
}

/*
 * Find a replacement for this registration.  In order
 * of preference:
 *
 *      - Least difference in subtree length
 *      - Best (lowest) priority
 *
 * For example, if we need to replace .1.3.6.1.69, 
 * we would pick .1.3.6.1.69.1 instead of .1.3.6.69.1.1
 *
 */
static smux_reg *
smux_find_replacement(oid * name, size_t name_len)
{
    smux_reg       *rptr, *bestptr;
    int             bestlen, difflen;

    bestlen = SMUX_MAX_PRIORITY;
    bestptr = NULL;

    for (rptr = PassiveRegs; rptr; rptr = rptr->sr_next) {
        if (!snmp_oidtree_compare(rptr->sr_name, rptr->sr_name_len,
                                  name, name_len)) {
            if ((difflen = rptr->sr_name_len - name_len)
                < bestlen || !bestptr) {
                bestlen = difflen;
                bestptr = rptr;
            } else if ((difflen == bestlen) &&
                       (rptr->sr_priority < bestptr->sr_priority))
                bestptr = rptr;
        }
    }
    return bestptr;
}

u_char         *
smux_snmp_process(int exact,
                  oid * objid,
                  size_t * len,
                  size_t * return_len, u_char * return_type, int sd)
{
    u_char          packet[SMUXMAXPKTSIZE], *ptr, result[SMUXMAXPKTSIZE];
    ssize_t         length = SMUXMAXPKTSIZE;
    int             tmp_length;
    u_char          type;
    size_t          packet_len;

    /*
     * Send the query to the peer
     */
    if (exact)
        type = SMUX_GET;
    else
        type = SMUX_GETNEXT;

    if (smux_build(type, smux_reqid, objid, len, 0, NULL,
                   *len, packet, (size_t *) &length) < 0) {
        snmp_log(LOG_ERR, "[smux_snmp_process]: smux_build failed\n");
        return NULL;
    }
    DEBUGMSGTL(("smux", "[smux_snmp_process] oid from build: "));
    DEBUGMSGOID(("smux", objid, *len));
    DEBUGMSG(("smux", "\n"));

    if (sendto(sd, (char *) packet, length, 0, NULL, 0) < 0) {
        snmp_log_perror("[smux_snmp_process] send failed");
    }

    DEBUGMSGTL(("smux",
                "[smux_snmp_process] Sent %d request to peer; %" NETSNMP_PRIz "d bytes\n",
                (int) type, length));

    while (1) {
        /*
         * peek at what's received 
         */
        length = recvfrom(sd, (char *) result, SMUXMAXPKTSIZE, MSG_PEEK, NULL,
                          NULL);
        if (length <= 0) {
            if ((length == -1) && ((errno == EINTR) || (errno == EAGAIN)))
            {
               continue;
            }
            else
            {
               snmp_log_perror("[smux_snmp_process] peek failed");
               smux_peer_cleanup(sd);
               smux_snmp_select_list_del(sd);
               return NULL;
            }
        }

        DEBUGMSGTL(("smux", "[smux_snmp_process] Peeked at %" NETSNMP_PRIz "d bytes\n",
                    length));
        DEBUGDUMPSETUP("smux_snmp_process", result, length);

        /*
         * determine if we received more than one packet 
         */
        packet_len = length;
        ptr = asn_parse_header(result, &packet_len, &type);
        if (ptr == NULL)
            return NULL;
        packet_len += (ptr - result);
        if (length > packet_len) {
            /*
             * set length to receive only the first packet 
             */
            length = packet_len;
        }

        /*
         * receive the first packet 
         */
        tmp_length = length;
        do
        {
           length = tmp_length;
           length = recvfrom(sd, (char *) result, length, 0, NULL, NULL);
        }
        while((length == -1) && ((errno == EINTR) || (errno == EAGAIN)));

        if (length <= 0) {
           snmp_log_perror("[smux_snmp_process] recv failed");
           smux_peer_cleanup(sd);
           smux_snmp_select_list_del(sd);
           return NULL;
        }

        DEBUGMSGTL(("smux", "[smux_snmp_process] Received %" NETSNMP_PRIz "d bytes\n",
                    length));

        if (result[0] == SMUX_TRAP) {
            DEBUGMSGTL(("smux", "[smux_snmp_process] Received trap\n"));
            DEBUGMSGTL(("smux", "Got trap from peer on fd %d\n", sd));
            ptr = asn_parse_header(result, (size_t *) &length, &type);
            if (ptr == NULL)
                return NULL;
            smux_trap_process(ptr, (size_t *) &length);

            /*
             * go and peek at received data again 
             */
            /*
             * we could receive the reply or another trap 
             */
        } else {
            /*
             * Interpret reply 
             */
            ptr = smux_parse(result, objid, len, return_len, return_type);
            /*
             * ptr will point to query result or NULL if error 
             */
            break;
        }
    }                           /* while (1) */

    return ptr;
}

static u_char  *
smux_parse(u_char * rsp,
           oid * objid,
           size_t * oidlen, size_t * return_len, u_char * return_type)
{
    size_t          length = SMUXMAXPKTSIZE;
    u_char         *ptr, type;
    long            reqid, errstat, errindex;

    ptr = rsp;

    /*
     * Return pointer to the snmp/smux return value.
     * return_len should contain the number of bytes in the value
     * returned above.
     * objid is the next object, with len for GETNEXT.
     * objid and len are not changed for GET
     */
    ptr = asn_parse_header(ptr, &length, &type);
    if (ptr == NULL || type != SNMP_MSG_RESPONSE)
        return NULL;

    if ((ptr = asn_parse_int(ptr, &length, &type, &reqid,
                             sizeof(reqid))) == NULL) {
        DEBUGMSGTL(("smux", "[smux_parse] parse of reqid failed\n"));
        return NULL;
    }
    if ((ptr = asn_parse_int(ptr, &length, &type, &errstat,
                             sizeof(errstat))) == NULL) {
        DEBUGMSGTL(("smux",
                    "[smux_parse] parse of error status failed\n"));
        return NULL;
    }
    if ((ptr = asn_parse_int(ptr, &length, &type, &errindex,
                             sizeof(errindex))) == NULL) {
        DEBUGMSGTL(("smux", "[smux_parse] parse of error index failed\n"));
        return NULL;
    }

    /*
     * XXX How to send something intelligent back in case of an error 
     */
    DEBUGMSGTL(("smux",
                "[smux_parse] Message type %d, reqid %ld, errstat %ld, \n\terrindex %ld\n",
                (int) type, reqid, errstat, errindex));
    if (ptr == NULL || errstat != SNMP_ERR_NOERROR)
        return NULL;

    /*
     * stuff to return 
     */
    return (smux_parse_var
            (ptr, &length, objid, oidlen, return_len, return_type));
}


static u_char  *
smux_parse_var(u_char * varbind,
               size_t * varbindlength,
               oid * objid,
               size_t * oidlen, size_t * varlength, u_char * vartype)
{
    oid             var_name[MAX_OID_LEN];
    size_t          var_name_len;
    size_t          var_val_len;
    u_char         *var_val;
    size_t          str_len, objid_len;
    size_t          len;
    u_char         *ptr;
    u_char          type;

    ptr = varbind;
    len = *varbindlength;

    DEBUGMSGTL(("smux", "[smux_parse_var] before any processing: "));
    DEBUGMSGOID(("smux", objid, *oidlen));
    DEBUGMSG(("smux", "\n"));

    ptr = asn_parse_header(ptr, &len, &type);
    if (ptr == NULL || type != (ASN_SEQUENCE | ASN_CONSTRUCTOR)) {
        snmp_log(LOG_NOTICE, "[smux_parse_var] Panic: type %d\n",
                 (int) type);
        return NULL;
    }

    /*
     * get hold of the objid and the asn1 coded value 
     */
    var_name_len = MAX_OID_LEN;
    ptr = snmp_parse_var_op(ptr, var_name, &var_name_len, vartype,
                            &var_val_len, &var_val, &len);

    *oidlen = var_name_len;
    memcpy(objid, var_name, var_name_len * sizeof(oid));

    DEBUGMSGTL(("smux", "[smux_parse_var] returning oid : "));
    DEBUGMSGOID(("smux", objid, *oidlen));
    DEBUGMSG(("smux", "\n"));
    /*
     * XXX 
     */
    len = SMUXMAXPKTSIZE;
    DEBUGMSGTL(("smux",
                "[smux_parse_var] Asn coded len of var %" NETSNMP_PRIz
                "u, type %d\n", var_val_len, (int) *vartype));

    switch ((short) *vartype) {
    case ASN_INTEGER:
        *varlength = sizeof(long);
        asn_parse_int(var_val, &len, vartype,
                      (long *) &smux_long, *varlength);
        return (u_char *) & smux_long;
        break;
    case ASN_COUNTER:
    case ASN_GAUGE:
    case ASN_TIMETICKS:
    case ASN_UINTEGER:
        *varlength = sizeof(u_long);
        asn_parse_unsigned_int(var_val, &len, vartype,
                               (u_long *) & smux_ulong, *varlength);
        return (u_char *) & smux_ulong;
        break;
    case ASN_COUNTER64:
        *varlength = sizeof(smux_counter64);
        asn_parse_unsigned_int64(var_val, &len, vartype,
                                 (struct counter64 *) &smux_counter64,
                                 *varlength);
        return (u_char *) & smux_counter64;
        break;
    case ASN_IPADDRESS:
        *varlength = 4;
        /*
         * consume the tag and length, but just copy here
         * because we know it is an ip address
         */
        if ((var_val = asn_parse_header(var_val, &len, &type)) == NULL)
            return NULL;
        memcpy((u_char *) & (smux_sa.sin_addr.s_addr), var_val,
               *varlength);
        return (u_char *) & (smux_sa.sin_addr.s_addr);
        break;
    case ASN_OCTET_STR:
        /*
         * XXX 
         */
        if (len == 0)
            return NULL;
        str_len = SMUXMAXSTRLEN;
        asn_parse_string(var_val, &len, vartype, smux_str, &str_len);
        *varlength = str_len;
        return smux_str;
        break;
    case ASN_OPAQUE:
    case ASN_NSAP:
    case ASN_OBJECT_ID:
        objid_len = MAX_OID_LEN;
        asn_parse_objid(var_val, &len, vartype, smux_objid, &objid_len);
        *varlength = objid_len * sizeof(oid);
        return (u_char *) smux_objid;
        break;
    case SNMP_NOSUCHOBJECT:
    case SNMP_NOSUCHINSTANCE:
    case SNMP_ENDOFMIBVIEW:
    case ASN_NULL:
        return NULL;
        break;
    case ASN_BIT_STR:
        /*
         * XXX 
         */
        if (len == 0)
            return NULL;
        str_len = SMUXMAXSTRLEN;
        asn_parse_bitstring(var_val, &len, vartype, smux_str, &str_len);
        *varlength = str_len;
        return (u_char *) smux_str;
        break;
    default:
        snmp_log(LOG_ERR, "bad type returned (%x)\n", *vartype);
        return NULL;
        break;
    }
}

/*
 * XXX This is a bad hack - do not want to muck with ucd code 
 */
static int
smux_build(u_char type,
           long reqid,
           oid * objid,
           size_t * oidlen,
           u_char val_type,
           u_char * val, size_t val_len, u_char * packet, size_t * length)
{
    u_char         *ptr, *save1, *save2;
    size_t          len;
    long            errstat = 0;
    long            errindex = 0;

    /*
     * leave space for Seq and length 
     */
    save1 = packet;
    ptr = packet + 4;
    len = *length - 4;

    /*
     * build reqid 
     */
    ptr = asn_build_int(ptr, &len,
                                 (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE |
                                           ASN_INTEGER), &reqid,
                                 sizeof(reqid));
    if (ptr == NULL) {
        return -1;
    }

    /*
     * build err stat 
     */
    ptr = asn_build_int(ptr, &len,
                        (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE |
                                  ASN_INTEGER), &errstat, sizeof(errstat));
    if (ptr == NULL) {
        return -1;
    }

    /*
     * build err index 
     */
    ptr = asn_build_int(ptr, &len,
                        (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE |
                                  ASN_INTEGER), &errindex,
                        sizeof(errindex));
    if (ptr == NULL) {
        return -1;
    }

    save2 = ptr;
    ptr += 4;
    len -= 4;

    if (type != SMUX_SET) {
        val_type = ASN_NULL;
        val_len = 0;
    }

    /*
     * build var list : snmp_build_var_op not liked by gated XXX 
     */
    ptr = snmp_build_var_op(ptr, objid, oidlen, val_type, val_len,
                            val, &len);
    if (ptr == NULL) {
        return -1;
    }

    len = ptr - save1;
    asn_build_sequence(save1, &len, type, (ptr - save1 - 4));

    len = ptr - save2;
    asn_build_sequence(save2, &len,
                       (ASN_SEQUENCE | ASN_CONSTRUCTOR),
                       (ptr - save2 - 4));

    *length = ptr - packet;

    return 0;
}

static void
smux_peer_cleanup(int sd)
{
    smux_reg       *nrptr, *rptr, *rptr2;
    int             i;
    netsnmp_handler_registration *reg;

    /*
     * close the descriptor 
     */
    close(sd);

    /*
     * delete all of the passive registrations that this peer owns 
     */
    for (rptr = PassiveRegs; rptr; rptr = nrptr) {
        nrptr = rptr->sr_next;
        if (rptr->sr_fd == sd) {
            smux_list_detach(&PassiveRegs, rptr);
            free(rptr);
        }
        rptr = nrptr;
    }
    /*
     * find replacements for all of the active registrations found 
     */
    for (rptr = ActiveRegs; rptr; rptr = rptr2) {
        rptr2 = rptr->sr_next;
        if (rptr->sr_fd == sd) {
            smux_list_detach(&ActiveRegs, rptr);
            if (rptr->reginfo) {
                netsnmp_unregister_handler(rptr->reginfo);
                rptr->reginfo = NULL;
            }
            if ((nrptr = smux_find_replacement(rptr->sr_name,
                                               rptr->sr_name_len)) !=
                                                       NULL) {
                smux_list_detach(&PassiveRegs, nrptr);
                reg = netsnmp_create_handler_registration("smux",
                        smux_handler,
                        nrptr->sr_name,
                        nrptr->sr_name_len,
                        HANDLER_CAN_RWRITE);
                if (reg == NULL) {
                    snmp_log(LOG_ERR, "SMUX: cannot create new smux peer "
                            "registration\n");
                    continue;
                }
                if (netsnmp_register_handler(reg) != MIB_REGISTERED_OK) {
                    snmp_log(LOG_ERR, "SMUX: cannot register new smux peer\n");
                    continue;
                }
                nrptr->reginfo = reg;
                smux_list_add(&ActiveRegs, nrptr);
            }
            free(rptr);
        }
    }

    /*
     * decrement the peer count 
     */
    npeers--;

    /*
     * make his auth available again 
     */
    for (i = 0; i < nauths; i++) {
        if (Auths[i]->sa_active_fd == sd) {
            char            oid_name[128];
            Auths[i]->sa_active_fd = -1;
            snprint_objid(oid_name, sizeof(oid_name), Auths[i]->sa_oid,
                          Auths[i]->sa_oid_len);
            DEBUGMSGTL(("smux", "peer disconnected: %s\n", oid_name));
        }
    }
}

int
smux_send_rrsp(int sd, int pri)
{
    u_char          outdata[2 + sizeof(int)];
    u_char         *ptr = outdata;
    int             intsize = sizeof(int);
    u_int           mask = ((u_int) 0xFF) << (8 * (sizeof(int) - 1));
    /*
     * e.g. mask is 0xFF000000 on a 32-bit machine 
     */
    int             sent;

    /*
     * This is kind of like calling asn_build_int(), but the
     * encoding will always be the size of an integer on this
     * machine, never shorter.
     */
    *ptr++ = (u_char) SMUX_RRSP;
    *ptr++ = (u_char) intsize;

    /*
     * Copy each byte, most significant first. 
     */
    while (intsize--) {
        *ptr++ = (u_char) ((pri & mask) >> (8 * (sizeof(int) - 1)));
        pri <<= 8;
    }

    sent = sendto(sd, (char *) outdata, sizeof outdata, 0, NULL, 0);
    if (sent < 0) {
        DEBUGMSGTL(("smux", "[smux_send_rrsp] send failed\n"));
    }
    return (sent);
}

static u_char  *
smux_trap_process(u_char * rsp, size_t * len)
{
    oid             sa_enterpriseoid[MAX_OID_LEN], var_name[MAX_OID_LEN];
    size_t          datalen, var_name_len, var_val_len, maxlen;
    size_t          sa_enterpriseoid_len;
    u_char          vartype, *ptr, *var_val;

    long            trap, specific;
    u_long          timestamp;

    netsnmp_variable_list *snmptrap_head, *snmptrap_ptr, *snmptrap_tmp;
    snmptrap_head = NULL;
    snmptrap_ptr = NULL;

    ptr = rsp;

    /*
     * parse the sub-agent enterprise oid 
     */
    sa_enterpriseoid_len = MAX_OID_LEN;
    if ((ptr = asn_parse_objid(ptr, len,
                               &vartype, (oid *) & sa_enterpriseoid,
                               &sa_enterpriseoid_len)) == NULL) {
        DEBUGMSGTL(("smux",
                    "[smux_trap_process] asn_parse_objid failed\n"));
        return NULL;
    }

    /*
     * parse the agent-addr ipAddress 
     */
    datalen = SMUXMAXSTRLEN;
    if (((ptr = asn_parse_string(ptr, len,
                                 &vartype, smux_str,
                                 &datalen)) == NULL) ||
        (vartype != (u_char) ASN_IPADDRESS)) {
        DEBUGMSGTL(("smux",
                    "[smux_trap_process] asn_parse_string failed\n"));
        return NULL;
    }

    /*
     * parse the generic trap int 
     */
    datalen = sizeof(long);
    if ((ptr = asn_parse_int(ptr, len, &vartype, &trap, datalen)) == NULL) {
        DEBUGMSGTL(("smux",
                    "[smux_trap_process] asn_parse_int generic failed\n"));
        return NULL;
    }

    /*
     * parse the specific trap int 
     */
    datalen = sizeof(long);
    if ((ptr = asn_parse_int(ptr, len,
                             &vartype, &specific, datalen)) == NULL) {
        DEBUGMSGTL(("smux",
                    "[smux_trap_process] asn_parse_int specific failed\n"));
        return NULL;
    }

    /*
     * parse the timeticks timestamp 
     */
    datalen = sizeof(u_long);
    if (((ptr = asn_parse_unsigned_int(ptr, len,
                                       &vartype, (u_long *) & timestamp,
                                       datalen)) == NULL) ||
        (vartype != (u_char) ASN_TIMETICKS)) {
        DEBUGMSGTL(("smux",
                    "[smux_trap_process] asn_parse_unsigned_int (timestamp) failed\n"));
        return NULL;
    }

    /*
     * parse out the overall sequence 
     */
    ptr = asn_parse_header(ptr, len, &vartype);
    if (ptr == NULL || vartype != (ASN_SEQUENCE | ASN_CONSTRUCTOR)) {
        return NULL;
    }

    /*
     * parse the variable bindings 
     */
    while (ptr && *len) {

        /*
         * get the objid and the asn1 coded value 
         */
        var_name_len = MAX_OID_LEN;
        ptr = snmp_parse_var_op(ptr, var_name, &var_name_len, &vartype,
                                &var_val_len, (u_char **) & var_val, len);

        if (ptr == NULL)
            goto err;

        maxlen = SMUXMAXPKTSIZE;
        switch ((short) vartype) {
        case ASN_INTEGER:
            var_val_len = sizeof(long);
            asn_parse_int(var_val, &maxlen, &vartype,
                          (long *) &smux_long, var_val_len);
            var_val = (u_char *) & smux_long;
            break;
        case ASN_COUNTER:
        case ASN_GAUGE:
        case ASN_TIMETICKS:
        case ASN_UINTEGER:
            var_val_len = sizeof(u_long);
            asn_parse_unsigned_int(var_val, &maxlen, &vartype,
                                   (u_long *) & smux_ulong, var_val_len);
            var_val = (u_char *) & smux_ulong;
            break;
        case ASN_COUNTER64:
            var_val_len = sizeof(smux_counter64);
            asn_parse_unsigned_int64(var_val, &maxlen, &vartype,
                                     (struct counter64 *) &smux_counter64,
                                     var_val_len);
            var_val = (u_char *) & smux_counter64;
            break;
        case ASN_IPADDRESS:
            var_val_len = 4;
            /*
             * consume the tag and length, but just copy here
             * because we know it is an ip address
             */
            if ((var_val =
                 asn_parse_header(var_val, &maxlen, &vartype)) == NULL)
                goto err;
            memcpy((u_char *) & (smux_sa.sin_addr.s_addr), var_val,
                   var_val_len);
            var_val = (u_char *) & (smux_sa.sin_addr.s_addr);
            break;
        case ASN_OPAQUE:
        case ASN_OCTET_STR:
            /*
             * XXX 
             */
            var_val_len = SMUXMAXSTRLEN;
            asn_parse_string(var_val, &maxlen, &vartype,
                             smux_str, &var_val_len);
            var_val = smux_str;
            break;
        case ASN_OBJECT_ID:
            var_val_len = MAX_OID_LEN;
            asn_parse_objid(var_val, &maxlen, &vartype,
                            smux_objid, &var_val_len);
            var_val_len *= sizeof(oid);
            var_val = (u_char *) smux_objid;
            break;
        case SNMP_NOSUCHOBJECT:
        case SNMP_NOSUCHINSTANCE:
        case SNMP_ENDOFMIBVIEW:
        case ASN_NULL:
            var_val = NULL;
            break;
        case ASN_BIT_STR:
            /*
             * XXX 
             */
            var_val_len = SMUXMAXSTRLEN;
            asn_parse_bitstring(var_val, &maxlen, &vartype,
                                smux_str, &var_val_len);
            var_val = (u_char *) smux_str;
            break;
        case ASN_NSAP:
        default:
            snmp_log(LOG_ERR, "bad type returned (%x)\n", vartype);
            var_val = NULL;
            break;
        }

        snmptrap_tmp = calloc(1, sizeof(netsnmp_variable_list));
        if (snmptrap_tmp == NULL)
            goto err;
        if (snmptrap_head == NULL) {
            snmptrap_head = snmptrap_tmp;
            snmptrap_ptr = snmptrap_head;
        } else {
            snmptrap_ptr->next_variable = snmptrap_tmp;
            snmptrap_ptr = snmptrap_ptr->next_variable;
        }

        snmptrap_ptr->type = vartype;
        snmptrap_ptr->next_variable = NULL;
        snmp_set_var_objid(snmptrap_ptr, var_name, var_name_len);
        snmp_set_var_value(snmptrap_ptr, (char *) var_val, var_val_len);

    }

    /*
     * send the traps 
     */
    send_enterprise_trap_vars(trap, specific, (oid *) & sa_enterpriseoid,
                              sa_enterpriseoid_len, snmptrap_head);

    /*
     * free trap variables 
     */
    snmp_free_varbind(snmptrap_head);

    return ptr;

err:
    snmp_free_varbind(snmptrap_head);
    return NULL;
}

#define NUM_SOCKETS	32
static int      sdlist[NUM_SOCKETS], sdlen = 0;

int smux_snmp_select_list_add(int sd)
{
   if (sdlen < NUM_SOCKETS)
   {
      sdlist[sdlen++] = sd;
      return(1);
   }
   return(0);
}

int smux_snmp_select_list_del(int sd)
{
   int i, found=0;

   for (i = 0; i < (sdlen); i++) {
      if (sdlist[i] == sd)
      {
         sdlist[i] = 0;
         found = 1;
      }
      if ((found) &&(i < (sdlen - 1)))
         sdlist[i] = sdlist[i + 1];
   }
   if (found)
   {
      sdlen--;
      return(1);
   }
   return(0);
}

int smux_snmp_select_list_get_length(void)
{
   return(sdlen);
}

int smux_snmp_select_list_get_SD_from_List(int pos)
{
   if (pos < NUM_SOCKETS)
   {
      return(sdlist[pos]);
   }
   return(0);
}
