/*
 * MISC - Miscellaneous utility functions
 *
 * Author:
 * Emile van Bergen, emile@evbergen.xs4all.nl
 *
 * Permission to redistribute an original or modified version of this program
 * in source, intermediate or object code form is hereby granted exclusively
 * under the terms of the GNU General Public License, version 2. Please see the
 * file COPYING for details, or refer to http://www.gnu.org/copyleft/gpl.html.
 *
 * History:
 * 2001/06/25 - EvB - Moved hex and memrchr here from lang_vm.c
 * 2002/04/07 - EvB - Added encryption/decryption functions as used in
 * 		      RFC 2868 (tunneling attrs) and RFC 2548 (MS VSAs)
 * 2002/02/23 - EvB - Added encrypt_attr_pap 
 * 2005/06/01 - EvB - Added hmac_md5, moved get_random_data here from radclient
 */

char misc_id[] = "MISC - Copyright (C) 2001 Emile van Bergen.";


 /*
  * INCLUDES & DEFINES
  */


#include <string.h>		/* For memset / memcpy / strlen */
#include <unistd.h>		/* For open */
#include <stdlib.h>		/* For rand */
#include <fcntl.h>		/* For O_RDONLY */

#include <constants.h>
#include <metadata.h>		/* For putord / getord */
#include <debug.h>
#include <md5.h>

#include <misc.h>


/*
 * FUNCTIONS
 */


char *memrchr(char *s, int c, ssize_t len)
{
	char *ret;

	if (len && s) {
		for(ret = s + len - 1; ret >= s; ret--) 
			if (*ret == c) return ret;
	}
	return 0;
}


void hex(char *buf, const char *src, ssize_t len)
{
	static char *hextbl = "0123456789abcdef";

	for( ; len > 0; len--) {
		*buf++ = hextbl[(*src >> 4) & 0xf];
		*buf++ = hextbl[*src++ & 0xf];
	}
}


/* Get reasonably good random data */

#define RANDOM_DEV	"/dev/urandom"

void get_random_data(char *p, ssize_t len)
{
#ifdef RANDOM_DEV
	int fd;
	ssize_t n;

	if ((fd = open(RANDOM_DEV, O_RDONLY)) != -1 &&
	    (n = read(fd, p, len)) == len &&
	    close(fd) != -1) return;

	msg(F_MISC, L_NOTICE, "Warning: No data from " RANDOM_DEV ", using rand()\n");
#endif
	while(len-- > 0) *p++ = (char)(256.0 * rand() / (RAND_MAX + 1.0));
}


/* 
 * Encrypt/decrypt string attributes, User-Password (PAP) style (RFC2865)
 */


/* These functions allow encryption/decryption in place, so it's possible to
   set dst to src and dstlen pointing to srclen. Note that dstlen in no way
   limits the number of bytes output; in case of encryption that's always
   srclen rounded up to an even multiple of 16, and in case of decryption
   it's srclen or less, srclen - 15 minimum. */


void encrypt_attr_pap(char *src, META_ORD srclen,
		      char *dst, META_ORD *dstlen,
		      char *sec, META_ORD seclen,
		      char *auth, META_ORD authlen)
{
	char *i, *o, *p, md5buf[16], *m;
	int l, chunks, n, retlen;
	md5_state_t md5ctx;

	/* Use request authenticator after secret to hash first chunk */

	p = auth; l = authlen;

	/* Initialise */

	i = src; o = dst; retlen = 0;

	/* Process full chunks at beginning, if any */

	for(chunks = srclen >> 4; chunks > 0; chunks--) {

		md5_init(&md5ctx);
		md5_append(&md5ctx, sec, seclen);
		md5_append(&md5ctx, p, l);
		md5_finish(&md5ctx, md5buf);

		p = o; 
		l = 16;
		for(m = md5buf, n = l; n > 0; n--) 
			*o++ = *i++ ^ *m++;
		retlen += l;
	}

	/* Process partial chunk at end, if any */

	if (srclen & 15) {

		md5_init(&md5ctx);
		md5_append(&md5ctx, sec, seclen);
		md5_append(&md5ctx, p, l);
		md5_finish(&md5ctx, md5buf);

		for(m = md5buf, n = srclen & 15; n > 0; n--)
			*o++ = *i++ ^ *m++;
		memcpy(o, m, 16 - (srclen & 15));   /* 0 ^ m = m after all */
		retlen += 16;
	}

	*dstlen = retlen;
}


