10 use File::Copy qw(copy);
13 # Path to configuration file
14 my $confpath = "botnow.conf";
15 my $backupspath = "/home/botnow/backups/";
17 # Returns date in YYYYMMDD format
19 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
20 my $localtime = sprintf("%04d%02d%02d", $year+1900, $mon+1, $mday);
24 # Read from filename and return array of lines without trailing newlines
27 open(my $fh, '<', $filename) or die "Could not read file '$filename' $!";
28 chomp(my @lines = <$fh>);
33 # Read from filename and return as string
36 open(my $fh, '<', $filename) or die "Could not read file '$filename' $!";
37 my $str = do { local $/; <$fh> };
42 # Write str to filename
44 my ($filename, $str) = @_;
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";
52 # Append str to filename
54 my ($filename, $str) = @_;
55 open(my $fh, '>>', "$filename") or die "Could not append to $filename";
60 # Returns timestamp in "Day MM DD HH:MM:SS" format
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);
70 my( $from, $to, $fromname, $subject, $body )=@_;
76 Content-Type: text/plain; charset=us-ascii
77 Content-Disposition: inline
81 open(my $fh, "| /usr/sbin/sendmail -tv -F '$fromname' -f $from") or die "Could not send mail $!";
87 # Configuration variables will be stored in key => value pairs
90 foreach my $line (readarray($confpath)) {
91 if ($line =~ /^#/ or $line =~ /^\s*$/) { # skip comments and whitespace
93 } elsif ($line =~ /^([-_a-zA-Z0-9]+)\s*=\s*([[:print:]]+)$/) {
96 die "ERROR: botnow.conf format invalid: $line";
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;
108 chomp($conf{hostname} = $conf{hostname} || `hostname`);
111 chomp($conf{mailhostname} = $conf{mailhostname} || `hostname`);
114 chomp($conf{webpanel} = $conf{webpanel} || "https://bnc.$conf{hostname}");
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";
137 if (defined($conf{networks})) {
138 @networks = split(/\s+/, $conf{networks});
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;
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)
173 $conf{verbose} = $conf{verbose} || ERRORS;
175 if(defined($conf{die})) { die $conf{die}; }
178 if (defined($conf{modules})) {
179 @modules = split(/\s+/, $conf{modules});
182 foreach my $mod (@modules) {
185 foreach my $mod (@modules) {
186 my $init = "${mod}::init";
191 my @chans = split /[,\s]+/m, $conf{chans};
193 if (defined($conf{teamchans})) { @teamchans = split /[,\s]+/m, $conf{teamchans}; }
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() or die "Unable to lock unveil $!";
210 #dns and inet for sockets, proc and exec for figlet
211 #rpath for reading file, wpath for writing file, cpath for creating path
212 #flock, fattr for sqlite
213 pledge( qw(stdio rpath wpath cpath inet dns proc exec flock fattr) ) or die "Unable to pledge: $!";
216 my $sel = IO::Select->new( );
217 foreach my $network (@networks) {
218 my $socket = IO::Socket::INET->new(PeerAddr=>$host, PeerPort=>$port, Proto=>'tcp', Timeout=>'300') || print "Failed to establish connection\n";
220 my $bot = {("sock" => $socket), ("name" => $network)};
222 putserv($bot, "NICK $nick");
223 putserv($bot, "USER $nick * * :$nick");
226 while(my @ready = $sel->can_read) {
227 my ($bot, $response);
229 foreach my $socket (@ready) {
230 foreach my $b (@bots) {
231 if($socket == $b->{sock}) {
236 if (!defined($response = <$socket>)) {
237 debug(ERRORS, "ERROR ".$bot->{name}." has no response:");
240 if ($response =~ /^PING :(.*)\r\n$/i) {
241 putserv($bot, "PONG :$1");
242 } elsif ($response =~ /^:irc.znc.in (.*) (.*) :(.*)\r\n$/) {
243 my ($type, $target, $text) = ($1, $2, $3);
244 if ($type eq "464" && $target =~ /^$nick.?$/ && $text eq "Password required") {
245 putserv($bot, "PASS $nick/$bot->{name}:$pass");
246 if ($bot->{name} =~ /^$localnet$/i) {
247 putserv($bot, "OPER $nick $pass");
248 putserv($bot, "PRIVMSG *status :LoadMod --type=user controlpanel");
249 putserv($bot, "PRIVMSG *controlpanel :get Admin $nick");
250 putserv($bot, "PRIVMSG *controlpanel :get Nick cloneuser");
251 foreach my $chan (@teamchans) {
252 putserv($bot, "JOIN $chan");
255 if ($bot->{name} !~ /^$localnet$/i) {
256 foreach my $chan (@chans) {
257 putserv($bot, "JOIN $chan");
260 } elsif ($type eq "464" && $target =~ /^$nick.?$/ && $text eq "Invalid Password") {
261 die "ERROR: Wrong Username/Password: $bot->{name}";
263 debug(ALL, "Debug type: $type, target: $target, text: $text");
265 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) PRIVMSG ([^ ]+) :(.*)\r\n$/i) {
266 my ($hostmask, $sendnick, $host, $target, $text) = ($1, $2, $3, $4, $5);
267 if ($hostmask eq '*status!znc@znc.in' && $target =~ /^$nick.?$/) {
268 if ($text =~ /Network ([[:ascii:]]+) doesn't exist./) {
269 debug(ERRORS, "ERROR: nonexistent: $1");
270 } elsif ($text eq "You are currently disconnected from IRC. Use 'connect' to reconnect.") {
271 debug(ERRORS, "ERROR: disconnected: $bot->{name}");
272 } elsif ($text =~ /Unable to load module (.*): Module (.*) already loaded./) {
273 debug(ALL, "Module $1 already loaded\n");
274 } elsif ($text =~ /^Disconnected from IRC.*$/) {
275 debug(ERRORS, "ERROR: $bot->{name}: $text");
276 } elsif ($text =~ /^|/) {
277 debug(ERRORS, "ERROR: $bot->{name}: $text");
279 debug(ERRORS, "ERROR: Unexpected: $response");
281 } elsif ($text =~ /^!([[:graph:]]+)\s*(.*)/) {
282 my ($cmd, $text) = ($1, $2);
283 my $hand = $staff; # TODO fix later
284 if ($target =~ /^#/) {
285 foreach my $c (@{$call->{pub}}) {
286 if ($cmd eq $c->{cmd}) {
287 my $proc = $c->{proc};
288 $proc->($bot, $sendnick, $host, $hand, $target, $text);
292 foreach my $c (@{$call->{msg}}) {
293 if ($cmd eq $c->{cmd}) {
294 my $proc = $c->{proc};
295 $proc->($bot, $sendnick, $host, $hand, $text);
300 my $hand = $staff; # TODO fix later
301 if ($target =~ /^#/) {
302 foreach my $c (@{$call->{pubm}}) {
303 my $proc = $c->{proc};
304 $proc->($bot, $sendnick, $host, $hand, $target, $text);
307 foreach my $c (@{$call->{msgm}}) {
308 my $proc = $c->{proc};
309 $proc->($bot, $sendnick, $host, $hand, $text);
313 debug(ALL, "$hostmask $target $text");
314 } elsif($response =~ /^:([^ ]+) NOTICE ([^ ]+) :(.*)\r\n$/i) {
315 my ($hostmask, $target, $text) = ($1, $2, $3);
316 if ($hostmask =~ /([^!]+)!([^@]+@[^@ ]+)/) {
317 my ($sendnick, $host) = ($1, $2);
318 my $hand = $staff; # TODO fix later
319 foreach my $c (@{$call->{notc}}) {
320 # if ($text eq $c->{mask}) { # TODO fix later
321 my $proc = $c->{proc};
322 $proc->($bot, $sendnick, $host, $hand, $text, $target);
327 if ($hostmask ne '*status!znc@znc.in') {
328 if ($text =~ /^(PING|VERSION|TIME|USERINFO) (.*)$/i) {
329 my ($key, $val) = ($1, $2);
330 my $id = SQLite::id("irc", "nick", $sendnick, $expires);
331 SQLite::set("irc", "id", $id, "ctcp".lc($key), $val);
332 SQLite::set("irc", "id", $id, "localtime", time());
336 debug(ALL, "$hostmask NOTICE $target $text");
337 #:portlane.se.quakenet.org NOTICE guava :Highest connection count: 1541 (1540 clients)
338 #:portlane.se.quakenet.org NOTICE guava :on 2 ca 2(4) ft 20(20) tr
339 } elsif($response =~ /^:([^ ]+) MODE ([^ ]+) ([^ ]+)\s*(.*)\r\n$/i) {
340 my ($hostmask, $chan, $change, $targets) = ($1, $2, $3, $4);
341 if ($hostmask =~ /([^!]+)!([^@]+@[^@ ]+)/) {
342 my ($sendnick, $host) = ($1, $2);
343 my $hand = $staff; # TODO fix later
344 foreach my $c (@{$call->{mode}}) {
345 # TODO filter by mask
346 my $proc = $c->{proc};
347 $proc->($bot, $sendnick, $host, $hand, $chan, $change, $targets);
350 debug(ALL, "$hostmask MODE $chan $change $targets");
351 #:guava!guava@guava.guava.ircnow.org MODE guava :+Ci
352 #:ChanServ!services@services.irc.ircnow.org MODE #testing +q jrmu
353 #:jrmu!jrmu@jrmu.staff.ircnow.org MODE #testing +o jrmu
354 #Unexpected bncnow.pl 460: :irc.guava.ircnow.org MODE guava :+o
355 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) JOIN :?(.*)\r\n$/i) {
356 my ($hostmask, $sendnick, $host, $chan) = ($1, $2, $3, $4);
357 my $hand = $staff; # TODO fix later
358 foreach my $c (@{$call->{join}}) {
359 my $proc = $c->{proc};
360 $proc->($bot, $sendnick, $host, $hand, $chan);
362 debug(ALL, "$hostmask JOIN $chan");
363 #:jrmu!jrmu@jrmu.staff.ircnow.org JOIN :#testing
364 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) PART ([^ ]+) :(.*)\r\n$/i) {
365 my ($hostmask, $sendnick, $host, $chan, $text) = ($1, $2, $3, $4, $5);
366 my $hand = $staff; # TODO fix later
367 foreach my $c (@{$call->{part}}) {
368 # if ($text eq $c->{mask}) { # TODO fix later
369 my $proc = $c->{proc};
370 $proc->($bot, $sendnick, $host, $hand, $chan, $text);
373 debug(ALL, "$hostmask PART $chan :$text");
374 #:jrmu!jrmu@jrmu.staff.ircnow.org PART #testing :
375 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) KICK (#[^ ]+) ([^ ]+) :(.*)\r\n$/i) {
376 my ($hostmask, $sendnick, $host, $chan, $kicked, $text) = ($1, $2, $3, $4, $5, $6);
377 my $hand = $staff; # TODO fix later
378 foreach my $c (@{$call->{kick}}) {
379 # if ($text eq $c->{mask}) { # TODO fix later
380 my $proc = $c->{proc};
381 $proc->($bot, $sendnick, $host, $hand, $chan, $text);
384 debug(ALL, "$hostmask KICK $chan $kicked :$text");
385 #jrmu!jrmu@jrmu.users.undernet.org KICK #ircnow guava :this is a test
386 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) NICK :?(.*)\r\n$/i) {
387 my ($hostmask, $sendnick, $host, $text) = ($1, $2, $3, $4);
388 debug(ALL, "$hostmask NICK $text");
389 #:Fly0nDaWaLL|dal!psybnc@do.not.h4ck.me NICK :nec|dal
390 } elsif($response =~ /^:(([^!]+)!([^@]+@[^@ ]+)) QUIT :(.*)\r\n$/i) {
391 my ($hostmask, $sendnick, $host, $text) = ($1, $2, $3, $4);
392 debug(ALL, "$hostmask QUIT :$text");
393 #:Testah!~sid268081@aa38a510 QUIT :Client closed connection
394 } elsif($response =~ /^NOTICE AUTH :(.*)\r\n$/i) {
396 debug(ALL, "NOTICE AUTH: $text");
397 #NOTICE AUTH :*** Looking up your hostname
398 #NOTICE AUTH: *** Looking up your hostname
399 #NOTICE AUTH: *** Checking Ident
400 #NOTICE AUTH: *** Got ident response
401 #NOTICE AUTH: *** Found your hostname
402 } elsif ($response =~ /^:([[:graph:]]+) (\d\d\d) $nick.? :?(.*)\r?\n?\r$/i) {
403 my ($server, $code, $text) = ($1, $2, $3);
404 if ($code =~ /^001$/) { # Server Info
405 debug(ERRORS, "connected: $bot->{name}");
406 } elsif ($code =~ /^0\d\d$/) { # Server Info
407 debug(ALL, "$server $code $text");
408 } elsif ($code =~ /^2\d\d$/) { # Server Stats
409 debug(ALL, "$server $code $text");
410 } elsif ($code == 301 && $text =~ /^([-_\|`a-zA-Z0-9]+) :([[:graph:]]+)/) {
412 } elsif ($code == 307 && $text =~ /^([-_\|`a-zA-Z0-9]+) (.*)/) {
413 my ($sender, $key) = ($1, "registered");
414 $val = $2 eq ":is a registered nick" ? "True" : "$2";
415 my $id = SQLite::id("irc", "nick", $sender, $expires);
416 SQLite::set("irc", "id", $id, "identified", $val);
417 debug(ALL, "$key: $val");
418 } elsif ($code == 311 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([^:]+)\s+([^:]+) \* :([^:]*)/) {
419 my ($sender, $key, $val) = ($1, "hostmask", "$1\!$2\@$3");
420 my $id = SQLite::id("irc", "nick", $sender, $expires);
421 SQLite::set("irc", "id", $id, $key, $val);
422 debug(ALL, "$key: $val");
423 } elsif ($code == 312 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([^:]+) :([^:]+)/) {
424 my ($sender, $key, $val) = ($1, "server", $2);
425 my $id = SQLite::id("irc", "nick", $sender, $expires);
426 SQLite::set("irc", "id", $id, $key, $val);
427 debug(ALL, "$key: $val");
428 } elsif ($code == 313 && $text =~ /^([-_\|`a-zA-Z0-9]+) :?(.*)/) {
429 my ($sender, $key, $val) = ($1, "oper", ($2 eq "is an IRC operator" ? "True" : "$2"));
430 my $id = SQLite::id("irc", "nick", $sender, $expires);
431 SQLite::set("irc", "id", $id, $key, $val);
432 debug(ALL, "$key: $val");
433 } elsif ($code == 315 && $text =~ /^([-_\|`a-zA-Z0-9]+) :End of \/?WHO(IS)? list/) {
434 debug(ALL, "End of WHOIS");
435 } elsif ($code == 317 && $text =~ /^([-_\|`a-zA-Z0-9]+) (\d+) (\d+) :?(.*)/) {
436 ($sender, my $idle, my $epochtime) = ($1, $2, $3);
437 my $id = SQLite::id("irc", "nick", $sender, $expires);
438 SQLite::set("irc", "id", $id, "idle", $idle);
439 # SQLite::set("irc", "id", $id, "epochtime", time());
440 debug(ALL, "idle: $idle, epochtime: $epochtime");
441 } elsif ($code == 318 && $text =~ /^([-_\|`a-zA-Z0-9]+) :End of \/?WHOIS list/) {
442 debug(ALL, "End of WHOIS");
443 } elsif ($code == 319 && $text =~ /^([-_\|`a-zA-Z0-9]+) :(.*)/) {
444 my ($sender, $key, $val) = ($1, "chans", $2);
445 my $id = SQLite::id("irc", "nick", $sender, $expires);
446 SQLite::set("irc", "id", $id, $key, $val);
447 debug(ALL, "$key: $val");
448 } elsif ($code == 330 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([-_\|`a-zA-Z0-9]+) :?(.*)/) {
449 my ($sender, $key, $val) = ($1, "identified", ($3 eq "is logged in as" ? "True" : $2));
450 my $id = SQLite::id("irc", "nick", $sender, $expires);
451 SQLite::set("irc", "id", $id, $key, $val);
452 debug(ALL, "$key: $val");
453 } elsif ($code == 338 && $text =~ /^([-_\|`a-zA-Z0-9]+) ([0-9a-fA-F:.]+) :actually using host/) {
454 my ($sender, $key, $val) = ($1, "ip", $2);
455 my $id = SQLite::id("irc", "nick", $sender, $expires);
456 SQLite::set("irc", "id", $id, $key, $val);
457 debug(ALL, "$key: $val");
458 #Unexpected: efnet.port80.se 338 jrmu 206.253.167.44 :actually using host
459 } elsif ($code == 378 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is connecting from ([^ ]+)\s*([0-9a-fA-F:.]+)?/) {
460 my ($sender, $key, $val) = ($1, "ip", $3);
461 my $id = SQLite::id("irc", "nick", $sender, $expires);
462 SQLite::set("irc", "id", $id, $key, $val);
463 debug(ALL, "$key: $val");
464 } elsif ($code == 671 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is using a secure connection/) {
465 my ($sender, $key, $val) = ($1, "ssl", "True");
466 my $id = SQLite::id("irc", "nick", $sender, $expires);
467 SQLite::set("irc", "id", $id, $key, $val);
468 debug(ALL, "$key: $val");
469 } elsif ($code =~ /^332$/) { # Topic
471 } elsif ($code =~ /^333$/) { #
472 # print "$server $text\r\n";
473 #karatkievich.freenode.net 333 #ircnow jrmu!znc@206.253.167.44 1579277253
474 } elsif ($code =~ /^352$/) { # Hostmask
475 #:datapacket.hk.quakenet.org 352 * znc guava.guava.ircnow.org *.quakenet.org guava H :0 guava
476 # print "$server $code $text\r\n";
477 } elsif ($code =~ /^353$/) { # Names
478 # print "$server $code $text\r\n";
479 } elsif ($code =~ /^366$/) { # End of names
480 # print "$server $code $text\r\n";
481 } elsif ($code =~ /^37\d$/) { # MOTD
482 # print "$server $code $text\r\n";
483 } elsif ($code =~ /^381$/) { # IRC Operator Verified
484 # print "IRC Oper Verified\r\n";
485 } elsif ($code =~ /^401$/) { # IRC Operator Verified
486 # print "IRC Oper Verified\r\n";
487 } elsif ($code =~ /^403$/) { # No such channel
488 # debug(ERRORS, "$text");
489 } elsif ($code =~ /^422$/) { # MOTD missing
490 # print "$server $code $text\r\n";
491 } elsif ($code =~ /^396$/) { # Display hostname
492 # print "$server $code $text\r\n";
493 #Unexpected bncnow.pl 454: irc.guava.ircnow.org 396 guava.guava.ircnow.org :is your displayed hostname now
494 } elsif ($code =~ /^464$/) { # Invalid password for oper
495 foreach my $chan (@teamchans) {
496 putserv($bot, "PRIVMSG $chan :$nick oper password failed; the bot will be unable to view uncloaked IP addresses");
498 } elsif ($code =~ /^477$/) { # Can't join channel
499 foreach my $chan (@teamchans) {
500 putserv($bot, "PRIVMSG $chan :ERROR: $nick on $server: $text");
502 } elsif ($code == 716 && $text =~ /^([-_\|`a-zA-Z0-9]+) :is in \+g mode \(server-side ignore.\)/) {
505 debug(ERRORS, "Unexpected bncnow.pl 454: $server $code $text");
508 debug(ERRORS, "Unexpected bncnow.pl 460: $response");
514 my( $bot, $text )=@_;
515 my $socket = $bot->{sock};
516 if ($text =~ /^([^:]+):([[:ascii:]]*)$/m) {
517 my ($cmd, $line) = ($1, $2);
518 my @lines = split /\r?\n/m, $line;
519 foreach my $l (@lines) {
520 print $socket "$cmd:$l\r\n";
523 print $socket "$text\r\n";
527 sub putservlocalnet {
528 my( $bot, $text )=@_;
530 foreach my $b (@bots) {
531 if($b->{name} =~ /^$localnet$/i) {
536 putserv($botlocalnet, $text);
540 my( $socket, $target )=@_;
541 print $socket "WHOIS $target $target\r\n";
545 my( $socket, $target )=@_;
546 # print $socket "PRIVMSG $target :".chr(01)."CLIENTINFO".chr(01)."\r\n";
547 # print $socket "PRIVMSG $target :".chr(01)."FINGER".chr(01)."\r\n";
548 # print $socket "PRIVMSG $target :".chr(01)."SOURCE".chr(01)."\r\n";
549 print $socket "PRIVMSG $target :".chr(01)."TIME".chr(01)."\r\n";
550 # print $socket "PRIVMSG $target :".chr(01)."USERINFO".chr(01)."\r\n";
551 print $socket "PRIVMSG $target :".chr(01)."VERSION".chr(01)."\r\n";
552 # print $socket "PRIVMSG $target :".chr(01)."PING".chr(01)."\r\n";
556 my ($type, $flags, $cmd, $proc) = @_;
557 if ($type eq "pub") {
558 push(@{$call->{pub}}, {cmd => $cmd, proc => $proc});
559 } elsif ($type eq "msg") {
560 push(@{$call->{msg}}, {cmd => $cmd, proc => $proc});
561 } elsif ($type eq "notc") {
562 push(@{$call->{notc}}, {mask => $cmd, proc => $proc});
563 } elsif ($type eq "mode") {
564 push(@{$call->{mode}}, {mask => $cmd, proc => $proc});
565 } elsif ($type eq "join") {
566 push(@{$call->{join}}, {mask => $cmd, proc => $proc});
567 } elsif ($type eq "partcall") {
568 push(@{$call->{part}}, {mask => $cmd, proc => $proc});
569 } elsif ($type eq "pubm") {
570 push(@{$call->{pubm}}, {mask => $cmd, proc => $proc});
571 } elsif ($type eq "msgm") {
572 push(@{$call->{msgm}}, {mask => $cmd, proc => $proc});
577 my ($level, $msg) = @_;
578 if ($verbose >= $level) { print "$msg\n"; }
582 my( $bot, $nick ) = @_;
583 if( !( $bot->{name} =~ /^$localnet$/i ) ) {
586 my $lnick = lc $nick;
587 foreach( @stafflist ) {