/*
 * tunnel.c --
 * 
 * 	An implementation of the TUNNEL-MIB for the UCD-SNMP 4.2
 *	agent running on Linux 2.2.x.
 * 	
 * Copyright (c) 2000 Frank Strauss <strauss@ibr.cs.tu-bs.de>
 *
 *                          All Rights Reserved
 * 
 * Permission to use, copy, modify and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appears in all copies and
 * that both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of the author and CMU and
 * The Regents of the University of California not be used in advertising
 * or publicity pertaining to distribution of the software without
 * specific written permission.
 * 
 * THE AUTHOR AND CMU AND THE REGENTS OF THE UNIVERSITY OF CALIFORNIA
 * DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL
 * THE AUTHOR OR CMU OR THE REGENTS OF THE UNIVERSITY OF CALIFORNIA BE
 * LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
 * DAMAGES WHATSOEVER RESULTING FROM THE LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 */

/*
 * NOTE: This TUNNEL-MIB implementation
 *
 *       (a) DOES NOT implement write access on the tunnelConfigTable,
 *	     i.e. no new tunnels can be created and no existing tunnels
 *	     can be removed through SET operations.
 *
 *	 (b) DOES implement write access on some tunnelIfTable objects
 *	     to allow reconfiguring established tunnels. This violates
 *           RFC 2667! However, the author thinks it makes sense. ;-)
 */

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <linux/if.h>
#include <linux/ip.h>
#include <linux/sockios.h>
#include <linux/if_tunnel.h>
#include <linux/if_arp.h>

#include "config.h"
#include "snmp_alarm.h"
#include "callback.h"
#include "default_store.h"
#include "asn1.h"
#include "snmp.h"
#include "snmp_debug.h"
#include "snmp_api.h"
#include "snmp_impl.h"
#include "snmp_vars.h"
#include "var_struct.h"
#include "agent_read_config.h"
#include "tools.h"
#include "util_funcs.h"

#include "tunnel.h"

#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif



/* This is used, because the TUNNEL-MIB augments ifTable. */
extern unsigned char *var_ifEntry(struct variable *,
				  oid *, size_t *,
				  int, size_t *,
				  WriteMethod **);


/* 
 * tunnel_variables_oid:
 *   this is the top level oid that we want to register under.  This
 *   is essentially a prefix, with the suffix appearing in the
 *   variable below.
 */
oid tunnel_variables_oid[] = {1, 3, 6, 1, 2, 1, 10, 131, 1, 1};
const int tunnel_len = 10;

oid tunnel_ifEntry_oid[] = {1, 3, 6, 1, 2, 1, 10, 131, 1, 1, 1, 1};
const int tunnel_ifEntry_len = 12;

oid tunnel_configEntry_oid[] = {1, 3, 6, 1, 2, 1, 10, 131, 1, 1, 2, 1};
const int tunnel_configEntry_len = 12;



struct tunnel {
    oid	           ifindex;
    int		   id;
    char	   *ifname;
    int		   active;
    unsigned long  local;
    unsigned long  remote;
    int		   encaps;
    int		   hoplimit;
    int		   security;
    int		   tos;
    oid		   config_name[MAX_OID_LEN];
    size_t	   config_length;
    struct tunnel  *next;
};



/* 
 * variable4 tunnel_variables:
 *   this variable defines function callbacks and type return information 
 *   for the tunnel mib section 
 */

