1 package PMG
::API2
::Quarantine
;
13 use Time
::HiRes
qw(usleep gettimeofday tv_interval);
17 use Mail
::SpamAssassin
;
20 use PVE
::Exception
qw(raise_param_exc raise_perm_exc);
21 use PVE
::Tools
qw(extract_param);
22 use PVE
::JSONSchema
qw(get_standard_option);
25 use PVE
::APIServer
::Formatter
;
28 use PMG
::AccessControl
;
36 use base
qw(PVE::RESTHandler);
40 my $extract_pmail = sub {
41 my ($authuser, $role) = @_;
43 if ($authuser =~ m/^(.+)\@quarantine$/) {
46 raise_param_exc
({ pmail
=> "got unexpected authuser '$authuser' with role '$role'"});
49 my $verify_optional_pmail = sub {
50 my ($authuser, $role, $pmail_param) = @_;
53 if ($role eq 'quser') {
54 $pmail = $extract_pmail->($authuser, $role);
55 raise_param_exc
({ pmail
=> "parameter not allwed with role '$role'"})
56 if defined($pmail_param) && ($pmail ne $pmail_param);
58 raise_param_exc
({ pmail
=> "parameter required with role '$role'"})
59 if !defined($pmail_param);
60 $pmail = $pmail_param;
69 return $res if !defined($info);
71 my $saversion = Mail
::SpamAssassin-
>VERSION;
73 my $salocaldir = "/var/lib/spamassassin/$saversion/updates_spamassassin_org";
75 $spamdesc = PMG
::Utils
::load_sa_descriptions
([$salocaldir]) if !$spamdesc;
77 foreach my $test (split (',', $info)) {
78 my ($name, $score) = split (':', $test);
80 my $info = { name
=> $name, score
=> $score + 0, desc
=> '-' };
81 if (my $si = $spamdesc->{$name}) {
82 $info->{desc
} = $si->{desc
};
83 $info->{url
} = $si->{url
} if defined($si->{url
});
91 my $extract_email = sub {
94 return $data if !$data;
96 if ($data =~ m/^.*\s(\S+)\s*$/) {
100 if ($data =~ m/^<([^<>\s]+)>$/) {
104 if ($data !~ m/[\s><]/ && $data =~ m/^(.+\@[^\.]+\..*[^\.]+)$/) {
113 my $get_real_sender = sub {
116 my @lines = split('\n', $ref->{header
});
117 my $head = Mail
::Header-
>new(\
@lines);
119 my @fromarray = split ('\s*,\s*', $head->get ('from') || $ref->{sender
});
120 my $from = $extract_email->($fromarray[0]) || $ref->{sender
};;
121 my $sender = $extract_email->($head->get ('sender'));
123 return $sender if $sender;
128 my $parse_header_info = sub {
131 my $res = { subject
=> '', from
=> '' };
133 my @lines = split('\n', $ref->{header
});
134 my $head = Mail
::Header-
>new(\
@lines);
136 $res->{subject
} = PMG
::Utils
::decode_rfc1522
(PVE
::Tools
::trim
($head->get('subject'))) // '';
138 $res->{from
} = PMG
::Utils
::decode_rfc1522
(PVE
::Tools
::trim
($head->get('from') || $ref->{sender
})) // '';
140 my $sender = PMG
::Utils
::decode_rfc1522
(PVE
::Tools
::trim
($head->get('sender')));
141 $res->{sender
} = $sender if $sender && ($sender ne $res->{from
});
143 $res->{envelope_sender
} = $ref->{sender
};
144 $res->{receiver
} = $ref->{receiver
} // $ref->{pmail
};
145 $res->{id
} = 'C' . $ref->{cid
} . 'R' . $ref->{rid
} . 'T' . $ref->{ticketid
};
146 $res->{time} = $ref->{time};
147 $res->{bytes
} = $ref->{bytes
};
149 my $qtype = $ref->{qtype
};
152 $res->{virusname
} = $ref->{info
};
153 $res->{spamlevel
} = 0;
154 } elsif ($qtype eq 'S') {
155 $res->{spamlevel
} = $ref->{spamlevel
} // 0;
161 my $pmail_param_type = get_standard_option
('pmg-email-address', {
162 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.",
166 __PACKAGE__-
>register_method ({
170 permissions
=> { user
=> 'all' },
171 description
=> "Directory index.",
173 additionalProperties
=> 0,
182 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
188 { name
=> 'whitelist' },
189 { name
=> 'blacklist' },
190 { name
=> 'content' },
192 { name
=> 'spamusers' },
193 { name
=> 'spamstatus' },
195 { name
=> 'virusstatus' },
196 { name
=> 'quarusers' },
197 { name
=> 'attachment' },
198 { name
=> 'listattachments' },
199 { name
=> 'download' },
200 { name
=> 'sendlink' },
207 my $read_or_modify_user_bw_list = sub {
208 my ($listname, $param, $addrs, $delete) = @_;
210 my $rpcenv = PMG
::RESTEnvironment-
>get();
211 my $authuser = $rpcenv->get_user();
212 my $role = $rpcenv->get_role();
214 my $pmail = $verify_optional_pmail->($authuser, $role, $param->{pmail
});
216 my $dbh = PMG
::DBTools
::open_ruledb
();
218 my $list = PMG
::Quarantine
::add_to_blackwhite
(
219 $dbh, $pmail, $listname, $addrs, $delete);
222 foreach my $a (@$list) { push @$res, { address
=> $a }; }
226 __PACKAGE__-
>register_method ({
230 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
231 description
=> "Show user whitelist.",
233 additionalProperties
=> 0,
235 pmail
=> $pmail_param_type,
252 return $read_or_modify_user_bw_list->('WL', $param);
255 __PACKAGE__-
>register_method ({
256 name
=> 'whitelist_add',
259 description
=> "Add user whitelist entries.",
260 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
263 additionalProperties
=> 0,
265 pmail
=> $pmail_param_type,
266 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
267 description
=> "The address you want to add.",
271 returns
=> { type
=> 'null' },
275 my $addresses = [split(',', $param->{address
})];
276 $read_or_modify_user_bw_list->('WL', $param, $addresses);
281 __PACKAGE__-
>register_method ({
282 name
=> 'whitelist_delete_base',
285 description
=> "Delete user whitelist entries.",
286 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
289 additionalProperties
=> 0,
291 pmail
=> $pmail_param_type,
292 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
294 description
=> "The address, or comma-separated list of addresses, you want to remove.",
298 returns
=> { type
=> 'null' },
302 my $addresses = [split(',', $param->{address
})];
303 $read_or_modify_user_bw_list->('WL', $param, $addresses, 1);
308 # FIXME: remove for PMG 7.0 - addresses can contain stuff like '/' which breaks
309 # API path resolution, thus we replaced it by above "un-templated" call
310 __PACKAGE__-
>register_method ({
311 name
=> 'whitelist_delete',
312 path
=> 'whitelist/{address}',
314 description
=> "Delete user whitelist entries.",
315 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
318 additionalProperties
=> 0,
320 pmail
=> $pmail_param_type,
321 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
322 description
=> "The address you want to remove.",
326 returns
=> { type
=> 'null' },
330 my $addresses = [split(',', $param->{address
})];
331 $read_or_modify_user_bw_list->('WL', $param, $addresses, 1);
336 __PACKAGE__-
>register_method ({
340 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
341 description
=> "Show user blacklist.",
343 additionalProperties
=> 0,
345 pmail
=> $pmail_param_type,
362 return $read_or_modify_user_bw_list->('BL', $param);
365 __PACKAGE__-
>register_method ({
366 name
=> 'blacklist_add',
369 description
=> "Add user blacklist entries.",
370 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
373 additionalProperties
=> 0,
375 pmail
=> $pmail_param_type,
376 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
377 description
=> "The address you want to add.",
381 returns
=> { type
=> 'null' },
385 my $addresses = [split(',', $param->{address
})];
386 $read_or_modify_user_bw_list->('BL', $param, $addresses);
391 __PACKAGE__-
>register_method ({
392 name
=> 'blacklist_delete_base',
395 description
=> "Delete user blacklist entries.",
396 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
399 additionalProperties
=> 0,
401 pmail
=> $pmail_param_type,
402 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
404 description
=> "The address, or comma-separated list of addresses, you want to remove.",
408 returns
=> { type
=> 'null' },
412 my $addresses = [split(',', $param->{address
})];
413 $read_or_modify_user_bw_list->('BL', $param, $addresses, 1);
418 # FIXME: remove for PMG 7.0 - addresses can contain stuff like '/' which breaks
419 # API path resolution, thus we replaced it by above "un-templated" call
420 __PACKAGE__-
>register_method ({
421 name
=> 'blacklist_delete',
422 path
=> 'blacklist/{address}',
424 description
=> "Delete user blacklist entries.",
425 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
428 additionalProperties
=> 0,
430 pmail
=> $pmail_param_type,
431 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
432 description
=> "The address you want to remove.",
436 returns
=> { type
=> 'null' },
440 my $addresses = [split(',', $param->{address
})];
441 $read_or_modify_user_bw_list->('BL', $param, $addresses, 1);
446 __PACKAGE__-
>register_method ({
450 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
451 description
=> "Get a list of receivers of spam in the given timespan (Default the last 24 hours).",
453 additionalProperties
=> 0,
455 starttime
=> get_standard_option
('pmg-starttime'),
456 endtime
=> get_standard_option
('pmg-endtime'),
465 description
=> 'the receiving email',
474 my $rpcenv = PMG
::RESTEnvironment-
>get();
475 my $authuser = $rpcenv->get_user();
479 my $dbh = PMG
::DBTools
::open_ruledb
();
481 my $start = $param->{starttime
} // (time - 86400);
482 my $end = $param->{endtime
} // ($start + 86400);
484 my $sth = $dbh->prepare(
485 "SELECT DISTINCT pmail " .
486 "FROM CMailStore, CMSReceivers WHERE " .
487 "time >= $start AND time < $end AND " .
488 "QType = 'S' AND CID = CMailStore_CID AND RID = CMailStore_RID " .
489 "AND Status = 'N' ORDER BY pmail");
493 while (my $ref = $sth->fetchrow_hashref()) {
494 push @$res, { mail
=> $ref->{pmail
} };
500 __PACKAGE__-
>register_method ({
501 name
=> 'spamstatus',
502 path
=> 'spamstatus',
504 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
505 description
=> "Get Spam Quarantine Status",
507 additionalProperties
=> 0,
514 description
=> 'Number of stored mails.',
518 description
=> "Estimated disk space usage in MByte.",
522 description
=> "Average size of stored mails in bytes.",
526 description
=> "Average spam level.",
534 my $dbh = PMG
::DBTools
::open_ruledb
();
535 my $ref = PMG
::DBTools
::get_quarantine_count
($dbh, 'S');
540 __PACKAGE__-
>register_method ({
544 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
545 description
=> "Get a list of users with whitelist/blacklist setttings.",
547 additionalProperties
=> 0,
551 description
=> 'If set, limits the result to the given list.',
552 enum
=> ['BL', 'WL'],
563 description
=> 'the receiving email',
572 my $rpcenv = PMG
::RESTEnvironment-
>get();
573 my $authuser = $rpcenv->get_user();
577 my $dbh = PMG
::DBTools
::open_ruledb
();
580 if ($param->{list
}) {
581 $sth = $dbh->prepare("SELECT DISTINCT pmail FROM UserPrefs WHERE name = ? ORDER BY pmail");
582 $sth->execute($param->{list
});
584 $sth = $dbh->prepare("SELECT DISTINCT pmail FROM UserPrefs ORDER BY pmail");
588 while (my $ref = $sth->fetchrow_hashref()) {
589 push @$res, { mail
=> $ref->{pmail
} };
595 my $quarantine_api = sub {
596 my ($param, $quartype, $check_pmail) = @_;
598 my $rpcenv = PMG
::RESTEnvironment-
>get();
599 my $authuser = $rpcenv->get_user();
601 my $start = $param->{starttime
} // (time - 86400);
602 my $end = $param->{endtime
} // ($start + 86400);
607 my $role = $rpcenv->get_role();
608 $pmail = $verify_optional_pmail->($authuser, $role, $param->{pmail
});
609 $select = "SELECT * " .
610 "FROM CMailStore, CMSReceivers WHERE " .
611 "pmail = ? AND time >= $start AND time < $end AND " .
612 "QType = '$quartype' AND CID = CMailStore_CID AND RID = CMailStore_RID " .
613 "AND Status = 'N' ORDER BY pmail, time, receiver";
615 $select = "SELECT * " .
616 "FROM CMailStore, CMSReceivers WHERE " .
617 "time >= $start AND time < $end AND " .
618 "QType = '$quartype' AND CID = CMailStore_CID AND RID = CMailStore_RID " .
619 "AND Status = 'N' ORDER BY time, receiver";
624 my $dbh = PMG
::DBTools
::open_ruledb
();
626 my $sth = $dbh->prepare($select);
629 $sth->execute($pmail);
634 while (my $ref = $sth->fetchrow_hashref()) {
635 my $data = $parse_header_info->($ref);
642 __PACKAGE__-
>register_method ({
646 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
647 description
=> "Get a list of quarantined spam mails in the given timeframe (default the last 24 hours) for the given user.",
649 additionalProperties
=> 0,
651 starttime
=> get_standard_option
('pmg-starttime'),
652 endtime
=> get_standard_option
('pmg-endtime'),
653 pmail
=> $pmail_param_type,
662 description
=> 'Unique ID',
666 description
=> "Size of raw email.",
670 description
=> "SMTP envelope sender.",
674 description
=> "Header 'From' field.",
678 description
=> "Header 'Sender' field.",
683 description
=> "Receiver email address",
687 description
=> "Header 'Subject' field.",
691 description
=> "Receive time stamp",
695 description
=> "Spam score.",
703 return $quarantine_api->($param, 'S', 1);
706 __PACKAGE__-
>register_method ({
710 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit' ] },
711 description
=> "Get a list of quarantined virus mails in the given timeframe (default the last 24 hours).",
713 additionalProperties
=> 0,
715 starttime
=> get_standard_option
('pmg-starttime'),
716 endtime
=> get_standard_option
('pmg-endtime'),
725 description
=> 'Unique ID',
729 description
=> "Size of raw email.",
733 description
=> "SMTP envelope sender.",
737 description
=> "Header 'From' field.",
741 description
=> "Header 'Sender' field.",
746 description
=> "Receiver email address",
750 description
=> "Header 'Subject' field.",
754 description
=> "Receive time stamp",
758 description
=> "Virus name.",
766 return $quarantine_api->($param, 'V');
769 __PACKAGE__-
>register_method ({
770 name
=> 'attachment',
771 path
=> 'attachment',
773 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit' ] },
774 description
=> "Get a list of quarantined attachment mails in the given timeframe (default the last 24 hours).",
776 additionalProperties
=> 0,
778 starttime
=> get_standard_option
('pmg-starttime'),
779 endtime
=> get_standard_option
('pmg-endtime'),
788 description
=> 'Unique ID',
792 description
=> "Size of raw email.",
796 description
=> "SMTP envelope sender.",
800 description
=> "Header 'From' field.",
804 description
=> "Header 'Sender' field.",
809 description
=> "Receiver email address",
813 description
=> "Header 'Subject' field.",
817 description
=> "Receive time stamp",
825 return $quarantine_api->($param, 'A');
828 __PACKAGE__-
>register_method ({
829 name
=> 'virusstatus',
830 path
=> 'virusstatus',
832 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
833 description
=> "Get Virus Quarantine Status",
835 additionalProperties
=> 0,
842 description
=> 'Number of stored mails.',
846 description
=> "Estimated disk space usage in MByte.",
850 description
=> "Average size of stored mails in bytes.",
858 my $dbh = PMG
::DBTools
::open_ruledb
();
859 my $ref = PMG
::DBTools
::get_quarantine_count
($dbh, 'V');
861 delete $ref->{avgspam
};
866 my $get_and_check_mail = sub {
867 my ($id, $rpcenv, $dbh) = @_;
869 my ($cid, $rid, $tid) = $id =~ m/^C(\d+)R(\d+)T(\d+)$/;
875 $dbh = PMG
::DBTools
::open_ruledb
();
878 my $ref = PMG
::DBTools
::load_mail_data
($dbh, $cid, $rid, $tid);
880 my $authuser = $rpcenv->get_user();
881 my $role = $rpcenv->get_role();
883 if ($role eq 'quser') {
884 my $quar_username = $ref->{pmail
} . '@quarantine';
885 raise_perm_exc
("mail does not belong to user '$authuser' ($ref->{pmail})")
886 if $authuser ne $quar_username;
892 __PACKAGE__-
>register_method ({
896 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
897 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).",
899 additionalProperties
=> 0,
902 description
=> 'Unique ID',
904 pattern
=> 'C\d+R\d+T\d+',
908 description
=> "Display 'raw' eml data. Deactivates size limit.",
919 description
=> 'Unique ID',
923 description
=> "Size of raw email.",
927 description
=> "SMTP envelope sender.",
931 description
=> "Header 'From' field.",
935 description
=> "Header 'Sender' field.",
940 description
=> "Receiver email address",
944 description
=> "Header 'Subject' field.",
948 description
=> "Receive time stamp",
952 description
=> "Spam score.",
956 description
=> "Information about matched spam tests (name, score, desc, url).",
960 description
=> "Raw email header data.",
964 description
=> "Raw email data (first 4096 bytes). Useful for preview. NOTE: The 'htmlmail' formatter displays the whole email.",
972 my $rpcenv = PMG
::RESTEnvironment-
>get();
973 my $format = $rpcenv->get_format();
975 my $raw = $param->{raw
} // 0;
977 my $ref = $get_and_check_mail->($param->{id
}, $rpcenv);
979 my $res = $parse_header_info->($ref);
981 my $filename = $ref->{file
};
982 my $spooldir = $PMG::MailQueue
::spooldir
;
984 my $path = "$spooldir/$filename";
986 if ($format eq 'htmlmail') {
988 my $cfg = PMG
::Config-
>new();
989 my $viewimages = $cfg->get('spamquar', 'viewimages');
990 my $allowhref = $cfg->get('spamquar', 'allowhrefs');
992 $res->{content
} = PMG
::HTMLMail
::email_to_html
($path, $raw, $viewimages, $allowhref) // 'unable to parse mail';
994 # to make result verification happy
997 $res->{spamlevel
} = 0;
998 $res->{spaminfo
} = [];
1000 # include additional details
1002 # we want to get the whole email in raw mode
1003 my $maxbytes = (!$raw)?
4096 : undef;
1005 my ($header, $content) = PMG
::HTMLMail
::read_raw_email
($path, $maxbytes);
1007 $res->{file
} = $ref->{file
};
1008 $res->{spaminfo
} = decode_spaminfo
($ref->{info
});
1009 $res->{header
} = $header;
1010 $res->{content
} = $content;
1017 my $get_attachments = sub {
1018 my ($mailid, $dumpdir, $with_path) = @_;
1020 my $rpcenv = PMG
::RESTEnvironment-
>get();
1022 my $ref = $get_and_check_mail->($mailid, $rpcenv);
1024 my $filename = $ref->{file
};
1025 my $spooldir = $PMG::MailQueue
::spooldir
;
1027 my $parser = PMG
::MIMEUtils
::new_mime_parser
({
1030 extract_uuencode
=> 0,
1031 dumpdir
=> $dumpdir,
1034 my $entity = $parser->parse_open("$spooldir/$filename");
1035 PMG
::MIMEUtils
::fixup_multipart
($entity);
1036 PMG
::MailQueue
::decode_entities
($parser, 'attachmentquarantine', $entity);
1041 PMG
::MIMEUtils
::traverse_mime_parts
($entity, sub {
1043 my $name = PMG
::Utils
::extract_filename
($part->head) || "part-$id";
1044 my $attachment_path = $part->{PMX_decoded_path
};
1045 return if !$attachment_path || ! -f
$attachment_path;
1046 my $size = -s
$attachment_path // 0;
1051 'content-type' => $part->head->mime_attr('content-type'),
1053 $entry->{path
} = $attachment_path if $with_path;
1061 __PACKAGE__-
>register_method ({
1062 name
=> 'listattachments',
1063 path
=> 'listattachments',
1065 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
1066 description
=> "Get Attachments for E-Mail in Quarantine.",
1068 additionalProperties
=> 0,
1071 description
=> 'Unique ID',
1073 pattern
=> 'C\d+R\d+T\d+',
1084 description
=> 'Attachment ID',
1088 description
=> "Size of raw attachment in bytes.",
1092 description
=> "Raw email header data.",
1096 description
=> "Raw email header data.",
1105 my $dumpdir = "/run/pmgproxy/pmg-$param->{id}-$$";
1106 my $res = $get_attachments->($param->{id
}, $dumpdir);
1113 __PACKAGE__-
>register_method ({
1117 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
1118 description
=> "Download E-Mail or Attachment from Quarantine.",
1121 additionalProperties
=> 0,
1124 description
=> 'Unique ID',
1126 pattern
=> 'C\d+R\d+T\d+',
1130 description
=> "The Attachment ID for the mail.",
1142 my $mailid = $param->{mailid
};
1143 my $attachmentid = $param->{attachmentid
};
1145 my $dumpdir = "/run/pmgproxy/pmg-$mailid-$$/";
1148 if ($attachmentid) {
1149 my $attachments = $get_attachments->($mailid, $dumpdir, 1);
1150 $res = $attachments->[$attachmentid];
1152 raise_param_exc
({ attachmentid
=> "Invalid Attachment ID for Mail."});
1155 my $rpcenv = PMG
::RESTEnvironment-
>get();
1156 my $ref = $get_and_check_mail->($mailid, $rpcenv);
1157 my $spooldir = $PMG::MailQueue
::spooldir
;
1160 'content-type' => 'message/rfc822',
1161 path
=> "$spooldir/$ref->{file}",
1165 $res->{fh
} = IO
::File-
>new($res->{path
}, '<') ||
1166 die "unable to open file '$res->{path}' - $!\n";
1168 rmtree
$dumpdir if -e
$dumpdir;
1174 PVE
::APIServer
::Formatter
::register_page_formatter
(
1175 'format' => 'htmlmail',
1177 path
=> '/quarantine/content',
1179 my ($res, $data, $param, $path, $auth, $config) = @_;
1181 if(!HTTP
::Status
::is_success
($res->{status
})) {
1182 return ("Error $res->{status}: $res->{message}", "text/plain");
1185 my $ct = "text/html;charset=UTF-8";
1187 my $raw = $data->{content
};
1189 return (encode
('UTF-8', $raw), $ct, 1);
1192 __PACKAGE__-
>register_method ({
1196 description
=> "Execute quarantine actions.",
1197 permissions
=> { check
=> [ 'admin', 'qmanager', 'quser'] },
1200 additionalProperties
=> 0,
1203 description
=> 'Unique IDs, seperate with ;',
1205 pattern
=> 'C\d+R\d+T\d+(;C\d+R\d+T\d+)*',
1208 description
=> 'Action - specify what you want to do with the mail.',
1210 enum
=> ['whitelist', 'blacklist', 'deliver', 'delete'],
1214 returns
=> { type
=> "null" },
1218 my $rpcenv = PMG
::RESTEnvironment-
>get();
1219 my $action = $param->{action
};
1220 my @idlist = split(';', $param->{id
});
1222 my $dbh = PMG
::DBTools
::open_ruledb
();
1224 for my $id (@idlist) {
1226 my $ref = $get_and_check_mail->($id, $rpcenv, $dbh);
1227 my $sender = $get_real_sender->($ref);
1229 if ($action eq 'whitelist') {
1230 PMG
::Quarantine
::add_to_blackwhite
($dbh, $ref->{pmail
}, 'WL', [ $sender ]);
1231 } elsif ($action eq 'blacklist') {
1232 PMG
::Quarantine
::add_to_blackwhite
($dbh, $ref->{pmail
}, 'BL', [ $sender ]);
1233 } elsif ($action eq 'deliver') {
1234 PMG
::Quarantine
::deliver_quarantined_mail
($dbh, $ref, $ref->{receiver
} // $ref->{pmail
});
1235 } elsif ($action eq 'delete') {
1236 PMG
::Quarantine
::delete_quarantined_mail
($dbh, $ref);
1238 die "internal error"; # should not be reached
1245 my $link_map_fn = "/run/pmgproxy/quarantinelink.map";
1246 my $per_user_limit = 60*60; # 1 hour
1248 my sub send_link_mail
{
1249 my ($cfg, $receiver) = @_;
1251 my $hostname = PVE
::INotify
::nodename
();
1252 my $fqdn = $cfg->get('spamquar', 'hostname') //
1253 PVE
::Tools
::get_fqdn
($hostname);
1255 my $port = $cfg->get('spamquar', 'port') // 8006;
1257 my $protocol = $cfg->get('spamquar', 'protocol') // 'https';
1259 my $protocol_fqdn_port = "$protocol://$fqdn";
1260 if (($protocol eq 'https' && $port != 443) ||
1261 ($protocol eq 'http' && $port != 80)) {
1262 $protocol_fqdn_port .= ":$port";
1265 my $mailfrom = $cfg->get ('spamquar', 'mailfrom') //
1266 "Proxmox Mail Gateway <postmaster>";
1268 my $ticket = PMG
::Ticket
::assemble_quarantine_ticket
($receiver);
1269 my $esc_ticket = uri_escape
($ticket);
1270 my $link = "$protocol_fqdn_port/quarantine?ticket=${esc_ticket}";
1272 my $text = "Here is your Link for the Spam Quarantine on $fqdn:\n\n$link\n";
1274 my $mail = MIME
::Entity-
>build(
1275 Type
=> "text/plain",
1278 Subject
=> "Proxmox Mail Gateway - Quarantine Link",
1282 # we use an empty envelope sender (we dont want to receive NDRs)
1283 PMG
::Utils
::reinject_mail
($mail, '', [$receiver], undef, $fqdn);
1286 __PACKAGE__-
>register_method ({
1290 description
=> "Send Quarantine link to given e-mail.",
1291 permissions
=> { user
=> 'world' },
1294 additionalProperties
=> 0,
1296 mail
=> get_standard_option
('pmg-email-address'),
1299 returns
=> { type
=> "null" },
1303 my $starttime = time();
1305 my $cfg = PMG
::Config-
>new();
1306 my $is_enabled = $cfg->get('spamquar', 'quarantinelink');
1308 die "This feature is not enabled\n";
1311 my $stat = File
::stat::stat($link_map_fn);
1313 if (defined($stat) && ($stat->mtime) + 5 > $starttime) {
1314 die "Too many requests. Please try again later\n";
1317 my $domains = PVE
::INotify
::read_file
('domains');
1318 my $domainregex = PMG
::Utils
::domain_regex
([keys %$domains]);
1320 my $receiver = $param->{mail
};
1322 if ($receiver !~ $domainregex) {
1323 return undef; # silently ignore invalid mails
1326 PVE
::Tools
::lock_file_full
("${link_map_fn}.lck", 10, 1, sub {
1327 if (-f
$link_map_fn) {
1328 # check if user is allowed to request mail
1329 my $lines = [split("\n", PVE
::Tools
::file_get_contents
($link_map_fn))];
1330 for my $line (@$lines) {
1331 next if $line !~ m/^\Q$receiver\E (\d+)$/;
1332 if (($1 + $per_user_limit) > $starttime) {
1333 die "Too many requests for '$receiver', only one request per hour is permitted. ".
1334 "Please try again later\n";
1343 # we are allowed to send mail, lock and update file and send
1344 PVE
::Tools
::lock_file
("${link_map_fn}.lck", 10, sub {
1345 my $newdata = "$receiver $starttime\n";
1347 if (-f
$link_map_fn) {
1348 my $data = PVE
::Tools
::file_get_contents
($link_map_fn);
1349 for my $line (split("\n", $data)) {
1350 if ($line =~ m/^(?:.*) (\d+)$/) {
1351 if (($1 + $per_user_limit) > $starttime) {
1352 $newdata .= $line . "\n";
1357 PVE
::Tools
::file_set_contents
($link_map_fn, $newdata);
1361 send_link_mail
($cfg, $receiver);