/* The password generator for any resources with the one "master" password

   The original idea by http://relf.livejournal.com/103948.html
   It make MD5 hash for a concatenated string "masterresource"
	and base64 encode a need first bytes of hash.
   This program is likely this example code:
   echo -n "$master""$resource" | md5sum | \
    perl -e 'print(pack("H*",substr(<>, 0, 12)))' | \
    uuencode -m /dev/stdout


    Usage: unipass [-m master_password] [-r resource] [-l use_hash_bytes]
    if the -m option do not present, the program ask a password
	in the hide mode.
    if the -r option do not present, the program ask a resource
	in the regular mode.
    if the -l option do not present, the program use 6 bytes of the MD5 hash
	and generate a 8 bytes long password. The argument for the -l option
	must be 3,6,9,12 or 15 the value. The result password is 4..20 bytes
	long. The base64 have encode the 3 may be binary bytes to a 4 ASCII
	bytes.

  Credentials:
    md5_lib - Compute MD5 checksum of strings according to the
	    definition of MD5 in RFC 1321 from April 1992.

    Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.

    Copyright (C) 1995-1999 Free Software Foundation, Inc.
    Vlastimil Klima (C) March, 2006,

    Base64 encoder stolen from busybox
    based on the function base64_encode from http.c in wget v1.6
    Copyright (C) 1995, 1996, 1997, 1998, 2000 Free Software Foundation, Inc.
    and optimize by
    (C) 2003 Vladimir Oleynik <dzo@simtreas.ru>

    Licensed under the GPL v2 or later, see the file LICENSE in this tarball.

    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.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include <getopt.h>
#include <sys/ioctl.h>

#define PASSWORD_BUFFER_SIZE 128

static char *
askpass(const char * prompt, char passwd[PASSWORD_BUFFER_SIZE], int hide)
{
	char *ret;
	int i;
	struct termios old, new;

	if(hide) {
	    tcgetattr(STDIN_FILENO, &old);
	    tcflush(STDIN_FILENO, TCIFLUSH);
	}

	ret = passwd;
	memset(passwd, 0, PASSWORD_BUFFER_SIZE);

	fputs(prompt, stdout);
	fflush(stdout);

	if(hide) {
	    tcgetattr(STDIN_FILENO, &new);
	    new.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY);
	    new.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP);
	    tcsetattr(STDIN_FILENO, TCSANOW, &new);
	}

	if (read(STDIN_FILENO, passwd, PASSWORD_BUFFER_SIZE-1) <= 0) {
		ret = NULL;
	} else {
		for(i = 0; i < PASSWORD_BUFFER_SIZE && passwd[i]; i++) {
			if (passwd[i] == '\r' || passwd[i] == '\n') {
				passwd[i] = 0;
				break;
			}
		}
	}

	if(hide) {
	    tcsetattr(STDIN_FILENO, TCSANOW, &old);
	    fputs("\n", stdout);
	}
	fflush(stdout);
	return ret;
}

typedef struct _MD5_CTX
{
  unsigned long  state[4];
  unsigned long  count[2];
  unsigned char  data[64];
} MD5_CTX;


#define F1(x, y, z) ( ((x) & (y)) | ((~x) & (z)) )
#define F2(x, y, z) ( ((x) & (z)) | ((y) & (~z)) )
#define F3(x, y, z) ( (x) ^ (y) ^ (z) )
#define F4(x, y, z) ( (y) ^ ((x) | (~z)) )

#define RL(x, n) ( ((x) << (n)) | ((x) >> (32-(n))) )

#define Sub1(a, b, c, d, e, f, g) { \
	(a) += F1((b), (c), (d)) + (e) + (unsigned long)(g); \
	(a) = RL((a), (f)); \
	(a) += (b); \
	}

#define Sub2(a, b, c, d, e, f, g) { \
	(a) += F2((b), (c), (d)) + (e) + (unsigned long)(g); \
	(a) = RL((a), (f)); \
	(a) += (b); \
	}

#define Sub3(a, b, c, d, e, f, g) { \
	(a) += F3((b), (c), (d)) + (e) + (unsigned long)(g); \
	(a) = RL((a), (f)); \
	(a) += (b); \
	}

#define Sub4(a, b, c, d, e, f, g) { \
	(a) += F4((b), (c), (d)) + (e) + (unsigned long)(g); \
	(a) = RL((a), (f)); \
	(a) += (b); \
	}

/*=============================================*/
static void long_to_byte(unsigned char *dst, const unsigned long *src, unsigned long size)
{
	unsigned long i, j;

	for (i = 0, j = 0; i < size; i += 4, j++)
	{
		dst[i]   = (unsigned char)(src[j]);
		dst[i+1] = (unsigned char)(src[j] >> 8);
		dst[i+2] = (unsigned char)(src[j] >> 16);
		dst[i+3] = (unsigned char)(src[j] >> 24);
	}
}

