///*****************************
// *** FiSH v0.97a for xchat ***
///*****************************


#include "xchat-plugin.h"
#include "FiSH.h"

static xchat_plugin *ph=0;   // plugin handle

unsigned char default_iniKey[]="blowinikey\0ADDITIONAL SPACE FOR CUSTOM BLOW.INI PASSWORD";
unsigned char iniKey[100], iniPath[255], randomPath[255];
unsigned char g_myPrivKey[300], g_myPubKey[300];

#define DEFAULT_FORMAT "\00315\002<\002\003%s\00315\002>\002\003\t%s\n"		// <nick>	message
#define FORMAT_MSG_SEND "\00315\002*\002\003%s\00315\002*\002\003\t%s"		// *nick*	message
#define FORMAT_NOTICE_SEND "\00315\002]\002\003%s\00315\002[\002\003\t%s"	// ]nick[	notice
#define	unsetiniFlag (void *)0xBEEF



#ifdef WIN32
HINSTANCE g_hInstance;

BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
	{
		case DLL_PROCESS_ATTACH:
			PRNGInit();
			if(GetModuleFileName(hModule, randomPath, sizeof(randomPath)) == 0) return FALSE;
			strcpy(strrchr(randomPath, 0x5C), "\\random.ECL");
			PRNGLoad(randomPath);
			PRNGAddEvent();
			g_hInstance=hModule;
			break;
    }
    return TRUE;
}
#endif



// encrypt a message (using key for target contactName)
static int FiSH_encrypt(char *msg_ptr, char *target, char *bf_dest)
{
	unsigned char contactName[100]="", theKey[500]="";


	if(msg_ptr==NULL || *msg_ptr=='\0' || target==NULL || *target=='\0' || bf_dest==NULL) return XCHAT_EAT_NONE;

	if(strlen(target) >= sizeof(contactName)) return XCHAT_EAT_NONE;
	strcpy(contactName, target);
	FixContactName(contactName);	// replace '[' and ']' with '~' in contact name

	GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath);
	if(theKey[0]==0 || strlen(theKey)<4) return XCHAT_EAT_NONE;		// don't process, key not found in ini

	if(strncmp(theKey, "+OK ", 4)==0)
	{	// key is encrypted, lets decrypt
		decrypt_string((char *)iniKey, theKey+4, theKey, strlen(theKey+4));
		if(*theKey=='\0')
		{	// don't process, decrypted key is bad
			ZeroMemory(theKey, sizeof(theKey));
			return XCHAT_EAT_NONE;
		}
	}

	encrypt_string(theKey, msg_ptr, bf_dest, strlen(msg_ptr));
	ZeroMemory(theKey, sizeof(theKey));

	bf_dest[512]=0;

	return 166;
}



// decrypt a base64 cipher text (using key for target contactName)
static int FiSH_decrypt(char *msg_ptr, char *contactName)
{
	unsigned char theKey[500]="", bf_dest[1500]="", myMark[20]="";
	unsigned int msg_len, i, mark_broken_block=0;


	if(msg_ptr==NULL || *msg_ptr=='\0' || contactName==NULL || *contactName=='\0') return XCHAT_EAT_NONE;

	FixContactName(contactName);	// replace '[' and ']' with '~' in contact name

	GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath);
	if(theKey[0]==0 || strlen(theKey)<4) return XCHAT_EAT_NONE;	// don't process, key not found in ini
	if(strncmp(theKey, "+OK ", 4)==0)
	{	// key is encrypted, lets decrypt
		decrypt_string((char *)iniKey, theKey+4, theKey, strlen(theKey+4));
		if(*theKey=='\0')
		{	// don't process, decrypted key is bad
			ZeroMemory(theKey, sizeof(theKey));
			return XCHAT_EAT_NONE;
		}
	}

	// Verify base64 string
	msg_len=strlen(msg_ptr);
	if((strspn(msg_ptr, B64) != msg_len) || (msg_len < 12)) return XCHAT_EAT_NONE;

	// block-align blowcrypt strings if truncated by IRC server (each block is 12 chars long)
	// such a truncated block is destroyed and not needed anymore
	if(msg_len != (msg_len/12)*12)
	{
		msg_len=(msg_len/12)*12;
		msg_ptr[msg_len]='\0';
		GetPrivateProfileString("FiSH", "mark_broken_block", " \002&\002", myMark, sizeof(myMark), iniPath);
		if(*myMark=='\0' || *myMark=='0' || *myMark=='n' || *myMark=='N') mark_broken_block=0;
		else mark_broken_block=1;
	}

	decrypt_string(theKey, msg_ptr, bf_dest, msg_len);
	ZeroMemory(theKey, sizeof(theKey));
	if(*bf_dest=='\0') return XCHAT_EAT_NONE;	// don't process, decrypted msg is bad

	i=0;
	while(bf_dest[i] != 0x0A && bf_dest[i] != 0x0D && bf_dest[i] != 0x00) i++;
	bf_dest[i]=0x00;	// in case of wrong key, decrypted message might have control characters -> cut message

	// append broken-block-mark?
	if(mark_broken_block) strcat(bf_dest, myMark);

	strcpy(msg_ptr, bf_dest);	// copy decrypted message back (overwriting the base64 cipher text)
	ZeroMemory(bf_dest, sizeof(bf_dest));

	return 166;
}



