/*
 * Copyright (C) 2007  Martin J. Muench <mjm@codito.de>
 *
 * SIP digest authentication password (hash) cracker
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <time.h>

#ifdef NO_OPENSSL
# include "md5.h"
#else
# include <openssl/md5.h>
#endif

#include "global.h"
#include "wrap.h"
#include "debug.h"

int opterr = 0; /* shutup getopt() */

/* Local functions */
static int  get_login_data(login_t *, const char *);
static int  crack_login_data(login_t *, const char *, char *, int, int);
static int  parse_sniffed_line(login_t *, char *);
static void usage(const char *);

/*
 * sipcrack main
 */

int main(int argc, char *argv[])
{
  int     c, use_stdin=0, print_process=0;
  char    *wordlist_file=NULL, *dump_file=NULL;
  char    cracked_pass[PW_MAXLEN];
  login_t login_info;

  memset(&login_info, 0, sizeof(login_info));

  printf("\nSIPcrack %s  ( MaJoMu | www.codito.de ) \n"  \
         "----------------------------------------\n\n", VERSION);

  /* Parse command line */

  while((c = getopt(argc, argv, "w:p:s")) != -1) {
    switch(c) {
    case 'w':
      wordlist_file = (char *)Malloc(strlen(optarg)+1);
      strcpy(wordlist_file, optarg);
      break;
    case 's':
      use_stdin = 1;
      break;
    case 'p':
      print_process = atoi(optarg);
      break;
    default:
      usage("Invalid arguments");
    }
  }

  /* Check for mode */

  if((wordlist_file == NULL && !use_stdin) || (wordlist_file != NULL && use_stdin))
    usage("Either -w <wordlist> or -s has to be given");

  /* Get dump file */

  argv += optind;
  argc -= optind;

  if(argc != 1) {
    SAFE_DELETE(wordlist_file);
    usage("You need to specify dump file");
  }

  dump_file = (char *)Malloc(strlen(argv[0])+1);
  strcpy(dump_file, argv[0]);

  /* Get Login data that shall be cracked */

  debug(("Reading and parsing dump file...\n"));
  if(get_login_data(&login_info, dump_file) < 0) {
    SAFE_DELETE(wordlist_file);
    SAFE_DELETE(dump_file);
    exit(EXIT_FAILURE);
  }

  /* Start main cracking function */

  if(crack_login_data(&login_info, 
		      wordlist_file, 
		      cracked_pass, 
		      use_stdin, 
		      print_process) == 1) 
  {
    printf("* Updating dump file '%s'... ", dump_file);
    fflush(stdout);
    update_login_data(&login_info, cracked_pass, dump_file);
    printf("done\n");
  }
  
  /* Clean up and exit */

  SAFE_DELETE(wordlist_file);
  SAFE_DELETE(dump_file);

  exit(EXIT_SUCCESS);
}


/* 
 * Display content of dump file and request selection from user
 */

static int get_login_data(login_t *login_info, const char *dump_file)
{
  FILE         *lfile=NULL;
  char         buffer[LOGIN_MAXLEN], input[8];
  char         *login_buffer=NULL;
  login_t      *login_ptr;
  unsigned int i=0, j=0, use=0;

  login_buffer = Malloc(sizeof(login_t));
  login_ptr    = (login_t*)login_buffer;

  /* Open dump file */

  if((lfile = fopen(dump_file, "r")) == NULL) {
    fprintf(stderr, "* Cannot open dump file: %s\n", strerror(errno));
    SAFE_DELETE(login_buffer);
    return -1;
  }

  /* Parse each line from dump file */

  while((fgets(buffer, sizeof(buffer), lfile)) != NULL) {
      
      /* Copy buffer to struct, if fails ignore line */
      if(parse_sniffed_line(login_ptr, buffer) < 0)
	  continue;
      
      i++;

      /* Increase buffer as we have more lines */
      login_buffer = Realloc(login_buffer, sizeof(login_t) * (i + 1));
      login_ptr = (login_t*)(login_buffer + sizeof(login_t) * i);
      
  }
  fclose(lfile);

  /* We found any logins? */

  if(!i) {
      printf("* No sniffed logins found, exiting\n");
      SAFE_DELETE(login_buffer);
      return -1;
  }
  
  /* Print parsed login data */

  printf("* Found Accounts:\n\nNum\tServer\t\tClient\t\tUser\tHash|Password\n\n");
  for(j = 0 ; j < i ; j++) {
    login_ptr = (login_t*)(login_buffer + sizeof(login_t) * j);

    printf("%d\t%s\t%s\t%s\t%s\n", 
	   j + 1,
	   login_ptr->server,
	   login_ptr->client,
           login_ptr->user,
	   login_ptr->hash
	   );
    login_ptr = (login_t*)(login_buffer + sizeof(login_t) * (j + 1));
  }
  printf("\n");    

  /* Get selection from user */

  do {
    get_string_input(input, sizeof(input), "* Select which entry to crack (1 - %d): ", i);
    use = atoi(input);
  } while(!use || use > i);


  /* Check if already cracked */

  login_ptr = (login_t*)(login_buffer + sizeof(login_t) * (use - 1));

  if(!strncmp(login_ptr->algorithm, "PLAIN", sizeof(login_ptr->algorithm))) {
    printf("* Password already cracked: '%s'\n", login_ptr->hash);
    SAFE_DELETE(login_buffer);
    exit(EXIT_SUCCESS);
  }

  /* Copy selected structure to pointer */

  memcpy(login_info, login_ptr , sizeof(login_t));

  /* Free buffer and return */

  SAFE_DELETE(login_buffer);
 
  return 1;
}

