[BACK]Return to BNC.pm CVS log [TXT][DIR] Up to [local] / botnow

Annotation of botnow/BNC.pm, Revision 1.1

1.1     ! bountyht    1: #!/usr/bin/perl
        !             2:
        !             3: package BNC;
        !             4:
        !             5: use strict;
        !             6: use warnings;
        !             7: use OpenBSD::Pledge;
        !             8: use OpenBSD::Unveil;
        !             9: use MIME::Base64;
        !            10: use Digest::SHA qw(sha256_hex);
        !            11: use lib './';
        !            12: require "SQLite.pm";
        !            13: require "Hash.pm";
        !            14: require "DNS.pm";
        !            15: require "Mail.pm";
        !            16:
        !            17: my %conf = %main::conf;
        !            18: my $chans = $conf{chans};
        !            19: my $teamchans = $conf{teamchans};
        !            20: my @teamchans = split /[,\s]+/m, $teamchans;
        !            21: my $staff = $conf{staff};
        !            22: my $zncdir = $conf{zncdir};
        !            23: my $znclog = $conf{znclog} || "$zncdir/.znc/moddata/adminlog/znc.log";
        !            24: my $hostname = $conf{hostname};
        !            25: my $terms = $conf{terms};
        !            26: my @logs;
        !            27: my $expires = $conf{expires};
        !            28: my $sslport = $conf{sslport};
        !            29: my $plainport = $conf{plainport};
        !            30: my $mailfrom = $conf{mailfrom};
        !            31: my $mailname = $conf{mailname};
        !            32: my $zncconfpath = $conf{zncconfpath} || "$zncdir/.znc/configs/znc.conf";
        !            33: my $znctree = { Node => "root" };
        !            34:
        !            35: use constant {
        !            36:        NONE => 0,
        !            37:        ERRORS => 1,
        !            38:        WARNINGS => 2,
        !            39:        ALL => 3,
        !            40: };
        !            41:
        !            42: `doas chown znc:daemon /home/znc/home/znc/.znc/configs/znc.conf`;
        !            43: `doas chmod g+r /home/znc/home/znc/.znc/`;
        !            44: my @zncconf = main::readarray($zncconfpath);
        !            45: $znctree;
        !            46: my @users;
        !            47: foreach my $line (@zncconf) {
        !            48:        if ($line =~ /<User (.*)>/) {
        !            49:                push(@users, $1);
        !            50:        }
        !            51: }
        !            52: #$znctree = parseml($znctree, @zncconf);
        !            53: main::cbind("pub", "-", "bnc", \&mbnc);
        !            54: main::cbind("msg", "-", "bnc", \&mbnc);
        !            55: main::cbind("msg", "-", "regex", \&mregex);
        !            56: main::cbind("msg", "-", "foreach", \&mforeach);
        !            57: main::cbind("msgm", "-", "*", \&mcontrolpanel);
        !            58: main::cbind("msg", "-", "taillog", \&mtaillog);
        !            59: main::cbind("msg", "-", "lastseen", \&mlastseen);
        !            60:
        !            61: sub init {
        !            62:        #znc.conf file
        !            63:        unveil("$zncconfpath", "r") or die "Unable to unveil $!";
        !            64:        #dependencies for figlet
        !            65:        unveil("/usr/local/bin/figlet", "rx") or die "Unable to unveil $!";
        !            66:        unveil("/usr/lib/libc.so.95.1", "r") or die "Unable to unveil $!";
        !            67:        unveil("/usr/libexec/ld.so", "r") or die "Unable to unveil $!";
        !            68:        unveil("/usr/bin/tail", "rx") or die "Unable to unveil $!";
        !            69:        #znc.log file
        !            70:        unveil("$znclog", "r") or die "Unable to unveil $!";
        !            71:        #print treeget($znctree, "AnonIPLimit")."\n";
        !            72:        #print treeget($znctree, "ServerThrottle")."\n";
        !            73:        #print treeget($znctree, "ConnectDelay")."\n";
        !            74:        #print "treeget\n";
        !            75:        #print Dumper \treeget($znctree, "User", "Node");
        !            76:        #print Dumper \treeget($znctree, "User", "Network", "Node");
        !            77: }
        !            78:
        !            79: # parseml($tree, @lines)
        !            80: # tree is a reference to a hash
        !            81: # returns hash ref of tree
        !            82: sub parseml {
        !            83:        my ($tree, @lines) = @_;
        !            84:        #if (scalar(@lines) == 0) { return $tree; }
        !            85:        while (scalar(@lines) > 0) {
        !            86:                my $line = shift(@lines);
        !            87:                if ($line =~ /^\s*([^=<>\s]+)\s*=\s*([^=<>]+)\s*$/) {
        !            88:                        my ($tag, $val) = ($1, $2);
        !            89:                        $tree->{$tag} = $val;
        !            90:                } elsif ($line =~ /^\/\//) { # skip comments
        !            91:                } elsif ($line =~ /^\s*$/) { # skip blank lines
        !            92:                } elsif ($line =~ /^\s*<([^>\s\/]+)\s*([^>\/]*)>\s*$/) {
        !            93:                        my ($tag, $val) = ($1, $2);
        !            94:                        if (!defined($tree->{$tag})) { $tree->{$tag} = []; }
        !            95:                        my @newlines;
        !            96:                        while (scalar(@lines) > 0) {
        !            97:                                my $line = shift(@lines);
        !            98:                                if ($line =~ /^\s*<\/$tag>\s*$/) {
        !            99:                                        my $subtree = parseml({ Node => $val }, @newlines);
        !           100:                                        push(@{$tree->{$tag}}, $subtree);
        !           101:                                        return parseml($tree, @lines);
        !           102:                                }
        !           103:                                push(@newlines, $line);
        !           104:                        }
        !           105:                } else { print "ERROR: $line\n"; }
        !           106:                #TODO ERRORS not defined??
        !           107: #              } else { main::debug(ERRORS, "ERROR: $line"); }
        !           108:        }
        !           109:        return $tree;
        !           110: }
        !           111:
        !           112: #Returns array of all values
        !           113: #treeget($tree, "User");
        !           114: #treeget($tree, "MaFFia Network");
        !           115: sub treeget {
        !           116:        my ($tree, @keys) = @_;
        !           117:        my $subtree;
        !           118:        my @rest = @keys;
        !           119:        my $key = shift(@rest);
        !           120:        $subtree = $tree->{$key};
        !           121:        if (!defined($subtree)) {
        !           122:                return ("Undefined");
        !           123:        } elsif (ref($subtree) eq 'HASH') {
        !           124:                return treeget($subtree, @rest);
        !           125:        } elsif (ref($subtree) eq 'ARRAY') {
        !           126:                my @array = @{$subtree};
        !           127:                my @ret;
        !           128:                foreach my $hashref (@array) {
        !           129:                        push(@ret, treeget($hashref, @rest));
        !           130:                }
        !           131:                return @ret;
        !           132:                #my @array = @{$subtree};
        !           133:                #print Dumper treeget($hashref, @rest);
        !           134:                #print Dumper treeget({$key => $subtree}, @rest);
        !           135:                #return (treeget($hashref, @rest), treeget({$key => $subtree}, @rest));
        !           136:        } else {
        !           137:                return ($subtree);
        !           138:        }
        !           139: }
        !           140:
        !           141: sub mbnc {
        !           142:        my ($bot, $nick, $host, $hand, @args) = @_;
        !           143:        my ($chan, $text);
        !           144:        if (@args == 2) {
        !           145:                ($chan, $text) = ($args[0], $args[1]);
        !           146:        } else { $text = $args[0]; }
        !           147:        my $hostmask = "$nick!$host";
        !           148:        if (defined($chan) && $chans =~ /$chan/) {
        !           149:                main::putserv($bot, "PRIVMSG $chan :$nick: Please check private message");
        !           150:        }
        !           151:        if ($text =~ /^$/) {
        !           152:                main::putserv($bot, "PRIVMSG $nick :Type !help for new instructions");
        !           153:                foreach my $chan (@teamchans) {
        !           154:                        main::putservlocalnet($bot, "PRIVMSG $chan :Help *$nick* on ".$bot->{name});
        !           155:                }
        !           156:                return;
        !           157:        } elsif (main::isstaff($bot, $nick) && $text =~ /^delete\s+([[:ascii:]]+)/) {
        !           158:                my $username = $1;
        !           159:                if (SQLite::deleterows("bnc", "username", $username)) {
        !           160:                        main::putserv($bot, "PRIVMSG *controlpanel :deluser $username");
        !           161:                        foreach my $chan (@teamchans) {
        !           162:                                main::putserv($bot, "PRIVMSG $chan :$username deleted");
        !           163:                        }
        !           164:                }
        !           165:                return;
        !           166:        } elsif ($staff =~ /$nick/ && $text =~ /^cloneuser$/i) {
        !           167:                main::putserv($bot, "PRIVMSG *controlpanel :deluser cloneuser");
        !           168:                sleep 3;
        !           169:                main::putserv($bot, "PRIVMSG *controlpanel :get Nick cloneuser");
        !           170:        }
        !           171:        ### TODO: Check duplicate emails ###
        !           172:        my @rows = SQLite::selectrows("irc", "hostmask", $hostmask);
        !           173:        foreach my $row (@rows) {
        !           174:                my $password = SQLite::get("bnc", "ircid", $row->{id}, "password");
        !           175:                if (defined($password)) {
        !           176:                        main::putserv($bot, "PRIVMSG $nick :Sorry, only one account per person. Please contact staff if you need help.");
        !           177:                        return;
        !           178:                }
        !           179:        }
        !           180:        if ($text =~ /^captcha\s+([[:alnum:]]+)/) {
        !           181:                my $text = $1;
        !           182:                # TODO avoid using host mask because cloaking can cause problems
        !           183:                my $ircid = SQLite::id("irc", "nick", $nick, $expires);
        !           184:                my $captcha = SQLite::get("bnc", "ircid", $ircid, "captcha");
        !           185:                if ($text ne $captcha) {
        !           186:                        main::putserv($bot, "PRIVMSG $nick :Wrong captcha. To get a new captcha, type !bnc <username> <email>");
        !           187:                        return;
        !           188:                }
        !           189:                my $pass = Hash::newpass();
        !           190:                chomp(my $encrypted = `encrypt $pass`);
        !           191:                my $username = SQLite::get("bnc", "ircid", $ircid, "username");
        !           192:                my $email = SQLite::get("bnc", "ircid", $ircid, "email");
        !           193:                my $hashirc = SQLite::get("irc", "id", $ircid, "hashid");
        !           194:                my $bindhost = "$username.$hostname";
        !           195:                SQLite::set("bnc", "ircid", $ircid, "password", $encrypted);
        !           196:                if (DNS::nextdns($username)) {
        !           197:                        sleep(2);
        !           198:                        createbnc($bot, $username, $pass, $bindhost);
        !           199:                        main::putserv($bot, "PRIVMSG $nick :Check your email!");
        !           200:                        mailbnc($username, $email, $pass, "bouncer", $hashirc);
        !           201:                        #www($newnick, $reply, $password, "bouncer");
        !           202:                } else {
        !           203:                        foreach my $chan (@teamchans) {
        !           204:                                main::putserv($bot, "PRIVMSG $chan :Assigning bindhost $bindhost failed");
        !           205:                        }
        !           206:                }
        !           207:                return;
        !           208:        } elsif ($text =~ /^([[:alnum:]]+)\s+([[:ascii:]]+)/) {
        !           209:                my ($username, $email) = ($1, $2);
        !           210: #              my @users = treeget($znctree, "User", "Node");
        !           211:                foreach my $user (@users) {
        !           212:                        if ($user eq $username) {
        !           213:                                main::putserv($bot, "PRIVMSG $nick :Sorry, username taken. Please contact staff if you need help.");
        !           214:                        }
        !           215:                }
        !           216:                #my $captcha = join'', map +(0..9,'a'..'z','A'..'Z')[rand(10+26*2)], 1..4;
        !           217:                my $captcha = int(rand(999));
        !           218:                my $ircid = int(rand(9223372036854775807));
        !           219:                my $hashid = sha256_hex("$ircid");
        !           220:                SQLite::set("irc", "id", $ircid, "localtime", time());
        !           221:                SQLite::set("irc", "id", $ircid, "hashid", sha256_hex($ircid));
        !           222:                SQLite::set("irc", "id", $ircid, "date", main::date());
        !           223:                SQLite::set("irc", "id", $ircid, "hostmask", $hostmask);
        !           224:                SQLite::set("irc", "id", $ircid, "nick", $nick);
        !           225:                SQLite::set("bnc", "ircid", $ircid, "username", $username);
        !           226:                SQLite::set("bnc", "ircid", $ircid, "email", $email);
        !           227:                SQLite::set("bnc", "ircid", $ircid, "captcha", $captcha);
        !           228:                SQLite::set("bnc", "ircid", $ircid, "hashid", $hashid);
        !           229:                main::whois($bot->{sock}, $nick);
        !           230:                main::ctcp($bot->{sock}, $nick);
        !           231:                main::putserv($bot, "PRIVMSG $nick :".`figlet $captcha`);
        !           232:                main::putserv($bot, "PRIVMSG $nick :https://$hostname/$hashid/captcha.png");
        !           233:                main::putserv($bot, "PRIVMSG $nick :https://$hostname/register.php?hashirc=$hashid");
        !           234:                main::putserv($bot, "PRIVMSG $nick :Type !bnc captcha <text>");
        !           235:                foreach my $chan (@teamchans) {
        !           236:                        main::putservlocalnet($bot, "PRIVMSG $chan :$nick\'s captcha is $captcha");
        !           237:                }
        !           238:        } else {
        !           239:                main::putserv($bot, "PRIVMSG $nick :Invalid username or email. Type !bnc <username> <email> to try again.");
        !           240:                foreach my $chan (@teamchans) {
        !           241:                        main::putservlocalnet($bot, "PRIVMSG $chan :Help *$nick* on ".$bot->{name});
        !           242:                }
        !           243:        }
        !           244: }
        !           245:
        !           246: sub mregex {
        !           247:        my ($bot, $nick, $host, $hand, $text) = @_;
        !           248:        if (!main::isstaff($bot, $nick)) { return; }
        !           249:        if ($text =~ /^ips?\s+([-_()|0-9A-Za-z:\.?*\s]{3,})$/) {
        !           250:                my $ips = $1; # space-separated list of IPs
        !           251:                main::putserv($bot, "PRIVMSG $nick :".regexlist($ips));
        !           252:        } elsif ($text =~ /^users?\s+([-_()|0-9A-Za-z:\.?*\s]{3,})$/) {
        !           253:                my $users = $1; # space-separated list of usernames
        !           254:                main::putserv($bot, "PRIVMSG $nick :".regexlist($users));
        !           255:        } elsif ($text =~ /^[-_()|0-9A-Za-z:,\.?*\s]{3,}$/) {
        !           256:                my @lines = regex($text);
        !           257:                foreach my $l (@lines) { print "$l\n"; }
        !           258:        }
        !           259: }
        !           260: sub mforeach {
        !           261:        my ($bot, $nick, $host, $hand, $text) = @_;
        !           262:        if ($staff !~ /$nick/) { return; }
        !           263:        if ($text =~ /^network\s+del\s+([[:graph:]]+)\s+(#[[:graph:]]+)$/) {
        !           264:                my ($user, $chan) = ($1, $2);
        !           265:                foreach my $n (@main::networks) {
        !           266:                        main::putserv($bot, "PRIVMSG *controlpanel :delchan $user $n->{name} $chan");
        !           267:                }
        !           268:        }
        !           269: }
        !           270:
        !           271: sub mcontrolpanel {
        !           272:        my ($bot, $nick, $host, $hand, @args) = @_;
        !           273:        my ($chan, $text);
        !           274:        if (@args == 2) {
        !           275:                ($chan, $text) = ($args[0], $args[1]);
        !           276:        } else { $text = $args[0]; }
        !           277:        my $hostmask = "$nick!$host";
        !           278:        if($hostmask eq '*controlpanel!znc@znc.in') {
        !           279:                if ($text =~ /^Error: User \[cloneuser\] does not exist/) {
        !           280:                        createclone($bot);
        !           281:                        foreach my $chan (@teamchans) {
        !           282:                                main::putserv($bot, "PRIVMSG $chan :Cloneuser created");
        !           283:                        }
        !           284:                } elsif ($text =~ /^User (.*) added!$/) {
        !           285:                        main::debug(ALL, "User $1 created");
        !           286:                } elsif ($text =~ /^Password has been changed!$/) {
        !           287:                        main::debug(ALL, "Password changed");
        !           288:                } elsif ($text =~ /^Queued network (.*) of user (.*) for a reconnect.$/) {
        !           289:                        main::debug(ALL, "$2 now connecting to $1...");
        !           290:                } elsif ($text =~ /^Admin = false/) {
        !           291:                        foreach my $chan (@teamchans) {
        !           292:                                main::putserv($bot, "PRIVMSG $chan :ERROR: $nick is not admin");
        !           293:                        }
        !           294:                        die "ERROR: $nick is not admin";
        !           295:                } elsif ($text =~ /^Admin = true/) {
        !           296:                        main::debug(ALL, "$nick is ZNC admin");
        !           297:                } elsif ($text =~ /(.*) = (.*)/) {
        !           298:                        my ($key, $val) = ($1, $2);
        !           299:                        main::debug(ALL, "ZNC: $key => $val");
        !           300:                } else {
        !           301:                        main::debug(ERRORS, "Unexpected 290 BNC.pm: $hostmask $text");
        !           302:                }
        !           303:        }
        !           304: }
        !           305: sub loadlog {
        !           306:        open(my $fh, '<', "$znclog") or die "Could not read file 'znc.log' $!";
        !           307:        chomp(@logs = <$fh>);
        !           308:        close $fh;
        !           309: }
        !           310:
        !           311: # return all lines matching a pattern
        !           312: sub regex {
        !           313:        my ($pattern) = @_;
        !           314:        if (!@logs) { loadlog(); }
        !           315:        return grep(/$pattern/, @logs);
        !           316: }
        !           317:
        !           318: # given a list of IPs, return matching users
        !           319: # or given a list of users, return matching IPs
        !           320: sub regexlist {
        !           321:        my ($items) = @_;
        !           322:        my @items = split /[,\s]+/m, $items;
        !           323:        my $pattern = "(".join('|', @items).")";
        !           324:        if (!@logs) { loadlog(); }
        !           325:        my @matches = grep(/$pattern/, @logs);
        !           326:        my @results;
        !           327:        foreach my $match (@matches) {
        !           328:                if ($match =~ /^\[\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\] \[([^]\/]+)(\/[^]]+)?\] connected to ZNC from (.*)/) {
        !           329:                        my ($user, $ip) = ($1, $3);
        !           330:                        if ($items =~ /[.:]/) { # items are IP addresses
        !           331:                                push(@results, $user);
        !           332:                        } else { # items are users
        !           333:                                push(@results, $ip);
        !           334:                        }
        !           335:                }
        !           336:        }
        !           337:        my @sorted = sort @results;
        !           338:        @results = do { my %seen; grep { !$seen{$_}++ } @sorted }; # uniq
        !           339:        return join(' ', @results);
        !           340: }
        !           341:
        !           342: sub createclone {
        !           343:        my ($bot) = @_;
        !           344:        my $socket = $bot->{sock};
        !           345:        my $password = Hash::newpass();
        !           346:        my $msg = <<"EOF";
        !           347: adduser cloneuser $password
        !           348: set Nick cloneuser cloneuser
        !           349: set Altnick cloneuser cloneuser_
        !           350: set Ident cloneuser cloneuser
        !           351: set RealName cloneuser cloneuser
        !           352: set MaxNetworks cloneuser 1000
        !           353: set ChanBufferSize cloneuser 1000
        !           354: set MaxQueryBuffers cloneuser 1000
        !           355: set QueryBufferSize cloneuser 1000
        !           356: set NoTrafficTimeout cloneuser 600
        !           357: set QuitMsg cloneuser IRCNow and Forever!
        !           358: set RealName cloneuser cloneuser
        !           359: set DenySetBindHost cloneuser true
        !           360: set Timezone cloneuser US/Pacific
        !           361: LoadModule cloneuser controlpanel
        !           362: LoadModule cloneuser chansaver
        !           363: EOF
        !           364: #LoadModule cloneuser buffextras
        !           365:        main::putserv($bot, "PRIVMSG *controlpanel :$msg");
        !           366:        foreach my $n (@main::networks) {
        !           367:                my $net = $n->{name};
        !           368:                my $server = $n->{server};
        !           369:                my $port = $n->{port};
        !           370:                my $trustcerts = $n->{trustcerts};
        !           371:                $msg = <<"EOF";
        !           372: addnetwork cloneuser $net
        !           373: addserver cloneuser $net $server $port
        !           374: disconnect cloneuser $net
        !           375: EOF
        !           376:                if ($trustcerts) {
        !           377:                        $msg .= "SetNetwork TrustAllCerts cloneuser $net True\r\n";
        !           378:                }
        !           379:                my @chans = split /[,\s]+/m, $chans;
        !           380:                foreach my $chan (@chans) {
        !           381:                        $msg .= "addchan cloneuser $net $chan\r\n";
        !           382:                }
        !           383:                main::putserv($bot, "PRIVMSG *controlpanel :$msg");
        !           384:        }
        !           385: }
        !           386:
        !           387: sub createbnc {
        !           388:        my ($bot, $username, $password, $bindhost) = @_;
        !           389:        my $netname = $bot->{name};
        !           390:        my $msg = <<"EOF";
        !           391: cloneuser cloneuser $username
        !           392: set Nick $username $username
        !           393: set Altnick $username ${username}_
        !           394: set Ident $username $username
        !           395: set RealName $username $username
        !           396: set Password $username $password
        !           397: set MaxNetworks $username 1000
        !           398: set ChanBufferSize $username 1000
        !           399: set MaxQueryBuffers $username 1000
        !           400: set QueryBufferSize $username 1000
        !           401: set NoTrafficTimeout $username 600
        !           402: set QuitMsg $username IRCNow and Forever!
        !           403: set BindHost $username $bindhost
        !           404: set DCCBindHost $username $bindhost
        !           405: set DenySetBindHost $username true
        !           406: reconnect $username $netname
        !           407: EOF
        !           408: #set Language $username en-US
        !           409:        main::putserv($bot, "PRIVMSG *controlpanel :$msg");
        !           410:        return 1;
        !           411: }
        !           412: sub mailbnc {
        !           413:        my( $username, $email, $password, $service, $hashirc )=@_;
        !           414:        my $passhash = sha256_hex("$username");
        !           415:
        !           416: my $body = <<"EOF";
        !           417: You created a bouncer!
        !           418:
        !           419: Username: $username
        !           420: Password: $password
        !           421: Server: $hostname
        !           422: Port: $sslport for SSL (secure connection)
        !           423: Port: $plainport for plaintext
        !           424:
        !           425: *IMPORTANT*: Verify your email address:
        !           426:
        !           427: https://$hostname/register.php?hashirc=$hashirc
        !           428:
        !           429: You *MUST* click on the link or your account will be deleted.
        !           430:
        !           431: IRCNow
        !           432: EOF
        !           433:        Mail::mail($mailfrom, $email, $mailname, "Verify IRCNow Account", $body);
        !           434: }
        !           435:
        !           436: sub mtaillog {
        !           437:        my ($bot, $nick, $host, $hand, @args) = @_;
        !           438:        my ($chan, $text);
        !           439:        if (@args == 2) {
        !           440:                ($chan, $text) = ($args[0], $args[1]);
        !           441:        } else { $text = $args[0]; }
        !           442:        my $hostmask = "$nick!$host";
        !           443:        open(my $fh, "-|", "/usr/bin/tail", "-f", $znclog) or die "could not start tail: $!";
        !           444:        while (my $line = <$fh>) {
        !           445:                foreach my $chan (@teamchans) {
        !           446:                        main::putserv($bot, "PRIVMSG $chan :$line");
        !           447:                }
        !           448:        }
        !           449: }
        !           450:
        !           451: sub mlastseen {
        !           452:        my ($bot, $nick, $host, $hand, @args) = @_;
        !           453:        my ($chan, $text);
        !           454:        if (@args == 2) {
        !           455:                ($chan, $text) = ($args[0], $args[1]);
        !           456:        } else { $text = $args[0]; }
        !           457:        my $hostmask = "$nick!$host";
        !           458:        if (!@logs) { loadlog(); }
        !           459:        my @users = treeget($znctree, "User", "Node");
        !           460:        foreach my $user (@users) {
        !           461:                my @lines = grep(/^\[\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\] \[$user\] connected to ZNC from [.0-9a-fA-F:]+/, @logs);
        !           462:                if (scalar(@lines) == 0) {
        !           463:                        foreach my $chan (@teamchans) {
        !           464:                                main::putserv($bot, "PRIVMSG $chan :$user never logged in");
        !           465:                        }
        !           466:                        next;
        !           467:                }
        !           468:                my $recent = pop(@lines);
        !           469:                if ($recent =~ /^\[(\d{4}-\d\d-\d\d) \d\d:\d\d:\d\d\] \[$user\] connected to ZNC from [.0-9a-fA-F:]+/) {
        !           470:                        my $date = $1;
        !           471:                        foreach my $chan (@teamchans) {
        !           472:                                main::putserv($bot, "PRIVMSG $chan :$user $date");
        !           473:                        }
        !           474:                }
        !           475:        }
        !           476: }
        !           477: #sub resend {
        !           478: #      my ($bot, $newnick, $email) = @_;
        !           479: #      my $password = newpass();
        !           480: #      sendmsg($bot, "*controlpanel", "set Password $newnick $password");
        !           481: #      mailverify($newnick, $email, $password, "bouncer");
        !           482: #      sendmsg($bot, "$newnick", "Email sent");
        !           483: #}
        !           484:
        !           485: #      if ($reply =~ /^!resend ([-_0-9a-zA-Z]+) ([-_0-9a-zA-Z]+@[-_0-9a-zA-Z]+\.[-_0-9a-zA-Z]+)$/i) {
        !           486: #              my ($newnick, $email) = ($1, $2);
        !           487: #              my $password = newpass();
        !           488: #              resend($bot, $newnick, $email);
        !           489: #      }
        !           490:
        !           491: #sub resetznc {
        !           492: #
        !           493: #AnonIPLimit 10000
        !           494: #AuthOnlyViaModule false
        !           495: #ConnectDelay 0
        !           496: #HideVersion true
        !           497: #LoadModule
        !           498: #ServerThrottle
        !           499: #1337  209.141.38.137
        !           500: #31337  209.141.38.137
        !           501: #1337  2605:6400:20:5cc::
        !           502: #31337  2605:6400:20:5cc::
        !           503: #1337  127.0.0.1
        !           504: #1338  127.0.0.1
        !           505: #}
        !           506: #
        !           507: #alias   Provides bouncer-side command alias support.
        !           508: #autoreply   Reply to queries when you are away
        !           509: #block_motd   Block the MOTD from IRC so it's not sent to your client(s).
        !           510: #bouncedcc   Bounces DCC transfers through ZNC instead of sending them directly to the user.
        !           511: #clientnotify   Notifies you when another IRC client logs into or out of your account. Configurable.
        !           512: #ctcpflood   Don't forward CTCP floods to clients
        !           513: #dcc   This module allows you to transfer files to and from ZNC
        !           514: #perform   Keeps a list of commands to be executed when ZNC connects to IRC.
        !           515: #webadmin   Web based administration module.
        !           516:
        !           517:
        !           518: 1; # MUST BE LAST STATEMENT IN FILE

CVSweb