struct variable4 tunnel_variables[] = {
/*  magic number        , variable type , ro/rw , callback fn  , L, oidsuffix */
#define   LOCALADDRESS          1
  { LOCALADDRESS        , ASN_IPADDRESS , RWRITE, var_tunnelIfEntry, 3, { 1, 1, 1 } },
#define   REMOTEADDRESS         2
  { REMOTEADDRESS       , ASN_IPADDRESS , RWRITE, var_tunnelIfEntry, 3, { 1, 1, 2 } },
#define   ENCAPSMETHOD          3
  { ENCAPSMETHOD        , ASN_INTEGER   , RONLY , var_tunnelIfEntry, 3, { 1, 1, 3 } },
#define   HOPLIMIT              4
  { HOPLIMIT            , ASN_INTEGER   , RWRITE, var_tunnelIfEntry, 3, { 1, 1, 4 } },
#define   SECURITY              5
  { SECURITY            , ASN_INTEGER   , RONLY , var_tunnelIfEntry, 3, { 1, 1, 5 } },
#define   TOS                   6
  { TOS                 , ASN_INTEGER   , RWRITE, var_tunnelIfEntry, 3, { 1, 1, 6 } },

#define   IFINDEX               7
  { IFINDEX             , ASN_INTEGER   , RONLY , var_tunnelConfigEntry, 3, { 2, 1, 5 } },
#define   ROWSTATUS             8
  { ROWSTATUS           , ASN_INTEGER   , RWRITE, var_tunnelConfigEntry, 3, { 2, 1, 6 } },
};



extern int register_sysORTable(oid *, size_t, const char *);
extern int unregister_sysORTable(oid *, size_t);

static oid sysORTable_reg[] = {1, 3, 6, 1, 2, 1, 10, 131};
static size_t sysORTable_reglen = 8;

static struct tunnel *tunnels;



void deinit_tunnel(void)
{
    unregister_sysORTable(sysORTable_reg, sysORTable_reglen);
}



int term_tunnel(int majorID, int minorID, void *serverarg, void *clientarg)
{
    deinit_tunnel();
    return 0;
}



void init_tunnel(void)
{
    register_sysORTable(sysORTable_reg, sysORTable_reglen,
			"RFC 2667 TUNNEL-MIB implementation for "
			"Linux 2.2.x kernels.");

    ds_set_string(DS_LIBRARY_ID, DS_LIB_APPTYPE, "snmpd");

    /* register ourselves with the agent to handle our mib tree */
    REGISTER_MIB("tunnel", tunnel_variables, variable4,
		 tunnel_variables_oid);

    snmp_register_callback(SNMP_CALLBACK_LIBRARY,
			   SNMP_CALLBACK_SHUTDOWN,
			   term_tunnel, NULL);

    tunnels = NULL;
}



static int getType(int index)
{
    oid name[MAX_OID_LEN] = {1, 3, 6, 1, 2, 1, 2, 2, 1, 3};
    size_t length = 10;
    struct variable ifType_variable =
	{ 3, ASN_INTEGER, RONLY, var_ifEntry, 10,
	  { 1, 3, 6, 1, 2, 1, 2, 2, 1, 3 } };
    unsigned char *p;
    size_t var_len;
    WriteMethod *write_method;
    
    name[length] = index;
    length++;

    p = var_ifEntry(&ifType_variable,
		    name, &length,
		    1 /* exact */, &var_len, &write_method);
    if (!p)
	return 0;

    return *(int *)p;
}



static char *getName(int index)
{
    oid name[MAX_OID_LEN] = {1, 3, 6, 1, 2, 1, 2, 2, 1, 2};
    size_t length = 10;
    struct variable ifName_variable =
	{ 2, ASN_INTEGER, RONLY, var_ifEntry, 10,
	  { 1, 3, 6, 1, 2, 1, 2, 2, 1, 2 } };
    unsigned char *p;
    size_t var_len;
    WriteMethod *write_method;
    
    name[length] = index;
    length++;

    p = var_ifEntry(&ifName_variable,
		    name, &length,
		    1 /* exact */, &var_len, &write_method);
    if (!p)
	return NULL;

    return p;
}



static struct ip_tunnel_parm *getTunnelParm(char *ifname)
{
    struct ifreq ifrq;
    int fd;
    static struct ip_tunnel_parm parm;
    
    if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	return NULL;
    }

    memset(&parm, 0, sizeof(struct ip_tunnel_parm));
    strcpy (ifrq.ifr_name, ifname);
    ifrq.ifr_ifru.ifru_data = (void*)&parm;
    if (ioctl(fd, SIOCGETTUNNEL, &ifrq) < 0) {
	/*
	 * try again with the last char of the device name cut off.
	 * it might have been a zero digit appended by the agent.
	 */
	ifrq.ifr_name[strlen(ifrq.ifr_name)-1] = 0;
	if (ioctl(fd, SIOCGETTUNNEL, &ifrq) < 0) {
	    close(fd);
	    return NULL;
	}
	ifname[strlen(ifname)-1] = 0;
    }
    
    close(fd);

    return &parm;
}