static int decrypt_incoming(char *word[], char *word_eol[], void *userdata)
{
	unsigned char *msg_ptr, contactName[100]="", from_nick[50], ini_incoming[20]="", msg_event[100]="";
	unsigned int psylog_found=0;
	xchat_context *find_query_ctx;

	// word[1] = :fromnick!ident@host
	// word[2] = PRIVMSG/NOTICE/TOPIC
	// word[3] = target nick/#channel
	// word[4] = :+OK or :mcps
	// word[5] = BASe.64/STRinG

	if(word[5]==0 || word[5][0]==0) return XCHAT_EAT_NONE;
	if( (strcmp(word[4], ":+OK")!=0) &&
		(strcmp(word[4], ":mcps")!=0) &&
		(strncmp(word[1], ":-psyBNC!", 9)!=0))
		return XCHAT_EAT_NONE;		// prefix/psyBNC not found, don't process

	GetPrivateProfileString("FiSH", "process_incoming", "1", ini_incoming, sizeof(ini_incoming), iniPath);
	if(*ini_incoming=='0' || *ini_incoming=='n' || *ini_incoming=='N') return XCHAT_EAT_NONE;

	if(word[1][0] == ':') ExtractRnick(from_nick, word[1]);
	else from_nick[0]=0;

	msg_ptr = word[5];

	if (word[3][0]=='#') strcpy(contactName, word[3]);
	else if(strcmp(from_nick, "-psyBNC")==0)
	{
		// word[8] = :(nick!ident@host.org)
		// word[9] = +OK or mcps
		// word[10] = BASe.64/STRinG
		if(word[10]==0) return XCHAT_EAT_NONE;

		if(strncmp(word[8], ":(", 2)==0) ExtractRnick(contactName, word[8]+2);
		else return XCHAT_EAT_NONE;

		if( (strcmp(word[9], "+OK")!=0) &&
			(strcmp(word[9], "mcps")!=0))
			return XCHAT_EAT_NONE;		// prefix not found, don't process

		msg_ptr = word[10];
		word[10] = 0;
		psylog_found=1;
	}
	else strcpy(contactName, from_nick);

	// decrypt the base64 cipher text (using key for target contactName)
	if(FiSH_decrypt(msg_ptr, contactName) == XCHAT_EAT_NONE) return XCHAT_EAT_NONE;

	// move message pointer to previous argument ('+OK' not needed)
	if(psylog_found) strcpy(strstr(word_eol[4], "+OK "), msg_ptr);
	else word_eol[4] = msg_ptr;

	// let xchat display ACTION/NOTICE/TOPIC messages (without crypt-mark ...)
	if (strncmp(msg_ptr, "\001ACTION ", 8)==0 ||
		strcmp(word[2], "TOPIC")==0 ||
		strcmp(word[2], "NOTICE")==0) return XCHAT_EAT_NONE;

	if(contactName[0] != '#')
	{
		find_query_ctx = xchat_find_context(ph, NULL, from_nick);
		if(find_query_ctx == 0)
		{	// no query window yet, lets open one
			xchat_commandf(ph, "query %s", from_nick);
			find_query_ctx = xchat_find_context(ph, NULL, from_nick);
		}
		xchat_set_context(ph, find_query_ctx);

		GetPrivateProfileString("incoming_format", "crypted_privmsg", DEFAULT_FORMAT, msg_event, sizeof(msg_event), iniPath);
	}
	else GetPrivateProfileString("incoming_format", "crypted_chanmsg", DEFAULT_FORMAT, msg_event, sizeof(msg_event), iniPath);

	// display formatted nick and decrypted message
	xchat_printf(ph, msg_event, from_nick, word_eol[4]+psylog_found);
	return XCHAT_EAT_XCHAT;
}



static int encrypt_outgoing(char *word[], char *word_eol[], void *userdata)
{
	unsigned char bf_dest[2000]="", new_msg[1000]="";
	unsigned char ini_tmp_buf[20]="";
	unsigned int pp;
	const char *contactPtr, *own_nick;

	if ((word_eol[1]==0) || (word_eol[1][0]==0)) return XCHAT_EAT_NONE;		// don't process

	GetPrivateProfileString("FiSH", "process_outgoing", "1", ini_tmp_buf, sizeof(ini_tmp_buf), iniPath);
	if(ini_tmp_buf[0]=='0' || ini_tmp_buf[0]=='n' || ini_tmp_buf[0]=='N') return XCHAT_EAT_NONE;

	contactPtr = xchat_get_info(ph, "channel");
	own_nick = xchat_get_info(ph, "nick");

	// plain-prefix in msg found?
	pp=0;
	GetPrivateProfileString("FiSH", "plain_prefix", "+p ", ini_tmp_buf, sizeof(ini_tmp_buf), iniPath);
	if(ini_tmp_buf[0] != 0)
	{
		pp=strlen(ini_tmp_buf);
		if(strnicmp(word_eol[1], ini_tmp_buf, pp)==0)
		{
			sprintf(new_msg, "PRIVMSG %s :%s", contactPtr, word_eol[1]+pp);
			goto MySendMsg;
		}
		else pp=0;
	}

	// encrypt a message (using key for target)
	if(FiSH_encrypt(word_eol[1], (char *)contactPtr, bf_dest) == XCHAT_EAT_NONE) return XCHAT_EAT_NONE;

	sprintf(new_msg, "PRIVMSG %s :%s %s\n", contactPtr, "+OK", bf_dest);

MySendMsg:
	if(contactPtr[0] != '#')
	{
		if(pp) xchat_emit_print(ph, "Private Message to Dialog", own_nick, word_eol[1]+pp, NULL, NULL);
		else GetPrivateProfileString("outgoing_format", "crypted_privmsg", DEFAULT_FORMAT, bf_dest, sizeof(bf_dest), iniPath);
	}
	else
	{
		if(pp) xchat_emit_print(ph, "Channel Message", own_nick, word_eol[1]+pp, NULL, NULL);
		else GetPrivateProfileString("outgoing_format", "crypted_chanmsg", DEFAULT_FORMAT, bf_dest, sizeof(bf_dest), iniPath);
	}

	// display formatted plain-text message in local IRC client
	if(!pp) xchat_printf(ph, bf_dest, own_nick, word_eol[1]+pp);

	ZeroMemory(bf_dest, sizeof(bf_dest));

	xchat_command(ph, new_msg);	// send message to IRC server
	return XCHAT_EAT_XCHAT;
}



