/* CSL - Common Sound Layer
 * Copyright (C) 2000-2001 Stefan Westerfeld and Tim Janik
 *  
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 *
 * Part of this code implements the MD5 message-digest algorithm.
 * The algorithm is due to Ron Rivest.	This code was
 * written by Colin Plumb in 1993, no copyright is claimed.
 * This code is in the public domain; do with it what you wish.
 *
 * Equivalent code is available from RSA Data Security, Inc.
 * This code has been tested against that, and is equivalent,
 * except that you don't need to include two pages of legalese
 * with every copy.
 */
#include	"artsmd5.h"

#include	"cslutils.h"
#include	<string.h>
#include	<unistd.h>
#include	<stdlib.h>
#include	<fcntl.h>
#include	<sys/time.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<sys/utsname.h>
#include	<assert.h>
#include	<stdio.h>
#include	<time.h>

#define uint32 unsigned int

/* --- prototypes --- */
static void	md5_transform	(uint32	buf[4],
				 uint32	in[16]);


/* --- variables --- */
static char    md5_file_seed[ARTS_MD5_COOKIE_LEN + 1];
static char    md5_auth_cookie[ARTS_MD5_COOKIE_LEN + 1];
static CslBool md5_initialized = FALSE;


/* --- functions --- */
void
_arts_md5_auth_set_cookie (const char *cookie)
{
  assert (cookie != NULL);
  assert (strlen (cookie) == ARTS_MD5_COOKIE_LEN);

  strncpy (md5_auth_cookie, cookie, ARTS_MD5_COOKIE_LEN);
  md5_auth_cookie[ARTS_MD5_COOKIE_LEN] = 0;
  md5_initialized = TRUE;
}

const char*
_arts_md5_auth_cookie (void)
{
  assert (md5_initialized != FALSE);

  return md5_auth_cookie;
}

static char*
md5_to_ascii_overwrite (char *md5)
{
  char outascii[ARTS_MD5_COOKIE_LEN + 1], *result;
  int i;

  /* FIXME: arts still has 33 and 16 hardcoded, should use ARTS_MD5_COOKIE_LEN + 1 and
   * ARTS_MD5_COOKIE_LEN / 2 instead.
   */
  
  /* convert to readable text */
  for (i = 0; i < ARTS_MD5_COOKIE_LEN / 2; i++)
    snprintf (&outascii[i * 2], ARTS_MD5_COOKIE_LEN + 1, "%02x", md5[i] & 0xff);
  result = csl_strdup (outascii);
  
  /* try to zero out traces */
  memset (md5, 0, ARTS_MD5_COOKIE_LEN / 2);
  memset (outascii, 0, ARTS_MD5_COOKIE_LEN + 1);
  
  /* well, here it is */
  return result;
}

char*
_arts_md5_auth_mangle (const char *cookie)
{
  char mangle[ARTS_MD5_COOKIE_LEN * 2 + 1];
  char out[ARTS_MD5_COOKIE_LEN];

  assert (md5_initialized != FALSE);

  strcpy (mangle, md5_auth_cookie);
  strcat (mangle, cookie);
  
  _arts_md5_sum (ARTS_MD5_COOKIE_LEN * 2, mangle, out);
  memset (mangle, 0, ARTS_MD5_COOKIE_LEN * 2);
  
  return md5_to_ascii_overwrite (out);
}