int setTunnelParm(char *ifname, struct ip_tunnel_parm *parm)
{
    struct ifreq ifrq;
    int fd;
    int err;
    
    if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	return -1;
    }

    strcpy (ifrq.ifr_name, ifname);
    ifrq.ifr_ifru.ifru_data = (void*)parm;
    err = ioctl(fd, SIOCCHGTUNNEL, &ifrq);
    close(fd);

    return err;
}



/*
 * update a struct tunnel. its index and ifname elements have to be set.
 */
static struct tunnel *updateTunnel(struct tunnel *tunnel)
{
    struct ip_tunnel_parm *parm;
    int fd;
    struct ifreq ifrq;

    /* NOTE: getTunnelParm() may adjust the passed ifname. */
    parm = getTunnelParm(tunnel->ifname);
    if (!parm) {
	DEBUGMSGTL(("tunnel",
		    "updateTunnel(): getTunnelParm(\"%s\") returned NULL\n",
		    tunnel->ifname));
	tunnel->active = 0;
	return NULL;
    }

    tunnel->active = 1;
    
    tunnel->local = parm->iph.saddr;
    tunnel->remote = parm->iph.daddr;

    if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	DEBUGMSGTL(("snmpd",
		    "socket open failure in updateTunnels()\n"));
	return NULL;
    } else {
	strcpy (ifrq.ifr_name, tunnel->ifname);
	if (ioctl(fd, SIOCGIFHWADDR, &ifrq) == 0)
	    switch (ifrq.ifr_hwaddr.sa_family) {
	    case ARPHRD_TUNNEL:
		tunnel->encaps = 2; break;; /* direct */
	    case ARPHRD_TUNNEL6:
		tunnel->encaps = 2; break;; /* direct */
	    case ARPHRD_IPGRE:
		tunnel->encaps = 3; break;; /* gre */
	    case ARPHRD_SIT:
		tunnel->encaps = 2; break;; /* direct */
	    default:
		tunnel->encaps = 1; /* other */
	    }
	close(fd);
    }

    tunnel->hoplimit = parm->iph.ttl;
    tunnel->security = 1;
    tunnel->tos = (parm->iph.tos & 1) ? -1 : parm->iph.tos;
    /* XXX: adjust tos mapping (kernel <-> TUNNEL-MIB::tunnelIfTOS) */

    return tunnel;
}



static void updateTunnels(void)
{
    static int max_index = 1;
    static struct tunnel *last_tunnel = NULL;
    struct tunnel *tunnel;
    char *ifname;
    int type;
    
    /* uptime the tunnels we have so far */
    for (tunnel = tunnels; tunnel; tunnel = tunnel->next) {
	DEBUGMSG(("tunnel",
		  "updateTunnels(): updating %s (index=%d)\n",
		  tunnel->ifname, tunnel->ifindex));
	updateTunnel(tunnel);
    }

    /* look for new tunnels */
    for (; max_index < 256; max_index++) {
	DEBUGMSG(("tunnel",
		  "updateTunnels(): looking for new index=%d\n", max_index));
	type = getType(max_index);
	if (type == 131) {
	    tunnel = (struct tunnel *)malloc(sizeof(struct tunnel));
	    if (!tunnel) continue;

	    tunnel->ifindex = max_index;
	    tunnel->id = 1;
	    
	    ifname = getName(max_index);
	    if (!ifname) {
		free(tunnel);
		continue;
	    }

	    tunnel->ifname = strdup(ifname);
	    if (!tunnel->ifname) {
		free(tunnel);
		continue;
	    }

	    if (!updateTunnel(tunnel)) {
		free(tunnel->ifname);
		free(tunnel);
		continue;
	    }

	    if (last_tunnel) last_tunnel->next = tunnel;
	    if (!tunnels) tunnels = last_tunnel = tunnel;
	    tunnel->next = NULL;
	    last_tunnel = tunnel;

	    DEBUGMSG(("tunnel",
		      "updateTunnels(): added %s (index=%d state=%d)\n",
		      tunnel->ifname, tunnel->ifindex, tunnel->active));
	}
	if (type == 0) break;
    }
}



