//---------------------------------------------------------------------------

// MircryptionClass.cpp

// New C++ classes for mircryption

//

// http://mircryption.sourceforge.net

// By Dark Raichu, 12/01 - 07/03

//---------------------------------------------------------------------------




//---------------------------------------------------------------------------

// Application includes

#include "mircryptionclass.h"

//---------------------------------------------------------------------------




//---------------------------------------------------------------------------

// 01/27/06 to prevent evil attempts to crash by sending nick+chan to big

// thanks to www.rainbowcrack-online.com and ircfuz for finding this dangerous possibility

void mcensuresafebuflen(char *buf, int maxlen) {if (strlen(buf)>=maxlen-10) buf[maxlen-10]='\0';};
//---------------------------------------------------------------------------





//---------------------------------------------------------------------------

MircryptionClass::MircryptionClass()
{
	// constructor


	// set default keyfile name

	strcpy(keyfilename,MCP_DefaultFilename);
	// mark keyfile as being locked (ie not decrypted)

	passphrasesunlockedflag=false;
	// initiailize keypair linked list

	keypairs=NULL;
	keys=0;
	// remember that we have not backed up keyfile yet

	keyfilebackedup=false;
	// reset count of number of failed tries to unlock keyfile

	badtries=0;
	// when we read password file, we want to know if the channel names were encrypted

	foundencryptedchannels=false;
	// have we warned user yet about locked keys?

	gavewarning_lockedkeys=false;
}

MircryptionClass::~MircryptionClass()
{
	// destructor


	// unload keys if they are loaded - perfunctary as all changes are saved after any change

	unloadkeys();

	// bleach key memory

	bleachmemory();
}
//---------------------------------------------------------------------------









//---------------------------------------------------------------------------

//---------------------------------------------------------------------------

// PUBLICALLY EXPOSED FUNCTIONS

//---------------------------------------------------------------------------

//---------------------------------------------------------------------------








//---------------------------------------------------------------------------

bool MircryptionClass::mc_encrypt (char *channelname,char *text, char *returndata)
{
	char key[MAXKEYSIZE];

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// new force lowercase, to be safe

//	mylowercasify(channelname);


	lookup_channelkey(channelname,key,false);
	if (key[0]!='\0')
		{
		if (strlen(text)>CRASHLINELEN)
			{
			// text[CRASHLINELEN]='\0';

			sprintf(returndata,"%s. Line not sent. Encryption engine cannot encrypt lines more than %d characters.",MIRCRYPTIONERROR,CRASHLINELEN);
			memset(key,0,MAXKEYSIZE);
			return false;
			}

		// ok, we got a key for this channel, so encrypt it

		if (verify_keysunlocked())
			{
			if (strcmp(key,"_ENCRYPTED_")==0)
				lookup_channelkey(channelname,key,false);	// relook up a prev. encrypted key

			char* p = encrypt_string(key,text);
			sprintf(returndata,"%s",p);
			bleachdelete(p);
			}
		else
			{
			sprintf(returndata,"%s master keyfile is not unlocked.",MIRCRYPTIONERROR);
			memset(key,0,MAXKEYSIZE);
			return false;
			}
		}
	else
		{
		// no key found for this channel, returnvalue=""

		sprintf(returndata,"");
		// should we return true or false?

		memset(key,0,MAXKEYSIZE);
		return false;
		}

	memset(key,0,MAXKEYSIZE);
	return true;
}


bool MircryptionClass::mc_forceencrypt (char *channelname,char *text, char *returndata)
{
	// same as mc_encrypt, but we encrypt even for disabled channels

	char key[MAXKEYSIZE];

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// new force lowercase, to be safe

//	mylowercasify(channelname);


	lookup_channelkey(channelname,key,false);
	if (key[0]=='\0')
		{
		// no key for this channel, but check if there is one for the disabled version of this channel

		char tempchannel[MAXCHANNELNAMESIZE+1];
		sprintf(tempchannel,"-%s",channelname);
		// now set key to value of key for '-#channelname'

		lookup_channelkey(tempchannel,key,false);
		}

	if (key[0]!='\0')
		{
		if (strlen(text)>CRASHLINELEN)
			{
			sprintf(returndata,"%s. Line not sent. Encryption engine cannot encrypt lines more than %d characters.",MIRCRYPTIONERROR,CRASHLINELEN);
			memset(key,0,MAXKEYSIZE);
			return false;
			}

		// ok, we got a key for this channel, so encrypt it

		if (verify_keysunlocked())
			{
			if (strcmp(key,"_ENCRYPTED_")==0)
				lookup_channelkey(channelname,key,false);	// relook up a prev. encrypted key

			char* p = encrypt_string(key,text);
			sprintf(returndata,"%s",p);
			bleachdelete(p);
			}
		else
			{
			sprintf(returndata,"%s master keyfile is not unlocked.",MIRCRYPTIONERROR);
			memset(key,0,MAXKEYSIZE);
			return false;
			}
		}
	else
		{
		// no key found for this channel, returnvalue=""

		sprintf(returndata,"");
		// should we return true or false?

		return false;
		}

	memset(key,0,MAXKEYSIZE);
	return true;
}


bool MircryptionClass::mc_decrypt (char *channelname,char *text, char *returndata)
{
	char key[MAXKEYSIZE];

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// new force lowercase, to be safe

//	mylowercasify(channelname);

	
	lookup_channelkey(channelname,key,false);
	if (key[0]=='\0')
		{
		// no key for this channel, but check if there is one for the disabled version of this channel

		char tempchannel[MAXCHANNELNAMESIZE+1];
		sprintf(tempchannel,"-%s",channelname);
		lookup_channelkey(tempchannel,key,false);
		}

	if (key[0]!='\0')
		{
		// ok, we got a key for this channel, so encrypt it

		if (verify_keysunlocked())
			{
			if (strcmp(key,"_ENCRYPTED_")==0)
				lookup_channelkey(channelname,key,false);	// relook up a prev. encrypted key

			char* p = decrypt_string(key,text);
			sprintf(returndata,"%s",p);
			bleachdelete(p);
			}
		else
			{
			strcpy(returndata,"key found for channel but text could not be decrypted - make sure master passphrase is set.");
			memset(key,0,MAXKEYSIZE);
			return false;
			}
		}
	else
		{
		// no key found for this channel, returnvalue=""

		sprintf(returndata,"");
		// should we return true or false?

		return false;
		}		

	memset(key,0,MAXKEYSIZE);
	return true;
}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------

