Commit Diff


commit - 924cdc429d4c151314160caa0ca66da6f58bdccd
commit + e11136a333c70cd414281eecca353983c8443183
blob - /dev/null
blob + 176d6fc46029352447e235c1dc96b00f2d2d5145 (mode 755)
--- /dev/null
+++ botnow
@@ -0,0 +1,600 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+no strict 'refs';
+use IO::Socket;
+use IO::Select;
+use OpenBSD::Pledge;
+use OpenBSD::Unveil;
+
+# Read from filename and return array of lines without trailing newlines
+sub readarray {
+	my ($filename) = @_;
+	open(my $fh, '<', $filename) or die "Could not read file '$filename' $!";
+	chomp(my @lines = <$fh>);
+	close $fh;
+	return @lines;
+}
+
+# Read from filename and return as string
+sub readstr {
+	my ($filename) = @_;
+	open(my $fh, '<', $filename) or die "Could not read file '$filename' $!";
+	my $str = do { local $/; <$fh> };
+	close $fh;
+	return $str;
+}
+
+# Write str to filename
+sub writefile {
+	my ($filename, $str) = @_;
+	open(my $fh, '>', "$filename") or die "Could not write to $filename";
+	print $fh $str;
+	close $fh;
+}
+
+# Append str to filename
+sub appendfile {
+	my ($filename, $str) = @_;
+	open(my $fh, '>>', "$filename") or die "Could not append to $filename";
+	print $fh $str;
+	close $fh;
+}
+
+# Path to configuration file
+my $confpath = "botnow.conf";
+
+# Configuration variables will be stored in key => value pairs
+our %conf;
+
+foreach my $line (readarray($confpath)) {
+	if ($line =~ /^#/ or $line =~ /^\s*$/) { # skip comments and whitespace
+		next;
+	} elsif ($line =~ /^([-_a-zA-Z0-9]+)\s*=\s*([[:print:]]+)$/) {
+		$conf{$1} = $2;
+	} else {
+		die "ERROR: botnow.conf format invalid: $line";
+	}
+}
+
+# Name of local network
+$conf{localnet} = $conf{localnet} or die "ERROR: botnow.conf: localnet";
+
+# Internal IPv4 address and plaintext port
+$conf{host} = $conf{host} || "127.0.0.1";
+$conf{port} = $conf{port} || 1337;
+
+# Bouncer hostname
+chomp($conf{hostname} = $conf{hostname} || `hostname`);
+
+# External IPv4 address, plaintext and ssl port
+$conf{ip4} = $conf{ip4} or die "ERROR: botnow.conf: ip4";
+$conf{ip6} = $conf{ip6} or die "ERROR: botnow.conf: ip6";
+$conf{ip6subnet} = $conf{ip6subnet} or die "ERROR: botnow.conf: ip6subnet";
+$conf{ip6prefix} = $conf{ip6prefix} or die "ERROR: botnow.conf: ip6prefix";
+$conf{plainport} = $conf{plainport} || 1337;
+$conf{sslport} = $conf{sslport} || 31337;
+
+# Nick and password of bot -- Make sure to add to oper block
+$conf{nick} = $conf{nick} or die "ERROR: botnow.conf: nick";
+$conf{pass} = $conf{pass} or die "ERROR: botnow.conf: pass";
+
+# Comma-separated list of channels for requesting bouncers
+$conf{chans} = $conf{chans} or die "ERROR: botnow.conf: chans";
+
+# Number of words in password
+$conf{passlength} = $conf{passlength} || 3;
+
+# Mail from address
+if (!defined($conf{mailname})) {
+	if ($conf{mailfrom} =~ /^([^@]+)@/) {
+		$conf{mailname} = $1 or die "ERROR: botnow.conf mailname";
+	}
+}
+
+# ZNC install directory
+$conf{zncdir} = $conf{zncdir} || "/home/znc/home/znc";
+
+# NSD zone dir
+$conf{zonedir} = $conf{zonedir} || "/var/nsd/zones/master/";
+
+# Network Interface Config File
+$conf{hostnameif} = $conf{hostnameif} || "/etc/hostname.vio0";
+
+# Verbosity: 0 (no errors), 1 (errors), 2 (warnings), 3 (diagnostic)
+use constant {
+	NONE => 0,
+	ERRORS => 1,
+	WARNINGS => 2,
+	ALL => 3,
+};
+$conf{verbose} = $conf{verbose} || ERRORS;
+
+# Terms of Service; don't edit lines with the word EOF
+$conf{terms} = $conf{terms} or die "ERROR: botnow.conf terms";
+
+$conf{ipv6path} = "ipv6s"; # ipv6 file path
+$conf{netpath} = "networks"; # networks file path
+$conf{expires} = $conf{expires} || 1800; # time before captcha expires
+
+if(defined($conf{die})) { die $conf{die}; }
+
+my @modules;
+if (defined($conf{modules})) {
+	@modules = split(/\s+/, $conf{modules});
+}
+my @activenets;
+if (defined($conf{activenets})) {
+	@activenets = split(/\s+/, $conf{activenets});
+}
+
+use lib './';
+foreach my $mod (@modules) {
+	require "$mod.pm";
+}
+foreach my $mod (@modules) {
+	my $init = "${mod}::init";
+	$init->();
+}
+
+our @networks;
+my @bots;
+my @months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
+my @days = qw(Sun Mon Tue Wed Thu Fri Sat Sun);
+my @chans = split /[,\s]+/m, $conf{chans};
+my @teamchans;
+if (defined($conf{teamchans})) { @teamchans = split /[,\s]+/m, $conf{teamchans}; }
+my $call;
+my $botnick = $conf{nick};
+my $host = $conf{host};
+my $port = $conf{port};
+my $pass = $conf{pass};
+my $localnet = $conf{localnet};
+my $staff = $conf{staff};
+my @stafflist = split(/ /,$staff);
+my $verbose = $conf{verbose};
+my $ipv6path = $conf{ipv6path};
+my $netpath = $conf{netpath};
+my $expires = $conf{expires};
+
+unveil("./", "r") or die "Unable to unveil $!";
+unveil("$confpath", "r") or die "Unable to unveil $!";
+unveil("$netpath", "r") or die "Unable to unveil $!";
+unveil("$ipv6path", "rwc") or die "Unable to unveil $!";
+unveil() or die "Unable to lock unveil $!";
+
+#dns and inet for sockets, proc and exec for figlet
+#rpath for reading file, wpath for writing file, cpath for creating path
+#flock, fattr for sqlite
+pledge( qw(stdio rpath wpath cpath inet dns proc exec flock fattr) ) or die "Unable to pledge: $!";
+# Return list of networks from filename
+# To add multiple servers for a single network, simply create a new entry with
+# the same net name; znc ignores addnetwork commands when a network already exists
+sub readnetworks {
+	my ($filename) = @_;
+	my @lines = readarray($filename);
+	my @networks;
+	foreach my $line (@lines) {
+		if ($line =~ /^#/ or $line =~ /^\s*$/) { # skip comments and whitespace
+			next;
+		} elsif ($line =~ /^\s*([-a-zA-Z0-9]+)\s*([-_.:a-zA-Z0-9]+)\s*(~|\+)?([0-9]+)\s*$/) {
+			my ($name, $server, $port) = ($1, $2, $4);
+			my $trustcerts;
+			if (!defined($3)) {
+				$trustcerts = 0;
+			} elsif ($3 eq "~") { # Use SSL but trust all certs
+				$port = "+".$port;
+				$trustcerts = 1;
+			} else { # Use SSL and verify certs
+				$port = "+".$port;
+				$trustcerts = 0;
+			}
+			push(@networks, {"name" => $name, "server" => $server, "port" => $port, "trustcerts" => $trustcerts });
+		} else {
+			die "network format invalid: $line\n";
+		}
+	}
+	return @networks;
+}
+
+@networks = readnetworks($netpath);
+
+# networks must be sorted to avoid multiple connections
+@networks = sort @networks;
+
+# create sockets
+my $sel = IO::Select->new( );
+foreach my $network (@activenets) {
+	my $socket = IO::Socket::INET->new(PeerAddr=>$host, PeerPort=>$port, Proto=>'tcp', Timeout=>'300') || print "Failed to establish connection\n";
+	$sel->add($socket);
+	my $bot = {("sock" => $socket), ("name" => $network)};
+	push(@bots, $bot);
+	putserv($bot, "NICK $botnick");
+	putserv($bot, "USER $botnick * * :$botnick");
+}
+
+while(my @ready = $sel->can_read) {
+	my ($bot, $response);
+	my ($sender, $val);
+	foreach my $socket (@ready) {
+		foreach my $b (@bots) {
+			if($socket == $b->{sock}) {
+				$bot = $b;
+				last;
+			}
+		}
+		if (!defined($response = <$socket>)) {
+			debug(ERRORS, "ERROR ".$bot->{name}." has no response:");
+			next;
+		}
+		if ($response =~ /^PING :(.*)\r\n$/i) {
+			putserv($bot, "PONG :$1");
+		} elsif ($response =~ /^:irc.znc.in (.*) (.*) :(.*)\r\n$/) {
+			my ($type, $target, $text) = ($1, $2, $3);
+			if ($type eq "001" && $target =~ /^$botnick.?$/ && $text eq "Welcome to ZNC") {
+			} elsif ($type eq "NOTICE" && $target =~ /^$botnick.?$/ && $text eq "*** To connect now, you can use /quote PASS <username>:<password>, or /quote PASS <username>/<network>:<password> to connect to a specific network.") {
+			} elsif ($type eq "NOTICE" && $target =~ /^$botnick.?$/ && $text eq "*** You need to send your password. Configure your client to send a server password.") {
+			} elsif ($type eq "464" && $target =~ /^$botnick.?$/ && $text eq "Password required") {
+				putserv($bot, "PASS $botnick/$bot->{name}:$pass");
+				if ($bot->{name} =~ /^$localnet$/i) {
+					putserv($bot, "OPER $botnick $pass");
+					putserv($bot, "PRIVMSG *status :LoadMod --type=user controlpanel");
+					putserv($bot, "PRIVMSG *controlpanel :get Admin $botnick");
+					putserv($bot, "PRIVMSG *controlpanel :get Nick cloneuser");
+					foreach my $chan (@teamchans) {
+						putserv($bot, "JOIN $chan");
+					}
+				}
+				if ($bot->{name} !~ /^$localnet$/i) {
+					foreach my $chan (@chans) {
+						putserv($bot, "JOIN $chan");
+					}
+				}
+			} elsif ($type eq "464" && $target =~ /^$botnick.?$/ && $text eq "Invalid Password") {
+				die "ERROR: Wrong Username/Password: $bot->{name}";
+			} else {
+				debug(ERRORS, "Unexpected bncnow.pl 257: type: $type, target: $target, text: $text");
+			}
+		} elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) PRIVMSG ([^ ]+) :(.*)\r\n$/i) {
+			my ($hostmask, $nick, $host, $target, $text) = ($1, $2, $3, $4, $5);
+			if ($hostmask eq '*status!znc@znc.in' && $target =~ /^$botnick.?$/) {
+				if ($text =~ /Network ([[:ascii:]]+) doesn't exist./) {
+					debug(ERRORS, "nonexistent: $1");
+				} elsif ($text eq "You are currently disconnected from IRC. Use 'connect' to reconnect.") {
+					debug(ERRORS, "disconnected: $bot->{name}");
+				} elsif ($text =~ /Unable to load module (.*): Module (.*) already loaded./) {
+					debug(ALL, "Module $1 already loaded\n");
+				} elsif ($text =~ /^Disconnected from IRC.*$/) {
+					debug(ERRORS, "$bot->{name}: $text");
+				} elsif ($text =~ /^|/) {
+					debug(ERRORS, "$bot->{name}: $text");
+				} else {
+					debug(ERRORS, "Unexpected bncnow.pl 273: $response");
+				}
+			} elsif ($text =~ /^!([[:graph:]]+)\s*(.*)/) {
+				my ($cmd, $text) = ($1, $2);
+				my $hand = $staff; # TODO fix later
+				if ($target =~ /^#/) {
+					foreach my $c (@{$call->{pub}}) {
+						if ($cmd eq $c->{cmd}) {
+							my $proc = $c->{proc};
+							$proc->($bot, $nick, $host, $hand, $target, $text);
+						}
+					}
+				} else {
+					foreach my $c (@{$call->{msg}}) {
+						if ($cmd eq $c->{cmd}) {
+							my $proc = $c->{proc};
+							$proc->($bot, $nick, $host, $hand, $text);
+						}
+					}
+				}
+			} else {
+				my $hand = $staff; # TODO fix later
+				if ($target =~ /^#/) {
+					foreach my $c (@{$call->{pubm}}) {
+						my $proc = $c->{proc};
+						$proc->($bot, $nick, $host, $hand, $target, $text);
+					}
+				} else {
+					foreach my $c (@{$call->{msgm}}) {
+						my $proc = $c->{proc};
+						$proc->($bot, $nick, $host, $hand, $text);
+					}
+				}
+			}
+			debug(ALL, "$hostmask $target $text");
+		} elsif($response =~ /^:([^ ]+) NOTICE ([^ ]+) :(.*)\r\n$/i) {
+			my ($hostmask, $target, $text) = ($1, $2, $3);
+			if ($hostmask =~ /([^!]+)!([^@]+@[^@ ]+)/) {
+				my ($nick, $host) = ($1, $2);
+				my $hand = $staff; # TODO fix later
+				foreach my $c (@{$call->{notc}}) {
+					#	if ($text eq $c->{mask}) { # TODO fix later
+					my $proc = $c->{proc};
+					$proc->($bot, $nick, $host, $hand, $text, $target);
+					#	}
+				}
+				# TODO use CTCR
+				# CTCP replies
+				if ($hostmask ne '*status!znc@znc.in') {
+					if ($text =~ /^(PING|VERSION|TIME|USERINFO) (.*)$/i) {
+						my ($key, $val) = ($1, $2);
+						my $id = SQLite::id("irc", "nick", $nick, $expires);
+						SQLite::set("irc", "id", $id, "ctcp".lc($key), $val);
+						SQLite::set("irc", "id", $id, "localtime", time());
+					}
+				}
+			}
+			debug(ALL, "$hostmask NOTICE $target $text");
+#:portlane.se.quakenet.org NOTICE guava :Highest connection count: 1541 (1540 clients)
+#:portlane.se.quakenet.org NOTICE guava :on 2 ca 2(4) ft 20(20) tr
+		} elsif($response =~ /^:([^ ]+) MODE ([^ ]+) ([^ ]+)\s*(.*)\r\n$/i) {
+			my ($hostmask, $chan, $change, $targets) = ($1, $2, $3, $4);
+			if ($hostmask =~ /([^!]+)!([^@]+@[^@ ]+)/) {
+				my ($nick, $host) = ($1, $2);
+				my $hand = $staff; # TODO fix later
+				foreach my $c (@{$call->{mode}}) {
+					# TODO filter by mask
+					my $proc = $c->{proc};
+					$proc->($bot, $nick, $host, $hand, $chan, $change, $targets);
+				}
+			}
+			debug(ALL, "$hostmask MODE $chan $change $targets");
+#:guava!guava@guava.guava.ircnow.org MODE guava :+Ci
+#:ChanServ!services@services.irc.ircnow.org MODE #testing +q jrmu
+#:jrmu!jrmu@jrmu.staff.ircnow.org MODE #testing +o jrmu
+#Unexpected bncnow.pl 460: :irc.guava.ircnow.org MODE guava :+o
+		} elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) JOIN :?(.*)\r\n$/i) {
+			my ($hostmask, $nick, $host, $chan) = ($1, $2, $3, $4);
+			my $hand = $staff; # TODO fix later
+			foreach my $c (@{$call->{join}}) {
+				my $proc = $c->{proc};
+				$proc->($bot, $nick, $host, $hand, $chan);
+			}
+			debug(ALL, "$hostmask JOIN $chan");
+#:jrmu!jrmu@jrmu.staff.ircnow.org JOIN :#testing
+		} elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) PART ([^ ]+) :(.*)\r\n$/i) {
+			my ($hostmask, $nick, $host, $chan, $text) = ($1, $2, $3, $4, $5);
+			my $hand = $staff; # TODO fix later
+			foreach my $c (@{$call->{part}}) {
+				#	if ($text eq $c->{mask}) { # TODO fix later
+				my $proc = $c->{proc};
+				$proc->($bot, $nick, $host, $hand, $chan, $text);
+				#	}
+			}
+			debug(ALL, "$hostmask PART $chan :$text");
+#:jrmu!jrmu@jrmu.staff.ircnow.org PART #testing :
+		} elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) KICK (#[^ ]+) ([^ ]+) :(.*)\r\n$/i) {
+			my ($hostmask, $nick, $host, $chan, $kicked, $text) = ($1, $2, $3, $4, $5, $6);
+			my $hand = $staff; # TODO fix later
+			foreach my $c (@{$call->{kick}}) {
+				#	if ($text eq $c->{mask}) { # TODO fix later
+				my $proc = $c->{proc};
+				$proc->($bot, $nick, $host, $hand, $chan, $text);
+				#	}
+			}
+			debug(ALL, "$hostmask KICK $chan $kicked :$text");
+#jrmu!jrmu@jrmu.users.undernet.org KICK #ircnow guava :this is a test
+		} elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) NICK :?(.*)\r\n$/i) {
+			my ($hostmask, $nick, $host, $text) = ($1, $2, $3, $4);
+			debug(ALL, "$hostmask NICK $text");
+#:Fly0nDaWaLL|dal!psybnc@do.not.h4ck.me NICK :nec|dal
+		} elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) QUIT :(.*)\r\n$/i) {
+			my ($hostmask, $nick, $host, $text) = ($1, $2, $3, $4);
+			debug(ALL, "$hostmask QUIT :$text");
+#:Testah!~sid268081@aa38a510 QUIT :Client closed connection
+		} elsif($response =~ /^NOTICE AUTH :(.*)\r\n$/i) {
+			my ($text) = ($1);
+			debug(ALL, "NOTICE AUTH: $text");
+#NOTICE AUTH :*** Looking up your hostname
+#NOTICE AUTH: *** Looking up your hostname
+#NOTICE AUTH: *** Checking Ident
+#NOTICE AUTH: *** Got ident response
+#NOTICE AUTH: *** Found your hostname
+		} elsif ($response =~ /^:([[:graph:]]+) (\d\d\d) $botnick.? :?(.*)\r?\n?\r$/i) {
+			my ($server, $code, $text) = ($1, $2, $3);
+			if ($code =~ /^001$/) { # Server Info
+				debug(ERRORS, "connected: $bot->{name}");
+			} elsif ($code =~ /^0\d\d$/) { # Server Info
+				debug(ALL, "$server $code $text");
+			} elsif ($code =~ /^2\d\d$/) { # Server Stats
+				debug(ALL, "$server $code $text");
+			} elsif ($code == 301 && $text =~ /^([-_\|`a-zA-Z0-9]+) :([[:graph:]]+)/) {
+				debug(ALL, "$text");
+			} elsif ($code == 307 && $text =~ /^([-_\|`a-zA-Z0-9]+) (.*)/) {
+				my ($sender, $key) = ($1, "registered");
+				$val = $2 eq ":is a registered nick" ? "True" : "$2";
+				my $id = SQLite::id("irc", "nick", $sender, $expires);
+				SQLite::set("irc", "id", $id, "identified", $val);
+				debug(ALL, "$key: $val");
+			} elsif ($code == 311 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([^:]+)\s+([^:]+) \* :([^:]*)/) {
+				my ($sender, $key, $val) = ($1, "hostmask", "$1\!$2\@$3");
+				my $id = SQLite::id("irc", "nick", $sender, $expires);
+				SQLite::set("irc", "id", $id, $key, $val);
+				debug(ALL, "$key: $val");
+			} elsif ($code == 312 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([^:]+) :([^:]+)/) {
+				my ($sender, $key, $val) = ($1, "server", $2);
+				my $id = SQLite::id("irc", "nick", $sender, $expires);
+				SQLite::set("irc", "id", $id, $key, $val);
+				debug(ALL, "$key: $val");
+			} elsif ($code == 313 && $text =~ /^([-_\|`a-zA-Z0-9]+) :?(.*)/) {
+				my ($sender, $key, $val) = ($1, "oper", ($2 eq "is an IRC operator" ? "True" : "$2"));
+				my $id = SQLite::id("irc", "nick", $sender, $expires);
+				SQLite::set("irc", "id", $id, $key, $val);
+				debug(ALL, "$key: $val");
+			} elsif ($code == 315 && $text =~ /^([-_\|`a-zA-Z0-9]+) :End of \/?WHO(IS)? list/) {
+				debug(ALL, "End of WHOIS");
+			} elsif ($code == 317 && $text =~ /^([-_\|`a-zA-Z0-9]+) (\d+) (\d+) :?(.*)/) {
+				($sender, my $idle, my $epochtime) = ($1, $2, $3);
+				my $id = SQLite::id("irc", "nick", $sender, $expires);
+				SQLite::set("irc", "id", $id, "idle", $idle);
+#				SQLite::set("irc", "id", $id, "epochtime", time());
+				debug(ALL, "idle: $idle, epochtime: $epochtime");
+			} elsif ($code == 318 && $text =~ /^([-_\|`a-zA-Z0-9]+) :End of \/?WHOIS list/) {
+				debug(ALL, "End of WHOIS");
+			} elsif ($code == 319 && $text =~ /^([-_\|`a-zA-Z0-9]+) :(.*)/) {
+				my ($sender, $key, $val) = ($1, "chans", $2);
+				my $id = SQLite::id("irc", "nick", $sender, $expires);
+				SQLite::set("irc", "id", $id, $key, $val);
+				debug(ALL, "$key: $val");
+			} elsif ($code == 330 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([-_\|`a-zA-Z0-9]+) :?(.*)/) {
+				my ($sender, $key, $val) = ($1, "identified", ($3 eq "is logged in as" ? "True" : $2));
+				my $id = SQLite::id("irc", "nick", $sender, $expires);
+				SQLite::set("irc", "id", $id, $key, $val);
+				debug(ALL, "$key: $val");
+			} elsif ($code == 338 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([0-9a-fA-F:.]+) :actually using host/) {
+				my ($sender, $key, $val) = ($1, "ip", $2);
+				my $id = SQLite::id("irc", "nick", $sender, $expires);
+				SQLite::set("irc", "id", $id, $key, $val);
+				debug(ALL, "$key: $val");
+	#Unexpected: efnet.port80.se 338 jrmu 206.253.167.44 :actually using host
+			} elsif ($code == 378 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is connecting from ([^ ]+)\s*([0-9a-fA-F:.]+)?/) {
+				my ($sender, $key, $val) = ($1, "ip", $3);
+				my $id = SQLite::id("irc", "nick", $sender, $expires);
+				SQLite::set("irc", "id", $id, $key, $val);
+				debug(ALL, "$key: $val");
+			} elsif ($code == 671 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is using a secure connection/) {
+				my ($sender, $key, $val) = ($1, "ssl", "True");
+				my $id = SQLite::id("irc", "nick", $sender, $expires);
+				SQLite::set("irc", "id", $id, $key, $val);
+				debug(ALL, "$key: $val");
+			} elsif ($code =~ /^332$/) { # Topic
+		#		print "$text\r\n";
+			} elsif ($code =~ /^333$/) { #
+		#		print "$server $text\r\n";
+		#karatkievich.freenode.net 333 #ircnow jrmu!znc@206.253.167.44 1579277253
+			} elsif ($code =~ /^352$/) { # Hostmask
+#:datapacket.hk.quakenet.org 352 * znc guava.guava.ircnow.org *.quakenet.org guava H :0 guava
+		#		print "$server $code $text\r\n";
+			} elsif ($code =~ /^353$/) { # Names
+		#		print "$server $code $text\r\n";
+			} elsif ($code =~ /^366$/) { # End of names
+		#		print "$server $code $text\r\n";
+			} elsif ($code =~ /^37\d$/) { # MOTD
+		#		print "$server $code $text\r\n";
+			} elsif ($code =~ /^381$/) { # IRC Operator Verified
+		#		print "IRC Oper Verified\r\n";
+			} elsif ($code =~ /^401$/) { # IRC Operator Verified
+		#		print "IRC Oper Verified\r\n";
+			} elsif ($code =~ /^403$/) { # No such channel
+		#		debug(ERRORS, "$text");
+			} elsif ($code =~ /^422$/) { # MOTD missing
+		#		print "$server $code $text\r\n";
+			} elsif ($code =~ /^396$/) { # Display hostname
+		#		print "$server $code $text\r\n";
+#Unexpected bncnow.pl 454: irc.guava.ircnow.org 396 guava.guava.ircnow.org :is your displayed hostname now
+			} elsif ($code =~ /^464$/) { # Invalid password for oper
+				foreach my $chan (@teamchans) {
+					putserv($bot, "PRIVMSG $chan :$botnick oper password failed; the bot will be unable to view uncloaked IP addresses");
+				}
+			} elsif ($code =~ /^477$/) { # Can't join channel
+				foreach my $chan (@teamchans) {
+					putserv($bot, "PRIVMSG $chan :ERROR: $botnick on $server: $text");
+				}
+			} elsif ($code == 716 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is in \+g mode \(server-side ignore.\)/) {
+				debug(ALL, "$text");
+			} else {
+				debug(ERRORS, "Unexpected bncnow.pl 454: $server $code $text");
+			}
+		} else {
+			debug(ERRORS, "Unexpected bncnow.pl 460: $response");
+		}
+	}
+}
+
+sub putserv {
+	my( $bot, $text )=@_;
+	my $socket = $bot->{sock};
+	if ($text =~ /^([^:]+):([[:ascii:]]*)$/m) {
+		my ($cmd, $line) = ($1, $2);
+		my @lines = split /\r?\n/m, $line;
+		foreach my $l (@lines) {
+			print $socket "$cmd:$l\r\n";
+		}
+	} else {
+		print $socket "$text\r\n";
+	}
+}
+
+sub putservlocalnet {
+	my( $bot, $text )=@_;
+	my $botlocalnet;
+	foreach my $b (@bots) {
+		if($b->{name} =~ /^$localnet$/i) {
+			$botlocalnet = $b;
+			last;
+		}
+	}	
+	putserv($botlocalnet, $text);
+}
+
+
+sub date {
+	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
+	my $localtime = sprintf("%04d%02d%02d", $year+1900, $mon+1, $mday);
+	return $localtime;
+}
+sub gettime {
+	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
+	my $localtime = sprintf("%s %s %d %02d:%02d:%02d", $days[$wday], $months[$mon], $mday, $hour, $min, $sec);
+	return $localtime;
+}
+
+sub whois {
+	my( $socket, $target )=@_;
+	print $socket "WHOIS $target $target\r\n";
+}
+
+sub ctcp {
+	my( $socket, $target )=@_;
+#	print $socket "PRIVMSG $target :".chr(01)."CLIENTINFO".chr(01)."\r\n";
+#	print $socket "PRIVMSG $target :".chr(01)."FINGER".chr(01)."\r\n";
+#	print $socket "PRIVMSG $target :".chr(01)."SOURCE".chr(01)."\r\n";
+	print $socket "PRIVMSG $target :".chr(01)."TIME".chr(01)."\r\n";
+#	print $socket "PRIVMSG $target :".chr(01)."USERINFO".chr(01)."\r\n";
+	print $socket "PRIVMSG $target :".chr(01)."VERSION".chr(01)."\r\n";
+#	print $socket "PRIVMSG $target :".chr(01)."PING".chr(01)."\r\n";
+}
+
+sub cbind {
+	my ($type, $flags, $cmd, $proc) = @_;
+	if ($type eq "pub") {
+		push(@{$call->{pub}}, {cmd => $cmd, proc => $proc});
+	} elsif ($type eq "msg") {
+		push(@{$call->{msg}}, {cmd => $cmd, proc => $proc});
+	} elsif ($type eq "notc") {
+		push(@{$call->{notc}}, {mask => $cmd, proc => $proc});
+	} elsif ($type eq "mode") {
+		push(@{$call->{mode}}, {mask => $cmd, proc => $proc});
+	} elsif ($type eq "join") {
+		push(@{$call->{join}}, {mask => $cmd, proc => $proc});
+	} elsif ($type eq "partcall") {
+		push(@{$call->{part}}, {mask => $cmd, proc => $proc});
+	} elsif ($type eq "pubm") {
+		push(@{$call->{pubm}}, {mask => $cmd, proc => $proc});
+	} elsif ($type eq "msgm") {
+		push(@{$call->{msgm}}, {mask => $cmd, proc => $proc});
+	}
+}
+
+sub debug {
+	my ($level, $msg) = @_;
+	if ($verbose >= $level) { print "$msg\n"; }
+}
+
+sub isstaff {
+	my( $bot, $nick ) = @_;
+	if( !( $bot->{name} =~ /^$localnet$/i ) )
+	{
+		return 0;
+	}
+	my $lnick = lc $nick;
+	foreach( @stafflist ) {
+		if( $lnick eq $_ ) {
+			return 1;
+		}            
+	}
+	return 0;
+}