/*
 * GXSNMP -- An snmp mangament application
 * Copyright (C) 1998 Gregory McLean & Jochen Friedrich
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
 * This module implements an interface for writing SMUX based SNMP subagents
 * with glib/gtk/gnome applications.
 */

/* API:
 *
 * gxsnmp_smux = gxsnmp_smux_new()
 * Create new SMUX object.
 *  Create TCP Socket
 *  Create GIOUnix
 * gxsnmp_smux_connect(gxsnmp_smux, host, oid, len, password)
 * SMUX connect
 *  Connect TCP Socket
 *  Construct SMUX Open PDU
 *  Send PDU
 *  Wait for Reply (Sync)
 *  Return result
 * gxsnmp_smux_register(gxsnmp_smux, var, width, num, oid, len)
 * Register Subtree
 *  Construct SMUX Register PDU
 *  Send PDU
 *  Wait for Reply (Sync)
 *  Return result
 * gxsnmp_smux_trap(gxsnmp_smux, oid, len, variables)
 * Send SNMPv1 trap
 *  Construct SMUX Trap PDU
 *  Send PDU
 * gxsnmp_smux_close(gxsnmp_smux)
 * Cleanup
 *  Construct SMUX Close PDU
 *  Send PDU
 *  Wait for Reply (Sync)
 *  Close Socket
 */

#include <glib.h>
#include <gxsnmp_asn1.h>
#include <gxsnmp_smux.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

static GSList *treelist;

static void *
gxsnmp_oid_copy (void *dest, void *src, guint size)
{
  return memcpy (dest, src, size * sizeof (gulong));
}

static int
gxsnmp_oid_compare (gulong *o1, guint o1_len, gulong *o2, guint o2_len)
{
  guint i;

  for (i = 0; i < MIN (o1_len, o2_len); i++)
    {
      if (o1[i] < o2[i])
        return -1;
      else if (o1[i] > o2[i])
        return 1;
    }
  if (o1_len < o2_len)
    return -1;
  if (o1_len > o2_len)
    return 1;

  return 0;
}

static int
gxsnmp_oid_compare_part (gulong *o1, guint o1_len, gulong *o2, guint o2_len)
{
  guint i;

  for (i = 0; i < MIN (o1_len, o2_len); i++)
    {
      if (o1[i] < o2[i])
        return -1;
      else if (o1[i] > o2[i])
        return 1;
    }
  if (o1_len < o2_len)
    return -1;

  return 0;
}

#define GXSNMP_SNMP_IPA    0
#define GXSNMP_SNMP_CNT    1
#define GXSNMP_SNMP_GGE    2
#define GXSNMP_SNMP_TIT    3
#define GXSNMP_SNMP_OPQ    4
#define GXSNMP_SNMP_C64    6

#define GXSNMP_SERR_NSO    0
#define GXSNMP_SERR_NSI    1
#define GXSNMP_SERR_EOM    2

typedef struct _SNMP_CNV SNMP_CNV;

struct _SNMP_CNV
{
  guint class;
  guint tag;
  gint  syntax;
};

static SNMP_CNV SnmpCnv [] =
{
  {GXSNMP_ASN1_UNI, GXSNMP_ASN1_NUL, SNMP_NULL},
  {GXSNMP_ASN1_UNI, GXSNMP_ASN1_INT, SNMP_INTEGER},
  {GXSNMP_ASN1_UNI, GXSNMP_ASN1_OTS, SNMP_OCTETSTR},
  {GXSNMP_ASN1_UNI, GXSNMP_ASN1_OTS, SNMP_DISPLAYSTR},
  {GXSNMP_ASN1_UNI, GXSNMP_ASN1_OJI, SNMP_OBJECTID},
  {GXSNMP_ASN1_APL, GXSNMP_SNMP_IPA, SNMP_IPADDR},
  {GXSNMP_ASN1_APL, GXSNMP_SNMP_CNT, SNMP_COUNTER},
  {GXSNMP_ASN1_APL, GXSNMP_SNMP_GGE, SNMP_GAUGE},
  {GXSNMP_ASN1_APL, GXSNMP_SNMP_TIT, SNMP_TIMETICKS},
  {GXSNMP_ASN1_APL, GXSNMP_SNMP_OPQ, SNMP_OPAQUE},
  {GXSNMP_ASN1_UNI, GXSNMP_ASN1_BTS, SNMP_BITSTR},
  {GXSNMP_ASN1_APL, GXSNMP_SNMP_C64, SNMP_COUNTER64},
  {GXSNMP_ASN1_CTX, GXSNMP_SERR_NSO, SNMP_NOSUCHOBJECT},
  {GXSNMP_ASN1_CTX, GXSNMP_SERR_NSI, SNMP_NOSUCHINSTANCE},
  {GXSNMP_ASN1_CTX, GXSNMP_SERR_EOM, SNMP_ENDOFMIBVIEW},
  {0,       0,       -1}
};