// These are new versions of mc_encrypt and mc_decrypt - they work differently than the old ones

// 1. they wrap the text in start_encryption and end_encryption tags

// 2. they add a couple chars before encryption (and remove after) that allows them to confirm succesfull decoding

// 3. decoding will also operate on embeddded *substrings* of encoded text, where only parts of a string are encrypted

// 4. no whitespaces or comas are present in substring-crypted text, which makes it easier to identify crypted substrings

// 5. during decoding, if channel name key fails to decrypt the text, system will try *ALL* stored keys for a successful decode

// All of these features are meant to allow mircryption to better decode text with substrings of encrypted text which can be useful when using mircryption with existing scripts


bool MircryptionClass::mc_encrypt2 (char *channelname,char *text, char *returndata)
{
	char key[MAXKEYSIZE];

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// new force lowercase, to be safe

//	mylowercasify(channelname);

	
	lookup_channelkey(channelname,key,false);
	if (key[0]!='\0')
		{
/*
// we dont need to worry about this anymore, mirc script will take care of it
		if (strlen(text)>CRASHLINELEN)
			{
			// text[CRASHLINELEN]='\0';
			sprintf(returndata,"%s. Line not sent. Encryption engine cannot encrypt lines more than %d characters.",MIRCRYPTIONERROR,CRASHLINELEN);
			return false;
			}
*/

		// ok, we got a key for this channel, so encrypt it

		if (verify_keysunlocked())
			{
			if (strcmp(key,"_ENCRYPTED_")==0)
				lookup_channelkey(channelname,key,false);	// relook up a prev. encrypted key

			mc_encrypt2key(key,text,returndata);
			}
		else
			{
			sprintf(returndata,"%s master keyfile is not unlocked.",MIRCRYPTIONERROR);
			memset(key,0,MAXKEYSIZE);
			return false;
			}
		}
	else
		{
		// no encryption for this channel, BUT we still return the original text

		sprintf(returndata,"%s",text);
		// should we return true or false? for now we drop down and return true

		}

	memset(key,0,MAXKEYSIZE);
	return true;
}


bool MircryptionClass::mc_encrypt2key (char *key, char *text, char *returndata)
{
	// do new encryption given a key


	// protect against too long keys

	if (strlen(key)>MAXSAFEKEYSIZE)
		key[MAXSAFEKEYSIZE]='\0';

	// first encrypt the (crc+text)

	char plaintext[MAXLINELEN];
	strcpy(plaintext,MCPS2_CRC);
	strcat(plaintext,text);
	char* p = encrypt_string(key,plaintext);
	// now write it to data var, with start and end tages

	sprintf(returndata,"%s%s%s",MCPS2_STARTTAG,p,MCPS2_ENDTAG);
	// now convert whitespaces to replacements

	repwhitespaces(returndata);
	// delete encrypted pointer

	bleachdelete(p);
	return true;
}


bool MircryptionClass::mc_decrypt2 (char *channelname,char *text, char *returndata)
{
	char key[MAXKEYSIZE];

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// new force lowercase, to be safe

//	mylowercasify(channelname);


	if (!verify_keysunlocked())
		{
		// cant open decrypted keys, SO warn them first, then return undecrypted text on other runs

		if (gavewarning_lockedkeys)
			sprintf(returndata,"%s",text);	
		else
			{
			sprintf(returndata,"WARNING: Your mircryption keys are not unlocked - no encyrption/decryption will occur until you set your master password.");
			gavewarning_lockedkeys=true;
			}
		memset(key,0,MAXKEYSIZE);
		return false;
		}
		
	lookup_channelkey(channelname,key,false);
	if (key[0]=='\0')
		{
		// we can still decrypt using a disabled ecnryption channel

		char tempchannel[MAXCHANNELNAMESIZE+1];
		sprintf(tempchannel,"-%s",channelname);
		lookup_channelkey(tempchannel,key,false);
		}
	if (key[0]!='\0')
		{
		// ok, we got a key for this channel, so decrypt it

		if (strcmp(key,"_ENCRYPTED_")==0)
			lookup_channelkey(channelname,key,false);	// relook up a prev. encrypted key

		}

	// legacy decrypt of style 1

	if (strncmp(text,"mcps ",5)==0)
		{
		char* p = decrypt_string(key,text+5);
		sprintf(returndata,"%s",p);
		bleachdelete(p);
		// ATTN: should we verify from decrypt_string whether decrypt was successful or no?

		memset(key,0,MAXKEYSIZE);
		return true;
		}

	// should we also decrypt the "OK+" format of blowcrypt or bnc blowfish?

	// THIS WAS CHANGED ON 8/1/03 - it *should* work fine

	//if (false && strncmp(text,"+OK ",4)==0)

	if (strncmp(text,"+OK ",4)==0)
		{
		char* p = decrypt_string(key,text+4);
		sprintf(returndata,"%s",p);
		bleachdelete(p);
		// ATTN: should we verify from decrypt_string whether decrypt was successful or no?

		memset(key,0,MAXKEYSIZE);
		return true;
		}

	// now loop through replacing substrings of encoded text (which may be nonexistent)

	mc_decrypt2key(key,text,returndata);
	memset(key,0,MAXKEYSIZE);
	return true;
}