/*
 * Main cracking function 
 */

static int crack_login_data(login_t    *login, 
			    const char *wordlist, 
			    char       *cracked_pw, 
			    int        use_stdin, 
			    int        print_process) 
{
  /* Hash */
  MD5_CTX       md5_ctx;
  unsigned char md5_bin_hash[MD5_LEN];
  char          static_hash[MD5_LEN_HEX+1], dynamic_hash[MD5_LEN_HEX+1], final_hash[MD5_LEN_HEX+1];
  char          dynamic_hash_data[DYNAMIC_HASH_SIZE]; /* USER:REALM: */
  char          static_hash_data[STATIC_HASH_SIZE];   /* :nonce:nonce_count:cnonce:qop:static_hash */
  size_t        static_hash_data_len, dynamic_hash_data_len;

  /* password */
  char         pw[PW_MAXLEN];
  size_t       pw_len=0;
  FILE         *passdb=NULL;
  unsigned int num_pass=0;
  int          found=0;

  /* misc */
  time_t       begin, end;
  char         bin2hex_table[256][2]; /* table for bin<->hex mapping */


  /* For now only support MD5 till I saw another one in the wild */

  if(strncmp(login->algorithm, "MD5", strlen(login->algorithm))) {
    printf("\n* Cannot crack '%s' hash, only MD5 supported so far...\n", login->algorithm);
    return -1;
  }

  /* Init bin 2 hex table for faster conversions later */

  init_bin2hex(bin2hex_table);

  /* Generating MD5 static hash: 'METHOD:URI' */

  printf("\n* Generating static %s hash... ", login->algorithm);
  fflush(stdout);

  MD5_Init(&md5_ctx);
  MD5_Update(&md5_ctx, (unsigned char*)login->method, strlen( login->method ));
  MD5_Update(&md5_ctx, (unsigned char*)":", 1);
  MD5_Update(&md5_ctx, (unsigned char*)login->uri, strlen( login->uri ));
  MD5_Final(md5_bin_hash, &md5_ctx);
  bin_to_hex(bin2hex_table, md5_bin_hash, MD5_LEN, static_hash, MD5_LEN_HEX);
  printf("%s\n", static_hash);

  /* Constructing first part of dynamic hash: 'USER:REALM:' */

  snprintf(dynamic_hash_data, sizeof(dynamic_hash_data), "%s:%s:", login->user, login->realm);

  /* Construct last part of final hash data: ':NONCE(:CNONCE:NONCE_COUNT:QOP):<static_hash>' */

  /* no qop */
  if(!strlen(login->qop))
    snprintf(static_hash_data, sizeof(static_hash_data), ":%s:%s", login->nonce, static_hash);
  /* qop/conce/cnonce_count */
  else 
    snprintf(static_hash_data, sizeof(static_hash_data), ":%s:%s:%s:%s:%s", 
	     login->nonce, 
             login->nonce_count,
             login->cnonce,
             login->qop,
	     static_hash);

  /* Get lens of static buffers */

  dynamic_hash_data_len = strlen(dynamic_hash_data);
  static_hash_data_len  = strlen(static_hash_data);

  /* Set filepointer to wordlist file or stdin */

  if(!use_stdin) {
    /* Open wordlist */
    if((passdb=fopen(wordlist, "rb")) == NULL) {
      fprintf(stderr, "* Cannot open wordlist '%s'\n", wordlist);
      return -1;
    }
    printf("* Loaded wordlist: '%s'\n", wordlist);
  }
  else {
    passdb=stdin;
    printf("* Type your passwords:\n");
  }

  time(&begin);

  /* Begin brute force attack */
   
  printf("* Starting bruteforce against user '%s' (%s: '%s')\n",
       login->user, login->algorithm, login->hash);


  /* Generate hashes for each password and check for matching response */

  while(fgets(pw, sizeof(pw), passdb) != NULL) {

    num_pass++;

    /* Generate dynamic hash including pw (see above) */

    MD5_Init(&md5_ctx);
    MD5_Update(&md5_ctx, (unsigned char*)dynamic_hash_data, dynamic_hash_data_len);
    pw_len = strlen(pw);
    MD5_Update(&md5_ctx, 
	       (unsigned char*)pw, 
	       (pw[pw_len-2] == 0x0d ? pw_len-2 : pw[pw_len-1] == 0x0a ? pw_len -1 : pw_len));
    MD5_Final(md5_bin_hash, &md5_ctx);
    bin_to_hex(bin2hex_table, md5_bin_hash, MD5_LEN, dynamic_hash, MD5_LEN_HEX);
    
    /* Generate digest response hash */

    MD5_Init(&md5_ctx);
    MD5_Update(&md5_ctx, (unsigned char*)dynamic_hash, MD5_LEN_HEX);
    MD5_Update(&md5_ctx, (unsigned char*)static_hash_data, static_hash_data_len);
    MD5_Final(md5_bin_hash, &md5_ctx);
    bin_to_hex(bin2hex_table, md5_bin_hash, MD5_LEN, final_hash, MD5_LEN_HEX);
    
    /* Check for match */

    if(!strncmp(final_hash, login->hash, MD5_LEN_HEX)) {
      found = 1;
      break;
    }

    /* Debug print */

    if(print_process) {
      if(num_pass % print_process == 0)
	printf("* Already tried %d passwords (last '%s')\n", num_pass, pw); 
    }
    
  } /* while */

  time(&end);

  printf("* Tried %u passwords in %d seconds\n", num_pass, (int)(end - begin));

  /* Close file fd */

  if(!use_stdin)
    fclose(passdb);

  /* Print result */

  if(found == 1) {

    /* remove %0d%0a */

    if(pw[pw_len-2] == 0x0d)
      pw[pw_len-2] = 0x00;
    else if(pw[pw_len-1] == 0x0a)
      pw[pw_len-1] = 0x00;

    printf("\n* Found password: '%s'\n", pw);

    /* copy to return buffer for dump file update */

    memset(cracked_pw, 0, PW_MAXLEN);
    strncpy(cracked_pw, pw, PW_MAXLEN - 1);
    return 1;
  }
  else {
    printf("\n* Tried all passwords, no match\n");
    return 0;
  }

}

