]> git.proxmox.com Git - pmg-api.git/blame - src/PMG/API2/Quarantine.pm
RuleDB/Remove: add attachment quarantine option
[pmg-api.git] / src / PMG / API2 / Quarantine.pm
CommitLineData
b66faa68
DM
1package PMG::API2::Quarantine;
2
3use strict;
4use warnings;
5use Time::Local;
6use Time::Zone;
7use Data::Dumper;
34db0c3f 8use Encode;
b66faa68 9
34db0c3f 10use Mail::Header;
e325aa6f 11use Mail::SpamAssassin;
dae021a8 12
b66faa68 13use PVE::SafeSyslog;
6e8886d4 14use PVE::Exception qw(raise_param_exc raise_perm_exc);
b66faa68
DM
15use PVE::Tools qw(extract_param);
16use PVE::JSONSchema qw(get_standard_option);
17use PVE::RESTHandler;
18use PVE::INotify;
34db0c3f 19use PVE::APIServer::Formatter;
b66faa68 20
dae021a8 21use PMG::Utils;
b66faa68 22use PMG::AccessControl;
34db0c3f 23use PMG::Config;
b66faa68 24use PMG::DBTools;
34db0c3f 25use PMG::HTMLMail;
e84bf942 26use PMG::Quarantine;
b66faa68
DM
27
28use base qw(PVE::RESTHandler);
29
1284c016
DM
30my $spamdesc;
31
9efdabf0
DM
32my $extract_pmail = sub {
33 my ($authuser, $role) = @_;
34
35 if ($authuser =~ m/^(.+)\@quarantine$/) {
36 return $1;
37 }
38 raise_param_exc({ pmail => "got unexpected authuser '$authuser' with role '$role'"});
39};
40
157a946b 41my $verify_optional_pmail = sub {
666b5e8f 42 my ($authuser, $role, $pmail_param) = @_;
157a946b 43
666b5e8f 44 my $pmail;
157a946b 45 if ($role eq 'quser') {
9efdabf0 46 $pmail = $extract_pmail->($authuser, $role);
666b5e8f
DM
47 raise_param_exc({ pmail => "parameter not allwed with role '$role'"})
48 if defined($pmail_param) && ($pmail ne $pmail_param);
157a946b 49 } else {
5182cea0 50 raise_param_exc({ pmail => "parameter required with role '$role'"})
666b5e8f
DM
51 if !defined($pmail_param);
52 $pmail = $pmail_param;
157a946b
DM
53 }
54 return $pmail;
55};
56
1284c016
DM
57sub decode_spaminfo {
58 my ($info) = @_;
59
21752ddf
ML
60 my $res = [];
61 return $res if !defined($info);
62
e325aa6f
DC
63 my $saversion = Mail::SpamAssassin->VERSION;
64
65 my $salocaldir = "/var/lib/spamassassin/$saversion/updates_spamassassin_org";
66
67 $spamdesc = PMG::Utils::load_sa_descriptions([$salocaldir]) if !$spamdesc;
1284c016 68
1284c016
DM
69 foreach my $test (split (',', $info)) {
70 my ($name, $score) = split (':', $test);
71
273c538f 72 my $info = { name => $name, score => $score + 0, desc => '-' };
1284c016
DM
73 if (my $si = $spamdesc->{$name}) {
74 $info->{desc} = $si->{desc};
75 $info->{url} = $si->{url} if defined($si->{url});
76 }
77 push @$res, $info;
78 }
79
80 return $res;
81}
b66faa68 82
157a946b
DM
83my $extract_email = sub {
84 my $data = shift;
85
86 return $data if !$data;
87
88 if ($data =~ m/^.*\s(\S+)\s*$/) {
89 $data = $1;
90 }
91
92 if ($data =~ m/^<([^<>\s]+)>$/) {
93 $data = $1;
94 }
95
96 if ($data !~ m/[\s><]/ && $data =~ m/^(.+\@[^\.]+\..*[^\.]+)$/) {
97 $data = $1;
98 } else {
99 $data = undef;
100 }
101
102 return $data;
103};
104
105my $get_real_sender = sub {
106 my ($ref) = @_;
107
108 my @lines = split('\n', $ref->{header});
109 my $head = Mail::Header->new(\@lines);
110
111 my @fromarray = split ('\s*,\s*', $head->get ('from') || $ref->{sender});
112 my $from = $extract_email->($fromarray[0]) || $ref->{sender};;
113 my $sender = $extract_email->($head->get ('sender'));
114
115 return $sender if $sender;
116
117 return $from;
118};
119
dae021a8
DM
120my $parse_header_info = sub {
121 my ($ref) = @_;
122
123 my $res = { subject => '', from => '' };
124
125 my @lines = split('\n', $ref->{header});
126 my $head = Mail::Header->new(\@lines);
127
128 $res->{subject} = PMG::Utils::decode_rfc1522(PVE::Tools::trim($head->get('subject'))) // '';
129
130 my @fromarray = split('\s*,\s*', $head->get('from') || $ref->{sender});
131
132 $res->{from} = PMG::Utils::decode_rfc1522(PVE::Tools::trim ($fromarray[0])) // '';
133
134 my $sender = PMG::Utils::decode_rfc1522(PVE::Tools::trim($head->get('sender')));
1284c016 135 $res->{sender} = $sender if $sender && ($sender ne $res->{from});
dae021a8
DM
136
137 $res->{envelope_sender} = $ref->{sender};
e84bf942 138 $res->{receiver} = $ref->{receiver} // $ref->{pmail};
666b5e8f 139 $res->{id} = 'C' . $ref->{cid} . 'R' . $ref->{rid} . 'T' . $ref->{ticketid};
dae021a8
DM
140 $res->{time} = $ref->{time};
141 $res->{bytes} = $ref->{bytes};
142
1284c016
DM
143 my $qtype = $ref->{qtype};
144
145 if ($qtype eq 'V') {
146 $res->{virusname} = $ref->{info};
2ad4def7 147 $res->{spamlevel} = 0;
1284c016
DM
148 } elsif ($qtype eq 'S') {
149 $res->{spamlevel} = $ref->{spamlevel} // 0;
1284c016
DM
150 }
151
dae021a8
DM
152 return $res;
153};
154
7b6ff2ee 155my $pmail_param_type = get_standard_option('pmg-email-address', {
157a946b 156 description => "List entries for the user with this primary email address. Quarantine users cannot speficy this parameter, but it is required for all other roles.",
157a946b 157 optional => 1,
7b6ff2ee 158});
6e8886d4 159
b66faa68
DM
160__PACKAGE__->register_method ({
161 name => 'index',
162 path => '',
163 method => 'GET',
164 permissions => { user => 'all' },
165 description => "Directory index.",
166 parameters => {
167 additionalProperties => 0,
168 properties => {},
169 },
170 returns => {
171 type => 'array',
172 items => {
173 type => "object",
174 properties => {},
175 },
176 links => [ { rel => 'child', href => "{name}" } ],
177 },
178 code => sub {
179 my ($param) = @_;
180
181 my $result = [
157a946b
DM
182 { name => 'whitelist' },
183 { name => 'blacklist' },
6e8886d4 184 { name => 'content' },
b66faa68 185 { name => 'spam' },
4f41cebc 186 { name => 'spamusers' },
ab02ba10 187 { name => 'spamstatus' },
b66faa68 188 { name => 'virus' },
c3246f47 189 { name => 'virusstatus' },
091d8086 190 { name => 'quarusers' },
b66faa68
DM
191 ];
192
193 return $result;
194 }});
195
157a946b 196
767657cb
DM
197my $read_or_modify_user_bw_list = sub {
198 my ($listname, $param, $addrs, $delete) = @_;
157a946b
DM
199
200 my $rpcenv = PMG::RESTEnvironment->get();
201 my $authuser = $rpcenv->get_user();
202 my $role = $rpcenv->get_role();
203
204 my $pmail = $verify_optional_pmail->($authuser, $role, $param->{pmail});
205
206 my $dbh = PMG::DBTools::open_ruledb();
207
767657cb
DM
208 my $list = PMG::Quarantine::add_to_blackwhite(
209 $dbh, $pmail, $listname, $addrs, $delete);
157a946b
DM
210
211 my $res = [];
212 foreach my $a (@$list) { push @$res, { address => $a }; }
213 return $res;
214};
215
216__PACKAGE__->register_method ({
217 name => 'whitelist',
218 path => 'whitelist',
219 method => 'GET',
220 permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] },
221 description => "Show user whitelist.",
222 parameters => {
223 additionalProperties => 0,
224 properties => {
225 pmail => $pmail_param_type,
226 },
227 },
228 returns => {
229 type => 'array',
230 items => {
231 type => "object",
232 properties => {
233 address => {
234 type => "string",
235 },
236 },
237 },
238 },
239 code => sub {
240 my ($param) = @_;
241
767657cb
DM
242 return $read_or_modify_user_bw_list->('WL', $param);
243 }});
244
245__PACKAGE__->register_method ({
246 name => 'whitelist_add',
247 path => 'whitelist',
248 method => 'POST',
249 description => "Add user whitelist entries.",
250 permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] },
251 protected => 1,
252 parameters => {
253 additionalProperties => 0,
254 properties => {
255 pmail => $pmail_param_type,
1a1bde13 256 address => get_standard_option('pmg-whiteblacklist-entry-list', {
767657cb 257 description => "The address you want to add.",
01929101 258 }),
767657cb
DM
259 },
260 },
261 returns => { type => 'null' },
262 code => sub {
263 my ($param) = @_;
264
1a1bde13
DC
265 my $addresses = [split(',', $param->{address})];
266 $read_or_modify_user_bw_list->('WL', $param, $addresses);
767657cb
DM
267
268 return undef;
269 }});
270
271__PACKAGE__->register_method ({
272 name => 'whitelist_delete',
273 path => 'whitelist/{address}',
274 method => 'DELETE',
275 description => "Delete user whitelist entries.",
276 permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] },
277 protected => 1,
278 parameters => {
279 additionalProperties => 0,
280 properties => {
281 pmail => $pmail_param_type,
1a1bde13 282 address => get_standard_option('pmg-whiteblacklist-entry-list', {
767657cb 283 description => "The address you want to remove.",
01929101 284 }),
767657cb
DM
285 },
286 },
287 returns => { type => 'null' },
288 code => sub {
289 my ($param) = @_;
290
1a1bde13
DC
291 my $addresses = [split(',', $param->{address})];
292 $read_or_modify_user_bw_list->('WL', $param, $addresses, 1);
767657cb
DM
293
294 return undef;
157a946b
DM
295 }});
296
297__PACKAGE__->register_method ({
298 name => 'blacklist',
299 path => 'blacklist',
300 method => 'GET',
301 permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] },
302 description => "Show user blacklist.",
303 parameters => {
304 additionalProperties => 0,
305 properties => {
306 pmail => $pmail_param_type,
307 },
308 },
309 returns => {
310 type => 'array',
311 items => {
312 type => "object",
313 properties => {
314 address => {
315 type => "string",
316 },
317 },
318 },
319 },
320 code => sub {
321 my ($param) = @_;
322
767657cb
DM
323 return $read_or_modify_user_bw_list->('BL', $param);
324 }});
325
326__PACKAGE__->register_method ({
327 name => 'blacklist_add',
328 path => 'blacklist',
329 method => 'POST',
330 description => "Add user blacklist entries.",
331 permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] },
332 protected => 1,
333 parameters => {
334 additionalProperties => 0,
335 properties => {
336 pmail => $pmail_param_type,
1a1bde13 337 address => get_standard_option('pmg-whiteblacklist-entry-list', {
767657cb 338 description => "The address you want to add.",
01929101 339 }),
767657cb
DM
340 },
341 },
342 returns => { type => 'null' },
343 code => sub {
344 my ($param) = @_;
345
1a1bde13
DC
346 my $addresses = [split(',', $param->{address})];
347 $read_or_modify_user_bw_list->('BL', $param, $addresses);
767657cb
DM
348
349 return undef;
350 }});
351
352__PACKAGE__->register_method ({
353 name => 'blacklist_delete',
354 path => 'blacklist/{address}',
355 method => 'DELETE',
356 description => "Delete user blacklist entries.",
357 permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] },
358 protected => 1,
359 parameters => {
360 additionalProperties => 0,
361 properties => {
362 pmail => $pmail_param_type,
1a1bde13 363 address => get_standard_option('pmg-whiteblacklist-entry-list', {
767657cb 364 description => "The address you want to remove.",
01929101 365 }),
767657cb
DM
366 },
367 },
368 returns => { type => 'null' },
369 code => sub {
370 my ($param) = @_;
371
1a1bde13
DC
372 my $addresses = [split(',', $param->{address})];
373 $read_or_modify_user_bw_list->('BL', $param, $addresses, 1);
767657cb
DM
374
375 return undef;
157a946b
DM
376 }});
377
4f41cebc
DC
378__PACKAGE__->register_method ({
379 name => 'spamusers',
380 path => 'spamusers',
381 method => 'GET',
24a0be04 382 permissions => { check => [ 'admin', 'qmanager', 'audit'] },
4f41cebc
DC
383 description => "Get a list of receivers of spam in the given timespan (Default the last 24 hours).",
384 parameters => {
385 additionalProperties => 0,
386 properties => {
6214b5cf
DM
387 starttime => get_standard_option('pmg-starttime'),
388 endtime => get_standard_option('pmg-endtime'),
4f41cebc
DC
389 },
390 },
391 returns => {
392 type => 'array',
393 items => {
394 type => "object",
395 properties => {
396 mail => {
397 description => 'the receiving email',
398 type => 'string',
399 },
400 },
401 },
402 },
403 code => sub {
404 my ($param) = @_;
405
406 my $rpcenv = PMG::RESTEnvironment->get();
407 my $authuser = $rpcenv->get_user();
4f41cebc
DC
408
409 my $res = [];
410
411 my $dbh = PMG::DBTools::open_ruledb();
412
413 my $start = $param->{starttime} // (time - 86400);
414 my $end = $param->{endtime} // ($start + 86400);
415
416 my $sth = $dbh->prepare(
417 "SELECT DISTINCT pmail " .
418 "FROM CMailStore, CMSReceivers WHERE " .
419 "time >= $start AND time < $end AND " .
420 "QType = 'S' AND CID = CMailStore_CID AND RID = CMailStore_RID " .
421 "AND Status = 'N' ORDER BY pmail");
422
423 $sth->execute();
424
425 while (my $ref = $sth->fetchrow_hashref()) {
426 push @$res, { mail => $ref->{pmail} };
427 }
428
429 return $res;
430 }});
431
ab02ba10
DM
432__PACKAGE__->register_method ({
433 name => 'spamstatus',
434 path => 'spamstatus',
435 method => 'GET',
436 permissions => { check => [ 'admin', 'qmanager', 'audit'] },
437 description => "Get Spam Quarantine Status",
438 parameters => {
439 additionalProperties => 0,
440 properties => {},
441 },
442 returns => {
443 type => "object",
444 properties => {
445 count => {
446 description => 'Number of stored mails.',
447 type => 'integer',
448 },
449 mbytes => {
450 description => "Estimated disk space usage in MByte.",
451 type => 'number',
452 },
453 avgbytes => {
454 description => "Average size of stored mails in bytes.",
455 type => 'number',
456 },
457 avgspam => {
458 description => "Average spam level.",
459 type => 'number',
460 },
461 },
462 },
463 code => sub {
464 my ($param) = @_;
465
ab02ba10
DM
466 my $dbh = PMG::DBTools::open_ruledb();
467 my $ref = PMG::DBTools::get_quarantine_count($dbh, 'S');
468
469 return $ref;
470 }});
471
9867e2da
DM
472__PACKAGE__->register_method ({
473 name => 'quarusers',
474 path => 'quarusers',
475 method => 'GET',
476 permissions => { check => [ 'admin', 'qmanager', 'audit'] },
477 description => "Get a list of users with whitelist/blacklist setttings.",
478 parameters => {
479 additionalProperties => 0,
648954c6
DC
480 properties => {
481 list => {
482 type => 'string',
483 description => 'If set, limits the result to the given list.',
484 enum => ['BL', 'WL'],
485 optional => 1,
486 },
487 },
9867e2da
DM
488 },
489 returns => {
490 type => 'array',
491 items => {
492 type => "object",
493 properties => {
494 mail => {
495 description => 'the receiving email',
496 type => 'string',
497 },
498 },
499 },
500 },
501 code => sub {
502 my ($param) = @_;
503
504 my $rpcenv = PMG::RESTEnvironment->get();
505 my $authuser = $rpcenv->get_user();
9867e2da
DM
506
507 my $res = [];
508
509 my $dbh = PMG::DBTools::open_ruledb();
510
648954c6
DC
511 my $sth;
512 if ($param->{list}) {
513 $sth = $dbh->prepare("SELECT DISTINCT pmail FROM UserPrefs WHERE name = ? ORDER BY pmail");
514 $sth->execute($param->{list});
515 } else {
516 $sth = $dbh->prepare("SELECT DISTINCT pmail FROM UserPrefs ORDER BY pmail");
517 $sth->execute();
518 }
9867e2da
DM
519
520 while (my $ref = $sth->fetchrow_hashref()) {
521 push @$res, { mail => $ref->{pmail} };
522 }
523
524 return $res;
525 }});
526
daebd67e
DC
527my $quarantine_api = sub {
528 my ($param, $quartype, $check_pmail) = @_;
529
530 my $rpcenv = PMG::RESTEnvironment->get();
531 my $authuser = $rpcenv->get_user();
532
533 my $start = $param->{starttime} // (time - 86400);
534 my $end = $param->{endtime} // ($start + 86400);
535
536 my $select;
537 my $pmail;
538 if ($check_pmail) {
539 my $role = $rpcenv->get_role();
540 $pmail = $verify_optional_pmail->($authuser, $role, $param->{pmail});
541 $select = "SELECT * " .
542 "FROM CMailStore, CMSReceivers WHERE " .
543 "pmail = ? AND time >= $start AND time < $end AND " .
544 "QType = '$quartype' AND CID = CMailStore_CID AND RID = CMailStore_RID " .
545 "AND Status = 'N' ORDER BY pmail, time, receiver";
546 } else {
547 $select = "SELECT * " .
548 "FROM CMailStore, CMSReceivers WHERE " .
549 "time >= $start AND time < $end AND " .
550 "QType = '$quartype' AND CID = CMailStore_CID AND RID = CMailStore_RID " .
551 "AND Status = 'N' ORDER BY time, receiver";
552 }
553
554 my $res = [];
555
556 my $dbh = PMG::DBTools::open_ruledb();
557
558 my $sth = $dbh->prepare($select);
559
560 if ($check_pmail) {
561 $sth->execute($pmail);
562 } else {
563 $sth->execute();
564 }
565
566 while (my $ref = $sth->fetchrow_hashref()) {
567 my $data = $parse_header_info->($ref);
568 push @$res, $data;
569 }
570
571 return $res;
572};
573
ded33c7c 574__PACKAGE__->register_method ({
83ce499f
DC
575 name => 'spam',
576 path => 'spam',
ded33c7c
DM
577 method => 'GET',
578 permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] },
83ce499f 579 description => "Get a list of quarantined spam mails in the given timeframe (default the last 24 hours) for the given user.",
ded33c7c
DM
580 parameters => {
581 additionalProperties => 0,
582 properties => {
6214b5cf
DM
583 starttime => get_standard_option('pmg-starttime'),
584 endtime => get_standard_option('pmg-endtime'),
157a946b 585 pmail => $pmail_param_type,
ded33c7c
DM
586 },
587 },
588 returns => {
589 type => 'array',
590 items => {
591 type => "object",
dae021a8
DM
592 properties => {
593 id => {
594 description => 'Unique ID',
595 type => 'string',
596 },
597 bytes => {
598 description => "Size of raw email.",
599 type => 'integer' ,
600 },
601 envelope_sender => {
602 description => "SMTP envelope sender.",
603 type => 'string',
604 },
605 from => {
606 description => "Header 'From' field.",
607 type => 'string',
608 },
609 sender => {
610 description => "Header 'Sender' field.",
611 type => 'string',
612 optional => 1,
613 },
614 receiver => {
615 description => "Receiver email address",
616 type => 'string',
617 },
618 subject => {
619 description => "Header 'Subject' field.",
620 type => 'string',
621 },
622 time => {
623 description => "Receive time stamp",
624 type => 'integer',
625 },
1284c016
DM
626 spamlevel => {
627 description => "Spam score.",
628 type => 'number',
629 },
dae021a8 630 },
ded33c7c
DM
631 },
632 },
633 code => sub {
634 my ($param) = @_;
daebd67e 635 return $quarantine_api->($param, 'S', 1);
b66faa68
DM
636 }});
637
bb01dcf8
DM
638__PACKAGE__->register_method ({
639 name => 'virus',
640 path => 'virus',
641 method => 'GET',
642 permissions => { check => [ 'admin', 'qmanager', 'audit' ] },
643 description => "Get a list of quarantined virus mails in the given timeframe (default the last 24 hours).",
644 parameters => {
645 additionalProperties => 0,
646 properties => {
6214b5cf
DM
647 starttime => get_standard_option('pmg-starttime'),
648 endtime => get_standard_option('pmg-endtime'),
bb01dcf8
DM
649 },
650 },
651 returns => {
652 type => 'array',
653 items => {
654 type => "object",
655 properties => {
656 id => {
657 description => 'Unique ID',
658 type => 'string',
659 },
660 bytes => {
661 description => "Size of raw email.",
662 type => 'integer' ,
663 },
664 envelope_sender => {
665 description => "SMTP envelope sender.",
666 type => 'string',
667 },
668 from => {
669 description => "Header 'From' field.",
670 type => 'string',
671 },
672 sender => {
673 description => "Header 'Sender' field.",
674 type => 'string',
675 optional => 1,
676 },
677 receiver => {
678 description => "Receiver email address",
679 type => 'string',
680 },
681 subject => {
682 description => "Header 'Subject' field.",
683 type => 'string',
684 },
685 time => {
686 description => "Receive time stamp",
687 type => 'integer',
688 },
689 virusname => {
690 description => "Virus name.",
691 type => 'string',
692 },
693 },
694 },
695 },
696 code => sub {
697 my ($param) = @_;
daebd67e 698 return $quarantine_api->($param, 'V');
bb01dcf8
DM
699 }});
700
c3246f47
DM
701__PACKAGE__->register_method ({
702 name => 'virusstatus',
703 path => 'virusstatus',
704 method => 'GET',
705 permissions => { check => [ 'admin', 'qmanager', 'audit'] },
706 description => "Get Virus Quarantine Status",
707 parameters => {
708 additionalProperties => 0,
709 properties => {},
710 },
711 returns => {
712 type => "object",
713 properties => {
714 count => {
715 description => 'Number of stored mails.',
716 type => 'integer',
717 },
718 mbytes => {
719 description => "Estimated disk space usage in MByte.",
720 type => 'number',
721 },
722 avgbytes => {
723 description => "Average size of stored mails in bytes.",
724 type => 'number',
725 },
726 },
727 },
728 code => sub {
729 my ($param) = @_;
730
731 my $dbh = PMG::DBTools::open_ruledb();
732 my $ref = PMG::DBTools::get_quarantine_count($dbh, 'V');
733
f7fa880f
DM
734 delete $ref->{avgspam};
735
c3246f47
DM
736 return $ref;
737 }});
738
6e8886d4
DM
739__PACKAGE__->register_method ({
740 name => 'content',
741 path => 'content',
742 method => 'GET',
743 permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] },
34db0c3f 744 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
745 parameters => {
746 additionalProperties => 0,
747 properties => {
748 id => {
749 description => 'Unique ID',
750 type => 'string',
666b5e8f
DM
751 pattern => 'C\d+R\d+T\d+',
752 maxLength => 60,
6e8886d4 753 },
34db0c3f 754 raw => {
c81cf188 755 description => "Display 'raw' eml data. Deactivates size limit.",
34db0c3f
DM
756 type => 'boolean',
757 optional => 1,
758 default => 0,
759 },
6e8886d4
DM
760 },
761 },
762 returns => {
763 type => "object",
cd31bb45
DM
764 properties => {
765 id => {
766 description => 'Unique ID',
767 type => 'string',
768 },
769 bytes => {
770 description => "Size of raw email.",
771 type => 'integer' ,
772 },
773 envelope_sender => {
774 description => "SMTP envelope sender.",
775 type => 'string',
776 },
777 from => {
778 description => "Header 'From' field.",
779 type => 'string',
780 },
781 sender => {
782 description => "Header 'Sender' field.",
783 type => 'string',
784 optional => 1,
785 },
786 receiver => {
787 description => "Receiver email address",
788 type => 'string',
789 },
790 subject => {
791 description => "Header 'Subject' field.",
792 type => 'string',
793 },
794 time => {
795 description => "Receive time stamp",
796 type => 'integer',
797 },
798 spamlevel => {
799 description => "Spam score.",
800 type => 'number',
801 },
802 spaminfo => {
803 description => "Information about matched spam tests (name, score, desc, url).",
804 type => 'array',
805 },
806 header => {
807 description => "Raw email header data.",
808 type => 'string',
809 },
810 content => {
811 description => "Raw email data (first 4096 bytes). Useful for preview. NOTE: The 'htmlmail' formatter displays the whole email.",
812 type => 'string',
6eac8473 813 },
cd31bb45 814 },
6e8886d4
DM
815 },
816 code => sub {
817 my ($param) = @_;
818
819 my $rpcenv = PMG::RESTEnvironment->get();
820 my $authuser = $rpcenv->get_user();
821 my $role = $rpcenv->get_role();
34db0c3f 822 my $format = $rpcenv->get_format();
6e8886d4 823
c81cf188
DC
824 my $raw = $param->{raw} // 0;
825
666b5e8f 826 my ($cid, $rid, $tid) = $param->{id} =~ m/^C(\d+)R(\d+)T(\d+)$/;
6e8886d4
DM
827 $cid = int($cid);
828 $rid = int($rid);
666b5e8f 829 $tid = int($tid);
9efdabf0 830
6e8886d4
DM
831 my $dbh = PMG::DBTools::open_ruledb();
832
666b5e8f 833 my $ref = PMG::DBTools::load_mail_data($dbh, $cid, $rid, $tid);
6e8886d4
DM
834
835 if ($role eq 'quser') {
6060e345 836 my $quar_username = $ref->{pmail} . '@quarantine';
666b5e8f 837 raise_perm_exc("mail does not belong to user '$authuser' ($ref->{pmail})")
6060e345 838 if $authuser ne $quar_username;
6e8886d4
DM
839 }
840
841 my $res = $parse_header_info->($ref);
842
6e8886d4
DM
843 my $filename = $ref->{file};
844 my $spooldir = $PMG::MailQueue::spooldir;
845
846 my $path = "$spooldir/$filename";
847
34db0c3f
DM
848 if ($format eq 'htmlmail') {
849
850 my $cfg = PMG::Config->new();
851 my $viewimages = $cfg->get('spamquar', 'viewimages');
852 my $allowhref = $cfg->get('spamquar', 'allowhrefs');
853
69da4eb6 854 $res->{content} = PMG::HTMLMail::email_to_html($path, $raw, $viewimages, $allowhref) // 'unable to parse mail';
34db0c3f 855
157a946b
DM
856 # to make result verification happy
857 $res->{file} = '';
858 $res->{header} = '';
2ad4def7 859 $res->{spamlevel} = 0;
157a946b 860 $res->{spaminfo} = [];
34db0c3f 861 } else {
cd31bb45 862 # include additional details
34db0c3f 863
c81cf188
DC
864 # we want to get the whole email in raw mode
865 my $maxbytes = (!$raw)? 4096 : undef;
866
867 my ($header, $content) = PMG::HTMLMail::read_raw_email($path, $maxbytes);
157a946b 868
cd31bb45
DM
869 $res->{file} = $ref->{file};
870 $res->{spaminfo} = decode_spaminfo($ref->{info});
34db0c3f
DM
871 $res->{header} = $header;
872 $res->{content} = $content;
873 }
6e8886d4 874
6e8886d4
DM
875
876 return $res;
877
878 }});
879
34db0c3f
DM
880PVE::APIServer::Formatter::register_page_formatter(
881 'format' => 'htmlmail',
882 method => 'GET',
883 path => '/quarantine/content',
884 code => sub {
885 my ($res, $data, $param, $path, $auth, $config) = @_;
886
887 if(!HTTP::Status::is_success($res->{status})) {
888 return ("Error $res->{status}: $res->{message}", "text/plain");
889 }
890
891 my $ct = "text/html;charset=UTF-8";
892
893 my $raw = $data->{content};
894
895 return (encode('UTF-8', $raw), $ct, 1);
896});
897
157a946b
DM
898__PACKAGE__->register_method ({
899 name =>'action',
900 path => 'content',
901 method => 'POST',
902 description => "Execute quarantine actions.",
903 permissions => { check => [ 'admin', 'qmanager', 'quser'] },
904 protected => 1,
905 parameters => {
906 additionalProperties => 0,
907 properties => {
908 id => {
b7894d7c 909 description => 'Unique IDs, seperate with ;',
157a946b 910 type => 'string',
b7894d7c 911 pattern => 'C\d+R\d+T\d+(;C\d+R\d+T\d+)*',
157a946b
DM
912 },
913 action => {
914 description => 'Action - specify what you want to do with the mail.',
915 type => 'string',
916 enum => ['whitelist', 'blacklist', 'deliver', 'delete'],
917 },
918 },
919 },
920 returns => { type => "null" },
921 code => sub {
922 my ($param) = @_;
923
924 my $rpcenv = PMG::RESTEnvironment->get();
925 my $authuser = $rpcenv->get_user();
926 my $role = $rpcenv->get_role();
927 my $action = $param->{action};
b7894d7c 928 my @idlist = split(';', $param->{id});
157a946b 929
e18901ef
DM
930 my $dbh = PMG::DBTools::open_ruledb();
931
b7894d7c
DC
932 for my $id (@idlist) {
933 my ($cid, $rid, $tid) = $id =~ m/^C(\d+)R(\d+)T(\d+)$/;
934 $cid = int($cid);
935 $rid = int($rid);
936 $tid = int($tid);
8f34e0b1 937
b7894d7c 938 my $ref = PMG::DBTools::load_mail_data($dbh, $cid, $rid, $tid);
157a946b 939
b7894d7c
DC
940 if ($role eq 'quser') {
941 my $quar_username = $ref->{pmail} . '@quarantine';
942 raise_perm_exc("mail does not belong to user '$authuser' ($ref->{pmail})")
6060e345 943 if $authuser ne $quar_username;
b7894d7c
DC
944 }
945
946 my $sender = $get_real_sender->($ref);
947
948 if ($action eq 'whitelist') {
949 PMG::Quarantine::add_to_blackwhite($dbh, $ref->{pmail}, 'WL', [ $sender ]);
950 } elsif ($action eq 'blacklist') {
951 PMG::Quarantine::add_to_blackwhite($dbh, $ref->{pmail}, 'BL', [ $sender ]);
952 } elsif ($action eq 'deliver') {
953 PMG::Quarantine::deliver_quarantined_mail($dbh, $ref, $ref->{receiver} // $ref->{pmail});
954 } elsif ($action eq 'delete') {
955 PMG::Quarantine::delete_quarantined_mail($dbh, $ref);
956 } else {
957 die "internal error"; # should not be reached
958 }
157a946b
DM
959 }
960
961 return undef;
962 }});
e84bf942 963
b66faa68 9641;