static gboolean
gxsnmp_syntax2tag_cls (GxsnmpAsn1Tag *tag, GxsnmpAsn1Class *cls, gint syntax)
{
  SNMP_CNV *cnv;

  cnv = SnmpCnv;
  while (cnv->syntax != -1)
    {
      if (cnv->syntax == syntax)
        {
          *tag = (GxsnmpAsn1Tag) cnv->tag;
          *cls = (GxsnmpAsn1Class) cnv->class;
          return TRUE;
        }
      cnv++;
    }
  return FALSE;
}

gboolean
gxsnmp_header_generic (struct gxsnmp_variable *v, gulong *name, gint *length,
	gboolean exact, guint *var_len, GxsnmpPutVar **write_method)
{
  gulong fulloid[1024];
  int ret;

  gxsnmp_oid_copy (fulloid, v->name, v->namelen);
  fulloid[v->namelen] = 0;
  /* Check against full instance. */
  ret = gxsnmp_oid_compare (name, *length, fulloid, v->namelen + 1);

  /* Check single instance. */
  if ((exact && (ret != 0)) || (!exact && (ret >= 0)))
        return FALSE;

  /* In case of getnext, fill in full instance. */
  memcpy (name, fulloid, (v->namelen + 1) * sizeof (gulong));
  *length = v->namelen + 1;

  memcpy (name, fulloid, (v->namelen + 1) * sizeof (gulong));
  *length = v->namelen + 1;

  *write_method = NULL;
  *var_len = sizeof(gulong);    /* default to 'long' results */

  return TRUE;
}

static gboolean
gxsnmp_getresp_send (GxsnmpSmux *smux, gulong objid[], guint objid_len,
	glong reqid, glong errstat, glong errindex, guchar val_type, void *arg,
	guint arg_len)
{
  GxsnmpAsn1 *asn1;
  guchar buf[32768];
  guchar *eov, *eop;
  guchar *eoc, *end;
  guchar *ptr;
  guint  len, len1;
  GxsnmpAsn1Class cls;
  GxsnmpAsn1Tag tag;

  len = sizeof(buf);
  asn1 = gxsnmp_asn1_new(buf, len, GXSNMP_ASN1_ENC);

  if (!gxsnmp_asn1_eoc_encode (asn1, &eop))
    return FALSE;
  if (!gxsnmp_asn1_eoc_encode (asn1, &eov))
    return FALSE;
  if (!gxsnmp_asn1_eoc_encode (asn1, &eoc))
    return FALSE;
  switch (val_type)
    {
      case SNMP_INTEGER:
        if (!gxsnmp_asn1_long_encode (asn1, &end, *(glong *)arg))
          return FALSE;
        break;
      case SNMP_OCTETSTR:
      case SNMP_OPAQUE:
        if (!gxsnmp_asn1_octets_encode (asn1, &end, arg, arg_len))
          return FALSE;
        break;
      case SNMP_NULL:
      case SNMP_NOSUCHOBJECT:
      case SNMP_NOSUCHINSTANCE:
      case SNMP_ENDOFMIBVIEW:
        if (!gxsnmp_asn1_null_encode (asn1, &end))
          return FALSE;
        break;
      case SNMP_OBJECTID:
        if (!gxsnmp_asn1_oid_encode (asn1, &end, arg, arg_len))
          return FALSE;
        break;
      case SNMP_IPADDR:
        if (!gxsnmp_asn1_octets_encode (asn1, &end, arg, arg_len))
          return FALSE;
        break;
      case SNMP_COUNTER:
      case SNMP_GAUGE:
      case SNMP_TIMETICKS:
        if (!gxsnmp_asn1_ulong_encode (asn1, &end, *(gulong *)arg))
          return FALSE;
        break;
      default:
        return FALSE;
    }
  if (!gxsnmp_syntax2tag_cls (&tag, &cls, val_type))
    return FALSE;
  if (!gxsnmp_asn1_header_encode (asn1, end, cls, GXSNMP_ASN1_PRI, tag))
    return FALSE;
  if (!gxsnmp_asn1_oid_encode (asn1, &end, objid, objid_len))
    return FALSE;
  if (!gxsnmp_asn1_header_encode (asn1, end, GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI,
    GXSNMP_ASN1_OJI))
    return FALSE;
  if (!gxsnmp_asn1_header_encode (asn1, eoc, GXSNMP_ASN1_UNI, GXSNMP_ASN1_CON,
    GXSNMP_ASN1_SEQ))
    return FALSE;
  if (!gxsnmp_asn1_header_encode (asn1, eov, GXSNMP_ASN1_UNI, GXSNMP_ASN1_CON,
    GXSNMP_ASN1_SEQ))
    return FALSE;
  if (!gxsnmp_asn1_long_encode (asn1, &end, errindex))
    return FALSE;
  if (!gxsnmp_asn1_header_encode (asn1, end, GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI,
    GXSNMP_ASN1_INT))
    return FALSE;
  if (!gxsnmp_asn1_long_encode (asn1, &end, errstat))
    return FALSE;
  if (!gxsnmp_asn1_header_encode (asn1, end, GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI,
    GXSNMP_ASN1_INT))
    return FALSE;
  if (!gxsnmp_asn1_long_encode (asn1, &end, reqid))
    return FALSE;
  if (!gxsnmp_asn1_header_encode (asn1, end, GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI,
    GXSNMP_ASN1_INT))
    return FALSE;
  if (!gxsnmp_asn1_header_encode (asn1, eop, GXSNMP_ASN1_CTX, GXSNMP_ASN1_CON,
    GXSNMP_SNMP_GETRSP))
    return FALSE;

  gxsnmp_asn1_destroy(asn1, &ptr, &len);
  g_io_channel_write(smux->channel, ptr, len, &len1);
  return TRUE;
}