bool MircryptionClass::mc_decrypt2key (char *key,char *text, char *returndata)
{
	char decodedtext[MAXLINELENE];
	char decodedsubstring[MAXLINELENE];
	char *p,*p2;

	// protect against too long keys

	if (strlen(key)>MAXSAFEKEYSIZE)
		key[MAXSAFEKEYSIZE]='\0';

	strcpy(decodedtext,text);
	p=decodedtext;
	for(;;)
		{
		// find leftmost substring

		p=strstr(p,MCPS2_STARTTAG);
		if (p==NULL)
			break;
		p2=strstr(p,MCPS2_ENDTAG);
		if (p2!=NULL)
			{
			// cap substring

			*p2='\0';
			}
		else
			{
			// truncated string

			p2=p+strlen(p);
			}

		// decode substring

		decrypt2_substring(p+strlen(MCPS2_STARTTAG),decodedsubstring,key);
		// add right half of string to decoded substring

		strcat(decodedsubstring,p2+strlen(MCPS2_ENDTAG));
		// cap main string before first tag

		*p='\0';
		// now add left half to right half

		strcat(decodedtext,decodedsubstring);
		// advance p to find next encrypted substring

		// p=decodedtext+strlen(decodedsubstring);

		p=decodedtext+1;
		}

	// kludge fix for xchat, which expands our tags wierdly

	p=decodedtext;
	for(;;)
		{
		// find leftmost substring

		p=strstr(p,MCPS2_STARTTAGb);
		if (p==NULL)
			break;
		p2=strstr(p,MCPS2_ENDTAGb);
		if (p2!=NULL)
			{
			// cap substring

			*p2='\0';
			}
		else
			{
			// truncated string

			p2=p+strlen(p);
			}

		// decode substring

		decrypt2_substring(p+strlen(MCPS2_STARTTAGb),decodedsubstring,key);
		// add right half of string to decoded substring

		strcat(decodedsubstring,p2+strlen(MCPS2_ENDTAGb));
		// cap main string before first tag

		*p='\0';
		// now add left half to right half

		strcat(decodedtext,decodedsubstring);
		// advance p to find next encrypted substring

		//p=decodedtext+strlen(decodedsubstring);

		p=decodedtext+1;
		}

	strcpy(returndata,decodedtext);
	// bleach

	memset(decodedtext,0,MAXLINELENE);
	memset(decodedsubstring,0,MAXLINELENE);
	return true;
}

//---------------------------------------------------------------------------




//---------------------------------------------------------------------------

bool MircryptionClass::mc_setkey (char *channelname,char *key,char *returndata)
{
	// either change the value for a key, or add a new keypair

	char dchannelname[MAXCHANNELNAMESIZE];
	MircryptionKeypair *keyp=keypairs;

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// protect against too long keys

	if (strlen(key)>MAXSAFEKEYSIZE)
		key[MAXSAFEKEYSIZE]='\0';

	// protect against too long keys

	if (strlen(key)>MAXSAFEKEYSIZE)
		key[MAXSAFEKEYSIZE]='\0';

	// new force lowercase, to be safe

//	mylowercasify(channelname);


	if (!verify_keysunlocked())
		{
		strcpy(returndata,"mircryption keys cannot be set until you set the master passphrase.");
		return false;
		}

	// search for an existing key (use dchannelname to search for 'disabled' keys)

	sprintf(dchannelname,"-%s",channelname);
	while (keyp!=NULL)
		{
		if (strcmp(keyp->channelname,channelname)==0 || strcmp(keyp->channelname,dchannelname)==0)
			{
			if (strcmp(keyp->key,key)!=0)
				{
				strcpy(keyp->key,key);
				sprintf(returndata,"mircryption key for %s has been changed to %s.",channelname,key);
				}
			else
				{
				// key was already set to this, so there is nothing for us to do

				sprintf(returndata,"mircryption key for %s was already set to %s.",channelname,key);
				// force it to enable if it is disabled

				strcpy(keyp->channelname,channelname);
				}
			break;
			}
		keyp=keyp->nextp;
		}

	// if no existing key, make a new one

	if (keyp==NULL)
		{
		// add the new key to top of the keypairs linked list

		if (keypairs==NULL)
			{
			// this happens when we are creating our FIRST key of all, which means we didnt read a magicID yet, so our keyscount

			// is 0 when it should be 1 (including magicid key), so we do a little kludge here just for printing.

			keys=1;
			}
		keyp=keypairs;
		keypairs=new MircryptionKeypair(channelname,key);
		keypairs->nextp=keyp;
		sprintf(returndata,"mircryption key '%s' added for channel %s.",key,channelname);
		++keys;
		}

	// save changed keys immediately

	save_keys();
	return true;
}


bool MircryptionClass::mc_delkey (char *channelname,char *returndata)
{
	char dchannelname[MAXCHANNELNAMESIZE];
	MircryptionKeypair *keyp=keypairs;
	MircryptionKeypair **priorp=&keypairs;

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// new force lowercase, to be safe

//	mylowercasify(channelname);



	// search for an existing key (use dchannelname to search for 'disabled' keys)

	sprintf(dchannelname,"-%s",channelname);
	// walk through the linked list and delete the key if we find it

	while (keyp!=NULL)
		{
		if (strcmp(keyp->channelname,channelname)==0 || strcmp(keyp->channelname,dchannelname)==0)
			{
			sprintf(returndata,"mircryption key for %s has been deleted (was %s).",channelname,keyp->key);
			*priorp=keyp->nextp;
			// bleach it before deletion

			memset(keyp->key,0,strlen(keyp->key));
			memset(keyp->channelname,0,strlen(keyp->channelname));
			delete keyp;
			--keys;
			// save changed keys and break

			save_keys();
			break;
			}
		priorp=&(keyp->nextp);
		keyp=keyp->nextp;
		}

	if (keyp==NULL)
		{
		sprintf(returndata,"mircryption key for %s was not found.",channelname);
		return false;
		}

	return true;
}


bool MircryptionClass::mc_disablekey (char *channelname,char *returndata)
{
	char dchannelname[MAXCHANNELNAMESIZE];
	MircryptionKeypair *keyp=keypairs;

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// new force lowercase, to be safe

//	mylowercasify(channelname);


	// walk through the linked list and disable the key if we find it

	sprintf(dchannelname,"-%s",channelname);
	while (keyp!=NULL)
		{
		if (strcmp(keyp->channelname,channelname)==0)
			{
			sprintf(keyp->channelname,"-%s",channelname);
			sprintf(returndata,"mircryption for %s has been temporarily disabled. type /enablekey to re-enable it.",channelname);
			save_keys();
			break;
			}
		else if (strcmp(keyp->channelname,dchannelname)==0)
			{
			sprintf(returndata,"mircryption for %s is already disabled.",channelname);
			return false;
			}
		keyp=keyp->nextp;
		}

	if (keyp==NULL)
		{
		sprintf(returndata,"mircryption key for %s was not found.",channelname);
		return false;
		}

	return true;
}


