/* Proxytunnel - (C) 2001-2008 Jos Visser / Mark Janssen    */
/* Contact:                  josv@osp.nl / maniac@maniac.nl */

/*
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/* ntlm.c -- Code for handling NTLM authentication */
/* NTLM Code from Paul Solomon <psolomon@tpg.com.au> */

#include "ntlm.h"
#include "global.h"
#include "base64.h"
#include <stdlib.h>
#include <string.h>
#include "proxytunnel.h"
#include <ctype.h>
#include <sys/time.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
	#include <openssl/provider.h>
	#include <openssl/evp.h>
#else
	#include <openssl/md4.h>
	#include <openssl/md5.h>
#endif

#define TYPE1_DATA_SEG 8
#define TYPE2_BUF_SIZE 2048
#define DOMAIN_BUFLEN 256
#define LM2_DIGEST_LEN 24

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
const EVP_MD *md4alg;
const EVP_MD *md5alg;
EVP_MD_CTX *mdctx;
#endif

int ntlm_challenge = 0;
void message( char *s, ... );
int unicode = 0;

unsigned char challenge[8];
char domain[DOMAIN_BUFLEN];
char workstation[] = "WORKSTATION";

unsigned char unipasswd[DOMAIN_BUFLEN * 2];

unsigned char t2_buf[TYPE2_BUF_SIZE];

unsigned char *pblob = NULL;
int bloblen;

unsigned char *t_info;
int t_info_len;

uint32_t flags;

unsigned char lm2digest[LM2_DIGEST_LEN];

void init_ntlm() {
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
	OSSL_PROVIDER_load(NULL, "default");
	OSSL_PROVIDER_load(NULL, "legacy");
	md4alg = EVP_md4();
	md5alg = EVP_md5();
	mdctx = EVP_MD_CTX_new();
#endif
}

void build_type1() {
	ntlm_type1 *type1;
	int len = sizeof(ntlm_type1) + sizeof(unsigned char) * TYPE1_DATA_SEG;

	type1 = (ntlm_type1 *)alloca(len);

	memset(type1, 0, len);
	type1->signature[0] = 'N';
	type1->signature[1] = 'T';
	type1->signature[2] = 'L';
	type1->signature[3] = 'M';
	type1->signature[4] = 'S';
	type1->signature[5] = 'S';
	type1->signature[6] = 'P';
	type1->signature[7] = '\0';

	type1->message_type = NTLM_TYPE_1;
	type1->flags = NEG_UNICODE | NEG_OEM | REQ_TARGET | NEG_NTLM | NEG_ASIGN | NEG_NTLM2 | NEG_128 | NEG_56 | IE_SETSTHIS;

	base64((unsigned char *)ntlm_type1_buf, (unsigned char *)type1, len);

	return;
}


int parse_type2(unsigned char *buf) {
	int len = unbase64(t2_buf, buf, TYPE2_BUF_SIZE);
	ntlm_type2 *t2 = (ntlm_type2 *)t2_buf;
	int i;

	if (len <= 0) {
		message("parse_type2: failed to decode the message\n");
		return -1;
	}

	if (strcmp((const char *)t2->signature, "NTLMSSP") != 0) {
		message("parse_type2: Signature did not match\n");
		return -1;
	}

	if( args_info.verbose_flag )
		message("parse_type2: Signature matched\n");

	if (t2->message_type != NTLM_TYPE_2) {
		message("parse_type2: Incorrect message type sent\n");
		return -1;
	}

	if (t2->target_name.length > 0 && t2->target_name.length < DOMAIN_BUFLEN && (t2->target_name.length + t2->target_name.offset < len)) {
		int sp = 1;
		if (t2->flags & NEG_UNICODE)
			sp = 2;
		for (i = 0; i < t2->target_name.length / sp; i++)
			domain[i] = t2_buf[t2->target_name.offset + i * sp];
		domain[i] = 0;
	} else {
		domain[0] = 0;
	}

	for (i = 0; i < 8; i++)
		challenge[i] = t2->challenge[i];

	if( args_info.verbose_flag )
		message("NTLM Got Domain: %s\n", domain);

	if( args_info.domain_given ) {
		if( ! args_info.quiet_flag )
			message( "NTLM Overriding domain: %s\n", args_info.domain_arg );
		for( i = 0; i < strlen(args_info.domain_arg); i++ ) {
			domain[i] = args_info.domain_arg[i];
		}
		domain[i] = 0;
	}

	if( args_info.verbose_flag ) {
		message("NTLM Domain: %s\n", domain);
		message("NTLM Got Challenge: ");

		for (i = 0; i < 8; i++)
			message("%02X", challenge[i]);
		message("\n");
	}

	if (!(t2->flags & NEG_NTLM && t2->flags & NEG_NTLM2)) {
		message("parse_type2: Sorry, only NTLMv2 is supported at this time\n");
		return -1;
	}

	if (t2->flags & NEG_UNICODE)
		unicode = 1;
	else
		unicode = 0;

	t_info = &t2_buf[t2->target_info.offset];
	t_info_len = t2->target_info.length;

	flags = t2->flags;

	ntlm_challenge = 1;

	build_ntlm2_response();

	return 0;
}


