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

Annotation of botnow/Shell.pm, Revision 1.1.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