/* 
 * Parse sniffed login data line and copy to struct
 */

static int parse_sniffed_line(login_t *login, char *buffer) 
{
  char   **lines;
  int    num_lines, i;
  size_t len;

  memset(login, 0, sizeof(login_t));

  len = strlen(buffer);
  
  /* Remove \r\n */
  for(i = 0; i < len; i++) {
    if(buffer[i] == 0x0a)      { buffer[i] = 0x00; break; }
    else if(buffer[i] == 0x0d) { buffer[i] = 0x00; break; }
  }

  /* split by separator '"' */

  lines = stringtoarray(buffer, '"', &num_lines);

  /* Copy to struct */

  if(num_lines == 12) {
    strncpy(login->server,      lines[0], sizeof(login->server)      - 1 );
    strncpy(login->client,      lines[1], sizeof(login->client)      - 1 );
    strncpy(login->user,        lines[2], sizeof(login->user)        - 1 );
    strncpy(login->realm,       lines[3], sizeof(login->realm)       - 1 );
    strncpy(login->method,      lines[4], sizeof(login->method)      - 1 );
    strncpy(login->uri,         lines[5], sizeof(login->uri)         - 1 );
    strncpy(login->nonce,       lines[6], sizeof(login->nonce)       - 1 );
    strncpy(login->cnonce,      lines[7], sizeof(login->cnonce)      - 1 );
    strncpy(login->nonce_count, lines[8], sizeof(login->nonce_count) - 1 );
    strncpy(login->qop,         lines[9], sizeof(login->qop)         - 1 );
    strncpy(login->algorithm,   lines[10], sizeof(login->algorithm)  - 1 );
    strncpy(login->hash,        lines[11], sizeof(login->hash)       - 1 );
  }

  /* Free mem */

  for(i = 0 ; i < num_lines ; i++) {
    SAFE_DELETE(lines[i]);
  }

  /* Return */

  if(num_lines != 12)
    return -1;

  return 0;
}

/* 
 * Show usage and exit
 */

static void usage(const char *err_msg)
{
  printf("Usage: sipcrack [OPTIONS] [ -s | -w <wordlist> ] <dump file>\n\n"    \
         "       <dump file>   = file containing logins sniffed by SIPdump \n\n" \
         "       Options:                                                  \n" \
         "       -s            = use stdin for passwords                   \n" \
	 "       -w wordlist   = file containing all passwords to try      \n" \
	 "       -p num        = print cracking process every n passwords (for -w)\n" \
	 "                       (ATTENTION: slows down heavily)           \n" \
         "\n* %s\n", err_msg);
  exit(EXIT_FAILURE);
}
