// FiSH encryption module for irssi, v0.99

#include "FiSH.h"



// encrypt a message and store in bf_dest (using key for target)
static int FiSH_encrypt(char *msg_ptr, const char *target, char *bf_dest)
{
	unsigned char contactName[100]="", theKey[150]="", ini_outgoing[10]="";


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

	GetPrivateProfileString("FiSH", "process_outgoing", "1", ini_outgoing, sizeof(ini_outgoing), iniPath);
	if(*ini_outgoing=='0' || *ini_outgoing=='n' || *ini_outgoing=='N') return 0;

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

	GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath);
	if(strlen(theKey) < 4) return 0;		// 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 0;
		}
	}

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

	return 1;
}



// decrypt a base64 cipher text (using key for target)
static int FiSH_decrypt(char *msg_ptr, char *msg_bak, const char *target)
{
	unsigned char contactName[100], theKey[150]="", bf_dest[1000]="";
	unsigned char ini_incoming[10]="", myMark[20]="", mark_pos[20]="";
	unsigned int msg_len, i, mark_broken_block=0;


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

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

	if(strncmp(msg_ptr, "+OK ", 4)==0) msg_ptr += 4;
	else if(strncmp(msg_ptr, "mcps ", 5)==0) msg_ptr += 5;
	else return 0;		// don't process, blowcrypt-prefix not found

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

	GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath);
	if(*theKey=='\0' || strlen(theKey)<4) return 0;	// 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 0;
		}
	}

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

	// usually a received message does not exceed 512 chars, but we want to prevent evil buffer overflow
	if(msg_len >= (int)(sizeof(bf_dest)*1.5)) msg_ptr[(int)(sizeof(bf_dest)*1.5)-20]='\0';

	// 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));

	i=0;
	while(bf_dest[i] != 0x0A && bf_dest[i] != 0x0D && bf_dest[i] != '\0') i++;
	bf_dest[i]='\0';	// in case of wrong key, decrypted message might have control characters -> cut message
	if(*bf_dest=='\0') return 0;	// don't process, decrypted msg is bad

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

	// append crypt-mark?
	GetPrivateProfileString(contactName, "mark_encrypted", "1", myMark, sizeof(myMark), iniPath);
	if(*myMark!='0' && *myMark!='n' && *myMark!='N')
	{
		GetPrivateProfileString("FiSH", "mark_encrypted", "", myMark, sizeof(myMark), iniPath);	// global setting
		if(*myMark != '\0')
		{
			GetPrivateProfileString("FiSH", "mark_position", "0", mark_pos, sizeof(mark_pos), iniPath);
			if(*mark_pos=='0') strcat(bf_dest, myMark);		//append mark at the end
			else
			{	// prefix mark
				i=strlen(myMark);
				memmove(bf_dest+i, bf_dest, strlen(bf_dest)+1);
				strncpy(bf_dest, myMark, i);
			}
		}
	}

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

	return 1;
}



static void decrypt_incoming(SERVER_REC *server, char *msg, const char *nick, const char *addr, const char *target)
{
	unsigned char contactName[100]="", *msg_bak;


	if(msg==NULL || target==NULL || nick==NULL) return;
	else if(strncmp(msg, "+OK ", 4)!=0 && strncmp(msg, "mcps ", 5)!=0 && strcmp(nick, "-psyBNC")!=0) return;	// don't process, blowcrypt-prefix not found

	msg_bak=(unsigned char *)msg;	// this is needed, because of psyBNC

	//channel?
	if(*target=='#' || *target=='&') strcpy(contactName, target);
	else if(strcmp(nick, "-psyBNC")==0)
	{
		msg=strstr(msg, " :(")+3;	// points to nick!ident@host in psybnc log
		if(msg==(char *)3) return;
		ExtractRnick(contactName, msg);
		msg=strchr(msg, ' ')+1;
		if(msg==(char *)1) return;
		if((strncmp(msg, "+OK ", 4)!=0) && (strncmp(msg, "mcps ", 5)!=0)) return;
	}
	else strcpy(contactName, nick);

	if(FiSH_decrypt(msg, msg, contactName))
	{
		signal_stop();
		signal_emit(signal_get_emitted(), 5, server, msg_bak, nick, addr, target);
	}
}



