/*
** Hash-A-Lot by Ben Slusky <sluskyb@paranoiacs.org>
** 
** This program will read a passphrase from standard input and print a binary
** (not printable) hash to standard output. The output is suitable for use as
** an encryption key.
**
** USAGE:
**		hashalot [ -x ] [ -s _salt_ ] [ -n _#bytes_ ] _hashtype_
**	OR
**		_hashtype_ [ -x ] [ -s _salt_ ] [ -n _#bytes_ ]
**
** Most of the code was cribbed from the kerneli.org patch to util-linux,
** by Marc Mutz <Marc@Mutz.com>. Most of what wasn't, was cribbed from GnuPG,
** v.1.0.3 (http://www.gnupg.org/).
*/

#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>

#include <sys/types.h>
#include <sys/mman.h>

#include "rmd160.h"
#include "sha512.h"

#define PASSWDBUFFLEN 130

typedef int (*phash_func_t)(char dest[], size_t dest_len, const char src[], size_t src_len);

static int
phash_rmd160(char dest[], size_t dest_len, const char src[], size_t src_len)
{
	char tmp[PASSWDBUFFLEN] = { 'A', 0, };
	char key[RMD160_HASH_SIZE * 2] = { 0, };

	strncpy(tmp + 1, src, PASSWDBUFFLEN - 1);
	tmp[PASSWDBUFFLEN - 1] = '\0';
  
	rmd160_hash_buffer(key, src, src_len);
	rmd160_hash_buffer(key + RMD160_HASH_SIZE, tmp, src_len + 1 /* dangerous! */);

	memcpy(dest, key, dest_len);

	memset (tmp, 0, PASSWDBUFFLEN);        /* paranoia */
	memset (key, 0, RMD160_HASH_SIZE * 2); /* paranoia */

	return dest_len;
}

static int
phash_sha256(char dest[], size_t dest_len, const char src[], size_t src_len)
{
	memset(dest, 0, dest_len);
	sha256_hash_buffer((char *) src, src_len, dest, dest_len);
	return dest_len;
}

static int
phash_sha384(char dest[], size_t dest_len, const char src[], size_t src_len)
{
	memset(dest, 0, dest_len);
	sha384_hash_buffer((char *) src, src_len, dest, dest_len);
	return dest_len;
}

static int
phash_sha512(char dest[], size_t dest_len, const char src[], size_t src_len)
{
	memset(dest, 0, dest_len);
	sha512_hash_buffer((char *) src, src_len, dest, dest_len);
	return dest_len;
}

struct func_table_t {
	const char *name;
	phash_func_t func;
	size_t def_length;
} static func_table[] = {
	{ "ripemd160",	phash_rmd160, 20 },
	{ "rmd160",	phash_rmd160, 20 },
	{ "rmd160compat", phash_rmd160, 16 },
	{ "sha256",	phash_sha256, 32 },
	{ "sha384",	phash_sha384, 48 },
	{ "sha512",	phash_sha512, 64 },
	{ 0, 0, 0 }
};

static int
show_usage(const char argv0[])
{
	struct func_table_t *p = func_table;

	fprintf (stderr,
		 "usage:\n"
		 "    hashalot [ -x ] [ -s SALT ] [ -n _#bytes_ ] HASHTYPE\n"
		 "  or\n"
		 "    HASHTYPE [ -x ] [ -s SALT ] [ -n _#bytes_ ]\n"
		 "\n"
		 "supported values for HASHTYPE: ");

	for (; p->name; ++p)
		fprintf (stderr, "%s ", p->name);

	fprintf (stderr, "\n");
	
	return 1;
}

static phash_func_t 
phash_lookup(const char phash_name[], size_t *length)
{
	struct func_table_t *p = func_table;

	if (!phash_name)
		return 0;

	for (; p->name && strcmp(phash_name, p->name); ++p);
  
	if (length) *length = p->def_length;
	return p->func;
}

/* A function to read the passphrase either from the terminal or from
 * an open file descriptor */