/*=============================================*/
static void byte_to_long(unsigned long *dst, const unsigned char *src, unsigned long size)
{
	unsigned long i, j;

	for (i = 0, j = 0; i < size; i += 4, j++)
	{
		dst[j] = ((unsigned long)src[i]) | (((unsigned long)src[i+1]) << 8) |
			(((unsigned long)src[i+2]) << 16) | (((unsigned long)src[i+3]) << 24);
	}
}

/*=============================================*/
static void Init_MD5(MD5_CTX *MD5_ctx)
{
	MD5_ctx->count[0] = 0;
	MD5_ctx->count[1] = 0;
	MD5_ctx->state[0] = 0x67452301;
	MD5_ctx->state[1] = 0xefcdab89;
	MD5_ctx->state[2] = 0x98badcfe;
	MD5_ctx->state[3] = 0x10325476;
}

/*=============================================*/
static void Process_One_Block_MD5(MD5_CTX *MD5_ctx, const unsigned char block[64])
{
	unsigned long a = MD5_ctx->state[0], b = MD5_ctx->state[1];
	unsigned long c = MD5_ctx->state[2], d = MD5_ctx->state[3];
	unsigned long e[16];

	byte_to_long(e, block, 64);

	Sub1(a, b, c, d, e[0], 7, 0xd76aa478);
	Sub1(d, a, b, c, e[1], 12, 0xe8c7b756);
	Sub1(c, d, a, b, e[2], 17, 0x242070db);
	Sub1(b, c, d, a, e[3], 22, 0xc1bdceee);
	Sub1(a, b, c, d, e[4], 7, 0xf57c0faf);
	Sub1(d, a, b, c, e[5], 12, 0x4787c62a);
	Sub1(c, d, a, b, e[6], 17, 0xa8304613);
	Sub1(b, c, d, a, e[7], 22, 0xfd469501);
	Sub1(a, b, c, d, e[8], 7, 0x698098d8);
	Sub1(d, a, b, c, e[9], 12, 0x8b44f7af);
	Sub1(c, d, a, b, e[10], 17, 0xffff5bb1);
	Sub1(b, c, d, a, e[11], 22, 0x895cd7be);
	Sub1(a, b, c, d, e[12], 7, 0x6b901122);
	Sub1(d, a, b, c, e[13], 12, 0xfd987193);
	Sub1(c, d, a, b, e[14], 17, 0xa679438e);
	Sub1(b, c, d, a, e[15], 22, 0x49b40821);

	Sub2(a, b, c, d, e[1], 5, 0xf61e2562);
	Sub2(d, a, b, c, e[6], 9, 0xc040b340);
	Sub2(c, d, a, b, e[11], 14, 0x265e5a51);
	Sub2(b, c, d, a, e[0], 20, 0xe9b6c7aa);
	Sub2(a, b, c, d, e[5], 5, 0xd62f105d);
	Sub2(d, a, b, c, e[10], 9,  0x2441453);
	Sub2(c, d, a, b, e[15], 14, 0xd8a1e681);
	Sub2(b, c, d, a, e[4], 20, 0xe7d3fbc8);
	Sub2(a, b, c, d, e[9], 5, 0x21e1cde6);
	Sub2(d, a, b, c, e[14], 9, 0xc33707d6);
	Sub2(c, d, a, b, e[3], 14, 0xf4d50d87);
	Sub2(b, c, d, a, e[8], 20, 0x455a14ed);
	Sub2(a, b, c, d, e[13], 5, 0xa9e3e905);
	Sub2(d, a, b, c, e[2], 9, 0xfcefa3f8);
	Sub2(c, d, a, b, e[7], 14, 0x676f02d9);
	Sub2(b, c, d, a, e[12], 20, 0x8d2a4c8a);

	Sub3(a, b, c, d, e[5], 4, 0xfffa3942);
	Sub3(d, a, b, c, e[8], 11, 0x8771f681);
	Sub3(c, d, a, b, e[11], 16, 0x6d9d6122);
	Sub3(b, c, d, a, e[14], 23, 0xfde5380c);
	Sub3(a, b, c, d, e[1], 4, 0xa4beea44);
	Sub3(d, a, b, c, e[4], 11, 0x4bdecfa9);
	Sub3(c, d, a, b, e[7], 16, 0xf6bb4b60);
	Sub3(b, c, d, a, e[10], 23, 0xbebfbc70);
	Sub3(a, b, c, d, e[13], 4, 0x289b7ec6);
	Sub3(d, a, b, c, e[0], 11, 0xeaa127fa);
	Sub3(c, d, a, b, e[3], 16, 0xd4ef3085);
	Sub3(b, c, d, a, e[6], 23,  0x4881d05);
	Sub3(a, b, c, d, e[9], 4, 0xd9d4d039);
	Sub3(d, a, b, c, e[12], 11, 0xe6db99e5);
	Sub3(c, d, a, b, e[15], 16, 0x1fa27cf8);
	Sub3(b, c, d, a, e[2], 23, 0xc4ac5665);

	Sub4(a, b, c, d, e[0], 6, 0xf4292244);
	Sub4(d, a, b, c, e[7], 10, 0x432aff97);
	Sub4(c, d, a, b, e[14], 15, 0xab9423a7);
	Sub4(b, c, d, a, e[5], 21, 0xfc93a039);
	Sub4(a, b, c, d, e[12], 6, 0x655b59c3);
	Sub4(d, a, b, c, e[3], 10, 0x8f0ccc92);
	Sub4(c, d, a, b, e[10], 15, 0xffeff47d);
	Sub4(b, c, d, a, e[1], 21, 0x85845dd1);
	Sub4(a, b, c, d, e[8], 6, 0x6fa87e4f);
	Sub4(d, a, b, c, e[15], 10, 0xfe2ce6e0);
	Sub4(c, d, a, b, e[6], 15, 0xa3014314);
	Sub4(b, c, d, a, e[13], 21, 0x4e0811a1);
	Sub4(a, b, c, d, e[4], 6, 0xf7537e82);
	Sub4(d, a, b, c, e[11], 10, 0xbd3af235);
	Sub4(c, d, a, b, e[2], 15, 0x2ad7d2bb);
	Sub4(b, c, d, a, e[9], 21, 0xeb86d391);

	MD5_ctx->state[0] += a;
	MD5_ctx->state[1] += b;
	MD5_ctx->state[2] += c;
	MD5_ctx->state[3] += d;

	memset(e, 0, sizeof(e));
}