static void encrypt_outgoing(SERVER_REC *server, char *msg, char *target, char *orig_target)
{
	unsigned char bf_dest[800]="";
	unsigned char contactName[100]="", plain_prefix[20]="";
	unsigned char myMark[20]="", mark_pos[20]="";
	unsigned int i;


	// generally cut a message to a size of 512 byte, as everything above will never arrive complete anyway
	if(strlen(msg) > 512) msg[512]='\0';

	// plain-prefix in msg found?
	GetPrivateProfileString("FiSH", "plain_prefix", "+p ", plain_prefix, sizeof(plain_prefix), iniPath);
	if(*plain_prefix != '\0')
	{
		i=strlen(plain_prefix);
		if(strncasecmp(msg, plain_prefix, i)==0)
		{
			irc_send_cmdv(server, "PRIVMSG %s :%s", target, msg+i);
			return;
		}
	}

	if(FiSH_encrypt(msg, target, bf_dest)==0)
	{	// send plain-text (no key found)
		irc_send_cmdv(server, "PRIVMSG %s :%s", target, msg);
		return;
	}

	bf_dest[512]='\0';
	irc_send_cmdv(server, "PRIVMSG %s :%s %s\n", target, "+OK", bf_dest);

	strcpy(bf_dest, msg);

	// append crypt-mark?
	GetPrivateProfileString(contactName, "mark_encrypted", "1", myMark, sizeof(myMark), iniPath);
	if(*myMark!='0' && *myMark!='n' && *myMark!='N')
	{
		GetPrivateProfileString("FiSH", "mark_encrypted", "", myMark, sizeof(myMark), iniPath);	// global setting
		if(*myMark != '\0')
		{
			GetPrivateProfileString("FiSH", "mark_position", "0", mark_pos, sizeof(mark_pos), iniPath);
			if(*mark_pos=='0') strcat(bf_dest, myMark);		//append mark at the end
			else
			{	// prefix mark
				i=strlen(myMark);
				memmove(bf_dest+i, bf_dest, strlen(bf_dest)+1);
				strncpy(bf_dest, myMark, i);
			}
		}
	}

	signal_continue(4, server, bf_dest, target, orig_target);
	ZeroMemory(bf_dest, sizeof(bf_dest));

	return;
}



static void notice_incoming(SERVER_REC *server, char *msg, char *nick, char *address, char *target)
{	// decrypt NOTICE messages (and forward DH1080 key-exchange)
	unsigned char *contactPtr;

	if(strncmp(msg, "DH1024_", 7)==0)
	{
		irc_send_cmdv(server, "NOTICE %s :Received \002old DH1024\002 public key from you! Please update to latest version: http://fish.sekure.us\n", nick);
		printtext(server, NULL, MSGLEVEL_CRAP, "\002FiSH:\002 Received \002old DH1024\002 public key from %s! Telling him to update...", nick);
		return;
	}

	if(strncmp(msg, "DH1080_", 7)==0)
	{
		DH1080_received(server, msg, nick, address, target);
		return;
	}

	if(*target=='#' || *target=='&') contactPtr=target;
	else contactPtr=nick;

	if(FiSH_decrypt(msg, msg, contactPtr))
	{
		signal_stop();
		signal_emit(signal_get_emitted(), 5, server, msg, nick, address, target);
	}
}



static void decrypt_action(SERVER_REC *server, char *msg, char *nick, char *address, char *target)
{
	if(FiSH_decrypt(msg, msg, target))
	{
		signal_stop();
		signal_emit(signal_get_emitted(), 5, server, msg, nick, address, target);
	}
}



static void decrypt_topic(SERVER_REC *server, char *channel, char *topic, char *nick, char *address)
{
	if(FiSH_decrypt(topic, topic, channel))
	{
		signal_stop();
		signal_emit(signal_get_emitted(), 5, server, channel, topic, nick, address);
	}
}



static void topic_changed(CHANNEL_REC *chan_rec)
{
	FiSH_decrypt(chan_rec->topic, chan_rec->topic, chan_rec->name);
}