static gboolean
gxsnmp_var (GxsnmpAsn1 *asn1, gulong *objid, guint *objid_len)
{
  guchar *eov, *eoc, *end;
  gulong *oid;
  GxsnmpAsn1Class cls;
  GxsnmpAsn1Constructed con;
  GxsnmpAsn1Tag tag;

  if (!gxsnmp_asn1_header_decode (asn1, &eov, &cls, &con, &tag))
    return FALSE;
  if (cls != GXSNMP_ASN1_UNI || con != GXSNMP_ASN1_CON ||
    tag != GXSNMP_ASN1_SEQ)
    return FALSE;
  if (gxsnmp_asn1_eoc_decode (asn1, eov))
    return FALSE;

  if (!gxsnmp_asn1_header_decode (asn1, &eoc, &cls, &con, &tag))
    return FALSE;
  if (cls != GXSNMP_ASN1_UNI || con != GXSNMP_ASN1_CON ||
    tag != GXSNMP_ASN1_SEQ)
    return FALSE;
  if (!gxsnmp_asn1_header_decode (asn1, &end, &cls, &con, &tag))
    return FALSE;
  if (cls != GXSNMP_ASN1_UNI || con != GXSNMP_ASN1_PRI ||
    tag != GXSNMP_ASN1_OJI)
    return FALSE;
  if (!gxsnmp_asn1_oid_decode (asn1, end, &oid, objid_len))
    return FALSE;
  gxsnmp_oid_copy(objid, oid, *objid_len);
  g_free(oid);
  if (!gxsnmp_asn1_header_decode (asn1, &end, &cls, &con, &tag))
    return FALSE;
  if (con != GXSNMP_ASN1_PRI)
    return FALSE;
#if 0 /* Currently, SET is not supported */
  switch (type)
    {
      case SNMP_INTEGER:
        len = sizeof(glong);
        if (!g_asn1_long_decode (asn1, end, &l))
          return FALSE;
        *obj = g_malloc(sizeof(SNMP_OBJECT) + len);
        (*obj)->syntax.l[0] = l;
        break;
      case SNMP_OCTETSTR:
      case SNMP_OPAQUE:
        if (!g_asn1_octets_decode (asn1, end, &p, &len))
          return FALSE;
        *obj = g_malloc(sizeof(SNMP_OBJECT) + len);
        memcpy((*obj)->syntax.c, p, len);
        g_free(p);
        break;
      case SNMP_NULL:
      case SNMP_NOSUCHOBJECT:
      case SNMP_NOSUCHINSTANCE:
      case SNMP_ENDOFMIBVIEW:
        len = 0;
        *obj = g_malloc(sizeof(SNMP_OBJECT));
        if (!g_asn1_null_decode (asn1, end))
          {
            g_free(*obj);
            *obj = NULL;
            return FALSE;
          }
        break;
      case SNMP_OBJECTID:
        if (!g_asn1_oid_decode (asn1, end, (gulong **)&lp, &len))
          return FALSE;
        len *= sizeof(gulong);
        *obj = g_malloc(sizeof(SNMP_OBJECT) + len);
        memcpy((*obj)->syntax.ul, lp, len);
        g_free(lp);
        break;
      case SNMP_IPADDR:
        if (!g_asn1_octets_decode (asn1, end, &p, &len))
          g_free(id);
        if ((len != 4) && (len != 16))
          {
            g_free(p);
            return FALSE;
          }
        *obj = g_malloc(sizeof(SNMP_OBJECT) + len);
        memcpy((*obj)->syntax.uc, p, len);
        g_free(p);
        break;
      case SNMP_COUNTER:
      case SNMP_GAUGE:
      case SNMP_TIMETICKS:
        len = sizeof(gulong);
        if (!g_asn1_ulong_decode (asn1, end, &ul))
          return FALSE;
        *obj = g_malloc(sizeof(SNMP_OBJECT) + len);
        (*obj)->syntax.ul[0] = ul;
        break;
      default:
        g_snmpErrStatus = SNMP_BADVALUE;
        return FALSE;
    }
  (*obj)->syntax_len = len;
  (*obj)->type       = type;
  (*obj)->id         = id;
  (*obj)->id_len     = idlen;

  if (!g_asn1_eoc_decode (asn1, eoc))
    {
      g_free(*obj);
      *obj = NULL;
      return FALSE;
    }
#endif
  if (!gxsnmp_asn1_eoc_decode (asn1, eov))
    return FALSE;
  return TRUE;
}