void
_arts_md5_sum (int            length,
	     unsigned char *data,
	     char           md5sum[ARTS_MD5_COOKIE_LEN + 1])
{
  uint32 buffer[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 };
  uint32 finalsize = length + 1; /* in bytes */
  uint32 in[16];
  int i, j;
  unsigned char w;
  
  assert (data != NULL);
  assert (md5sum != NULL);
  
  while ((finalsize & 63) != 56)
    finalsize++;
  finalsize += 8;
  
  for (i = 0, j = 0; i < finalsize; i++)
    {
      if (i < length)
	w = data[i];
      else if (i == length)
	w = 0x80;
      else if ((finalsize-i > 8) || (finalsize-i <= 4))
	w = 0;
      else
	{
	  /* well, the length thing encoded in here will only
	   * work until 2^32 bits (though md5 would support 2^64
	   * bits)
	   */
	  w = (length * 8 >> (i + 8 - finalsize) * 8) & 0xff;
	}
      switch (i & 3)
	{
	case 0:	in[j]  = w;		break;
	case 1:	in[j] |= w << 8;	break;
	case 2:	in[j] |= w << 16;	break;
	case 3:	in[j] |= w << 24;
	  j++;
	  if (j == 16)
	    {
	      md5_transform (buffer, in);
	      j = 0;
	    }
	  break;
	}
    }
  
  for (i = 0, j = 0; j < 4; j++)
    {
      md5sum[i++] = buffer[j] & 0xff;
      md5sum[i++] = (buffer[j] >> 8) & 0xff;
      md5sum[i++] = (buffer[j] >> 16) & 0xff;
      md5sum[i++] = (buffer[j] >> 24) & 0xff;
    }
}

char*
_arts_md5_auth_mkcookie (void)
{
  static int md5_random_cookie_number = 0;
  struct {
    char		mktemp[8];
    char		dev_urandom[16];
    struct timeval	tv;
    int		  	pid;
    struct utsname 	un;
    char		seed[ARTS_MD5_COOKIE_LEN + 1];
    int			number;
  } rinfo = { ".XXXXXX", { 0, }, };
  char out[16];
  int fd;
  
  /* probably everyone has mktemp() and gives us 6 random chars for free */
  mktemp (rinfo.mktemp);
  
  /* linux random device ; if that works, we have good random anyway */
  fd = open ("/dev/urandom", O_RDONLY);
  if (fd >= 0)
    {
      read (fd, rinfo.dev_urandom, 16);
      close (fd);
    }

  /* collect some "random" system information */
  gettimeofday (&rinfo.tv, 0);
  rinfo.pid = getpid ();
  uname (&rinfo.un);

  /* ensure that two cookies that are  requested very shortly after each
   * other (so that it looks like "at the same time") won't be the same
   */
  rinfo.number = ++md5_random_cookie_number;
  
  /* this is some seed from a file which is updated sometimes with a
   * new _arts_md5_auth_mkcookie() after initialization
   */
  strncmp (rinfo.seed, md5_file_seed, ARTS_MD5_COOKIE_LEN);

  /* build hash value of all information */
  _arts_md5_sum (sizeof (rinfo), (unsigned char *) &rinfo, out);
  
  /* zero out traces and return */
  memset (&rinfo, 0, sizeof (rinfo));

  return md5_to_ascii_overwrite (out);
}

unsigned long
_arts_md5_auth_mkrand (void)
{
  char *cookie = _arts_md5_auth_mkcookie (), *p = cookie;
  unsigned long h = *p;

  for (p += 1; *p != '\0'; p++)
    h = (h << 5) - h + *p;

  csl_free (cookie);

  return h;
}

CslBool
_arts_md5_load_cookie (const char *filename,
		     char        cookie[ARTS_MD5_COOKIE_LEN + 1])
{
  int i, fd;

  assert (filename != NULL);
  assert (cookie != NULL);

  fd = open (filename, O_RDONLY);
  if (fd >= 0)
    {
      struct stat stat_buf;
      
      for (i = 0; i < 5; i++)
	{
	  fstat (fd, &stat_buf);
	  
	  if (stat_buf.st_size >= ARTS_MD5_COOKIE_LEN)
	    {
	      lseek (fd, 0, SEEK_SET);
	      if (read (fd, cookie, ARTS_MD5_COOKIE_LEN) == ARTS_MD5_COOKIE_LEN)
		{
		  cookie[ARTS_MD5_COOKIE_LEN] = 0;
		  close (fd);

		  return TRUE;
		}
	    }
	  /* authority file has wrong size, just being written */
	  sleep (1);
	}
    }

  return FALSE;
}

