/*
 * Copyright (c) 1996-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

#include	<stdio.h>
#include	<sys/types.h>
#include	<time.h>
#include	<inttypes.h>

#include <stdlib.h>
#include <unistd.h>

#include <ctype.h>
#include <strings.h>
#include <sys/param.h>

#ifdef	OpenBSD
#include <ssl/md4.h>
#include <ssl/md5.h>
#include <ssl/sha.h>
#include <ssl/ripemd.h>
#else
#include <openssl/md4.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <openssl/ripemd.h>
#endif

#include	"firewall.h"
#include	"firewall2.h"
#include	"auth.h"
#include	"authdb.h"

static	char	*moduleId ATTR_UNUSED = "$Id: skey.c,v 1.10 2007/09/10 02:49:13 arkenoi Exp $";

#ifdef	AUTHPROTO_SKEY

#include	"skey.h"
#include	<string.h>

#define MAX_PROTONAME_LEN  20

/* Default hash function to use (index into skey_hash_types array) */
#ifndef SKEY_HASH_DEFAULT
#define SKEY_HASH_DEFAULT	1
#endif


typedef struct {
        int n;
        char seed[SKEY_MAX_SEED_LEN];
        char val[SKEY_MAX_CHALLENGE];
} skeydb;


int s_keyset(char*,char*,Auth*,char*);

static void f_md4(char *x);
static void f_md5 (char *x);
static void f_sha1(char *x);
static void f_rmd160(char *x);
static int keycrunch_md4(char *result, char *seed, char *passwd);
static int keycrunch_md5(char *result, char *seed, char *passwd);
static int keycrunch_sha1(char *result, char *seed, char *passwd);
static int keycrunch_rmd160(char *result, char *seed, char *passwd);
static void lowcase(char *s);

/* Current hash type (index into skey_hash_types array) */
static int skey_hash_type = SKEY_HASH_DEFAULT;

/*
 * Hash types we support.
 * Each has an associated keycrunch() and f() function.
 */
#define SKEY_ALGORITH_LAST	4
struct skey_algorithm_table {
	const char *name;
	int (*keycrunch) (char *, char *, char *);
	void (*f) (char *);
};
static struct skey_algorithm_table skey_algorithm_table[] = {
	{ "md4", keycrunch_md4, f_md4 },
	{ "md5", keycrunch_md5, f_md5 },
	{ "sha1", keycrunch_sha1, f_sha1 },
	{ "rmd160", keycrunch_rmd160, f_rmd160 }
};


static	int		challenged = 0;

extern	AProto		*codetoproto();

char* proto2hashname(char proto_typ)
{
  static char hashname[SKEY_MAX_HASHNAME_LEN + 1];
  char protoname[MAX_PROTONAME_LEN + 1];
  AProto* proto;
  char *proto_extr;
  int i=0;

  hashname[SKEY_MAX_HASHNAME_LEN] = protoname[MAX_PROTONAME_LEN] = '\0';

  strncpy(protoname, (proto = codetoproto(proto_typ)) ? proto->name : "skey-md4", MAX_PROTONAME_LEN);

  if((proto_extr = rindex(protoname, '-')))
  	while(i < SKEY_MAX_HASHNAME_LEN && (hashname[i++] = tolower(*++proto_extr))) ;
  else
  	strncpy(hashname, "md4", SKEY_MAX_HASHNAME_LEN);

  return hashname;
}


int
s_keychallng(user,buf,bs)
char	*user;
char	*buf;
int	bs;
{
	Auth    userrec;
	skeydb* skey;

	strlcpy(buf,"Skey Challenge ",MAX_STR);

	if(!auth_dbgetu(user, &userrec) && userrec.authproto_rec.active) {
		skey_set_algorithm(proto2hashname(userrec.atyp));
		skey = (skeydb *)userrec.authproto_rec.buf;
		(void)snprintf(&buf[15], MAX_STR-16, "otp-%.*s %d %.*s",
			SKEY_MAX_HASHNAME_LEN, skey_get_algorithm(),
			skey->n - 1, SKEY_MAX_SEED_LEN, skey->seed);

		strlcat(buf, " :", MAX_STR);
		challenged = 1;
		return(0);
	}
	else {
		snprintf(buf,MAX_STR,"Skey not initialized for %s",user);
		return(1);
	}
}


