commit e11136a333c70cd414281eecca353983c8443183 from: jrmu date: Fri Feb 10 04:41:05 2023 UTC Only connect to active nets 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 :, or /quote PASS /: 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; +}