void
_arts_md5_auth_init_seed (const char *seedfile_name)
{
  struct stat stat_buf;
  int max_age, lstat_result;

  /* don't care if it works - no harm is being done if it doesn't */
  _arts_md5_load_cookie (seedfile_name, md5_file_seed);

  /* maxage ensures that not everybody will try to update the seed
   * at the same time, while it will take at most 5 hours between
   * updates (if there are any initialization calls)
   */
  max_age = 3600 + (getpid () & 0xfff) * 4;
  lstat_result = lstat (seedfile_name, &stat_buf);

  if (lstat_result != 0 || (time (NULL) - stat_buf.st_mtime) > max_age)
    {
      int fd = open (seedfile_name, O_TRUNC | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);

      if (fd >= 0)
	{
	  char *cookie = _arts_md5_auth_mkcookie ();

	  write (fd, cookie, strlen (cookie));
	  memset (cookie, 0, strlen (cookie));
	  csl_free (cookie);
	  close (fd);
	}
    }
}

/* How that MD5 auth stuff is supposed to work:
 *
 * Initialization:
 *
 * Your service calls md5_auth_set_cookie and passes a "secret cookie".
 * Lets call the "secret cookie" S. As soon as a client wants to connect,
 * he needs the same secret cookie S.
 *
 * Of course the user can copy the "secret cookie" using a secure connection
 * to any computer from which he wants to access the service.
 *
 * 0. SERVER: if no common secret cookie is available, generate a random
 *            cookie and keep it secret - ensure (through secure connections)
 *            that the client gets the secret cookie
 *
 * 1. SERVER: generate a new (random) cookie R
 * 2. SERVER: send it to the client
 * 3. CLIENT: (should get/have the "secret cookie" S from somewhere secure)
 * 4. CLIENT: mangle the cookies R and S to a mangled cookie M
 * 5. CLIENT: send M to the server
 * 6. SERVER: verify that mangling R and S gives just the same thing as the
 *    cookie M received from the client. If yes, authentication is successful.
 *
 * The advantage of that protocol is, that even somebody who can read all
 * network traffic can't find out the secret cookie S, as that is never
 * transferred as plaintext.
 */
/* I used the central part of Colin Plumb's public domain MD5 implementation */

/* #define F1(x, y, z)	(x & y | ~x & z) */
#define F1(x, y, z)	(z ^ (x & (y ^ z)))	/* optimized? version */
#define F2(x, y, z)	(F1 (z, x, y))
#define F3(x, y, z)	(x ^ y ^ z)
#define F4(x, y, z)	(y ^ (x | ~z))

/* This is the central step in the MD5 algorithm. */
#define MD5STEP(f, w, x, y, z, data, s)	\
             (w += f (x, y, z) + data,  w = w << s | w >> (32 - s),  w += x )

