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