/* exact version. */
static int
gxsnmp_get (gulong *reqid, guint *reqid_len, 
          guchar *val_type, void **val, guint *val_len)
{
  int i, j;
  struct gxsnmp_subtree *subtree;
  struct gxsnmp_variable *v;
  gint subresult;
  gulong *suffix;
  guint suffix_len;
  gint result;
  GxsnmpPutVar *write_method=NULL;

  /* Check */
  for (i = 0; i < g_slist_length(treelist); i++)
    {
      subtree = g_slist_nth_data (treelist, i);
      subresult = gxsnmp_oid_compare_part (reqid, *reqid_len,
                                    subtree->name, subtree->name_len);

      /* Subtree matched. */
      if (subresult == 0)
        {
          /* Prepare suffix. */
          suffix = reqid + subtree->name_len;
          suffix_len = *reqid_len - subtree->name_len;
          result = subresult;

          /* Check variables. */
          for (j = 0; j < subtree->variables_num; j++)
            {
              v = &subtree->variables[j];

              /* Always check suffix */
              result = gxsnmp_oid_compare_part (suffix, suffix_len,
                                         v->name, v->namelen);

              /* This is exact match so result must be zero. */
              if (result == 0)
                {
                  *val = (*v->find) (v, suffix, &suffix_len, TRUE,
                    val_len, &write_method);

                  /* There is no instance. */
                  if (*val == NULL)
                    return SNMP_NOSUCHINSTANCE;

                  /* Call is suceed. */
                  *val_type = v->type;

                  return 0;
                }

              /* If above execution is failed or oid is small (so
                 there is no further match). */
              if (result < 0)
                return SNMP_NOSUCHOBJECT;
            }
        }
    }
  return SNMP_NOSUCHOBJECT;
}