static void raw_handler(SERVER_REC *server, char *data)
{
	char channel[100], *ptr, *ptr_bak;
	int len;

	if(data==NULL || *data=='\0') return;

	ptr=strchr(data, ' ');	// point to command
	if(ptr==NULL) return;
	ptr++;

	if(strncmp(ptr, "332 ", 4)!=0) return;	// 332 = TOPIC

	ptr_bak=ptr;
	ptr=strchr(ptr, '#');	// point to #channel
	if(ptr==NULL)
	{
		ptr=strchr(ptr_bak, '&');	// point to &channel
		if(ptr_bak==NULL) return;
	}

	len=strchr(ptr, ' ')-ptr;
	if(len >= sizeof(channel)-2) return;	// channel string too long, something went wrong
	strncpy(channel, ptr, len);
	channel[len]='\0';

	ptr=strchr(ptr, ':');	// point to topic msg start
	if(ptr==NULL) return;
	ptr++;

	FiSH_decrypt(ptr, ptr, channel);
}



static void encrypt_notice(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{	// New command: /notice+ <nick/#channel> <notice message>
	unsigned char bf_dest[1000]="";
	const char *target;
	unsigned char *msg;
	void *free_arg=NULL;


	if(data==NULL || (strlen(data) < 3)) goto notice_error;
	if(!cmd_get_params(data, &free_arg, 2, &target, &msg)) goto notice_error;

	if (target==NULL || *target=='\0' || msg==NULL || *msg=='\0') goto notice_error;

	// generally refuse a notice size of more than 512 byte, as everything above will never arrive complete anyway
	if(strlen(msg) >= 512)
	{
		printtext(server, target, MSGLEVEL_CRAP, "\002FiSH:\002 /notice+ error: message argument exceeds buffer size!");
		goto notice_error;
	}

	if(FiSH_encrypt(msg, target, bf_dest)==0)
	{
		printtext(server, target, MSGLEVEL_CRAP, "\002FiSH:\002 /notice+ error: Encryption disabled or no key found for %s.", target);
		goto notice_error;
	}

	bf_dest[512]='\0';
	irc_send_cmdv(server, "NOTICE %s :%s %s\n", target, "+OK", bf_dest);

	signal_emit("message irc own_notice", 3, server, msg, target);
	cmd_params_free(free_arg);
	return;

notice_error:
	if(free_arg) cmd_params_free(free_arg);
	printtext(server, item!=NULL ? window_item_get_target(item) : NULL, MSGLEVEL_CRAP,
			"\002FiSH:\002 Usage: /notice+ <nick/#channel> <notice message>");
}



// set encrypted topic for current channel, irssi syntax: /topic+ <your topic>
static void command_crypt_TOPIC(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
	unsigned char bf_dest[1000]="";
	const char *target;


	if(data==0 || *data=='\0') goto topic_error;
	if(item!=NULL) target=window_item_get_target(item);
	else goto topic_error;

	if(*target!='#' && *target!='&')
	{
		printtext(server, target, MSGLEVEL_CRAP, "\002FiSH:\002 Please change to the channel window where you want to set the topic!");
		goto topic_error;
	}

	// generally refuse a topic size of more than 512 byte, as everything above will never arrive complete anyway
	if(strlen(data) >= 512)
	{
		printtext(server, target, MSGLEVEL_CRAP, "\002FiSH:\002 /topic+ error: topic length exceeds buffer size!");
		goto topic_error;
	}

	// encrypt a message (using key for target)
	if(FiSH_encrypt((char *)data, target, bf_dest)==0)
	{
		printtext(server, target, MSGLEVEL_CRAP, "\002FiSH:\002 /topic+ error: Encryption disabled or no key found for %s.", target);
		goto topic_error;
	}

	bf_dest[512]='\0';
	irc_send_cmdv(server, "TOPIC %s :%s %s\n", target, "+OK", bf_dest);
	return;

topic_error:
	printtext(server, item!=NULL ? window_item_get_target(item) : NULL, MSGLEVEL_CRAP,
			"\002FiSH:\002 Usage: /topic+ <your new topic>");
}



static void command_helpfish(const char *arg, SERVER_REC *server, WI_ITEM_REC *item)
{
	printtext(NULL, NULL, MSGLEVEL_CRAP,
		"\n\002FiSH HELP:\002 For more information read FiSH-irssi.txt :)\n\n"
		" /topic+ <your new topic>\n"
		" /notice+ <nick/#channel> <notice message>\n"
		" /key [<nick/#channel>]\n"
		" /setkey [<nick/#channel>] <sekure_key>\n"
		" /delkey <nick/#channel>\n"
		" /keyx [<nick>] (DH1080 KeyXchange)\n"
		" /setinipw <sekure_blow.ini_password>\n"
		" /unsetinipw\n");
}



static void command_setinipw(const char *iniPW, SERVER_REC *server, WI_ITEM_REC *item)
{
	unsigned int i=0, pw_len, re_enc=0;
	unsigned char B64digest[50], SHA256digest[35];
	unsigned char bfKey[512], new_iniKey[100], old_iniKey[100], *fptr, *ok_ptr, line_buf[1000], iniPath_new[300];
	FILE *h_ini, *h_ini_new;

	pw_len=strlen(iniPW);
	if(pw_len < 1 || pw_len > sizeof(new_iniKey))
	{
		printtext(server, item!=NULL ? window_item_get_target(item) : NULL, MSGLEVEL_CRAP,
				"\002FiSH:\002 No parameters. Usage: /setinipw <sekure_blow.ini_password>");
		return;
	}

	strfcpy(new_iniKey, (char *)iniPW);
	ZeroMemory(iniPW, pw_len);
	pw_len=strlen(new_iniKey);

	if(pw_len < 8)
	{
		printtext(server, item!=NULL ? window_item_get_target(item) : NULL, MSGLEVEL_CRAP,
				"\002FiSH:\002 Password too short, at least 8 characters needed! Usage: /setinipw <sekure_blow.ini_password>");
		return;
	}

	SHA256_memory(new_iniKey, pw_len, SHA256digest);
	ZeroMemory(new_iniKey, sizeof(new_iniKey));
	for(i=0;i<40872;i++) SHA256_memory(SHA256digest, 32, SHA256digest);
	htob64(SHA256digest, B64digest, 32);
	strcpy(old_iniKey, iniKey);
	if(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)
	{
		while (!feof(h_ini))
		{
			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));
					ZeroMemory(ok_ptr+4, strlen(ok_ptr+4)+1);
					encrypt_string(iniKey, bfKey, ok_ptr+4, strlen(bfKey));
					strcat(line_buf, "\n");
				}
				if(fprintf(h_ini_new, "%s", line_buf) < 0)
				{
					ZeroMemory(B64digest, sizeof(B64digest));
					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_new);

					printtext(server, item!=NULL ? window_item_get_target(item) : NULL,	MSGLEVEL_CRAP,
							"\002FiSH ERROR:\002 Unable to write new blow.ini, probably out of disc space.");

					return;
				}
			}
		}

		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);
	}
	else return;

	if(WritePrivateProfileString("FiSH", "ini_password_Hash", B64digest, iniPath) == -1)
	{
		ZeroMemory(B64digest, sizeof(B64digest));
		printtext(server, item!=NULL ? window_item_get_target(item) : NULL,	MSGLEVEL_CRAP,
				"\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
		return;
	}

	ZeroMemory(B64digest, sizeof(B64digest));

	if(re_enc) printtext(server, item!=NULL ? window_item_get_target(item) : NULL,
					MSGLEVEL_CRAP, "\002FiSH: Re-encrypted blow.ini\002 with new password.");

	if(!unsetiniFlag) printtext(server, item!=NULL ? window_item_get_target(item) : NULL,
						MSGLEVEL_CRAP, "\002FiSH:\002 blow.ini password hash saved.");
}