void decrypt_attr_pap(char *src, META_ORD srclen,
		      char *dst, META_ORD *dstlen,
		      char *sec, META_ORD seclen,
		      char *auth, META_ORD authlen)
{
	char *i, *o, md5buf[16], *m;
	int n, retlen;
	md5_state_t md5ctx;

	/* Initialise. Round down source length to multiple of 16 and
	 * exit if not at least one chunk. */

	srclen &= ~0xf; 
	if (!srclen) { *dstlen = 0; return; }
	i = src + srclen; o = dst + srclen; retlen = 0;

	/* Process chunks beyond first one, if any, using md5(sec . prev) 
	 * Everything happens in reverse to allow decrypting in place */

	while (i >= src + 32) {

		md5_init(&md5ctx);
		md5_append(&md5ctx, sec, seclen);
		md5_append(&md5ctx, i - 32, 16);
		md5_finish(&md5ctx, md5buf);

		for(n = 16, m = md5buf + n; n > 0; n--) *--o = *--i ^ *--m;
		retlen += 16;
	}

	/* Process first chunk using md5(sec . auth) */

	md5_init(&md5ctx);
	md5_append(&md5ctx, sec, seclen);
	md5_append(&md5ctx, auth, authlen);
	md5_finish(&md5ctx, md5buf);

	for(n = 16, m = md5buf + n; n > 0; n--) *--o = *--i ^ *--m;
	retlen += 16;

	*dstlen = retlen;
}

/*
 * Encrypt/decrypt string attributes, style 1 (RFC2868)
 *
 * See RFC 2868, section 3.5 for details. Currently probably indeed 
 * only useful for Tunnel-Password, but why make it a special case. 
 * It's optimized a little for speed, but it could probably be better.
 *
 * Both operations are done in place; for encryption, there must be
 * enough *extra* room for 16 - (cleartextlen + 1) % 16 + 2 bytes.
 * To be safe, you could simply have 18 bytes extra at all times. 
 * The maximum cleartext length is limited to 256 bytes, so 274 bytes
 * is the maximum that can ever get output.
 */

#define	CLEAR_STRING_LEN	256 	/* The RFC says it is */
#define AUTHENTICATOR_LEN	16	/* The RFC says it is */
#define	MD5_LEN			16	/* The algorithm specifies it */
#define	SALT_LEN		2	/* The RFC says it is */


void encrypt_attr_style_1(char *text, META_ORD *len, char *secret,char *reqauth)
{
	char clear_buf[CLEAR_STRING_LEN];
	char work_buf[C_MAX_SECRSIZE + AUTHENTICATOR_LEN + SALT_LEN];
	char digest[MD5_LEN];
	char *i,*o;
	unsigned short salt;
	int clear_len;
	int work_len;
	int secret_len;
	int n;

	/* Create the string we'll actually be processing by copying up to 255
	   bytes of original cleartext, padding it with zeroes to a multiple of
	   16 bytes and inserting a length octet in front. */

	/* Limit length */
	clear_len = *len;
	if (clear_len > CLEAR_STRING_LEN - 1) clear_len = CLEAR_STRING_LEN - 1;

	/* Write the 'limited original' length byte and copy the buffer */
	*clear_buf = clear_len;
	memcpy(clear_buf + 1, text, clear_len);

	/* From now on, the length byte is included with the byte count */
	clear_len++;

	/* Pad the string to a multiple of 1 chunk */
	if (clear_len % MD5_LEN) {
		memset(clear_buf+clear_len, 0, MD5_LEN - (clear_len % MD5_LEN));
	}

	/* Define input and number of chunks to process */
	i = clear_buf;
	clear_len = (clear_len + (MD5_LEN - 1)) / MD5_LEN;	

	/* Define output and its starting length */
	o = text;
	*len = sizeof(salt);

	/*
	 * Fill in salt. Must be unique per attribute that uses it in the same 
	 * packet, and the most significant bit must be set - see RFC 2868.
	 * We use the memory adddress of the text xor-ed with the longword
	 * from the request authenticator.
	 */
	salt = ( ((long)text ^ *(long *)reqauth) & 0xffff ) | 0x8000;
	putord(o, sizeof(salt), salt); o += sizeof(salt);

	/* Create a first working buffer to calc the MD5 hash over */
	secret_len = strlen(secret);	/* already limited by read_clients */
	memcpy(work_buf, secret, secret_len);
	memcpy(work_buf + secret_len, reqauth, AUTHENTICATOR_LEN);
	putord(work_buf + secret_len + AUTHENTICATOR_LEN, sizeof(salt), salt);
	work_len = secret_len + AUTHENTICATOR_LEN + sizeof(salt);

	for( ; clear_len; clear_len--) {

		/* Get the digest */
		md5(digest, work_buf, work_len);

		/* Xor the clear text to get the output chunk and next buffer */
		for(n = 0; n < MD5_LEN; n++) {
			*(work_buf + secret_len + n) = *o++ = *i++ ^ digest[n];
		}

		/* This is the size of the next working buffer */
		work_len = secret_len + MD5_LEN;

		/* Increment the output length */
		*len += MD5_LEN;
	}
}


