//
//
// AuthOption
//
//

extern "C" {
#include <time.h>
#include <stdlib.h>
}

#include <Windows.h>
#include <winsock.h>
#include "TelnetApp.h"
#include "Negotiator.h"
#include "AuthOption.h"
extern "C" {
#include "des.h"
#include "KClient.h"
#include "krb.h"
}
#include "string.h"
#include "TelnetEngine.h"

AuthOption::AuthOption (CharStream *stream, 
						TelnetEngine *engine, 
						const char *hostname)
: Option (stream, engine, AUTHENTICATION)
{
	memset (host, 0, sizeof (host));
	strncpy (host, (char*)hostname, strlen(hostname));
}

AuthOption::~AuthOption ()
{
	/*if (KClientStatus () == KClientLoggedIn)
		KClientLogout ();
  */
}

BOOL
AuthOption::KerbInit ()
{
	int res;
	res = KClientInitSession(&pTEngine->mKClientSession, 0, 0, 0, 0);
	return res;
}

void
AuthOption::NegotiateOption (unsigned char command)
{
	unsigned char c;
	
	switch (command) {
		case DO:
			unsigned char buff[3];
			buff[0] = IAC;
			if (KerbInit ())
				buff[1] = WONT;
			else
				buff[1] = WILL;
			buff[2] = option;
			SendReply (buff, 3);
			stream->MarkAsRead ();
			break;

		case SB:
			if (!stream->GetChar (&c))
				return;
			switch (c) {
				case SEND:
					NegotiateSend ();
					break;
				case REPLY:
					NegotiateReply ();
					break;
				default:
					break;
			}		
			break;
		default:
			break;
	}
}


void
AuthOption::SendNoKerb4 ()
{
	static unsigned char buff[8];

	buff[0] = IAC;
	buff[1] = SB;
	buff[2] = AUTHENTICATION;
	buff[3] = IS;
	buff[4] = NULL;
	buff[5] = 0;
	buff[6] = IAC;
	buff[7] = SE;
	SendReply (buff, 8);
}

void
AuthOption::NegotiateSend ()
{
	unsigned char        pair[2], c = 0, padKBuff[2048];
	char                 kBuff[2048], *pKey, principal[255], username[50], hostname[255];
	unsigned long        kBuffLen = sizeof (kBuff);
	Kerr                result;
		
	pair[0] = SE; // dummy init
	
	while (pair[0] != KERBEROS_V4 && pair[0] != IAC) {
		if (!stream->GetChar (pair))
			return;
		if (!stream->GetChar (pair + 1))
			return;
	}
	
	while (pair[0] == KERBEROS_V4) {
		if (pair[1] == AUTH_HOW_MUTUAL)
			break;
		if (!stream->GetChar (pair)) 
			return;
		if (!stream->GetChar (pair + 1))
			return;
	}
	
	if (pair[0] == IAC) {
		if (!stream->GetChar (&c))  // Get SE
			return;
	}
	else
		if (ReadToIACSE ())
			return;

	stream->MarkAsRead ();
		
	if (pair[0] != KERBEROS_V4) {  // Cannot do kerb4, one-way -> Send NULL authpair
		SendNoKerb4 ();
		return;
	}
	

	// Get Kerberos ticket
	principal[0] = '\0';
	strcat (principal, "rcmd.");
	strcpy(hostname,host);
	char *p = strchr(hostname, '.');
	if(p)
		*p = 0;
	strcat (principal, hostname);
	strcat(principal,"@");
	strcat (principal, krb_realmofhost(host));
	//KClientSetUserName("d93-jka");
	result = KClientGetTicketForService (&pTEngine->mKClientSession,
			principal, kBuff, &kBuffLen);
	if (result) {
		char error[255];
		KClientErrorText(result, error); 
		TelnetApp::Error(result, error);
		SendNoKerb4 ();
		return;
	}
	// Get loginid
	result = KClientGetUserName (username);
	if (result) {
		char error[255];
		KClientErrorText(result, error); 
		TelnetApp::Error(result, error);
		SendNoKerb4 ();
		return;
	}
	
	// Send name
	unsigned char nameBuff[4];
	unsigned char iacseBuff[2];
	nameBuff[0] = IAC;
	nameBuff[1] = SB;
	nameBuff[2] = AUTHENTICATION;
	nameBuff[3] = NAME;
	SendReply (nameBuff, 4);
	SendReply (username, strlen (username));
	iacseBuff[0] = IAC;
	iacseBuff[1] = SE;
	SendReply (iacseBuff, 2);
		
	// Check if kBufflen in beginning of kBuff
	if (*((unsigned long *)kBuff) == kBuffLen - sizeof (unsigned long)) {
		pKey = kBuff + sizeof (unsigned long);
		kBuffLen -= sizeof (unsigned long);
	}
	else
		pKey = kBuff;
	
	// Send ticket
	unsigned char tktBuff[7];
	tktBuff[0] = IAC;
	tktBuff[1] = SB;
	tktBuff[2] = AUTHENTICATION;
	tktBuff[3] = IS;
	tktBuff[4] = KERBEROS_V4;
	tktBuff[5] = AUTH_CLIENT_TO_SERVER | AUTH_HOW_MUTUAL;
	tktBuff[6] = AUTH;
	SendReply (tktBuff, 7);
	
	unsigned long padKBuffLen;
	PadBinaryBuff ((unsigned char*)pKey, &kBuffLen, (unsigned char*)padKBuff, &padKBuffLen); 
	SendReply(padKBuff, padKBuffLen);
	SendReply(iacseBuff, 2);
}