void build_type3_response() {
	unsigned char *t3;
	ntlm_type3 *type3;
	int len;
	int sp = 1;
	int i;

	if (unicode)
		sp = 2;

	len = sizeof(ntlm_type3) + sizeof(unsigned char) * (LM2_DIGEST_LEN + bloblen + (strlen(domain) + strlen(args_info.user_arg) + strlen(workstation)) * sp);

	type3 = (ntlm_type3 *)alloca(len);
	t3 = (unsigned char *) type3;

	memset(type3, 0, len);
	type3->signature[0] = 'N';
	type3->signature[1] = 'T';
	type3->signature[2] = 'L';
	type3->signature[3] = 'M';
	type3->signature[4] = 'S';
	type3->signature[5] = 'S';
	type3->signature[6] = 'P';
	type3->signature[7] = '\0';

	type3->message_type = NTLM_TYPE_3;
	type3->flags = flags & ~TAR_DOMAIN & ~NEG_TARINFO;

	type3->LM_response.length = LM2_DIGEST_LEN;
	type3->LM_response.space = LM2_DIGEST_LEN;
	type3->LM_response.offset = sizeof(ntlm_type3);
	memcpy(&t3[type3->LM_response.offset], lm2digest, LM2_DIGEST_LEN);

	type3->NTLM_response.length = bloblen;
	type3->NTLM_response.space = bloblen;
	type3->NTLM_response.offset = type3->LM_response.offset + type3->LM_response.space;
	memcpy(&t3[type3->NTLM_response.offset], pblob, bloblen);

	type3->domain.length = strlen(domain) * sp;
	type3->domain.space = strlen(domain) * sp;
	type3->domain.offset = type3->NTLM_response.offset + type3->NTLM_response.space;
	for (i = 0; i < strlen(domain); i++)
		t3[type3->domain.offset + i * sp] = domain[i];

	type3->user.length = strlen(args_info.user_arg) * sp;
	type3->user.space = strlen(args_info.user_arg) * sp;
	type3->user.offset = type3->domain.offset + type3->domain.space;
	for (i = 0; i < strlen(args_info.user_arg); i++)
		t3[type3->user.offset + i * sp] = args_info.user_arg[i];

	type3->workstation.length = strlen(workstation) * sp;
	type3->workstation.space = strlen(workstation) * sp;
	type3->workstation.offset = type3->user.offset + type3->user.space;
	for (i = 0; i < strlen(workstation); i++)
		t3[type3->workstation.offset + i * sp] = workstation[i];

	base64((unsigned char *)ntlm_type3_buf, (unsigned char *)type3, len);

	return;
}

/*
** Function: hmac_md5
*/

