use PMG::Config;
use PMG::DBTools;
use PMG::HTMLMail;
+use PMG::Quarantine;
use base qw(PVE::RESTHandler);
$res->{sender} = $sender if $sender && ($sender ne $res->{from});
$res->{envelope_sender} = $ref->{sender};
- $res->{receiver} = $ref->{receiver};
+ $res->{receiver} = $ref->{receiver} // $ref->{pmail};
$res->{id} = 'C' . $ref->{cid} . 'R' . $ref->{rid};
$res->{time} = $ref->{time};
$res->{bytes} = $ref->{bytes};
my $dbh = PMG::DBTools::open_ruledb();
- my $list = PMG::DBTools::add_to_blackwhite($dbh, $pmail, $listname);
+ my $list = PMG::Quarantine::add_to_blackwhite($dbh, $pmail, $listname);
my $res = [];
foreach my $a (@$list) { push @$res, { address => $a }; }
my $username = $ref->{pmail};
if ($action eq 'whitelist') {
- PMG::DBTools::add_to_blackwhite($dbh, $username, 'WL', [ $sender ]);
+ PMG::Quarantine::add_to_blackwhite($dbh, $username, 'WL', [ $sender ]);
} elsif ($action eq 'blacklist') {
- PMG::DBTools::add_to_blackwhite($dbh, $username, 'BL', [ $sender ]);
+ PMG::Quarantine::add_to_blackwhite($dbh, $username, 'BL', [ $sender ]);
} elsif ($action eq 'deliver') {
+ my $targets = [ $ref->{pmail} ];
+ PMG::Quarantine::deliver_quarantined_mail($dbh, $ref, $targets);
} elsif ($action eq 'delete') {
+ PMG::Quarantine::delete_quarantined_mail($dbh, $ref);
+ } else {
+ die "internal error"; # should not be reached
}
return undef;
}});
+
1;
return $res;
}
-sub add_to_blackwhite {
- my ($dbh, $username, $listname, $addrs, $delete) = @_;
-
- my $name = $listname eq 'BL' ? 'BL' : 'WL';
- my $oname = $listname eq 'BL' ? 'WL' : 'BL';
- my $qu = $dbh->quote ($username);
-
- my $sth = $dbh->prepare(
- "SELECT * FROM UserPrefs WHERE pmail = $qu AND (Name = 'BL' OR Name = 'WL')");
- $sth->execute();
-
- my $list = { 'WL' => {}, 'BL' => {} };
-
- while (my $ref = $sth->fetchrow_hashref()) {
- my $data = $ref->{data};
- $data =~ s/[,;]/ /g;
- my @alist = split('\s+', $data);
-
- my $tmp = {};
- foreach my $a (@alist) {
- if ($a =~ m/^[[:ascii:]]+$/) {
- $tmp->{$a} = 1;
- }
- }
-
- $list->{$ref->{name}} = $tmp;
- }
-
- $sth->finish;
-
- if ($addrs) {
-
- foreach my $v (@$addrs) {
- die "email address '$v' is too long (> 512 characters)\n"
- if length($v) > 512;
-
- if ($delete) {
- delete($list->{$name}->{$v});
- } else {
- if ($v =~ m/[[:^ascii:]]/) {
- die "email address '$v' contains invalid characters\n";
- }
- $list->{$name}->{$v} = 1;
- delete ($list->{$oname}->{$v});
- }
- }
-
- my $wlist = $dbh->quote(join (',', keys %{$list->{WL}}) || '');
- my $blist = $dbh->quote(join (',', keys %{$list->{BL}}) || '');
-
- if (!$delete) {
- my $maxlen = 200000;
- die "whitelist size exceeds limit (> $maxlen bytes)\n"
- if length($wlist) > $maxlen;
- die "blacklist size exceeds limit (> $maxlen bytes)\n"
- if length($blist) > $maxlen;
- }
-
- $dbh->do(
- "DELETE FROM UserPrefs WHERE pmail = $qu AND (Name = 'WL' OR Name = 'BL');" .
- "INSERT INTO UserPrefs (PMail, Name, Data, MTime) " .
- "VALUES ($qu, 'BL', $blist, EXTRACT (EPOCH FROM now()));" .
- "INSERT INTO UserPrefs (PMail, Name, Data, MTime) " .
- "VALUES ($qu, 'WL', $wlist, EXTRACT (EPOCH FROM now()));");
- }
-
- my $values = [ keys %{$list->{$name}} ];
-
- return $values;
-}
-
1;
--- /dev/null
+package PMG::Quarantine;
+
+use strict;
+use warnings;
+use Net::SMTP;
+
+use PVE::SafeSyslog;
+use PVE::Tools;
+
+use PMG::Utils;
+use PMG::RuleDB;
+use PMG::MailQueue;
+
+sub add_to_blackwhite {
+ my ($dbh, $username, $listname, $addrs, $delete) = @_;
+
+ my $name = $listname eq 'BL' ? 'BL' : 'WL';
+ my $oname = $listname eq 'BL' ? 'WL' : 'BL';
+ my $qu = $dbh->quote ($username);
+
+ my $sth = $dbh->prepare(
+ "SELECT * FROM UserPrefs WHERE pmail = $qu AND (Name = 'BL' OR Name = 'WL')");
+ $sth->execute();
+
+ my $list = { 'WL' => {}, 'BL' => {} };
+
+ while (my $ref = $sth->fetchrow_hashref()) {
+ my $data = $ref->{data};
+ $data =~ s/[,;]/ /g;
+ my @alist = split('\s+', $data);
+
+ my $tmp = {};
+ foreach my $a (@alist) {
+ if ($a =~ m/^[[:ascii:]]+$/) {
+ $tmp->{$a} = 1;
+ }
+ }
+
+ $list->{$ref->{name}} = $tmp;
+ }
+
+ $sth->finish;
+
+ if ($addrs) {
+
+ foreach my $v (@$addrs) {
+ die "email address '$v' is too long (> 512 characters)\n"
+ if length($v) > 512;
+
+ if ($delete) {
+ delete($list->{$name}->{$v});
+ } else {
+ if ($v =~ m/[[:^ascii:]]/) {
+ die "email address '$v' contains invalid characters\n";
+ }
+ $list->{$name}->{$v} = 1;
+ delete ($list->{$oname}->{$v});
+ }
+ }
+
+ my $wlist = $dbh->quote(join (',', keys %{$list->{WL}}) || '');
+ my $blist = $dbh->quote(join (',', keys %{$list->{BL}}) || '');
+
+ if (!$delete) {
+ my $maxlen = 200000;
+ die "whitelist size exceeds limit (> $maxlen bytes)\n"
+ if length($wlist) > $maxlen;
+ die "blacklist size exceeds limit (> $maxlen bytes)\n"
+ if length($blist) > $maxlen;
+ }
+
+ $dbh->do(
+ "DELETE FROM UserPrefs WHERE pmail = $qu AND (Name = 'WL' OR Name = 'BL');" .
+ "INSERT INTO UserPrefs (PMail, Name, Data, MTime) " .
+ "VALUES ($qu, 'BL', $blist, EXTRACT (EPOCH FROM now()));" .
+ "INSERT INTO UserPrefs (PMail, Name, Data, MTime) " .
+ "VALUES ($qu, 'WL', $wlist, EXTRACT (EPOCH FROM now()));");
+ }
+
+ my $values = [ keys %{$list->{$name}} ];
+
+ return $values;
+}
+
+sub deliver_quarantined_mail {
+ my ($dbh, $ref, $targets) = @_;
+
+ my $filename = $ref->{file};
+ my $spooldir = $PMG::MailQueue::spooldir;
+ my $path = "$spooldir/$filename";
+
+ my $id = 'C' . $ref->{cid} . 'R' . $ref->{rid};
+
+ my $sender = 'postmaster'; # notify postmaster if something fails
+
+ my $smtp;
+
+ eval {
+ my $smtp = Net::SMTP->new ('127.0.0.1', Port => 10025, Hello => 'quarantine') ||
+ die "unable to connect to localhost at port 10025\n";
+
+ my $resid;
+
+ if (!$smtp->mail($sender)) {
+ die sprintf("smtp from error - got: %s %s\n", $smtp->code, $smtp->message);
+ }
+
+ if (!$smtp->to (@$targets)) {
+ die sprintf("smtp to error - got: %s %s\n", $smtp->code, $smtp->message);
+ }
+
+ $smtp->data();
+
+ my $header = 1;
+
+ open(my $fh, '<', $path) || die "unable to open file '$path' - $!\n";
+
+ while (defined(my $line = <$fh>)) {
+ chomp $line;
+ if ($header && ($line =~ m/^\s*$/)) {
+ $header = 0;
+ }
+
+ # skip Delivered-To and Return-Path (avoid problem with postfix
+ # forwarding loop detection (man local))
+ next if ($header && (($line =~ m/^Delivered-To:/i) || ($line =~ m/^Return-Path:/i)));
+
+ # rfc821 requires this
+ $line =~ s/^\./\.\./mg;
+ $smtp->datasend("$line\n");
+ }
+ close($fh);
+
+ if ($smtp->dataend()) {
+ my (@msgs) = $smtp->message;
+ my ($last_msg) = $msgs[$#msgs];
+ ($resid) = $last_msg =~ m/Ok: queued as ([0-9A-Z]+)/;
+ if (!$resid) {
+ die sprintf("smtp error - got: %s %s\n", $smtp->code, $smtp->message);
+ }
+ } else {
+ die sprintf("sending data failed - got: %s %s\n", $smtp->code, $smtp->message);
+ }
+
+ my $sth = $dbh->prepare(
+ "UPDATE CMSReceivers SET Status='D', MTime = ? " .
+ "WHERE CMailStore_CID = ? AND CMailStore_RID = ? AND PMail = ?");
+ $sth->execute(time(), $ref->{cid}, $ref->{rid}, $ref->{pmail});
+ $sth->finish;
+ };
+ my $err = $@;
+
+ $smtp->quit if $smtp;
+
+ if ($err) {
+ my $msg = "deliver quarantined mail '$id' ($path) failed: $err";
+ syslog('err', $msg);
+ die "$msg\n";
+ }
+
+ syslog('info', "delivered quarantined mail '$id' ($path)");
+
+ return 1;
+}
+
+sub delete_quarantined_mail {
+ my ($dbh, $ref) = @_;
+
+ my $filename = $ref->{file};
+ my $spooldir = $PMG::MailQueue::spooldir;
+ my $path = "$spooldir/$filename";
+
+ my $id = 'C' . $ref->{cid} . 'R' . $ref->{rid};
+
+ eval {
+ my $sth = $dbh->prepare(
+ "UPDATE CMSReceivers SET Status='D', MTime = ? WHERE " .
+ "CMailStore_CID = ? AND CMailStore_RID = ? AND PMail = ?");
+ $sth->execute (time(), $ref->{cid}, $ref->{rid}, $ref->{pmail});
+ $sth->finish;
+ };
+ if (my $err = $@) {
+ my $msg = "delete quarantined mail '$id' ($path) failed: $err";
+ syslog ('err', $msg);
+ die "$msg\n";
+ }
+
+ syslog ('info', "marked quarantined mail '$id' as deleted ($path)");
+
+ return 1;
+}
+
+
+1;