static int
gxsnmp_getnext (gulong *reqid, guint *reqid_len, 
                 guchar *val_type, void **val, guint *val_len)
{
  int i, j;
  guint save[1024];
  guint savelen = 0;
  struct gxsnmp_subtree *subtree;
  struct gxsnmp_variable *v;
  int subresult;
  gulong *suffix;
  guint suffix_len;
  int result;
  GxsnmpPutVar *write_method=NULL;

  /* Save incoming request. */
  gxsnmp_oid_copy (save, reqid, *reqid_len);
  savelen = *reqid_len;

  /* Check */
  for (i = 0; i < g_slist_length (treelist); i++)
    {
      subtree = g_slist_nth_data (treelist, i);

      subresult = gxsnmp_oid_compare_part (reqid, *reqid_len,
                                    subtree->name, subtree->name_len);

      /* If request is in the tree. The agent has to make sure we
         only receive requests we have registered for. */
      if (subresult == 0)
        {

          /* Prepare suffix. */
          suffix = reqid + subtree->name_len;
          suffix_len = *reqid_len - subtree->name_len;
          result = subresult;

          for (j = 0; j < subtree->variables_num; j++)
            {
              v = &subtree->variables[j];

              /* Next then check result >= 0. */
              if (result >= 0)
                result = gxsnmp_oid_compare_part (suffix, suffix_len,
                                           v->name, v->namelen);

              if (result <= 0)
                {
                  if(result<0)
                    {
                      gxsnmp_oid_copy(suffix, v->name, v->namelen);
                      suffix_len = v->namelen;
                    }
                  *val = (*v->find) (v, suffix, &suffix_len, FALSE,
                    val_len, &write_method);
                  *reqid_len = suffix_len + subtree->name_len;
                  if (*val)
                    {
                      *val_type = v->type;
                      return 0;
                    }
                }
            }
        }
    }
  memcpy (reqid, save, savelen * sizeof(gulong));
  *reqid_len = savelen;

  return SNMP_NOSUCHOBJECT;
}

static gboolean
gxsnmp_parse_get_header (GxsnmpAsn1 *asn1, glong *reqid)
{
  glong errstat;
  glong errindex;
  guchar     *eoc;
  GxsnmpAsn1Class cls;
  GxsnmpAsn1Constructed con;
  GxsnmpAsn1Tag tag;

  /* Request ID. */
  if (!gxsnmp_asn1_header_decode(asn1, &eoc, &cls, &con, &tag))
    return FALSE;
  if (cls != GXSNMP_ASN1_UNI || con != GXSNMP_ASN1_PRI ||
    tag != GXSNMP_ASN1_INT)
    return FALSE;
  if (!gxsnmp_asn1_long_decode (asn1, eoc, reqid))
    return FALSE;

  /* Error status. */
  if (!gxsnmp_asn1_header_decode(asn1, &eoc, &cls, &con, &tag))
    return FALSE;
  if (cls != GXSNMP_ASN1_UNI || con != GXSNMP_ASN1_PRI || 
    tag != GXSNMP_ASN1_INT)
    return FALSE;
  if (!gxsnmp_asn1_long_decode (asn1, eoc, &errstat))
    return FALSE;

  /* Error index. */
  if (!gxsnmp_asn1_header_decode(asn1, &eoc, &cls, &con, &tag))
    return FALSE;
  if (cls != GXSNMP_ASN1_UNI || con != GXSNMP_ASN1_PRI || 
    tag != GXSNMP_ASN1_INT)
    return FALSE;
  if (!gxsnmp_asn1_long_decode (asn1, eoc, &errindex))
    return FALSE;

  return TRUE;
}

static gboolean
gxsnmp_parse_get (GxsnmpSmux *smux, GxsnmpAsn1 *asn1, guchar *eoc,
	gboolean exact)
{
  long reqid;
  gulong oid[1024];
  guint oid_len;
  u_char val_type;
  void *val;
  size_t val_len;
  int ret;

  /* Parse GET message header. */
  if (!gxsnmp_parse_get_header (asn1, &reqid))
    return FALSE;

  /* Parse GET message object ID. */
  if (!gxsnmp_var (asn1, oid, &oid_len))
    return FALSE;

  /* Traditional getstatptr. */
  if (exact)
    ret = gxsnmp_get (oid, &oid_len, &val_type, &val, &val_len);
  else
    ret = gxsnmp_getnext (oid, &oid_len, &val_type, &val, &val_len);

  /* Return result. */
  if (ret == 0)
    return gxsnmp_getresp_send (smux, oid, oid_len, reqid, 0, 0, val_type,
      val, val_len);
  else
    return gxsnmp_getresp_send (smux, oid, oid_len, reqid, ret, 1, 
      SNMP_NULL, NULL, 0);
}