static char *
xgetpass(const char *prompt)
{
	if (isatty(STDIN_FILENO))	/* terminal */
		return getpass(prompt); /* FIXME getpass(3) obsolete */
	else {				/* file descriptor */
		char *pass = NULL;
		int buflen, i;

		buflen=0;
		for (i=0; ; i++) {
			if (i >= buflen - 1) {
				/* we're running out of space in the buffer. 
				 * Make it bigger: */
				char *tmppass = pass;
				buflen += 128;
				pass = (char *) realloc(tmppass, buflen);
				if (pass == NULL) {
					/* realloc failed. Stop reading _now_. */
					fprintf(stderr, "not enough memory while reading passphrase\n");
					pass = tmppass; /* the old buffer hasn't changed */
					break;
				}
			}
			if (read(STDIN_FILENO, pass+i, 1) != 1 || pass[i] == '\n')
				break;
		}
		if (pass == NULL)
			return "";
		else {
			pass[i] = 0;
			return pass;
		}
	}
}

static void *
xmalloc (size_t size) {
        void *p;

        if (size == 0)
                return NULL;

        p = malloc(size);
        if (p == NULL) {
		perror("malloc");
                exit(1);
        }

        return p;
}

/* function to append a "salt" to the passphrase, to better resist
 * dictionary attacks */
static char *
salt_passphrase(char *pass, char *salt) {
	char *buf = xmalloc(strlen(pass) + strlen(salt) + 1);
	sprintf(buf, "%s%s", pass, salt);

	memset (pass, 0, strlen (pass)); /* paranoia */
	free(pass);

	return buf;
}

static void
hexify(char *hash, size_t hashlen) {
	int i;
	char *h = xmalloc(hashlen);
	memcpy(h, hash, hashlen);

	for (i=0; i < hashlen; i++)
		snprintf((hash + 2*i), 3, "%.2x", (unsigned char) h[i]);
	strcat(hash, "\n");

	memset(h, 0, hashlen); /* paranoia */
	free(h);
}

int
main(int argc, char *argv[]) 
{
	char *pass, *passhash, *salt = NULL, *p;
	size_t hashlen = 0;
	phash_func_t func;
	int hex_output = 0, c;

	while ((c = getopt(argc, argv, "n:s:x")) != -1) {
		switch (c) {
		case 'n':
			hashlen = strtoul(optarg, &p, 0);
			if (*p != '\0' || *optarg == '\0') {
				fprintf (stderr,
					 "%s: argument to -n option must be numeric\n",
					 argv[0]);
				show_usage(argv[0]);
				exit(EXIT_FAILURE);
			}
			break;
                case 's':
                        salt = optarg;
                        break;
		case 'x':
			hex_output++;
			break;
                default:
                        show_usage(argv[0]);
			exit(EXIT_FAILURE);
                }
	}

	if (!(func = phash_lookup(basename(argv[0]), (hashlen ? NULL : &hashlen))))
		/* lookup failed, so try next argv */
		if (!(func = phash_lookup(argv[optind], (hashlen ? NULL : &hashlen)))) {
		/* lookup failed again */
			fprintf (stderr,
				 "%s: missing or unknown hash type requested\n",
				 argv[0]);
			show_usage(argv[0]);
			exit(EXIT_FAILURE);
		} 

	assert (func != 0);

	/* allocate memory for the password hash:
	 * enough for 2 hex digits per byte of the requested hash length,
	 * plus a newline, plus a null */
	passhash = xmalloc(2*hashlen + 2);

	/* try to lock memory so it doesn't get swapped out for sure */
	if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
		perror("mlockall");
		fputs("Warning: couldn't lock memory, are you root?\n", stderr);
	}

	/* here we acquire the precious passphrase... */
	pass = xgetpass("Enter passphrase: ");
	if (salt)
		pass = salt_passphrase(pass, salt);
	hashlen = func(passhash, hashlen, pass, strlen(pass));
	memset (pass, 0, strlen (pass)); /* paranoia */
	free(pass);

	if (hex_output) {
		hexify(passhash, hashlen);
		hashlen = hashlen * 2 + 1;
	}

	if (write(STDOUT_FILENO, passhash, hashlen) != hashlen)
		perror("write");

	memset (passhash, 0, hashlen); /* paranoia again */
	free(passhash);

	exit(EXIT_SUCCESS);
}