static void
md5_transform (uint32 buf[4],
	       uint32 in[16])
{
  uint32 a, b, c, d;
  
  /* The core of the MD5 algorithm, this alters an existing MD5 hash to
   * reflect the addition of 16 longwords of new data.
   */

  a = buf[0];
  b = buf[1];
  c = buf[2];
  d = buf[3];
  
  MD5STEP (F1, a, b, c, d,  in[0] + 0xd76aa478,  7);
  MD5STEP (F1, d, a, b, c,  in[1] + 0xe8c7b756, 12);
  MD5STEP (F1, c, d, a, b,  in[2] + 0x242070db, 17);
  MD5STEP (F1, b, c, d, a,  in[3] + 0xc1bdceee, 22);
  MD5STEP (F1, a, b, c, d,  in[4] + 0xf57c0faf,  7);
  MD5STEP (F1, d, a, b, c,  in[5] + 0x4787c62a, 12);
  MD5STEP (F1, c, d, a, b,  in[6] + 0xa8304613, 17);
  MD5STEP (F1, b, c, d, a,  in[7] + 0xfd469501, 22);
  MD5STEP (F1, a, b, c, d,  in[8] + 0x698098d8,  7);
  MD5STEP (F1, d, a, b, c,  in[9] + 0x8b44f7af, 12);
  MD5STEP (F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
  MD5STEP (F1, b, c, d, a, in[11] + 0x895cd7be, 22);
  MD5STEP (F1, a, b, c, d, in[12] + 0x6b901122,  7);
  MD5STEP (F1, d, a, b, c, in[13] + 0xfd987193, 12);
  MD5STEP (F1, c, d, a, b, in[14] + 0xa679438e, 17);
  MD5STEP (F1, b, c, d, a, in[15] + 0x49b40821, 22);
  
  MD5STEP (F2, a, b, c, d,  in[1] + 0xf61e2562,  5);
  MD5STEP (F2, d, a, b, c,  in[6] + 0xc040b340,  9);
  MD5STEP (F2, c, d, a, b, in[11] + 0x265e5a51, 14);
  MD5STEP (F2, b, c, d, a,  in[0] + 0xe9b6c7aa, 20);
  MD5STEP (F2, a, b, c, d,  in[5] + 0xd62f105d,  5);
  MD5STEP (F2, d, a, b, c, in[10] + 0x02441453,  9);
  MD5STEP (F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
  MD5STEP (F2, b, c, d, a,  in[4] + 0xe7d3fbc8, 20);
  MD5STEP (F2, a, b, c, d,  in[9] + 0x21e1cde6,  5);
  MD5STEP (F2, d, a, b, c, in[14] + 0xc33707d6,  9);
  MD5STEP (F2, c, d, a, b,  in[3] + 0xf4d50d87, 14);
  MD5STEP (F2, b, c, d, a,  in[8] + 0x455a14ed, 20);
  MD5STEP (F2, a, b, c, d, in[13] + 0xa9e3e905,  5);
  MD5STEP (F2, d, a, b, c,  in[2] + 0xfcefa3f8,  9);
  MD5STEP (F2, c, d, a, b,  in[7] + 0x676f02d9, 14);
  MD5STEP (F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
  
  MD5STEP (F3, a, b, c, d,  in[5] + 0xfffa3942,  4);
  MD5STEP (F3, d, a, b, c,  in[8] + 0x8771f681, 11);
  MD5STEP (F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
  MD5STEP (F3, b, c, d, a, in[14] + 0xfde5380c, 23);
  MD5STEP (F3, a, b, c, d,  in[1] + 0xa4beea44,  4);
  MD5STEP (F3, d, a, b, c,  in[4] + 0x4bdecfa9, 11);
  MD5STEP (F3, c, d, a, b,  in[7] + 0xf6bb4b60, 16);
  MD5STEP (F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
  MD5STEP (F3, a, b, c, d, in[13] + 0x289b7ec6,  4);
  MD5STEP (F3, d, a, b, c,  in[0] + 0xeaa127fa, 11);
  MD5STEP (F3, c, d, a, b,  in[3] + 0xd4ef3085, 16);
  MD5STEP (F3, b, c, d, a,  in[6] + 0x04881d05, 23);
  MD5STEP (F3, a, b, c, d,  in[9] + 0xd9d4d039,  4);
  MD5STEP (F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
  MD5STEP (F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
  MD5STEP (F3, b, c, d, a,  in[2] + 0xc4ac5665, 23);
  
  MD5STEP (F4, a, b, c, d,  in[0] + 0xf4292244,  6);
  MD5STEP (F4, d, a, b, c,  in[7] + 0x432aff97, 10);
  MD5STEP (F4, c, d, a, b, in[14] + 0xab9423a7, 15);
  MD5STEP (F4, b, c, d, a,  in[5] + 0xfc93a039, 21);
  MD5STEP (F4, a, b, c, d, in[12] + 0x655b59c3,  6);
  MD5STEP (F4, d, a, b, c,  in[3] + 0x8f0ccc92, 10);
  MD5STEP (F4, c, d, a, b, in[10] + 0xffeff47d, 15);
  MD5STEP (F4, b, c, d, a,  in[1] + 0x85845dd1, 21);
  MD5STEP (F4, a, b, c, d,  in[8] + 0x6fa87e4f,  6);
  MD5STEP (F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
  MD5STEP (F4, c, d, a, b,  in[6] + 0xa3014314, 15);
  MD5STEP (F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
  MD5STEP (F4, a, b, c, d,  in[4] + 0xf7537e82,  6);
  MD5STEP (F4, d, a, b, c, in[11] + 0xbd3af235, 10);
  MD5STEP (F4, c, d, a, b,  in[2] + 0x2ad7d2bb, 15);
  MD5STEP (F4, b, c, d, a,  in[9] + 0xeb86d391, 21);
  
  buf[0] += a;
  buf[1] += b;
  buf[2] += c;
  buf[3] += d;
}

#undef uint32
