Blob


1 #!/usr/bin/perl
3 use strict;
4 use warnings;
5 no strict 'refs';
6 use IO::Socket;
7 use IO::Select;
8 use OpenBSD::Pledge;
9 use OpenBSD::Unveil;
10 use File::Copy qw(copy);
11 use File::Basename;
13 # Path to configuration file
14 my $confpath = "botnow.conf";
15 my $backupspath = "/home/botnow/backups/";
17 # Returns date in YYYYMMDD format
18 sub date {
19 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
20 my $localtime = sprintf("%04d%02d%02d", $year+1900, $mon+1, $mday);
21 return $localtime;
22 }
24 # Read from filename and return array of lines without trailing newlines
25 sub readarray {
26 my ($filename) = @_;
27 open(my $fh, '<', $filename) or die "Could not read file '$filename' $!";
28 chomp(my @lines = <$fh>);
29 close $fh;
30 return @lines;
31 }
33 # Read from filename and return as string
34 sub readstr {
35 my ($filename) = @_;
36 open(my $fh, '<', $filename) or die "Could not read file '$filename' $!";
37 my $str = do { local $/; <$fh> };
38 close $fh;
39 return $str;
40 }
42 # Write str to filename
43 sub writefile {
44 my ($filename, $str) = @_;
45 my $date = date();
46 copy($filename, $backupspath.basename($filename).".".date()) or die "Could not make backup of $filename";
47 open(my $fh, '>', "$filename") or die "Could not write to $filename";
48 print $fh $str;
49 close $fh;
50 }
52 # Append str to filename
53 sub appendfile {
54 my ($filename, $str) = @_;
55 open(my $fh, '>>', "$filename") or die "Could not append to $filename";
56 print $fh $str;
57 close $fh;
58 }
60 # Returns timestamp in "Day MM DD HH:MM:SS" format
61 sub gettime {
62 my @months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
63 my @days = qw(Sun Mon Tue Wed Thu Fri Sat Sun);
64 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
65 my $localtime = sprintf("%s %s %d %02d:%02d:%02d", $days[$wday], $months[$mon], $mday, $hour, $min, $sec);
66 return $localtime;
67 }
69 sub mail {
70 my( $from, $to, $fromname, $subject, $body )=@_;
71 my $msg = <<"EOF";
72 From: $from
73 To: $to
74 Subject: $subject
75 MIME-Version: 1.0
76 Content-Type: text/plain; charset=us-ascii
77 Content-Disposition: inline
79 $body
80 EOF
81 open(my $fh, "| /usr/sbin/sendmail -tv -F '$fromname' -f $from") or die "Could not send mail $!";
82 print $fh $msg;
83 close $fh;
84 return "true";
85 }
87 # Configuration variables will be stored in key => value pairs
88 our %conf;
90 foreach my $line (readarray($confpath)) {
91 if ($line =~ /^#/ or $line =~ /^\s*$/) { # skip comments and whitespace
92 next;
93 } elsif ($line =~ /^([-_a-zA-Z0-9]+)\s*=\s*([[:print:]]+)$/) {
94 $conf{$1} = $2;
95 } else {
96 die "ERROR: botnow.conf format invalid: $line";
97 }
98 }
100 # Name of local network
101 $conf{localnet} = $conf{localnet} or die "ERROR: botnow.conf: localnet";
103 # Internal IPv4 address and plaintext port
104 $conf{host} = $conf{host} || "127.0.0.1";
105 $conf{port} = $conf{port} || 1337;
107 # Bouncer hostname
108 chomp($conf{hostname} = $conf{hostname} || `hostname`);
110 # Mail hostname
111 chomp($conf{mailhostname} = $conf{mailhostname} || `hostname`);
113 # Webpanel URL
114 chomp($conf{webpanel} = $conf{webpanel} || "https://bnc.$conf{hostname}");
116 # Webmail URL
117 chomp($conf{webmail} = $conf{webmail} || "https://mail.$conf{hostname}");
119 # External IPv4 address, plaintext and ssl port
120 $conf{ip4} = $conf{ip4} or die "ERROR: botnow.conf: ip4";
121 $conf{ip6} = $conf{ip6} or die "ERROR: botnow.conf: ip6";
122 $conf{ip6subnet} = $conf{ip6subnet} or die "ERROR: botnow.conf: ip6subnet";
123 $conf{ip6prefix} = $conf{ip6prefix} or die "ERROR: botnow.conf: ip6prefix";
124 $conf{plainport} = $conf{plainport} || 1337;
125 $conf{sslport} = $conf{sslport} || 31337;
126 $conf{imapport} = $conf{imapport} || 143;
127 $conf{smtpport} = $conf{smtpport} || 587;
129 # Nick and password of bot -- Make sure to add to oper block
130 $conf{nick} = $conf{nick} or die "ERROR: botnow.conf: nick";
131 $conf{pass} = $conf{pass} or die "ERROR: botnow.conf: pass";
133 # Comma-separated list of channels for requesting bouncers
134 $conf{chans} = $conf{chans} or die "ERROR: botnow.conf: chans";
136 my @networks;
137 if (defined($conf{networks})) {
138 @networks = split(/\s+/, $conf{networks});
141 # Mail from address
142 if (!defined($conf{mailname})) {
143 if ($conf{mailfrom} =~ /^([^@]+)@/) {
144 $conf{mailname} = $1 or die "ERROR: botnow.conf mailname";
148 # Terms of Service; don't edit lines with the word EOF
149 $conf{terms} = $conf{terms} or die "ERROR: botnow.conf terms";
151 # Number of words in password
152 $conf{passlength} = $conf{passlength} || 3;
154 # Time before captcha expires
155 $conf{expires} = $conf{expires} || 1800;
157 # NSD zone dir
158 $conf{zonedir} = $conf{zonedir} || "/var/nsd/zones/master/";
160 # ZNC install directory
161 $conf{zncdir} = $conf{zncdir} || "/home/znc/home/znc";
163 # Network Interface Config File
164 $conf{hostnameif} = $conf{hostnameif} || "/etc/hostname.vio0";
166 # Verbosity: 0 (no errors), 1 (errors), 2 (warnings), 3 (diagnostic)
167 use constant {
168 NONE => 0,
169 ERRORS => 1,
170 WARNINGS => 2,
171 ALL => 3,
172 };
173 $conf{verbose} = $conf{verbose} || ERRORS;
175 if(defined($conf{die})) { die $conf{die}; }
177 my @modules;
178 if (defined($conf{modules})) {
179 @modules = split(/\s+/, $conf{modules});
181 use lib './';
182 foreach my $mod (@modules) {
183 require "$mod.pm";
185 foreach my $mod (@modules) {
186 my $init = "${mod}::init";
187 $init->();
190 my @bots;
191 my @chans = split /[,\s]+/m, $conf{chans};
192 my @teamchans;
193 if (defined($conf{teamchans})) { @teamchans = split /[,\s]+/m, $conf{teamchans}; }
194 my $call;
195 my $nick = $conf{nick};
196 my $host = $conf{host};
197 my $port = $conf{port};
198 my $pass = $conf{pass};
199 my $localnet = $conf{localnet};
200 my $staff = $conf{staff};
201 my @stafflist = split(/ /,$staff);
202 my $verbose = $conf{verbose};
203 my $expires = $conf{expires};
205 unveil("./", "r") or die "Unable to unveil $!";
206 unveil("$confpath", "r") or die "Unable to unveil $!";
207 unveil("$backupspath", "rwc") or die "Unable to unveil $!";
208 unveil("/usr/sbin/sendmail", "rx") or die "Unable to unveil $!";
209 unveil("/bin/sh", "rx") or die "Unable to unveil $!";
210 unveil() or die "Unable to lock unveil $!";
212 #dns and inet for sockets, proc and exec for figlet
213 #rpath for reading file, wpath for writing file, cpath for creating path
214 #flock, fattr for sqlite
215 pledge( qw(stdio rpath wpath cpath inet dns proc exec flock fattr) ) or die "Unable to pledge: $!";
217 # create sockets
218 my $sel = IO::Select->new( );
219 foreach my $network (@networks) {
220 my $socket = IO::Socket::INET->new(PeerAddr=>$host, PeerPort=>$port, Proto=>'tcp', Timeout=>'300') || print "Failed to establish connection\n";
221 $sel->add($socket);
222 my $bot = {("sock" => $socket), ("name" => $network)};
223 push(@bots, $bot);
224 putserv($bot, "NICK $nick");
225 putserv($bot, "USER $nick * * :$nick");
228 while(my @ready = $sel->can_read) {
229 my ($bot, $response);
230 foreach my $socket (@ready) {
231 foreach my $b (@bots) {
232 if($socket == $b->{sock}) {
233 $bot = $b;
234 last;
237 if (!defined($response = <$socket>)) {
238 debug(ERRORS, "ERROR ".$bot->{name}." has no response:");
239 next;
241 if ($response =~ /^PING :(.*)\r\n$/i) {
242 putserv($bot, "PONG :$1");
243 } elsif ($response =~ /^:irc.znc.in (.*) (.*) :(.*)\r\n$/) {
244 my ($type, $target, $text) = ($1, $2, $3);
245 if ($type eq "464" && $target =~ /^$nick.?$/ && $text eq "Password required") {
246 putserv($bot, "PASS $nick/$bot->{name}:$pass");
247 if ($bot->{name} =~ /^$localnet$/i) {
248 putserv($bot, "OPER $nick $pass");
249 putserv($bot, "PRIVMSG *status :LoadMod --type=user controlpanel");
250 putserv($bot, "PRIVMSG *controlpanel :get Admin $nick");
251 putserv($bot, "PRIVMSG *controlpanel :get Nick cloneuser");
252 foreach my $chan (@teamchans) {
253 putserv($bot, "JOIN $chan");
256 if ($bot->{name} !~ /^$localnet$/i) {
257 foreach my $chan (@chans) {
258 putserv($bot, "JOIN $chan");
261 } elsif ($type eq "464" && $target =~ /^$nick.?$/ && $text eq "Invalid Password") {
262 die "ERROR: Wrong Username/Password: $bot->{name}";
263 } else {
264 debug(ALL, "Debug type: $type, target: $target, text: $text");
266 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) PRIVMSG ([^ ]+) :(.*)\r\n$/i) {
267 my ($hostmask, $sendnick, $host, $target, $text) = ($1, $2, $3, $4, $5);
268 if ($hostmask eq '*status!znc@znc.in' && $target =~ /^$nick.?$/) {
269 if ($text =~ /Network ([[:ascii:]]+) doesn't exist./) {
270 debug(ERRORS, "ERROR: nonexistent: $1");
271 } elsif ($text eq "You are currently disconnected from IRC. Use 'connect' to reconnect.") {
272 debug(ERRORS, "ERROR: disconnected: $bot->{name}");
273 } elsif ($text =~ /Unable to load module (.*): Module (.*) already loaded./) {
274 debug(ALL, "Module $1 already loaded\n");
275 } elsif ($text =~ /^Disconnected from IRC.*$/) {
276 debug(ERRORS, "ERROR: $bot->{name}: $text");
277 } elsif ($text =~ /^|/) {
278 debug(ERRORS, "ERROR: $bot->{name}: $text");
279 } else {
280 debug(ERRORS, "ERROR: Unexpected: $response");
282 } elsif ($text =~ /^!([[:graph:]]+)\s*(.*)/) {
283 my ($cmd, $text) = ($1, $2);
284 my $hand = $staff; # TODO fix later
285 if ($target =~ /^#/) {
286 foreach my $c (@{$call->{pub}}) {
287 if ($cmd eq $c->{cmd}) {
288 my $proc = $c->{proc};
289 $proc->($bot, $sendnick, $host, $hand, $target, $text);
292 } else {
293 foreach my $c (@{$call->{msg}}) {
294 if ($cmd eq $c->{cmd}) {
295 my $proc = $c->{proc};
296 $proc->($bot, $sendnick, $host, $hand, $text);
300 } else {
301 my $hand = $staff; # TODO fix later
302 if ($target =~ /^#/) {
303 foreach my $c (@{$call->{pubm}}) {
304 my $proc = $c->{proc};
305 $proc->($bot, $sendnick, $host, $hand, $target, $text);
307 } else {
308 foreach my $c (@{$call->{msgm}}) {
309 my $proc = $c->{proc};
310 $proc->($bot, $sendnick, $host, $hand, $text);
314 debug(ALL, "$hostmask $target $text");
315 } elsif($response =~ /^:([^ ]+) NOTICE ([^ ]+) :(.*)\r\n$/i) {
316 my ($hostmask, $target, $text) = ($1, $2, $3);
317 if ($hostmask =~ /([^!]+)!([^@]+@[^@ ]+)/) {
318 my ($sendnick, $host) = ($1, $2);
319 my $hand = $staff; # TODO fix later
320 foreach my $c (@{$call->{notc}}) {
321 # if ($text eq $c->{mask}) { # TODO fix later
322 my $proc = $c->{proc};
323 $proc->($bot, $sendnick, $host, $hand, $text, $target);
324 # }
327 # CTCP replies
328 if ($hostmask ne '*status!znc@znc.in') {
329 if ($text =~ /^(PING|VERSION|TIME|USERINFO) (.*)$/i) {
330 my ($key, $val) = ($1, $2);
331 my $id = SQLite::id("irc", "nick", $sendnick, $expires);
332 SQLite::set("irc", "id", $id, "ctcp".lc($key), $val);
333 SQLite::set("irc", "id", $id, "localtime", time());
337 debug(ALL, "$hostmask NOTICE $target $text");
338 #:portlane.se.quakenet.org NOTICE guava :Highest connection count: 1541 (1540 clients)
339 #:portlane.se.quakenet.org NOTICE guava :on 2 ca 2(4) ft 20(20) tr
340 } elsif($response =~ /^:([^ ]+) MODE ([^ ]+) ([^ ]+)\s*(.*)\r\n$/i) {
341 my ($hostmask, $chan, $change, $targets) = ($1, $2, $3, $4);
342 if ($hostmask =~ /([^!]+)!([^@]+@[^@ ]+)/) {
343 my ($sendnick, $host) = ($1, $2);
344 my $hand = $staff; # TODO fix later
345 foreach my $c (@{$call->{mode}}) {
346 # TODO filter by mask
347 my $proc = $c->{proc};
348 $proc->($bot, $sendnick, $host, $hand, $chan, $change, $targets);
351 debug(ALL, "$hostmask MODE $chan $change $targets");
352 #:guava!guava@guava.guava.ircnow.org MODE guava :+Ci
353 #:ChanServ!services@services.irc.ircnow.org MODE #testing +q jrmu
354 #:jrmu!jrmu@jrmu.staff.ircnow.org MODE #testing +o jrmu
355 #Unexpected bncnow.pl 460: :irc.guava.ircnow.org MODE guava :+o
356 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) JOIN :?(.*)\r\n$/i) {
357 my ($hostmask, $sendnick, $host, $chan) = ($1, $2, $3, $4);
358 my $hand = $staff; # TODO fix later
359 foreach my $c (@{$call->{join}}) {
360 my $proc = $c->{proc};
361 $proc->($bot, $sendnick, $host, $hand, $chan);
363 debug(ALL, "$hostmask JOIN $chan");
364 #:jrmu!jrmu@jrmu.staff.ircnow.org JOIN :#testing
365 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) PART ([^ ]+) :(.*)\r\n$/i) {
366 my ($hostmask, $sendnick, $host, $chan, $text) = ($1, $2, $3, $4, $5);
367 my $hand = $staff; # TODO fix later
368 foreach my $c (@{$call->{part}}) {
369 # if ($text eq $c->{mask}) { # TODO fix later
370 my $proc = $c->{proc};
371 $proc->($bot, $sendnick, $host, $hand, $chan, $text);
372 # }
374 debug(ALL, "$hostmask PART $chan :$text");
375 #:jrmu!jrmu@jrmu.staff.ircnow.org PART #testing :
376 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) KICK (#[^ ]+) ([^ ]+) :(.*)\r\n$/i) {
377 my ($hostmask, $sendnick, $host, $chan, $kicked, $text) = ($1, $2, $3, $4, $5, $6);
378 my $hand = $staff; # TODO fix later
379 foreach my $c (@{$call->{kick}}) {
380 # if ($text eq $c->{mask}) { # TODO fix later
381 my $proc = $c->{proc};
382 $proc->($bot, $sendnick, $host, $hand, $chan, $text);
383 # }
385 debug(ALL, "$hostmask KICK $chan $kicked :$text");
386 #jrmu!jrmu@jrmu.users.undernet.org KICK #ircnow guava :this is a test
387 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) NICK :?(.*)\r\n$/i) {
388 my ($hostmask, $sendnick, $host, $text) = ($1, $2, $3, $4);
389 debug(ALL, "$hostmask NICK $text");
390 #:Fly0nDaWaLL|dal!psybnc@do.not.h4ck.me NICK :nec|dal
391 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) QUIT :(.*)\r\n$/i) {
392 my ($hostmask, $sendnick, $host, $text) = ($1, $2, $3, $4);
393 debug(ALL, "$hostmask QUIT :$text");
394 #:Testah!~sid268081@aa38a510 QUIT :Client closed connection
395 } elsif($response =~ /^NOTICE AUTH :(.*)\r\n$/i) {
396 my ($text) = ($1);
397 debug(ALL, "NOTICE AUTH: $text");
398 #NOTICE AUTH :*** Looking up your hostname
399 #NOTICE AUTH: *** Looking up your hostname
400 #NOTICE AUTH: *** Checking Ident
401 #NOTICE AUTH: *** Got ident response
402 #NOTICE AUTH: *** Found your hostname
403 } elsif ($response =~ /^:([[:graph:]]+) (\d\d\d) $nick.? :?(.*)\r?\n?\r$/i) {
404 my ($server, $code, $text) = ($1, $2, $3);
405 if ($code =~ /^001$/) { # Server Info
406 debug(ERRORS, "connected: $bot->{name}");
407 } elsif ($code =~ /^0\d\d$/) { # Server Info
408 debug(ALL, "$server $code $text");
409 } elsif ($code =~ /^2\d\d$/) { # Server Stats
410 debug(ALL, "$server $code $text");
411 } elsif ($code == 301 && $text =~ /^([-_\|`a-zA-Z0-9]+) :([[:graph:]]+)/) {
412 debug(ALL, "$text");
413 } elsif ($code == 307 && $text =~ /^([-_\|`a-zA-Z0-9]+) (.*)/) {
414 my ($sender, $key) = ($1, "registered");
415 my $val = $2 eq ":is a registered nick" ? "True" : "$2";
416 my $id = SQLite::id("irc", "nick", $sender, $expires);
417 SQLite::set("irc", "id", $id, "identified", $val);
418 debug(ALL, "$key: $val");
419 } elsif ($code == 311 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([^:]+)\s+([^:]+) \* :([^:]*)/) {
420 my ($sender, $key, $val) = ($1, "hostmask", "$1\!$2\@$3");
421 my $id = SQLite::id("irc", "nick", $sender, $expires);
422 SQLite::set("irc", "id", $id, $key, $val);
423 debug(ALL, "$key: $val");
424 } elsif ($code == 312 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([^:]+) :([^:]+)/) {
425 my ($sender, $key, $val) = ($1, "server", $2);
426 my $id = SQLite::id("irc", "nick", $sender, $expires);
427 SQLite::set("irc", "id", $id, $key, $val);
428 debug(ALL, "$key: $val");
429 } elsif ($code == 313 && $text =~ /^([-_\|`a-zA-Z0-9]+) :?(.*)/) {
430 my ($sender, $key, $val) = ($1, "oper", ($2 eq "is an IRC operator" ? "True" : "$2"));
431 my $id = SQLite::id("irc", "nick", $sender, $expires);
432 SQLite::set("irc", "id", $id, $key, $val);
433 debug(ALL, "$key: $val");
434 } elsif ($code == 315 && $text =~ /^([-_\|`a-zA-Z0-9]+) :End of \/?WHO(IS)? list/) {
435 debug(ALL, "End of WHOIS");
436 } elsif ($code == 317 && $text =~ /^([-_\|`a-zA-Z0-9]+) (\d+) (\d+) :?(.*)/) {
437 my ($sender, $idle, $epochtime) = ($1, $2, $3);
438 my $id = SQLite::id("irc", "nick", $sender, $expires);
439 SQLite::set("irc", "id", $id, "idle", $idle);
440 # SQLite::set("irc", "id", $id, "epochtime", time());
441 debug(ALL, "idle: $idle, epochtime: $epochtime");
442 } elsif ($code == 318 && $text =~ /^([-_\|`a-zA-Z0-9]+) :End of \/?WHOIS list/) {
443 debug(ALL, "End of WHOIS");
444 } elsif ($code == 319 && $text =~ /^([-_\|`a-zA-Z0-9]+) :(.*)/) {
445 my ($sender, $key, $val) = ($1, "chans", $2);
446 my $id = SQLite::id("irc", "nick", $sender, $expires);
447 SQLite::set("irc", "id", $id, $key, $val);
448 debug(ALL, "$key: $val");
449 } elsif ($code == 330 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([-_\|`a-zA-Z0-9]+) :?(.*)/) {
450 my ($sender, $key, $val) = ($1, "identified", ($3 eq "is logged in as" ? "True" : $2));
451 my $id = SQLite::id("irc", "nick", $sender, $expires);
452 SQLite::set("irc", "id", $id, $key, $val);
453 debug(ALL, "$key: $val");
454 } elsif ($code == 338 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([0-9a-fA-F:.]+) :actually using host/) {
455 my ($sender, $key, $val) = ($1, "ip", $2);
456 my $id = SQLite::id("irc", "nick", $sender, $expires);
457 SQLite::set("irc", "id", $id, $key, $val);
458 debug(ALL, "$key: $val");
459 #Unexpected: efnet.port80.se 338 jrmu 206.253.167.44 :actually using host
460 } elsif ($code == 378 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is connecting from ([^ ]+)\s*([0-9a-fA-F:.]+)?/) {
461 my ($sender, $key, $val) = ($1, "ip", $3);
462 my $id = SQLite::id("irc", "nick", $sender, $expires);
463 SQLite::set("irc", "id", $id, $key, $val);
464 debug(ALL, "$key: $val");
465 } elsif ($code == 671 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is using a secure connection/) {
466 my ($sender, $key, $val) = ($1, "ssl", "True");
467 my $id = SQLite::id("irc", "nick", $sender, $expires);
468 SQLite::set("irc", "id", $id, $key, $val);
469 debug(ALL, "$key: $val");
470 } elsif ($code =~ /^332$/) { # Topic
471 # print "$text\r\n";
472 } elsif ($code =~ /^333$/) { #
473 # print "$server $text\r\n";
474 #karatkievich.freenode.net 333 #ircnow jrmu!znc@206.253.167.44 1579277253
475 } elsif ($code =~ /^352$/) { # Hostmask
476 #:datapacket.hk.quakenet.org 352 * znc guava.guava.ircnow.org *.quakenet.org guava H :0 guava
477 # print "$server $code $text\r\n";
478 } elsif ($code =~ /^353$/) { # Names
479 # print "$server $code $text\r\n";
480 } elsif ($code =~ /^366$/) { # End of names
481 # print "$server $code $text\r\n";
482 } elsif ($code =~ /^37\d$/) { # MOTD
483 # print "$server $code $text\r\n";
484 } elsif ($code =~ /^381$/) { # IRC Operator Verified
485 # print "IRC Oper Verified\r\n";
486 } elsif ($code =~ /^401$/) { # IRC Operator Verified
487 # print "IRC Oper Verified\r\n";
488 } elsif ($code =~ /^403$/) { # No such channel
489 # debug(ERRORS, "$text");
490 } elsif ($code =~ /^422$/) { # MOTD missing
491 # print "$server $code $text\r\n";
492 } elsif ($code =~ /^396$/) { # Display hostname
493 # print "$server $code $text\r\n";
494 #Unexpected bncnow.pl 454: irc.guava.ircnow.org 396 guava.guava.ircnow.org :is your displayed hostname now
495 } elsif ($code =~ /^464$/) { # Invalid password for oper
496 foreach my $chan (@teamchans) {
497 putserv($bot, "PRIVMSG $chan :$nick oper password failed; the bot will be unable to view uncloaked IP addresses");
499 } elsif ($code =~ /^477$/) { # Can't join channel
500 foreach my $chan (@teamchans) {
501 putserv($bot, "PRIVMSG $chan :ERROR: $nick on $server: $text");
503 } elsif ($code == 716 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is in \+g mode \(server-side ignore.\)/) {
504 debug(ALL, "$text");
505 } else {
506 debug(ERRORS, "Unexpected bncnow.pl 454: $server $code $text");
508 } else {
509 debug(ERRORS, "Unexpected bncnow.pl 460: $response");
514 sub putserv {
515 my( $bot, $text )=@_;
516 my $socket = $bot->{sock};
517 if ($text =~ /^([^:]+):([[:ascii:]]*)$/m) {
518 my ($cmd, $line) = ($1, $2);
519 my @lines = split /\r?\n/m, $line;
520 foreach my $l (@lines) {
521 print $socket "$cmd:$l\r\n";
523 } else {
524 print $socket "$text\r\n";
528 sub putservlocalnet {
529 my( $bot, $text )=@_;
530 my $botlocalnet;
531 foreach my $b (@bots) {
532 if($b->{name} =~ /^$localnet$/i) {
533 $botlocalnet = $b;
534 last;
537 putserv($botlocalnet, $text);
540 sub whois {
541 my( $socket, $target )=@_;
542 print $socket "WHOIS $target $target\r\n";
545 sub ctcp {
546 my( $socket, $target )=@_;
547 # print $socket "PRIVMSG $target :".chr(01)."CLIENTINFO".chr(01)."\r\n";
548 # print $socket "PRIVMSG $target :".chr(01)."FINGER".chr(01)."\r\n";
549 # print $socket "PRIVMSG $target :".chr(01)."SOURCE".chr(01)."\r\n";
550 print $socket "PRIVMSG $target :".chr(01)."TIME".chr(01)."\r\n";
551 # print $socket "PRIVMSG $target :".chr(01)."USERINFO".chr(01)."\r\n";
552 print $socket "PRIVMSG $target :".chr(01)."VERSION".chr(01)."\r\n";
553 # print $socket "PRIVMSG $target :".chr(01)."PING".chr(01)."\r\n";
556 sub cbind {
557 my ($type, $flags, $cmd, $proc) = @_;
558 if ($type eq "pub") {
559 push(@{$call->{pub}}, {cmd => $cmd, proc => $proc});
560 } elsif ($type eq "msg") {
561 push(@{$call->{msg}}, {cmd => $cmd, proc => $proc});
562 } elsif ($type eq "notc") {
563 push(@{$call->{notc}}, {mask => $cmd, proc => $proc});
564 } elsif ($type eq "mode") {
565 push(@{$call->{mode}}, {mask => $cmd, proc => $proc});
566 } elsif ($type eq "join") {
567 push(@{$call->{join}}, {mask => $cmd, proc => $proc});
568 } elsif ($type eq "partcall") {
569 push(@{$call->{part}}, {mask => $cmd, proc => $proc});
570 } elsif ($type eq "pubm") {
571 push(@{$call->{pubm}}, {mask => $cmd, proc => $proc});
572 } elsif ($type eq "msgm") {
573 push(@{$call->{msgm}}, {mask => $cmd, proc => $proc});
577 sub debug {
578 my ($level, $msg) = @_;
579 if ($verbose >= $level) { print "$msg\n"; }
582 sub isstaff {
583 my( $bot, $nick ) = @_;
584 if( !( $bot->{name} =~ /^$localnet$/i ) ) {
585 return 0;
587 foreach( @stafflist ) {
588 if( $nick eq $_ ) {
589 return 1;
592 return 0;