int
s_keyverify(user,pass,ap,rbuf)
char	*user;
char	*pass;
Auth	*ap;
char	*rbuf;
{

	char key[SKEY_BINKEY_SIZE];
	char fkey[SKEY_BINKEY_SIZE];
	char filekey[SKEY_BINKEY_SIZE];
	time_t now;
	struct tm *tm;
	char tbuf[27];
	skeydb* skey;

	strlcpy(rbuf,"Permission Denied.",MAX_STR);
	if(!challenged)
		return(1);
	challenged = 0;


	time(&now);
	tm = localtime(&now);
	(void)strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm);

	if (pass == NULL) {
		return(1);
	}
	rip(pass);

	/* Convert pass to binary */
	if (etob(key, pass) != 1 && atob8(key, pass) != 0) {
		/* Neither english words or ascii hex */
		return(1);
	}

	/* Compute fkey = f(key) */
	(void)memcpy(fkey, key, sizeof(key));
        (void)fflush(stdout);
	f(fkey);


	/* Reread the s/key record NOW and leave DB  (and open) */
	/*
	 * Obtain an exclusive lock on the DB file so the same password
	 * cannot be used twice to get in to the system.
	 */
	if(auth_dbgetu_forupdate(user, ap) || !ap->authproto_rec.active) {
	  auth_dbclose();
	  return(1);
	}

	skey_set_algorithm(proto2hashname(ap->atyp));

	skey = (skeydb *)ap->authproto_rec.buf;

	/* And convert file value to hex for comparison */
	atob8(filekey, skey->val);

	/* Do actual comparison */
	if (memcmp(filekey, fkey, SKEY_BINKEY_SIZE) != 0){
		/* Wrong pass */
		auth_dbclose();
		return(1);
	}

	btoa8(skey->val,key);
	skey->n--;
	ap->authproto_rec.active = (skey->n > 0);

	if(auth_dbputu(user,ap)) {
                sosay(0, "database error");
		auth_dbclose();  /* Unlock DB file and close it */
                return(1);
        }

	/* Unlock DB file and close it */
	auth_dbclose();

	strlcpy(rbuf,"ok",MAX_STR);
	if(skey->n < 5)
		strlcat(rbuf,"Warning -- Change password soon!",MAX_STR);
	return(0);

}



#define NAMELEN 2

int
s_keyset_md4(user,pass,ap,rbuf)
char	*user;
char	*pass;
Auth	*ap;
char	*rbuf;
{
	skey_set_algorithm("md4");
	return(s_keyset(user,pass,ap,rbuf));
}

int
s_keyset_md5(user,pass,ap,rbuf)
char	*user;
char	*pass;
Auth	*ap;
char	*rbuf;
{
	skey_set_algorithm("md5");
	return(s_keyset(user,pass,ap,rbuf));
}

int
s_keyset_sha1(user,pass,ap,rbuf)
char	*user;
char	*pass;
Auth	*ap;
char	*rbuf;
{
	skey_set_algorithm("sha1");
	return(s_keyset(user,pass,ap,rbuf));
}

int
s_keyset_rmd160(user,pass,ap,rbuf)
char	*user;
char	*pass;
Auth	*ap;
char	*rbuf;
{
	skey_set_algorithm("rmd160");
	return(s_keyset(user,pass,ap,rbuf));
}

