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

Annotation of botnow/Shell.pm, Revision 1.1

1.1     ! bountyht    1: #!/usr/bin/perl
        !             2:
        !             3: package Shell;
        !             4:
        !             5: use strict;
        !             6: use warnings;
        !             7: use OpenBSD::Pledge;
        !             8: use OpenBSD::Unveil;
        !             9: use MIME::Base64;
        !            10: use Data::Dumper;
        !            11: use Digest::SHA qw(sha256_hex);
        !            12: use lib './';
        !            13: require "SQLite.pm";
        !            14: require "Hash.pm";
        !            15:
        !            16: my %conf = %main::conf;
        !            17: my $chans = $conf{chans};
        !            18: my $teamchans = $conf{teamchans};
        !            19: my @teamchans = split /[,\s]+/m, $teamchans;
        !            20: my $staff = $conf{staff};
        !            21: my $captchaURL = "https://example.com/captcha.php?vhost=";
        !            22: my $hostname = $conf{hostname};
        !            23: my $terms = $conf{terms};
        !            24: my $expires = $conf{expires};
        !            25: my $mailfrom = $conf{mailfrom};
        !            26: my $mailname = $conf{mailname};
        !            27: my $passpath = "/etc/passwd";
        !            28: my $httpdconfpath = "/etc/httpd.conf";
        !            29: my $acmeconfpath = "/etc/acme-client.conf";
        !            30: main::cbind("pub", "-", "shell", \&mshell);
        !            31: main::cbind("msg", "-", "shell", \&mshell);
        !            32:
        !            33: sub init {
        !            34:        #dependencies for figlet
        !            35:        unveil("/usr/local/bin/figlet", "rx") or die "Unable to unveil $!";
        !            36:        unveil("/usr/lib/libc.so.95.1", "r") or die "Unable to unveil $!";
        !            37:        unveil("/usr/libexec/ld.so", "r") or die "Unable to unveil $!";
        !            38:        #dependencies for shell account
        !            39:        unveil($passpath, "r") or die "Unable to unveil $!";
        !            40:        unveil($httpdconfpath, "rwxc") or die "Unable to unveil $!";
        !            41:        unveil($acmeconfpath, "rwxc") or die "Unable to unveil $!";
        !            42:        unveil("/usr/sbin/chown", "rx") or die "Unable to unveil $!";
        !            43:        unveil("/bin/chmod", "rx") or die "Unable to unveil $!";
        !            44:        unveil("/usr/sbin/groupadd", "rx") or die "Unable to unveil $!";
        !            45:        unveil("/usr/sbin/useradd", "rx") or die "Unable to unveil $!";
        !            46:        unveil("/usr/sbin/groupdel", "rx") or die "Unable to unveil $!";
        !            47:        unveil("/usr/sbin/userdel", "rx") or die "Unable to unveil $!";
        !            48:        unveil("/bin/mkdir", "rx") or die "Unable to unveil $!";
        !            49:        unveil("/bin/ln", "rx") or die "Unable to unveil $!";
        !            50:        unveil("/usr/sbin/acme-client", "rx") or die "Unable to unveil $!";
        !            51:        unveil("/bin/rm", "rx") or die "Unable to unveil $!";
        !            52:        unveil("/bin/mv", "rx") or die "Unable to unveil $!";
        !            53:        unveil("/home/", "rwxc") or die "Unable to unveil $!";
        !            54: }
        !            55:
        !            56: # !shell <username> <email>
        !            57: # !shell captcha <captcha>
        !            58: sub mshell {
        !            59:        my ($bot, $nick, $host, $hand, @args) = @_;
        !            60:        my ($chan, $text);
        !            61:        if (@args == 2) {
        !            62:                ($chan, $text) = ($args[0], $args[1]);
        !            63:        } else { $text = $args[0]; }
        !            64:        my $hostmask = "$nick!$host";
        !            65:        if (defined($chan) && $chans =~ /$chan/) {
        !            66:                main::putserv($bot, "PRIVMSG $chan :$nick: Please check private message");
        !            67:        }
        !            68:        if ($text =~ /^$/) {
        !            69:                main::putserv($bot, "PRIVMSG $nick :Type !help for new instructions");
        !            70:                foreach my $chan (@teamchans) {
        !            71:                        main::putservlocalnet($bot, "PRIVMSG $chan :Help shell *$nick* on ".$bot->{name});
        !            72:                }
        !            73:                return;
        !            74:        } elsif (main::isstaff($bot, $nick) && $text =~ /^delete\s+([[:ascii:]]+)/) {
        !            75:                my $username = $1;
        !            76:                if (SQLite::deleterows("shell", "username", $username)) {
        !            77:                        # TODO delete shell
        !            78:                        deleteshell($bot, $username);
        !            79:                        foreach my $chan (@teamchans) {
        !            80:                                main::putserv($bot, "PRIVMSG $chan :$username deleted");
        !            81:                        }
        !            82:                }
        !            83:                return;
        !            84:        }
        !            85:        ### TODO: Check duplicate emails ###
        !            86:        my @rows = SQLite::selectrows("irc", "nick", $nick);
        !            87:        foreach my $row (@rows) {
        !            88:                my $password = SQLite::get("shell", "ircid", $row->{id}, "password");
        !            89:                if (defined($password)) {
        !            90:                        main::putserv($bot, "PRIVMSG $nick :Sorry, only one account per person. Please contact staff if you need help.");
        !            91:                        return;
        !            92:                }
        !            93:        }
        !            94:        if ($text =~ /^lastseen\s+([[:alnum:]]+)/) {
        !            95:        }
        !            96:        if ($text =~ /^captcha\s+([[:alnum:]]+)/) {
        !            97:                my $text = $1;
        !            98:                my $ircid = SQLite::id("irc", "nick", $nick, $expires);
        !            99:                if (!defined($ircid)) { die "undefined ircid"; }
        !           100:                my $captcha = SQLite::get("shell", "ircid", $ircid, "captcha");
        !           101:                if ($text ne $captcha) {
        !           102:                        main::putserv($bot, "PRIVMSG $nick :Wrong captcha. To get a new captcha, type !shell <username> <email>");
        !           103:                        return;
        !           104:                }
        !           105:                my $pass = Hash::newpass();
        !           106:                chomp(my $encrypted = `encrypt $pass`);
        !           107:                my $username = SQLite::get("shell", "ircid", $ircid, "username");
        !           108:                my $email = SQLite::get("shell", "ircid", $ircid, "email");
        !           109:                my $version = SQLite::get("shell", "ircid", $ircid, "version");
        !           110:                my $bindhost = "$username.$hostname";
        !           111:                SQLite::set("shell", "ircid", $ircid, "password", $encrypted);
        !           112:                if (DNS::nextdns($username)) {
        !           113:                        sleep(2);
        !           114:                        createshell($bot, $username, $pass, $bindhost);
        !           115:                        mailshell($username, $email, $pass, "shell", $version);
        !           116:                        main::putserv($bot, "PRIVMSG $nick :Check your email!");
        !           117:
        !           118:                        #www($newnick, $reply, $password, "bouncer");
        !           119:                } else {
        !           120:                        foreach my $chan (@teamchans) {
        !           121:                                main::putserv($bot, "PRIVMSG $chan :Assigning bindhost $bindhost failed");
        !           122:                        }
        !           123:                }
        !           124:                return;
        !           125:        } elsif ($text =~ /^([[:alnum:]]+)\s+([[:ascii:]]+)/) {
        !           126:                my ($username, $email) = ($1, $2);
        !           127:                my @users = col($passpath, 1, ":");
        !           128:                my @matches = grep(/^$username$/i, @users);
        !           129:                if (scalar(@matches) > 0) {
        !           130:                        main::putserv($bot, "PRIVMSG $nick :Sorry, username taken. Please choose another username, or contact staff for help.");
        !           131:                        return;
        !           132:                }
        !           133:                #               my $captcha = join'', map +(0..9,'a'..'z','A'..'Z')[rand(10+26*2)], 1..4;
        !           134:                my $captcha = int(rand(999));
        !           135:                my $ircid = int(rand(2147483647));
        !           136:                SQLite::set("irc", "id", $ircid, "localtime", time());
        !           137:                SQLite::set("irc", "id", $ircid, "date", main::date());
        !           138:                SQLite::set("irc", "id", $ircid, "hostmask", $hostmask);
        !           139:                SQLite::set("irc", "id", $ircid, "nick", $nick);
        !           140:                SQLite::set("shell", "ircid", $ircid, "username", $username);
        !           141:                SQLite::set("shell", "ircid", $ircid, "email", $email);
        !           142:                SQLite::set("shell", "ircid", $ircid, "captcha", $captcha);
        !           143:                main::whois($bot->{sock}, $nick);
        !           144:                main::ctcp($bot->{sock}, $nick);
        !           145:                main::putserv($bot, "PRIVMSG $nick :".`figlet $captcha`);
        !           146:                main::putserv($bot, "PRIVMSG $nick :$captchaURL".encode_base64($captcha));
        !           147:                main::putserv($bot, "PRIVMSG $nick :Type !shell captcha <text>");
        !           148:                foreach my $chan (@teamchans) {
        !           149:                        main::putservlocalnet($bot, "PRIVMSG $chan :$nick\'s captcha on $bot->{name} is $captcha");
        !           150:                }
        !           151:        } else {
        !           152:                main::putserv($bot, "PRIVMSG $nick :Invalid username or email. Type !shell <username> <email> to try again.");
        !           153:                foreach my $chan (@teamchans) {
        !           154:                        main::putserv($bot, "PRIVMSG $chan :Help *$nick* on ".$bot->{name});
        !           155:                }
        !           156:        }
        !           157: }
        !           158: sub mailshell {
        !           159:        my( $username, $email, $password, $service, $version )=@_;
        !           160:        my $passhash = sha256_hex("$username");
        !           161:        my $versionhash = encode_base64($version);
        !           162:        my $ports;
        !           163:        my $body = <<"EOF";
        !           164: You created a shell account!
        !           165:
        !           166: Username: $username
        !           167: Password: $password
        !           168: Server: $hostname
        !           169: SSH Port: 22
        !           170: Your Ports: $ports for plaintext
        !           171:
        !           172: *IMPORTANT*: Verify your email address:
        !           173:
        !           174: https://www.$hostname/register.php?id=$passhash&version=$versionhash
        !           175:
        !           176: You *MUST* click on the link or your account will be deleted.
        !           177:
        !           178: IRCNow
        !           179: EOF
        !           180:        Mail::mail($mailfrom, $email, $mailname, "Verify IRCNow Account", $body);
        !           181: }
        !           182:
        !           183:
        !           184: #sub mregex {
        !           185: #      my ($bot, $nick, $host, $hand, $text) = @_;
        !           186: #      if ($staff !~ /$nick/) { return; }
        !           187: #      if ($text =~ /^ips?\s+([-_()|0-9A-Za-z:\.?*\s]{3,})$/) {
        !           188: #              my $ips = $1; # space-separated list of IPs
        !           189: #              main::putserv($bot, "PRIVMSG $nick :".regexlist($ips));
        !           190: #      } elsif ($text =~ /^users?\s+([-_()|0-9A-Za-z:\.?*\s]{3,})$/) {
        !           191: #              my $users = $1; # space-separated list of usernames
        !           192: #              main::putserv($bot, "PRIVMSG $nick :".regexlist($users));
        !           193: #      } elsif ($text =~ /^[-_()|0-9A-Za-z:,\.?*\s]{3,}$/) {
        !           194: #              my @lines = regex($text);
        !           195: #              foreach my $l (@lines) { print "$l\n"; }
        !           196: #      }
        !           197: #}
        !           198: #sub mforeach {
        !           199: #      my ($bot, $nick, $host, $hand, $text) = @_;
        !           200: #      if ($staff !~ /$nick/) { return; }
        !           201: #      if ($text =~ /^network\s+del\s+([[:graph:]]+)\s+(#[[:graph:]]+)$/) {
        !           202: #              my ($user, $chan) = ($1, $2);
        !           203: #              foreach my $n (@main::networks) {
        !           204: #                      main::putserv($bot, "PRIVMSG *controlpanel :delchan $user $n->{name} $chan");
        !           205: #              }
        !           206: #      }
        !           207: #}
        !           208:
        !           209: #sub loadlog {
        !           210: #      open(my $fh, '<', "$authlog") or die "Could not read file 'authlog' $!";
        !           211: #      chomp(@logs = <$fh>);
        !           212: #      close $fh;
        !           213: #}
        !           214:
        !           215: # return all lines matching a pattern
        !           216: #sub regex {
        !           217: #      my ($pattern) = @_;
        !           218: #      if (!@logs) { loadlog(); }
        !           219: #      return grep(/$pattern/, @logs);
        !           220: #}
        !           221:
        !           222: # given a list of IPs, return matching users
        !           223: # or given a list of users, return matching IPs
        !           224: #sub regexlist {
        !           225: #      my ($items) = @_;
        !           226: #      my @items = split /[,\s]+/m, $items;
        !           227: #      my $pattern = "(".join('|', @items).")";
        !           228: #      if (!@logs) { loadlog(); }
        !           229: #      my @matches = grep(/$pattern/, @logs);
        !           230: #      my @results;
        !           231: #      foreach my $match (@matches) {
        !           232: #              if ($match =~ /^\[\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\] \[([^]\/]+)(\/[^]]+)?\] connected to ZNC from (.*)/) {
        !           233: #                      my ($user, $ip) = ($1, $3);
        !           234: #                      if ($items =~ /[.:]/) { # items are IP addresses
        !           235: #                              push(@results, $user);
        !           236: #                      } else { # items are users
        !           237: #                              push(@results, $ip);
        !           238: #                      }
        !           239: #              }
        !           240: #      }
        !           241: #      my @sorted = sort @results;
        !           242: #      @results = do { my %seen; grep { !$seen{$_}++ } @sorted }; # uniq
        !           243: #      return join(' ', @results);
        !           244: #}
        !           245:
        !           246: sub createshell {
        !           247:        my ($bot, $username, $password, $bindhost) = @_;
        !           248:        my $netname = $bot->{name};
        !           249:        system "doas groupadd $username";
        !           250:        system "doas adduser -batch $username $username $username `encrypt $password`";
        !           251:        system "doas chmod 700 /home/$username /home/$username/.ssh";
        !           252:        system "doas chmod 600 /home/$username/{.Xdefaults,.cshrc,.cvsrc,.login,.mailrc,.profile}";
        !           253:        system "doas mkdir /var/www/htdocs/$username";
        !           254:        system "doas ln -s /var/www/htdocs/$username /home/$username/htdocs";
        !           255:        system "doas chown -R $username:www /var/www/htdocs/$username /home/$username/htdocs";
        !           256:        system "doas chmod -R o-rx /var/www/htdocs/$username /home/$username/htdocs";
        !           257:        system "doas chmod -R g+rwx /var/www/htdocs/$username /home/$username/htdocs";
        !           258:        my $lusername = lc $username;
        !           259:        my $block = <<"EOF";
        !           260: server "$lusername.$hostname" {
        !           261:        listen on * port 80
        !           262:        listen on * port 8001
        !           263:        location "/.well-known/acme-challenge/*" {
        !           264:                root "/acme"
        !           265:                request strip 2
        !           266:        }
        !           267:        location "*.php" {
        !           268:                fastcgi socket "/run/php-fpm.sock"
        !           269:        }
        !           270:        root "/htdocs/$username"
        !           271: }
        !           272: EOF
        !           273:        main::appendfile($httpdconfpath, $block);
        !           274:        $block = <<"EOF";
        !           275: domain "$lusername.$hostname" {
        !           276:        domain key "/etc/ssl/private/$lusername.$hostname.key"
        !           277:        domain full chain certificate "/etc/ssl/$lusername.$hostname.fullchain.pem"
        !           278:        sign with letsencrypt
        !           279: }
        !           280: EOF
        !           281:        main::appendfile($acmeconfpath, $block);
        !           282:
        !           283:        system "doas rcctl reload httpd";
        !           284:        system "doas mv /etc/ssl/private/$hostname.key /etc/ssl/private/l.k";
        !           285:         system "doas acme-client -F $lusername.$hostname";
        !           286:        system "doas ln -s /etc/ssl/crt/$lusername.$hostname.fullchain.pem /etc/ssl/$lusername.$hostname.crt";
        !           287:         system "doas mv /etc/ssl/private/l.k /etc/ssl/private/$hostname.key";
        !           288: #edquota $username
        !           289:        return 1;
        !           290: }
        !           291:
        !           292: sub deleteshell {
        !           293:        my ($bot, $username, $bindhost) = @_;
        !           294:        my $netname = $bot->{name};
        !           295:        my $lusername = lc $username;
        !           296:        system "doas groupdel $username";
        !           297:        system "doas userdel $username";
        !           298:        system "doas rm -f /etc/ssl/$lusername.$hostname.crt /etc/ssl/$lusername.$hostname.fullchain.pem /etc/ssl/private/$lusername.$hostname.key";
        !           299:        my $httpdconf = main::readstr($httpdconfpath);
        !           300:        my $block = <<"EOF";
        !           301: server "$lusername.$hostname" {
        !           302:        listen on * port 80
        !           303:        location "/.well-known/acme-challenge/*" {
        !           304:                root "/acme"
        !           305:                request strip 2
        !           306:        }
        !           307:        location "*.php" {
        !           308:                fastcgi socket "/run/php-fpm.sock"
        !           309:        }
        !           310:        root "/htdocs/$username"
        !           311: }
        !           312: EOF
        !           313:        $block =~ s/{/\\{/gm;
        !           314:        $block =~ s/}/\\}/gm;
        !           315:        $block =~ s/\./\\./gm;
        !           316:        $block =~ s/\*/\\*/gm;
        !           317:        $httpdconf =~ s{$block}{}gm;
        !           318:        print $httpdconf;
        !           319:        main::writefile($httpdconfpath, $httpdconf);
        !           320:
        !           321:        my $acmeconf = main::readstr($acmeconfpath);
        !           322:        $block = <<"EOF";
        !           323: domain "$lusername.$hostname" {
        !           324:        domain key "/etc/ssl/private/$lusername.$hostname.key"
        !           325:        domain full chain certificate "/etc/ssl/$lusername.$hostname.fullchain.pem"
        !           326:        sign with letsencrypt
        !           327: }
        !           328: EOF
        !           329:        $block =~ s/{/\\{/gm;
        !           330:        $block =~ s/}/\\}/gm;
        !           331:        $block =~ s/\./\\./gm;
        !           332:        $block =~ s/\*/\\*/gm;
        !           333:        $acmeconf =~ s{$block}{}gm;
        !           334:        main::writefile($acmeconfpath, $acmeconf);
        !           335:        return 1;
        !           336: }
        !           337:
        !           338: #TODO Fix for $i
        !           339: # Return column $i from $filename as an array with file separator $FS
        !           340: sub col {
        !           341:        my ($filename, $i, $FS) = @_;
        !           342:        my @rows = main::readarray($filename);
        !           343:        my @results;
        !           344:        foreach my $row (@rows) {
        !           345:                if ($row =~ /^(.*?)$FS/) {
        !           346:                        push(@results, $1);
        !           347:                }
        !           348:        }
        !           349:        return @results;
        !           350: }
        !           351: #unveil("./newacct", "rx") or die "Unable to unveil $!";
        !           352: 1; # MUST BE LAST STATEMENT IN FILE

CVSweb