/* AIX smd5 cracker patch for JtR. Hacked together during April of 2013 by Dhiru
 * Kholia <dhiru at openwall.com>.
 *
 * This software is Copyright (c) 2013 Dhiru Kholia <dhiru at openwall.com> and
 * it is hereby released to the general public under the following terms:
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted.
 */

#if FMT_EXTERNS_H
extern struct fmt_main fmt_smd5;
#elif FMT_REGISTERS_H
john_register_one(&fmt_smd5);
#else

#include <string.h>
#include <assert.h>
#include <errno.h>
#ifdef _OPENMP
static int omp_t = 1;
#include <omp.h>
#ifndef OMP_SCALE
#define OMP_SCALE               16 // tuned on i7 w/HT
#endif
#endif

#include "md5.h"
#include "arch.h"
#include "misc.h"
#include "common.h"
#include "formats.h"
#include "params.h"
#include "options.h"
#include "memdbg.h"

#define FORMAT_LABEL		"aix-smd5"
#define FORMAT_NAME		"AIX LPA {smd5} (modified crypt-md5)"
#define ALGORITHM_NAME		"MD5 32/" ARCH_BITS_STR
#define BENCHMARK_COMMENT	""
#define BENCHMARK_LENGTH	-1
#define PLAINTEXT_LENGTH	125
#define BINARY_SIZE		16
#define BINARY_ALIGN			4
#define SALT_SIZE		sizeof(struct custom_salt)
#define SALT_ALIGN			sizeof(int)
#define MIN_KEYS_PER_CRYPT	1
#define MAX_KEYS_PER_CRYPT	1

static struct fmt_tests smd5_tests[] = {
	/* following hashes are AIX non-standard smd5 hashes */
	{"{smd5}s8/xSJ/v$uGam4GB8hOjTLQqvBfxJ2/", "password"},
	{"{smd5}alRJaSLb$aKM3H1.h1ycXl5GEVDH1e1", "aixsucks?"},
	{"{smd5}eLB0QWeS$Eg.YfWY8clZuCxF0xNrKg.", "0123456789ABCDE"},
	/* following hashes are AIX standard smd5 hashes (with corrected tag)
	 * lpa_options = std_hash=true */
	{"$1$JVDbGx8K$T9h8HK4LZxeLPMTAxCfpc1", "password"},
	{"$1$1Cu6fEvv$42kuaJ5fMEqyVStPuFG040", "0123456789ABCDE"},
	{"$1$ql5x.xXL$vYVDhExol2xUBBpERRWcn1", "jtr>hashcat"},
	{NULL}
};

static char (*saved_key)[PLAINTEXT_LENGTH + 1];
static ARCH_WORD_32 (*crypt_out)[BINARY_SIZE / sizeof(ARCH_WORD_32)];

static struct custom_salt {
	int is_standard;
	unsigned char salt[16];
} *cur_salt;

static void init(struct fmt_main *self)
{
#ifdef _OPENMP
	omp_t = omp_get_max_threads();
	self->params.min_keys_per_crypt *= omp_t;
	omp_t *= OMP_SCALE;
	self->params.max_keys_per_crypt *= omp_t;
#endif
	saved_key = mem_calloc(self->params.max_keys_per_crypt,
	                       sizeof(*saved_key));
	crypt_out = mem_calloc(self->params.max_keys_per_crypt,
	                       sizeof(*crypt_out));
}

static void done(void)
{
	MEM_FREE(crypt_out);
	MEM_FREE(saved_key);
}

static int valid(char *ciphertext, struct fmt_main *self)
{
	char *p;
	char *ctcopy;
	char *keeptr;
	if (strncmp(ciphertext, "{smd5}", 6) != 0 &&
		strncmp(ciphertext, "$1$", 3))
		return 0;

	ctcopy = strdup(ciphertext);
	keeptr = ctcopy;

	if (!strncmp(ciphertext, "{smd5}", 6))
		ctcopy += 6;
	else
		ctcopy += 3;

	if ((p = strtokm(ctcopy, "$")) == NULL)	/* salt */
		goto err;
	if (strlen(p) != 8)
		goto err;
	if ((p = strtokm(NULL, "$")) == NULL)	/* hash */
		goto err;
	MEM_FREE(keeptr);
	return 1;
err:
	MEM_FREE(keeptr);
	return 0;
}

