/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * GXSNMP -- An snmp mangament application
 *
 * This code implements the MD2 message-digest algorithm.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <glib.h>
#include <gobject/gobject.h>
#include "gxsnmp_hash.h"
#include "gxsnmp_md2.h"

/* Permutation of 0..255 constructed from the digits of pi. It gives a
   "random" nonlinear byte substitution operation.
 */
static guchar PI_SUBST[256] = {
  41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6,
  19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188,
  76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24,
  138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251,
  245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63,
  148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50,
  39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165,
  181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210,
  150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157,
  112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27,
  96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15,
  85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197,
  234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65,
  129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123,
  8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233,
  203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228,
  166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237,
  31, 26, 219, 153, 141, 51, 159, 17, 131, 20
};

static guchar *PADDING[] = {
  (guchar *)"",
  (guchar *)"\001",
  (guchar *)"\002\002",
  (guchar *)"\003\003\003",
  (guchar *)"\004\004\004\004",
  (guchar *)"\005\005\005\005\005",
  (guchar *)"\006\006\006\006\006\006",
  (guchar *)"\007\007\007\007\007\007\007",
  (guchar *)"\010\010\010\010\010\010\010\010",
  (guchar *)"\011\011\011\011\011\011\011\011\011",
  (guchar *)"\012\012\012\012\012\012\012\012\012\012",
  (guchar *)"\013\013\013\013\013\013\013\013\013\013\013",
  (guchar *)"\014\014\014\014\014\014\014\014\014\014\014\014",
  (guchar *)"\015\015\015\015\015\015\015\015\015\015\015\015\015",
  (guchar *)"\016\016\016\016\016\016\016\016\016\016\016\016\016\016",
  (guchar *)"\017\017\017\017\017\017\017\017\017\017\017\017\017\017\017",
  (guchar *)"\020\020\020\020\020\020\020\020\020\020\020\020\020\020\020\020"
};

static void MD2Init(GxsnmpMd2 *md2);
static void MD2Transform(guchar state[16], guchar checksum[16], 
			 guchar block[16]);

static void gxsnmp_md2_init        (GxsnmpMd2       *md2);
static void gxsnmp_md2_class_init  (GxsnmpMd2Class  *klass);
static void gxsnmp_md2_finalize    (GObject         *object);

static void gxsnmp_md2_update      (GxsnmpHash      *hash,
				    guchar const    *buf,
				    guint	     len);
static void gxsnmp_md2_final       (GxsnmpHash      *hash,
			 	    guchar          *buf);
static void gxsnmp_md2_reset       (GxsnmpHash      *hash);

static gpointer parent_class = NULL;

GType
gxsnmp_md2_get_type (void)
{
  static GType object_type = 0;

  if (!object_type)
    {
      static const GTypeInfo object_info =
        {
          sizeof (GxsnmpMd2Class),
          (GBaseInitFunc) NULL,
          (GBaseFinalizeFunc) NULL,
          (GClassInitFunc) gxsnmp_md2_class_init,
          NULL,           /* class_finalize */
          NULL,           /* class_data */
          sizeof (GxsnmpMd2),
          0,              /* n_preallocs */
          (GInstanceInitFunc) gxsnmp_md2_init,
        };

        object_type = g_type_register_static (gxsnmp_hash_get_type(),
                                              "GxsnmpMd2",
                                              &object_info, 0);
    }
  return object_type;
}

static void
gxsnmp_md2_init (GxsnmpMd2 *object)
{
  GxsnmpHash *hash = GXSNMP_HASH(object);

  hash->result    = g_malloc(16);
  hash->hashlen   = 16;
  hash->hashname  = "MD2";

  if (hash->result)
    MD2Init(object);
}

static void
gxsnmp_md2_class_init (GxsnmpMd2Class *klass)
{
  GObjectClass *object_class  = G_OBJECT_CLASS (klass);
  GxsnmpHashClass *hash_class = GXSNMP_HASH_CLASS (klass);
  parent_class = g_type_class_peek_parent (klass);

  hash_class->update    = gxsnmp_md2_update;
  hash_class->final     = gxsnmp_md2_final;
  hash_class->reset     = gxsnmp_md2_reset;

  object_class->finalize = gxsnmp_md2_finalize;
}

static void
gxsnmp_md2_finalize (GObject *object)
{
  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gxsnmp_md2_reset (GxsnmpHash *hash)
{
  GxsnmpMd2 *md2;

  md2 = GXSNMP_MD2(hash);

  if (hash->result)
    MD2Init(md2);
}

static void
gxsnmp_md2_update (GxsnmpHash *hash, guchar const *buf, guint len)
{
  GxsnmpMd2 *md2;
  guint i, index, partLen;
	    
  md2 = GXSNMP_MD2(hash);

  /* Update number of bytes mod 16 */
  index = md2->count;
  md2->count = (index + len) & 0xf;

  partLen = 16 - index;
		  
  /* Transform as many times as possible.  */
  if (len >= partLen) {
    g_memmove (&md2->data[index], buf, partLen);
    MD2Transform ((guchar *)hash->result, md2->checksum, md2->data);
    
    for (i = partLen; i + 15 < len; i += 16)
      MD2Transform ((guchar *)hash->result, md2->checksum, (guchar *)&buf[i]);

    index = 0;
  }
  else
    i = 0;

  /* Buffer remaining input */
  g_memmove (&md2->data[index], &buf[i], len-i);
}

static void
gxsnmp_md2_final (GxsnmpHash *hash, guchar *buf)
{
  guint index, padLen;
  GxsnmpMd2 *md2;

  md2 = GXSNMP_MD2(hash);
  /* Pad out to multiple of 16. */
  index = md2->count;
  padLen = 16 - index;
  gxsnmp_md2_update (hash, PADDING[padLen], padLen);

  /* Extend with checksum */
  gxsnmp_md2_update (hash, md2->checksum, 16);

  /* Store state in digest */
  g_memmove (buf, hash->result, 16);

  /* Zeroize sensitive information. */
  memset (hash->result, 0, hash->hashlen);
  memset (md2->checksum, 0, 16);
  memset (md2->data, 0, 16);
}

/*
 * Start MD2 accumulation.  Set bit count to 0 and buffer to mysterious
 * initialization constants.
 */
static void 
MD2Init(GxsnmpMd2 *object)
{
  GxsnmpHash *hash = GXSNMP_HASH(object);

  object->count   = 0;
  memset(object->checksum, 0, 16);
  memset(hash->result, 0, 16);
}

static void 
MD2Transform(guchar state[16], guchar checksum[16], guchar block[16])
{
  guint i, j, t;
  guchar x[48];

  /* Form encryption block from state, block, state ^ block. */
  g_memmove(x, state, 16);
  g_memmove(x+16, block, 16);
  for (i = 0; i < 16; i++)
    x[i+32] = state[i] ^ block[i];

  /* Encrypt block (18 rounds). */
  t = 0;
  for (i = 0; i < 18; i++)
    {
      for (j = 0; j < 48; j++)
	t = x[j] ^= PI_SUBST[t];
      t = (t + i) & 0xff;
    }

  /* Save new state */
  g_memmove(state, x, 16);

  /* Update checksum. */
  t = checksum[15];
  for (i = 0; i < 16; i++)
    t = checksum[i] ^= PI_SUBST[block[i] ^ t];

  /* Zeroize sensitive information. */
  memset(x, 0, sizeof (x));
}

GxsnmpMd2*   gxsnmp_md2_new()
{
  GxsnmpMd2* md2;

  md2 = g_object_new (gxsnmp_md2_get_type (), NULL);

  return md2;
}
