Blob


1 #!/usr/bin/perl
3 package Mail;
5 use strict;
6 use warnings;
7 use OpenBSD::Pledge;
8 use OpenBSD::Unveil;
9 use File::Copy qw(copy);
10 use MIME::Base64;
11 use Digest::SHA qw(sha256_hex);
13 my %conf = %main::conf;
14 my $chans = $conf{chans};
15 my $staff = $conf{staff};
16 my $mailhostname = $conf{mailhostname};
17 my $mailfrom = $conf{mailfrom};
18 my $mailname = $conf{mailname};
19 my $imapport = $conf{imapport};
20 my $smtpport = $conf{smtpport};
21 my $teamchans = $conf{teamchans};
22 my @teamchans = split /[,\s]+/m, $teamchans;
23 my $webmail = $conf{webmail};
24 my $approval = $conf{approval};
25 my $expires = $conf{expires};
26 my $passwdpath = "/etc/mail/passwd";
27 my $virtualspath = "/etc/mail/virtuals";
28 my $senderspath = "/etc/mail/users";
29 my @users;
31 main::cbind("msg", "-", "mail", \&mmail);
33 sub init {
34 #dependencies for encrypt
35 unveil("/usr/bin/encrypt", "rx") or die "Unable to unveil $!";
36 #dependencies for mail
37 unveil("/usr/sbin/sendmail", "rx") or die "Unable to unveil $!";
38 unveil($passwdpath, "rwc") or die "Unable to unveil $!";
39 unveil($virtualspath, "rwc") or die "Unable to unveil $!";
40 unveil($senderspath, "rwc") or die "Unable to unveil $!";
41 unveil("$passwdpath.bak", "rwc") or die "Unable to unveil $!";
42 unveil("$virtualspath.bak", "rwc") or die "Unable to unveil $!";
43 unveil("$senderspath.bak", "rwc") or die "Unable to unveil $!";
44 unveil("/usr/lib/libutil.so.13.1", "r") or die "Unable to unveil $!";
45 unveil("/bin/sh", "rx") or die "Unable to unveil $!";
46 }
48 sub mmail {
49 my ($bot, $nick, $host, $hand, @args) = @_;
50 my ($chan, $text);
51 if (@args == 2) {
52 ($chan, $text) = ($args[0], $args[1]);
53 } else { $text = $args[0]; }
54 my $hostmask = "$nick!$host";
55 if (defined($chan) && $chans =~ /$chan/) {
56 main::putserv($bot, "PRIVMSG $chan :$nick: Please check private message");
57 }
58 if ($text =~ /^$/) {
59 main::putserv($bot, "PRIVMSG $nick :Type !help for new instructions");
60 foreach my $chan (@teamchans) {
61 main::putservlocalnet($bot, "PRIVMSG $chan :$staff: Help *$nick* on network ".$bot->{name}." with mail");
62 }
63 return;
64 } elsif (main::isstaff($bot, $nick) && $text =~ /^delete\s+([[:ascii:]]+)/) {
65 my $username = $1;
66 if (SQLite::deleterows("mail", "username", $username)) {
67 deletemail($username);
68 foreach my $chan (@teamchans) {
69 main::putserv($bot, "PRIVMSG $chan :$username email deleted");
70 }
71 }
72 return;
73 } elsif (main::isstaff($bot, $nick) && $text =~ /^approve\s+([[:ascii:]]+)/) {
74 my $username = $1;
75 my @passwd = main::readarray($passwdpath);
76 foreach my $line (@passwd) {
77 $line =~ s/^#(${username}\@${mailhostname}.*)/$1/;
78 }
79 # trailing newline necessary
80 `doas touch $passwdpath.bak`;
81 `doas chmod g+w $passwdpath.bak`;
82 main::writefile("$passwdpath.bak", join("\n", @passwd)."\n");
83 copy "${passwdpath}.bak", $passwdpath;
85 foreach my $chan (@teamchans) {
86 main::putserv($bot, "PRIVMSG $chan :$username mail approved");
87 }
88 return;
89 }
90 ### Check duplicate hostmasks ###
91 my @rows = SQLite::selectrows("irc", "hostmask", $hostmask);
92 foreach my $row (@rows) {
93 my $password = SQLite::get("mail", "ircid", $row->{id}, "password");
94 if (defined($password)) {
95 main::putserv($bot, "PRIVMSG $nick :Sorry, only one account per person. Please contact staff if you need help.");
96 return;
97 }
98 }
100 if ($text =~ /^captcha\s+([[:alnum:]]+)/) {
101 my $text = $1;
102 # TODO avoid using host mask because cloaking can cause problems
103 my $ircid = SQLite::id("irc", "nick", $nick, $expires);
104 my $captcha = SQLite::get("mail", "ircid", $ircid, "captcha");
105 if ($text ne $captcha) {
106 main::putserv($bot, "PRIVMSG $nick :Wrong captcha. To get a new captcha, type !mail <username> <email>");
107 return;
109 my $pass = Hash::newpass();
110 chomp(my $encrypted = `encrypt $pass`);
111 my $username = SQLite::get("mail", "ircid", $ircid, "username");
112 my $email = SQLite::get("mail", "ircid", $ircid, "email");
113 my $hashirc = SQLite::get("irc", "id", $ircid, "hashid");
114 SQLite::set("mail", "ircid", $ircid, "password", $encrypted);
115 sleep(2);
116 createmail($pass, $username);
117 main::putserv($bot, "PRIVMSG $nick :Check your email!");
118 sleep(5);
119 mailmail($username, $pass, $email);
120 if ($approval) {
121 my @passwd = main::readarray($passwdpath);
122 foreach my $line (@passwd) {
123 $line =~ s/^(${username}\@${mailhostname}.*)/#$1/;
125 # trailing newline necessary
126 `doas touch $passwdpath.bak`;
127 `doas chmod g+w $passwdpath.bak`;
128 main::writefile("$passwdpath.bak", join("\n", @passwd)."\n");
129 copy "${passwdpath}.bak", $passwdpath;
131 main::putserv($bot, "PRIVMSG $nick :Your account has been created but must be manually approved by your admins ($staff) before it can be used.");
132 foreach my $chan (@teamchans) {
133 main::putservlocalnet($bot, "PRIVMSG $chan :$staff: $nick\'s account $username must be manually unblocked before it can be used.");
136 foreach my $chan (@teamchans) {
137 main::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");
139 #www($newnick, $reply, $password, "bouncer");
140 return;
141 } elsif ($text =~ /^([[:alnum:]]+)\s+([[:ascii:]]+)/) {
142 my ($username, $email) = ($1, $2);
143 my @userrows = SQLite::selectrows("mail", "username", $username);
144 foreach my $row (@userrows) {
145 my $password = SQLite::get("mail", "ircid", $row->{id}, "password");
146 if (defined($password)) {
147 main::putserv($bot, "PRIVMSG $nick :Sorry, only one account per person. Please contact staff if you need help.");
148 return;
151 my @emailrows = SQLite::selectrows("mail", "email", $email);
152 foreach my $row (@userrows) {
153 my $password = SQLite::get("mail", "ircid", $row->{id}, "password");
154 if (defined($password)) {
155 main::putserv($bot, "PRIVMSG $nick :Sorry, only one account per person. Please contact staff if you need help.");
156 return;
160 # my @users = treeget($znctree, "User", "Node");
161 foreach my $user (@users) {
162 if ($user eq $username) {
163 main::putserv($bot, "PRIVMSG $nick :Sorry, username taken. Please contact staff if you need help.");
164 return;
168 #my $captcha = join'', map +(0..9,'a'..'z','A'..'Z')[rand(10+26*2)], 1..4;
169 my $captcha = int(rand(999));
170 my $ircid = int(rand(9223372036854775807));
171 my $hashid = sha256_hex("$ircid");
172 SQLite::set("irc", "id", $ircid, "localtime", time());
173 SQLite::set("irc", "id", $ircid, "hashid", sha256_hex($ircid));
174 SQLite::set("irc", "id", $ircid, "date", main::date());
175 SQLite::set("irc", "id", $ircid, "hostmask", $hostmask);
176 SQLite::set("irc", "id", $ircid, "nick", $nick);
177 SQLite::set("mail", "ircid", $ircid, "username", $username);
178 SQLite::set("mail", "ircid", $ircid, "email", $email);
179 SQLite::set("mail", "ircid", $ircid, "captcha", $captcha);
180 SQLite::set("mail", "ircid", $ircid, "hashid", $hashid);
181 main::whois($bot, $nick);
182 main::ctcp($bot, $nick);
183 main::putserv($bot, "PRIVMSG $nick :".`figlet $captcha`);
184 #main::putserv($bot, "PRIVMSG $nick :https://$hostname/$hashid/captcha.png");
185 #main::putserv($bot, "PRIVMSG $nick :https://$hostname/register.php?hashirc=$hashid");
186 main::putserv($bot, "PRIVMSG $nick :Type !mail captcha <text>");
187 foreach my $chan (@teamchans) {
188 main::putservlocalnet($bot, "PRIVMSG $chan :$nick\'s on $bot->{name} mail captcha is $captcha");
190 } else {
191 main::putserv($bot, "PRIVMSG $nick :Invalid username or email. Type !mail <username> <email> to try again.");
192 foreach my $chan (@teamchans) {
193 main::putservlocalnet($bot, "PRIVMSG $chan :$staff: Help *$nick* on network ".$bot->{name}." with mail");
198 sub mailmail {
199 my( $username, $password, $email )=@_;
200 my $approvemsg;
201 if ($approval eq "true") {
202 $approvemsg = <<"EOF";
204 *IMPORTANT*: Your account has been created but it has not yet been
205 approved. To get your account approved, please contact your admins
206 $staff on IRC and by email.
208 EOF
210 my $body = <<"EOF";
211 Welcome to IRCNow!
213 You created an email account:
215 Username: $username\@$mailhostname
216 Password: $password
217 Server: $mailhostname
218 IMAP Port: $imapport (STARTTLS)
219 SMTP Port: $smtpport (STARTTLS)
220 Webpanel: $webmail
221 $approvemsg
222 *IMPORTANT*: Verify your email address:
224 Please reply to this email to indicate you have received the email. You must
225 reply in order to keep your account.
227 Connection Instructions: https://wiki.ircnow.org/?n=Email.Email
229 IRCNow
230 EOF
231 main::mail($mailfrom, $email, $mailname, "Verify IRCNow Account", $body);
235 sub createmail {
236 my ($password, $username) = @_;
237 chomp(my $encrypted = `encrypt $password`);
238 my $line = "${username}\@$mailhostname:${encrypted}::::::userdb_quota_rule=*:storage=1G";
239 $line =~ s{\$}{\\\$}g;
240 my $line2 = "${username}\@$mailhostname vmail";
241 my $line3 = "${username}\@$mailhostname: ${username}\@$mailhostname";
242 `doas sh -c 'echo $line >> $passwdpath'`;
243 `doas sh -c 'echo $line2 >> $virtualspath'`;
244 `doas sh -c 'echo $line3 >> $senderspath'`;
245 `doas smtpctl update table passwd`;
246 `doas smtpctl update table virtuals`;
247 `doas smtpctl update table users`;
248 `doas rcctl reload dovecot`;
251 sub deletemail {
252 my ($username) = @_;
253 my @passwd = main::readarray($passwdpath);
254 my @virtuals = main::readarray($virtualspath);
255 my @senders = main::readarray($senderspath);
256 @passwd = grep !/^${username}\@${mailhostname}/, @passwd;
257 @virtuals = grep !/^${username}\@${mailhostname}/, @virtuals;
258 @senders = grep !/^${username}\@${mailhostname}/, @senders;
260 # trailing newline necessary
261 `doas touch $passwdpath.bak`;
262 `doas touch $virtualspath.bak`;
263 `doas touch $senderspath.bak`;
264 `doas chmod g+w $passwdpath.bak $virtualspath.bak $senderspath.bak`;
265 main::writefile("$passwdpath.bak", join("\n", @passwd)."\n");
266 copy "${passwdpath}.bak", $passwdpath;
267 main::writefile("$virtualspath.bak", join("\n", @virtuals)."\n");
268 copy "${virtualspath}.bak", $virtualspath;
269 main::writefile("$senderspath.bak", join("\n", @senders)."\n");
270 copy "${senderspath}.bak", $senderspath;
272 `doas smtpctl update table passwd`;
273 `doas smtpctl update table virtuals`;
274 `doas smtpctl update table users`;
275 `doas rcctl reload dovecot`;
277 1; # MUST BE LAST STATEMENT IN FILE