static void *get_salt(char *ciphertext)
{
	char *ctcopy = strdup(ciphertext);
	char *keeptr = ctcopy;
	char *p;
	static struct custom_salt cs;
	memset(&cs, 0, sizeof(cs));
	keeptr = ctcopy;
	if (!strncmp(ciphertext, "{smd5}", 6)) {
		ctcopy += 6;
		cs.is_standard = 0;
	}
	else {
		ctcopy += 3;
		cs.is_standard = 1;
	}

	p = strtokm(ctcopy, "$");
	strncpy((char*)cs.salt, p, 9);
	p = strtokm(NULL, "$");

	MEM_FREE(keeptr);
	return (void *)&cs;
}

#define TO_BINARY(b1, b2, b3) \
	value = \
		(ARCH_WORD_32)atoi64[ARCH_INDEX(pos[0])] | \
		((ARCH_WORD_32)atoi64[ARCH_INDEX(pos[1])] << 6) | \
		((ARCH_WORD_32)atoi64[ARCH_INDEX(pos[2])] << 12) | \
		((ARCH_WORD_32)atoi64[ARCH_INDEX(pos[3])] << 18); \
	pos += 4; \
	out.b[b1] = value >> 16; \
	out.b[b2] = value >> 8; \
	out.b[b3] = value;

static void* get_binary(char *ciphertext)
{
	static union {
		char b[16];
		ARCH_WORD w;
	} out;
	char *pos;
	ARCH_WORD_32 value;

	pos = ciphertext + 3;
	if (!strncmp(ciphertext, "{smd5}", 6))
		pos = ciphertext + 6;

	while (*pos++ != '$');

	TO_BINARY(0, 6, 12);
	TO_BINARY(1, 7, 13);
	TO_BINARY(2, 8, 14);
	TO_BINARY(3, 9, 15);
	TO_BINARY(4, 10, 5);
	out.b[11] =
		(ARCH_WORD_32)atoi64[ARCH_INDEX(pos[0])] |
		((ARCH_WORD_32)atoi64[ARCH_INDEX(pos[1])] << 6);

	return out.b;
}

static int get_hash_0(int index) { return crypt_out[index][0] & 0xf; }
static int get_hash_1(int index) { return crypt_out[index][0] & 0xff; }
static int get_hash_2(int index) { return crypt_out[index][0] & 0xfff; }
static int get_hash_3(int index) { return crypt_out[index][0] & 0xffff; }
static int get_hash_4(int index) { return crypt_out[index][0] & 0xfffff; }
static int get_hash_5(int index) { return crypt_out[index][0] & 0xffffff; }
static int get_hash_6(int index) { return crypt_out[index][0] & 0x7ffffff; }

static void set_salt(void *salt)
{
	cur_salt = (struct custom_salt *)salt;
}

/*
 * $Id: md5_crypt.c,v 1.1 2002-05-11 14:42:35 cpbotha Exp $
 *
 * ----------------------------------------------------------------------------
 * "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
 * ----------------------------------------------------------------------------
 *
 * Origin: Id: crypt.c,v 1.3 1995/05/30 05:42:22 rgrimes Exp
 *
 */

