]> git.proxmox.com Git - pmg-api.git/blob - src/PMG/Quarantine.pm
bump version to 6.0-6
[pmg-api.git] / src / PMG / Quarantine.pm
1 package PMG::Quarantine;
2
3 use strict;
4 use warnings;
5 use Net::SMTP;
6
7 use PVE::SafeSyslog;
8 use PVE::Tools;
9
10 use PMG::Utils;
11 use PMG::RuleDB;
12 use PMG::MailQueue;
13
14 sub add_to_blackwhite {
15 my ($dbh, $username, $listname, $addrs, $delete) = @_;
16
17 my $name = $listname eq 'BL' ? 'BL' : 'WL';
18 my $oname = $listname eq 'BL' ? 'WL' : 'BL';
19 my $qu = $dbh->quote ($username);
20
21 my $sth = $dbh->prepare(
22 "SELECT * FROM UserPrefs WHERE pmail = $qu AND (Name = 'BL' OR Name = 'WL')");
23 $sth->execute();
24
25 my $list = { 'WL' => {}, 'BL' => {} };
26
27 while (my $ref = $sth->fetchrow_hashref()) {
28 my $data = $ref->{data};
29 $data =~ s/[,;]/ /g;
30 my @alist = split('\s+', $data);
31
32 my $tmp = {};
33 foreach my $a (@alist) {
34 if ($a =~ m/^[[:ascii:]]+$/) {
35 $tmp->{$a} = 1;
36 }
37 }
38
39 $list->{$ref->{name}} = $tmp;
40 }
41
42 $sth->finish;
43
44 if ($addrs) {
45
46 foreach my $v (@$addrs) {
47 die "email address '$v' is too long (> 512 characters)\n"
48 if length($v) > 512;
49
50 if ($delete) {
51 delete($list->{$name}->{$v});
52 } else {
53 if ($v =~ m/[[:^ascii:]]/) {
54 die "email address '$v' contains invalid characters\n";
55 }
56 $list->{$name}->{$v} = 1;
57 delete ($list->{$oname}->{$v});
58 }
59 }
60
61 my $wlist = $dbh->quote(join (',', keys %{$list->{WL}}) || '');
62 my $blist = $dbh->quote(join (',', keys %{$list->{BL}}) || '');
63
64 if (!$delete) {
65 my $maxlen = 200000;
66 die "whitelist size exceeds limit (> $maxlen bytes)\n"
67 if length($wlist) > $maxlen;
68 die "blacklist size exceeds limit (> $maxlen bytes)\n"
69 if length($blist) > $maxlen;
70 }
71
72 my $queries = "DELETE FROM UserPrefs WHERE pmail = $qu AND (Name = 'WL' OR Name = 'BL');";
73 if (scalar(keys %{$list->{WL}})) {
74 $queries .=
75 "INSERT INTO UserPrefs (PMail, Name, Data, MTime) " .
76 "VALUES ($qu, 'WL', $wlist, EXTRACT (EPOCH FROM now()));";
77 }
78 if (scalar(keys %{$list->{BL}})) {
79 $queries .=
80 "INSERT INTO UserPrefs (PMail, Name, Data, MTime) " .
81 "VALUES ($qu, 'BL', $blist, EXTRACT (EPOCH FROM now()));";
82 }
83 $dbh->do($queries);
84 }
85
86 my $values = [ keys %{$list->{$name}} ];
87
88 return $values;
89 }
90
91 sub deliver_quarantined_mail {
92 my ($dbh, $ref, $receiver) = @_;
93
94 my $filename = $ref->{file};
95 my $spooldir = $PMG::MailQueue::spooldir;
96 my $path = "$spooldir/$filename";
97
98 my $id = 'C' . $ref->{cid} . 'R' . $ref->{rid} . 'T' . $ref->{ticketid};;
99
100 my $sender = 'postmaster'; # notify postmaster if something fails
101
102 my $smtp;
103
104 eval {
105 my $smtp = Net::SMTP->new ('127.0.0.1', Port => 10025, Hello => 'quarantine') ||
106 die "unable to connect to localhost at port 10025\n";
107
108 my $resid;
109
110 if (!$smtp->mail($sender)) {
111 die sprintf("smtp from error - got: %s %s\n", $smtp->code, $smtp->message);
112 }
113
114 if (!$smtp->to($receiver)) {
115 die sprintf("smtp to error - got: %s %s\n", $smtp->code, $smtp->message);
116 }
117
118 $smtp->data();
119
120 my $header = 1;
121
122 open(my $fh, '<', $path) || die "unable to open file '$path' - $!\n";
123
124 while (defined(my $line = <$fh>)) {
125 chomp $line;
126 if ($header && ($line =~ m/^\s*$/)) {
127 $header = 0;
128 }
129
130 # skip Delivered-To and Return-Path (avoid problem with postfix
131 # forwarding loop detection (man local))
132 next if ($header && (($line =~ m/^Delivered-To:/i) || ($line =~ m/^Return-Path:/i)));
133
134 # rfc821 requires this
135 $line =~ s/^\./\.\./mg;
136 $smtp->datasend("$line\n");
137 }
138 close($fh);
139
140 if ($smtp->dataend()) {
141 my (@msgs) = $smtp->message;
142 my ($last_msg) = $msgs[$#msgs];
143 ($resid) = $last_msg =~ m/Ok: queued as ([0-9A-Z]+)/;
144 if (!$resid) {
145 die sprintf("smtp error - got: %s %s\n", $smtp->code, $smtp->message);
146 }
147 } else {
148 die sprintf("sending data failed - got: %s %s\n", $smtp->code, $smtp->message);
149 }
150
151 my $sth = $dbh->prepare(
152 "UPDATE CMSReceivers SET Status='D', MTime = ? " .
153 "WHERE CMailStore_CID = ? AND CMailStore_RID = ? AND TicketID = ?");
154 $sth->execute(time(), $ref->{cid}, $ref->{rid}, $ref->{ticketid});
155 $sth->finish;
156 };
157 my $err = $@;
158
159 $smtp->quit if $smtp;
160
161 if ($err) {
162 my $msg = "deliver quarantined mail '$id' ($path) failed: $err";
163 syslog('err', $msg);
164 die "$msg\n";
165 }
166
167 syslog('info', "delivered quarantined mail '$id' ($path)");
168
169 return 1;
170 }
171
172 sub delete_quarantined_mail {
173 my ($dbh, $ref) = @_;
174
175 my $filename = $ref->{file};
176 my $spooldir = $PMG::MailQueue::spooldir;
177 my $path = "$spooldir/$filename";
178
179 my $id = 'C' . $ref->{cid} . 'R' . $ref->{rid} . 'T' . $ref->{ticketid};;
180
181 eval {
182 my $sth = $dbh->prepare(
183 "UPDATE CMSReceivers SET Status='D', MTime = ? WHERE " .
184 "CMailStore_CID = ? AND CMailStore_RID = ? AND TicketID = ?");
185 $sth->execute (time(), $ref->{cid}, $ref->{rid}, $ref->{ticketid});
186 $sth->finish;
187 };
188 if (my $err = $@) {
189 my $msg = "delete quarantined mail '$id' ($path) failed: $err";
190 syslog ('err', $msg);
191 die "$msg\n";
192 }
193
194 syslog ('info', "marked quarantined mail '$id' as deleted ($path)");
195
196 return 1;
197 }
198
199
200 1;