// An IRC analyzer contributed by Roland Gruber.

#include <iostream>
#include "IRC.h"
#include "analyzer/protocol/tcp/ContentLine.h"
#include "NetVar.h"
#include "Event.h"
#include "analyzer/protocol/zip/ZIP.h"
#include "analyzer/Manager.h"

#include "events.bif.h"

using namespace analyzer::irc;

IRC_Analyzer::IRC_Analyzer(Connection* conn)
: tcp::TCP_ApplicationAnalyzer("IRC", conn)
	{
	invalid_msg_count = 0;
	invalid_msg_max_count = 20;
	orig_status = WAIT_FOR_REGISTRATION;
	resp_status = WAIT_FOR_REGISTRATION;
	orig_zip_status = NO_ZIP;
	resp_zip_status = NO_ZIP;
	AddSupportAnalyzer(new tcp::ContentLine_Analyzer(conn, true));
	AddSupportAnalyzer(new tcp::ContentLine_Analyzer(conn, false));
	}

void IRC_Analyzer::Done()
	{
	tcp::TCP_ApplicationAnalyzer::Done();
	}

void IRC_Analyzer::DeliverStream(int length, const u_char* line, bool orig)
	{
	tcp::TCP_ApplicationAnalyzer::DeliverStream(length, line, orig);

	// check line size
	if ( length > 512 )
		{
		Weird("irc_line_size_exceeded");
		return;
		}

	if ( length < 2 )
		{
		Weird("irc_line_too_short");
		return;
		}

	string myline = string((const char*) line);

	// Check for prefix.
	string prefix = "";
	if ( line[0] == ':' )
		{ // find end of prefix and extract it
		unsigned int pos = myline.find(' ');
		if ( pos > (unsigned int) length )
			{
			Weird("irc_invalid_line");
			return;
			}

		prefix = myline.substr(1, pos - 1);
		myline = myline.substr(pos + 1);  // remove prefix from line
		}


	if ( orig )
		ProtocolConfirmation();

	int code = 0;
	string command = "";

	// Check if line is long enough to include status code or command.
	if ( myline.size() < 4 )
		{
		Weird("irc_invalid_line");
		ProtocolViolation("line too short");
		return;
		}

	// Check if this is a server reply.
	if ( isdigit(myline[0]) )
		{
		if ( isdigit(myline[1]) && isdigit(myline[2]) &&
		     myline[3] == ' ')
			{
			code = (myline[0] - '0') * 100 +
				(myline[1] - '0') * 10 + (myline[2] - '0');
			myline = myline.substr(4);
			}
		else
			{
			Weird("irc_invalid_reply_number");
			ProtocolViolation("invalid reply number");
			return;
			}
		}
	else
		{ // get command
		unsigned int pos = myline.find(' ');
		if ( pos > (unsigned int) length )
			{
			Weird("irc_invalid_line");
			return;
			}

		command = myline.substr(0, pos);
		for ( unsigned int i = 0; i < command.size(); ++i )
			command[i] = toupper(command[i]);

		myline = myline.substr(pos + 1);
		}

	// Extract parameters.
	string params = myline;

	// Check for Server2Server - connections with ZIP enabled.
	if ( orig && orig_status == WAIT_FOR_REGISTRATION )
		{
		if ( command == "PASS" )
			{
			vector<string> p = SplitWords(params,' ');
			if ( p.size() > 3 &&
			     (p[3].find('Z')<=p[3].size() ||
			      p[3].find('z')<=p[3].size()) )
				orig_zip_status = ACCEPT_ZIP;
			else
			        orig_zip_status = NO_ZIP;
			}

		// We do not check if SERVER command is successful, since
		// the connection will be terminated by the server if
		// authentication fails.
		//
		// (### This seems not quite prudent to me - VP)
		if ( command == "SERVER" && prefix == "")
		        {
			orig_status = REGISTERED;
			}
		}

	if ( ! orig && resp_status == WAIT_FOR_REGISTRATION )
		{
		if ( command == "PASS" )
		        {
			vector<string> p = SplitWords(params,' ');
			if ( p.size() > 3 &&
			     (p[3].find('Z')<=p[3].size() ||
			      p[3].find('z')<=p[3].size()) )
			        resp_zip_status = ACCEPT_ZIP;
			else
			        resp_zip_status = NO_ZIP;

			}

		// Again, don't bother checking whether SERVER command
		// is successful.
		if ( command == "SERVER" && prefix == "")
			resp_status = REGISTERED;
		}

	// Analyze server reply messages.
	if ( code > 0 )
		{
		switch ( code ) {
		/*
		case 1: // RPL_WELCOME
		case 2: // RPL_YOURHOST
		case 3: // RPL_CREATED
		case 4: // RPL_MYINFO
		case 5: // RPL_BOUNCE
		case 252: // number of ops online
		case 253: // number of unknown connections
		case 265: // RPL_LOCALUSERS
		case 312: // whois server reply
		case 315: // end of who list
		case 317: // whois idle reply
		case 318: // end of whois list
		case 366: // end of names list
		case 372: // RPL_MOTD
		case 375: // RPL_MOTDSTART
		case 376: // RPL_ENDOFMOTD
		case 331: // RPL_NOTOPIC
			break;
		*/

		// Count of users, services and servers in whole network.
		case 251:
			if ( ! irc_network_info )
				break;

			{
			vector<string> parts = SplitWords(params, ' ');
			int users = 0;
			int services = 0;
			int servers = 0;

			for ( unsigned int i = 1; i < parts.size(); ++i )
				{
				if ( parts[i] == "users" )
					users = atoi(parts[i-1].c_str());
				else if ( parts[i] == "services" )
					services = atoi(parts[i-1].c_str());
				else if ( parts[i] == "servers" )
					servers = atoi(parts[i-1].c_str());
				// else ###
				}

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new Val(users, TYPE_INT));
			vl->append(new Val(services, TYPE_INT));
			vl->append(new Val(servers, TYPE_INT));

			ConnectionEvent(irc_network_info, vl);
			}
			break;

		// List of users in a channel (names command).
		case 353:
			if ( ! irc_names_info )
				break;

			{
			vector<string> parts = SplitWords(params, ' ');

			// Remove nick name.
			parts.erase(parts.begin());
			if ( parts.size() < 2 )
				{
				Weird("irc_invalid_names_line");
				return;
				}

			string type = parts[0];
			string channel = parts[1];

			// Remove type and channel.
			parts.erase(parts.begin());
			parts.erase(parts.begin());

			if ( parts.size() > 0 && parts[0][0] == ':' )
				parts[0] = parts[0].substr(1);

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(type.c_str()));
			vl->append(new StringVal(channel.c_str()));

			TableVal* set = new TableVal(string_set);
			for ( unsigned int i = 0; i < parts.size(); ++i )
				{
				if ( parts[i][0] == '@' )
					parts[i] = parts[i].substr(1);
				set->Assign(new StringVal(parts[i].c_str()), 0);
				}
			vl->append(set);

			ConnectionEvent(irc_names_info, vl);
			}
			break;

		// Count of users and services on this server.
		case 255:
			if ( ! irc_server_info )
				break;

			{
			vector<string> parts = SplitWords(params, ' ');
			int users = 0;
			int services = 0;
			int servers = 0;

			for ( unsigned int i = 1; i < parts.size(); ++i )
				{
				if ( parts[i] == "users," )
					users = atoi(parts[i-1].c_str());
				else if ( parts[i] == "clients" )
					users = atoi(parts[i-1].c_str());
				else if ( parts[i] == "services" )
					services = atoi(parts[i-1].c_str());
				else if ( parts[i] == "servers" )
					servers = atoi(parts[i-1].c_str());
				// else ###
				}

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new Val(users, TYPE_INT));
			vl->append(new Val(services, TYPE_INT));
			vl->append(new Val(servers, TYPE_INT));

			ConnectionEvent(irc_server_info, vl);
			}
			break;

		// Count of channels.
		case 254:
			if ( ! irc_channel_info )
				break;

			{
			vector<string> parts = SplitWords(params, ' ');
			int channels = 0;
			for ( unsigned int i = 1; i < parts.size(); ++i )
				if ( parts[i] == ":channels" )
					channels = atoi(parts[i - 1].c_str());

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new Val(channels, TYPE_INT));

			ConnectionEvent(irc_channel_info, vl);
			}
			break;

		// RPL_GLOBALUSERS
		case 266:
			{
			// FIXME: We should really streamline all this
			// parsing code ...
			if ( ! irc_global_users )
				break;

			const char* prefix = params.c_str();

			const char* eop = strchr(prefix, ' ');
			if ( ! eop )
				{
				Weird("invalid_irc_global_users_reply");
				break;
				}

			const char *msg = strchr(++eop, ':');
			if ( ! msg )
				{
				Weird("invalid_irc_global_users_reply");
				break;
				}

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(eop - prefix, prefix));
			vl->append(new StringVal(++msg));
			ConnectionEvent(irc_global_users, vl);
			break;
			}

		// WHOIS user reply line.
		case 311:
			if ( ! irc_whois_user_line )
				break;

			{
			vector<string> parts = SplitWords(params, ' ');

			if ( parts.size() > 1 )
				parts.erase(parts.begin());
			if ( parts.size() < 5 )
				{
				Weird("irc_invalid_whois_user_line");
				return;
				}

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(parts[0].c_str()));
			vl->append(new StringVal(parts[1].c_str()));
			vl->append(new StringVal(parts[2].c_str()));

			parts.erase(parts.begin(), parts.begin() + 4);

			string real_name = parts[0];
			for ( unsigned int i = 1; i < parts.size(); ++i )
				real_name = real_name + " " + parts[i];

			if ( real_name[0] == ':' )
				real_name = real_name.substr(1);

			vl->append(new StringVal(real_name.c_str()));

			ConnectionEvent(irc_whois_user_line, vl);
			}
			break;

		// WHOIS operator reply line.
		case 313:
			if ( ! irc_whois_operator_line )
				break;

			{
			vector<string> parts = SplitWords(params, ' ');

			if ( parts.size() > 1 )
				parts.erase(parts.begin());

			if ( parts.size() < 2 )
				{
				Weird("irc_invalid_whois_operator_line");
				return;
				}

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(parts[0].c_str()));

			ConnectionEvent(irc_whois_operator_line, vl);
			}
			break;

		// WHOIS channel reply.
		case 319:
			if ( ! irc_whois_channel_line )
				break;

			{
			vector<string> parts = SplitWords(params, ' ');

			// Remove nick name.
			parts.erase(parts.begin());
			if ( parts.size() < 2 )
				{
				Weird("irc_invalid_whois_channel_line");
				return;
				}

			string nick = parts[0];
			parts.erase(parts.begin());

			if ( parts.size() > 0 && parts[0][0] == ':' )
				parts[0] = parts[0].substr(1);

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(nick.c_str()));
			TableVal* set = new TableVal(string_set);
			for ( unsigned int i = 0; i < parts.size(); ++i )
				{
				Val* idx = new StringVal(parts[i].c_str());
				set->Assign(idx, 0);
				Unref(idx);
				}

			vl->append(set);

			ConnectionEvent(irc_whois_channel_line, vl);
			}
			break;

		// RPL_TOPIC reply.
		case 332:
			{
			if ( ! irc_channel_topic )
				break;

			vector<string> parts = SplitWords(params, ' ');
			if ( parts.size() < 4 )
				{
				Weird("irc_invalid_topic_reply");
				return;
				}

			unsigned int pos = params.find(':');
			if ( pos < params.size() )
				{
				string topic = params.substr(pos + 1);
				val_list* vl = new val_list;

				vl->append(BuildConnVal());
				vl->append(new Val(orig, TYPE_BOOL));
				vl->append(new StringVal(parts[1].c_str()));

				const char* t = topic.c_str();
				if ( *t == ':' )
					++t;

				vl->append(new StringVal(t));

				ConnectionEvent(irc_channel_topic, vl);
				}
			else
				{
				Weird("irc_invalid_topic_reply");
				return;
				}
			break;
			}

		// WHO reply line.
		case 352:
			if ( ! irc_who_line )
				break;

			{
			vector<string> parts = SplitWords(params, ' ');
			if ( parts.size() < 9 )
				{
				Weird("irc_invalid_who_line");
				return;
				}

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(parts[0].c_str()));
			vl->append(new StringVal(parts[1].c_str()));
			if ( parts[2][0] == '~' )
				parts[2] = parts[2].substr(1);
			vl->append(new StringVal(parts[2].c_str()));
			vl->append(new StringVal(parts[3].c_str()));
			vl->append(new StringVal(parts[4].c_str()));
			vl->append(new StringVal(parts[5].c_str()));
			vl->append(new StringVal(parts[6].c_str()));
			if ( parts[7][0] == ':' )
				parts[7] = parts[7].substr(1);
			vl->append(new Val(atoi(parts[7].c_str()), TYPE_INT));
			vl->append(new StringVal(parts[8].c_str()));

			ConnectionEvent(irc_who_line, vl);
			}
			break;

		// Invalid nick name.
		case 431:
		case 432:
		case 433:
		case 436:
			if ( irc_invalid_nick )
				{
				val_list* vl = new val_list;
				vl->append(BuildConnVal());
				vl->append(new Val(orig, TYPE_BOOL));
				ConnectionEvent(irc_invalid_nick, vl);
				}
			break;

		// Operator responses.
		case 381:  // User is operator
		case 491:  // user is not operator
			if ( irc_oper_response )
				{
				val_list* vl = new val_list;
				vl->append(BuildConnVal());
				vl->append(new Val(orig, TYPE_BOOL));
				vl->append(new Val(code == 381, TYPE_BOOL));
				ConnectionEvent(irc_oper_response, vl);
				}
			break;

		// All other server replies.
		default:
			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(prefix.c_str()));
			vl->append(new Val(code, TYPE_COUNT));
			vl->append(new StringVal(params.c_str()));

			ConnectionEvent(irc_reply, vl);
			break;
		}
		return;
		}

	// Check if command is valid.
	if ( command.size() > 20 )
		{
		Weird("irc_invalid_command");
		if ( ++invalid_msg_count > invalid_msg_max_count )
			{
			Weird("irc_too_many_invalid");
			ProtocolViolation("too many long lines");
			return;
			}
		return;
		}

	else if ( irc_privmsg_message || (irc_dcc_message && command == "PRIVMSG") )
		{
		unsigned int pos = params.find(' ');
		if ( pos >= params.size() )
			{
			Weird("irc_invalid_privmsg_message_format");
			return;
			}

		string target = params.substr(0, pos);
		string message = params.substr(pos + 1);

		if ( message.size() > 0 && message[0] == ':' )
			message = message.substr(1);
		if ( message.size() > 0 && message[0] == 1 )
			message = message.substr(1); // DCC

		// Check for DCC messages.
		if ( message.size() > 3 && message.substr(0, 3) == "DCC" )
			{
			if ( message.size() > 0 &&
			     message[message.size() - 1] == 1 )
				message = message.substr(0, message.size() - 1);

			vector<string> parts = SplitWords(message, ' ');
			if ( parts.size() < 5 || parts.size() > 6 )
				{
				// Turbo DCC extension appends a "T" at the end of handshake.
				if ( ! (parts.size() == 7 && parts[6] == "T") )
					{
					Weird("irc_invalid_dcc_message_format");
					return;
					}
				}

			// Calculate IP address.
			uint32 raw_ip = 0;
			for ( unsigned int i = 0; i < parts[3].size(); ++i )
				{
				string s = parts[3].substr(i, 1);
				raw_ip = (10 * raw_ip) + atoi(s.c_str());
				}

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(prefix.c_str()));
			vl->append(new StringVal(target.c_str()));
			vl->append(new StringVal(parts[1].c_str()));
			vl->append(new StringVal(parts[2].c_str()));
			vl->append(new AddrVal(htonl(raw_ip)));
			vl->append(new Val(atoi(parts[4].c_str()), TYPE_COUNT));
			if ( parts.size() >= 6 )
				vl->append(new Val(atoi(parts[5].c_str()),
							TYPE_COUNT));
			else
				vl->append(new Val(0, TYPE_COUNT));

			ConnectionEvent(irc_dcc_message, vl);
			}

		else
			{
			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(prefix.c_str()));
			vl->append(new StringVal(target.c_str()));
			vl->append(new StringVal(message.c_str()));

			ConnectionEvent(irc_privmsg_message, vl);
			}
		}

	else if ( irc_notice_message && command == "NOTICE" )
		{
		unsigned int pos = params.find(' ');
		if ( pos >= params.size() )
			{
			Weird("irc_invalid_notice_message_format");
			return;
			}

		string target = params.substr(0, pos);
		string message = params.substr(pos + 1);
		if ( message[0] == ':' )
			message = message.substr(1);

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new StringVal(prefix.c_str()));
		vl->append(new StringVal(target.c_str()));
		vl->append(new StringVal(message.c_str()));

		ConnectionEvent(irc_notice_message, vl);
		}

	else if ( irc_squery_message && command == "SQUERY" )
		{
		unsigned int pos = params.find(' ');
		if ( pos >= params.size() )
			{
			Weird("irc_invalid_squery_message_format");
			return;
			}

		string target = params.substr(0, pos);
		string message = params.substr(pos + 1);
		if ( message[0] == ':' )
			message = message.substr(1);

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new StringVal(prefix.c_str()));
		vl->append(new StringVal(target.c_str()));
		vl->append(new StringVal(message.c_str()));

		ConnectionEvent(irc_squery_message, vl);
		}

	else if ( irc_user_message && command == "USER" )
		{
		// extract username and real name
		vector<string> parts = SplitWords(params, ' ');
		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));

		if ( parts.size() > 0 )
			vl->append(new StringVal(parts[0].c_str()));
		else vl->append(new StringVal(""));

		if ( parts.size() > 1 )
			vl->append(new StringVal(parts[1].c_str()));
		else vl->append(new StringVal(""));

		if ( parts.size() > 2 )
			vl->append(new StringVal(parts[2].c_str()));
		else vl->append(new StringVal(""));

		string realname;
		for ( unsigned int i = 3; i < parts.size(); i++ )
			{
			realname += parts[i];
			if ( i > 3 )
				realname += " ";
			}

		const char* name = realname.c_str();
		vl->append(new StringVal(*name == ':' ? name + 1 : name));

		ConnectionEvent(irc_user_message, vl);
		}

	else if ( irc_oper_message && command == "OPER" )
		{
		// extract username and password
		vector<string> parts = SplitWords(params, ' ');
		if ( parts.size() == 2 )
			{
			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(parts[0].c_str()));
			vl->append(new StringVal(parts[1].c_str()));

			ConnectionEvent(irc_oper_message, vl);
			}

		else
			Weird("irc_invalid_oper_message_format");
		}

	else if ( irc_kick_message && command == "KICK" )
		{
		// Extract channels, users and comment.
		vector<string> parts = SplitWords(params, ' ');
		if ( parts.size() <= 1 )
			{
			Weird("irc_invalid_kick_message_format");
			return;
			}

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new StringVal(prefix.c_str()));
		vl->append(new StringVal(parts[0].c_str()));
		vl->append(new StringVal(parts[1].c_str()));
		if ( parts.size() > 2 )
			{
			string comment = parts[2];
			for ( unsigned int i = 3; i < parts.size(); ++i )
				comment += " " + parts[i];

			if ( comment[0] == ':' )
				comment = comment.substr(1);

			vl->append(new StringVal(comment.c_str()));
			}
		else
			vl->append(new StringVal(""));

		ConnectionEvent(irc_kick_message, vl);
		}

	else if ( irc_join_message && command == "JOIN" )
		{
		if ( params[0] == ':' )
			params = params.substr(1);

		vector<string> parts = SplitWords(params, ' ');

		if ( parts.size() < 1 )
			{
			Weird("irc_invalid_join_line");
			return;
			}

		string nickname = "";
		if ( prefix.size() > 0 )
			{
			unsigned int pos = prefix.find('!');
			if ( pos < prefix.size() )
				nickname = prefix.substr(0, pos);
			}

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));

		TableVal* list = new TableVal(irc_join_list);
		vector<string> channels = SplitWords(parts[0], ',');
		vector<string> passwords;

		if ( parts.size() > 1 )
			passwords = SplitWords(parts[1], ',');

		string empty_string = "";
		for ( unsigned int i = 0; i < channels.size(); ++i )
			{
			RecordVal* info = new RecordVal(irc_join_info);
			info->Assign(0, new StringVal(nickname.c_str()));
			info->Assign(1, new StringVal(channels[i].c_str()));
			if ( i < passwords.size() )
				info->Assign(2, new StringVal(passwords[i].c_str()));
			else
				info->Assign(2, new StringVal(empty_string.c_str()));
			// User mode.
			info->Assign(3, new StringVal(empty_string.c_str()));
			list->Assign(info, 0);
			Unref(info);
			}

		vl->append(list);

		ConnectionEvent(irc_join_message, vl);
		}

	else if ( irc_join_message && command == "NJOIN" )
		{
		vector<string> parts = SplitWords(params, ' ');
		if ( parts.size() != 2 )
			{
			Weird("irc_invalid_njoin_line");
			return;
			}

		string channel = parts[0];
		if ( parts[1][0] == ':' )
			parts[1] = parts[1].substr(1);

		vector<string> users = SplitWords(parts[1], ',');

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));

		TableVal* list = new TableVal(irc_join_list);
		string empty_string = "";

		for ( unsigned int i = 0; i < users.size(); ++i )
			{
			RecordVal* info = new RecordVal(irc_join_info);
			string nick = users[i];
			string mode = "none";

			if ( nick[0] == '@' )
				{
				if ( nick[1] == '@' )
					{
					nick = nick.substr(2);
					mode = "creator";
					}
				else
					{
					nick = nick.substr(1);
					mode = "operator";
					}
				}

			else if ( nick[0] == '+' )
				{
				nick = nick.substr(1);
				mode = "voice";
				}

			info->Assign(0, new StringVal(nick.c_str()));
			info->Assign(1, new StringVal(channel.c_str()));
			// Password:
			info->Assign(2, new StringVal(empty_string.c_str()));
			// User mode:
			info->Assign(3, new StringVal(mode.c_str()));
			list->Assign(info, 0);
			Unref(info);
			}

		vl->append(list);

		ConnectionEvent(irc_join_message, vl);
		}

	else if ( irc_part_message && command == "PART" )
		{
		string channels = params;
		string message = "";
		unsigned int pos = params.find(' ');

		if ( pos < params.size() )
			{
			channels = params.substr(0, pos);
			if ( params.size() > pos + 1 )
				message = params.substr(pos + 1);
			if ( message[0] == ':' )
				message = message.substr(1);
			}

		string nick = prefix;
		pos = nick.find('!');
		if ( pos < nick.size() )
			nick = nick.substr(0, pos);

		vector<string> channelList = SplitWords(channels, ',');
		TableVal* set = new TableVal(string_set);

		for ( unsigned int i = 0; i < channelList.size(); ++i )
			{
			Val* idx = new StringVal(channelList[i].c_str());
			set->Assign(idx, 0);
			Unref(idx);
			}

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new StringVal(nick.c_str()));
		vl->append(set);
		vl->append(new StringVal(message.c_str()));

		ConnectionEvent(irc_part_message, vl);
		}

	else if ( irc_quit_message && command == "QUIT" )
		{
		string message = params;
		if ( message[0] == ':' )
			message = message.substr(1);

		string nickname = "";
		if ( prefix.size() > 0 )
			{
			unsigned int pos = prefix.find('!');
			if ( pos < prefix.size() )
				nickname = prefix.substr(0, pos);
			}

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new StringVal(nickname.c_str()));
		vl->append(new StringVal(message.c_str()));

		ConnectionEvent(irc_quit_message, vl);
		}

	else if ( irc_nick_message && command == "NICK" )
		{
		string nick = params;
		if ( nick[0] == ':' )
			nick = nick.substr(1);

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new StringVal(prefix.c_str()));
		vl->append(new StringVal(nick.c_str()));

		ConnectionEvent(irc_nick_message, vl);
		}

	else if ( irc_who_message && command == "WHO" )
		{
		vector<string> parts = SplitWords(params, ' ');
		if ( parts.size() < 1 || parts.size() > 2 )
			{
			Weird("irc_invalid_who_message_format");
			return;
			}

		bool oper = false;
		if ( parts.size() == 2 && parts[1] == "o" )
			oper = true;

		// Remove ":" from mask.
		if ( parts[0].size() > 0 && parts[0][0] == ':' )
			parts[0] = parts[0].substr(1);

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new StringVal(parts[0].c_str()));
		vl->append(new Val(oper, TYPE_BOOL));

		ConnectionEvent(irc_who_message, vl);
		}

	else if ( irc_whois_message && command == "WHOIS" )
		{
		vector<string> parts = SplitWords(params, ' ');
		if ( parts.size() < 1 || parts.size() > 2 )
			{
			Weird("irc_invalid_whois_message_format");
			return;
			}

		string server = "";
		string users = "";

		if ( parts.size() == 2 )
			{
			server = parts[0];
			users = parts[1];
			}
		else
			users = parts[0];

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new StringVal(server.c_str()));
		vl->append(new StringVal(users.c_str()));

		ConnectionEvent(irc_whois_message, vl);
		}

	else if ( irc_error_message && command == "ERROR" )
		{
		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new StringVal(prefix.c_str()));
		if ( params[0] == ':' )
			params = params.substr(1);
		vl->append(new StringVal(params.c_str()));

		ConnectionEvent(irc_error_message, vl);
		}

	else if ( irc_invite_message && command == "INVITE" )
		{
		vector<string> parts = SplitWords(params, ' ');
		if ( parts.size() == 2 )
			{ // remove ":" from channel
			if ( parts[1].size() > 0 && parts[1][0] == ':' )
				parts[1] = parts[1].substr(1);

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(prefix.c_str()));
			vl->append(new StringVal(parts[0].c_str()));
			vl->append(new StringVal(parts[1].c_str()));

			ConnectionEvent(irc_invite_message, vl);
			}
		else
			Weird("irc_invalid_invite_message_format");
		}

	else if ( irc_mode_message && command == "MODE" )
		{
		if ( params.size() > 0 )
			{
			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(prefix.c_str()));
			vl->append(new StringVal(params.c_str()));

			ConnectionEvent(irc_mode_message, vl);
			}

		else
			Weird("irc_invalid_mode_message_format");
		}

	else if ( irc_password_message && command == "PASS" )
		{
		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new StringVal(params.c_str()));
		ConnectionEvent(irc_password_message, vl);
		}

	else if ( irc_squit_message && command == "SQUIT" )
		{
		string server = params;
		string message = "";

		unsigned int pos = params.find(' ');
		if ( pos < params.size() )
			{
			server = params.substr(0, pos);
			message = params.substr(pos + 1);
			if ( message[0] == ':' )
				message = message.substr(1);
			}

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new Val(orig, TYPE_BOOL));
		vl->append(new StringVal(prefix.c_str()));
		vl->append(new StringVal(server.c_str()));
		vl->append(new StringVal(message.c_str()));

		ConnectionEvent(irc_squit_message, vl);
		}


	else if ( orig )
		{
		if ( irc_request )
			{
			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(prefix.c_str()));
			vl->append(new StringVal(command.c_str()));
			vl->append(new StringVal(params.c_str()));

			ConnectionEvent(irc_request, vl);
			}
		}

	else
		{
		if ( irc_message )
			{
			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new Val(orig, TYPE_BOOL));
			vl->append(new StringVal(prefix.c_str()));
			vl->append(new StringVal(command.c_str()));
			vl->append(new StringVal(params.c_str()));

			ConnectionEvent(irc_message, vl);
			}
		}

	if ( orig_status == REGISTERED && resp_status == REGISTERED &&
	     orig_zip_status == ACCEPT_ZIP && resp_zip_status == ACCEPT_ZIP )
		{
		orig_zip_status = ZIP_LOADED;
		resp_zip_status = ZIP_LOADED;
		AddSupportAnalyzer(new zip::ZIP_Analyzer(Conn(), true));
		AddSupportAnalyzer(new zip::ZIP_Analyzer(Conn(), false));
		}

	return;
	}

vector<string> IRC_Analyzer::SplitWords(const string input, const char split)
	{
	vector<string> words;

	if ( input.size() < 1 )
		return words;

	unsigned int start = 0;
	unsigned int split_pos = 0;

	// Ignore split-characters at the line beginning.
	while ( input[start] == split )
		{
		++start;
		++split_pos;
		}

	string word = "";
	while ( (split_pos = input.find(split, start)) < input.size() )
		{
		word = input.substr(start, split_pos - start);
		if ( word.size() > 0 && word[0] != split )
			words.push_back(word);

		start = split_pos + 1;
		}

	// Add line end if needed.
	if ( start < input.size() )
		{
		word = input.substr(start, input.size() - start);
		words.push_back(word);
		}

	return words;
	}
