commit - 508e42881039eebebd6f2b001a42579950242266
commit + 0f1839cb8eb6c873a99d1568e34d96d450767862
blob - /dev/null
blob + cf0e088ca4e13e263abd86615d38a43500cfc497 (mode 644)
--- /dev/null
+++ lib/IRCNOW/Acct/Mail.pm
+package BotNow::Mail;
+
+use strict;
+use warnings;
+use OpenBSD::Pledge;
+use OpenBSD::Unveil;
+use lib qw(./lib);
+use IRCNOW::IO qw(readarray writefile);
+use IRCNOW::IO::IRC;
+use BotNow::Hash;
+use File::Copy qw(copy);
+use MIME::Base64;
+use Digest::SHA qw(sha256_hex);
+
+my %conf = %main::conf;
+my $chans = $conf{chans};
+my $staff = $conf{staff};
+my $mailhostname = $conf{mailhostname};
+my $mailfrom = $conf{mailfrom};
+my $mailname = $conf{mailname};
+my $imapport = $conf{imapport};
+my $smtpport = $conf{smtpport};
+my $teamchans = $conf{teamchans};
+my @teamchans = split /[,\s]+/m, $teamchans;
+my $webmail = $conf{webmail};
+my $approval = $conf{approval};
+my $expires = $conf{expires};
+my $passwdpath = "/etc/mail/passwd";
+my $virtualspath = "/etc/mail/virtuals";
+my $senderspath = "/etc/mail/users";
+my @users;
+
+IRCNOW::IO::IRC::cbind("msg", "-", "mail", \&mmail);
+
+sub init {
+ #dependencies for encrypt
+ unveil("/usr/bin/encrypt", "rx") or die "Unable to unveil $!";
+ #dependencies for mail
+ unveil("/usr/sbin/sendmail", "rx") or die "Unable to unveil $!";
+ unveil($passwdpath, "rwc") or die "Unable to unveil $!";
+ unveil($virtualspath, "rwc") or die "Unable to unveil $!";
+ unveil($senderspath, "rwc") or die "Unable to unveil $!";
+ unveil("$passwdpath.bak", "rwc") or die "Unable to unveil $!";
+ unveil("$virtualspath.bak", "rwc") or die "Unable to unveil $!";
+ unveil("$senderspath.bak", "rwc") or die "Unable to unveil $!";
+ unveil("/usr/lib/libutil.so.13.1", "r") or die "Unable to unveil $!";
+ unveil("/bin/sh", "rx") or die "Unable to unveil $!";
+}
+
+sub mmail {
+ my ($bot, $nick, $host, $hand, @args) = @_;
+ my ($chan, $text);
+ if (@args == 2) {
+ ($chan, $text) = ($args[0], $args[1]);
+ } else { $text = $args[0]; }
+ my $hostmask = "$nick!$host";
+ if (defined($chan) && $chans =~ /$chan/) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $chan :$nick: Please check private message");
+ }
+ if ($text =~ /^$/) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Type !help for new instructions");
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$staff: Help *$nick* on network ".$bot->{name}." with mail");
+ }
+ return;
+ } elsif (IRCNOW::IO::IRC::isstaff($bot, $nick) && $text =~ /^delete\s+([[:ascii:]]+)/) {
+ my $username = $1;
+ if (BotNow::SQLite::deleterows("mail", "username", $username)) {
+ deletemail($username);
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $chan :$username email deleted");
+ }
+ }
+ return;
+ } elsif (IRCNOW::IO::IRC::isstaff($bot, $nick) && $text =~ /^approve\s+([[:ascii:]]+)/) {
+ my $username = $1;
+ my @passwd = readarray($passwdpath);
+ foreach my $line (@passwd) {
+ $line =~ s/^#(${username}\@${mailhostname}.*)/$1/;
+ }
+ # trailing newline necessary
+ `doas touch $passwdpath.bak`;
+ `doas chmod g+w $passwdpath.bak`;
+ writefile("$passwdpath.bak", join("\n", @passwd)."\n");
+ copy "${passwdpath}.bak", $passwdpath;
+
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $chan :$username mail approved");
+ }
+ return;
+ }
+ ### Check duplicate hostmasks ###
+ my @rows = BotNow::SQLite::selectrows("irc", "hostmask", $hostmask);
+ foreach my $row (@rows) {
+ my $password = BotNow::SQLite::get("mail", "ircid", $row->{id}, "password");
+ if (defined($password)) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Sorry, only one account per person. Please contact staff if you need help.");
+ return;
+ }
+ }
+
+ if ($text =~ /^captcha\s+([[:alnum:]]+)/) {
+ my $text = $1;
+ # TODO avoid using host mask because cloaking can cause problems
+ my $ircid = BotNow::SQLite::id("irc", "nick", $nick, $expires);
+ my $captcha = BotNow::SQLite::get("mail", "ircid", $ircid, "captcha");
+ if ($text ne $captcha) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Wrong captcha. To get a new captcha, type !mail <username> <email>");
+ return;
+ }
+ my $pass = BotNot::Hash::newpass();
+ chomp(my $encrypted = `encrypt $pass`);
+ my $username = BotNow::SQLite::get("mail", "ircid", $ircid, "username");
+ my $email = BotNow::SQLite::get("mail", "ircid", $ircid, "email");
+ my $hashirc = BotNow::SQLite::get("irc", "id", $ircid, "hashid");
+ BotNow::SQLite::set("mail", "ircid", $ircid, "password", $encrypted);
+ sleep(2);
+ createmail($pass, $username);
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Check your email!");
+ sleep(5);
+ mailmail($username, $pass, $email);
+ if ($approval) {
+ my @passwd = readarray($passwdpath);
+ foreach my $line (@passwd) {
+ $line =~ s/^(${username}\@${mailhostname}.*)/#$1/;
+ }
+ # trailing newline necessary
+ `doas touch $passwdpath.bak`;
+ `doas chmod g+w $passwdpath.bak`;
+ writefile("$passwdpath.bak", join("\n", @passwd)."\n");
+ copy "${passwdpath}.bak", $passwdpath;
+
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Your account has been created but must be manually approved by your admins ($staff) before it can be used.");
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$staff: $nick\'s account $username must be manually unblocked before it can be used.");
+ }
+ }
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$staff: $nick\'s mail registration of $username\@$mailhostname on $bot->{name} was successful, but you *must* help him to connect. Most users are unable to connect. Show him https://wiki.ircnow.org/?n=Email.Email");
+ }
+ #www($newnick, $reply, $password, "bouncer");
+ return;
+ } elsif ($text =~ /^([[:alnum:]]+)\s+([[:ascii:]]+)/) {
+ my ($username, $email) = ($1, $2);
+ my @userrows = BotNow::SQLite::selectrows("mail", "username", $username);
+ foreach my $row (@userrows) {
+ my $password = BotNow::SQLite::get("mail", "ircid", $row->{id}, "password");
+ if (defined($password)) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Sorry, only one account per person. Please contact staff if you need help.");
+ return;
+ }
+ }
+ my @emailrows = BotNow::SQLite::selectrows("mail", "email", $email);
+ foreach my $row (@userrows) {
+ my $password = BotNow::SQLite::get("mail", "ircid", $row->{id}, "password");
+ if (defined($password)) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Sorry, only one account per person. Please contact staff if you need help.");
+ return;
+ }
+ }
+
+# my @users = treeget($znctree, "User", "Node");
+ foreach my $user (@users) {
+ if ($user eq $username) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Sorry, username taken. Please contact staff if you need help.");
+ return;
+ }
+ }
+
+ #my $captcha = join'', map +(0..9,'a'..'z','A'..'Z')[rand(10+26*2)], 1..4;
+ my $captcha = int(rand(999));
+ my $ircid = int(rand(9223372036854775807));
+ my $hashid = sha256_hex("$ircid");
+ BotNow::SQLite::set("irc", "id", $ircid, "localtime", time());
+ BotNow::SQLite::set("irc", "id", $ircid, "hashid", sha256_hex($ircid));
+ BotNow::SQLite::set("irc", "id", $ircid, "date", IRCNOW::IO::data());
+ BotNow::SQLite::set("irc", "id", $ircid, "hostmask", $hostmask);
+ BotNow::SQLite::set("irc", "id", $ircid, "nick", $nick);
+ BotNow::SQLite::set("mail", "ircid", $ircid, "username", $username);
+ BotNow::SQLite::set("mail", "ircid", $ircid, "email", $email);
+ BotNow::SQLite::set("mail", "ircid", $ircid, "captcha", $captcha);
+ BotNow::SQLite::set("mail", "ircid", $ircid, "hashid", $hashid);
+ IRCNOW::IO::IRC::whois($bot->{sock}, $nick);
+ IRCNOW::IO::IRC::ctcp($bot->{sock}, $nick);
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :".`figlet $captcha`);
+#IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :https://$hostname/$hashid/captcha.png");
+#IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :https://$hostname/register.php?hashirc=$hashid");
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Type !mail captcha <text>");
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$nick\'s on $bot->{name} mail captcha is $captcha");
+ }
+ } else {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Invalid username or email. Type !mail <username> <email> to try again.");
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$staff: Help *$nick* on network ".$bot->{name}." with mail");
+ }
+ }
+}
+
+sub mailmail {
+ my( $username, $password, $email )=@_;
+ my $approvemsg;
+ if ($approval eq "true") {
+ $approvemsg = <<"EOF";
+
+*IMPORTANT*: Your account has been created but it has not yet been
+approved. To get your account approved, please contact your admins
+$staff on IRC and by email.
+
+EOF
+ }
+my $body = <<"EOF";
+Welcome to IRCNow!
+
+You created an email account:
+
+Username: $username\@$mailhostname
+Password: $password
+Server: $mailhostname
+IMAP Port: $imapport (STARTTLS)
+SMTP Port: $smtpport (STARTTLS)
+Webpanel: $webmail
+$approvemsg
+*IMPORTANT*: Verify your email address:
+
+Please reply to this email to indicate you have received the email. You must
+reply in order to keep your account.
+
+Connection Instructions: https://wiki.ircnow.org/?n=Email.Email
+
+IRCNow
+EOF
+ IRCNOW::IO::mail($mailfrom, $email, $mailname, "Verify IRCNow Account", $body);
+}
+
+
+sub createmail {
+ my ($password, $username) = @_;
+ chomp(my $encrypted = `encrypt $password`);
+ my $line = "${username}\@$mailhostname:${encrypted}::::::userdb_quota_rule=*:storage=1G";
+ $line =~ s{\$}{\\\$}g;
+ my $line2 = "${username}\@$mailhostname vmail";
+ my $line3 = "${username}\@$mailhostname: ${username}\@$mailhostname";
+ `doas sh -c 'echo $line >> $passwdpath'`;
+ `doas sh -c 'echo $line2 >> $virtualspath'`;
+ `doas sh -c 'echo $line3 >> $senderspath'`;
+ `doas smtpctl update table passwd`;
+ `doas smtpctl update table virtuals`;
+ `doas smtpctl update table users`;
+ `doas rcctl reload dovecot`;
+}
+
+sub deletemail {
+ my ($username) = @_;
+ my @passwd = readarray($passwdpath);
+ my @virtuals = readarray($virtualspath);
+ my @senders = readarray($senderspath);
+ @passwd = grep !/^${username}\@${mailhostname}/, @passwd;
+ @virtuals = grep !/^${username}\@${mailhostname}/, @virtuals;
+ @senders = grep !/^${username}\@${mailhostname}/, @senders;
+
+ # trailing newline necessary
+ `doas touch $passwdpath.bak`;
+ `doas touch $virtualspath.bak`;
+ `doas touch $senderspath.bak`;
+ `doas chmod g+w $passwdpath.bak $virtualspath.bak $senderspath.bak`;
+ writefile("$passwdpath.bak", join("\n", @passwd)."\n");
+ copy "${passwdpath}.bak", $passwdpath;
+ writefile("$virtualspath.bak", join("\n", @virtuals)."\n");
+ copy "${virtualspath}.bak", $virtualspath;
+ writefile("$senderspath.bak", join("\n", @senders)."\n");
+ copy "${senderspath}.bak", $senderspath;
+
+ `doas smtpctl update table passwd`;
+ `doas smtpctl update table virtuals`;
+ `doas smtpctl update table users`;
+ `doas rcctl reload dovecot`;
+}
+1; # MUST BE LAST STATEMENT IN FILE
blob - /dev/null
blob + 803e30c732c0446fce3b6e12349424af16d133fa (mode 644)
--- /dev/null
+++ lib/IRCNOW/Acct/Sh.pm
+package BotNow::Shell;
+
+use strict;
+use warnings;
+use OpenBSD::Pledge;
+use OpenBSD::Unveil;
+use lib qw(./lib);
+use IRCNOW::IO qw(readarray);
+
+use Data::Dumper;
+
+my $authlog = "/var/log/authlog";
+my $etcpasswd = "/etc/master.passwd";
+my @etcpasswd = readarray($etcpasswd);
+my @users;
+foreach my $line (@etcpasswd) {
+ if ($line =~ /^([^:]+):[^:]+:([^:]+)/) {
+ my ($username, $uid) = ($1, $2);
+ if ($uid > 1000) {
+ push(@users, $username);
+ }
+ }
+}
+my @files = ("/var/log/authlog");
+push(@files, glob q("/var/log/authlog.?"));
+push(@files, glob q("/var/log/authlog.1?"));
+foreach my $user (@users) {
+ my $lastseen;
+ foreach my $file (@files) {
+ my @logs = readarray($file);
+ my @seen = grep(/$user/, @logs);
+ if (scalar(@seen) && $seen[0] =~ /^(\w+ \d+ \d\d:\d\d:\d\d)/) {
+ $lastseen = $1;
+ print "$user => $lastseen\n";
+ last;
+ }
+ }
+ if (!defined($lastseen)) {
+ print "$user => Never logged in\n";
+ }
+}
blob - /dev/null
blob + b29fc08961862de6e3c59c2afb6ba166869f76ea (mode 644)
--- /dev/null
+++ lib/IRCNOW/Acct/Shell.pm
+package BotNow::Shell;
+
+use strict;
+use warnings;
+use OpenBSD::Pledge;
+use OpenBSD::Unveil;
+
+use MIME::Base64;
+use Digest::SHA qw(sha256_hex);
+
+use lib './lib';
+use IRCNOW::IO qw(:FILEIO :DEBUG);
+use IRCNOW::IO::IRC;
+use BotNow::SQLite;
+use BotNow::Hash;
+
+use Data::Dumper;
+
+my %conf = %main::conf;
+my $chans = $conf{chans};
+my $teamchans = $conf{teamchans};
+my @teamchans = split /[,\s]+/m, $teamchans;
+my $staff = $conf{staff};
+my $captchaURL = "https://example.com/captcha.php?vhost=";
+my $hostname = $conf{hostname};
+my $terms = $conf{terms};
+my $expires = $conf{expires};
+my $mailfrom = $conf{mailfrom};
+my $mailname = $conf{mailname};
+my $approval = $conf{approval};
+my $loginclass = $conf{loginclass} || "freeshell";
+my $passpath = "/etc/passwd";
+my $httpdconfpath = "/etc/httpd.conf";
+my $acmeconfpath = "/etc/acme-client.conf";
+my $pfconfpath = "/etc/pf.conf";
+my $relaydconfpath = "/etc/relayd.conf";
+my $startPort;
+my $endPort;
+
+use constant {
+ NONE => 0,
+ ERRORS => 1,
+ WARNINGS => 2,
+ ALL => 3,
+};
+
+IRCNOW::IO::IRC::cbind("pub", "-", "shell", \&mshell);
+IRCNOW::IO::IRC::cbind("msg", "-", "shell", \&mshell);
+
+sub init {
+ #dependencies for figlet
+ unveil("/usr/local/bin/figlet", "rx") or die "Unable to unveil $!";
+ unveil("/usr/lib/libc.so.95.1", "r") or die "Unable to unveil $!";
+ unveil("/usr/libexec/ld.so", "r") or die "Unable to unveil $!";
+ #dependencies for shell account
+ unveil($passpath, "r") or die "Unable to unveil $!";
+ unveil($httpdconfpath, "rwxc") or die "Unable to unveil $!";
+ unveil($acmeconfpath, "rwxc") or die "Unable to unveil $!";
+ unveil($pfconfpath, "rwxc") or die "Unable to unveil $!";
+ unveil($relaydconfpath, "rwxc") or die "Unable to unveil $!";
+ unveil("/usr/sbin/chown", "rx") or die "Unable to unveil $!";
+ unveil("/bin/chmod", "rx") or die "Unable to unveil $!";
+ unveil("/usr/sbin/groupadd", "rx") or die "Unable to unveil $!";
+ unveil("/usr/sbin/useradd", "rx") or die "Unable to unveil $!";
+ unveil("/usr/sbin/usermod", "rx") or die "Unable to unveil $!";
+ unveil("/usr/sbin/groupdel", "rx") or die "Unable to unveil $!";
+ unveil("/usr/sbin/userdel", "rx") or die "Unable to unveil $!";
+ unveil("/bin/mkdir", "rx") or die "Unable to unveil $!";
+ unveil("/bin/ln", "rx") or die "Unable to unveil $!";
+ unveil("/usr/sbin/acme-client", "rx") or die "Unable to unveil $!";
+ unveil("/bin/rm", "rx") or die "Unable to unveil $!";
+ unveil("/bin/mv", "rx") or die "Unable to unveil $!";
+ unveil("/home/", "rwxc") or die "Unable to unveil $!";
+}
+
+# !shell <username> <email>
+# !shell captcha <captcha>
+sub mshell {
+ my ($bot, $nick, $host, $hand, @args) = @_;
+ my ($chan, $text);
+ if (@args == 2) {
+ ($chan, $text) = ($args[0], $args[1]);
+ } else { $text = $args[0]; }
+ my $hostmask = "$nick!$host";
+ if (defined($chan) && $chans =~ /$chan/) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $chan :$nick: Please check private message");
+ }
+ if ($text =~ /^$/) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Type !help for new instructions");
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$staff: Help *$nick* on network ".$bot->{name}." with shell registration");
+ }
+ return;
+ } elsif (IRCNOW::IO::IRC::isstaff($bot, $nick) && $text =~ /^delete\s+([[:ascii:]]+)/) {
+ my $username = $1;
+ if (BotNow::SQLite::deleterows("shell", "username", $username)) {
+ # TODO delete shell
+ deleteshell($username);
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $chan :$username deleted");
+ }
+ }
+ return;
+ } elsif (IRCNOW::IO::IRC::isstaff($bot, $nick) && $text =~ /^approve\s+([[:ascii:]]+)/) {
+ my $username = $1;
+ system "doas usermod -U $username";
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $chan :$username approved");
+ }
+ return;
+ }
+ ### TODO: Check duplicate emails ###
+ my @rows = BotNow::SQLite::selectrows("irc", "nick", $nick);
+ foreach my $row (@rows) {
+ my $password = BotNow::SQLite::get("shell", "ircid", $row->{id}, "password");
+ if (defined($password)) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Sorry, only one account per person. Please contact staff if you need help.");
+ return;
+ }
+ }
+ if ($text =~ /^lastseen\s+([[:alnum:]]+)/) {
+ }
+ if ($text =~ /^captcha\s+([[:alnum:]]+)/) {
+ my $text = $1;
+ my $ircid = BotNow::SQLite::id("irc", "nick", $nick, $expires);
+ if (!defined($ircid)) { die "undefined ircid"; }
+ my $captcha = BotNow::SQLite::get("shell", "ircid", $ircid, "captcha");
+ if ($text ne $captcha) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Wrong captcha. To get a new captcha, type !shell <username> <email>");
+ return;
+ }
+ my $pass = BotNow::Hash::newpass();
+ chomp(my $encrypted = `encrypt $pass`);
+ my $username = BotNow::SQLite::get("shell", "ircid", $ircid, "username");
+ my $email = BotNow::SQLite::get("shell", "ircid", $ircid, "email");
+ my $version = BotNow::SQLite::get("shell", "ircid", $ircid, "version");
+ my $bindhost = "$username.$hostname";
+ BotNow::SQLite::set("shell", "ircid", $ircid, "password", $encrypted);
+ if (DNS::nextdns($username)) {
+ sleep(2);
+ createshell($username, $pass, $bindhost);
+ mailshell($username, $email, $pass, "shell", $version);
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Check your email!");
+ if ($approval eq "true") {
+ system "doas usermod -Z $username";
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Your account has been created but must be manually approved by your admins ($staff) before it can be used.");
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$staff: $nick\'s account $username must be manually unblocked before it can be used.");
+ }
+ }
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$staff: $nick\'s shell registration of $username on $bot->{name} was successful, *but* you *must* help him connect. Most users are unable to connect. Show him https://wiki.ircnow.org/?n=Shell.Shell");
+ }
+
+
+ #www($newnick, $reply, $password, "bouncer");
+ } else {
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $chan :Assigning bindhost $bindhost failed");
+ }
+ }
+ return;
+ } elsif ($text =~ /^([[:alnum:]]+)\s+([[:ascii:]]+)/) {
+ my ($username, $email) = ($1, $2);
+ my @users = col($passpath, 1, ":");
+ my @matches = grep(/^$username$/i, @users);
+ if (scalar(@matches) > 0) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Sorry, username taken. Please choose another username, or contact staff for help.");
+ return;
+ }
+ # my $captcha = join'', map +(0..9,'a'..'z','A'..'Z')[rand(10+26*2)], 1..4;
+ my $captcha = int(rand(999));
+ my $ircid = int(rand(2147483647));
+ BotNow::SQLite::set("irc", "id", $ircid, "localtime", time());
+ BotNow::SQLite::set("irc", "id", $ircid, "date", BotNow::IO::date());
+ BotNow::SQLite::set("irc", "id", $ircid, "hostmask", $hostmask);
+ BotNow::SQLite::set("irc", "id", $ircid, "nick", $nick);
+ BotNow::SQLite::set("shell", "ircid", $ircid, "username", $username);
+ BotNow::SQLite::set("shell", "ircid", $ircid, "email", $email);
+ BotNow::SQLite::set("shell", "ircid", $ircid, "captcha", $captcha);
+ IRCNOW::IO::IRC::whois($bot->{sock}, $nick);
+ IRCNOW::IO::IRC::ctcp($bot->{sock}, $nick);
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :".`figlet $captcha`);
+ # IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :$captchaURL".encode_base64($captcha));
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Type !shell captcha <text>");
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$nick\'s captcha on $bot->{name} is $captcha");
+ }
+ } else {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Invalid username or email. Type !shell <username> <email> to try again.");
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $chan :$staff: Help *$nick* on network ".$bot->{name}." with shell registration");
+ }
+ }
+}
+sub mailshell {
+ my( $username, $email, $password, $service, $version )=@_;
+ my $passhash = sha256_hex("$username");
+ my $versionhash = encode_base64($version);
+ my $approvemsg;
+ if ($approval eq "true") {
+ $approvemsg = <<"EOF";
+
+*IMPORTANT*: Your account has been created but it has not yet been
+approved. To get your account approved, please contact your admins
+$staff on IRC and by email.
+
+EOF
+ }
+
+ my $body = <<"EOF";
+You created a shell account!
+
+Username: $username
+Password: $password
+Server: $hostname
+SSH Port: 22
+Your Ports: $startPort to $endPort
+
+To customize your vhost, connect to ask in $chans
+$approvemsg
+*IMPORTANT*: Verify your email address:
+
+Please reply to this email to indicate you have received the email. You must
+reply in order to keep your account.
+
+IRCNow
+EOF
+ BotNow::IO::mail($mailfrom, $email, $mailname, "Verify IRCNow Account", $body);
+}
+
+
+#sub mregex {
+# my ($bot, $nick, $host, $hand, $text) = @_;
+# if ($staff !~ /$nick/) { return; }
+# if ($text =~ /^ips?\s+([-_()|0-9A-Za-z:\.?*\s]{3,})$/) {
+# my $ips = $1; # space-separated list of IPs
+# IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :".regexlist($ips));
+# } elsif ($text =~ /^users?\s+([-_()|0-9A-Za-z:\.?*\s]{3,})$/) {
+# my $users = $1; # space-separated list of usernames
+# IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :".regexlist($users));
+# } elsif ($text =~ /^[-_()|0-9A-Za-z:,\.?*\s]{3,}$/) {
+# my @lines = regex($text);
+# foreach my $l (@lines) { print "$l\n"; }
+# }
+#}
+#sub mforeach {
+# my ($bot, $nick, $host, $hand, $text) = @_;
+# if ($staff !~ /$nick/) { return; }
+# if ($text =~ /^network\s+del\s+([[:graph:]]+)\s+(#[[:graph:]]+)$/) {
+# my ($user, $chan) = ($1, $2);
+# foreach my $n (@main::networks) {
+# IRCNOW::IO::IRC::putserv($bot, "PRIVMSG *controlpanel :delchan $user $n->{name} $chan");
+# }
+# }
+#}
+
+#sub loadlog {
+# open(my $fh, '<', "$authlog") or die "Could not read file 'authlog' $!";
+# chomp(@logs = <$fh>);
+# close $fh;
+#}
+
+# return all lines matching a pattern
+#sub regex {
+# my ($pattern) = @_;
+# if (!@logs) { loadlog(); }
+# return grep(/$pattern/, @logs);
+#}
+
+# given a list of IPs, return matching users
+# or given a list of users, return matching IPs
+#sub regexlist {
+# my ($items) = @_;
+# my @items = split /[,\s]+/m, $items;
+# my $pattern = "(".join('|', @items).")";
+# if (!@logs) { loadlog(); }
+# my @matches = grep(/$pattern/, @logs);
+# my @results;
+# foreach my $match (@matches) {
+# if ($match =~ /^\[\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\] \[([^]\/]+)(\/[^]]+)?\] connected to ZNC from (.*)/) {
+# my ($user, $ip) = ($1, $3);
+# if ($items =~ /[.:]/) { # items are IP addresses
+# push(@results, $user);
+# } else { # items are users
+# push(@results, $ip);
+# }
+# }
+# }
+# my @sorted = sort @results;
+# @results = do { my %seen; grep { !$seen{$_}++ } @sorted }; # uniq
+# return join(' ', @results);
+#}
+
+sub createshell {
+ my ($username, $password, $bindhost) = @_;
+ system "doas groupadd $username";
+ system "doas adduser -batch $username $username $username `encrypt $password`";
+ system "doas chmod 700 /home/$username /home/$username/.ssh";
+ system "doas chmod 600 /home/$username/{.Xdefaults,.cshrc,.cvsrc,.login,.mailrc,.profile}";
+ system "doas mkdir /var/www/htdocs/$username";
+ system "doas ln -s /var/www/htdocs/$username /home/$username/htdocs";
+ system "doas chown -R $username:www /var/www/htdocs/$username /home/$username/htdocs";
+ system "doas chmod -R o-rx /var/www/htdocs/$username /home/$username/htdocs";
+ system "doas chmod -R g+rwx /var/www/htdocs/$username /home/$username/htdocs";
+ system "doas chown root:wheel $httpdconfpath $pfconfpath $acmeconfpath $relaydconfpath";
+ system "doas chmod g+rw $httpdconfpath $pfconfpath $acmeconfpath $relaydconfpath";
+ my $lusername = lc $username;
+ my $block = <<"EOF";
+server "$lusername.$hostname" {
+ listen on * port 80
+ location "/.well-known/acme-challenge/*" {
+ root "/acme"
+ request strip 2
+ }
+ location "*.php" {
+ fastcgi socket "/run/php-fpm.sock"
+ }
+ root "/htdocs/$username"
+}
+EOF
+ appendfile($httpdconfpath, $block);
+ $block = <<"EOF";
+domain "$lusername.$hostname" {
+ domain key "/etc/ssl/private/$lusername.$hostname.key"
+ domain full chain certificate "/etc/ssl/$lusername.$hostname.crt"
+ sign with letsencrypt
+}
+EOF
+ appendfile($acmeconfpath, $block);
+ configurepf($username);
+ system "doas rcctl reload httpd";
+ system "doas acme-client -F $lusername.$hostname";
+ system "doas ln -s /etc/ssl/$lusername.$hostname.crt /etc/ssl/$lusername.$hostname.fullchain.pem";
+ system "doas pfctl -f /etc/pf.conf";
+ configurerelayd($username);
+ $block = <<"EOF";
+~ * * * * acme-client $lusername.$hostname && rcctl reload relayd
+EOF
+ system "echo $block | doas crontab -";
+ system "doas usermod -L $loginclass $username";
+#edquota $username
+ return 1;
+}
+
+sub deleteshell {
+ my ($username, $bindhost) = @_;
+ my $lusername = lc $username;
+ system "doas chown root:wheel $httpdconfpath $pfconfpath $acmeconfpath $relaydconfpath";
+ system "doas chmod g+rw $httpdconfpath $pfconfpath $acmeconfpath $relaydconfpath";
+ system "doas groupdel $username";
+ system "doas userdel $username";
+ system "doas rm -f /etc/ssl/$lusername.$hostname.crt /etc/ssl/$lusername.$hostname.fullchain.pem /etc/ssl/private/$lusername.$hostname.key";
+ my $httpdconf = readstr($httpdconfpath);
+ my $block = <<"EOF";
+server "$lusername.$hostname" {
+ listen on * port 80
+ location "/.well-known/acme-challenge/*" {
+ root "/acme"
+ request strip 2
+ }
+ location "*.php" {
+ fastcgi socket "/run/php-fpm.sock"
+ }
+ root "/htdocs/$username"
+}
+EOF
+ $block =~ s/{/\\{/gm;
+ $block =~ s/}/\\}/gm;
+ $block =~ s/\./\\./gm;
+ $block =~ s/\*/\\*/gm;
+ $httpdconf =~ s{$block}{}gm;
+ print $httpdconf;
+ writefile($httpdconfpath, $httpdconf);
+
+ my $acmeconf = readstr($acmeconfpath);
+ $block = <<"EOF";
+domain "$lusername.$hostname" {
+ domain key "/etc/ssl/private/$lusername.$hostname.key"
+ domain full chain certificate "/etc/ssl/$lusername.$hostname.fullchain.pem"
+ sign with letsencrypt
+}
+EOF
+ $block =~ s/{/\\{/gm;
+ $block =~ s/}/\\}/gm;
+ $block =~ s/\./\\./gm;
+ $block =~ s/\*/\\*/gm;
+ $acmeconf =~ s{$block}{}gm;
+ writefile($acmeconfpath, $acmeconf);
+ return 1;
+}
+
+#TODO Fix for $i
+# Return column $i from $filename as an array with file separator $FS
+sub col {
+ my ($filename, $i, $FS) = @_;
+ my @rows = readarray($filename);
+ my @results;
+ foreach my $row (@rows) {
+ if ($row =~ /^(.*?)$FS/) {
+ push(@results, $1);
+ }
+ }
+ return @results;
+}
+
+sub configurepf {
+ my $username = shift;
+ my @read = split('\n', readstr($pfconfpath) );
+
+ my $previousline = "";
+ my @pfcontent;
+ foreach my $line(@read)
+ {
+ my $currline = $line;
+ if( $currline ne "# end user ports") {
+ $previousline = $currline;
+ } else {
+ #pass in proto {tcp udp} to port {31361:31370} user {JL}
+ if( $previousline =~ /(\d*):(\d*)/ ) {
+ my $startport = ( $1 + 10 );
+ my $endport = ( $2 + 10 );
+ my $insert = "pass in proto {tcp udp} to port {$startport:$endport} user {$username}";
+ push(@pfcontent, $insert);
+ $startPort = $startport;
+ $endPort = $endport;
+ }
+ }
+ push(@pfcontent, $currline)
+ }
+ writefile("$pfconfpath", join("\n",@pfcontent))
+}
+
+sub configurerelayd {
+ my ($username) = @_;
+ my $block = "tls { keypair $username.$hostname }";
+ my $relaydconf = readstr($relaydconfpath);
+ my $newconf;
+ if ($relaydconf =~ /^.*tls\s+{\s+keypair\s+[.0-9a-zA-Z]+\s*}/m) {
+ $newconf = "$`$&\n\t$block$'";
+ } else {
+ $newconf = $relaydconf;
+ debug(ERRORS, "ERROR: regex can't match tls { keypair \$username.$hostname }");
+ }
+ writefile($relaydconfpath, $newconf);
+}
+
+#unveil("./newacct", "rx") or die "Unable to unveil $!";
+1; # MUST BE LAST STATEMENT IN FILE
blob - /dev/null
blob + 74f111b30cdf483d670c95dd133b06cb00ccc4c1 (mode 644)
--- /dev/null
+++ lib/IRCNOW/Acct/VPN.pm
+package BotNow::VPN;
+
+use strict;
+use warnings;
+use OpenBSD::Pledge;
+use OpenBSD::Unveil;
+use lib qw(./lib);
+use IRCNOW::IO qw(:DEBUG readarray);
+use IRCNOW::IO::IRC;
+use BotNow::DNS;
+use BotNow::SQLite;
+
+my %conf = %main::conf;
+my $chans = $conf{chans};
+my $teamchans = $conf{teamchans};
+my @teamchans = split /[,\s]+/m, $teamchans;
+my $staff = $conf{staff};
+my $expires = $conf{expires};
+my $ikedconf = $conf{ikedconf} || "/etc/iked.conf";
+# File containing IRC networks
+my $netpath = "networks";
+my @networks;
+
+IRCNOW::IO::IRC::cbind("pub", "-", "vpn", \&vpn);
+IRCNOW::IO::IRC::cbind("msg", "-", "vpn", \&vpn);
+
+sub init {
+# unveil("/usr/bin/rcctl", "rx") or die "Unable to unveil $!";
+ unveil($ikedconf, "crx") or die "Unable to unveil $!";
+}
+
+sub vpn {
+ my ($bot, $nick, $host, $hand, @args) = @_;
+ my ($chan, $text);
+ if (@args == 2) {
+ ($chan, $text) = ($args[0], $args[1]);
+ } else { $text = $args[0]; }
+ my $hostmask = "$nick!$host";
+ if (defined($chan) && $chans =~ /$chan/) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $chan :$nick: Please check private message");
+ }
+ if ($text =~ /^$/) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Type !help for new instructions");
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$staff: Help *$nick* on network".$bot->{name});
+ }
+ return;
+ }
+ my @rows = BotNow::SQLite::selectrows("irc", "nick", $nick);
+ foreach my $row (@rows) {
+ my $password = BotNow::SQLite::get("vpn", "ircid", $row->{id}, "password");
+ if (defined($password)) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Sorry, only one account per person. Please contact staff if you need help.");
+ return;
+ }
+ }
+ if ($text =~ /^captcha\s+([[:alnum:]]+)/) {
+ my $text = $1;
+ my $ircid = BotNow::SQLite::id("irc", "nick", $nick, $expires);
+ if (!defined($ircid)) { die "undefined ircid"; }
+ my $captcha = BotNow::SQLite::get("vpn", "ircid", $ircid, "captcha");
+ if ($text ne $captcha) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Wrong captcha. To get a new captcha, type !vpn <username> <email>");
+ return;
+ }
+ my $pass = Hash::newpass();
+ chomp(my $encrypted = `encrypt $pass`);
+ my $username = BotNow::SQLite::get("vpn", "ircid", $ircid, "username");
+ my $email = BotNow::SQLite::get("vpn", "ircid", $ircid, "email");
+ my $version = BotNow::SQLite::get("vpn", "ircid", $ircid, "version");
+ BotNow::SQLite::set("vpn", "ircid", $ircid, "password", $encrypted);
+
+ createvpn($username, $pass);
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$staff: vpn created for $username");
+ }
+ my $msg = <<"EOF";
+Your vpn account has been created! Username: $username with password: $pass
+Our official support channel is #vpn. To connect, please follow these instructions:
+https://wiki.ircnow.org/Vpn/Vpn
+EOF
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :$msg");
+ } elsif ($text =~ /^([[:alnum:]]+)\s+([[:ascii:]]+)/) {
+ my ($username, $email) = ($1, $2);
+ if ($staff !~ /$nick/) {
+ return;
+ }
+ my @users = col($ikedconf);
+ my @matches = grep(/^$username$/i, @users);
+ if (scalar(@matches) > 0) {
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Sorry, username taken. Please choose another username, or contact staff for help.");
+ return;
+ }
+
+ my $captcha = int(rand(999));
+ my $ircid = int(rand(2147483647));
+ BotNow::SQLite::set("irc", "id", $ircid, "localtime", time());
+ BotNow::SQLite::set("irc", "id", $ircid, "date", BotNow::IO::date());
+ BotNow::SQLite::set("irc", "id", $ircid, "hostmask", $hostmask);
+ BotNow::SQLite::set("irc", "id", $ircid, "nick", $nick);
+ BotNow::SQLite::set("vpn", "ircid", $ircid, "username", $username);
+ BotNow::SQLite::set("vpn", "ircid", $ircid, "email", $email);
+ BotNow::SQLite::set("vpn", "ircid", $ircid, "captcha", $captcha);
+ IRCNOW::IO::IRC::whois($bot->{sock}, $nick);
+ IRCNOW::IO::IRC::ctcp($bot->{sock}, $nick);
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :".`figlet $captcha`);
+ # IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :$captchaURL".encode_base64($captcha));
+ IRCNOW::IO::IRC::putserv($bot, "PRIVMSG $nick :Type !vpn captcha <text>");
+ foreach my $chan (@teamchans) {
+ IRCNOW::IO::IRC::putservlocalnet($bot, "PRIVMSG $chan :$nick\'s captcha on $bot->{name} is $captcha");
+ }
+ }
+}
+sub createvpn {
+ my ($username, $password) = @_;
+ `doas sh -c 'echo "user $username $password" >> /etc/iked.conf'`;
+ `doas rcctl reload iked`;
+}
+sub col {
+ my ($filename) = @_;
+ my @rows = readarray($filename);
+ my @results;
+ foreach my $row (@rows) {
+ if ($row =~ /^user (.*?) /) {
+ push(@results, $1);
+ }
+ }
+ return @results;
+}
+
+#sub init {
+#}
+# if ($reply =~ /^!vpn (.*) ([-_0-9a-zA-Z]+)$/i) {
+# my $ircnick = $1;
+# my $newnick = $2;
+# if ($staff !~ /$sender/) {
+# return;
+# }
+# my $password = newpass();
+# createvpn($password, $newnick);
+# sendmsg($bot, $sender, "vpn created for $newnick");
+#my $msg = <<"EOF";
+#Your vpn account has been created! Username: $newnick with password: $password
+#Our official support channel is #vpn. To connect, please follow these instructions: https://ircnow.org/kb/doku.php?id=vpn:vpn .
+#EOF
+# sendmsg($bot, $ircnick, $msg);
+# }
+#sub createvpn {
+# my ($password, $username) = @_;
+# `doas sh -c 'echo "user '$username' '$password'" >> /etc/iked.conf'`;
+# `doas rcctl reload iked`;
+#}
+
+1; # MUST BE LAST STATEMENT IN FILE