void
hmac_md5(text, text_len, key, key_len, digest)
unsigned char* text; /* pointer to data stream */
int text_len; /* length of data stream */
unsigned char* key; /* pointer to authentication key */
int key_len; /* length of authentication key */
unsigned char digest[16]; /* caller digest to be filled in */
{
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#else
	MD5_CTX context;
#endif
	unsigned char k_ipad[65];    /* inner padding - key XORd with ipad */
	unsigned char k_opad[65];    /* outer padding - key XORd with opad */
	unsigned char tk[16];
	int i;

	/* if key is longer than 64 bytes reset it to key=MD5(key) */
	if (key_len > 64) {
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
		EVP_DigestInit_ex(mdctx, md5alg, NULL);
		EVP_DigestUpdate(mdctx, key, key_len);
		EVP_DigestFinal_ex(mdctx, tk, NULL);
#else
		MD5_Init(&context);
		MD5_Update(&context, key, key_len);
		MD5_Final(tk, &context);
#endif
		key = tk;
		key_len = 16;
	}

	/*
	 * the HMAC_MD5 transform looks like:
	 *
	 * MD5(K XOR opad, MD5(K XOR ipad, text))
	 *
	 * where K is an n byte key
	 * ipad is the byte 0x36 repeated 64 times
	 * opad is the byte 0x5c repeated 64 times
	 * and text is the data being protected
	 */

	/* start out by storing key in pads */
	memset(k_ipad, 0, sizeof(k_ipad));
	memset(k_opad, 0, sizeof(k_opad));
	memcpy(k_ipad, key, key_len);
	memcpy(k_opad, key, key_len);

	/* XOR key with ipad and opad values */
	for (i=0; i<64; i++) {
		k_ipad[i] ^= 0x36;
		k_opad[i] ^= 0x5c;
	}

	/* perform inner MD5 */
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
	EVP_DigestInit_ex(mdctx, md5alg, NULL);  /* init context for 1st pass */
	EVP_DigestUpdate(mdctx, k_ipad, 64);     /* start with inner pad */
	EVP_DigestUpdate(mdctx, text, text_len); /* then text of datagram */
	EVP_DigestFinal_ex(mdctx, digest, NULL); /* finish up 1st pass */
#else
	MD5_Init(&context);                   /* init context for 1st pass */
	MD5_Update(&context, k_ipad, 64);     /* start with inner pad */
	MD5_Update(&context, text, text_len); /* then text of datagram */
	MD5_Final(digest, &context);          /* finish up 1st pass */
#endif

	/* perform outer MD5 */
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
	EVP_DigestInit_ex(mdctx, md5alg, NULL);  /* init context for 1st pass */
	EVP_DigestUpdate(mdctx, k_opad, 64);     /* start with inner pad */
	EVP_DigestUpdate(mdctx, digest, 16);     /* then text of datagram */
	EVP_DigestFinal_ex(mdctx, digest, NULL); /* finish up 1st pass */
#else
	MD5_Init(&context);                   /* init context for 2nd pass */
	MD5_Update(&context, k_opad, 64);     /* start with outer pad */
	MD5_Update(&context, digest, 16);     /* then results of 1st hash */
	MD5_Final(digest, &context);          /* finish up 2nd pass */
#endif
}