bool MircryptionClass::mc_enablekey (char *channelname,char *returndata)
{
	char dchannelname[MAXCHANNELNAMESIZE];
	MircryptionKeypair *keyp=keypairs;

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// new force lowercase, to be safe

//	mylowercasify(channelname);


	// walk through the linked list and enable the key if we find it

	sprintf(dchannelname,"-%s",channelname);
	while (keyp!=NULL)
		{
		if (strcmp(keyp->channelname,dchannelname)==0)
			{
			sprintf(returndata,"mircryption for %s has been re-enabled.",channelname);
			strcpy(keyp->channelname,channelname);
			save_keys();
			break;
			}
		else if (strcmp(keyp->channelname,channelname)==0)
			{
			sprintf(returndata,"mircryption for %s is already enabled.",channelname);
			return false;
			}
		keyp=keyp->nextp;
		}

	if (keyp==NULL)
		{
		sprintf(returndata,"mircryption key for %s was not found.",channelname);
		return false;
		}

	return true;
}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------

bool MircryptionClass::mc_displaykey (char *channelname,char *returndata)
{
	char dchannelname[MAXCHANNELNAMESIZE];
	MircryptionKeypair *keyp=keypairs;

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// new force lowercase, to be safe

//	mylowercasify(channelname);


	if (!verify_keysunlocked())
		{
		strcpy(returndata,"not displayable until you set the master passphrase.");
		return false;
		}

	// walk through the linked list and disaply the key if we find it

	sprintf(dchannelname,"-%s",channelname);
	while (keyp!=NULL)
		{
		if (strcmp(keyp->channelname,channelname)==0 || strcmp(keyp->channelname,dchannelname)==0)
			{
			sprintf(returndata,"%s",keyp->key);
			break;
			}
		keyp=keyp->nextp;
		}

	if (keyp==NULL)
		{
		strcpy(returndata,"");
		return false;
		}

	return true;
}


bool MircryptionClass::mc_listkeys (char *returndata)
{
	// we use the virtual function display_statustext() rather than return text answer to display many lines


	// unlock keys if not

	if (!verify_keysunlocked())
		{
		present_messagebox("You must set the master passphrase before keys can be accessed (type /mircryption for help).","Attention");
		strcpy(returndata,"");
		return true;
		}

	// intro message

	if (keys==0)
		sprintf(returndata,"no mircryption keys found for any channels.");
	else if (strcmp(masterpassphrase,"")==0)
		sprintf(returndata,"mircryption passphrase is not yet set.  %d Keys:   ",keys-1);
	else
		sprintf(returndata,"mircryption passphrase is '%s'.  %d Keys:   ",masterpassphrase,keys-1);
	display_statustext(returndata);

	// walk through the linked list and print all keys

	MircryptionKeypair *keyp=keypairs;
	while (keyp!=NULL)
		{
		if (strcmp(keyp->channelname,"MAGICID")!=0)
			{
			sprintf(returndata,"  %s -> '%s'",keyp->channelname,keyp->key);
			display_statustext(returndata);
			}
		keyp=keyp->nextp;
		}

	strcpy(returndata,"");
	return true;
}
//---------------------------------------------------------------------------




//---------------------------------------------------------------------------

bool MircryptionClass::mc_setunlockpassphrase (char *passphrase, char *returndata)
{
	// set master keyfile password

	bool bretv;

	mcensuresafebuflen(passphrase,MAXSAFEKEYSIZE);

	strcpy(returndata,"");
	// set the master passphrase and try to unlock the global file	

	if (strcmp(passphrase,"?")==0)
		{
		// if we are passed a master passphrase of '?' it means to use function request_unlockpassphrase() to request it interactively

		bretv=request_unlockpassphrase();
		if (!bretv)
			{
			if (passphrasesunlockedflag || keypairs==NULL)
				sprintf(returndata,"Change of mircryption master passphrase canceled.");
			else
				sprintf(returndata,"Mircryption master passphrase not set.");
			return false;
			}
		}
	else
		bretv=setunlockpassphrase(passphrase);

	if (bretv)
		{
		if (passphrasesunlockedflag)
			{
			sprintf(returndata,"MircryptionSuite - Mircryption master passphrase accepted.");
			if (keys>0)
				save_keys();
			}
		}
	else
		{
		sprintf(returndata,"mircryption master passphrase rejected.");
		return false;
		}

	return true;
}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------

bool MircryptionClass::mc_setkeyfilename (char *filename,char *returndata)
{
	// change the name of the keyfile (unloading and reloading if one is already loaded)

	if (returndata!=NULL)
		strcpy(returndata,"");
	if (strcmp(keyfilename,filename)==0)
		{
		// keyfile name has not changed, so just ignore this

		load_keys_ifnotalready();
		return true;
		}
	strcpy(keyfilename,filename);
	load_keys();
	return true;
}

bool MircryptionClass::mc_iskeyfileunlocked (char *returndata)
{
	// has master keyfile been unlocked?

	if (returndata!=NULL)
		strcpy(returndata,"");
	if (!passphrasesunlockedflag)
		return false;
	return true;
}


bool MircryptionClass::mc_isencrypting (char *channelname,char *returndata)
{
	// is current channel have a key and is encrypting?

	char key[MAXKEYSIZE];

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// new force lowercase, to be safe

//	mylowercasify(channelname);


	if (returndata!=NULL)
		strcpy(returndata,"");
	lookup_channelkey(channelname,key,false);
	if (key[0]=='\0')
		return false;
	memset(key,0,MAXKEYSIZE);
	return true;
}