static int decrypt_topic_332(char *word[], char *word_eol[], void *userdata)
{
	unsigned char contactName[100]="";
	unsigned char *msg_ptr, ini_incoming[20]="";

	// word[1] = :irc.server.com
	// word[2] = 332
	// word[3] = own_nick
	// word[4] = target #channel
	// word[5] = :+OK or :mcps
	// word[6] = BASe.64/T0PiC.STRinG

	if(word[6]==0 || word[6][0]==0) return XCHAT_EAT_NONE;
	if( (strcmp(word[5], ":+OK")!=0) &&
		(strcmp(word[5], ":mcps")!=0) &&
		(word[4][0] != '#'))
		return XCHAT_EAT_NONE;		// prefix not found/not a channel, don't process

	GetPrivateProfileString("FiSH", "process_incoming", "1", ini_incoming, sizeof(ini_incoming), iniPath);
	if(*ini_incoming=='0' || *ini_incoming=='n' || *ini_incoming=='N') return XCHAT_EAT_NONE;

	msg_ptr = word[6];

	strcpy(contactName, word[4]);

	// decrypt the base64 cipher text (using key for target contactName)
	if(FiSH_decrypt(msg_ptr, contactName) == XCHAT_EAT_NONE) return XCHAT_EAT_NONE;

	// move message pointer to previous argument ('+OK' not needed)
	word_eol[5] = msg_ptr;

	return XCHAT_EAT_NONE;
}



// copy key for old nick to use with the new one
static int nick_changed(char *word[], char *word_eol[], void *userdata)
{
	unsigned char contactName[100]="", theKey[500]="", ini_nicktracker[10];

	// word[1] = :user@host.com
	// word[2] = NICK
	// word[3] = :newNick

	if(word[3]==0 || word[3][1]=='\0') return XCHAT_EAT_NONE;

	GetPrivateProfileString("FiSH", "nicktracker", "1", ini_nicktracker, sizeof(ini_nicktracker), iniPath);

	if(	*ini_nicktracker=='0' || *ini_nicktracker=='N' || *ini_nicktracker=='n' ||
		(ExtractRnick(contactName, word[1])==0) ||
		(stricmp(contactName, word[3]+1)==0))
		return XCHAT_EAT_NONE;

	// process only if a query is open
	if(xchat_find_context(ph, NULL, contactName)==0) return XCHAT_EAT_NONE; 

	FixContactName(contactName);	// replace '[' and ']' with '~' in contact name

	GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath);
	if(strlen(theKey) < 4) return XCHAT_EAT_NONE;	// don't process, key not found in ini

	strcpy(contactName, word[3]+1);
	FixContactName(contactName);

	WritePrivateProfileString(contactName, "key", theKey, iniPath);

	ZeroMemory(theKey, sizeof(theKey));

	return XCHAT_EAT_NONE;
}



