/*  bfmd5: Utility to bruteforce md5 hashes
 *  Copyright (C) 2004 Armin Burgmeier
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <assert.h>
#include <openssl/md5.h>

#include "bfmd5.h"

int bfmd5_max = 16;
int bfmd5_progress = 0x7ffff;
int bfmd5_quiet = 0;
int bfmd5_checkall = 0;

char* bfmd5_init = NULL;
size_t bfmd5_initlen = 0;

/* Make sure the terminating '\0' is not included, otherwise it is also used
 * for bruteforcing! */
static const char LOWCHARS[62] =
	"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

static char* bfmd5_strdup(const char* src)
{
  size_t len;
  char* dst;

  len = strlen(src);
  dst = malloc(sizeof(char) * (len + 1));
  if(dst == NULL) return NULL;

  strcpy(dst, src);
  return dst;
}

int main(int argc, char* argv[])
{
  char* result;
  char* search;
  int i, opt;
  size_t rsize;
  unsigned char digest[MD5_DIGEST_LENGTH];
 
  bfmd5_init = NULL; 
  while((opt = getopt(argc, argv, "p:hve:qai:")) != -1)
  {
    switch(opt)
    {
      case 'p':
        bfmd5_progress = strtol(optarg, NULL, 0);
	if(!bfmd5_progress) bfmd5_progress = 0xffff;
        break;
      case 'e':
        bfmd5_max = strtol(optarg, NULL, 0);
	if(!bfmd5_max) bfmd5_max = 16;
	break;
      case 'q':
        bfmd5_quiet = 1;
	break;
      case 'a':
        bfmd5_checkall = 1;
	break;
      case 'i':
        bfmd5_init = bfmd5_strdup(optarg);
	bfmd5_initlen = strlen(bfmd5_init);
	break;
      case 'h':
        bfmd5_help();
	return EXIT_SUCCESS;
      case 'v':
        bfmd5_version();
	return EXIT_SUCCESS;
    }
  }

  if(bfmd5_initlen > 0 && bfmd5_max < bfmd5_initlen)
    bfmd5_max = bfmd5_initlen;

  /* Check for given md5 search hash */
  if(optind >= argc)
  {
    bfmd5_usage(argc, argv);
    if(bfmd5_init) free(bfmd5_init);
    return EXIT_FAILURE;
  }
  else
    search = argv[optind];

  /* Check for a valid md5 hash */
  if(!bfmd5_check(search) )
  {
    fprintf(stderr, "%s is not a valid md5 hash\n", search);
    if(bfmd5_init) free(bfmd5_init);
    return EXIT_FAILURE;
  }

  bfmd5_makedigest(search, digest);

  rsize = sizeof(char) * (bfmd5_max);
  result = malloc(rsize);

  if(!result)
  {
    fprintf(stderr, "Insufficent memory available\n");
    if(bfmd5_init) free(bfmd5_init);
    return EXIT_FAILURE;
  }

  strncpy(result, bfmd5_init, bfmd5_initlen);
  free(bfmd5_init);

  for(i = bfmd5_initlen; i < bfmd5_max; ++ i)
  {
    bfmd5_bf(digest, result, i, 0);
  }

  free(result);
  
  return EXIT_SUCCESS;
}

