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 # Send email using sendmail
70 sub mail {
71 my( $from, $to, $fromname, $subject, $body )=@_;
72 my $msg = <<"EOF";
73 From: $from
74 To: $to
75 Subject: $subject
76 MIME-Version: 1.0
77 Content-Type: text/plain; charset=us-ascii
78 Content-Disposition: inline
80 $body
81 EOF
82 open(my $fh, "| /usr/sbin/sendmail -tv -F '$fromname' -f $from") or die "Could not send mail $!";
83 print $fh $msg;
84 close $fh;
85 return "true";
86 }
88 # Configuration variables will be stored in key => value pairs
89 our %conf;
91 foreach my $line (readarray($confpath)) {
92 if ($line =~ /^#/ or $line =~ /^\s*$/) { # skip comments and whitespace
93 next;
94 } elsif ($line =~ /^([-_a-zA-Z0-9]+)\s*=\s*([[:print:]]+)$/) {
95 $conf{$1} = $2;
96 } else {
97 die "ERROR: botnow.conf format invalid: $line";
98 }
99 }
101 # Name of local network
102 $conf{localnet} = $conf{localnet} or die "ERROR: botnow.conf: localnet";
104 # Internal IPv4 address and plaintext port
105 $conf{host} = $conf{host} || "127.0.0.1";
106 $conf{port} = $conf{port} || 1337;
108 # Bouncer hostname
109 chomp($conf{hostname} = $conf{hostname} || `hostname`);
111 # Mail hostname
112 chomp($conf{mailhostname} = $conf{mailhostname} || `hostname`);
114 # Webpanel URL
115 chomp($conf{webpanel} = $conf{webpanel} || "https://bnc.$conf{hostname}");
117 # Webmail URL
118 chomp($conf{webmail} = $conf{webmail} || "https://mail.$conf{hostname}");
120 # External IPv4 address, plaintext and ssl port
121 $conf{ip4} = $conf{ip4} or die "ERROR: botnow.conf: ip4";
122 $conf{ip6} = $conf{ip6} or die "ERROR: botnow.conf: ip6";
123 $conf{ip6subnet} = $conf{ip6subnet} or die "ERROR: botnow.conf: ip6subnet";
124 $conf{ip6prefix} = $conf{ip6prefix} or die "ERROR: botnow.conf: ip6prefix";
125 $conf{plainport} = $conf{plainport} || 1337;
126 $conf{sslport} = $conf{sslport} || 31337;
127 $conf{imapport} = $conf{imapport} || 143;
128 $conf{smtpport} = $conf{smtpport} || 587;
130 # Nick and password of bot -- Make sure to add to oper block
131 $conf{nick} = $conf{nick} or die "ERROR: botnow.conf: nick";
132 $conf{pass} = $conf{pass} or die "ERROR: botnow.conf: pass";
134 # Comma-separated list of channels for requesting bouncers
135 $conf{chans} = $conf{chans} or die "ERROR: botnow.conf: chans";
137 my @networks;
138 if (defined($conf{networks})) {
139 @networks = split(/\s+/, $conf{networks});
142 # Mail from address
143 if (!defined($conf{mailname})) {
144 if ($conf{mailfrom} =~ /^([^@]+)@/) {
145 $conf{mailname} = $1 or die "ERROR: botnow.conf mailname";
149 # Terms of Service
150 $conf{terms} = $conf{terms} or die "ERROR: botnow.conf terms";
152 # Number of words in password
153 $conf{passlength} = $conf{passlength} || 3;
155 # Time before captcha expires
156 $conf{expires} = $conf{expires} || 1800;
158 # NSD zone dir
159 $conf{zonedir} = $conf{zonedir} || "/var/nsd/zones/master/";
161 # ZNC install directory
162 $conf{zncdir} = $conf{zncdir} || "/home/znc/home/znc";
164 # Network Interface Config File
165 $conf{hostnameif} = $conf{hostnameif} || "/etc/hostname.vio0";
167 # Verbosity: 0 (no errors), 1 (errors), 2 (warnings), 3 (diagnostic)
168 use constant {
169 NONE => 0,
170 ERRORS => 1,
171 WARNINGS => 2,
172 ALL => 3,
173 };
175 $conf{verbose} = $conf{verbose} || ERRORS;
177 if(defined($conf{die})) { die $conf{die}; }
179 my @modules;
180 if (defined($conf{modules})) {
181 @modules = split(/\s+/, $conf{modules});
183 use lib './';
184 foreach my $mod (@modules) {
185 require "$mod.pm";
187 foreach my $mod (@modules) {
188 my $init = "${mod}::init";
189 $init->();
192 my @bots;
193 my @chans = split /[,\s]+/m, $conf{chans};
194 my @teamchans;
195 if (defined($conf{teamchans})) { @teamchans = split /[,\s]+/m, $conf{teamchans}; }
196 my $call;
197 my $nick = $conf{nick};
198 my $host = $conf{host};
199 my $port = $conf{port};
200 my $pass = $conf{pass};
201 my $localnet = $conf{localnet};
202 my $staff = $conf{staff};
203 my @stafflist = split(/ /,$staff);
204 my $verbose = $conf{verbose};
205 my $expires = $conf{expires};
207 # Unveil limits files and directories that botnow can access
208 unveil("./", "r") or die "Unable to unveil $!";
209 unveil("$confpath", "r") or die "Unable to unveil $!";
210 unveil("$backupspath", "rwc") or die "Unable to unveil $!";
211 unveil("/usr/sbin/sendmail", "rx") or die "Unable to unveil $!";
212 unveil("/bin/sh", "rx") or die "Unable to unveil $!";
213 unveil() or die "Unable to lock unveil $!";
215 # Pledge limits botnow's syscalls
216 # dns and inet for sockets, proc and exec for figlet
217 # rpath for reading file, wpath for writing file, cpath for creating path
218 # flock, fattr for sqlite
219 pledge( qw(stdio rpath wpath cpath inet dns proc exec flock fattr) ) or die "Unable to pledge: $!";
221 # Writes text to the network socket
222 sub putserv {
223 my( $bot, $text )=@_;
224 my $socket = $bot->{sock};
225 # If a multiline command, write multiple times; else write once
226 if ($text =~ /^([^:]+):([[:ascii:]]*)$/m) {
227 my ($cmd, $line) = ($1, $2);
228 my @lines = split /\r?\n/m, $line;
229 foreach my $l (@lines) {
230 print $socket "$cmd:$l\r\n";
232 } else {
233 print $socket "$text\r\n";
237 # Writes text to the local network socket
238 sub putservlocalnet {
239 my( $bot, $text )=@_;
240 foreach my $b (@bots) {
241 if($b->{name} =~ /^$localnet$/i) {
242 putserv($b, $text);
243 return;
248 sub whois {
249 my( $bot, $target )=@_;
250 putserv($bot, "WHOIS $target $target\r\n");
253 sub ctcp {
254 my( $bot, $target )=@_;
255 #putserv($bot, "PRIVMSG $target :".chr(01)."CLIENTINFO".chr(01)."\r\n");
256 #putserv($bot, "PRIVMSG $target :".chr(01)."FINGER".chr(01)."\r\n");
257 #putserv($bot, "PRIVMSG $target :".chr(01)."SOURCE".chr(01)."\r\n");
258 putserv($bot, "PRIVMSG $target :".chr(01)."TIME".chr(01)."\r\n");
259 #putserv($bot, "PRIVMSG $target :".chr(01)."USERINFO".chr(01)."\r\n");
260 putserv($bot, "PRIVMSG $target :".chr(01)."VERSION".chr(01)."\r\n");
261 #putserv($bot, "PRIVMSG $target :".chr(01)."PING".chr(01)."\r\n");
264 sub debug {
265 my ($level, $msg) = @_;
266 if ($verbose >= $level) { print "$msg\n"; }
269 sub isstaff {
270 my( $bot, $nick ) = @_;
271 if( !( $bot->{name} =~ /^$localnet$/i ) ) {
272 return 0;
274 foreach( @stafflist ) {
275 if( $nick eq $_ ) {
276 return 1;
279 return 0;
283 # Create sockets
284 my $sel = IO::Select->new( );
285 foreach my $network (@networks) {
286 my $socket = IO::Socket::INET->new(PeerAddr=>$host, PeerPort=>$port, Proto=>'tcp', Timeout=>'300') || print "Failed to establish connection\n";
287 $sel->add($socket);
288 my $bot = {("sock" => $socket), ("name" => $network)};
289 push(@bots, $bot);
290 putserv($bot, "NICK $nick");
291 putserv($bot, "USER $nick * * :$nick");
294 while(my @ready = $sel->can_read) {
295 my ($bot, $response, $sender, $val);
296 foreach my $socket (@ready) {
297 foreach my $b (@bots) {
298 if($socket == $b->{sock}) {
299 $bot = $b;
300 last;
303 ### TODO: Reconnect if znc crashes
304 if (!defined($response = <$socket>)) {
305 debug(ERRORS, "ERROR ".$bot->{name}." has no response:");
306 next;
308 if ($response =~ /^PING :(.*)\r\n$/i) {
309 putserv($bot, "PONG :$1");
310 } elsif ($response =~ /^:irc.znc.in (.*) (.*) :(.*)\r\n$/) {
311 my ($type, $target, $text) = ($1, $2, $3);
312 if ($type eq "464" && $target =~ /^$nick.?$/ && $text eq "Password required") {
313 putserv($bot, "PASS $nick/$bot->{name}:$pass");
314 if ($bot->{name} =~ /^$localnet$/i) {
315 putserv($bot, "OPER $nick $pass");
316 putserv($bot, "PRIVMSG *status :LoadMod --type=user controlpanel");
317 putserv($bot, "PRIVMSG *controlpanel :get Admin $nick");
318 putserv($bot, "PRIVMSG *controlpanel :get Nick cloneuser");
319 foreach my $chan (@teamchans) {
320 putserv($bot, "JOIN $chan");
323 if ($bot->{name} !~ /^$localnet$/i) {
324 foreach my $chan (@chans) {
325 putserv($bot, "JOIN $chan");
328 } elsif ($type eq "464" && $target =~ /^$nick.?$/ && $text eq "Invalid Password") {
329 die "ERROR: Wrong Username/Password: $bot->{name}";
330 } else {
331 debug(ALL, "Debug type: $type, target: $target, text: $text");
333 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) PRIVMSG ([^ ]+) :(.*)\r\n$/i) {
334 my ($hostmask, $sendnick, $host, $target, $text) = ($1, $2, $3, $4, $5);
335 if ($hostmask eq '*status!znc@znc.in' && $target =~ /^$nick.?$/) {
336 if ($text =~ /Network ([[:ascii:]]+) doesn't exist./) {
337 debug(ERRORS, "ERROR: nonexistent: $1");
338 } elsif ($text eq "You are currently disconnected from IRC. Use 'connect' to reconnect.") {
339 debug(ERRORS, "ERROR: disconnected: $bot->{name}");
340 } elsif ($text =~ /Unable to load module (.*): Module (.*) already loaded./) {
341 debug(ALL, "Module $1 already loaded\n");
342 } elsif ($text =~ /^Disconnected from IRC.*$/) {
343 debug(ERRORS, "ERROR: $bot->{name}: $text");
344 } elsif ($text =~ /^|/) {
345 debug(ERRORS, "ERROR: $bot->{name}: $text");
346 } else {
347 debug(ERRORS, "ERROR: Unexpected: $response");
349 } elsif ($text =~ /^!([[:graph:]]+)\s*(.*)/) {
350 my ($cmd, $text) = ($1, $2);
351 my $hand = $staff; # TODO fix later
352 if ($target =~ /^#/) {
353 foreach my $c (@{$call->{pub}}) {
354 if ($cmd eq $c->{cmd}) {
355 my $proc = $c->{proc};
356 $proc->($bot, $sendnick, $host, $hand, $target, $text);
359 } else {
360 foreach my $c (@{$call->{msg}}) {
361 if ($cmd eq $c->{cmd}) {
362 my $proc = $c->{proc};
363 $proc->($bot, $sendnick, $host, $hand, $text);
367 } else {
368 my $hand = $staff; # TODO fix later
369 if ($target =~ /^#/) {
370 foreach my $c (@{$call->{pubm}}) {
371 my $proc = $c->{proc};
372 $proc->($bot, $sendnick, $host, $hand, $target, $text);
374 } else {
375 foreach my $c (@{$call->{msgm}}) {
376 my $proc = $c->{proc};
377 $proc->($bot, $sendnick, $host, $hand, $text);
381 debug(ALL, "$hostmask $target $text");
382 } elsif($response =~ /^:([^ ]+) NOTICE ([^ ]+) :(.*)\r\n$/i) {
383 my ($hostmask, $target, $text) = ($1, $2, $3);
384 if ($hostmask =~ /([^!]+)!([^@]+@[^@ ]+)/) {
385 my ($sendnick, $host) = ($1, $2);
386 my $hand = $staff; # TODO fix later
387 foreach my $c (@{$call->{notc}}) {
388 # if ($text eq $c->{mask}) { # TODO fix later
389 my $proc = $c->{proc};
390 $proc->($bot, $sendnick, $host, $hand, $text, $target);
391 # }
394 # CTCP replies
395 if ($hostmask ne '*status!znc@znc.in') {
396 if ($text =~ /^(PING|VERSION|TIME|USERINFO) (.*)$/i) {
397 my ($key, $val) = ($1, $2);
398 my $id = SQLite::id("irc", "nick", $sendnick, $expires);
399 SQLite::set("irc", "id", $id, "ctcp".lc($key), $val);
400 SQLite::set("irc", "id", $id, "localtime", time());
404 debug(ALL, "$hostmask NOTICE $target $text");
405 #:portlane.se.quakenet.org NOTICE guava :Highest connection count: 1541 (1540 clients)
406 #:portlane.se.quakenet.org NOTICE guava :on 2 ca 2(4) ft 20(20) tr
407 } elsif($response =~ /^:([^ ]+) MODE ([^ ]+) ([^ ]+)\s*(.*)\r\n$/i) {
408 my ($hostmask, $chan, $change, $targets) = ($1, $2, $3, $4);
409 if ($hostmask =~ /([^!]+)!([^@]+@[^@ ]+)/) {
410 my ($sendnick, $host) = ($1, $2);
411 my $hand = $staff; # TODO fix later
412 foreach my $c (@{$call->{mode}}) {
413 # TODO filter by mask
414 my $proc = $c->{proc};
415 $proc->($bot, $sendnick, $host, $hand, $chan, $change, $targets);
418 debug(ALL, "$hostmask MODE $chan $change $targets");
419 #:guava!guava@guava.guava.ircnow.org MODE guava :+Ci
420 #:ChanServ!services@services.irc.ircnow.org MODE #testing +q jrmu
421 #:jrmu!jrmu@jrmu.staff.ircnow.org MODE #testing +o jrmu
422 #Unexpected bncnow.pl 460: :irc.guava.ircnow.org MODE guava :+o
423 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) JOIN :?(.*)\r\n$/i) {
424 my ($hostmask, $sendnick, $host, $chan) = ($1, $2, $3, $4);
425 my $hand = $staff; # TODO fix later
426 foreach my $c (@{$call->{join}}) {
427 my $proc = $c->{proc};
428 $proc->($bot, $sendnick, $host, $hand, $chan);
430 debug(ALL, "$hostmask JOIN $chan");
431 #:jrmu!jrmu@jrmu.staff.ircnow.org JOIN :#testing
432 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) PART ([^ ]+) :(.*)\r\n$/i) {
433 my ($hostmask, $sendnick, $host, $chan, $text) = ($1, $2, $3, $4, $5);
434 my $hand = $staff; # TODO fix later
435 foreach my $c (@{$call->{part}}) {
436 # if ($text eq $c->{mask}) { # TODO fix later
437 my $proc = $c->{proc};
438 $proc->($bot, $sendnick, $host, $hand, $chan, $text);
439 # }
441 debug(ALL, "$hostmask PART $chan :$text");
442 #:jrmu!jrmu@jrmu.staff.ircnow.org PART #testing :
443 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) KICK (#[^ ]+) ([^ ]+) :(.*)\r\n$/i) {
444 my ($hostmask, $sendnick, $host, $chan, $kicked, $text) = ($1, $2, $3, $4, $5, $6);
445 my $hand = $staff; # TODO fix later
446 foreach my $c (@{$call->{kick}}) {
447 # if ($text eq $c->{mask}) { # TODO fix later
448 my $proc = $c->{proc};
449 $proc->($bot, $sendnick, $host, $hand, $chan, $text);
450 # }
452 debug(ALL, "$hostmask KICK $chan $kicked :$text");
453 #jrmu!jrmu@jrmu.users.undernet.org KICK #ircnow guava :this is a test
454 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) NICK :?(.*)\r\n$/i) {
455 my ($hostmask, $sendnick, $host, $text) = ($1, $2, $3, $4);
456 debug(ALL, "$hostmask NICK $text");
457 #:Fly0nDaWaLL|dal!psybnc@do.not.h4ck.me NICK :nec|dal
458 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) QUIT :(.*)\r\n$/i) {
459 my ($hostmask, $sendnick, $host, $text) = ($1, $2, $3, $4);
460 debug(ALL, "$hostmask QUIT :$text");
461 #:Testah!~sid268081@aa38a510 QUIT :Client closed connection
462 } elsif($response =~ /^NOTICE AUTH :(.*)\r\n$/i) {
463 my ($text) = ($1);
464 debug(ALL, "NOTICE AUTH: $text");
465 #NOTICE AUTH :*** Looking up your hostname
466 #NOTICE AUTH: *** Looking up your hostname
467 #NOTICE AUTH: *** Checking Ident
468 #NOTICE AUTH: *** Got ident response
469 #NOTICE AUTH: *** Found your hostname
470 } elsif ($response =~ /^:([[:graph:]]+) (\d\d\d) $nick.? :?(.*)\r?\n?\r$/i) {
471 my ($server, $code, $text) = ($1, $2, $3);
472 if ($code =~ /^001$/) { # Server Info
473 debug(ERRORS, "connected: $bot->{name}");
474 } elsif ($code =~ /^0\d\d$/) { # Server Info
475 debug(ALL, "$server $code $text");
476 } elsif ($code =~ /^2\d\d$/) { # Server Stats
477 debug(ALL, "$server $code $text");
478 } elsif ($code == 301 && $text =~ /^([-_\|`a-zA-Z0-9]+) :([[:graph:]]+)/) {
479 debug(ALL, "$text");
480 } elsif ($code == 307 && $text =~ /^([-_\|`a-zA-Z0-9]+) (.*)/) {
481 my ($sender, $key) = ($1, "registered");
482 $val = $2 eq ":is a registered nick" ? "True" : "$2";
483 my $id = SQLite::id("irc", "nick", $sender, $expires);
484 SQLite::set("irc", "id", $id, "identified", $val);
485 debug(ALL, "$key: $val");
486 } elsif ($code == 311 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([^:]+)\s+([^:]+) \* :([^:]*)/) {
487 my ($sender, $key, $val) = ($1, "hostmask", "$1\!$2\@$3");
488 my $id = SQLite::id("irc", "nick", $sender, $expires);
489 SQLite::set("irc", "id", $id, $key, $val);
490 debug(ALL, "$key: $val");
491 } elsif ($code == 312 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([^:]+) :([^:]+)/) {
492 my ($sender, $key, $val) = ($1, "server", $2);
493 my $id = SQLite::id("irc", "nick", $sender, $expires);
494 SQLite::set("irc", "id", $id, $key, $val);
495 debug(ALL, "$key: $val");
496 } elsif ($code == 313 && $text =~ /^([-_\|`a-zA-Z0-9]+) :?(.*)/) {
497 my ($sender, $key, $val) = ($1, "oper", ($2 eq "is an IRC operator" ? "True" : "$2"));
498 my $id = SQLite::id("irc", "nick", $sender, $expires);
499 SQLite::set("irc", "id", $id, $key, $val);
500 debug(ALL, "$key: $val");
501 } elsif ($code == 315 && $text =~ /^([-_\|`a-zA-Z0-9]+) :End of \/?WHO(IS)? list/) {
502 debug(ALL, "End of WHOIS");
503 } elsif ($code == 317 && $text =~ /^([-_\|`a-zA-Z0-9]+) (\d+) (\d+) :?(.*)/) {
504 ($sender, my $idle, my $epochtime) = ($1, $2, $3);
505 my $id = SQLite::id("irc", "nick", $sender, $expires);
506 SQLite::set("irc", "id", $id, "idle", $idle);
507 # SQLite::set("irc", "id", $id, "epochtime", time());
508 debug(ALL, "idle: $idle, epochtime: $epochtime");
509 } elsif ($code == 318 && $text =~ /^([-_\|`a-zA-Z0-9]+) :End of \/?WHOIS list/) {
510 debug(ALL, "End of WHOIS");
511 } elsif ($code == 319 && $text =~ /^([-_\|`a-zA-Z0-9]+) :(.*)/) {
512 my ($sender, $key, $val) = ($1, "chans", $2);
513 my $id = SQLite::id("irc", "nick", $sender, $expires);
514 SQLite::set("irc", "id", $id, $key, $val);
515 debug(ALL, "$key: $val");
516 } elsif ($code == 330 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([-_\|`a-zA-Z0-9]+) :?(.*)/) {
517 my ($sender, $key, $val) = ($1, "identified", ($3 eq "is logged in as" ? "True" : $2));
518 my $id = SQLite::id("irc", "nick", $sender, $expires);
519 SQLite::set("irc", "id", $id, $key, $val);
520 debug(ALL, "$key: $val");
521 } elsif ($code == 338 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([0-9a-fA-F:.]+) :actually using host/) {
522 my ($sender, $key, $val) = ($1, "ip", $2);
523 my $id = SQLite::id("irc", "nick", $sender, $expires);
524 SQLite::set("irc", "id", $id, $key, $val);
525 debug(ALL, "$key: $val");
526 #Unexpected: efnet.port80.se 338 jrmu 206.253.167.44 :actually using host
527 } elsif ($code == 378 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is connecting from ([^ ]+)\s*([0-9a-fA-F:.]+)?/) {
528 my ($sender, $key, $val) = ($1, "ip", $3);
529 my $id = SQLite::id("irc", "nick", $sender, $expires);
530 SQLite::set("irc", "id", $id, $key, $val);
531 debug(ALL, "$key: $val");
532 } elsif ($code == 671 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is using a secure connection/) {
533 my ($sender, $key, $val) = ($1, "ssl", "True");
534 my $id = SQLite::id("irc", "nick", $sender, $expires);
535 SQLite::set("irc", "id", $id, $key, $val);
536 debug(ALL, "$key: $val");
537 } elsif ($code =~ /^332$/) { # Topic
538 # print "$text\r\n";
539 } elsif ($code =~ /^333$/) { #
540 # print "$server $text\r\n";
541 #karatkievich.freenode.net 333 #ircnow jrmu!znc@206.253.167.44 1579277253
542 } elsif ($code =~ /^352$/) { # Hostmask
543 #:datapacket.hk.quakenet.org 352 * znc guava.guava.ircnow.org *.quakenet.org guava H :0 guava
544 # print "$server $code $text\r\n";
545 } elsif ($code =~ /^353$/) { # Names
546 # print "$server $code $text\r\n";
547 } elsif ($code =~ /^366$/) { # End of names
548 # print "$server $code $text\r\n";
549 } elsif ($code =~ /^37\d$/) { # MOTD
550 # print "$server $code $text\r\n";
551 } elsif ($code =~ /^381$/) { # IRC Operator Verified
552 # print "IRC Oper Verified\r\n";
553 } elsif ($code =~ /^401$/) { # IRC Operator Verified
554 # print "IRC Oper Verified\r\n";
555 } elsif ($code =~ /^403$/) { # No such channel
556 # debug(ERRORS, "$text");
557 } elsif ($code =~ /^422$/) { # MOTD missing
558 # print "$server $code $text\r\n";
559 } elsif ($code =~ /^396$/) { # Display hostname
560 # print "$server $code $text\r\n";
561 #Unexpected bncnow.pl 454: irc.guava.ircnow.org 396 guava.guava.ircnow.org :is your displayed hostname now
562 } elsif ($code =~ /^464$/) { # Invalid password for oper
563 foreach my $chan (@teamchans) {
564 putserv($bot, "PRIVMSG $chan :$nick oper password failed; the bot will be unable to view uncloaked IP addresses");
566 } elsif ($code =~ /^477$/) { # Can't join channel
567 foreach my $chan (@teamchans) {
568 putserv($bot, "PRIVMSG $chan :ERROR: $nick on $server: $text");
570 } elsif ($code == 716 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is in \+g mode \(server-side ignore.\)/) {
571 debug(ALL, "$text");
572 } else {
573 debug(ERRORS, "Unexpected bncnow.pl 454: $server $code $text");
575 } else {
576 debug(ERRORS, "Unexpected bncnow.pl 460: $response");
581 sub cbind {
582 my ($type, $flags, $cmd, $proc) = @_;
583 if ($type eq "pub") {
584 push(@{$call->{pub}}, {cmd => $cmd, proc => $proc});
585 } elsif ($type eq "msg") {
586 push(@{$call->{msg}}, {cmd => $cmd, proc => $proc});
587 } elsif ($type eq "notc") {
588 push(@{$call->{notc}}, {mask => $cmd, proc => $proc});
589 } elsif ($type eq "mode") {
590 push(@{$call->{mode}}, {mask => $cmd, proc => $proc});
591 } elsif ($type eq "join") {
592 push(@{$call->{join}}, {mask => $cmd, proc => $proc});
593 } elsif ($type eq "partcall") {
594 push(@{$call->{part}}, {mask => $cmd, proc => $proc});
595 } elsif ($type eq "pubm") {
596 push(@{$call->{pubm}}, {mask => $cmd, proc => $proc});
597 } elsif ($type eq "msgm") {
598 push(@{$call->{msgm}}, {mask => $cmd, proc => $proc});