// Set a custom blow.ini password, xchat syntax: /setinipw <sekure_blow.ini_password>
static int command_setinipw(char *word[], char *word_eol[], void *userdata)
{
	unsigned int i=0, pw_len, re_enc=0;
	unsigned char B64digest[50], SHA256digest[35];
	unsigned char bfKey[500], old_iniKey[100], *fptr, *ok_ptr, line_buf[1000], iniPath_new[300];
	FILE *h_ini, *h_ini_new;

	//char *cmd = word[1];
	char *iniPW = word[2];

	pw_len=strlen(iniPW);
	if(pw_len < 7)
    {
		xchat_printf(ph, "\002FiSH:\002 Password too short, at least 7 characters needed! Usage: /setinipw <sekure_blow.ini_password>");
		return XCHAT_EAT_ALL;
    }

	SHA256_memory(iniPW, pw_len, SHA256digest);
	memset(iniPW, 0x20, pw_len);
	for(i=0;i<40872;i++) SHA256_memory(SHA256digest, 32, SHA256digest);
	htob64(SHA256digest, B64digest, 32);
	strcpy(old_iniKey, iniKey);
	if(userdata == unsetiniFlag) strcpy(iniKey, default_iniKey);	// unsetinipw
	else strcpy(iniKey, B64digest);	// this is used for encrypting blow.ini
	for(i=0;i<30752;i++) SHA256_memory(SHA256digest, 32, SHA256digest);
	htob64(SHA256digest, B64digest, 32);	// this is used to verify the entered password
	ZeroMemory(SHA256digest, sizeof(SHA256digest));


	// re-encrypt blow.ini with new password
	strcpy(iniPath_new, iniPath);
	strcat(iniPath_new, "_new");
	h_ini_new=fopen(iniPath_new, "w");
	h_ini=fopen(iniPath,"r");
	if(h_ini && h_ini_new)
	{
		do
		{
			fptr=fgets(line_buf,sizeof(line_buf)-2,h_ini);
			if(fptr)
			{
				ok_ptr=strstr(line_buf, "+OK ");
				if(ok_ptr)
				{
					re_enc=1;
					strtok(ok_ptr+4," \n\r");
					decrypt_string(old_iniKey, ok_ptr+4, bfKey, strlen(ok_ptr+4));
					memset(ok_ptr+4, 0, strlen(ok_ptr+4)+1);
					encrypt_string(iniKey, bfKey, ok_ptr+4, strlen(bfKey));
					strcat(line_buf, "\n");
				}
				fprintf(h_ini_new,"%s",line_buf);
			}
		}
		while (!feof(h_ini));
		ZeroMemory(bfKey, sizeof(bfKey));
		ZeroMemory(line_buf, sizeof(line_buf));
		ZeroMemory(old_iniKey, sizeof(old_iniKey));
		fclose(h_ini);
		fclose(h_ini_new);
		remove(iniPath);
		rename(iniPath_new, iniPath);
	}

	WritePrivateProfileString("FiSH", "ini_password_Hash", B64digest, iniPath);

	ZeroMemory(B64digest, sizeof(B64digest));

	if(re_enc) xchat_printf(ph, "\002FiSH: Re-encrypted blow.ini\002 with new password.");

	if(userdata != unsetiniFlag) xchat_printf(ph, "\002FiSH:\002 blow.ini password hash saved.");
#ifndef WIN32
	if(userdata != unsetiniFlag)
	{
		xchat_print(ph, "\002FiSH:\002 You could use \002/load /path/to/fish.so <password>\002 next time to load FiSH and forward your blow.ini password as argument.");
		xchat_print(ph, "\002FiSH:\002 Or you enter your blow.ini password using \002/fishpw <password>\002 after loading FiSH...");
	}
#endif

	return XCHAT_EAT_ALL;
}



// Change back to default blow.ini password, xchat syntax: /unsetinipw
static int command_unsetinipw(char *word[], char *word_eol[], void *userdata)
{
	word[2] = "Some_boogie_dummy_key";

	command_setinipw(word, word_eol, unsetiniFlag);
	WritePrivateProfileString("FiSH", "ini_password_Hash", "0", iniPath);
	xchat_print(ph, "\002FiSH:\002 Changed back to default blow.ini password, you won't have to enter it on start-up anymore.");
	return XCHAT_EAT_ALL;
}



#ifndef WIN32
static int command_fishpw(char *word[], char *word_eol[], void *userdata)
{
	unsigned int i=0, pw_len;
	unsigned char iniPasswordHash[50], B64digest[50], SHA256digest[35];

	//char *cmd = word[1];
	char *iniPW = word[2];

	pw_len=strlen(iniPW);
	if(pw_len < 7)
    {
		xchat_printf(ph, "\002FiSH:\002 Password too short, at least 7 characters needed! Usage: /fishpw <password>");
		return XCHAT_EAT_ALL;
    }

	GetPrivateProfileString("FiSH", "ini_Password_hash", "0", iniPasswordHash, sizeof(iniPasswordHash), iniPath);
	if(strlen(iniPasswordHash) == 43)
	{
		SHA256_memory(iniPW, pw_len, SHA256digest);
		memset(iniPW, 0x20, pw_len);
		for(i=0;i<40872;i++) SHA256_memory(SHA256digest, 32, SHA256digest);
		htob64(SHA256digest, B64digest, 32);
		strcpy(iniKey, B64digest);      // this is used for encrypting blow.ini
		for(i=0;i<30752;i++) SHA256_memory(SHA256digest, 32, SHA256digest);
		htob64(SHA256digest, B64digest, 32);	// this is used to verify the entered password
		if(strcmp(B64digest, iniPasswordHash) != 0)
		{
			xchat_print(ph, "\002FiSH:\002 Wrong blow.ini password entered, try again...\n");
			iniKey[0]=0;
			return XCHAT_EAT_ALL;
		}
		xchat_print(ph, "\002FiSH:\002 Correct blow.ini password entered, lets go!\n");

		ZeroMemory(SHA256digest, sizeof(SHA256digest));
		ZeroMemory(B64digest, sizeof(B64digest));
	}
	else xchat_print(ph, "\002FiSH:\002 ERROR: Invalid ini_Password_hash in blow.ini found!\n");

	return XCHAT_EAT_ALL;
}
#endif