int decrypt_attr_style_1(char *text, META_ORD *len, char *secret, char *reqauth)
{
	char work_buf[C_MAX_SECRSIZE + AUTHENTICATOR_LEN + SALT_LEN];
	char digest[MD5_LEN];
	char *i,*o;
	unsigned short salt;
	int chunks;
	int secret_len;
	int n;

	/* Check the length; the salt and at least one chunk must be present,
	   and partial chunks are not allowed. */
	if (((*len - SALT_LEN) < MD5_LEN) || 
	    ((*len - SALT_LEN) % MD5_LEN)) {
		msg(F_MISC, L_ERR, "decrypt_attr_style_1: bogus cyphertext "
				   "length %d\n", *len);
		return -1;
	}

	/* Define input */
	i = text;
	chunks = ((*len - SALT_LEN) + (MD5_LEN - 1)) / MD5_LEN;

	/* Define output - it's done in place, overwriting the salt! */
	o = text;

	/* Get the salt from the input and check it */
	salt = getord(i, sizeof(salt)); i += sizeof(salt); 
	if (!(salt & 0x8000)) {
		msg(F_MISC, L_ERR, "decrypt_attr_style_1: bogus salt 0x%04x\n",
		    salt);
		return -1;
	}

	/* Create a first working buffer to calculate the MD5 hash over */
	secret_len = strlen(secret);	/* already limited by read_clients */
	memcpy(work_buf, secret, secret_len);
	memcpy(work_buf + secret_len, reqauth, AUTHENTICATOR_LEN);
	putord(work_buf + secret_len + AUTHENTICATOR_LEN, sizeof(salt), salt);

	/* Calculate the digest */
	md5(digest, work_buf, secret_len+AUTHENTICATOR_LEN+sizeof(salt));

	/* Process the first byte: the cleartext length */
	*(work_buf + secret_len + 0) = *i;
	*len = *i++ ^ digest[0];

	if ((*len > CLEAR_STRING_LEN) ||
	    (*len > MD5_LEN * chunks) ||
	    (*len < MD5_LEN * (chunks - 1))) {
		msg(F_MISC, L_ERR, "decrypt_attr_style_1: bogus decrypted "
				   "length %d\n", *len);
		return -1;
	}

	/* Process the rest of the first chunk */
	for(n = 1; n < MD5_LEN; n++) {
		*(work_buf + secret_len + n) = *i;
		*o++ = *i++ ^ digest[n];
	}

	/* One full chunk is already done. */
	chunks--;

	/* Loop over the rest of the chunks */
	for( ; chunks > 0; chunks--) {

		/* Get the digest */
		md5(digest, work_buf, secret_len + MD5_LEN);

		/* Fill the work buffer with the next cyphertext chunk and
		   xor the it with the digest to get the output segment */
		for(n = 0; n < MD5_LEN; n++) {
			*(work_buf + secret_len + n) = *i;
			*o++ = *i++ ^ digest[n];
		}
	}

	return 0;
}


/* Compute HMAC using MD5 as a hash */

void hmac_md5(char *out, char *in, META_ORD inl, char *key, META_ORD keyl)
{
	static unsigned char ipad[64], opad[64], inner[16];
	unsigned char *ip = ipad, *op = opad;
	md5_state_t mds;

	if (keyl > 64) {
		md5_init(&mds);
		md5_append(&mds, key, keyl);
		md5_finish(&mds, inner);
		key = inner; keyl = 16;
	}
	memset(ipad, 0x36, 64); memset(opad, 0x5c, 64); 
	while(keyl--) { *ip++ ^= *key; *op++ ^= *key++; }
	md5_init(&mds);
	md5_append(&mds, ipad, 64);
	md5_append(&mds, (unsigned char *)in, inl);
	md5_finish(&mds, inner);
	md5_init(&mds);
	md5_append(&mds, opad, 64);
	md5_append(&mds, inner, 16);
	md5_finish(&mds, out);
}



syntax highlighted by Code2HTML, v. 0.9.1