static gboolean
gxsnmp_smux_handler(GIOChannel *source, GIOCondition condition, gpointer data)
{
  GxsnmpSmux *self;
  guchar buffer[1500];
  guint       len;
  GxsnmpAsn1 *asn1;
  guchar     *eoc, *buf;
  GxsnmpAsn1Class cls;
  GxsnmpAsn1Constructed con;
  GxsnmpAsn1Tag tag;

  self = (GxsnmpSmux *)data;
  g_io_channel_read(self->channel, buffer, sizeof(buffer), &len);
  asn1 = gxsnmp_asn1_new(buffer, len, GXSNMP_ASN1_DEC);
  gxsnmp_asn1_header_decode(asn1, &eoc, &cls, &con, &tag);

  if ((cls == GXSNMP_ASN1_APL) && (con == GXSNMP_ASN1_CON) && 
      ((GxsnmpSmuxPdu)tag == GXSNMP_SMUX_OPEN))
    {
      g_warning("SMUX Open received from agent");
      return TRUE;
    }
  if ((cls == GXSNMP_ASN1_APL) && (con == GXSNMP_ASN1_PRI) && 
      ((GxsnmpSmuxPdu)tag == GXSNMP_SMUX_CLOSE))
    {
      g_warning("SMUX Close received from agent");
      return TRUE;
    }
  if ((cls == GXSNMP_ASN1_APL) && (con == GXSNMP_ASN1_CON) && 
      ((GxsnmpSmuxPdu)tag == GXSNMP_SMUX_RREQ))
    {
      g_warning("SMUX Registration Request received from agent");
      return TRUE;
    }
  if ((cls == GXSNMP_ASN1_APL) && (con == GXSNMP_ASN1_PRI) && 
      ((GxsnmpSmuxPdu)tag == GXSNMP_SMUX_RRSP))
    {
      g_warning("SMUX Registration Response received from agent");
      return TRUE;
    }
  if ((cls == GXSNMP_ASN1_APL) && (con == GXSNMP_ASN1_PRI) && 
      ((GxsnmpSmuxPdu)tag == GXSNMP_SMUX_SOUT))
    {
      g_warning("SMUX Commit or Rollback received from agent");
      return TRUE;
    }
  if ((cls == GXSNMP_ASN1_CTX) && (con == GXSNMP_ASN1_CON) && 
      (tag == 0))
    {
      gxsnmp_parse_get(self, asn1, eoc, TRUE); /* GET */
      return TRUE;
    }
  if ((cls == GXSNMP_ASN1_CTX) && (con == GXSNMP_ASN1_CON) && 
      (tag == 1))
    {
      gxsnmp_parse_get(self, asn1, eoc, FALSE); /* GETNEXT */
      return TRUE;
    }
  if ((cls == GXSNMP_ASN1_CTX) && (con == GXSNMP_ASN1_CON) && 
      (tag == 2))
    {
      g_warning("SNMP GET Response received from agent");
      return TRUE;
    }
  if ((cls == GXSNMP_ASN1_CTX) && (con == GXSNMP_ASN1_CON) && 
      (tag == 3))
    {
      g_warning("SNMP SET received from agent");
      return TRUE;
    }
    
  gxsnmp_asn1_destroy(asn1, &buf, &len);

  return TRUE;
}

GxsnmpSmux* 
gxsnmp_smux_new ()
{
  GxsnmpSmux *self;
  int fd;

  self = g_new(GxsnmpSmux, 1);
  self->registration = NULL;
  fd = socket(AF_INET, SOCK_STREAM, 0);
  self->channel = g_io_channel_unix_new(fd);
  g_io_add_watch(self->channel, G_IO_IN | G_IO_PRI, gxsnmp_smux_handler, self);
  self->error = GXSNMP_SMUX_NOERROR;
  return self;
}

void 
gxsnmp_smux_destroy (GxsnmpSmux *smux)
{
  guchar      buffer[1500];
  guchar     *buf;
  guint       len, len1;
  GxsnmpAsn1 *asn1;
  guchar     *eoc;

  asn1 = gxsnmp_asn1_new(buffer, sizeof(buffer), GXSNMP_ASN1_ENC);
  gxsnmp_asn1_int_encode(asn1, &eoc, 0); /* Going down */
  gxsnmp_asn1_header_encode(asn1, eoc, GXSNMP_ASN1_APL, GXSNMP_ASN1_PRI, 
	GXSNMP_SMUX_CLOSE);
  gxsnmp_asn1_destroy(asn1, &buf, &len);
  g_io_channel_write(smux->channel, buf, len, &len1);
  g_io_channel_close(smux->channel);
  g_free(smux);
}