bool MircryptionClass::mc_isdecrypting (char *channelname,char *returndata)
{
	// is current channel have a key? (ie is decryptable)	

	char key[MAXKEYSIZE];

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// new force lowercase, to be safe

//	mylowercasify(channelname);


	if (returndata!=NULL)
		strcpy(returndata,"");

	lookup_channelkey(channelname,key,false);
	if (key[0]=='\0')
		{
		// we can still decrypt using a disable ecnryption channel

		char tempchannel[MAXCHANNELNAMESIZE];
		sprintf(tempchannel,"-%s",channelname);
		lookup_channelkey(tempchannel,key,false);
		}

	if (key[0]=='\0')
		return false;
	memset(key,0,MAXKEYSIZE);
	return true;
}
//---------------------------------------------------------------------------




//---------------------------------------------------------------------------

bool MircryptionClass::mc_md5 (char *text, char *returndata)
{
	// simple md5 hash of a string

	CMD5 md5;
	md5.setPlainText(text);
	sprintf(returndata,"%s",md5.getMD5Digest());
	return true;
}
//---------------------------------------------------------------------------











//---------------------------------------------------------------------------

//---------------------------------------------------------------------------

// INTERNAL METHODS

//---------------------------------------------------------------------------

//---------------------------------------------------------------------------












//---------------------------------------------------------------------------

bool MircryptionClass::setunlockpassphrase(char *passphrase)
	{
	// Set the global master passphrase to enencrypt the key file, and unlock keyfile

	// returns true on successful unlocking

	bool bretv;

	mcensuresafebuflen(passphrase,MAXSAFEKEYSIZE);

	// record master keyfile password

	strcpy(masterpassphrase,passphrase);
	// try to unlock it and load keys

	bretv=unlock_keys();

	if (!bretv)
		{
		// keep track of how many bad tries to open it, so we can warn user

		++badtries;
		if (badtries==4)
			{
			present_messagebox("You poor bastard.  Have you forgotten your passphrase?  I warned you about this, didn't I?\r\n  I'm going to stop asking you for it.  You can delete the MircryptionKeys.txt file to reset all keys and set a new passphrase.","Attention");
			// ATTN: we return true to make the program stop asking for master keyfile - BUT is this a bad idea? willit cause keys to be overwritten?

			bretv=true;
			}
		else if (badtries>4)
			{
			// on subsequent call after failing, we reset the cycle

			badtries=1;
			}
		else
			{
			present_messagebox("Master passphrase does not match that stored in MircryptionKeys.txt file.\r\n  Try again or delete keyfile to erase all keys.","Attention");
			}
		}
	return bretv;
	}


bool MircryptionClass::unlock_keys()
{
	// unlock(uncrypt) keys which are already loaded

	// returns true if keys are unlocked with passphrase

	char key[MAXKEYSIZE];
	char tempstring[MAXLINELENE+1];
	char* p;

	if (foundencryptedchannels)
		{
		// this flag is set when we have just read encrypted channel names.  we need to disable it or our call to lookup_chankey below will recursively call us and fail.

		foundencryptedchannels=false;
		}

	// if already unlocked, then return

	if (passphrasesunlockedflag==true)
		return true;
	// if no master pass set, return false

	if (masterpassphrase[0]=='\0')
		return false;

	if (keys>0)
		{ 
		// if we have loaded keys which have not been decrypted, we need to test unlock key by checking the magic id

		lookup_channelkey("MAGICID",key,true);
		if (strcmp(key,"")==0)
			{
			// no magic identifier means this is not a proper keyfile

			return false;
			}
		// decrypt the magic id

		p = decrypt_string(masterpassphrase,key);
		strcpy(key,p);
		// returned char * from decrypt_string must be deleted

		bleachdelete(p);
		if (strcmp(key,MAGICID)!=0)
			{
			// it does not match, so this is bad master password

			memset(key,0,MAXKEYSIZE);
			return false;
			}

		// master pass is good, so cycle through all keys and unencrypt them

		MircryptionKeypair *keyp=keypairs;
		MircryptionKeypair *keyp2;
		MircryptionKeypair *priorkeyp=NULL;
		while (keyp!=NULL)
			{
			// decrypt it and store the decrypted value

			p = decrypt_string(masterpassphrase,keyp->key);
			strcpy(keyp->key,p);
			bleachdelete(p);
			// is the channel name itself encryped?  if so decrypt it

			mc_decrypt2key(masterpassphrase,keyp->channelname,keyp->channelname);
			strcpy(tempstring,keyp->channelname);
			// force it to lowercase

			if (strcmp(keyp->channelname,"magicid")==0)
				{
				// bug created when we accidentally lowercased magicid, so we want to kill this key and splice it out

				keyp2=keyp->nextp;
				if (priorkeyp!=NULL)
					priorkeyp->nextp=keyp2;
				else
					keypairs=keyp2;
				delete keyp;
				keyp=keyp2;
				--keys;
				continue;
				}
			else if (strcmp(keyp->channelname,"MAGICID")!=0)
				{
				mylowercasify(keyp->channelname);
				// check to see if channel name was not lowercase, which can happen with older keyfiles

				if (strcmp(keyp->channelname,tempstring)!=0)
					{
					// it wasnt lowercase, so this was old channel name, which could have a duplicate

					keyp2=keypairs;
					while (keyp2!=NULL && keyp2!=keyp)
						{
						if (strcmp(keyp->channelname,keyp2->channelname)==0)
							{
							// ok we got a duplicate key, SO we dont want to add both

							strcat(keyp->channelname,"_errorduplicate");
							break;
							}
						keyp2=keyp2->nextp;
						}
					}
				}
			// now loop to next key

			priorkeyp=keyp;
			keyp=keyp->nextp;
			}
		}
	
	// set flag to show the passphrases have been unlocked

	passphrasesunlockedflag=true;
	memset(key,0,MAXKEYSIZE);
	return true;
}


bool MircryptionClass::verify_keysunlocked()
{
	// verify keys are loaded and unlocked.  if keys are locked, try to get user to give passphrase to unlock

	// returns true on success

	bool bretv;

	// check if keys are already unlocked

	if (passphrasesunlockedflag)
		return true;
	if (badtries==4)
		{
		// after 4 bad tries at passphrase, we stop asking; this will reset if user explicitly tries to set passphrase

		return false;
		}

	// try to unlock them

	for (;;)
		{
		// now unlock them if need be

		bretv=unlock_keys();
		if (bretv)
			{
			// we successfully unlocked them

			break;
			}
		if (passphrasesunlockedflag)
			{
			// this can happen if we give up(?)

			break;;
			}
		// ask user for master pass

		bretv=request_unlockpassphrase();
		if (!bretv)
			break;
		// we give up after many bad attempts

		if (badtries==4)
			break;
		}

	// return flag saying whether keys are unlocked

	return passphrasesunlockedflag;
}


