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