/* this is a ripped-up version of skeyinit.c from the skey dist'n */
int
s_keyset(user,pass,ap,rbuf)
char	*user;
char	*pass;
Auth	*ap;
char	*rbuf;
{
	struct	tm	*tm;
	int		n;
	int		nn;
	char		seed[18];
	char		key[8];
	char		defaultseed[17];
	time_t		now;
	char		tbuf[27];
	char		hbuf[MAX_HOSTNAME];
	char		lastc;
	int		l;
	char		alg[32];
	skeydb* skey;

	if((pass == (char *)0) || (*pass == '\0')) {
		strlcpy(rbuf,"NULL passwords not permitted for S/key.",MAX_STR);
		return(1);
	}
	time(&now);
	tm = localtime(&now);
	strlcpy(alg,skey_get_algorithm(),sizeof(alg));
	strftime(tbuf, sizeof(tbuf), "%M%j", tm);
	gethostname(hbuf,sizeof(hbuf));
	defaultseed[0] = hbuf[0];
	defaultseed[1] = hbuf[1];
	strlcpy(&defaultseed[NAMELEN],tbuf,sizeof(defaultseed)-NAMELEN);

	if(ap == NULL) {
		strlcpy(rbuf,"Database error.",MAX_STR);
		return(1);
	}
	else {
		skey = (skeydb *)ap->authproto_rec.buf;

		if(!ap->authproto_rec.active)
			skey->seed[0] = '\0';
		else {
			l = strlen(skey->seed);
			if(l > 0) {
				lastc = skey->seed[l-1];
				if(isdigit(lastc) && lastc != '9') {
					strlcpy(defaultseed, skey->seed, sizeof(defaultseed));
					defaultseed[l-1] = lastc + 1;
				}
				if(isdigit(lastc) && lastc == '9' && l < 16) {
					strlcpy(defaultseed, skey->seed, sizeof(defaultseed));
					defaultseed[l-1] = '0';
					defaultseed[l] = '0';
					defaultseed[l+1] = '\0';
				}
			}
		}
	}
	skey_set_algorithm(alg);
	n = 664;
	strlcpy(seed,defaultseed,sizeof(seed));
	/* Crunch seed and password into starting key */
	if(keycrunch(key,seed,pass) != 0) {
		strlcpy(rbuf,"Database error.",MAX_STR);
		return(1);
	}
	nn = n;
	while(nn-- != 0)
		f(key);
	time(&now);
	tm = localtime(&now);
	strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm);

	btoa8(skey->val,key);
        skey->n = n;
	strncpy(skey->seed, seed, SKEY_MAX_SEED_LEN);
	ap->authproto_rec.active = 1;

	if(auth_dbputu(user,ap)) {
                sosay(0, "database error");
                return(1);
        }

	snprintf(rbuf,MAX_STR,"ID %s s/key-%s is %d %s",user,alg,n,seed);
	return(0);
}



/*
 * Crunch a key:
 * concatenate the seed and the password, run through MD4_/5 and
 * collapse to 64 bits. This is defined as the user's starting key.
 */
int
keycrunch(result, seed, passwd)
	char *result;	/* SKEY_BINKEY_SIZE result */
	char *seed;	/* Seed, any length */
	char *passwd;	/* Password, any length */
{
	return(skey_algorithm_table[skey_hash_type].keycrunch(result, seed, passwd));
}

static int
keycrunch_md4(result, seed, passwd)
	char *result;	/* SKEY_BINKEY_SIZE result */
	char *seed;	/* Seed, any length */
	char *passwd;	/* Password, any length */
{
	char *buf;
	MD4_CTX md;
	uint32_t results[4];
	unsigned int buflen;

	buflen = strlen(seed) + strlen(passwd);
	if ((buf = (char *)xmalloc(buflen+1)) == NULL)
		return(-1);
	(void)strlcpy(buf, seed, buflen);
	lowcase(buf);
	(void)strlcat(buf, passwd, buflen+1);

	/* Crunch the key through MD4_ */
	sevenbit(buf);
	MD4_Init(&md);
	MD4_Update(&md, (unsigned char *)buf, buflen);
	MD4_Final((unsigned char *)results, &md);
	(void)free(buf);

	/* Fold result from 128 to 64 bits */
	results[0] ^= results[2];
	results[1] ^= results[3];

	(void)memcpy((void *)result, (void *)results, SKEY_BINKEY_SIZE);

	return(0);
}

