/*
 *			donkey  --- Don't Key
 *	       An alternative for S/KEY's key program.
 *
 *		 Copyright (C) 1995 Kazuhiko Yamamoto
 *		 Copyright (C) 2016, 2018 Peter Pentchev <roam@ringlet.net>
 *
 * This software conforms to the GNU GENERAL PUBLIC LICENSE Version 2.
 *
 * Author:  Kazuhiko Yamamoto <kazu@is.aist-nara.ac.jp>
 * Created: April   25, 1995
 * Revised: October  4, 1995
 *
 */

#define _GNU_SOURCE

static const char version_message[] = "donkey version 1.0.2 ../../.. Peter Pentchev";

#include "config.h"
#include "donkey.h"
#include "md.h"

#include <errno.h>
#include <getopt.h>
#include <limits.h>

/*
 * global variables
 */

int MD = DefaultMD; 

const struct option longopts [] = {
	{"number", 	1, 0, 'n'},
	{"init",	0, 0, 'i'},
	{"help",	0, 0, 'h'},
	{"version",	0, 0, 'v'},
	{"function", 	1, 0, 'f'},
	{0, 0, 0, 0}
};

const char * const help_message[] = {
	" -n num [-f func]	Calculate one time passwords from (seq-num+1) to seq",
	" -i [-f func]		Print /etc/skeykeys entry",
	0
};


/*
 * definition of local functions
 */

static void usage(const char * const progname)
{
    fprintf(stderr, "usage:\t%s [-n num] [-f func] sequence seed\n\t%s -i\n", progname, progname);
}

static void help(const char * const progname)
{
    const char * const *p = help_message;

    fprintf(stderr, "help: %s\n", progname);
    usage(progname);
    while (*p) fprintf(stderr, "%s\n", *p++);
}

static void version(const char * const progname)
{
    fprintf(stderr, "version of %s: %s\n", progname, version_message);
}

static const char *stripname(const char * const filename)
{
    const char * const p = strrchr(filename, FILESEP);

    if (p != NULL)
	    return p + FILESEPLEN;
    else
	    return filename;
}

static int btoa8(char *out, const char *in)
{
	if(in == NULL || out == NULL)
		return -1;
	
	for(unsigned i=0; i < 8; i++){
		sprintf(out, "%02x", *in++ & 0xff);
		out += 2;
	}
	
	return 0;
}

static unsigned long strtoul_fatal(const char * const buf)
{
	char *end;
	errno = 0;
	const unsigned long val = strtoul(buf, &end, 10);
	if (*end != '\0' || (val == ULONG_MAX && errno == ERANGE)) {
		fprintf(stderr, "Invalid unsigned number '%s'\n", buf);
		exit(EXIT_FAILURE);
	}
	return val;
}

