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

/*
	This software module is a rewrite of the snk.c module to
use MD5. Wherever possible data structures and methods have been
exactly converted. Since this software uses a cryptographic one
way function that is designed for authentication only (unlike DES
which is useable for privacy) it is exportable. Some ugliness of
code (for which I apologise) results from the translation.

	The SNK authentication routines store the secret calculator
key as a set of 8 chars, encoded in hex values in the authentication
database as the user's password. These 8 values are converted into
a DES ECB mode key by the make_key_sched routine. For the MD5 version,
we use a similar approach. The user's MD5 seed is stored as 8 chars
also hex coded. This is used as an initializer to the MD5 function,
then followed by the challenge. The user performs the same operation
and if the values compare correctly, we know it is him.
	Where this system deviates from the SNK authentication scheme
is that the user remembers a password string. The softmd5.c authenticator
prompts the user for their secret password string, then MD5s is, and
pulls the first 8 chars out to use as the initializer to the MD5 function.
With an SNK, this step is unnecessary, since the user's "password" is
used to unlock the stored DES key. All other transformations performed
on the key are the same, except we don't use DES ECB.

*/

/*
From the snk.c code:
For those desiring to test their implementation, this is a dialog
that will serve as a key base for a functional SNK004:

? softsnk -I -f foo
Enter key:
Verifying password Enter key:
Enter key groups in octal:
group 1: 123
group 2: 123
group 3: 123
group 4: 123
group 5: 123
group 6: 123
group 7: 123
group 8: 123
wrote key, checksum is 17d819
? softsnk -f foo
Enter key:
Challenge: 987654
Response: 23162720
? softsnk -f foo
Enter key:
Challenge: 1234567
Response: 26686397
?
script done on Sat Nov 19 13:27:00 1994
*/


#include	<ctype.h>
#include	<inttypes.h>
#include	<stdlib.h>
#include	<stdio.h>
#include	<string.h>
#include	<strings.h>
#include	<sys/param.h>

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

#ifdef	AUTHPROTO_MDAUTH
#include	"global.h"
#ifdef	OpenBSD
#include	"ssl/md5.h"
#else
#include	"openssl/md5.h"
#endif

static	char	*moduleId ATTR_UNUSED = "$Id: mdauth.c,v 1.8 2007/09/10 02:49:12 arkenoi Exp $";
extern	long	randomnumber();
extern  size_t 	strlcat(char*,const char*,size_t);
extern  size_t 	strlcpy(char*,const char*,size_t);

typedef unsigned char md5_cblock[8];


static	int	challenged = 0;
static	char	challbuf[32];

int
mdauthchallng(user,buf,bs)
char	*user;
char	*buf;
int	bs;
{
	challenged = 1;
	strlcpy(buf,"MD5 Challenge \"",MAX_STR);
	snprintf(challbuf,32,"%6.6lu",randomnumber() % 999999);
	strlcat(buf,challbuf,MAX_STR);
	strlcat(buf,"\": ",MAX_STR);
	return(0);
}


/* is this ugly or what? it's late and I don't feel clever */
static	int
make_key_sched(s,k)
char		*s;
md5_cblock	k;
{
	unsigned int	k0;
	unsigned int	k1;
	unsigned int	k2;
	unsigned int	k3;
	unsigned int	k4;
	unsigned int	k5;
	unsigned int	k6;
	unsigned int	k7;
	int	x;

	x = sscanf(s, "%o %o %o %o %o %o %o %o",
		&k0, &k1, &k2, &k3, &k4, &k5, &k6, &k7);
	if(x != 8)
		return(1);
	k[0] = k0;
	k[1] = k1;
	k[2] = k2;
	k[3] = k3;
	k[4] = k4;
	k[5] = k5;
	k[6] = k6;
	k[7] = k7;
	return(0);
}


int
mdauthverify(user,pass,ap,rbuf)
char	*user;
char	*pass;
Auth	*ap;
char	*rbuf;
{
	MD5_CTX			keysched;
	md5_cblock		kblock;
	unsigned char		digest[16];
	char			buf[12];
	char			cbuf[12];
	int			i;
	int			j;
	unsigned long		kval = 0;

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

	/* lowercase the response code, in case it's hex */
	for(i=0; pass[i]; i++)
		if(isupper(pass[i]))
			pass[i] = tolower(pass[i]);

	/* set up a key from the shared secret */
	if(make_key_sched(ap->pw,kblock)) {
		strlcpy(rbuf,"Cannot decode user secret key",MAX_STR);
		return(1);
	}


	/* zeroize the entire buffer */
	for(i = 0; i < 9; i++)
		buf[i] = '\0';
	strncpy(buf,challbuf,8);

	/* begin MD5 specific code */
	MD5_Init(&keysched);
	/* set the "key" (initializer) into MD5 */
	MD5_Update(&keysched,kblock,8);

	/* push response through the rotating knives; normally DES ECB */
	MD5_Update(&keysched,buf,8);
	/* wrap up MD5 into digest */
	MD5_Final(digest,&keysched);
	/* emulate DES by pulling 8 bytes into cbuf */
	bcopy(digest,cbuf,8);
	/* done MD5 specific code */

	/* pull some bits out of the ciphertext into a long */
	for(i=0; i<4; i++)
		for(j = 0; j < 8; j++)
			kval = (kval << 1) | ((cbuf[i] >> (7 - j)) & 1);

	/* crunch it into a hex string */
	snprintf(buf,sizeof(buf),"%08lx",kval);
	if(!strcmp(pass,buf)) {
		strlcpy(rbuf,"ok",MAX_STR);
		return(0);
	}

	/* crunch hex to decimal and try that */
	for(i=0; buf[i]; i++)
		if(buf[i] == 'a' || buf[i] == 'b' || buf[i] == 'c')
			buf[i] = '2';
		else
			if(buf[i] == 'd' || buf[i] == 'e' || buf[i] == 'f')
				buf[i] = '3';
	if(strcmp(pass,buf))
		return(1);
	strlcpy(rbuf,"ok",MAX_STR);
	return(0);
}

int
mdauthset(user,pass,ap,rbuf)
char	*user;
char	*pass;
Auth	*ap;
char	*rbuf;
{
	md5_cblock		kblock;

	if(make_key_sched(pass,kblock)) {
		strlcpy(rbuf,"Cannot decode user secret key",MAX_STR);
		return(0);
	}
	if(strlen(pass) >= AUTH_PWSIZ) {
		strlcpy(rbuf,"Secret key too long",MAX_STR);
		return(0);
	}
	strlcpy(ap->pw,pass,AUTH_PWSIZ);
	if(auth_dbputu(user,ap) == 0)
		strlcpy(rbuf,"Secret key changed",MAX_STR);
	else
		strlcpy(rbuf,"Database error.",MAX_STR);
	return(0);
}
#endif