bool MircryptionClass::load_keys_ifnotalready()
{
	// load keys if they are not already loaded

	if (!passphrasesunlockedflag)
		load_keys();
	return true;
}


bool MircryptionClass::load_keys()
{
	// load encrypted keys from file

	// returns true on succesfull loading of keys, or if file does not exist

	FILE *fp;
	MircryptionKeypair *keyp;
	char tempstring[MAXLINELENE+1];
	char *p;
	int len;
	int c;
   	MircryptionKeypair *lastkeyp;
	char key[MAXKEYSIZE];

	// unload any that are already loaded

	unloadkeys();
	keypairs=lastkeyp=NULL;
	foundencryptedchannels=false;
	
	// open the file

	if ((fp=fopen(keyfilename,"r"))==(FILE *)NULL)
		{
		// this should only happen if file does not exist

		return true;
		}
	
	// read keys from file

	while (!feof(fp))
		{
		// read a line

		fgets(tempstring,MAXLINELENE,fp);
		tempstring[MAXLINELENE]='\0';
		len=strlen(tempstring)-1;

		// remove cr/lf at end

		while (tempstring[len]==13 || tempstring[len]==10)
			{
			tempstring[len]='\0';
			if (--len<0)
				break;
			}

		// skip comments and empty lines

		if ((tempstring[0]=='/' && tempstring[1]=='/') || tempstring[0]=='\0')
			{
			if (strcmp(tempstring,"// Using Encrypted Channel Names")==0)
				{
				// directive about encrypted channels

				// we dont actually have to detect this, we will pick it up when parsing channel keys

				// however, we do need to remember the channel names are encrypted, so we decrypt them before looking up channels

				foundencryptedchannels=true;
				}
			continue;
			}

		// pick off channel name

		p=tempstring;
		for(;;)
			{
			c=*p;
			if (c==' ' || c=='\0') break;
			++p;
			}
		// providing there is a key for this channel, add the keypair

		if (*p!='\0')
			{
			*p='\0';
			++p;
			if (strlen(p)>MAXKEYSIZE)
				{
				// do not allow keys bigger than this size (5/17/05)

				continue;
				}

			if (!foundencryptedchannels)
				{
				// all keyfiles are encrypted now, this is just for legacy keyfiles

				// legacy keys can have multicase channel names, which can cause troubles.

				// check to see if channel name was not lowercase

				if (strcmp(tempstring,"MAGICID")!=0)
					mylowercasify(tempstring);
				// check to see if it is a duplicate

				lookup_channelkey(tempstring,key,true);
				if (key[0]!='\0')
					{
					// ok we got a duplicate key, SO we dont want to add both

					strcat(tempstring,"_errorduplicate");
					}
				}
			// create a key and add it to the linked list

			keyp=new MircryptionKeypair(tempstring,p);
			keyp->nextp=NULL;
			if (lastkeyp!=NULL)
				lastkeyp->nextp=keyp;
			else
				keypairs=keyp;
			lastkeyp=keyp;
			++keys;
			}
		}

	// we have loaded the keyfile, but remember that it is not uncrypted(unlocked) yet

	passphrasesunlockedflag=false;
	fclose(fp);
	return true;
}


void MircryptionClass::unloadkeys()
{
	// force the keys out of memory, clear the master passphrase, and free keypair list

	MircryptionKeypair *nextp;

	masterpassphrase[0]='\0';
	passphrasesunlockedflag=false;

	// walk through the linked list and delete all keys

	while (keypairs!=NULL)
		{
		// bleach it before deletion

		memset(keypairs->key,0,strlen(keypairs->key));
		memset(keypairs->channelname,0,strlen(keypairs->channelname));
		// delete key

		nextp=keypairs->nextp;
		delete keypairs;
		keypairs=nextp;
		}
	keys=0;
	keypairs=NULL;
}


bool MircryptionClass::backupkeys()
{
	// backup key file

	FILE *fp,*fp2;
	char tempstring[MAXLINELENE+1];

	/*
	// double backup .bak to .bk2 ?
	sprintf(tempstring,"%s.bak",keyfilename);
	fp=fopen(tempstring,"r");
	sprintf(tempstring,"%s.bk2",keyfilename);
	fp2=fopen(tempstring,"w");
	if (fp!=(FILE *)NULL && fp2!=(FILE *)NULL)
		{
		while (!feof(fp))
			{
			fgets(tempstring,MAXLINELENE,fp);
			tempstring[MAXLINELENE]='\0';
			fputs(tempstring,fp2);
			}
		fclose(fp);
		fclose(fp2);
		}
	*/

	// create a bak file ending in .bak

	fp=fopen(keyfilename,"r");
	sprintf(tempstring,"%s.bak",keyfilename);
	fp2=fopen(tempstring,"w");
	if (fp!=(FILE *)NULL && fp2!=(FILE *)NULL)
		{
		// copy the file line by line

		while (!feof(fp))
			{
			fgets(tempstring,MAXLINELENE,fp);
			tempstring[MAXLINELENE]='\0';
			fputs(tempstring,fp2);
			}
		fclose(fp);
		fclose(fp2);
		// record flag saying we have backed up the file

		keyfilebackedup=true;
		}
	return keyfilebackedup;
}