// Change back to default blow.ini password, irssi syntax: /unsetinipw
static void command_unsetinipw(const char *arg, SERVER_REC *server, WI_ITEM_REC *item)
{
	unsetiniFlag=1;
	command_setinipw("Some_boogie_dummy_key", server, item);
	unsetiniFlag=0;
	if(WritePrivateProfileString("FiSH", "ini_password_Hash", "\0", iniPath) == -1)
	{
		printtext(server, item!=NULL ? window_item_get_target(item) : NULL,	MSGLEVEL_CRAP,
			"\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
		return;
	}
	printtext(server, item!=NULL ? window_item_get_target(item) : NULL, MSGLEVEL_CRAP,
		"\002FiSH:\002 Changed back to default blow.ini password, you won't have to enter it on start-up anymore!");
}



static void command_setkey(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
	unsigned char contactName[100]="", encryptedKey[150]="";
	const char *target, *key;
	void *free_arg;

	if (data==NULL || *data=='\0')
	{
		printtext(server, item!=NULL ? window_item_get_target(item) : NULL, MSGLEVEL_CRAP,
			"\002FiSH:\002 No parameters. Usage: /setkey [<nick/#channel>] <sekure_key>");
		return;
	}

	if (!cmd_get_params(data, &free_arg, 2, &target, &key)) return;

	if (*target=='\0')
	{
		printtext(server, item!=NULL ? window_item_get_target(item) : NULL, MSGLEVEL_CRAP,
				"\002FiSH:\002 No parameters. Usage: /setkey [<nick/#channel>] <sekure_key>");
		cmd_params_free(free_arg);
		return;
	}

	if (*key=='\0') {
		// one paramter given - it's the key
		key = target;
		if (item != NULL) target = window_item_get_target(item);
		else
		{
			printtext(NULL, NULL, MSGLEVEL_CRAP,
					"\002FiSH:\002 Please define nick/#channel. Usage: /setkey [<nick/#channel>] <sekure_key>");
			cmd_params_free(free_arg);
			return;
		}
	}

	strcpy(contactName, target);
	FixContactName(contactName);

	encrypt_key((char *)key, encryptedKey);

	if(WritePrivateProfileString(contactName, "key", encryptedKey, iniPath) == -1)
	{
		ZeroMemory(encryptedKey, sizeof(encryptedKey));
		printtext(server, item!=NULL ? window_item_get_target(item) : NULL,	MSGLEVEL_CRAP,
				"\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
		cmd_params_free(free_arg);
		return;
	}

	ZeroMemory(encryptedKey, sizeof(encryptedKey));

	printtext(server, item!=NULL ? window_item_get_target(item) : NULL, MSGLEVEL_CRAP,
			"\002FiSH:\002 Key for %s successfully set!", target);

	cmd_params_free(free_arg);
}



static void command_delkey(const char *target, SERVER_REC *server, WI_ITEM_REC *item)
{
	unsigned char contactName[100]="";

	if (target==NULL || *target=='\0')
	{
		printtext(server, item!=NULL ? window_item_get_target(item) : NULL, MSGLEVEL_CRAP,
				"\002FiSH:\002 No parameters. Usage: /delkey <nick/#channel>");
		return;
	}

	strfcpy(contactName, (char *)target);
	FixContactName(contactName);

	if(WritePrivateProfileString(contactName, "key", "\0", iniPath) == -1)
	{
		printtext(server, item!=NULL ? window_item_get_target(item) : NULL,	MSGLEVEL_CRAP,
			"\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
		return;
	}

	printtext(server, item!=NULL ? window_item_get_target(item) : NULL,	MSGLEVEL_CRAP,
		"\002FiSH:\002 Key for %s successfully removed!", target);
}



static void command_key(const char *target, SERVER_REC *server, WI_ITEM_REC *item)
{
	unsigned char contactName[100]="", theKey[150]="";

	if(target==NULL || *target=='\0')
	{
		if (item!=NULL) target=window_item_get_target(item);
		else
		{
			printtext(NULL, NULL, MSGLEVEL_CRAP, "\002FiSH:\002 Please define nick/#channel. Usage: /key <nick/#channel>");
			return;
		}
	}

	strfcpy(contactName, (char *)target);
	FixContactName(contactName);

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

	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));
			printtext(server, target, MSGLEVEL_CRAP, "\002FiSH:\002 Key for %s invalid!", target);
			return;
		}
	}

	printtext(server, target, MSGLEVEL_CRAP, "\002FiSH:\002 Key for %s: %s", target, theKey);

	ZeroMemory(theKey, sizeof(theKey));
}