// xchat syntax: /setkey [<nick/#channel>] <sekure_key>
static int command_setkey(char *word[], char *word_eol[], void *userdata)
{
	unsigned char contactName[100]="", encryptedKey[500]="";

	//char *cmd = word[1];
	char *target = word[2];
	char *key = word[3];


	if (*target==0 || target==0)
	{
		xchat_printf(ph, "\002FiSH:\002 No parameters. Usage: /setkey [<nick/#channel>] <sekure_key>");
		return XCHAT_EAT_ALL;
	}

	if (*key==0 || key==0)
	{
		// only one paramter given - it's the key
		key = target;
		target = (char *)xchat_get_info(ph, "channel");
		if (target==NULL || stricmp(target, xchat_get_info(ph, "network"))==0)
		{
			xchat_printf(ph, "\002FiSH:\002 Please define nick/#channel. Usage: /setkey [<nick/#channel>] <sekure_key>");
			return XCHAT_EAT_ALL;
		}
	}

	if(strlen(target) >= sizeof(contactName)) return XCHAT_EAT_NONE;

	strcpy(contactName, target);
	FixContactName(contactName);	// replace '[' and ']' with '~' in contact name

	strcpy(encryptedKey, key);
	memset(key, 0x20, strlen(key));
	encrypt_key(encryptedKey);

	WritePrivateProfileString(contactName, "key", encryptedKey, iniPath);

	ZeroMemory(encryptedKey, sizeof(encryptedKey));

	xchat_printf(ph, "\002FiSH:\002 Key for %s successfully set!", target);
	return XCHAT_EAT_ALL;
}



// xchat syntax: /delkey <nick/#channel
static int command_delkey(char *word[], char *word_eol[], void *userdata)
{
	unsigned char contactName[100]="";

	//char *cmd = word[1];
	char *target = word[2];


	if (*target==0 || target==0)
	{
		xchat_printf(ph, "\002FiSH:\002 No parameters. Usage: /delkey <nick/#channel>");
		return XCHAT_EAT_ALL;
	}

	if(strlen(target) >= sizeof(contactName)) return XCHAT_EAT_NONE;

	strcpy(contactName, target);
	FixContactName(contactName);	// replace '[' and ']' with '~' in contact name

	WritePrivateProfileString(contactName, "key", "0\r\n", iniPath);

	xchat_printf(ph, "\002FiSH:\002 Key for %s successfully removed!", target);
	return XCHAT_EAT_ALL;
}



// display key, xchat syntax: /key [<nick/#channel>]
static int command_key(char *word[], char *word_eol[], void *userdata)
{
	unsigned char contactName[100]="", theKey[500]="";

	//char *cmd = word[1];
	char *target = word[2];


	if(*target==0 || target==0)
	{
		// no paramter given - try current window
		target = (char *)xchat_get_info(ph, "channel");
		if (target==NULL || stricmp(target, xchat_get_info(ph, "network"))==0)
		{
			xchat_printf(ph, "\002FiSH:\002 Please define nick/#channel. Usage: /key <nick/#channel>");
			return XCHAT_EAT_ALL;
		}
	}

	if(strlen(target) >= sizeof(contactName)) return XCHAT_EAT_NONE;

	strcpy(contactName, target);
	FixContactName(contactName);	// replace '[' and ']' with '~' in contact name

	GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath);
	if(*theKey=='\0' || strlen(theKey)<4)
	{	// don't process, key not found in ini
		xchat_printf(ph, "\002FiSH:\002 Key for %s not found!", target);
		return XCHAT_EAT_ALL;
	}

	if(strncmp(theKey, "+OK ", 4)==0)
	{       // key is encrypted, lets decrypt
		decrypt_string((char *)iniKey, theKey+4, theKey, strlen(theKey+4));
		if(*theKey=='\0')
		{	// don't process, decrypted key is bad
			xchat_printf(ph, "\002FiSH:\002 Key for %s invalid!", target);
			return XCHAT_EAT_ALL;
		}
	}

	xchat_printf(ph, "\002FiSH:\002 Key for %s: %s", target, theKey);
	ZeroMemory(theKey, sizeof(theKey));
	return XCHAT_EAT_ALL;
}




// set encrypted topic for current channel, xchat syntax: /topic+ <your topic>
static int command_crypt_TOPIC(char *word[], char *word_eol[], void *userdata)
{
	unsigned char *target, bf_dest[2000]="";

	//char *cmd = word[1];
	char *topic = word_eol[2];


	if (*topic==0 || topic==0)
	{
		xchat_printf(ph, "\002FiSH:\002 No parameters. Usage: /topic+ <your new topic>");
		return XCHAT_EAT_ALL;
	}

	target = (char *)xchat_get_info(ph, "channel");
	if (target==NULL || *target!='#')
	{
		xchat_printf(ph, "\002FiSH:\002 Please change to the channel window where you want to set the topic!");
		return XCHAT_EAT_ALL;
	}

	// encrypt a message (using key for target)
	if(FiSH_encrypt(topic, target, bf_dest) == XCHAT_EAT_NONE)
	{
		xchat_printf(ph, "\002FiSH:\002 /topic+ error, no key found for %s. Usage: /topic+ <your new topic>", target);
		return XCHAT_EAT_ALL;
	}

	xchat_commandf(ph, "TOPIC %s %s %s\n", target, "+OK", bf_dest);
	ZeroMemory(bf_dest, sizeof(bf_dest));

	return XCHAT_EAT_ALL;
}