void
AuthOption::SendChallenge()
{
	int i;
	unsigned char reply1[] = { IAC, SB, AUTHENTICATION, IS, KERBEROS_V4, AUTH_CLIENT_TO_SERVER | AUTH_HOW_MUTUAL, CHALLENGE };
	unsigned char reply2[] = { IAC, SE };
	unsigned char msg[16];
	unsigned long len, padlen;
	des_cblock session;
	des_key_schedule schedule;

	srand(time(0));
	for(i = 0; i < 8; i++)
		pTEngine->enckey[i] = rand() % 256; // XXX
	des_set_odd_parity((des_cblock *)pTEngine->enckey);
	KClientGetSessionKey(&pTEngine->mKClientSession, (KClientKey*)&session);
	des_set_key(&session, schedule);
	des_ecb_encrypt(&pTEngine->enckey, &challenge, schedule, DES_DECRYPT);

	len = 8;
	PadBinaryBuff(challenge, &len, msg, &padlen);
	
	SendReply(reply1, sizeof(reply1));
	SendReply(msg, padlen);
	SendReply(reply2, sizeof(reply2));

	des_ecb_encrypt(&challenge, &challenge, schedule, DES_DECRYPT);

	for(i = 7; i >= 0; i--)
		if(++(challenge[i]))
			break;
	des_ecb_encrypt(&challenge, &challenge, schedule, DES_ENCRYPT);

	memset(&session, 0, sizeof(des_cblock));
	memset(&msg, 0, sizeof(msg));
	memset(&schedule, 0, sizeof(des_key_schedule));
}


void
AuthOption::NegotiateReply ()
{
	unsigned char pair[2];
	unsigned char reply;
	unsigned char reason[80];
	unsigned char c = 0;
	short i = 0;
	
	if (!stream->GetChar (pair) || !stream->GetChar (pair + 1)) // Get auth-pair
		return;
	
	if (!stream->GetChar (&reply))
		return;
	
	switch (reply) {
		case REJECT:
			
			while (c != IAC) {
				if (!stream->GetChar (&c))
					return;
				reason[i++] = c;
			}
			reason[i]  = '\0';
			TelnetApp::Error(GetLastError(),(char*) reason);
			stream->UngetChar ();
			break;	
		case ACCEPT:
			if(pair[1] & AUTH_HOW_MUTUAL)
				SendChallenge();
			break;
		case RESPONSE:
			des_cblock session;
			des_key_schedule schedule;
			des_cblock response;
			// Get encrypted response
			for(i = 0; i < 8; i++)
				if(!stream->GetChar(response + i))
					return;
			for(i = 0; i < 8; i++)
				if(response[i] != challenge[i])
					break; //return;
			pTEngine->mAuthenticated = TRUE;
			NegotiateEncryption();
			break;
		default:
			break;
	}
	
	if (ReadToIACSE ())
		return;
	else
		stream->MarkAsRead ();
}

void
AuthOption::NegotiateEncryption()
{
	unsigned char mBuf[] = {IAC, DO, ENCRYPT_OPT};
	SendReply(mBuf, 3);
	mBuf[1] = WILL;
	SendReply(mBuf, 3);
}

void AuthOption::ExecOption(unsigned int Command)
{

}