/*=============================================*/
static int Update_MD5(MD5_CTX *MD5_ctx, const unsigned char *buffer, unsigned long count)
{
	unsigned long i, rest, pad;

	if (count > 0xffffffc1)
	{
		Update_MD5(MD5_ctx, buffer, 0xffffffc1);
		Update_MD5(MD5_ctx, buffer + 0xffffffc1, count - 0xffffffc1);
		return 0;
	}
	rest = (MD5_ctx->count[0] >> 3) & 0x3F;

	if ((MD5_ctx->count[0] += (count << 3)) < (count << 3))
	{
		MD5_ctx->count[1]++;
	}
	MD5_ctx->count[1] += (count >> 29);
	pad = 64 - rest;

	if (count >= pad)
	{
		memcpy(MD5_ctx->data + rest, buffer, pad);
		Process_One_Block_MD5 (MD5_ctx, MD5_ctx->data);
		for (i = pad; i + 63 < count; i += 64)
		{
			Process_One_Block_MD5(MD5_ctx, buffer + i);
		}
		rest = 0;
	}
	else
	{
		i = 0;
	}

	memcpy(MD5_ctx->data + rest, buffer + i, count - i);

	return 0;
}
/*=============================================*/
static void Final_MD5(MD5_CTX *MD5_ctx)
{
	unsigned char bits[8];
	unsigned int rest, padLen;

	static unsigned char doplnek[64] = {
128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

	long_to_byte(bits, MD5_ctx->count, 8);

	rest = (MD5_ctx->count[0] >> 3) & 0x3f;
	padLen = (rest < 56) ? (56 - rest) : (120 - rest);
	Update_MD5(MD5_ctx, doplnek, padLen);

	Update_MD5(MD5_ctx, bits, 8);
}
/* end md5 lib */

static void
make_MD5 (unsigned char *data, int datalen, unsigned char *output)
{
  int i;
  static MD5_CTX MD5_ctx;

  Init_MD5 (&MD5_ctx);
  Update_MD5 (&MD5_ctx, data, datalen);
  Final_MD5 (&MD5_ctx);

  for (i = 0; i < 16; i++)
	output[i] = ((unsigned char *) MD5_ctx.state)[i];       //byl BUG
}


/* base64 lib */
/* Conversion table.  for base 64 */
static const char tbl_base64[] = {
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
  'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
  'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
  'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
  'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
  'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
  'w', 'x', 'y', 'z', '0', '1', '2', '3',
  '4', '5', '6', '7', '8', '9', '+', '/',
};


#if !defined(FULL_BASE64_LIB)
static void
#else
int
#endif
b64encode (const char *from, const char *store, const int length)
{
  int i;
  unsigned char *p = (unsigned char *) store;
  const unsigned char *s = (const unsigned char *) from;

  /* Transform the 3x8 bits to 4x6 bits, as required by base64.  */
  for (i = 0; i < length; i += 3) {
	*p++ = tbl_base64[s[0] >> 2];
	*p++ = tbl_base64[((s[0] & 3) << 4) + (s[1] >> 4)];
	*p++ = tbl_base64[((s[1] & 0xf) << 2) + (s[2] >> 6)];
	*p++ = tbl_base64[s[2] & 0x3f];
	s += 3;
  }
#if defined(FULL_BASE64_LIB)
  /* Pad the result if necessary...  */
  if (i == length + 1) {
	*(p - 1) = '=';
  } else if (i == length + 2) {
	*(p - 1) = *(p - 2) = '=';
  }
  /* ...and CR-terminate it.  */
  *p = '\n';

  return i / 3 * 4 + 1;
#endif
}
/* end base64 lib */

int main(int argc, char **argv)
{
    static char master[PASSWORD_BUFFER_SIZE];
    static char resource[PASSWORD_BUFFER_SIZE];
    static unsigned char md5_hash[17];
    static char base64_out[(15/3)*4 + 1];
    char *sum_str;
    char *m = NULL, *c = NULL;
    int opt;
    size_t l;
    long rez_sz = 6;
    const char *p, *prg_name;

    prg_name = argv[0];
    for (p = prg_name; *p; )
	if (*(p++) == '/') prg_name = p;

    while ((opt = getopt(argc, argv, "m:r:l:")) > 0) {
	switch(opt) {
	    case 'm':
		m = optarg;
		break;
	    case 'r':
		c = optarg;
		break;
	    case 'l':
		rez_sz = strtol(optarg, &sum_str, 0);
		if(rez_sz < 3 || rez_sz > 15 || (rez_sz % 3) != 0) {
		    fprintf(stderr, "Use: -l with 3,6,9,12 or 15 bytes\n");
		    return 4;
		}
		break;
	    default:
		fprintf(stderr, "Usage: %s [-m master_password] [-r resource] [-l use_hash_bytes]\n", prg_name);
		return 3;
	}
    }
    if(m == NULL) {
	askpass("Master password: ", master, 1);
	m = master;
    }
    if(*m == '\0') {
	fprintf(stderr, "%s: empty passwords is not safe, exiting\n", prg_name);
	return 3;
    }
    if(c == NULL) {
	askpass("Resource: ", resource, 0);
	c = resource;
    }
    l = strlen(m) + strlen(c);
    sum_str = malloc(l + 1);
    if(sum_str == NULL) {
	fprintf(stderr, "%s: memory exhausted\n", prg_name);
	return 2;
    }
    strcpy(sum_str, m);
    strcat(sum_str, c);
    /* clear master password buffer */
    while(*m != '\0')
	*m++ = '\0';
    make_MD5((unsigned char *)sum_str, l, md5_hash);
    /* clear sum with master password buffer */
    m = sum_str;
    while(*m != '\0')
	*m++ = '\0';

    b64encode(md5_hash, base64_out, rez_sz);
    return puts(base64_out) > 0;
}