// send an encrypted NOTICE message, xchat syntax: /notice+ <nick/#channel> <your notice>
static int command_crypt_NOTICE(char *word[], char *word_eol[], void *userdata)
{
	unsigned char bf_dest[2000]="";

	//char *cmd = word[1];
	char *target = word[2];
	char *notice = word_eol[3];


	if (*target==0 || target==0 || notice==0 || *notice==0)
	{
		xchat_printf(ph, "\002FiSH:\002 Bad parameters. Usage: /notice+ <nick/#channel> <your notice>");
		return XCHAT_EAT_ALL;
	}

	// encrypt a message (using key for target)
	if(FiSH_encrypt(notice, target, bf_dest) == XCHAT_EAT_NONE)
	{
		xchat_printf(ph, "\002FiSH:\002 /notice+ error, no key found for %s. Usage: /notice+ <nick/#channel> <your notice>", target);
		return XCHAT_EAT_ALL;
	}

	xchat_commandf(ph, "quote NOTICE %s :%s %s", target, "+OK", bf_dest);
	ZeroMemory(bf_dest, sizeof(bf_dest));

	xchat_printf(ph, FORMAT_NOTICE_SEND, target, notice);	// display notice in current window
	return XCHAT_EAT_ALL;
}



// send an encrypted message, xchat syntax: /msg+ <nick/#channel> <your message>
static int command_crypt_MSG(char *word[], char *word_eol[], void *userdata)
{
	unsigned char bf_dest[2000]="";
	xchat_context *find_query_ctx;

	char *target = word[2];
	char *message = word_eol[3];

	if (*target==0 || target==0 || message==0 || *message==0)
	{
		xchat_printf(ph, "\002FiSH:\002 Bad parameters. Usage: /msg+ <nick/#channel> <your message>");
		return XCHAT_EAT_ALL;
	}

	// encrypt a message (using key for target)
	if(FiSH_encrypt(message, target, bf_dest) == XCHAT_EAT_NONE)
	{
		xchat_printf(ph, "\002FiSH:\002 /msg+ error, no key found for %s. Usage: /msg+ <nick/#channel> <your message>", target);
		return XCHAT_EAT_ALL;
	}

	xchat_commandf(ph, "PRIVMSG %s :%s %s", target, "+OK", bf_dest);

	find_query_ctx = xchat_find_context(ph, NULL, target);
	if(find_query_ctx != 0)
	{	// open query/channel window found, display the event there
		xchat_set_context(ph, find_query_ctx);
		if(target[0] != '#') GetPrivateProfileString("outgoing_format", "crypted_privmsg", DEFAULT_FORMAT, bf_dest, sizeof(bf_dest), iniPath);
		else GetPrivateProfileString("outgoing_format", "crypted_chanmsg", DEFAULT_FORMAT, bf_dest, sizeof(bf_dest), iniPath);
		xchat_printf(ph, bf_dest, xchat_get_info(ph, "nick"), message);
	}
	else xchat_printf(ph, FORMAT_MSG_SEND, target, message);	// no open window found, display in current window

	ZeroMemory(bf_dest, sizeof(bf_dest));
	return XCHAT_EAT_ALL;
}



// Start a DH1080 key-exchange, xchat syntax: /keyx [<nick>]
static int command_keyx(char *word[], char *word_eol[], void *userdata)
{
	//char *cmd = word[1];
	char *target = word[2];
	xchat_context *find_query_ctx;


	if (*target==0 || target==0)
	{
		// no paramter given - try current window
		target = (char *)xchat_get_info(ph, "channel");
		if (target==NULL || stricmp(target, xchat_get_info(ph, "network"))==0)
		{
			xchat_printf(ph, "\002FiSH:\002 Please define target nick. Usage: /keyx <nick>");
			return XCHAT_EAT_ALL;
		}
	}

	if(target[0]=='#')
	{
		xchat_printf(ph, "\002FiSH:\002 KeyXchange does not work for channels!");
		return XCHAT_EAT_ALL;
	}


	DH1080_gen(g_myPrivKey, g_myPubKey);

	xchat_commandf(ph, "quote NOTICE %s :%s %s", target, "DH1080_INIT", g_myPubKey);	// send DH1080 init to target

	// if a query is already open, display info text there
	find_query_ctx = xchat_find_context(ph, NULL, target);
	if(find_query_ctx != 0) xchat_set_context(ph, find_query_ctx);

	xchat_printf(ph, "\002FiSH:\002 Sent my DH1080 public key to %s, waiting for reply ...", target);
	return XCHAT_EAT_ALL;
}