static struct tunnel *getTunnelByIfIndex(int index)
{
    struct tunnel *tunnel;
    
    DEBUGMSG(("tunnel", "getTunnelByIfIndex(%d): ", index));
    
    for (tunnel = tunnels; tunnel; tunnel = tunnel->next) {
	if (tunnel->ifindex == index) {
	    if (!tunnel->active) break;
	    DEBUGMSG(("tunnel",
		      "%s (index=%d)\n", tunnel->ifname, tunnel->ifindex));
	    return tunnel;
	}
    }
    DEBUGMSG(("tunnel", "NONE\n"));
    return NULL;
}



static struct tunnel *getNextTunnelByIfIndex(int index)
{
    struct tunnel *tunnel;
    
    DEBUGMSG(("tunnel", "getNextTunnelByIfIndex(%d): ", index));

    for (tunnel = tunnels; tunnel; tunnel = tunnel->next) {
	if (tunnel->ifindex > index) {
	    if (!tunnel->active) continue;
	    DEBUGMSG(("tunnel",
		      "%s (index=%d)\n", tunnel->ifname, tunnel->ifindex));
	    return tunnel;
	}
    }
    DEBUGMSG(("tunnel", "NONE\n"));
    return NULL;
}



static void fillConfigOid(oid *name, struct tunnel *tunnel)
{
    name[0] = ((unsigned char *)&tunnel->local)[0];
    name[1] = ((unsigned char *)&tunnel->local)[1];
    name[2] = ((unsigned char *)&tunnel->local)[2];
    name[3] = ((unsigned char *)&tunnel->local)[3];
    name[4] = ((unsigned char *)&tunnel->remote)[0];
    name[5] = ((unsigned char *)&tunnel->remote)[1];
    name[6] = ((unsigned char *)&tunnel->remote)[2];
    name[7] = ((unsigned char *)&tunnel->remote)[3];
    name[8] = tunnel->encaps;
    name[9] = tunnel->id;
    DEBUGMSGOID(("tunnel", name, 10));
}



static struct tunnel *getTunnelByConfigOid(oid *name, size_t *length)
{
    struct tunnel *tunnel;
    oid tname[4 + 4 + 1 + 1];

    DEBUGMSG(("tunnel", "getTunnelByConfigOid(): "));
    
    for (tunnel = tunnels; tunnel; tunnel = tunnel->next) {
	fillConfigOid(tname, tunnel);
	if (!snmp_oid_compare(tname, 4 + 4 + 1 + 1,
			      &name[tunnel_len + 3],
			      (*length) - tunnel_len - 3)) {
	    if (!tunnel->active) break;
	    DEBUGMSG(("tunnel",
		      "%s (index=%d)\n", tunnel->ifname, tunnel->ifindex));
	    return tunnel;
	}
    }
    DEBUGMSG(("tunnel", "NONE\n"));
    return NULL;
}



static struct tunnel *getNextTunnelByConfigOid(oid *name, size_t *length)
{
    struct tunnel *tunnel, *last_tunnel;
    oid tname[10], last_tname[10];

    DEBUGMSG(("tunnel", "getNextTunnelByConfigOid("));
    DEBUGMSGOID(("tunnel", name, *length));
    DEBUGMSG(("tunnel", "): "));

    last_tunnel = NULL;
    for (tunnel = tunnels; tunnel; tunnel = tunnel->next) {
	if (!tunnel->active) continue;
	fillConfigOid(tname, tunnel);
	if (snmp_oid_compare(tname, 10,
			     &name[tunnel_len + 3],
			     (*length) - tunnel_len - 3) > 0) {
	    if (!last_tunnel) {
		last_tunnel = tunnel;
		memcpy((char *)last_tname, (char *)tname, 10 * sizeof(oid));
	    } else {
		if (snmp_oid_compare(tname, 10, last_tname, 10) < 0) {
		    last_tunnel = tunnel;
		    memcpy((char *)last_tname, (char *)tname,
			   10 * sizeof(oid));
		}
	    }
	}
    }

