]>
Commit | Line | Data |
---|---|---|
b66faa68 DM |
1 | package PMG::API2::Quarantine; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
2ae75c2e | 5 | |
b66faa68 DM |
6 | use Time::Local; |
7 | use Time::Zone; | |
8 | use Data::Dumper; | |
34db0c3f | 9 | use Encode; |
03662e7b DC |
10 | use File::Path; |
11 | use IO::File; | |
6558ebc6 | 12 | use MIME::Entity; |
2ae75c2e | 13 | use URI::Escape qw(uri_escape); |
6558ebc6 | 14 | use File::stat (); |
b66faa68 | 15 | |
34db0c3f | 16 | use Mail::Header; |
e325aa6f | 17 | use Mail::SpamAssassin; |
dae021a8 | 18 | |
b66faa68 | 19 | use PVE::SafeSyslog; |
6e8886d4 | 20 | use PVE::Exception qw(raise_param_exc raise_perm_exc); |
b66faa68 DM |
21 | use PVE::Tools qw(extract_param); |
22 | use PVE::JSONSchema qw(get_standard_option); | |
23 | use PVE::RESTHandler; | |
24 | use PVE::INotify; | |
34db0c3f | 25 | use PVE::APIServer::Formatter; |
b66faa68 | 26 | |
639b28e6 | 27 | use PMG::Utils qw(try_decode_utf8); |
b66faa68 | 28 | use PMG::AccessControl; |
34db0c3f | 29 | use PMG::Config; |
b66faa68 | 30 | use PMG::DBTools; |
34db0c3f | 31 | use PMG::HTMLMail; |
e84bf942 | 32 | use PMG::Quarantine; |
03662e7b DC |
33 | use PMG::MailQueue; |
34 | use PMG::MIMEUtils; | |
b66faa68 DM |
35 | |
36 | use base qw(PVE::RESTHandler); | |
37 | ||
1284c016 DM |
38 | my $spamdesc; |
39 | ||
9efdabf0 DM |
40 | my $extract_pmail = sub { |
41 | my ($authuser, $role) = @_; | |
42 | ||
43 | if ($authuser =~ m/^(.+)\@quarantine$/) { | |
44 | return $1; | |
45 | } | |
46 | raise_param_exc({ pmail => "got unexpected authuser '$authuser' with role '$role'"}); | |
47 | }; | |
48 | ||
157a946b | 49 | my $verify_optional_pmail = sub { |
666b5e8f | 50 | my ($authuser, $role, $pmail_param) = @_; |
157a946b | 51 | |
666b5e8f | 52 | my $pmail; |
157a946b | 53 | if ($role eq 'quser') { |
9efdabf0 | 54 | $pmail = $extract_pmail->($authuser, $role); |
1359baef | 55 | raise_param_exc({ pmail => "parameter not allowed with role '$role'"}) |
666b5e8f | 56 | if defined($pmail_param) && ($pmail ne $pmail_param); |
157a946b | 57 | } else { |
5182cea0 | 58 | raise_param_exc({ pmail => "parameter required with role '$role'"}) |
666b5e8f DM |
59 | if !defined($pmail_param); |
60 | $pmail = $pmail_param; | |
157a946b DM |
61 | } |
62 | return $pmail; | |
63 | }; | |
64 | ||
1284c016 DM |
65 | sub decode_spaminfo { |
66 | my ($info) = @_; | |
67 | ||
21752ddf ML |
68 | my $res = []; |
69 | return $res if !defined($info); | |
70 | ||
e325aa6f DC |
71 | my $saversion = Mail::SpamAssassin->VERSION; |
72 | ||
73 | my $salocaldir = "/var/lib/spamassassin/$saversion/updates_spamassassin_org"; | |
db668bf8 | 74 | my $sacustomdir = "/etc/mail/spamassassin"; |
e325aa6f | 75 | |
db668bf8 | 76 | $spamdesc = PMG::Utils::load_sa_descriptions([$salocaldir, $sacustomdir]) if !$spamdesc; |
1284c016 | 77 | |
1284c016 DM |
78 | foreach my $test (split (',', $info)) { |
79 | my ($name, $score) = split (':', $test); | |
80 | ||
273c538f | 81 | my $info = { name => $name, score => $score + 0, desc => '-' }; |
1284c016 DM |
82 | if (my $si = $spamdesc->{$name}) { |
83 | $info->{desc} = $si->{desc}; | |
84 | $info->{url} = $si->{url} if defined($si->{url}); | |
85 | } | |
86 | push @$res, $info; | |
87 | } | |
88 | ||
89 | return $res; | |
90 | } | |
b66faa68 | 91 | |
157a946b DM |
92 | my $extract_email = sub { |
93 | my $data = shift; | |
94 | ||
95 | return $data if !$data; | |
96 | ||
97 | if ($data =~ m/^.*\s(\S+)\s*$/) { | |
98 | $data = $1; | |
99 | } | |
100 | ||
101 | if ($data =~ m/^<([^<>\s]+)>$/) { | |
102 | $data = $1; | |
103 | } | |
104 | ||
105 | if ($data !~ m/[\s><]/ && $data =~ m/^(.+\@[^\.]+\..*[^\.]+)$/) { | |
106 | $data = $1; | |
107 | } else { | |
108 | $data = undef; | |
109 | } | |
110 | ||
111 | return $data; | |
112 | }; | |
113 | ||
114 | my $get_real_sender = sub { | |
115 | my ($ref) = @_; | |
116 | ||
117 | my @lines = split('\n', $ref->{header}); | |
118 | my $head = Mail::Header->new(\@lines); | |
119 | ||
120 | my @fromarray = split ('\s*,\s*', $head->get ('from') || $ref->{sender}); | |
121 | my $from = $extract_email->($fromarray[0]) || $ref->{sender};; | |
122 | my $sender = $extract_email->($head->get ('sender')); | |
123 | ||
124 | return $sender if $sender; | |
125 | ||
126 | return $from; | |
127 | }; | |
128 | ||
dae021a8 DM |
129 | my $parse_header_info = sub { |
130 | my ($ref) = @_; | |
131 | ||
132 | my $res = { subject => '', from => '' }; | |
133 | ||
134 | my @lines = split('\n', $ref->{header}); | |
135 | my $head = Mail::Header->new(\@lines); | |
136 | ||
137 | $res->{subject} = PMG::Utils::decode_rfc1522(PVE::Tools::trim($head->get('subject'))) // ''; | |
138 | ||
0f123331 | 139 | $res->{from} = PMG::Utils::decode_rfc1522(PVE::Tools::trim($head->get('from') || $ref->{sender})) // ''; |
dae021a8 DM |
140 | |
141 | my $sender = PMG::Utils::decode_rfc1522(PVE::Tools::trim($head->get('sender'))); | |
1284c016 | 142 | $res->{sender} = $sender if $sender && ($sender ne $res->{from}); |
dae021a8 | 143 | |
639b28e6 DC |
144 | $res->{envelope_sender} = try_decode_utf8($ref->{sender}); |
145 | $res->{receiver} = try_decode_utf8($ref->{receiver} // $ref->{pmail}); | |
666b5e8f | 146 | $res->{id} = 'C' . $ref->{cid} . 'R' . $ref->{rid} . 'T' . $ref->{ticketid}; |
dae021a8 DM |
147 | $res->{time} = $ref->{time}; |
148 | $res->{bytes} = $ref->{bytes}; | |
149 | ||
1284c016 DM |
150 | my $qtype = $ref->{qtype}; |
151 | ||
152 | if ($qtype eq 'V') { | |
153 | $res->{virusname} = $ref->{info}; | |
2ad4def7 | 154 | $res->{spamlevel} = 0; |
1284c016 DM |
155 | } elsif ($qtype eq 'S') { |
156 | $res->{spamlevel} = $ref->{spamlevel} // 0; | |
1284c016 DM |
157 | } |
158 | ||
dae021a8 DM |
159 | return $res; |
160 | }; | |
161 | ||
7b6ff2ee | 162 | my $pmail_param_type = get_standard_option('pmg-email-address', { |
1359baef | 163 | description => "List entries for the user with this primary email address. Quarantine users cannot specify this parameter, but it is required for all other roles.", |
157a946b | 164 | optional => 1, |
7b6ff2ee | 165 | }); |
6e8886d4 | 166 | |
b66faa68 DM |
167 | __PACKAGE__->register_method ({ |
168 | name => 'index', | |
169 | path => '', | |
170 | method => 'GET', | |
171 | permissions => { user => 'all' }, | |
172 | description => "Directory index.", | |
173 | parameters => { | |
174 | additionalProperties => 0, | |
175 | properties => {}, | |
176 | }, | |
177 | returns => { | |
178 | type => 'array', | |
179 | items => { | |
180 | type => "object", | |
181 | properties => {}, | |
182 | }, | |
183 | links => [ { rel => 'child', href => "{name}" } ], | |
184 | }, | |
185 | code => sub { | |
186 | my ($param) = @_; | |
187 | ||
188 | my $result = [ | |
157a946b DM |
189 | { name => 'whitelist' }, |
190 | { name => 'blacklist' }, | |
6e8886d4 | 191 | { name => 'content' }, |
b66faa68 | 192 | { name => 'spam' }, |
4f41cebc | 193 | { name => 'spamusers' }, |
ab02ba10 | 194 | { name => 'spamstatus' }, |
b66faa68 | 195 | { name => 'virus' }, |
c3246f47 | 196 | { name => 'virusstatus' }, |
091d8086 | 197 | { name => 'quarusers' }, |
03662e7b DC |
198 | { name => 'attachment' }, |
199 | { name => 'listattachments' }, | |
200 | { name => 'download' }, | |
6558ebc6 | 201 | { name => 'sendlink' }, |
b66faa68 DM |
202 | ]; |
203 | ||
204 | return $result; | |
205 | }}); | |
206 | ||
157a946b | 207 | |
767657cb DM |
208 | my $read_or_modify_user_bw_list = sub { |
209 | my ($listname, $param, $addrs, $delete) = @_; | |
157a946b DM |
210 | |
211 | my $rpcenv = PMG::RESTEnvironment->get(); | |
212 | my $authuser = $rpcenv->get_user(); | |
213 | my $role = $rpcenv->get_role(); | |
214 | ||
215 | my $pmail = $verify_optional_pmail->($authuser, $role, $param->{pmail}); | |
216 | ||
217 | my $dbh = PMG::DBTools::open_ruledb(); | |
218 | ||
767657cb DM |
219 | my $list = PMG::Quarantine::add_to_blackwhite( |
220 | $dbh, $pmail, $listname, $addrs, $delete); | |
157a946b DM |
221 | |
222 | my $res = []; | |
223 | foreach my $a (@$list) { push @$res, { address => $a }; } | |
224 | return $res; | |
225 | }; | |
226 | ||
227 | __PACKAGE__->register_method ({ | |
228 | name => 'whitelist', | |
229 | path => 'whitelist', | |
230 | method => 'GET', | |
231 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, | |
232 | description => "Show user whitelist.", | |
233 | parameters => { | |
234 | additionalProperties => 0, | |
235 | properties => { | |
236 | pmail => $pmail_param_type, | |
237 | }, | |
238 | }, | |
239 | returns => { | |
240 | type => 'array', | |
241 | items => { | |
242 | type => "object", | |
243 | properties => { | |
244 | address => { | |
245 | type => "string", | |
246 | }, | |
247 | }, | |
248 | }, | |
249 | }, | |
250 | code => sub { | |
251 | my ($param) = @_; | |
252 | ||
767657cb DM |
253 | return $read_or_modify_user_bw_list->('WL', $param); |
254 | }}); | |
255 | ||
256 | __PACKAGE__->register_method ({ | |
257 | name => 'whitelist_add', | |
258 | path => 'whitelist', | |
259 | method => 'POST', | |
260 | description => "Add user whitelist entries.", | |
261 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, | |
262 | protected => 1, | |
263 | parameters => { | |
264 | additionalProperties => 0, | |
265 | properties => { | |
266 | pmail => $pmail_param_type, | |
1a1bde13 | 267 | address => get_standard_option('pmg-whiteblacklist-entry-list', { |
767657cb | 268 | description => "The address you want to add.", |
01929101 | 269 | }), |
767657cb DM |
270 | }, |
271 | }, | |
272 | returns => { type => 'null' }, | |
273 | code => sub { | |
274 | my ($param) = @_; | |
275 | ||
1a1bde13 DC |
276 | my $addresses = [split(',', $param->{address})]; |
277 | $read_or_modify_user_bw_list->('WL', $param, $addresses); | |
767657cb DM |
278 | |
279 | return undef; | |
280 | }}); | |
281 | ||
e8d909c1 DC |
282 | __PACKAGE__->register_method ({ |
283 | name => 'whitelist_delete_base', | |
284 | path => 'whitelist', | |
285 | method => 'DELETE', | |
286 | description => "Delete user whitelist entries.", | |
287 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, | |
288 | protected => 1, | |
289 | parameters => { | |
290 | additionalProperties => 0, | |
291 | properties => { | |
292 | pmail => $pmail_param_type, | |
293 | address => get_standard_option('pmg-whiteblacklist-entry-list', { | |
294 | pattern => '', | |
bec1937c | 295 | description => "The address, or comma-separated list of addresses, you want to remove.", |
e8d909c1 DC |
296 | }), |
297 | }, | |
298 | }, | |
299 | returns => { type => 'null' }, | |
300 | code => sub { | |
301 | my ($param) = @_; | |
302 | ||
303 | my $addresses = [split(',', $param->{address})]; | |
304 | $read_or_modify_user_bw_list->('WL', $param, $addresses, 1); | |
305 | ||
306 | return undef; | |
307 | }}); | |
308 | ||
157a946b DM |
309 | __PACKAGE__->register_method ({ |
310 | name => 'blacklist', | |
311 | path => 'blacklist', | |
312 | method => 'GET', | |
313 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, | |
314 | description => "Show user blacklist.", | |
315 | parameters => { | |
316 | additionalProperties => 0, | |
317 | properties => { | |
318 | pmail => $pmail_param_type, | |
319 | }, | |
320 | }, | |
321 | returns => { | |
322 | type => 'array', | |
323 | items => { | |
324 | type => "object", | |
325 | properties => { | |
326 | address => { | |
327 | type => "string", | |
328 | }, | |
329 | }, | |
330 | }, | |
331 | }, | |
332 | code => sub { | |
333 | my ($param) = @_; | |
334 | ||
767657cb DM |
335 | return $read_or_modify_user_bw_list->('BL', $param); |
336 | }}); | |
337 | ||
338 | __PACKAGE__->register_method ({ | |
339 | name => 'blacklist_add', | |
340 | path => 'blacklist', | |
341 | method => 'POST', | |
342 | description => "Add user blacklist entries.", | |
343 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, | |
344 | protected => 1, | |
345 | parameters => { | |
346 | additionalProperties => 0, | |
347 | properties => { | |
348 | pmail => $pmail_param_type, | |
1a1bde13 | 349 | address => get_standard_option('pmg-whiteblacklist-entry-list', { |
767657cb | 350 | description => "The address you want to add.", |
01929101 | 351 | }), |
767657cb DM |
352 | }, |
353 | }, | |
354 | returns => { type => 'null' }, | |
355 | code => sub { | |
356 | my ($param) = @_; | |
357 | ||
1a1bde13 DC |
358 | my $addresses = [split(',', $param->{address})]; |
359 | $read_or_modify_user_bw_list->('BL', $param, $addresses); | |
767657cb DM |
360 | |
361 | return undef; | |
362 | }}); | |
363 | ||
e8d909c1 DC |
364 | __PACKAGE__->register_method ({ |
365 | name => 'blacklist_delete_base', | |
366 | path => 'blacklist', | |
367 | method => 'DELETE', | |
368 | description => "Delete user blacklist entries.", | |
369 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, | |
370 | protected => 1, | |
371 | parameters => { | |
372 | additionalProperties => 0, | |
373 | properties => { | |
374 | pmail => $pmail_param_type, | |
375 | address => get_standard_option('pmg-whiteblacklist-entry-list', { | |
376 | pattern => '', | |
bec1937c | 377 | description => "The address, or comma-separated list of addresses, you want to remove.", |
e8d909c1 DC |
378 | }), |
379 | }, | |
380 | }, | |
381 | returns => { type => 'null' }, | |
382 | code => sub { | |
383 | my ($param) = @_; | |
384 | ||
385 | my $addresses = [split(',', $param->{address})]; | |
386 | $read_or_modify_user_bw_list->('BL', $param, $addresses, 1); | |
387 | ||
388 | return undef; | |
389 | }}); | |
390 | ||
157a946b | 391 | |
0020a3c4 DC |
392 | my $quar_type_map = { |
393 | spam => 'S', | |
394 | attachment => 'A', | |
395 | virus => 'V', | |
396 | }; | |
397 | ||
4f41cebc DC |
398 | __PACKAGE__->register_method ({ |
399 | name => 'spamusers', | |
400 | path => 'spamusers', | |
401 | method => 'GET', | |
24a0be04 | 402 | permissions => { check => [ 'admin', 'qmanager', 'audit'] }, |
4f41cebc DC |
403 | description => "Get a list of receivers of spam in the given timespan (Default the last 24 hours).", |
404 | parameters => { | |
405 | additionalProperties => 0, | |
406 | properties => { | |
6214b5cf DM |
407 | starttime => get_standard_option('pmg-starttime'), |
408 | endtime => get_standard_option('pmg-endtime'), | |
0020a3c4 DC |
409 | 'quarantine-type' => { |
410 | description => 'Query this type of quarantine for users.', | |
411 | type => 'string', | |
412 | default => 'spam', | |
413 | optional => 1, | |
414 | enum => [keys $quar_type_map->%*], | |
415 | }, | |
4f41cebc DC |
416 | }, |
417 | }, | |
418 | returns => { | |
419 | type => 'array', | |
420 | items => { | |
421 | type => "object", | |
422 | properties => { | |
423 | mail => { | |
424 | description => 'the receiving email', | |
425 | type => 'string', | |
426 | }, | |
427 | }, | |
428 | }, | |
429 | }, | |
430 | code => sub { | |
431 | my ($param) = @_; | |
432 | ||
433 | my $rpcenv = PMG::RESTEnvironment->get(); | |
434 | my $authuser = $rpcenv->get_user(); | |
4f41cebc DC |
435 | |
436 | my $res = []; | |
437 | ||
438 | my $dbh = PMG::DBTools::open_ruledb(); | |
439 | ||
440 | my $start = $param->{starttime} // (time - 86400); | |
441 | my $end = $param->{endtime} // ($start + 86400); | |
442 | ||
0020a3c4 DC |
443 | my $quar_type = $param->{'quarantine-type'} // 'spam'; |
444 | ||
4f41cebc DC |
445 | my $sth = $dbh->prepare( |
446 | "SELECT DISTINCT pmail " . | |
447 | "FROM CMailStore, CMSReceivers WHERE " . | |
448 | "time >= $start AND time < $end AND " . | |
0020a3c4 | 449 | "QType = ? AND CID = CMailStore_CID AND RID = CMailStore_RID " . |
4f41cebc DC |
450 | "AND Status = 'N' ORDER BY pmail"); |
451 | ||
0020a3c4 | 452 | $sth->execute($quar_type_map->{$quar_type}); |
4f41cebc DC |
453 | |
454 | while (my $ref = $sth->fetchrow_hashref()) { | |
07f7275e | 455 | push @$res, { mail => PMG::Utils::try_decode_utf8($ref->{pmail}) }; |
4f41cebc DC |
456 | } |
457 | ||
458 | return $res; | |
459 | }}); | |
460 | ||
ab02ba10 DM |
461 | __PACKAGE__->register_method ({ |
462 | name => 'spamstatus', | |
463 | path => 'spamstatus', | |
464 | method => 'GET', | |
465 | permissions => { check => [ 'admin', 'qmanager', 'audit'] }, | |
466 | description => "Get Spam Quarantine Status", | |
467 | parameters => { | |
468 | additionalProperties => 0, | |
469 | properties => {}, | |
470 | }, | |
471 | returns => { | |
472 | type => "object", | |
473 | properties => { | |
474 | count => { | |
475 | description => 'Number of stored mails.', | |
476 | type => 'integer', | |
477 | }, | |
478 | mbytes => { | |
479 | description => "Estimated disk space usage in MByte.", | |
480 | type => 'number', | |
481 | }, | |
482 | avgbytes => { | |
483 | description => "Average size of stored mails in bytes.", | |
484 | type => 'number', | |
485 | }, | |
486 | avgspam => { | |
487 | description => "Average spam level.", | |
488 | type => 'number', | |
489 | }, | |
490 | }, | |
491 | }, | |
492 | code => sub { | |
493 | my ($param) = @_; | |
494 | ||
ab02ba10 DM |
495 | my $dbh = PMG::DBTools::open_ruledb(); |
496 | my $ref = PMG::DBTools::get_quarantine_count($dbh, 'S'); | |
497 | ||
498 | return $ref; | |
499 | }}); | |
500 | ||
9867e2da DM |
501 | __PACKAGE__->register_method ({ |
502 | name => 'quarusers', | |
503 | path => 'quarusers', | |
504 | method => 'GET', | |
505 | permissions => { check => [ 'admin', 'qmanager', 'audit'] }, | |
1359baef | 506 | description => "Get a list of users with whitelist/blacklist settings.", |
9867e2da DM |
507 | parameters => { |
508 | additionalProperties => 0, | |
648954c6 DC |
509 | properties => { |
510 | list => { | |
511 | type => 'string', | |
512 | description => 'If set, limits the result to the given list.', | |
513 | enum => ['BL', 'WL'], | |
514 | optional => 1, | |
515 | }, | |
516 | }, | |
9867e2da DM |
517 | }, |
518 | returns => { | |
519 | type => 'array', | |
520 | items => { | |
521 | type => "object", | |
522 | properties => { | |
523 | mail => { | |
524 | description => 'the receiving email', | |
525 | type => 'string', | |
526 | }, | |
527 | }, | |
528 | }, | |
529 | }, | |
530 | code => sub { | |
531 | my ($param) = @_; | |
532 | ||
533 | my $rpcenv = PMG::RESTEnvironment->get(); | |
534 | my $authuser = $rpcenv->get_user(); | |
9867e2da DM |
535 | |
536 | my $res = []; | |
537 | ||
538 | my $dbh = PMG::DBTools::open_ruledb(); | |
539 | ||
648954c6 DC |
540 | my $sth; |
541 | if ($param->{list}) { | |
542 | $sth = $dbh->prepare("SELECT DISTINCT pmail FROM UserPrefs WHERE name = ? ORDER BY pmail"); | |
543 | $sth->execute($param->{list}); | |
544 | } else { | |
545 | $sth = $dbh->prepare("SELECT DISTINCT pmail FROM UserPrefs ORDER BY pmail"); | |
546 | $sth->execute(); | |
547 | } | |
9867e2da DM |
548 | |
549 | while (my $ref = $sth->fetchrow_hashref()) { | |
07f7275e | 550 | push @$res, { mail => PMG::Utils::try_decode_utf8($ref->{pmail}) }; |
9867e2da DM |
551 | } |
552 | ||
553 | return $res; | |
554 | }}); | |
555 | ||
daebd67e DC |
556 | my $quarantine_api = sub { |
557 | my ($param, $quartype, $check_pmail) = @_; | |
558 | ||
559 | my $rpcenv = PMG::RESTEnvironment->get(); | |
560 | my $authuser = $rpcenv->get_user(); | |
a3db6854 | 561 | my $role = $rpcenv->get_role(); |
daebd67e DC |
562 | |
563 | my $start = $param->{starttime} // (time - 86400); | |
564 | my $end = $param->{endtime} // ($start + 86400); | |
565 | ||
37f48a0e TL |
566 | |
567 | my $dbh = PMG::DBTools::open_ruledb(); | |
568 | ||
569 | my $sth; | |
daebd67e | 570 | my $pmail; |
a3db6854 | 571 | if ($check_pmail || $role eq 'quser') { |
daebd67e | 572 | $pmail = $verify_optional_pmail->($authuser, $role, $param->{pmail}); |
37f48a0e TL |
573 | $sth = $dbh->prepare( |
574 | "SELECT * FROM CMailStore, CMSReceivers WHERE pmail = ?" | |
575 | ." AND time >= $start AND time < $end AND QType = '$quartype' AND CID = CMailStore_CID" | |
576 | ." AND RID = CMailStore_RID AND Status = 'N' ORDER BY pmail, time, receiver" | |
577 | ); | |
daebd67e | 578 | } else { |
37f48a0e TL |
579 | $sth = $dbh->prepare( |
580 | "SELECT * FROM CMailStore, CMSReceivers WHERE time >= $start AND time < $end" | |
581 | ." AND QType = '$quartype' AND CID = CMailStore_CID AND RID = CMailStore_RID" | |
582 | ." AND Status = 'N' ORDER BY time, receiver" | |
583 | ); | |
daebd67e DC |
584 | } |
585 | ||
1227a4b9 | 586 | if ($check_pmail || $role eq 'quser') { |
07f7275e | 587 | $sth->execute(encode('UTF-8', $pmail)); |
daebd67e DC |
588 | } else { |
589 | $sth->execute(); | |
590 | } | |
591 | ||
37f48a0e | 592 | my $res = []; |
daebd67e | 593 | while (my $ref = $sth->fetchrow_hashref()) { |
37f48a0e | 594 | push @$res, $parse_header_info->($ref); |
daebd67e DC |
595 | } |
596 | ||
597 | return $res; | |
598 | }; | |
599 | ||
ded33c7c | 600 | __PACKAGE__->register_method ({ |
83ce499f DC |
601 | name => 'spam', |
602 | path => 'spam', | |
ded33c7c DM |
603 | method => 'GET', |
604 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, | |
83ce499f | 605 | description => "Get a list of quarantined spam mails in the given timeframe (default the last 24 hours) for the given user.", |
ded33c7c DM |
606 | parameters => { |
607 | additionalProperties => 0, | |
608 | properties => { | |
6214b5cf DM |
609 | starttime => get_standard_option('pmg-starttime'), |
610 | endtime => get_standard_option('pmg-endtime'), | |
157a946b | 611 | pmail => $pmail_param_type, |
ded33c7c DM |
612 | }, |
613 | }, | |
614 | returns => { | |
615 | type => 'array', | |
616 | items => { | |
617 | type => "object", | |
dae021a8 DM |
618 | properties => { |
619 | id => { | |
620 | description => 'Unique ID', | |
621 | type => 'string', | |
622 | }, | |
623 | bytes => { | |
624 | description => "Size of raw email.", | |
625 | type => 'integer' , | |
626 | }, | |
627 | envelope_sender => { | |
628 | description => "SMTP envelope sender.", | |
629 | type => 'string', | |
630 | }, | |
631 | from => { | |
632 | description => "Header 'From' field.", | |
633 | type => 'string', | |
634 | }, | |
635 | sender => { | |
636 | description => "Header 'Sender' field.", | |
637 | type => 'string', | |
638 | optional => 1, | |
639 | }, | |
640 | receiver => { | |
641 | description => "Receiver email address", | |
642 | type => 'string', | |
643 | }, | |
644 | subject => { | |
645 | description => "Header 'Subject' field.", | |
646 | type => 'string', | |
647 | }, | |
648 | time => { | |
649 | description => "Receive time stamp", | |
650 | type => 'integer', | |
651 | }, | |
1284c016 DM |
652 | spamlevel => { |
653 | description => "Spam score.", | |
654 | type => 'number', | |
655 | }, | |
dae021a8 | 656 | }, |
ded33c7c DM |
657 | }, |
658 | }, | |
659 | code => sub { | |
660 | my ($param) = @_; | |
a3db6854 | 661 | return $quarantine_api->($param, 'S', defined($param->{pmail})); |
b66faa68 DM |
662 | }}); |
663 | ||
bb01dcf8 DM |
664 | __PACKAGE__->register_method ({ |
665 | name => 'virus', | |
666 | path => 'virus', | |
667 | method => 'GET', | |
668 | permissions => { check => [ 'admin', 'qmanager', 'audit' ] }, | |
669 | description => "Get a list of quarantined virus mails in the given timeframe (default the last 24 hours).", | |
670 | parameters => { | |
671 | additionalProperties => 0, | |
672 | properties => { | |
6214b5cf DM |
673 | starttime => get_standard_option('pmg-starttime'), |
674 | endtime => get_standard_option('pmg-endtime'), | |
0020a3c4 | 675 | pmail => $pmail_param_type, |
bb01dcf8 DM |
676 | }, |
677 | }, | |
678 | returns => { | |
679 | type => 'array', | |
680 | items => { | |
681 | type => "object", | |
682 | properties => { | |
683 | id => { | |
684 | description => 'Unique ID', | |
685 | type => 'string', | |
686 | }, | |
687 | bytes => { | |
688 | description => "Size of raw email.", | |
689 | type => 'integer' , | |
690 | }, | |
691 | envelope_sender => { | |
692 | description => "SMTP envelope sender.", | |
693 | type => 'string', | |
694 | }, | |
695 | from => { | |
696 | description => "Header 'From' field.", | |
697 | type => 'string', | |
698 | }, | |
699 | sender => { | |
700 | description => "Header 'Sender' field.", | |
701 | type => 'string', | |
702 | optional => 1, | |
703 | }, | |
704 | receiver => { | |
705 | description => "Receiver email address", | |
706 | type => 'string', | |
707 | }, | |
708 | subject => { | |
709 | description => "Header 'Subject' field.", | |
710 | type => 'string', | |
711 | }, | |
712 | time => { | |
713 | description => "Receive time stamp", | |
714 | type => 'integer', | |
715 | }, | |
716 | virusname => { | |
717 | description => "Virus name.", | |
718 | type => 'string', | |
719 | }, | |
720 | }, | |
721 | }, | |
722 | }, | |
723 | code => sub { | |
724 | my ($param) = @_; | |
0020a3c4 | 725 | return $quarantine_api->($param, 'V', defined($param->{pmail})); |
bb01dcf8 DM |
726 | }}); |
727 | ||
03662e7b DC |
728 | __PACKAGE__->register_method ({ |
729 | name => 'attachment', | |
730 | path => 'attachment', | |
731 | method => 'GET', | |
732 | permissions => { check => [ 'admin', 'qmanager', 'audit' ] }, | |
733 | description => "Get a list of quarantined attachment mails in the given timeframe (default the last 24 hours).", | |
734 | parameters => { | |
735 | additionalProperties => 0, | |
736 | properties => { | |
737 | starttime => get_standard_option('pmg-starttime'), | |
738 | endtime => get_standard_option('pmg-endtime'), | |
0020a3c4 | 739 | pmail => $pmail_param_type, |
03662e7b DC |
740 | }, |
741 | }, | |
742 | returns => { | |
743 | type => 'array', | |
744 | items => { | |
745 | type => "object", | |
746 | properties => { | |
747 | id => { | |
748 | description => 'Unique ID', | |
749 | type => 'string', | |
750 | }, | |
751 | bytes => { | |
752 | description => "Size of raw email.", | |
753 | type => 'integer' , | |
754 | }, | |
755 | envelope_sender => { | |
756 | description => "SMTP envelope sender.", | |
757 | type => 'string', | |
758 | }, | |
759 | from => { | |
760 | description => "Header 'From' field.", | |
761 | type => 'string', | |
762 | }, | |
763 | sender => { | |
764 | description => "Header 'Sender' field.", | |
765 | type => 'string', | |
766 | optional => 1, | |
767 | }, | |
768 | receiver => { | |
769 | description => "Receiver email address", | |
770 | type => 'string', | |
771 | }, | |
772 | subject => { | |
773 | description => "Header 'Subject' field.", | |
774 | type => 'string', | |
775 | }, | |
776 | time => { | |
777 | description => "Receive time stamp", | |
778 | type => 'integer', | |
779 | }, | |
780 | }, | |
781 | }, | |
782 | }, | |
783 | code => sub { | |
784 | my ($param) = @_; | |
0020a3c4 | 785 | return $quarantine_api->($param, 'A', defined($param->{pmail})); |
03662e7b DC |
786 | }}); |
787 | ||
c3246f47 DM |
788 | __PACKAGE__->register_method ({ |
789 | name => 'virusstatus', | |
790 | path => 'virusstatus', | |
791 | method => 'GET', | |
792 | permissions => { check => [ 'admin', 'qmanager', 'audit'] }, | |
793 | description => "Get Virus Quarantine Status", | |
794 | parameters => { | |
795 | additionalProperties => 0, | |
796 | properties => {}, | |
797 | }, | |
798 | returns => { | |
799 | type => "object", | |
800 | properties => { | |
801 | count => { | |
802 | description => 'Number of stored mails.', | |
803 | type => 'integer', | |
804 | }, | |
805 | mbytes => { | |
806 | description => "Estimated disk space usage in MByte.", | |
807 | type => 'number', | |
808 | }, | |
809 | avgbytes => { | |
810 | description => "Average size of stored mails in bytes.", | |
811 | type => 'number', | |
812 | }, | |
813 | }, | |
814 | }, | |
815 | code => sub { | |
816 | my ($param) = @_; | |
817 | ||
818 | my $dbh = PMG::DBTools::open_ruledb(); | |
819 | my $ref = PMG::DBTools::get_quarantine_count($dbh, 'V'); | |
820 | ||
f7fa880f DM |
821 | delete $ref->{avgspam}; |
822 | ||
c3246f47 DM |
823 | return $ref; |
824 | }}); | |
825 | ||
03662e7b DC |
826 | my $get_and_check_mail = sub { |
827 | my ($id, $rpcenv, $dbh) = @_; | |
828 | ||
829 | my ($cid, $rid, $tid) = $id =~ m/^C(\d+)R(\d+)T(\d+)$/; | |
bb0ab4c0 | 830 | ($cid, $rid, $tid) = (int($cid), int($rid), int($tid)); |
03662e7b | 831 | |
bb0ab4c0 | 832 | $dbh = PMG::DBTools::open_ruledb() if !$dbh; |
03662e7b DC |
833 | |
834 | my $ref = PMG::DBTools::load_mail_data($dbh, $cid, $rid, $tid); | |
835 | ||
836 | my $authuser = $rpcenv->get_user(); | |
837 | my $role = $rpcenv->get_role(); | |
838 | ||
839 | if ($role eq 'quser') { | |
840 | my $quar_username = $ref->{pmail} . '@quarantine'; | |
841 | raise_perm_exc("mail does not belong to user '$authuser' ($ref->{pmail})") | |
d6e1e1d8 | 842 | if $authuser ne $quar_username; |
03662e7b DC |
843 | } |
844 | ||
845 | return $ref; | |
846 | }; | |
847 | ||
6e8886d4 DM |
848 | __PACKAGE__->register_method ({ |
849 | name => 'content', | |
850 | path => 'content', | |
851 | method => 'GET', | |
852 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, | |
34db0c3f | 853 | description => "Get email data. There is a special formatter called 'htmlmail' to get sanitized html view of the mail content (use the '/api2/htmlmail/quarantine/content' url).", |
6e8886d4 DM |
854 | parameters => { |
855 | additionalProperties => 0, | |
856 | properties => { | |
857 | id => { | |
858 | description => 'Unique ID', | |
859 | type => 'string', | |
666b5e8f DM |
860 | pattern => 'C\d+R\d+T\d+', |
861 | maxLength => 60, | |
6e8886d4 | 862 | }, |
34db0c3f | 863 | raw => { |
c81cf188 | 864 | description => "Display 'raw' eml data. Deactivates size limit.", |
34db0c3f DM |
865 | type => 'boolean', |
866 | optional => 1, | |
867 | default => 0, | |
868 | }, | |
6e8886d4 DM |
869 | }, |
870 | }, | |
871 | returns => { | |
872 | type => "object", | |
cd31bb45 DM |
873 | properties => { |
874 | id => { | |
875 | description => 'Unique ID', | |
876 | type => 'string', | |
877 | }, | |
878 | bytes => { | |
879 | description => "Size of raw email.", | |
880 | type => 'integer' , | |
881 | }, | |
882 | envelope_sender => { | |
883 | description => "SMTP envelope sender.", | |
884 | type => 'string', | |
885 | }, | |
886 | from => { | |
887 | description => "Header 'From' field.", | |
888 | type => 'string', | |
889 | }, | |
890 | sender => { | |
891 | description => "Header 'Sender' field.", | |
892 | type => 'string', | |
893 | optional => 1, | |
894 | }, | |
895 | receiver => { | |
896 | description => "Receiver email address", | |
897 | type => 'string', | |
898 | }, | |
899 | subject => { | |
900 | description => "Header 'Subject' field.", | |
901 | type => 'string', | |
902 | }, | |
903 | time => { | |
904 | description => "Receive time stamp", | |
905 | type => 'integer', | |
906 | }, | |
907 | spamlevel => { | |
908 | description => "Spam score.", | |
909 | type => 'number', | |
910 | }, | |
911 | spaminfo => { | |
912 | description => "Information about matched spam tests (name, score, desc, url).", | |
913 | type => 'array', | |
914 | }, | |
915 | header => { | |
916 | description => "Raw email header data.", | |
917 | type => 'string', | |
918 | }, | |
919 | content => { | |
920 | description => "Raw email data (first 4096 bytes). Useful for preview. NOTE: The 'htmlmail' formatter displays the whole email.", | |
921 | type => 'string', | |
6eac8473 | 922 | }, |
cd31bb45 | 923 | }, |
6e8886d4 DM |
924 | }, |
925 | code => sub { | |
926 | my ($param) = @_; | |
927 | ||
928 | my $rpcenv = PMG::RESTEnvironment->get(); | |
34db0c3f | 929 | my $format = $rpcenv->get_format(); |
6e8886d4 | 930 | |
c81cf188 DC |
931 | my $raw = $param->{raw} // 0; |
932 | ||
03662e7b | 933 | my $ref = $get_and_check_mail->($param->{id}, $rpcenv); |
6e8886d4 DM |
934 | |
935 | my $res = $parse_header_info->($ref); | |
936 | ||
6e8886d4 DM |
937 | my $filename = $ref->{file}; |
938 | my $spooldir = $PMG::MailQueue::spooldir; | |
939 | ||
940 | my $path = "$spooldir/$filename"; | |
941 | ||
34db0c3f DM |
942 | if ($format eq 'htmlmail') { |
943 | ||
944 | my $cfg = PMG::Config->new(); | |
945 | my $viewimages = $cfg->get('spamquar', 'viewimages'); | |
946 | my $allowhref = $cfg->get('spamquar', 'allowhrefs'); | |
947 | ||
69da4eb6 | 948 | $res->{content} = PMG::HTMLMail::email_to_html($path, $raw, $viewimages, $allowhref) // 'unable to parse mail'; |
34db0c3f | 949 | |
157a946b DM |
950 | # to make result verification happy |
951 | $res->{file} = ''; | |
952 | $res->{header} = ''; | |
2ad4def7 | 953 | $res->{spamlevel} = 0; |
157a946b | 954 | $res->{spaminfo} = []; |
34db0c3f | 955 | } else { |
cd31bb45 | 956 | # include additional details |
34db0c3f | 957 | |
c81cf188 DC |
958 | # we want to get the whole email in raw mode |
959 | my $maxbytes = (!$raw)? 4096 : undef; | |
960 | ||
961 | my ($header, $content) = PMG::HTMLMail::read_raw_email($path, $maxbytes); | |
157a946b | 962 | |
cd31bb45 DM |
963 | $res->{file} = $ref->{file}; |
964 | $res->{spaminfo} = decode_spaminfo($ref->{info}); | |
34db0c3f DM |
965 | $res->{header} = $header; |
966 | $res->{content} = $content; | |
967 | } | |
6e8886d4 | 968 | |
03662e7b DC |
969 | return $res; |
970 | ||
971 | }}); | |
972 | ||
973 | my $get_attachments = sub { | |
974 | my ($mailid, $dumpdir, $with_path) = @_; | |
975 | ||
976 | my $rpcenv = PMG::RESTEnvironment->get(); | |
977 | ||
978 | my $ref = $get_and_check_mail->($mailid, $rpcenv); | |
979 | ||
980 | my $filename = $ref->{file}; | |
981 | my $spooldir = $PMG::MailQueue::spooldir; | |
982 | ||
983 | my $parser = PMG::MIMEUtils::new_mime_parser({ | |
984 | nested => 1, | |
985 | decode_bodies => 0, | |
986 | extract_uuencode => 0, | |
987 | dumpdir => $dumpdir, | |
988 | }); | |
989 | ||
990 | my $entity = $parser->parse_open("$spooldir/$filename"); | |
991 | PMG::MIMEUtils::fixup_multipart($entity); | |
992 | PMG::MailQueue::decode_entities($parser, 'attachmentquarantine', $entity); | |
993 | ||
994 | my $res = []; | |
995 | my $id = 0; | |
996 | ||
997 | PMG::MIMEUtils::traverse_mime_parts($entity, sub { | |
998 | my ($part) = @_; | |
999 | my $name = PMG::Utils::extract_filename($part->head) || "part-$id"; | |
1000 | my $attachment_path = $part->{PMX_decoded_path}; | |
1001 | return if !$attachment_path || ! -f $attachment_path; | |
1002 | my $size = -s $attachment_path // 0; | |
1003 | my $entry = { | |
1004 | id => $id, | |
1005 | name => $name, | |
1006 | size => $size, | |
08cf2141 | 1007 | 'content-disposition' => $part->head->mime_attr('content-disposition'), |
03662e7b DC |
1008 | 'content-type' => $part->head->mime_attr('content-type'), |
1009 | }; | |
1010 | $entry->{path} = $attachment_path if $with_path; | |
1011 | push @$res, $entry; | |
1012 | $id++; | |
1013 | }); | |
1014 | ||
1015 | return $res; | |
1016 | }; | |
1017 | ||
1018 | __PACKAGE__->register_method ({ | |
1019 | name => 'listattachments', | |
1020 | path => 'listattachments', | |
1021 | method => 'GET', | |
c34e9a6a | 1022 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, |
03662e7b DC |
1023 | description => "Get Attachments for E-Mail in Quarantine.", |
1024 | parameters => { | |
1025 | additionalProperties => 0, | |
1026 | properties => { | |
1027 | id => { | |
1028 | description => 'Unique ID', | |
1029 | type => 'string', | |
1030 | pattern => 'C\d+R\d+T\d+', | |
1031 | maxLength => 60, | |
1032 | }, | |
1033 | }, | |
1034 | }, | |
1035 | returns => { | |
1036 | type => "array", | |
1037 | items => { | |
1038 | type => "object", | |
1039 | properties => { | |
1040 | id => { | |
1041 | description => 'Attachment ID', | |
1042 | type => 'integer', | |
1043 | }, | |
1044 | size => { | |
1045 | description => "Size of raw attachment in bytes.", | |
1046 | type => 'integer' , | |
1047 | }, | |
1048 | name => { | |
1049 | description => "Raw email header data.", | |
1050 | type => 'string', | |
1051 | }, | |
1052 | 'content-type' => { | |
1053 | description => "Raw email header data.", | |
1054 | type => 'string', | |
1055 | }, | |
1056 | }, | |
1057 | }, | |
1058 | }, | |
1059 | code => sub { | |
1060 | my ($param) = @_; | |
1061 | ||
1062 | my $dumpdir = "/run/pmgproxy/pmg-$param->{id}-$$"; | |
1063 | my $res = $get_attachments->($param->{id}, $dumpdir); | |
1064 | rmtree $dumpdir; | |
1065 | ||
1066 | return $res; | |
1067 | ||
1068 | }}); | |
1069 | ||
1070 | __PACKAGE__->register_method ({ | |
1071 | name => 'download', | |
1072 | path => 'download', | |
1073 | method => 'GET', | |
f18ae146 DC |
1074 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, |
1075 | description => "Download E-Mail or Attachment from Quarantine.", | |
03662e7b DC |
1076 | download => 1, |
1077 | parameters => { | |
1078 | additionalProperties => 0, | |
1079 | properties => { | |
1080 | mailid => { | |
1081 | description => 'Unique ID', | |
1082 | type => 'string', | |
1083 | pattern => 'C\d+R\d+T\d+', | |
1084 | maxLength => 60, | |
1085 | }, | |
1086 | attachmentid => { | |
1087 | description => "The Attachment ID for the mail.", | |
1088 | type => 'integer', | |
f18ae146 | 1089 | optional => 1, |
03662e7b DC |
1090 | }, |
1091 | }, | |
1092 | }, | |
1093 | returns => { | |
1094 | type => "object", | |
1095 | }, | |
1096 | code => sub { | |
1097 | my ($param) = @_; | |
1098 | ||
1099 | my $mailid = $param->{mailid}; | |
1100 | my $attachmentid = $param->{attachmentid}; | |
1101 | ||
1102 | my $dumpdir = "/run/pmgproxy/pmg-$mailid-$$/"; | |
f18ae146 DC |
1103 | my $res; |
1104 | ||
1105 | if ($attachmentid) { | |
1106 | my $attachments = $get_attachments->($mailid, $dumpdir, 1); | |
1107 | $res = $attachments->[$attachmentid]; | |
1108 | if (!$res) { | |
1109 | raise_param_exc({ attachmentid => "Invalid Attachment ID for Mail."}); | |
1110 | } | |
1111 | } else { | |
1112 | my $rpcenv = PMG::RESTEnvironment->get(); | |
1113 | my $ref = $get_and_check_mail->($mailid, $rpcenv); | |
1114 | my $spooldir = $PMG::MailQueue::spooldir; | |
1115 | ||
1116 | $res = { | |
1117 | 'content-type' => 'message/rfc822', | |
1118 | path => "$spooldir/$ref->{file}", | |
1119 | }; | |
03662e7b DC |
1120 | } |
1121 | ||
1122 | $res->{fh} = IO::File->new($res->{path}, '<') || | |
1123 | die "unable to open file '$res->{path}' - $!\n"; | |
1124 | ||
f18ae146 | 1125 | rmtree $dumpdir if -e $dumpdir; |
6e8886d4 DM |
1126 | |
1127 | return $res; | |
1128 | ||
1129 | }}); | |
1130 | ||
34db0c3f DM |
1131 | PVE::APIServer::Formatter::register_page_formatter( |
1132 | 'format' => 'htmlmail', | |
1133 | method => 'GET', | |
1134 | path => '/quarantine/content', | |
1135 | code => sub { | |
1136 | my ($res, $data, $param, $path, $auth, $config) = @_; | |
1137 | ||
1138 | if(!HTTP::Status::is_success($res->{status})) { | |
1139 | return ("Error $res->{status}: $res->{message}", "text/plain"); | |
1140 | } | |
1141 | ||
1142 | my $ct = "text/html;charset=UTF-8"; | |
1143 | ||
1144 | my $raw = $data->{content}; | |
1145 | ||
1146 | return (encode('UTF-8', $raw), $ct, 1); | |
1147 | }); | |
1148 | ||
157a946b DM |
1149 | __PACKAGE__->register_method ({ |
1150 | name =>'action', | |
1151 | path => 'content', | |
1152 | method => 'POST', | |
1153 | description => "Execute quarantine actions.", | |
1154 | permissions => { check => [ 'admin', 'qmanager', 'quser'] }, | |
1155 | protected => 1, | |
1156 | parameters => { | |
1157 | additionalProperties => 0, | |
1158 | properties => { | |
1159 | id => { | |
1359baef | 1160 | description => 'Unique IDs, separate with ;', |
157a946b | 1161 | type => 'string', |
b7894d7c | 1162 | pattern => 'C\d+R\d+T\d+(;C\d+R\d+T\d+)*', |
157a946b DM |
1163 | }, |
1164 | action => { | |
1165 | description => 'Action - specify what you want to do with the mail.', | |
1166 | type => 'string', | |
1167 | enum => ['whitelist', 'blacklist', 'deliver', 'delete'], | |
1168 | }, | |
1169 | }, | |
1170 | }, | |
1171 | returns => { type => "null" }, | |
1172 | code => sub { | |
1173 | my ($param) = @_; | |
1174 | ||
1175 | my $rpcenv = PMG::RESTEnvironment->get(); | |
157a946b | 1176 | my $action = $param->{action}; |
b7894d7c | 1177 | my @idlist = split(';', $param->{id}); |
157a946b | 1178 | |
e18901ef DM |
1179 | my $dbh = PMG::DBTools::open_ruledb(); |
1180 | ||
b7894d7c | 1181 | for my $id (@idlist) { |
b7894d7c | 1182 | |
03662e7b | 1183 | my $ref = $get_and_check_mail->($id, $rpcenv, $dbh); |
639b28e6 | 1184 | my $sender = try_decode_utf8($get_real_sender->($ref)); |
d616867d SI |
1185 | my $pmail = try_decode_utf8($ref->{pmail}); |
1186 | my $receiver = try_decode_utf8($ref->{receiver} // $ref->{pmail}); | |
b7894d7c DC |
1187 | |
1188 | if ($action eq 'whitelist') { | |
d616867d SI |
1189 | PMG::Quarantine::add_to_blackwhite($dbh, $pmail, 'WL', [ $sender ]); |
1190 | PMG::Quarantine::deliver_quarantined_mail($dbh, $ref, $receiver); | |
b7894d7c | 1191 | } elsif ($action eq 'blacklist') { |
d616867d | 1192 | PMG::Quarantine::add_to_blackwhite($dbh, $pmail, 'BL', [ $sender ]); |
69048a04 | 1193 | PMG::Quarantine::delete_quarantined_mail($dbh, $ref); |
b7894d7c | 1194 | } elsif ($action eq 'deliver') { |
d616867d | 1195 | PMG::Quarantine::deliver_quarantined_mail($dbh, $ref, $receiver); |
b7894d7c DC |
1196 | } elsif ($action eq 'delete') { |
1197 | PMG::Quarantine::delete_quarantined_mail($dbh, $ref); | |
1198 | } else { | |
cb6137e6 | 1199 | die "internal error, unknown action '$action'"; # should not be reached |
b7894d7c | 1200 | } |
157a946b DM |
1201 | } |
1202 | ||
1203 | return undef; | |
1204 | }}); | |
e84bf942 | 1205 | |
6558ebc6 DC |
1206 | my $link_map_fn = "/run/pmgproxy/quarantinelink.map"; |
1207 | my $per_user_limit = 60*60; # 1 hour | |
1208 | ||
1209 | my sub send_link_mail { | |
1210 | my ($cfg, $receiver) = @_; | |
1211 | ||
1212 | my $hostname = PVE::INotify::nodename(); | |
1213 | my $fqdn = $cfg->get('spamquar', 'hostname') // | |
1214 | PVE::Tools::get_fqdn($hostname); | |
1215 | ||
1216 | my $port = $cfg->get('spamquar', 'port') // 8006; | |
1217 | ||
1218 | my $protocol = $cfg->get('spamquar', 'protocol') // 'https'; | |
1219 | ||
1220 | my $protocol_fqdn_port = "$protocol://$fqdn"; | |
1221 | if (($protocol eq 'https' && $port != 443) || | |
1222 | ($protocol eq 'http' && $port != 80)) { | |
1223 | $protocol_fqdn_port .= ":$port"; | |
1224 | } | |
1225 | ||
1226 | my $mailfrom = $cfg->get ('spamquar', 'mailfrom') // | |
1227 | "Proxmox Mail Gateway <postmaster>"; | |
1228 | ||
1229 | my $ticket = PMG::Ticket::assemble_quarantine_ticket($receiver); | |
1230 | my $esc_ticket = uri_escape($ticket); | |
1231 | my $link = "$protocol_fqdn_port/quarantine?ticket=${esc_ticket}"; | |
1232 | ||
1233 | my $text = "Here is your Link for the Spam Quarantine on $fqdn:\n\n$link\n"; | |
1234 | ||
1235 | my $mail = MIME::Entity->build( | |
1236 | Type => "text/plain", | |
1237 | To => $receiver, | |
1238 | From => $mailfrom, | |
1239 | Subject => "Proxmox Mail Gateway - Quarantine Link", | |
1240 | Data => $text, | |
1241 | ); | |
1242 | ||
1359baef | 1243 | # we use an empty envelope sender (we don't want to receive NDRs) |
a56a4b54 | 1244 | PMG::Utils::reinject_local_mail ($mail, '', [$receiver], undef, $fqdn); |
6558ebc6 DC |
1245 | } |
1246 | ||
1247 | __PACKAGE__->register_method ({ | |
1248 | name =>'sendlink', | |
1249 | path => 'sendlink', | |
1250 | method => 'POST', | |
1251 | description => "Send Quarantine link to given e-mail.", | |
1252 | permissions => { user => 'world' }, | |
1253 | protected => 1, | |
1254 | parameters => { | |
1255 | additionalProperties => 0, | |
1256 | properties => { | |
1257 | mail => get_standard_option('pmg-email-address'), | |
1258 | }, | |
1259 | }, | |
1260 | returns => { type => "null" }, | |
1261 | code => sub { | |
1262 | my ($param) = @_; | |
1263 | ||
d7bd84c9 | 1264 | my $starttime = time(); |
6558ebc6 DC |
1265 | |
1266 | my $cfg = PMG::Config->new(); | |
1267 | my $is_enabled = $cfg->get('spamquar', 'quarantinelink'); | |
1268 | if (!$is_enabled) { | |
1269 | die "This feature is not enabled\n"; | |
1270 | } | |
1271 | ||
1272 | my $stat = File::stat::stat($link_map_fn); | |
1273 | ||
be9e7168 TL |
1274 | if (defined($stat) && ($stat->mtime + 5) > $starttime) { |
1275 | sleep(3); | |
6558ebc6 DC |
1276 | die "Too many requests. Please try again later\n"; |
1277 | } | |
1278 | ||
1279 | my $domains = PVE::INotify::read_file('domains'); | |
1280 | my $domainregex = PMG::Utils::domain_regex([keys %$domains]); | |
1281 | ||
1282 | my $receiver = $param->{mail}; | |
1283 | ||
1284 | if ($receiver !~ $domainregex) { | |
be9e7168 | 1285 | sleep(3); |
6558ebc6 DC |
1286 | return undef; # silently ignore invalid mails |
1287 | } | |
1288 | ||
1289 | PVE::Tools::lock_file_full("${link_map_fn}.lck", 10, 1, sub { | |
7fe973a9 TL |
1290 | return if !-f $link_map_fn; |
1291 | # check if user is allowed to request mail | |
1292 | my $data = PVE::Tools::file_get_contents($link_map_fn); | |
1293 | for my $line (split("\n", $data)) { | |
1294 | next if $line !~ m/^\Q$receiver\E (\d+)$/; | |
1295 | if (($1 + $per_user_limit) > $starttime) { | |
be9e7168 | 1296 | sleep(3); |
7fe973a9 TL |
1297 | die "Too many requests for '$receiver', only one request per" |
1298 | ."hour is permitted. Please try again later\n"; | |
1299 | } else { | |
1300 | last; | |
6558ebc6 DC |
1301 | } |
1302 | } | |
1303 | }); | |
1304 | die $@ if $@; | |
1305 | ||
1306 | # we are allowed to send mail, lock and update file and send | |
1307 | PVE::Tools::lock_file("${link_map_fn}.lck", 10, sub { | |
d7bd84c9 TL |
1308 | my $newdata = "$receiver $starttime\n"; |
1309 | ||
6558ebc6 DC |
1310 | if (-f $link_map_fn) { |
1311 | my $data = PVE::Tools::file_get_contents($link_map_fn); | |
1312 | for my $line (split("\n", $data)) { | |
d7bd84c9 TL |
1313 | if ($line =~ m/^(?:.*) (\d+)$/) { |
1314 | if (($1 + $per_user_limit) > $starttime) { | |
6558ebc6 DC |
1315 | $newdata .= $line . "\n"; |
1316 | } | |
1317 | } | |
1318 | } | |
1319 | } | |
6558ebc6 DC |
1320 | PVE::Tools::file_set_contents($link_map_fn, $newdata); |
1321 | }); | |
1322 | die $@ if $@; | |
1323 | ||
1324 | send_link_mail($cfg, $receiver); | |
be9e7168 | 1325 | sleep(1); # always delay for a bit |
6558ebc6 DC |
1326 | |
1327 | return undef; | |
1328 | }}); | |
1329 | ||
b66faa68 | 1330 | 1; |