static void command_keyx(const char *target, SERVER_REC *server, WI_ITEM_REC *item)
{
	if(target==NULL || *target=='\0')
	{
		if(item!=NULL) target = window_item_get_target(item);
		else
		{
			printtext(NULL, NULL, MSGLEVEL_CRAP, "\002FiSH:\002 Please define nick/#channel. Usage: /keyx <nick/#channel>");
			return;
		}
	}

	if(*target=='#' || *target=='&')
	{
		printtext(server, target, MSGLEVEL_CRAP, "\002FiSH:\002 KeyXchange does not work for channels!");
		return;
	}

	DH1080_gen(g_myPrivKey, g_myPubKey);

	irc_send_cmdv(server, "NOTICE %s :%s %s", target, "DH1080_INIT", g_myPubKey);

	printtext(server, item!=NULL ? window_item_get_target(item) : NULL, MSGLEVEL_CRAP,
			"\002FiSH:\002 Sent my DH1080 public key to %s, waiting for reply ...", target);
}



static void DH1080_received(SERVER_REC *server, char *msg, char *nick, char *address, char *target)
{
	unsigned int i;
	char hisPubKey[300], contactName[100]="", encryptedKey[150]="";

	if(*target=='&' || *target=='#' || *nick=='#' || *nick=='&') return;	// no KeyXchange for channels...
	i=strlen(msg);
	if(i<191 || i>195) return;

	if(strncmp(msg, "DH1080_INIT ", 12)==0)
	{
		strcpy(hisPubKey, msg+12);
		if(strspn(hisPubKey, B64ABC) != strlen(hisPubKey)) return;

		if(query_find(server, nick)==NULL)
		{	// query window not found, lets create one
			keyx_query_created=1;
			irc_query_create(server->tag, nick, TRUE);
			keyx_query_created=0;
		}

		printtext(server, nick, MSGLEVEL_CRAP, "\002FiSH:\002 Received DH1080 public key from %s, sending mine...", nick);

		DH1080_gen(g_myPrivKey, g_myPubKey);
		irc_send_cmdv(server, "NOTICE %s :%s %s", nick, "DH1080_FINISH", g_myPubKey);
	}
	else if(strncmp(msg, "DH1080_FINISH ", 14)==0) strcpy(hisPubKey, msg+14);
	else return;

	if(DH1080_comp(g_myPrivKey, hisPubKey)==0) return;
	signal_stop();

	strcpy(contactName, nick);
	FixContactName(contactName);

	encrypt_key(hisPubKey, encryptedKey);
	ZeroMemory(hisPubKey, sizeof(hisPubKey));

	if(WritePrivateProfileString(contactName, "key", encryptedKey, iniPath) == -1)
	{
		ZeroMemory(encryptedKey, sizeof(encryptedKey));
		printtext(server, nick,	MSGLEVEL_CRAP, "\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
		return;
	}

	ZeroMemory(encryptedKey, sizeof(encryptedKey));

	printtext(server, nick, MSGLEVEL_CRAP, "\002FiSH:\002 Key for %s successfully set!", nick);
}



static void do_auto_keyx(QUERY_REC *query, int automatic)
{	// perform auto-keyXchange only for known people
	unsigned char theKey[150]="", ini_auto_keyx[10], contactName[100]="";

	if(keyx_query_created) return;	// query was created by FiSH

	GetPrivateProfileString("FiSH", "auto_keyxchange", "1", ini_auto_keyx, sizeof(ini_auto_keyx), iniPath);
	if(*ini_auto_keyx=='0' || *ini_auto_keyx=='N' || *ini_auto_keyx=='n') return;

	strcpy(contactName, query->name);
	FixContactName(contactName);

	GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath);
	if(strlen(theKey) > 4)
	{
		ZeroMemory(theKey, sizeof(theKey));
		command_keyx(query->name, query->server, NULL);
	}
}