gboolean
gxsnmp_smux_connect (GxsnmpSmux *smux, guchar *host, gulong *oid,
		guint len, guchar *password)
{
  struct sockaddr_in  serv;
  struct servent     *sp;
  int                 fd;
  guchar              buffer[1500];
  guchar             *buf;
  guint               len1, len2;
  GxsnmpAsn1         *asn1;
  guchar             *eoc;
  guchar             *eoi;

  memset (&serv, 0, sizeof (struct sockaddr_in));
  serv.sin_family = AF_INET;

  sp = getservbyname ("smux", "tcp");
  if (sp != NULL)
    serv.sin_port = sp->s_port;
  else
    serv.sin_port = htons (199);
  serv.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
  fd = g_io_channel_unix_get_fd(smux->channel);
  connect (fd, (struct sockaddr *) &serv, sizeof (struct sockaddr_in));

  asn1 = gxsnmp_asn1_new(buffer, sizeof(buffer), GXSNMP_ASN1_ENC);
  gxsnmp_asn1_eoc_encode (asn1, &eoc);
  gxsnmp_asn1_octets_encode (asn1, &eoi, password, strlen(password));
  gxsnmp_asn1_header_encode(asn1, eoi, GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI,
	GXSNMP_ASN1_OTS);
  gxsnmp_asn1_octets_encode (asn1, &eoi, host, strlen(host));
  gxsnmp_asn1_header_encode(asn1, eoi, GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI,
	GXSNMP_ASN1_OTS);
  gxsnmp_asn1_oid_encode (asn1, &eoi, oid, len);
  gxsnmp_asn1_header_encode(asn1, eoi, GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI,
	GXSNMP_ASN1_OJI);
  gxsnmp_asn1_int_encode (asn1, &eoi, 0);
  gxsnmp_asn1_header_encode(asn1, eoi, GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI,
	GXSNMP_ASN1_INT);
  gxsnmp_asn1_header_encode(asn1, eoc, GXSNMP_ASN1_APL, GXSNMP_ASN1_CON, 
	GXSNMP_SMUX_OPEN);
  gxsnmp_asn1_destroy(asn1, &buf, &len1);
  g_io_channel_write(smux->channel, buf, len1, &len2);
  return TRUE;
}

guint
gxsnmp_smux_register (GxsnmpSmux *smux, struct gxsnmp_variable *var, 
	guint var_width, guint var_num, gulong *oid, guint len)
{
  guchar              buffer[1500];
  guchar             *buf;
  guint               len1, len2;
  GxsnmpAsn1         *asn1;
  guchar             *eoc;
  guchar             *eoi;
  struct gxsnmp_subtree *tree;

  tree = g_malloc(sizeof(struct gxsnmp_subtree));

  gxsnmp_oid_copy(tree->name, oid, len);
  tree->name_len = len;
  tree->variables = var;
  tree->variables_num = var_num;
  tree->variables_width = var_width;

  treelist = g_slist_append (treelist, tree);

  asn1 = gxsnmp_asn1_new(buffer, sizeof(buffer), GXSNMP_ASN1_ENC);
  gxsnmp_asn1_eoc_encode (asn1, &eoc);
  gxsnmp_asn1_int_encode (asn1, &eoi, 1);
  gxsnmp_asn1_header_encode(asn1, eoi, GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI,
	GXSNMP_ASN1_INT);
  gxsnmp_asn1_int_encode (asn1, &eoi, 0);
  gxsnmp_asn1_header_encode(asn1, eoi, GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI,
	GXSNMP_ASN1_INT);
  gxsnmp_asn1_oid_encode (asn1, &eoi, oid, len);
  gxsnmp_asn1_header_encode(asn1, eoi, GXSNMP_ASN1_UNI, GXSNMP_ASN1_PRI,
	GXSNMP_ASN1_OJI);
  gxsnmp_asn1_header_encode(asn1, eoc, GXSNMP_ASN1_APL, GXSNMP_ASN1_CON, 
	GXSNMP_SMUX_RREQ);
  gxsnmp_asn1_destroy(asn1, &buf, &len1);
  g_io_channel_write(smux->channel, buf, len1, &len2);
  return 0;
}

gboolean 
gxsnmp_smux_trap (GxsnmpSmux *smux, gulong *oid, guint len,
		GSList *variables)
{
  return TRUE;
}