void bfmd5_bf(const unsigned char* dig, char* buf, int n, int c)
{
  static int count = 0;
  static unsigned char calc_digest[MD5_DIGEST_LENGTH];
  static char digest_str[32];
  int i;
  
  if(n == 0)
  {
    ++ count;
    MD5(buf, c, calc_digest);

    if(!bfmd5_quiet && !(count % bfmd5_progress) )
    {
      bfmd5_md5sum(calc_digest, digest_str);
      printf("%.*s (%.32s)...\n", c, buf, digest_str);
      fflush(stdout);
    }

    if(memcmp(calc_digest, dig, MD5_DIGEST_LENGTH) == 0)
    {
      printf("%.*s matches!\n", c, buf);
      fflush(stdout);

      free(buf);
      exit(EXIT_SUCCESS);
    }
  }
  else
  {
    if(bfmd5_checkall)
    {
      /* Use existing value in buf as initalizer when no value has
       * been checked yet. Note that this works even if no initializer is
       * given because the first value checked without initializer is the
       * empty string "" that is not built via this routine but just by
       * using a zero length. */
      if(count > 0) buf[c] = 32;

      for(; buf[c] != 127; ++ buf[c])
        bfmd5_bf(dig, buf, n - 1, c + 1);
    }
    else
    {
      /* Use existing value in buf as initalizer when no value has
       * been checked yet. */
      if(count > 0)
	i = 0;
      else
	i = strchr(LOWCHARS, buf[c]) - LOWCHARS;

      for(; i < sizeof(LOWCHARS); ++ i)
      {
        buf[c] = LOWCHARS[i];
	bfmd5_bf(dig, buf, n - 1, c + 1);
      }
    }
  }
}

void bfmd5_md5sum(const unsigned char* digest, char* buf)
{
  int i;

  for(i = 0; i < MD5_DIGEST_LENGTH; ++ i)
    sprintf(buf + (i << 1), "%.2hhx", digest[i]);
}

void bfmd5_help(void)
{
  printf("bfmd5 %d.%d\n\n", BFMD5_VERSION_MAJOR, BFMD5_VERSION_MINOR);
  printf("Copyright (C) 2004 Armin Burgmeier\n");

  printf(" -a    \t\tTry all chars between ASCII 32 and 127 instead of\n");
  printf("       \t\tonly chars and digits.\n");
  printf(" -e num\t\tSpecifies the maximum number of chars to check\n");
  printf(" -h    \t\tPrints this information\n");
  printf(" -p num\t\tShow progress each num iterations\n");
  printf(" -q    \t\tDon't show any progress at all\n");
  printf(" -i str\t\tSpecifies where to begin to check\n");
  printf(" -v    \t\tShows the version of bfmd5\n");
  printf("\n");
  printf("Each num parameter may be given decimal (123), octal (0173)\n");
  printf("or hexadecimal (0x7b)\n");
}

void bfmd5_version(void)
{
  printf("bfmd5 %d.%d\n\n", BFMD5_VERSION_MAJOR, BFMD5_VERSION_MINOR);
  printf("Copyright (C) 2004 Armin Burgmeier\n");
  printf("This is free software; see the source for copying conditions.  "
         "There is NO\n");
  printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A "
         "PARTICULAR PURPOSE.\n");
}

void bfmd5_usage(int argc, char* argv[])
{
  argc = 0;
  printf("Usage: %s [-phvseqa] md5sum\n", argv[0]);
  printf("Print %s -h to get further information.\n", argv[0]);
}

int bfmd5_check(const char* md5sum)
{
  int len = strlen(md5sum), i;
  if(len != (MD5_DIGEST_LENGTH * 2) ) return 0;

  for(i = 0; i < len; ++ i)
  {
    if(!isxdigit(md5sum[i]))
      return 0;
  }
  return 1;
}

void bfmd5_makelower(char* md5sum)
{
  while(*md5sum)
  {
    *md5sum = tolower(*md5sum);
    md5sum ++;
  }
}

void bfmd5_makedigest(const char* md5sum, char* result)
{
  for(; *md5sum; md5sum += 2)
  {
    *result = (bfmd5_xdigit(*md5sum) << 4) | bfmd5_xdigit(*(md5sum+1));
    ++ result;
  }
}

int bfmd5_xdigit(char c)
{
  if(c >= '0' && c <= '9') return c - '0';
  if(c >= 'A' && c <= 'F') return c - 'A' + 10;
  if(c >= 'a' && c <= 'f') return c - 'a' + 10;
  assert(0); return 0;
}