static void query_nick_changed(QUERY_REC *query, char *orignick)
{	// copy key for old nick to use with the new one
	unsigned char theKey[150]="", ini_nicktracker[10], contactName[100]="";

	GetPrivateProfileString("FiSH", "nicktracker", "1", ini_nicktracker, sizeof(ini_nicktracker), iniPath);
	if(*ini_nicktracker=='0' || *ini_nicktracker=='N' || *ini_nicktracker=='n') return;

	if(orignick==NULL || strcasecmp(orignick, query->name)==0) return;	// same nick, different case?
	strcpy(contactName, orignick);
	FixContactName(contactName);

	GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath);
	if(strlen(theKey) < 4) return;	// see if there is a key for the old nick

	strcpy(contactName, query->name);
	FixContactName(contactName);

	if(WritePrivateProfileString(contactName, "key", theKey, iniPath) == -1)
	{
		ZeroMemory(theKey, sizeof(theKey));
		printtext(NULL, NULL, MSGLEVEL_CRAP, "\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
		return;
	}

	ZeroMemory(theKey, sizeof(theKey));
}



static void void_send(SERVER_REC *server, const char *target, const char *msg, int target_type)
{
}

static void server_register_fish(SERVER_REC *server)
{
	MODULE_SERVER_REC *rec;

	if(server==NULL) return;

	rec = g_new0(MODULE_SERVER_REC, 1);
	MODULE_DATA_SET(server, rec);
	rec->orig_send_message = server->send_message;
	server->send_message = void_send;
}