// DH1080 notice handling
static int notice_received(char *word[], char *word_eol[], void *userdata)
{
	unsigned int i;
	unsigned char hisPubKey[300], contactName[25]="", from_nick[25]="";
	xchat_context *find_query_ctx;

	//word[1];	// :nick!ident@host.com
	//char *cmd = word[2];
	//const char *target = word[3];	// target nick or #channel
	char *DH_msg = word[4];
	char *DH_pubkey = word[5];

	if(	word[5]==NULL || *word[5]=='\0' || *word[4]=='\0' ||
		*word[3]=='\0' || *word[1]=='\0') return XCHAT_EAT_NONE;

	if(strcmp(word[4], ":+OK")==0 || strcmp(word[4], ":mcps")==0)
		// encrypted notice message?
		return decrypt_incoming(word, word_eol, userdata);

	if(ExtractRnick(from_nick, word[1])==0) return XCHAT_EAT_NONE;
	i=strlen(DH_pubkey);
	if(i<179 || i>181) return XCHAT_EAT_NONE;


	// if a query is already open, display info text there
	find_query_ctx = xchat_find_context(ph, NULL, from_nick);

	if(strncmp(DH_msg, ":DH1080_INIT", 12)==0)
	{
		if(find_query_ctx != 0) xchat_set_context(ph, find_query_ctx);
		xchat_printf(ph, "\002FiSH:\002 Received DH1080 public key from %s, sending mine...", from_nick);

		DH1080_gen(g_myPrivKey, g_myPubKey);
		xchat_commandf(ph, "quote NOTICE %s :%s %s", from_nick, "DH1080_FINISH", g_myPubKey);	// send DH1080_FINISH (own pubkey) to from_nick
	}
	else if(strncmp(DH_msg, ":DH1080_FINISH", 14)!=0) return XCHAT_EAT_NONE;

	strcpy(hisPubKey, DH_pubkey);
	if(DH1080_comp(g_myPrivKey, hisPubKey)==0) return XCHAT_EAT_NONE;

	strcpy(contactName, from_nick);
	FixContactName(contactName);	// replace '[' and ']' with '~' in contact name
	encrypt_key(hisPubKey);
	WritePrivateProfileString(contactName, "key", hisPubKey, iniPath);

	ZeroMemory(hisPubKey, sizeof(hisPubKey));

	if(find_query_ctx != 0) xchat_set_context(ph, find_query_ctx);
	xchat_printf(ph, "\002FiSH:\002 Key for %s successfully set!", from_nick);
	return XCHAT_EAT_ALL;
}



int xchat_plugin_init(xchat_plugin *plugin_handle, char **plugin_name, char **plugin_desc, char **plugin_version, char *arg)
{
	unsigned char iniPasswordHash[50], SHA256digest[35], B64digest[50];
	unsigned int i;

	if (ph != 0)
	{
		xchat_print (ph, "\002FiSH is already loaded!\002\n");
		return FALSE;
	}

	// we need to save this for use with any xchat_* functions
	ph = plugin_handle;

	*plugin_name = "FiSH";
	*plugin_desc = "Blowfish IRC encryption, including secure Diffie-Hellman 1080 bit key-exchange";
	*plugin_version = "0.97a";

	initb64();
	mip=mirsys(500, 16);
	if(mip==NULL) return FALSE;

	strcpy(iniPath, xchat_get_info(ph, "xchatdir"));	// path to xchat config file
#ifdef WIN32
	strcat(iniPath, "\\blow.ini");
	PRNGAddEvent();
#else
	strcpy(randomPath, iniPath);
	strcat(randomPath, "/random.ECL");
	strcat(iniPath, "/blow.ini");
#endif

	GetPrivateProfileString("FiSH", "ini_Password_hash", "0", iniPasswordHash, sizeof(iniPasswordHash), iniPath);
	if(strlen(iniPasswordHash) == 43)
	{

#ifdef WIN32
		if(DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_PASS), NULL, (DLGPROC)PassFunc)==0)
		{
			xchat_print(ph, "\002FiSH:\002 No blow.ini password entered, try again...\n");
			xchat_print(ph, "\002FiSH not loaded.\002\n");
			return 0;
		}
#else
		if(arg==0 || *arg==0)
		{
			xchat_print(ph, "\002FiSH:\002 Please enter your blow.ini password using \002/fishpw <password>\002\n");
			xchat_print(ph, "\002FiSH:\002 You could also use \002/load /path/to/fish.so <password>\002 to load FiSH and forward your blow.ini password as argument.\n");
			iniKey[0]=0;
		}
		else strcpy(iniKey, arg);	// blow.ini password forwarded as argument
