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