1 package PMG
::API2
::Quarantine
;
13 use URI
::Escape
qw(uri_escape);
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
;
27 use PMG
::Utils
qw(try_decode_utf8);
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 allowed 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";
74 my $sacustomdir = "/etc/mail/spamassassin";
75 my $kamdir = "/var/lib/spamassassin/$saversion/kam_sa-channels_mcgrail_com";
77 $spamdesc = PMG
::Utils
::load_sa_descriptions
([$salocaldir, $sacustomdir, $kamdir]) if !$spamdesc;
79 foreach my $test (split (',', $info)) {
80 my ($name, $score) = split (':', $test);
82 my $info = { name
=> $name, score
=> $score + 0, desc
=> '-' };
83 if (my $si = $spamdesc->{$name}) {
84 $info->{desc
} = $si->{desc
};
85 $info->{url
} = $si->{url
} if defined($si->{url
});
93 my $extract_email = sub {
96 return $data if !$data;
98 if ($data =~ m/^.*\s(\S+)\s*$/) {
102 if ($data =~ m/^<([^<>\s]+)>$/) {
106 if ($data !~ m/[\s><]/ && $data =~ m/^(.+\@[^\.]+\..*[^\.]+)$/) {
115 my $get_real_sender = sub {
118 my @lines = split('\n', $ref->{header
});
119 my $head = Mail
::Header-
>new(\
@lines);
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'));
125 return $sender if $sender;
130 my $parse_header_info = sub {
133 my $res = { subject
=> '', from
=> '' };
135 my @lines = split('\n', $ref->{header
});
136 my $head = Mail
::Header-
>new(\
@lines);
138 $res->{subject
} = PMG
::Utils
::decode_rfc1522
(PVE
::Tools
::trim
($head->get('subject'))) // '';
140 $res->{from
} = PMG
::Utils
::decode_rfc1522
(PVE
::Tools
::trim
($head->get('from') || $ref->{sender
})) // '';
142 my $sender = PMG
::Utils
::decode_rfc1522
(PVE
::Tools
::trim
($head->get('sender')));
143 $res->{sender
} = $sender if $sender && ($sender ne $res->{from
});
145 $res->{envelope_sender
} = try_decode_utf8
($ref->{sender
});
146 $res->{receiver
} = try_decode_utf8
($ref->{receiver
} // $ref->{pmail
});
147 $res->{id
} = 'C' . $ref->{cid
} . 'R' . $ref->{rid
} . 'T' . $ref->{ticketid
};
148 $res->{time} = $ref->{time};
149 $res->{bytes
} = $ref->{bytes
};
151 my $qtype = $ref->{qtype
};
154 $res->{virusname
} = $ref->{info
};
155 $res->{spamlevel
} = 0;
156 } elsif ($qtype eq 'S') {
157 $res->{spamlevel
} = $ref->{spamlevel
} // 0;
163 my $pmail_param_type = get_standard_option
('pmg-email-address', {
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.",
168 __PACKAGE__-
>register_method ({
172 permissions
=> { user
=> 'all' },
173 description
=> "Directory index.",
175 additionalProperties
=> 0,
184 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
190 { name
=> 'whitelist' },
191 { name
=> 'blacklist' },
192 { name
=> 'content' },
194 { name
=> 'spamusers' },
195 { name
=> 'spamstatus' },
197 { name
=> 'virusstatus' },
198 { name
=> 'quarusers' },
199 { name
=> 'attachment' },
200 { name
=> 'listattachments' },
201 { name
=> 'download' },
202 { name
=> 'sendlink' },
209 my $read_or_modify_user_bw_list = sub {
210 my ($listname, $param, $addrs, $delete) = @_;
212 my $rpcenv = PMG
::RESTEnvironment-
>get();
213 my $authuser = $rpcenv->get_user();
214 my $role = $rpcenv->get_role();
216 my $pmail = $verify_optional_pmail->($authuser, $role, $param->{pmail
});
218 my $dbh = PMG
::DBTools
::open_ruledb
();
220 my $list = PMG
::Quarantine
::add_to_blackwhite
(
221 $dbh, $pmail, $listname, $addrs, $delete);
224 foreach my $a (@$list) { push @$res, { address
=> $a }; }
228 __PACKAGE__-
>register_method ({
232 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
233 description
=> "Show user whitelist.",
235 additionalProperties
=> 0,
237 pmail
=> $pmail_param_type,
254 return $read_or_modify_user_bw_list->('WL', $param);
257 __PACKAGE__-
>register_method ({
258 name
=> 'whitelist_add',
261 description
=> "Add user whitelist entries.",
262 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
265 additionalProperties
=> 0,
267 pmail
=> $pmail_param_type,
268 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
269 description
=> "The address you want to add.",
273 returns
=> { type
=> 'null' },
277 my $addresses = [split(',', $param->{address
})];
278 $read_or_modify_user_bw_list->('WL', $param, $addresses);
283 __PACKAGE__-
>register_method ({
284 name
=> 'whitelist_delete_base',
287 description
=> "Delete user whitelist entries.",
288 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
291 additionalProperties
=> 0,
293 pmail
=> $pmail_param_type,
294 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
296 description
=> "The address, or comma-separated list of addresses, you want to remove.",
300 returns
=> { type
=> 'null' },
304 my $addresses = [split(',', $param->{address
})];
305 $read_or_modify_user_bw_list->('WL', $param, $addresses, 1);
310 __PACKAGE__-
>register_method ({
314 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
315 description
=> "Show user blacklist.",
317 additionalProperties
=> 0,
319 pmail
=> $pmail_param_type,
336 return $read_or_modify_user_bw_list->('BL', $param);
339 __PACKAGE__-
>register_method ({
340 name
=> 'blacklist_add',
343 description
=> "Add user blacklist entries.",
344 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
347 additionalProperties
=> 0,
349 pmail
=> $pmail_param_type,
350 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
351 description
=> "The address you want to add.",
355 returns
=> { type
=> 'null' },
359 my $addresses = [split(',', $param->{address
})];
360 $read_or_modify_user_bw_list->('BL', $param, $addresses);
365 __PACKAGE__-
>register_method ({
366 name
=> 'blacklist_delete_base',
369 description
=> "Delete 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', {
378 description
=> "The address, or comma-separated list of addresses, you want to remove.",
382 returns
=> { type
=> 'null' },
386 my $addresses = [split(',', $param->{address
})];
387 $read_or_modify_user_bw_list->('BL', $param, $addresses, 1);
393 my $quar_type_map = {
399 __PACKAGE__-
>register_method ({
403 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
404 description
=> "Get a list of receivers of spam in the given timespan (Default the last 24 hours).",
406 additionalProperties
=> 0,
408 starttime
=> get_standard_option
('pmg-starttime'),
409 endtime
=> get_standard_option
('pmg-endtime'),
410 'quarantine-type' => {
411 description
=> 'Query this type of quarantine for users.',
415 enum
=> [keys $quar_type_map->%*],
425 description
=> 'the receiving email',
434 my $rpcenv = PMG
::RESTEnvironment-
>get();
435 my $authuser = $rpcenv->get_user();
439 my $dbh = PMG
::DBTools
::open_ruledb
();
441 my $start = $param->{starttime
} // (time - 86400);
442 my $end = $param->{endtime
} // ($start + 86400);
444 my $quar_type = $param->{'quarantine-type'} // 'spam';
446 my $sth = $dbh->prepare(
447 "SELECT DISTINCT pmail " .
448 "FROM CMailStore, CMSReceivers WHERE " .
449 "time >= $start AND time < $end AND " .
450 "QType = ? AND CID = CMailStore_CID AND RID = CMailStore_RID " .
451 "AND Status = 'N' ORDER BY pmail");
453 $sth->execute($quar_type_map->{$quar_type});
455 while (my $ref = $sth->fetchrow_hashref()) {
456 push @$res, { mail
=> PMG
::Utils
::try_decode_utf8
($ref->{pmail
}) };
462 __PACKAGE__-
>register_method ({
463 name
=> 'spamstatus',
464 path
=> 'spamstatus',
466 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
467 description
=> "Get Spam Quarantine Status",
469 additionalProperties
=> 0,
476 description
=> 'Number of stored mails.',
480 description
=> "Estimated disk space usage in MByte.",
484 description
=> "Average size of stored mails in bytes.",
488 description
=> "Average spam level.",
496 my $dbh = PMG
::DBTools
::open_ruledb
();
497 my $ref = PMG
::DBTools
::get_quarantine_count
($dbh, 'S');
502 __PACKAGE__-
>register_method ({
506 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
507 description
=> "Get a list of users with whitelist/blacklist settings.",
509 additionalProperties
=> 0,
513 description
=> 'If set, limits the result to the given list.',
514 enum
=> ['BL', 'WL'],
525 description
=> 'the receiving email',
534 my $rpcenv = PMG
::RESTEnvironment-
>get();
535 my $authuser = $rpcenv->get_user();
539 my $dbh = PMG
::DBTools
::open_ruledb
();
542 if ($param->{list
}) {
543 $sth = $dbh->prepare("SELECT DISTINCT pmail FROM UserPrefs WHERE name = ? ORDER BY pmail");
544 $sth->execute($param->{list
});
546 $sth = $dbh->prepare("SELECT DISTINCT pmail FROM UserPrefs ORDER BY pmail");
550 while (my $ref = $sth->fetchrow_hashref()) {
551 push @$res, { mail
=> PMG
::Utils
::try_decode_utf8
($ref->{pmail
}) };
557 my $quarantine_api = sub {
558 my ($param, $quartype, $check_pmail) = @_;
560 my $rpcenv = PMG
::RESTEnvironment-
>get();
561 my $authuser = $rpcenv->get_user();
562 my $role = $rpcenv->get_role();
564 my $start = $param->{starttime
} // (time - 86400);
565 my $end = $param->{endtime
} // ($start + 86400);
568 my $dbh = PMG
::DBTools
::open_ruledb
();
572 if ($check_pmail || $role eq 'quser') {
573 $pmail = $verify_optional_pmail->($authuser, $role, $param->{pmail
});
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"
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"
587 if ($check_pmail || $role eq 'quser') {
588 $sth->execute(encode
('UTF-8', $pmail));
594 while (my $ref = $sth->fetchrow_hashref()) {
595 push @$res, $parse_header_info->($ref);
601 __PACKAGE__-
>register_method ({
605 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
606 description
=> "Get a list of quarantined spam mails in the given timeframe (default the last 24 hours) for the given user.",
608 additionalProperties
=> 0,
610 starttime
=> get_standard_option
('pmg-starttime'),
611 endtime
=> get_standard_option
('pmg-endtime'),
612 pmail
=> $pmail_param_type,
621 description
=> 'Unique ID',
625 description
=> "Size of raw email.",
629 description
=> "SMTP envelope sender.",
633 description
=> "Header 'From' field.",
637 description
=> "Header 'Sender' field.",
642 description
=> "Receiver email address",
646 description
=> "Header 'Subject' field.",
650 description
=> "Receive time stamp",
654 description
=> "Spam score.",
662 return $quarantine_api->($param, 'S', defined($param->{pmail
}));
665 __PACKAGE__-
>register_method ({
669 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit' ] },
670 description
=> "Get a list of quarantined virus mails in the given timeframe (default the last 24 hours).",
672 additionalProperties
=> 0,
674 starttime
=> get_standard_option
('pmg-starttime'),
675 endtime
=> get_standard_option
('pmg-endtime'),
676 pmail
=> $pmail_param_type,
685 description
=> 'Unique ID',
689 description
=> "Size of raw email.",
693 description
=> "SMTP envelope sender.",
697 description
=> "Header 'From' field.",
701 description
=> "Header 'Sender' field.",
706 description
=> "Receiver email address",
710 description
=> "Header 'Subject' field.",
714 description
=> "Receive time stamp",
718 description
=> "Virus name.",
726 return $quarantine_api->($param, 'V', defined($param->{pmail
}));
729 __PACKAGE__-
>register_method ({
730 name
=> 'attachment',
731 path
=> 'attachment',
733 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit' ] },
734 description
=> "Get a list of quarantined attachment mails in the given timeframe (default the last 24 hours).",
736 additionalProperties
=> 0,
738 starttime
=> get_standard_option
('pmg-starttime'),
739 endtime
=> get_standard_option
('pmg-endtime'),
740 pmail
=> $pmail_param_type,
749 description
=> 'Unique ID',
753 description
=> "Size of raw email.",
757 description
=> "SMTP envelope sender.",
761 description
=> "Header 'From' field.",
765 description
=> "Header 'Sender' field.",
770 description
=> "Receiver email address",
774 description
=> "Header 'Subject' field.",
778 description
=> "Receive time stamp",
786 return $quarantine_api->($param, 'A', defined($param->{pmail
}));
789 __PACKAGE__-
>register_method ({
790 name
=> 'virusstatus',
791 path
=> 'virusstatus',
793 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
794 description
=> "Get Virus Quarantine Status",
796 additionalProperties
=> 0,
803 description
=> 'Number of stored mails.',
807 description
=> "Estimated disk space usage in MByte.",
811 description
=> "Average size of stored mails in bytes.",
819 my $dbh = PMG
::DBTools
::open_ruledb
();
820 my $ref = PMG
::DBTools
::get_quarantine_count
($dbh, 'V');
822 delete $ref->{avgspam
};
827 my $get_and_check_mail = sub {
828 my ($id, $rpcenv, $dbh) = @_;
830 my ($cid, $rid, $tid) = $id =~ m/^C(\d+)R(\d+)T(\d+)$/;
831 ($cid, $rid, $tid) = (int($cid), int($rid), int($tid));
833 $dbh = PMG
::DBTools
::open_ruledb
() if !$dbh;
835 my $ref = PMG
::DBTools
::load_mail_data
($dbh, $cid, $rid, $tid);
837 my $authuser = $rpcenv->get_user();
838 my $role = $rpcenv->get_role();
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})")
843 if $authuser ne $quar_username;
849 __PACKAGE__-
>register_method ({
853 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
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).",
856 additionalProperties
=> 0,
859 description
=> 'Unique ID',
861 pattern
=> 'C\d+R\d+T\d+',
865 description
=> "Display 'raw' eml data. Deactivates size limit.",
876 description
=> 'Unique ID',
880 description
=> "Size of raw email.",
884 description
=> "SMTP envelope sender.",
888 description
=> "Header 'From' field.",
892 description
=> "Header 'Sender' field.",
897 description
=> "Receiver email address",
901 description
=> "Header 'Subject' field.",
905 description
=> "Receive time stamp",
909 description
=> "Spam score.",
913 description
=> "Information about matched spam tests (name, score, desc, url).",
917 description
=> "Raw email header data.",
921 description
=> "Raw email data (first 4096 bytes). Useful for preview. NOTE: The 'htmlmail' formatter displays the whole email.",
929 my $rpcenv = PMG
::RESTEnvironment-
>get();
930 my $format = $rpcenv->get_format();
932 my $raw = $param->{raw
} // 0;
934 my $ref = $get_and_check_mail->($param->{id
}, $rpcenv);
936 my $res = $parse_header_info->($ref);
938 my $filename = $ref->{file
};
939 my $spooldir = $PMG::MailQueue
::spooldir
;
941 my $path = "$spooldir/$filename";
943 if ($format eq 'htmlmail') {
945 my $cfg = PMG
::Config-
>new();
946 my $viewimages = $cfg->get('spamquar', 'viewimages');
947 my $allowhref = $cfg->get('spamquar', 'allowhrefs');
949 $res->{content
} = PMG
::HTMLMail
::email_to_html
($path, $raw, $viewimages, $allowhref) // 'unable to parse mail';
951 # to make result verification happy
954 $res->{spamlevel
} = 0;
955 $res->{spaminfo
} = [];
957 # include additional details
959 # we want to get the whole email in raw mode
960 my $maxbytes = (!$raw)?
4096 : undef;
962 my ($header, $content) = PMG
::HTMLMail
::read_raw_email
($path, $maxbytes);
964 $res->{file
} = $ref->{file
};
965 $res->{spaminfo
} = decode_spaminfo
($ref->{info
});
966 $res->{header
} = $header;
967 $res->{content
} = $content;
974 my $get_attachments = sub {
975 my ($mailid, $dumpdir, $with_path) = @_;
977 my $rpcenv = PMG
::RESTEnvironment-
>get();
979 my $ref = $get_and_check_mail->($mailid, $rpcenv);
981 my $filename = $ref->{file
};
982 my $spooldir = $PMG::MailQueue
::spooldir
;
984 my $parser = PMG
::MIMEUtils
::new_mime_parser
({
987 extract_uuencode
=> 0,
991 my $entity = $parser->parse_open("$spooldir/$filename");
992 PMG
::MIMEUtils
::fixup_multipart
($entity);
993 PMG
::MailQueue
::decode_entities
($parser, 'attachmentquarantine', $entity);
998 PMG
::MIMEUtils
::traverse_mime_parts
($entity, sub {
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;
1008 'content-disposition' => $part->head->mime_attr('content-disposition'),
1009 'content-type' => $part->head->mime_attr('content-type'),
1011 $entry->{path
} = $attachment_path if $with_path;
1019 __PACKAGE__-
>register_method ({
1020 name
=> 'listattachments',
1021 path
=> 'listattachments',
1023 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
1024 description
=> "Get Attachments for E-Mail in Quarantine.",
1026 additionalProperties
=> 0,
1029 description
=> 'Unique ID',
1031 pattern
=> 'C\d+R\d+T\d+',
1042 description
=> 'Attachment ID',
1046 description
=> "Size of raw attachment in bytes.",
1050 description
=> "Raw email header data.",
1054 description
=> "Raw email header data.",
1063 my $dumpdir = "/run/pmgproxy/pmg-$param->{id}-$$";
1064 my $res = $get_attachments->($param->{id
}, $dumpdir);
1071 __PACKAGE__-
>register_method ({
1075 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
1076 description
=> "Download E-Mail or Attachment from Quarantine.",
1079 additionalProperties
=> 0,
1082 description
=> 'Unique ID',
1084 pattern
=> 'C\d+R\d+T\d+',
1088 description
=> "The Attachment ID for the mail.",
1100 my $mailid = $param->{mailid
};
1101 my $attachmentid = $param->{attachmentid
};
1103 my $dumpdir = "/run/pmgproxy/pmg-$mailid-$$/";
1106 if ($attachmentid) {
1107 my $attachments = $get_attachments->($mailid, $dumpdir, 1);
1108 $res = $attachments->[$attachmentid];
1110 raise_param_exc
({ attachmentid
=> "Invalid Attachment ID for Mail."});
1113 my $rpcenv = PMG
::RESTEnvironment-
>get();
1114 my $ref = $get_and_check_mail->($mailid, $rpcenv);
1115 my $spooldir = $PMG::MailQueue
::spooldir
;
1118 'content-type' => 'message/rfc822',
1119 path
=> "$spooldir/$ref->{file}",
1123 $res->{fh
} = IO
::File-
>new($res->{path
}, '<') ||
1124 die "unable to open file '$res->{path}' - $!\n";
1126 rmtree
$dumpdir if -e
$dumpdir;
1132 PVE
::APIServer
::Formatter
::register_page_formatter
(
1133 'format' => 'htmlmail',
1135 path
=> '/quarantine/content',
1137 my ($res, $data, $param, $path, $auth, $config) = @_;
1139 if(!HTTP
::Status
::is_success
($res->{status
})) {
1140 return ("Error $res->{status}: $res->{message}", "text/plain");
1143 my $ct = "text/html;charset=UTF-8";
1145 my $raw = $data->{content
};
1147 return (encode
('UTF-8', $raw), $ct, 1);
1150 __PACKAGE__-
>register_method ({
1154 description
=> "Execute quarantine actions.",
1155 permissions
=> { check
=> [ 'admin', 'qmanager', 'quser'] },
1158 additionalProperties
=> 0,
1161 description
=> 'Unique IDs, separate with ;',
1163 pattern
=> 'C\d+R\d+T\d+(;C\d+R\d+T\d+)*',
1166 description
=> 'Action - specify what you want to do with the mail.',
1168 enum
=> ['whitelist', 'blacklist', 'deliver', 'delete'],
1172 returns
=> { type
=> "null" },
1176 my $rpcenv = PMG
::RESTEnvironment-
>get();
1177 my $action = $param->{action
};
1178 my @idlist = split(';', $param->{id
});
1180 my $dbh = PMG
::DBTools
::open_ruledb
();
1182 for my $id (@idlist) {
1184 my $ref = $get_and_check_mail->($id, $rpcenv, $dbh);
1185 my $sender = try_decode_utf8
($get_real_sender->($ref));
1186 my $pmail = try_decode_utf8
($ref->{pmail
});
1187 my $receiver = try_decode_utf8
($ref->{receiver
} // $ref->{pmail
});
1189 if ($action eq 'whitelist') {
1190 PMG
::Quarantine
::add_to_blackwhite
($dbh, $pmail, 'WL', [ $sender ]);
1191 PMG
::Quarantine
::deliver_quarantined_mail
($dbh, $ref, $receiver);
1192 } elsif ($action eq 'blacklist') {
1193 PMG
::Quarantine
::add_to_blackwhite
($dbh, $pmail, 'BL', [ $sender ]);
1194 PMG
::Quarantine
::delete_quarantined_mail
($dbh, $ref);
1195 } elsif ($action eq 'deliver') {
1196 PMG
::Quarantine
::deliver_quarantined_mail
($dbh, $ref, $receiver);
1197 } elsif ($action eq 'delete') {
1198 PMG
::Quarantine
::delete_quarantined_mail
($dbh, $ref);
1200 die "internal error, unknown action '$action'"; # should not be reached
1207 my $link_map_fn = "/run/pmgproxy/quarantinelink.map";
1208 my $per_user_limit = 60*60; # 1 hour
1210 my sub send_link_mail
{
1211 my ($cfg, $receiver) = @_;
1213 my $hostname = PVE
::INotify
::nodename
();
1214 my $fqdn = $cfg->get('spamquar', 'hostname') //
1215 PVE
::Tools
::get_fqdn
($hostname);
1217 my $port = $cfg->get('spamquar', 'port') // 8006;
1219 my $protocol = $cfg->get('spamquar', 'protocol') // 'https';
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";
1227 my $mailfrom = $cfg->get ('spamquar', 'mailfrom') //
1228 "Proxmox Mail Gateway <postmaster>";
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}";
1234 my $text = "Here is your Link for the Spam Quarantine on $fqdn:\n\n$link\n";
1236 my $mail = MIME
::Entity-
>build(
1237 Type
=> "text/plain",
1240 Subject
=> "Proxmox Mail Gateway - Quarantine Link",
1244 # we use an empty envelope sender (we don't want to receive NDRs)
1245 PMG
::Utils
::reinject_local_mail
($mail, '', [$receiver], undef, $fqdn);
1248 __PACKAGE__-
>register_method ({
1252 description
=> "Send Quarantine link to given e-mail.",
1253 permissions
=> { user
=> 'world' },
1256 additionalProperties
=> 0,
1258 mail
=> get_standard_option
('pmg-email-address'),
1261 returns
=> { type
=> "null" },
1265 my $starttime = time();
1267 my $cfg = PMG
::Config-
>new();
1268 my $is_enabled = $cfg->get('spamquar', 'quarantinelink');
1270 die "This feature is not enabled\n";
1273 my $stat = File
::stat::stat($link_map_fn);
1275 if (defined($stat) && ($stat->mtime + 5) > $starttime) {
1277 die "Too many requests. Please try again later\n";
1280 my $domains = PVE
::INotify
::read_file
('domains');
1281 my $domainregex = PMG
::Utils
::domain_regex
([keys %$domains]);
1283 my $receiver = $param->{mail
};
1285 if ($receiver !~ $domainregex) {
1287 return undef; # silently ignore invalid mails
1290 PVE
::Tools
::lock_file_full
("${link_map_fn}.lck", 10, 1, sub {
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) {
1298 die "Too many requests for '$receiver', only one request per"
1299 ."hour is permitted. Please try again later\n";
1307 # we are allowed to send mail, lock and update file and send
1308 PVE
::Tools
::lock_file
("${link_map_fn}.lck", 10, sub {
1309 my $newdata = "$receiver $starttime\n";
1311 if (-f
$link_map_fn) {
1312 my $data = PVE
::Tools
::file_get_contents
($link_map_fn);
1313 for my $line (split("\n", $data)) {
1314 if ($line =~ m/^(?:.*) (\d+)$/) {
1315 if (($1 + $per_user_limit) > $starttime) {
1316 $newdata .= $line . "\n";
1321 PVE
::Tools
::file_set_contents
($link_map_fn, $newdata);
1325 send_link_mail
($cfg, $receiver);
1326 sleep(1); # always delay for a bit