void build_ntlm2_response() {
	int i, j;
	int passlen = 0;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#else
	MD4_CTX passcontext;
#endif
	unsigned char passdigest[16];
	unsigned char *userdom;
	int userdomlen;
	unsigned char userdomdigest[16];
	blob *b;
	struct timeval t;
	unsigned char responsedigest[16];
	unsigned char lm2data[16];

	if (pblob != NULL)
		free(pblob);

	memset(unipasswd, 0, sizeof(unsigned char) * DOMAIN_BUFLEN * 2);
	for (i = 0; i < strlen(args_info.pass_arg); i++) {
		if (unicode) {
			unipasswd[i * 2] = args_info.pass_arg[i];
			passlen++;
			passlen++;
		} else {
			unipasswd[i] = args_info.pass_arg[i];
			passlen++;
		}
	}

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
	EVP_DigestInit_ex(mdctx, md4alg, NULL);
	EVP_DigestUpdate(mdctx, unipasswd, passlen);
	EVP_DigestFinal_ex(mdctx, passdigest, NULL);
#else
	MD4_Init (&passcontext);
	MD4_Update (&passcontext, unipasswd, passlen);
	MD4_Final (passdigest, &passcontext);
#endif

	if( args_info.verbose_flag ) {
		message("NTLM: MD4 of password is: ");
		for( i = 0; i < 16; i++)
			message("%02X", passdigest[i]);
		message("\nDOMAIN: %s\nUSER: %s\n", domain, args_info.user_arg);
	}

	userdomlen = sizeof(unsigned char) * (strlen(args_info.user_arg) + strlen(domain)) * 2;
	userdom = (unsigned char *)alloca(userdomlen);
	memset(userdom, 0, userdomlen);

	userdomlen = 0;
	for (i = 0; i < strlen(args_info.user_arg); i++) {
		if (unicode) {
			userdom[i * 2] = toupper(args_info.user_arg[i]);
			userdomlen++;
			userdomlen++;
		} else {
			userdom[i] = toupper(args_info.user_arg[i]);
			userdomlen++;
		}
	}

	for (j = 0; j < strlen(domain); j++) {
		if (unicode) {
			userdom[i * 2 + j * 2] = toupper(domain[j]);
			userdomlen++;
			userdomlen++;
		} else {
			userdom[i + j] = toupper(domain[j]);
			userdomlen++;
		}
	}

	if( args_info.verbose_flag ) {
		message("userdom is: ");
		for( i = 0; i < userdomlen; i++)
			message("%02X", userdom[i]);
		message("\n");
	}

	hmac_md5(userdom, userdomlen, passdigest, 16, userdomdigest);

	if( args_info.verbose_flag ) {
		message("HMAC_MD5 of userdom keyed with MD4 pass is: ");
		for( i = 0; i < 16; i++)
			message("%02X", userdomdigest[i]);
		message("\n");
	}

	if ((sizeof(long long) != 8)) {
		message("We are in trouble here.. long long support is not here!!\n");
		exit(-1);
	}

	bloblen = sizeof(blob) + sizeof(unsigned char) * t_info_len;

	pblob = (unsigned char *)malloc(bloblen);
	if (!pblob) {
		message("Fatal Error in build_ntlm2_response, Malloc failed\n");
		exit(-1);
	}

	memset(pblob, 0, bloblen);

	b = (blob *)pblob;

	for (i = 0; i < 8; i++)
		b->digest[8 + i] = challenge[i];

	b->signature = 0x00000101;

	/* This is nasty, also not sure all this 64bit arithmetic will
	 * work all the time.. basically the spec says you need the
	 * number of 10ths of microseconds since jan 1, 1601.
	 */

	gettimeofday(&t, NULL);
	b->timestamp = (long long)t.tv_sec;
	b->timestamp += 11644473600LL;
	b->timestamp *= 1000000LL;
	b->timestamp += (long long)t.tv_usec;
	b->timestamp *= 10LL;

	// need a ramdom client challenge
	for (i = 0; i < 8; i++)
		b->client_challenge[i] = (unsigned char) ((256.0 * rand()) / (RAND_MAX + 1.0)) ;

	if( args_info.verbose_flag ) {
		message("client_challenge is: ");
		for( i = 0; i < 8; i++)
			message("%02X", b->client_challenge[i]);
		message("\n");
	}

	memcpy(&b->data_start, t_info, t_info_len);

	hmac_md5(&pblob[8], bloblen - 8, userdomdigest, 16, responsedigest);

	for(i = 0; i < 16; i++)
		b->digest[i] = responsedigest[i];

	if( args_info.verbose_flag ) {
		message("HMAC is: ");
		for( i = 0; i < 16; i++)
			message("%02X", responsedigest[i]);
		message("\n");
	}

	// LM2 response generation

	for (i = 0; i < 8; i++)
		lm2data[i] = challenge[i];

	for (i = 0; i < 8; i++)
		lm2data[8 + i] = b->client_challenge[i];

	hmac_md5(lm2data, 16, userdomdigest, 16, lm2digest);

	for (i = 0; i < 8; i++)
		lm2digest[16 + i] = b->client_challenge[i];
}

// vim:noexpandtab:ts=4