static void server_unregister_fish(SERVER_REC *server)
{
	MODULE_SERVER_REC *rec;

	if(server==NULL) return;

	rec = MODULE_DATA(server);
	if(rec==NULL) return;
	server->send_message = rec->orig_send_message;
	MODULE_DATA_UNSET(server);
	g_free(rec);
}



void fish_init(void)
{
	unsigned char iniPasswordHash[50], SHA256digest[35], B64digest[50], *iniPass_ptr;
	unsigned int i;

	g_slist_foreach(servers, (GFunc) server_register_fish, NULL);

	strcpy(iniPath, get_irssi_config());	// path to irssi config file
	strcpy(tempPath, iniPath);
	strcpy(strrchr(iniPath, '/'), blow_ini);
	strcpy(strrchr(tempPath, '/'), "/temp_FiSH.$$$");

	GetPrivateProfileString("FiSH", "ini_Password_hash", "0", iniPasswordHash, sizeof(iniPasswordHash), iniPath);
	if(strlen(iniPasswordHash) == 43)
	{
		iniPass_ptr = getpass(" --> Please enter your blow.ini password: ");
		strcpy(iniKey, iniPass_ptr);
		ZeroMemory(iniPass_ptr, strlen(iniPass_ptr));
		irssi_redraw();		// getpass() screws irssi GUI, lets redraw!

		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)
		{
			printtext(NULL, NULL, MSGLEVEL_CRAP, "\002FiSH:\002 Wrong blow.ini password entered, try again...");
			printtext(NULL, NULL, MSGLEVEL_CRAP, "\002FiSH module NOT loaded.\002");
			return;
		}
		printtext(NULL, NULL, MSGLEVEL_CRAP, "\002FiSH:\002 Correct blow.ini password entered, lets go!");
	}
	else
	{
		strcpy(iniKey, default_iniKey);
		printtext(NULL, NULL, MSGLEVEL_CRAP, "\002FiSH:\002 Using default password to decrypt blow.ini... Try /setinipw to set a custom password.");
	}


	signal_add_first("message private", (SIGNAL_FUNC) decrypt_incoming);
	signal_add_first("message public", (SIGNAL_FUNC) decrypt_incoming);
	signal_add_first("message irc notice", (SIGNAL_FUNC) notice_incoming);
	signal_add_first("message irc action", (SIGNAL_FUNC) decrypt_action);

	signal_add_first("message own_private", (SIGNAL_FUNC) encrypt_outgoing);
	signal_add_first("message own_public", (SIGNAL_FUNC) encrypt_outgoing);

	signal_add_first("channel topic changed", (SIGNAL_FUNC) topic_changed);
	signal_add_first("message topic", (SIGNAL_FUNC) decrypt_topic);
	signal_add_first("server incoming", (SIGNAL_FUNC) raw_handler);

	signal_add("server connected", (SIGNAL_FUNC) server_register_fish);
	signal_add("server disconnected", (SIGNAL_FUNC) server_unregister_fish);

	signal_add("query created", (SIGNAL_FUNC) do_auto_keyx);
	signal_add("query nick changed", (SIGNAL_FUNC) query_nick_changed);

	command_bind("topic+", NULL, (SIGNAL_FUNC) command_crypt_TOPIC);
	command_bind("notice+", NULL, (SIGNAL_FUNC) encrypt_notice);
	command_bind("notfish", NULL, (SIGNAL_FUNC) encrypt_notice);
	command_bind("setkey", NULL, (SIGNAL_FUNC) command_setkey);
	command_bind("delkey", NULL, (SIGNAL_FUNC) command_delkey);
	command_bind("key", NULL, (SIGNAL_FUNC) command_key);
	command_bind("keyx", NULL, (SIGNAL_FUNC) command_keyx);
	command_bind("setinipw", NULL, (SIGNAL_FUNC) command_setinipw);
	command_bind("unsetinipw", NULL, (SIGNAL_FUNC) command_unsetinipw);

	command_bind("fishhelp", NULL, (SIGNAL_FUNC) command_helpfish);
	command_bind("helpfish", NULL, (SIGNAL_FUNC) command_helpfish);

	printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
		"FiSH v0.99 - encryption module for irssi loaded! URL: http://fish.sekure.us\n"
		"Try /helpfish or /fishhelp for a short command overview");

	module_register("fish", "core");
}