bool MircryptionClass::save_keys()
{
	// save encrypted keys to file

	// returns true on succesful saving of keys

	FILE *fp;
   	char* p;
	char tempstring[MAXLINELENE+1];
	MircryptionKeypair *keyp=keypairs;
	bool encryptchannelnames=true;

	// keys not unlocked, so we refuse to save them

	if (masterpassphrase[0]=='\0' || keys==0 || passphrasesunlockedflag==false)
		return false;

	// before we save any changes, we backup the key file (but only if keys have changed since last backup)

	if (!keyfilebackedup)
		backupkeys();

	// open the file to write

	if ((fp=fopen(keyfilename,"w"))==(FILE *)NULL)
		return false;
	
	// save a header

	fprintf(fp,"// Mircryption Channel Keys - %s - stored in encrypted form (do not forget your master passphrase)\n",MIRCRYPTIONCLASSVER);
	if (encryptchannelnames==true)
		fprintf(fp,"// Using Encrypted Channel Names\n");
	fprintf(fp,"//\n");

	// save the magic id in encrypted form to help verify proper decrypting on next load

	p=encrypt_string(masterpassphrase,MAGICID);
	fprintf(fp,"MAGICID %s\n",p);
	bleachdelete(p);

	// cycle through all keys and save them in encrypted form

	// ATTN: we should check return codes from the fprintf below to make sure write succeeds

	while (keyp!=NULL)
		{
		if (strcmp(keyp->channelname,"MAGICID")!=0)
			    {
				if (encryptchannelnames)
					{
					// encrypt channel name and key

					mc_encrypt2key(masterpassphrase, keyp->channelname, tempstring);
					p=encrypt_string(masterpassphrase,keyp->key);
					fprintf(fp,"%s %s\n",tempstring,p);
					bleachdelete(p);
					}
				else
					{
					// dont encrypt channel name, just key

					p=encrypt_string(masterpassphrase,keyp->key);
					fprintf(fp,"%s %s\n",keyp->channelname,p);
					bleachdelete(p);
					}
			    }
		keyp=keyp->nextp;
		}

	fclose(fp);
	// bleach tempstrings

	memset(tempstring,1,MAXLINELENE);
	return true;
}
//---------------------------------------------------------------------------




//---------------------------------------------------------------------------

char* MircryptionClass::lookup_channelkey(char *channelname,char *key,bool wantencrypted)
{
	// internal func called to return the key associated with a channel

	// lookup the keypass for a channel or return "" if it doesnt exist

	// parameter wantencrypted tells us if it wants us to return undecrypted values (if keys are not yet unlocked), OR the keyword "_ENCRYPTED_" for keys which are not unlocked

	MircryptionKeypair *keyp=keypairs;

	mcensuresafebuflen(channelname,MAXCHANNELNAMESIZE);

	// protect against too long keys

	if (strlen(key)>MAXSAFEKEYSIZE)
		key[MAXSAFEKEYSIZE]='\0';

	// new force lowercase, to be safe

//	mylowercasify(channelname);


	if (foundencryptedchannels)
		{
		// if the keyfile has encrypted channels, then we need to make sure it is unlocked before we can lookup a channel key

		// kind of a kludge, but we need to reset foundencryptedchannels to avoid a recursive call back to us during decyrption

		foundencryptedchannels=false;
		verify_keysunlocked();
		}

	// loop through keys

	while (keyp!=NULL)
		{
		if (strcmp(keyp->channelname,channelname)==0)
			{
			// found a match, so return it

			strcpy(key,keyp->key);
			break;
			}
		keyp=keyp->nextp;
		}

	// if keys are not unlocked, modify returned value depending on wantencrypted parameter

	if (keyp==NULL)
		strcpy(key,"");
	else if (key[0]!='\0' && passphrasesunlockedflag==false && !wantencrypted)
		strcpy(key,"_ENCRYPTED_");
	return key;
}
//---------------------------------------------------------------------------




//---------------------------------------------------------------------------

void MircryptionClass::decrypt2_substring(char *insubstring,char *outstring, char *key)
{
	// given a substring which has start and end tags removed, decrypt it and store result in outstring


	// undo whitespace conversion created during encryption

	unrepwhitespaces(insubstring);

	// protect against too long keys

	if (strlen(key)>MAXSAFEKEYSIZE)
		key[MAXSAFEKEYSIZE]='\0';

	// decrypt it using provided key

	char* p = search_decrypt_string(key,insubstring);
	// copy to output

	if (p!=NULL)
		{
		// copy the result, ABSENT the initial crc tag

		strcpy(outstring,p+strlen(MCPS2_CRC));
		bleachdelete(p);
		}
	else
		strcpy(outstring,"");
}


char* MircryptionClass::search_decrypt_string(char *inkey, char *instring)
{
	// this is a wrapper for the blowfish command decrypt_string(char *key,char *instring) which can search through all decryption keys if the suggested one doesnt work

	// this is useful if we have some text and dont know which key to apply, though can be a bit slow if you have a lot of keys.

	char* p;
	char key[MAXKEYSIZE];
	char mcps2crcstr[10];
	int mcps2crclen;
	MircryptionKeypair *keyp=NULL;

	// crc tag

	strcpy(mcps2crcstr,MCPS2_CRC);
	mcps2crclen=strlen(mcps2crcstr);
	
	// protect against too long keys

	if (strlen(inkey)>MAXSAFEKEYSIZE)
		inkey[MAXSAFEKEYSIZE]='\0';

	// start with suggested key

	strcpy(key,inkey);
	for (;;)
		{
		// decrypt it

		p = decrypt_string(key,instring);
		if (p==NULL)
			break;
		// here we check if decryption is valid by checking a tag which will be a certain value if decryption is successful

		if (strncmp(p,mcps2crcstr,mcps2crclen)==0)
			{
			// ok, successfully decrypted, return pointer (caller should delete p when we return!)

			break;
			}
		// key did not work, do try another

		bleachdelete(p);
		p=NULL;
		// get next key to try

		if (keyp==NULL)
			keyp=keypairs;
		else
			keyp=keyp->nextp;
		if (keyp==NULL)
			break;
		strcpy(key,keyp->key);
		}
	memset(key,0,MAXKEYSIZE);
	return p;
}

//---------------------------------------------------------------------------



//---------------------------------------------------------------------------

void MircryptionClass::repwhitespaces(char *str)
{
	// given a blowfish encoded string str, convert all whitespaces to non-64bit (blowfish used) replacement characters

	char *p=str;
	char c;
	for (;;)
		{
		c=*p;
		if (c=='\0')
			break;
		if (c==9)
			*p=(char)162;
		else if (c==10)
			*p=(char)163;
		else if (c==13)
			*p=(char)164;
		else if (c==32)
			*p=(char)165;
		++p;
		}
}