    if (last_tunnel) {
	DEBUGMSG(("tunnel",
		  "%s (index=%d)\n",
		  last_tunnel->ifname, last_tunnel->ifindex));
    } else {
	DEBUGMSG(("tunnel", "NONE\n"));
    }
    
    return last_tunnel;
}



static int writeLocalAddress(int action, unsigned char *var_val,
			     unsigned char var_val_type, size_t var_val_len,
			     unsigned char *statP, oid *name, size_t name_len)
{
    static struct tunnel *tunnel;
    struct ip_tunnel_parm *parm;
    
    switch (action) {
    case RESERVE1:
	if (var_val_type != ASN_IPADDRESS) {
	    return SNMP_ERR_WRONGTYPE;
	}
	if (var_val_len != 4) {
	    return SNMP_ERR_WRONGLENGTH;
	}
    case RESERVE2:
	tunnel = getTunnelByIfIndex((int) name[name_len - 1]);
	if (!tunnel) {
	    return SNMP_ERR_NOSUCHNAME;
	}
    case FREE:
	break;
    case ACTION:
	break;
    case UNDO:
	break;
    case COMMIT:
	if (!tunnel) {
	    return SNMP_ERR_NOSUCHNAME;
	}
	parm = getTunnelParm(tunnel->ifname);
	if (!parm) {
	    return SNMP_ERR_NOSUCHNAME;
	}
	parm->iph.saddr = *(unsigned long *)var_val;
	setTunnelParm(tunnel->ifname, parm);
	break;
    }

    return SNMP_ERR_NOERROR;
}



static int writeRemoteAddress(int action, unsigned char *var_val,
			      unsigned char var_val_type, size_t var_val_len,
			      unsigned char *statP, oid *name, size_t name_len)
{
    static struct tunnel *tunnel;
    struct ip_tunnel_parm *parm;
    
    switch (action) {
    case RESERVE1:
	if (var_val_type != ASN_IPADDRESS) {
	    return SNMP_ERR_WRONGTYPE;
	}
	if (var_val_len != 4) {
	    return SNMP_ERR_WRONGLENGTH;
	}
    case RESERVE2:
	tunnel = getTunnelByIfIndex((int) name[name_len - 1]);
	if (!tunnel) {
	    return SNMP_ERR_NOSUCHNAME;
	}
    case FREE:
	break;
    case ACTION:
	break;
    case UNDO:
	break;
    case COMMIT:
	if (!tunnel) {
	    return SNMP_ERR_NOSUCHNAME;
	}
	parm = getTunnelParm(tunnel->ifname);
	if (!parm) {
	    return SNMP_ERR_NOSUCHNAME;
	}
	parm->iph.daddr = *(unsigned long *)var_val;
	setTunnelParm(tunnel->ifname, parm);
	break;
    }

    return SNMP_ERR_NOERROR;
}



static int writeHopLimit(int action, unsigned char *var_val,
			 unsigned char var_val_type, size_t var_val_len,
			 unsigned char *statP, oid *name, size_t name_len)
{
    static struct tunnel *tunnel;
    struct ip_tunnel_parm *parm;
    
    switch (action) {
    case RESERVE1:
	if (var_val_type != ASN_INTEGER) {
	    return SNMP_ERR_WRONGTYPE;
	}
	if (var_val_len > sizeof(long)) {
	    return SNMP_ERR_WRONGLENGTH;
	}
    case RESERVE2:
	tunnel = getTunnelByIfIndex((int) name[name_len - 1]);
	if (!tunnel) {
	    return SNMP_ERR_NOSUCHNAME;
	}
    case FREE:
	break;
    case ACTION:
	break;
    case UNDO:
	break;
    case COMMIT:
	if (!tunnel) {
	    return SNMP_ERR_NOSUCHNAME;
	}
	parm = getTunnelParm(tunnel->ifname);
	if (!parm) {
	    return SNMP_ERR_NOSUCHNAME;
	}
	parm->iph.ttl = *(long *)var_val;
	setTunnelParm(tunnel->ifname, parm);
	break;
    }

    return SNMP_ERR_NOERROR;
}