static void crypt_md5(char *pw, char *salt, int is_standard, char *passwd)
{
	char *magic = "$1$";
	/* This string is magic for this algorithm.  Having
	 * it this way, we can get get better later on */
	char *sp, *ep;
	unsigned char final[16];
	int sl, pl, i, j;
	MD5_CTX ctx, ctx1;

	/* 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,(unsigned char *)pw,strlen(pw));

	// The following license text applies to the "if" code block
	// License: belongs to the PUBLIC DOMAIN, donated to hashcat, credits MUST go to atom
	//          (hashcat) and philsmd for their hard work. Thx
	// Disclaimer: WE PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER
	//         EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
	//         OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
	//         Furthermore, NO GUARANTEES THAT IT WORKS FOR YOU AND WORKS CORRECTLY
	if (is_standard) {
		/* Then our magic string */
		MD5_Update(&ctx,(unsigned char *)magic,strlen(magic));

		/* Then the raw salt */
		MD5_Update(&ctx,(unsigned char *)sp,sl);
	}
	else {
		MD5_Update(&ctx,(unsigned char *)sp,sl);
	}

	/* Then just as many characters of the MD5_(pw,salt,pw) */
	MD5_Init(&ctx1);
	MD5_Update(&ctx1,(unsigned char *)pw,strlen(pw));
	MD5_Update(&ctx1,(unsigned char *)sp,sl);
	MD5_Update(&ctx1,(unsigned char *)pw,strlen(pw));

	MD5_Final(final,&ctx1);

	for (pl = strlen(pw); pl > 0; pl -= 16)
		MD5_Update(&ctx,(unsigned char *)final,pl>16 ? 16 : pl);

	memset(final, 0, sizeof final);

	/* Then something really weird... */
	for (j = 0, i = strlen(pw); i; i >>= 1)
		if (i & 1)
			MD5_Update(&ctx, (unsigned char *)final+j, 1);
		else
			MD5_Update(&ctx, (unsigned char *)pw+j, 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,(unsigned char *)pw,strlen(pw));
		else
			MD5_Update(&ctx1,(unsigned char *)final,16);

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

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

		if (i & 1)
			MD5_Update(&ctx1,(unsigned char *)final,16);
		else
			MD5_Update(&ctx1,(unsigned char *)pw,strlen(pw));
		MD5_Final(final,&ctx1);
	}
	memcpy(passwd, final, 16);
}

static int crypt_all(int *pcount, struct db_salt *salt)
{
	const int count = *pcount;
	int index = 0;

#ifdef _OPENMP
#pragma omp parallel for
	for (index = 0; index < count; index++)
#endif
	{
		crypt_md5(saved_key[index], (char*)cur_salt->salt, cur_salt->is_standard, (char *)crypt_out[index]);
	}
	return count;
}

static int cmp_all(void *binary, int count)
{
	int index = 0;
#ifdef _OPENMP
	for (; index < count; index++)
#endif
		if (!memcmp(binary, crypt_out[index], ARCH_SIZE))
			return 1;
	return 0;
}

static int cmp_one(void *binary, int index)
{
	return !memcmp(binary, crypt_out[index], BINARY_SIZE);
}

static int cmp_exact(char *source, int index)
{
	return 1;
}

static void smd5_set_key(char *key, int index)
{
	int saved_len = strlen(key);
	if (saved_len > PLAINTEXT_LENGTH)
		saved_len = PLAINTEXT_LENGTH;
	memcpy(saved_key[index], key, saved_len);
	saved_key[index][saved_len] = 0;
}

static char *get_key(int index)
{
	return saved_key[index];
}

static int salt_hash(void *salt)
{
	return *(unsigned int*)salt & (SALT_HASH_SIZE - 1);
}

struct fmt_main fmt_smd5 = {
	{
		FORMAT_LABEL,
		FORMAT_NAME,
		ALGORITHM_NAME,
		BENCHMARK_COMMENT,
		BENCHMARK_LENGTH,
		0,
		PLAINTEXT_LENGTH,
		BINARY_SIZE,
		BINARY_ALIGN,
		SALT_SIZE,
		SALT_ALIGN,
		MIN_KEYS_PER_CRYPT,
		MAX_KEYS_PER_CRYPT,
		FMT_CASE | FMT_8_BIT | FMT_OMP,
#if FMT_MAIN_VERSION > 11
		{ NULL },
#endif
		smd5_tests
	}, {
		init,
		done,
		fmt_default_reset,
		fmt_default_prepare,
		valid,
		fmt_default_split,
		get_binary,
		get_salt,
#if FMT_MAIN_VERSION > 11
		{ NULL },
#endif
		fmt_default_source,
		{
			fmt_default_binary_hash_0,
			fmt_default_binary_hash_1,
			fmt_default_binary_hash_2,
			fmt_default_binary_hash_3,
			fmt_default_binary_hash_4,
			fmt_default_binary_hash_5,
			fmt_default_binary_hash_6
		},
		salt_hash,
		NULL,
		set_salt,
		smd5_set_key,
		get_key,
		fmt_default_clear_keys,
		crypt_all,
		{
			get_hash_0,
			get_hash_1,
			get_hash_2,
			get_hash_3,
			get_hash_4,
			get_hash_5,
			get_hash_6
		},
		cmp_all,
		cmp_one,
		cmp_exact
	}
};

#endif /* plugin stanza */