static void key_init(void)
{
	/*
	 * user name
	 */
	const char * const defaultuser = getpwuid(getuid())->pw_name;
	fprintf(stderr, "Enter login name [default %s]: ", defaultuser);
	char user[BUFSIZ];
	for (size_t i = 0; i < BUFSIZ - 1; i++) {
		const int c = getchar();
		if (c == '\n' || c == EOF) {
			user[i] = '\0';
			break;
		} else {
			user[i] = (char) c;
		}
	}
	user[BUFSIZ - 1] = '\0';

	if (user[0] == '\0') {
		strcpy(user, defaultuser);
	}

	/*
	 * sequence
	 */

	fprintf(stderr, "Enter sequence 1 to 999 [default %d]: ", DEFAULT_SEQ);
	char seqbuf[BUFSIZ];
	for (size_t i = 0; i < BUFSIZ - 1; i++) {
		const int c = getchar();
		if (c == '\n' || c == EOF) {
			seqbuf[i] = '\0';
			break;
		} else {
			seqbuf[i] = (char) c;
		}
	}
	seqbuf[BUFSIZ - 1] = '\0';
		
	const unsigned long seq = seqbuf[0] == '\0'? DEFAULT_SEQ: strtoul_fatal(seqbuf);
	if (seq < 1 || 999 < seq) {
		fprintf(stderr,
			"sequence must be 1 =< and <= 999\n");
		exit(EXIT_FAILURE);
	}

	/*
	 * seed
	 */

	char defaultseed[SEED_LEN];
	for (size_t i=0; i < SEED_HOST_LEN; i++)
		defaultseed[i] = 'x';
#ifdef HAVE_UNAME
	{
		struct utsname sys;
		uname(&sys);
		strncpy(defaultseed, sys.nodename, SEED_HOST_LEN);
	}
#elif defined(HAVE_GETHOSTNAME)
	gethostname(defaultseed, SEED_HOST_LEN + 1);
#endif
	const time_t now = (time_t) time(NULL);
	sprintf(defaultseed + SEED_HOST_LEN,
		"%05ld", (long) (now % 100000));

	fprintf(stderr, "Enter new seed [default %s]: ", defaultseed);
	char seed[SEED_LEN];
	for (size_t i = 0; i < SEED_LEN - 1; i++) {
		const int c = getchar();
		if (c == '\n' || c == EOF) {
			seed[i] = '\0';
			break;
		} else {
			seed[i] = (char) c;
		}
	}
	seed[SEED_LEN - 1] = '\0';
	if (seed[0] == '\0') {
		strcpy(seed, defaultseed);
	}

	/*
	 * pass phrase
	 */

	fprintf(stderr,
		"Please choose passphrase between %d and %d characters.\n",
		PASS_PHRASE_MIN_LEN,PASS_PHRASE_MAX_LEN);
	char passphrase1[PASS_PHRASE_MAX_LEN];
	char passphrase2[PASS_PHRASE_MAX_LEN];
	passphrase("Enter passphrase : ",
		   passphrase1, PASS_PHRASE_MAX_LEN, true);
	passphrase("Re-enter passphrase : ",
		   passphrase2, PASS_PHRASE_MAX_LEN, false);
	if (strcmp(passphrase1, passphrase2) != 0) {
		fprintf(stderr, "No match, try again\n");
		exit(EXIT_FAILURE);
	}

	/*
	 * apply hash
	 */

	char key[8];
	if (keycrunch(key, seed, passphrase1) == RET_ERROR) {
		fprintf(stderr, "keycrunch failed\n");
		exit(EXIT_FAILURE);
	}
		
	for (size_t i = 0; i < seq; i++) secure_hash(key);

	/*
	 * The last time entry
	 */
	
	const time_t last = (time_t) time(NULL);
	char *tstr;
#ifdef HAVE_STRFTIME
	const struct tm * const tm = localtime(&last);
	char tbuf[27];
	strftime(tbuf, sizeof(tbuf), " %b %d,%Y %H:%M:%S", tm);
	tstr = tbuf;
#else
	tstr = ctime(&last);
	{
		char m[16], d[16], y[16], t[16], w[16];
		sscanf(tstr, "%s %s %s %s %s\n", w, m, d, t, y);
		sprintf(tstr, " %s %s,%s %s", m, d, y, t);
		
	}
#endif /* HAVE_STRFTIME */

	/*
	 * print the result
	 */
	
	char key2[BUFSIZ];
	btoa8(key2, key);
	
	printf("%s %04lu %-16s %s %-21s\n", user, seq, seed, key2, tstr);
	printf("%s\n", btoe(key));
	
	exit(EXIT_SUCCESS);
}

/*
 * main
 */

int main(const int argc, char * const argv[])
{
	const char * const progname = stripname(argv[0]);
	
	/*
	 * default option values
	 */

	unsigned long n=1;
	bool iflag=false;
	int optc;
	while((optc = getopt_long(argc, argv, "n:ihvf:", longopts, (int *)0))
	      != EOF) {
		switch (optc) {
		case 'n':
			n = strtoul_fatal(optarg);
			break;
		case 'i':
			iflag = true;
			break;
		case 'h':
			help(progname);
			exit(EXIT_SUCCESS);
			break;
		case 'v':
			version(progname);
			exit(EXIT_SUCCESS);
			break;
		case 'f':
			if (strcmp(optarg, "md4") == 0) {
				MD = D_MD4;
			} else if (strcmp(optarg, "md5") == 0) {
				MD = D_MD5;
			} else {
				fprintf(stderr, "%s: function is wrong.\n", progname);
				fprintf(stderr, "choice -- md4 and md5\n");
				exit(EXIT_FAILURE);
			}
			break;
		default:
			usage(progname);
			exit(EXIT_FAILURE);
		}
	}

	const int arg_count = argc - optind;

	if (iflag) {
		if (arg_count != 0) {
			usage(progname);
			exit(EXIT_FAILURE);
		}

		key_init();
	}

	/*
	 * no iflag
	 */
	
	if (arg_count != 2) {
		usage(progname);
		exit(EXIT_FAILURE);
	}

	const unsigned long seq = strtoul_fatal(argv[optind]);
	if ( n < 1 || n > (seq + 1)) {
		fprintf(stderr,
			"%s: count should be in the range from 1 to (seq + 1)\n",
			progname);
		exit(EXIT_FAILURE);		
	}

	char passphrase1[PASS_PHRASE_MAX_LEN];
	passphrase("Enter passphrase : ", passphrase1, PASS_PHRASE_MAX_LEN, false);
	
	char key[8];
	if (keycrunch(key, argv[optind+1], passphrase1) == RET_ERROR) {
		fprintf(stderr, "keycrunch failed\n");
		exit(EXIT_FAILURE);
	}

	if (n == 1) {
		for (size_t i = 0; i < seq; i++) secure_hash(key);
		printf("%s\n", btoe(key));
	} else {
		size_t i;
		for (i = 0; i < (seq - n + 1); i++) secure_hash(key);
		for (; i < seq + 1; i++) {
			printf("%zu: %-29s\n", i, btoe(key));
			secure_hash(key);
		}
	}
	
	exit(EXIT_SUCCESS);

}