static int writeTOS(int action, unsigned char *var_val,
		    unsigned char var_val_type, size_t var_val_len,
		    unsigned char *statP, oid *name, size_t name_len)
{
    static struct tunnel *tunnel;
    struct ip_tunnel_parm *parm;
    
    switch (action) {
    case RESERVE1:
	if (var_val_type != ASN_INTEGER) {
	    return SNMP_ERR_WRONGTYPE;
	}
	if (var_val_len > sizeof(long)) {
	    return SNMP_ERR_WRONGLENGTH;
	}
    case RESERVE2:
	tunnel = getTunnelByIfIndex((int) name[name_len - 1]);
	if (!tunnel) {
	    return SNMP_ERR_NOSUCHNAME;
	}
    case FREE:
	break;
    case ACTION:
	break;
    case UNDO:
	break;
    case COMMIT:
	if (!tunnel) {
	    return SNMP_ERR_NOSUCHNAME;
	}
	parm = getTunnelParm(tunnel->ifname);
	if (!parm) {
	    return SNMP_ERR_NOSUCHNAME;
	}
	/* this does not cover all meaningful values: */
	parm->iph.tos = (*(long *)var_val == -1) ? 1 : *(long *)var_val;
	setTunnelParm(tunnel->ifname, parm);
	break;
    }

    return SNMP_ERR_NOERROR;
}



unsigned char *var_tunnelIfEntry(struct variable *vp,
				 oid *name, size_t *length,
				 int exact, size_t *var_len,
				 WriteMethod **write_method) {
    static unsigned long ret_addr;
    static long ret_int;
    struct tunnel *tunnel;
    
    DEBUGMSGTL(("tunnel", "var_tunnelIfEntry: "));
    DEBUGMSGOID(("tunnel", name, *length));
    DEBUGMSG(("tunnel"," %d\n", exact));

    updateTunnels();
    
    if (exact) {
	if (*length != tunnel_len + 3 + 1) {
	    return NULL;
	}
	tunnel = getTunnelByIfIndex((int) name[*length - 1]);
    } else {
	if ((*length) < tunnel_len) {
	    memcpy((char *)name, (char *)tunnel_variables_oid,
		   tunnel_len * sizeof(oid));
	}
	if ((*length) < tunnel_len + 1) { name[tunnel_len] = 1; }
	if ((*length) < tunnel_len + 2) { name[tunnel_len + 1] = 1; }
	if ((*length) < tunnel_len + 3) { name[tunnel_len + 2] = 1; }
	if ((*length) < tunnel_len + 4) { name[tunnel_len + 3] = 0; }
	*length = tunnel_len + 4;

	tunnel = getNextTunnelByIfIndex(name[*length - 1]);
	if (!tunnel) {
	    /* end of column, continue with first row of next column */
	    tunnel = tunnels; 
	    name[tunnel_len + 2]++;
	    if (name[tunnel_len + 2] > 6) {
		/* there is no next column */
		return NULL;
	    }
	    if (!tunnel) {
		/* there is no (next) row */
		return NULL;
	    }
	}
    }

    if (!tunnel) {
	return NULL;
    }
	
    name[*length - 1] = tunnel->ifindex;
	    
    DEBUGMSGTL(("tunnel", "var_tunnelIfEntry: using"));
    DEBUGMSGOID(("tunnel", name, *length));
    DEBUGMSG(("tunnel", "\n"));
    
    switch (name[tunnel_len + 2]) {
    case 1: /* tunnelIfLocalAddress */
	ret_addr = tunnel->local;
	*var_len = 4;
	vp->type = ASN_IPADDRESS;
	*write_method = writeLocalAddress;
	return (u_char *) &ret_addr;
    case 2: /* tunnelIfRemoteAddress */
	ret_addr = tunnel->remote;
	*var_len = 4;
	vp->type = ASN_IPADDRESS;
	*write_method = writeRemoteAddress;
	return (u_char *) &ret_addr;
    case 3: /* tunnelIfEncapsMethod */
	ret_int = tunnel->encaps;
	*var_len = sizeof(ret_int);
	vp->type = ASN_INTEGER;
	return (u_char *) &ret_int;
    case 4: /* tunnelIfHopLimit */
	ret_int = tunnel->hoplimit;
	*var_len = sizeof(ret_int);
	vp->type = ASN_INTEGER;
	*write_method = writeHopLimit;
	return (u_char *) &ret_int;
    case 5: /* tunnelIfSecurity */
	ret_int = tunnel->security;
	*var_len = sizeof(ret_int);
	vp->type = ASN_INTEGER;
	return (u_char *) &ret_int;
    case 6: /* tunnelIfTOS */
	ret_int = tunnel->tos;
	*var_len = sizeof(ret_int);
	vp->type = ASN_INTEGER;
	*write_method = writeTOS;
	return (u_char *) &ret_int;
    default:
	return 0;
    }

    return NULL;
}