#endif

		if(*iniKey)
		{
			SHA256_memory(iniKey, strlen(iniKey), SHA256digest);
			for(i=0;i<40872;i++) SHA256_memory(SHA256digest, 32, SHA256digest);
			htob64(SHA256digest, B64digest, 32);
			strcpy(iniKey, B64digest);      // this is used for encrypting blow.ini
			for(i=0;i<30752;i++) SHA256_memory(SHA256digest, 32, SHA256digest);
			htob64(SHA256digest, B64digest, 32);	// this is used to verify the entered password
			if(strcmp(B64digest, iniPasswordHash) != 0)
			{
				xchat_print(ph, "\002FiSH:\002 Wrong blow.ini password entered, try again...\n");
				xchat_print(ph, "\002FiSH not loaded.\002\n");
				return 0;
			}
			xchat_print(ph, "\002FiSH:\002 Correct blow.ini password entered, lets go!\n");
		}
	}
	else
	{
		strcpy(iniKey, default_iniKey);
		xchat_print(ph, "\002FiSH:\002 Using default password to decrypt blow.ini... Try /setinipw to set a custom password.\n");
	}

	xchat_hook_server(ph, "PRIVMSG", XCHAT_PRI_NORM, decrypt_incoming, 0);
	xchat_hook_server(ph, "NOTICE", XCHAT_PRI_NORM, notice_received, 0);
	xchat_hook_server(ph, "TOPIC", XCHAT_PRI_NORM, decrypt_incoming, 0);
	xchat_hook_server(ph, "NICK", XCHAT_PRI_NORM, nick_changed, 0);
	xchat_hook_server(ph, "332", XCHAT_PRI_NORM, decrypt_topic_332, 0);
	xchat_hook_command(ph, "", XCHAT_PRI_NORM, encrypt_outgoing, 0, 0);

	xchat_hook_command(ph, "setkey", XCHAT_PRI_NORM, command_setkey, "Set key for target to sekure_key. If no target specified, the key for current window will be set to sekure_key. Usage: /setkey [<nick/#channel>] <sekure_key>", NULL);
	xchat_hook_command(ph, "delkey", XCHAT_PRI_NORM, command_delkey, "Delete key for target. You have to specify the target. Usage: /delkey <nick/#channel>", 0);
	xchat_hook_command(ph, "key", XCHAT_PRI_NORM, command_key, "Show key for target. If no target specified, the key for current window will be shown.\nUsage: /key [<nick/#channel>]", 0);
	xchat_hook_command(ph, "keyx", XCHAT_PRI_NORM, command_keyx, "Perform DH1080 KeyXchange with target. If no target specified, the KeyXchange takes place with the current query window. Usage: /keyx [<nick>]", 0);
	xchat_hook_command(ph, "setinipw", XCHAT_PRI_NORM, command_setinipw, "Set a custom password to encrypt your key-container (blow.ini) - you will be forced to enter it each time you load the module.\nUsage: /setinipw <sekure_blow.ini_password>", 0);
	xchat_hook_command(ph, "unsetinipw", XCHAT_PRI_NORM, command_unsetinipw, "Change back to default blow.ini password. Usage: /unsetinipw", 0);
	xchat_hook_command(ph, "topic+", XCHAT_PRI_NORM, command_crypt_TOPIC, "Set a new encrypted topic for the current channel. Usage: /topic+ <your topic>", 0);
	xchat_hook_command(ph, "notice+", XCHAT_PRI_NORM, command_crypt_NOTICE, "Send an encrypted notice. Usage: /notice+ <nick/#channel> <your notice>", 0);
	xchat_hook_command(ph, "msg+", XCHAT_PRI_NORM, command_crypt_MSG, "Send an encrypted message. Usage: /msg+ <nick/#channel> <your message>", 0);
#ifndef WIN32
	// only needed if blow.ini password was not forwarded using /load command
	if(iniKey[0]==0) xchat_hook_command(ph, "fishpw", XCHAT_PRI_NORM, command_fishpw, "Set FiSH password to decrypt your key-container (blow.ini).\nUsage: /fishpw <password>", 0);
#endif

	xchat_print(ph, "\002FiSH v0.97a\002 - encryption plugin for xchat \002loaded!\002\n");

	return 1;	// return 1 for success
}



int xchat_plugin_deinit(xchat_plugin *plugin_handle)
{
	xchat_set_context(ph, xchat_find_context(ph, NULL, NULL));	// set context to current window

	xchat_print(ph, "\002FiSH unloaded.\002\n");
	ph=0;

	if(mip) mirexit();

#ifdef WIN32
	PRNGSave(randomPath);
	PRNGCleanup();
#endif

	return 1;
}



// replace '[' and ']' from nick/channel with '~' (else problems with .ini files)
void FixContactName(char *contactName)
{
	while(*contactName != 0)
	{
		if((*contactName == '[') || (*contactName == ']')) *contactName='~';
		contactName++;
	}
}



// :somenick!ident@host.net PRIVMSG leetguy :Some Text -> Result: Rnick="somenick"
int ExtractRnick(char *Rnick, char *incoming_msg)		// needs pointer to "nick@host" or ":nick@host"
{
	int k=0;

	if(*incoming_msg == ':') incoming_msg++;

	while(*incoming_msg!='!' && *incoming_msg!=0) {
		Rnick[k]=*incoming_msg;
		incoming_msg++;
		k++;
	}
	Rnick[k]=0;

	if (*Rnick < '0') return FALSE;
	else return TRUE;
}



void memXOR(unsigned char *s1, const unsigned char *s2, int n)
{
	while(n--) *s1++ ^= *s2++;
}



#ifdef WIN32
BOOL CALLBACK PassFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	HICON hicon;

	switch (msg)
	{
		case WM_INITDIALOG:
			SetFocus(GetDlgItem(hwndDlg, IDC_PASS));
			hicon=LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_KEY));
			SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (long)hicon);
			SendDlgItemMessage(hwndDlg, IDC_PASS, EM_LIMITTEXT, 99, 0);		// Allow only 99 chars for IDC_PASS editbox
			break;
		case WM_COMMAND:
			if(LOWORD(wParam)==IDOK)
			{
				PRNGAddEvent();
				iniKey[0]=0;
				if(GetDlgItemText(hwndDlg, IDC_PASS, iniKey, 99)==0)
				{
					EndDialog(hwndDlg, 0);	// quit password dialog and return FALSE (error)
					return 0;
				}
				EndDialog(hwndDlg, 1);	// quit password dialog and return TRUE (success)
				break;
			}
			return TRUE;
		case WM_DESTROY:
		case WM_CLOSE:
			EndDialog(hwndDlg, 0);
			return TRUE;
	}
	return FALSE;
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1