void MircryptionClass::unrepwhitespaces(char *str)
{
	// unconvert all whitespace replaced characters above to original whitespaced

	char *p=str;
	char c;
	for (;;)
		{
		c=*p;
		if (c=='\0')
			break;
		if (c==(char)162)
			*p=(char)9;
		else if (c==(char)163)
			*p=(char)10;
		else if (c==(char)164)
			*p=(char)13;
		else if (c==(char)165)
			*p=(char)32;
		++p;
		}
}


char* MircryptionClass::mystrstr(char *startp,char *substring)
{
	// inefficient implementation of strstr - since im not sure builtin one is working

	int substrlen=strlen(substring);
	while (*startp!='\0')
		{
		if (strncmp(startp,substring,substrlen)==0)
			return startp;
		++startp;
		}
	return (char *)NULL;
}


void MircryptionClass::stripnicks(char *str)
{
	// strip any leading <nick> or [nick] or 'timestamp [nick]' from lines

	// the tricky part is the potential confusion from timestamps or alternative ways of specifying nick...

	char c;
	bool atlinefront=true;
	bool foundnick=false;
	char *cp=str;
	int anglecount=0;
	int bracketcount=0;
	char *nickstart=str;
	bool foundanglebracket=false;
	bool skipline=false;
	// if alwayskillprefix is true, then we kill everything to the left of the first space found (not inside a bracket/angle)

	// if alwayskillprefix is false, then we only kill prefixes if they were in a breacket/angle container

	bool alwayskillprefix=true;

	while ((c=*cp)!='\0')
		{
		
		// prepare for next char

		if (c==13 || c==10)
			{
			// on a linebreak we reset so we look for the next nick prefix

			atlinefront=true;
			foundnick=false;
			foundanglebracket=false;
			skipline=false;
			anglecount=0;
			bracketcount=0;
			nickstart=cp+1;
			}
		else
			{
			if (c=='<')
				{
				++anglecount;
				foundanglebracket=true;
				}
			else if (c=='>')
				--anglecount;
			else if (c=='[')
				{
				++bracketcount;
				foundanglebracket=true;
				}
			else if (c==']')
				--bracketcount;
			else if (foundnick==false && !atlinefront && !skipline)
				{
				// havent found the nick yet, could this be a good place for us? we are looking for a space after any brackets+angles

				if (c==32)
					{
					// we found a leading space, should we consider it the start of the real sentence after the nick

					if (alwayskillprefix==false && foundanglebracket==false)
						{
						// space without brackets in this alwayskillprefix==false mode tells us to skip this line

						skipline=true;
						}
					else if (anglecount==0 && bracketcount==0)
						{
						// we are not inside any angels or brackets so we consider this a separator for the nick

						foundnick=true;
						// now remove it

						strcpy(nickstart,cp+1);
						// and adjust cp

						cp=nickstart;
						}
					}
				}
			atlinefront=false;
			}
		++cp;
		}
}

void MircryptionClass::mylowercasify(char *str)
{
	if (str==NULL)
		return;

	int len=strlen(str);
	char c;

	for (int count=0;count<len;++count)
		{
		c= str[count];
		if (c>='A' && c<='Z')
			str[count]=c+32;
		}
}
//---------------------------------------------------------------------------




//---------------------------------------------------------------------------

void MircryptionClass::bleachmemory()
{
	// overwrite all passwords before freeing memory

	MircryptionKeypair *keyp=keypairs;

	// bleach master pass

	memset(masterpassphrase,0,MAXKEYSIZE-1);
	passphrasesunlockedflag=false;

	// loop through keys and bleach them

	while (keyp!=NULL)
		{
		memset(keyp->key,0,strlen(keyp->key));
		memset(keyp->channelname,0,strlen(keyp->channelname));
		keyp=keyp->nextp;
		}

}

void MircryptionClass::bleachdelete(char *p)
{
	// bleach the string and delete it

	memset(p,0,strlen(p));
	delete p;
}
//---------------------------------------------------------------------------




//---------------------------------------------------------------------------

int MircryptionClass::checkpasswordquality(char *key,char *errorstring)
{
	// static function which evaluates quality of key

	// returns <0 for keys considered unacceptable

	if (strlen(key)<14)
		{
		strcpy(errorstring,"Please do not use passwords smaller than 14 characters (20-50 characters recommended, mix case and digits, do not use dictionary words).");
		return -1;
		}

	// protect against too long keys

	if (strlen(key)>MAXSAFEKEYSIZE)
		key[MAXSAFEKEYSIZE]='\0';

	return 1;
}
//---------------------------------------------------------------------------







//---------------------------------------------------------------------------

//---------------------------------------------------------------------------

// PLATFORM DEPENDENT FUNCTIONS - OVERRIDE THESE FOR YOUR CLIENT

//---------------------------------------------------------------------------

//---------------------------------------------------------------------------











//---------------------------------------------------------------------------

// IMPORTANT: THESE ARE VIRTUAL FUNCTIONS, THEY NEED TO BE OVERRIDEN BY

//   PLATFORM-DEPENDENT IMPLEMENTATION, depending on your irc client.


char *MircryptionClass::get_classversionstring()
{
	// return some version information

	// by default return defined macro - you dont have to override this one

	return MIRCRYPTIONCLASSVER;
}

bool MircryptionClass::present_messagebox(char *messagetext,char *windowtitle)
{
	// present some info to the user, preferably in a pop-up modal dialog

	// returns true on success


	// by default just display it in status window - you dont have to override this one

	display_statustext(messagetext);

	return false;
}

bool MircryptionClass::request_unlockpassphrase()
{
	// we need the user to give us a valid master passphrase

	// returns true if user gives us a valid passphrase, and unlock the keys, false if not

	return false;
}


bool MircryptionClass::send_irccommand(char *irccommand,char *text)
{
	// send an irc command to server

	// returns true on success

	return false;
}

bool MircryptionClass::display_statustext(char *messagetext)
{
	// display some information for the user in some default text area (like status window in mirc)

	// returns true on success

	return false;
}
//---------------------------------------------------------------------------



syntax highlighted by Code2HTML, v. 0.9.1