unsigned char *var_tunnelConfigEntry(struct variable *vp,
				     oid *name, size_t *length,
				     int exact, size_t *var_len,
				     WriteMethod **write_method) {
    static long ret_int;
    struct tunnel *tunnel;
    int i;
    
    DEBUGMSGTL(("tunnel", "var_tunnelConfigEntry: "));
    DEBUGMSGOID(("tunnel", name, *length));
    DEBUGMSG(("tunnel"," %d\n", exact));

    updateTunnels();
    
    if (exact) {
	if (*length != tunnel_len + 3 + 4 + 4 + 1 + 1) {
	    return NULL;
	}
	tunnel = getTunnelByConfigOid(name, length);
    } else {
	if (snmp_oid_compare(name, *length,
			     tunnel_configEntry_oid,
			     tunnel_configEntry_len) < 0) {
	    *length = 0;
	}
	if ((*length) < tunnel_len) {
	    memcpy((char *)name, (char *)tunnel_variables_oid,
		   tunnel_len * sizeof(oid));
	}
	if ((*length) < tunnel_len + 1) { name[tunnel_len] = 2; }
	if ((*length) < tunnel_len + 2) { name[tunnel_len + 1] = 1; }
	if ((*length) < tunnel_len + 3) { name[tunnel_len + 2] = 5; }
	for (i = MAX(*length, tunnel_len + 3);
	     i < tunnel_len + 3 + 4 + 4 + 1 + 1; i++) {
	    name[i] = 0;
	}
	*length = tunnel_len + 3 + 4 + 4 + 1 + 1;
	tunnel = getNextTunnelByConfigOid(name, length);
	if (!tunnel) {
	    /* end of column, continue with first row of next column */
	    tunnel = tunnels; 
	    name[tunnel_len + 2]++;
	    if (name[tunnel_len + 2] > 6) {
		/* there is no next column */
		return NULL;
	    }
	    if (!tunnel) {
		/* there is no (next) row */
		return NULL;
	    }
	}
    }

    if (!tunnel) {
	return NULL;
    }

    fillConfigOid(&name[tunnel_len + 3], tunnel);
    
    DEBUGMSGTL(("tunnel", "var_tunnelConfigEntry: using "));
    DEBUGMSGOID(("tunnel", name, *length));
    DEBUGMSG(("tunnel", "\n"));
    
    switch (name[tunnel_len + 2]) {
    case 5: /* tunnelConfigIfIndex */
	ret_int = tunnel->ifindex;
	*var_len = sizeof(ret_int);
	vp->type = ASN_INTEGER;
	return (u_char *) &ret_int;
    case 6: /* tunnelConfigStatus */
	ret_int = 1; /* active */
	*var_len = sizeof(ret_int);
	vp->type = ASN_INTEGER;
	return (u_char *) &ret_int;
    default:
	return 0;
    }

    return NULL;
}
