
#ifndef lint
static const char revid[] =
  "$Id: crypt-md5.c 1142 2006-05-01 17:53:27Z brachman $";
#endif

/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
 * ----------------------------------------------------------------------------
 *
 * $FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.5.2.1 2001/05/24 12:20:02 markm Exp $
 *
 */

#if defined(LIBC_SCCS) && !defined(lint)
static const char rcsid[] = \
"$FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.5.2.1 2001/05/24 12:20:02 markm Exp $";
#endif /* LIBC_SCCS and not lint */

#include "dacs.h"

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <openssl/md5.h>

static const char *log_module_name = "crypt-md5";

enum {
  MD5_SIZE = CRYPTO_DIGEST_MD5_BYTE_LENGTH
};

static char	*magic = "$1$";

static void
to_a64(char *s, unsigned long v, int n)
{
  static unsigned char a64[] =
    "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

  while (--n >= 0) {
	*s++ = a64[v & 0x3f];
	v >>= 6;
  }
}

/*
 * Set the magic string value.
 * Apache uses "$apr1$" but Unix uses "$1"; things are more difficult than
 * they need to be...
 */
void
crypt_md5_magic(char *new_magic)
{

  magic = strdup(new_magic);
}

/*
 * UNIX password hashing method
 */
char *
crypt_md5(const char *pw, const char *salt)
{
  static char passwd[120], *p;
  static const char *sp, *ep;
  unsigned char	final[MD5_SIZE];
  int sl, pl, i;
  MD5_CTX ctx, ctx1;
  unsigned long l;

  /* Refine the salt first */
  sp = salt;

  /* If it starts with the magic string, then skip that */
  if (!strncmp(sp, magic, strlen(magic)))
	sp += strlen(magic);

  /* It stops at the first '$', max 8 chars */
  for (ep = sp; *ep && *ep != '$' && ep < (sp + 8); ep++)
	continue;

  /* Get the length of the true salt */
  sl = ep - sp;

  MD5_Init(&ctx);

  /* The password first, since that is what is most unknown */
  MD5_Update(&ctx, pw, strlen(pw));

  /* Then our magic string */
  MD5_Update(&ctx, magic, strlen(magic));

  /* Then the raw salt */
  MD5_Update(&ctx, sp, sl);

  /* Then just as many characters of the MD5(pw, salt, pw) */
  MD5_Init(&ctx1);
  MD5_Update(&ctx1, pw, strlen(pw));
  MD5_Update(&ctx1, sp, sl);
  MD5_Update(&ctx1, pw, strlen(pw));
  MD5_Final(final, &ctx1);
  for (pl = strlen(pw); pl > 0; pl -= MD5_SIZE)
	MD5_Update(&ctx, final, pl > MD5_SIZE ? MD5_SIZE : pl);

  /* Don't leave anything around in vm they could use. */
  memset(final, 0, sizeof(final));

  /* Then something really weird... */
  for (i = strlen(pw); i ; i >>= 1)
	if (i & 1)
	  MD5_Update(&ctx, final, 1);
	else
	  MD5_Update(&ctx, pw, 1);

  /* Now make the output string */
  strcpy(passwd, magic);
  strncat(passwd, sp, sl);
  strcat(passwd, "$");

  MD5_Final(final, &ctx);

  /*
   * and now, just to make sure things don't run too fast
   * On a 60 Mhz Pentium this takes 34 msec, so you would
   * need 30 seconds to build a 1000 entry dictionary...
   */
  for (i = 0; i < 1000 ;i++) {
	MD5_Init(&ctx1);
	if (i & 1)
	  MD5_Update(&ctx1, pw, strlen(pw));
	else
	  MD5_Update(&ctx1, final, MD5_SIZE);

	if (i % 3)
	  MD5_Update(&ctx1, sp, sl);

	if (i % 7)
	  MD5_Update(&ctx1, pw, strlen(pw));

	if (i & 1)
	  MD5_Update(&ctx1, final, MD5_SIZE);
	else
	  MD5_Update(&ctx1, pw, strlen(pw));
	MD5_Final(final, &ctx1);
  }

  p = passwd + strlen(passwd);

  l = (final[ 0] << 16) | (final[ 6] << 8) | final[12];
  to_a64(p, l, 4); p += 4;
  l = (final[ 1] << 16) | (final[ 7] << 8) | final[13];
  to_a64(p, l, 4); p += 4;
  l = (final[ 2] << 16) | (final[ 8] << 8) | final[14];
  to_a64(p, l, 4); p += 4;
  l = (final[ 3] << 16) | (final[ 9] << 8) | final[15];
  to_a64(p, l, 4); p += 4;
  l = (final[ 4] << 16) | (final[10] << 8) | final[ 5];
  to_a64(p, l, 4); p += 4;
  l =                      final[11];
  to_a64(p, l, 2); p += 2;
  *p = '\0';

  /* Don't leave anything around in vm they could use. */
  memset(final, 0, sizeof(final));

  return(passwd);
}