static int
keycrunch_md5(result, seed, passwd)
	char *result;	/* SKEY_BINKEY_SIZE result */
	char *seed;	/* Seed, any length */
	char *passwd;	/* Password, any length */
{
	char *buf;
	MD5_CTX md;
	uint32_t results[4];
	unsigned int buflen;

	buflen = strlen(seed) + strlen(passwd);
	if ((buf = (char *)xmalloc(buflen+1)) == NULL)
		return(-1);
	(void)strlcpy(buf, seed, buflen);
	lowcase(buf);
	(void)strlcat(buf, passwd, buflen+1);

	/* Crunch the key through MD5_ */
	sevenbit(buf);
	MD5_Init(&md);
	MD5_Update(&md, (unsigned char *)buf, buflen);
	MD5_Final((unsigned char *)results, &md);
	(void)free(buf);

	/* Fold result from 128 to 64 bits */
	results[0] ^= results[2];
	results[1] ^= results[3];

	(void)memcpy((void *)result, (void *)results, SKEY_BINKEY_SIZE);

	return(0);
}

static int
keycrunch_sha1(result, seed, passwd)
	char *result;	/* SKEY_BINKEY_SIZE result */
	char *seed;	/* Seed, any length */
	char *passwd;	/* Password, any length */
{
	char *buf;
	SHA_CTX sha;
	uint32_t results[5];
	unsigned int buflen;

	buflen = strlen(seed) + strlen(passwd);
	if ((buf = (char *)xmalloc(buflen+1)) == NULL)
		return(-1);
	(void)strlcpy(buf, seed, buflen);
	lowcase(buf);
	(void)strlcat(buf, passwd, buflen+1);

	/* Crunch the key through SHA1 */
	sevenbit(buf);
	SHA1_Init(&sha);
	SHA1_Update(&sha, (unsigned char *)buf, buflen);
	SHA1_Final((unsigned char *)results, &sha);
	(void)free(buf);

	/* Fold 160 to 64 bits */
	results[0] ^= results[2];
	results[1] ^= results[3];
	results[0] ^= results[4];

	(void)memcpy((void *)result, (void *)results, SKEY_BINKEY_SIZE);

	return(0);
}

static int
keycrunch_rmd160(result, seed, passwd)
	char *result;	/* SKEY_BINKEY_SIZE result */
	char *seed;	/* Seed, any length */
	char *passwd;	/* Password, any length */
{
	char *buf;
	RIPEMD160_CTX rmd;
	uint32_t results[5];
	unsigned int buflen;

	buflen = strlen(seed) + strlen(passwd);
	if ((buf = (char *)xmalloc(buflen+1)) == NULL)
		return(-1);
	(void)strlcpy(buf, seed, buflen);
	lowcase(buf);
	(void)strlcat(buf, passwd, buflen+1);

	/* Crunch the key through RMD-160 */
	sevenbit(buf);
	RIPEMD160_Init(&rmd);
	RIPEMD160_Update(&rmd, (unsigned char *)buf, buflen);
	RIPEMD160_Final((unsigned char *)results, &rmd);
	(void)free(buf);

	/* Fold 160 to 64 bits */
	results[0] ^= results[2];
	results[1] ^= results[3];
	results[0] ^= results[4];

	(void)memcpy((void *)result, (void *)results, SKEY_BINKEY_SIZE);

	return(0);
}

/*
 * The one-way function f().
 * Takes SKEY_BINKEY_SIZE bytes and returns SKEY_BINKEY_SIZE bytes in place.
 */
void
f(x)
	char *x;
{
	skey_algorithm_table[skey_hash_type].f(x);
}

static void
f_md4(x)
	char *x;
{
	MD4_CTX md;
	uint32_t results[4];

	MD4_Init(&md);
	MD4_Update(&md, (unsigned char *)x, SKEY_BINKEY_SIZE);
	MD4_Final((unsigned char *)results, &md);

	/* Fold 128 to 64 bits */
	results[0] ^= results[2];
	results[1] ^= results[3];

	(void)memcpy((void *)x, (void *)results, SKEY_BINKEY_SIZE);
}

static void
f_md5(x)
	char *x;
{
	MD5_CTX md;
	uint32_t results[4];

	MD5_Init(&md);
	MD5_Update(&md, (unsigned char *)x, SKEY_BINKEY_SIZE);
	MD5_Final((unsigned char *)results, &md);

	/* Fold 128 to 64 bits */
	results[0] ^= results[2];
	results[1] ^= results[3];

	(void)memcpy((void *)x, (void *)results, SKEY_BINKEY_SIZE);
}