void fish_deinit(void)
{
	g_slist_foreach(servers, (GFunc) server_unregister_fish, NULL);

	signal_remove("message private", (SIGNAL_FUNC) decrypt_incoming);
	signal_remove("message public", (SIGNAL_FUNC) decrypt_incoming);
	signal_remove("message irc notice", (SIGNAL_FUNC) notice_incoming);
	signal_remove("message irc action", (SIGNAL_FUNC) decrypt_action);

	signal_remove("message own_private", (SIGNAL_FUNC) encrypt_outgoing);
	signal_remove("message own_public", (SIGNAL_FUNC) encrypt_outgoing);

	signal_remove("channel topic changed", (SIGNAL_FUNC) topic_changed);
	signal_remove("message topic", (SIGNAL_FUNC) decrypt_topic);
	signal_remove("server incoming", (SIGNAL_FUNC) raw_handler);

	signal_remove("server connected", (SIGNAL_FUNC) server_register_fish);
	signal_remove("server disconnected", (SIGNAL_FUNC) server_unregister_fish);

	signal_remove("query created", (SIGNAL_FUNC) do_auto_keyx);
	signal_remove("query nick changed", (SIGNAL_FUNC) query_nick_changed);

	command_unbind("topic+", (SIGNAL_FUNC) command_crypt_TOPIC);
	command_unbind("notice+", (SIGNAL_FUNC) encrypt_notice);
	command_unbind("notfish", (SIGNAL_FUNC) encrypt_notice);
	command_unbind("setkey", (SIGNAL_FUNC) command_setkey);
	command_unbind("delkey", (SIGNAL_FUNC) command_delkey);
	command_unbind("key", (SIGNAL_FUNC) command_key);
	command_unbind("keyx", (SIGNAL_FUNC) command_keyx);
	command_unbind("setinipw", (SIGNAL_FUNC) command_setinipw);
	command_unbind("unsetinipw", (SIGNAL_FUNC) command_unsetinipw);

	command_unbind("fishhelp", (SIGNAL_FUNC) command_helpfish);
	command_unbind("helpfish", (SIGNAL_FUNC) command_helpfish);
}



// :someone!ident@host.net PRIVMSG leetguy :Some Text -> Result: Rnick="someone"
int ExtractRnick(char *Rnick, char *incoming_msg)		// needs direct 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 TRUE;
	else return FALSE;
}


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


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


char *strfcpy(unsigned char *dest, char *buffer)		// removes leading and trailing blanks from string
{
	int i=0, k=strlen(buffer);

	while(buffer[i]==' ') i++;
	while(buffer[k-1]==' ') k--;

	buffer[k]=0;

	strcpy(dest, buffer+i);
	return dest;
}


syntax highlighted by Code2HTML, v. 0.9.1