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

#include <config.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
# 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
#include <errno.h>
#if HAVE_NETDB_H
#include <netdb.h>
#endif

#include <sys/stat.h>
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#elif HAVE_WINSOCK_H
#include <winsock.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 "system.h"
#include "asn1.h"
#include "mibincl.h"
#include "mib.h"
#include "read_config.h"
#include "snmp.h"
#include "snmp_api.h"
#include "snmp_client.h"
#include "snmp_impl.h"
#include "smux.h"
#include "var_struct.h"
#include "util_funcs.h"
#include "mibdefs.h"
#include "default_store.h"
#include "ds_agent.h"
#include "snmpd.h"

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;

static struct timeval smux_rcv_timeout;
static u_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, u_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 (struct variable *, oid *, size_t *, int, size_t *,
                           WriteMethod **write_method);
int 		var_smux_write (int, u_char *, u_char, size_t, u_char *, 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;

struct variable2 smux_variables[] = {
  /* bogus entry, as in pass.c */
  {MIBINDEX, ASN_INTEGER, RWRITE, var_smux, 0, {MIBINDEX}},
};



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

	if ((aptr = (smux_peer_auth *)calloc(1, sizeof(smux_peer_auth))) == NULL) {
		snmp_log_perror("smux_parse_peer_auth: malloc");
		return;
	}
	aptr->sa_active_fd = -1;
	if (!cptr) {
		/* null passwords OK */
		Auths[nauths++] = aptr;
        	DEBUGMSGTL(("smux_conf", "null password\n"));
		return;
	}

	if(*cptr == '.')
		cptr++;

	if (!isdigit(*cptr)) {
		config_perror("second token is not an OID");
		free((char *)aptr);
		return;
	}
	/* oid */
	aptr->sa_oid_len = parse_miboid(cptr, aptr->sa_oid);

        DEBUGMSGTL(("smux_conf", "parsing registration for: %s\n", cptr));

	while (isdigit(*cptr) || *cptr == '.')
		 cptr++;
	cptr = skip_white(cptr);

        /* password */
        if (cptr)
          strcpy(aptr->sa_passwd, cptr);
        
	Auths[nauths++] = aptr;
}

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)
{

	struct sockaddr_in lo_socket;
	int one = 1;

	if (ds_get_boolean(DS_APPLICATION_ID, DS_AGENT_ROLE) == SUB_AGENT) {
		smux_listen_sd = -1;
		return;
	}

        snmpd_register_config_handler("smuxpeer", smux_parse_peer_auth,
                                      smux_free_peer_auth,
                                      "OID-IDENTITY PASSWORD");
	/* 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;
	lo_socket.sin_port = htons((u_short) SMUXPORT);

	if ((smux_listen_sd = 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)));
}

u_char *
var_smux(struct variable *vp,
	oid *name,
	size_t *length,
	int exact,
	size_t *var_len,
	WriteMethod **write_method)
{
	u_char *valptr, val_type;
	smux_reg *rptr;

	*write_method = var_smux_write; 
	/* search the active registration list */
	for (rptr = ActiveRegs; rptr; rptr = rptr->sr_next) {
		if (!compare_tree(vp->name, vp->namelen, 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, &val_type, rptr->sr_fd);

	if (valptr == NULL)
		return NULL;

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

int 
var_smux_write(
	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)
{
	smux_reg *rptr;
	u_char buf[SMUXMAXPKTSIZE], *ptr, sout[3], type;
	int reterr;
	size_t len, var_len, datalen, name_length, packet_len;
	long reqid, errsts, erridx;
	u_char var_type, *dataptr;

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

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

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

	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 (send(rptr->sr_fd, buf, len, 0) < 0) {
			DEBUGMSGTL (("smux","[var_smux_write] send failed\n"));
			return SNMP_ERR_GENERR;
		}

                while (1) { 
                    /* 
                     * peek at what's received 
                     */ 
                    if ((len = recv(rptr->sr_fd, buf, 
                                    SMUXMAXPKTSIZE, MSG_PEEK)) <= 0) { 
                        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); 
                        return SNMP_ERR_GENERR; 
                    } 

                    DEBUGMSGTL (("smux","[var_smux_write] Peeked at %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); 
                    packet_len += (ptr - buf); 
                    if (len > packet_len) { 
                        /* set length to receive only the first packet */
                        len = packet_len; 
                    } 

                    /* 
                     * receive the first packet 
                     */ 
                    if ((len = recv(rptr->sr_fd, buf, len, 0)) <= 0) { 
                        DEBUGMSGTL (("smux","[var_smux_write] recv failed or timed out\n")); 
                        /* do we need to do a peer cleanup in this case?? */ 
                        smux_peer_cleanup(rptr->sr_fd); 
                        return SNMP_ERR_GENERR; 
                    } 

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

                    if (buf[0] == SMUX_TRAP) { 
                        DEBUGMSGTL (("smux","[var_smux_write] Received trap\n")); 
                        snmp_log(LOG_INFO, "Got trap from peer on fd %d\n", rptr->sr_fd); 
                        ptr = asn_parse_header(buf, &len, &type); 
                        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 ((send(rptr->sr_fd, sout, 3, 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, alen;
	size_t length, 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 = accept(sd, (struct sockaddr *)&in_socket, &alen)) < 0) {
		snmp_log_perror("[smux_accept] accept failed");
		return -1;
	} else {
		snmp_log(LOG_INFO, "[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(sd);
			return -1;
		}

		/* now block for an OpenPDU */
		if ((length = recv(fd, (char *)data, SMUXMAXPKTSIZE, 0)) <= 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"));
			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", 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)
{
	size_t length;
	u_char data[SMUXMAXPKTSIZE];

	length = recv(fd, (char *)data, SMUXMAXPKTSIZE, 0);
	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 %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);
		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:
			snmp_log(LOG_INFO, "Got trap from peer on fd %d\n", fd);
			ptr = smux_trap_process(ptr, &len);
			/* watch out for close on top of this...should return correct end */
			/* debug this... */
			ptr = NULL;
			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 %d, len %d, 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));
	}
	sprint_objid(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 %d, 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 %d, 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 %d, 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, password %s, descr %s\n",
		     oid_print, passwd, descr);
	    *fail = TRUE;
	    return ptr;
	}
	snmp_log(LOG_INFO, "accepted smux peer: oid %s, password %s, descr %s\n",
		 oid_print, passwd, 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 (send (fd, (char *)outpacket, 3, 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;

	for (i = 0; i < nauths; i++) {
		if (snmp_oid_compare(Auths[i]->sa_oid, Auths[i]->sa_oid_len,
		    name, namelen) == 0) {
			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;
			}
			else
				return 0;
		}
	}
	/* 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 %d\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;

	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 %d\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 %d\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", 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;
		smux_list_add(&ActiveRegs, nrptr);
		register_mib("smux", (struct variable *)
		    smux_variables, sizeof(struct variable2),
		    1, nrptr->sr_name, nrptr->sr_name_len);
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)
{
	smux_list_detach(&ActiveRegs, &actptr);
	unregister_mib(actptr->sr_name, actptr->sr_name_len);

	smux_list_detach(&PassiveRegs, &pasptr);
	(void)smux_list_add(&ActiveRegs, pasptr);

	register_mib("smux", (struct variable *)smux_variables,
	    sizeof(struct variable2), 1, pasptr->sr_name,
	    pasptr->sr_name_len);
	free(actptr);
}

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) {
		*m_remove = *head;
		*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) {
			*m_remove = rptr2;
			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;
	int result;

	if(*head == NULL) {
		*head = add;
		(*head)->sr_next = NULL;
		return 0;
	}
	for (rptr = *head; rptr->sr_next; 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) && (add->sr_priority == rptr->sr_priority)) {
			/* same tree, same pri, nope */
			return -1;
		} else if (result < 0) {
			/* this can only happen if we go before the head */
			add->sr_next = *head;
			*head = add;
			return 0;
		} else if ((snmp_oid_compare(add->sr_name, add->sr_name_len,
		    rptr->sr_next->sr_name, rptr->sr_next->sr_name_len)) < 0) {
			/* insert here */
			add->sr_next = rptr->sr_next;
			rptr->sr_next = add;
			return 0;
		}
	}
	/* compare the last one */
	if ((snmp_oid_compare(add->sr_name, add->sr_name_len, rptr->sr_name,
	    rptr->sr_name_len) == 0) && add->sr_priority == rptr->sr_priority)
		return -1;
	else {
		rptr->sr_next = 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 (!compare_tree(rptr->sr_name, rptr->sr_name_len,
		    name, name_len)) {
			if ((difflen = rptr->sr_name_len - name_len)
			    < bestlen) {
				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];
	size_t length = SMUXMAXPKTSIZE;
	u_char type;
        size_t packet_len; 
	
	/* 
	 * Send the query to the peer
	 */
	smux_reqid++;

	if (exact)
		type = SMUX_GET;
	else
		type = SMUX_GETNEXT;

	if (smux_build(type, smux_reqid, objid, len, 0, NULL, 
	    *len, packet, &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 (send(sd, (char *)packet, length, 0) < 0) {
		snmp_log_perror("[smux_snmp_process] send failed");
	}

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

        while (1) { 
            /* 
             * peek at what's received 
             */ 
            length = recv(sd, (char *)result, SMUXMAXPKTSIZE, MSG_PEEK); 
            if (length < 0) { 
                snmp_log_perror("[smux_snmp_process] peek failed"); 
                smux_peer_cleanup(sd); 
                return NULL; 
            } 

            DEBUGMSGTL (("smux","[smux_snmp_process] Peeked at %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); 
            packet_len += (ptr - result); 
            if (length > packet_len) { 
                /* set length to receive only the first packet */
                length = packet_len; 
            } 

            /* 
             * receive the first packet 
             */
            length = recv(sd, (char *)result, length, 0); 
            if (length < 0) { 
                snmp_log_perror("[smux_snmp_process] recv failed"); 
                smux_peer_cleanup(sd); 
                return NULL; 
            } 

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

            if (result[0] == SMUX_TRAP) { 
                DEBUGMSGTL (("smux","[smux_snmp_process] Received trap\n")); 
                snmp_log(LOG_INFO, "Got trap from peer on fd %d\n", sd); 
                ptr = asn_parse_header(result, &length, &type); 
                smux_trap_process(ptr, &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 %d, errstat %d, \n\terrindex %d\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 %d, 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,
	u_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_unsigned_int(ptr, &len, 
	      (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
	      &reqid, sizeof(reqid));

	/* build err stat */
	ptr = asn_build_int(ptr, &len, 
	      (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
	      &errstat, sizeof(errstat));

	/* build err index */
	ptr = asn_build_int(ptr, &len, 
	      (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
	      &errindex, sizeof(errindex));

	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);

	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 nfound, i;

	nfound = 0;

	/* 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);
			unregister_mib(rptr->sr_name, rptr->sr_name_len);
			if ((nrptr = smux_find_replacement(rptr->sr_name,
			     rptr->sr_name_len)) != NULL) {
				smux_list_detach(&PassiveRegs, &nrptr);
				smux_list_add(&ActiveRegs, nrptr);
				register_mib("smux", (struct variable *)
				    smux_variables, sizeof(struct variable2), 
				    1, nrptr->sr_name, nrptr->sr_name_len);
			}
			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;
			sprint_objid(oid_name, Auths[i]->sa_oid, Auths[i]->sa_oid_len);
			snmp_log(LOG_INFO, "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 = send(sd, (char *)outdata, sizeof outdata, 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;
	int sa_enterpriseoid_len;
	u_char vartype, *ptr, *var_val;

	long trap, specific;
	u_long timestamp;

	struct variable_list *snmptrap_head, *snmptrap_ptr, *snmptrap_tmp;
	snmptrap_head = NULL;
	snmptrap_ptr = NULL;

	ptr = rsp;

	/* parse the sub-agent enterprise oid */
	datalen = 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) {
			return NULL;
		}

		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)
					return NULL;
				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 */
				if (len == 0)
					return NULL;
				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 */
				if (len == 0)
					return NULL;
				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 = (struct variable_list *)malloc(sizeof(struct variable_list));
		if (snmptrap_tmp == NULL)
		  return NULL;
		memset(snmptrap_tmp, 0, sizeof(struct variable_list));
		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;
		}

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

	}

	/* 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;

}