static void
f_sha1(x)
	char *x;
{
	SHA_CTX sha;
	uint32_t results[5];

	SHA1_Init(&sha);
	SHA1_Update(&sha, (unsigned char *)x, SKEY_BINKEY_SIZE);
	SHA1_Final((unsigned char *)results, &sha);

	/* Fold 160 to 64 bits */
	results[0] ^= results[2];
	results[1] ^= results[3];
	results[0] ^= results[4];

	(void)memcpy((void *)x, (void *)results, SKEY_BINKEY_SIZE);
}

static void
f_rmd160(x)
	char *x;
{
	RIPEMD160_CTX rmd;
	uint32_t results[5];

	RIPEMD160_Init(&rmd);
	RIPEMD160_Update(&rmd, (unsigned char *)x, SKEY_BINKEY_SIZE);
	RIPEMD160_Final((unsigned char *)results, &rmd);

	/* Fold 160 to 64 bits */
	results[0] ^= results[2];
	results[1] ^= results[3];
	results[0] ^= results[4];

	(void)memcpy((void *)x, (void *)results, SKEY_BINKEY_SIZE);
}

/* Strip trailing cr/lf from a line of text */
void
rip(buf)
	char *buf;
{
	buf += strcspn(buf, "\r\n");

	if (*buf)
		*buf = '\0';
}

/* Skip leading spaces from the string */
char *
skipspace(cp)
	register char *cp;
{
	while (*cp == ' ' || *cp == '\t')
		cp++;

	if (*cp == '\0')
		return(NULL);
	else
		return(cp);
}

/* Make sure line is all seven bits */
void
sevenbit(s)
	char *s;
{
	while (*s)
		*s++ &= 0x7f;
}

/* Convert string to lower case */
static void
lowcase(s)
	char *s;
{
	char *p;

	for (p = s; *p; p++)
		if (isupper(*p))
			*p = tolower(*p);
}

/* Remove backspaced over characters from the string */
void
backspace(buf)
	char *buf;
{
	char bs = 0x8;
	char *cp = buf;
	char *out = buf;

	while (*cp) {
		if (*cp == bs) {
			if (out == buf) {
				cp++;
				continue;
			} else {
				cp++;
				out--;
			}
		} else {
			*out++ = *cp++;
		}

	}
	*out = '\0';
}


/*
 * Convert 8-byte hex-ascii string to binary array
 * Returns 0 on success, -1 on error
 */
int
atob8(out, in)
	register char *out;
	register char *in;
{
	register int i;
	register int val;

	if (in == NULL || out == NULL)
		return(-1);

	for (i=0; i < 8; i++) {
		if ((in = skipspace(in)) == NULL)
			return(-1);
		if ((val = htoi(*in++)) == -1)
			return(-1);
		*out = val << 4;

		if ((in = skipspace(in)) == NULL)
			return(-1);
		if ((val = htoi(*in++)) == -1)
			return(-1);
		*out++ |= val;
	}
	return(0);
}

/* Convert 8-byte binary array to hex-ascii string */
int
btoa8(out, in)
	register char *out;
	register char *in;
{
	register int i;

	if (in == NULL || out == NULL)
		return(-1);

	for (i=0; i < 8; i++) {
		(void)snprintf(out, 3, "%02x", *in++ & 0xff);
		out += 2;
	}
	return(0);
}

/* Convert hex digit to binary integer */
int
htoi(c)
	register int c;
{
	if ('0' <= c && c <= '9')
		return(c - '0');
	if ('a' <= c && c <= 'f')
		return(10 + c - 'a');
	if ('A' <= c && c <= 'F')
		return(10 + c - 'A');
	return(-1);
}

/* Set hash algorithm type */
char *
skey_set_algorithm(new)
	char *new;
{
	int i;

	for (i = 0; i < SKEY_ALGORITH_LAST; i++) {
		if (strcmp(new, skey_algorithm_table[i].name) == 0) {
			skey_hash_type = i;
			return(new);
		}
	}

	return(NULL);
}

/* Get current hash type */
const char *
skey_get_algorithm()
{
	return(skey_algorithm_table[skey_hash_type].name);
}



#endif


