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";
76 $spamdesc = PMG
::Utils
::load_sa_descriptions
([$salocaldir, $sacustomdir]) if !$spamdesc;
78 foreach my $test (split (',', $info)) {
79 my ($name, $score) = split (':', $test);
81 my $info = { name
=> $name, score
=> $score + 0, desc
=> '-' };
82 if (my $si = $spamdesc->{$name}) {
83 $info->{desc
} = $si->{desc
};
84 $info->{url
} = $si->{url
} if defined($si->{url
});
92 my $extract_email = sub {
95 return $data if !$data;
97 if ($data =~ m/^.*\s(\S+)\s*$/) {
101 if ($data =~ m/^<([^<>\s]+)>$/) {
105 if ($data !~ m/[\s><]/ && $data =~ m/^(.+\@[^\.]+\..*[^\.]+)$/) {
114 my $get_real_sender = sub {
117 my @lines = split('\n', $ref->{header
});
118 my $head = Mail
::Header-
>new(\
@lines);
120 my @fromarray = split ('\s*,\s*', $head->get ('from') || $ref->{sender
});
121 my $from = $extract_email->($fromarray[0]) || $ref->{sender
};;
122 my $sender = $extract_email->($head->get ('sender'));
124 return $sender if $sender;
129 my $parse_header_info = sub {
132 my $res = { subject
=> '', from
=> '' };
134 my @lines = split('\n', $ref->{header
});
135 my $head = Mail
::Header-
>new(\
@lines);
137 $res->{subject
} = PMG
::Utils
::decode_rfc1522
(PVE
::Tools
::trim
($head->get('subject'))) // '';
139 $res->{from
} = PMG
::Utils
::decode_rfc1522
(PVE
::Tools
::trim
($head->get('from') || $ref->{sender
})) // '';
141 my $sender = PMG
::Utils
::decode_rfc1522
(PVE
::Tools
::trim
($head->get('sender')));
142 $res->{sender
} = $sender if $sender && ($sender ne $res->{from
});
144 $res->{envelope_sender
} = try_decode_utf8
($ref->{sender
});
145 $res->{receiver
} = try_decode_utf8
($ref->{receiver
} // $ref->{pmail
});
146 $res->{id
} = 'C' . $ref->{cid
} . 'R' . $ref->{rid
} . 'T' . $ref->{ticketid
};
147 $res->{time} = $ref->{time};
148 $res->{bytes
} = $ref->{bytes
};
150 my $qtype = $ref->{qtype
};
153 $res->{virusname
} = $ref->{info
};
154 $res->{spamlevel
} = 0;
155 } elsif ($qtype eq 'S') {
156 $res->{spamlevel
} = $ref->{spamlevel
} // 0;
162 my $pmail_param_type = get_standard_option
('pmg-email-address', {
163 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.",
167 __PACKAGE__-
>register_method ({
171 permissions
=> { user
=> 'all' },
172 description
=> "Directory index.",
174 additionalProperties
=> 0,
183 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
189 { name
=> 'whitelist' },
190 { name
=> 'blacklist' },
191 { name
=> 'content' },
193 { name
=> 'spamusers' },
194 { name
=> 'spamstatus' },
196 { name
=> 'virusstatus' },
197 { name
=> 'quarusers' },
198 { name
=> 'attachment' },
199 { name
=> 'listattachments' },
200 { name
=> 'download' },
201 { name
=> 'sendlink' },
208 my $read_or_modify_user_bw_list = sub {
209 my ($listname, $param, $addrs, $delete) = @_;
211 my $rpcenv = PMG
::RESTEnvironment-
>get();
212 my $authuser = $rpcenv->get_user();
213 my $role = $rpcenv->get_role();
215 my $pmail = $verify_optional_pmail->($authuser, $role, $param->{pmail
});
217 my $dbh = PMG
::DBTools
::open_ruledb
();
219 my $list = PMG
::Quarantine
::add_to_blackwhite
(
220 $dbh, $pmail, $listname, $addrs, $delete);
223 foreach my $a (@$list) { push @$res, { address
=> $a }; }
227 __PACKAGE__-
>register_method ({
231 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
232 description
=> "Show user whitelist.",
234 additionalProperties
=> 0,
236 pmail
=> $pmail_param_type,
253 return $read_or_modify_user_bw_list->('WL', $param);
256 __PACKAGE__-
>register_method ({
257 name
=> 'whitelist_add',
260 description
=> "Add user whitelist entries.",
261 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
264 additionalProperties
=> 0,
266 pmail
=> $pmail_param_type,
267 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
268 description
=> "The address you want to add.",
272 returns
=> { type
=> 'null' },
276 my $addresses = [split(',', $param->{address
})];
277 $read_or_modify_user_bw_list->('WL', $param, $addresses);
282 __PACKAGE__-
>register_method ({
283 name
=> 'whitelist_delete_base',
286 description
=> "Delete user whitelist entries.",
287 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
290 additionalProperties
=> 0,
292 pmail
=> $pmail_param_type,
293 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
295 description
=> "The address, or comma-separated list of addresses, you want to remove.",
299 returns
=> { type
=> 'null' },
303 my $addresses = [split(',', $param->{address
})];
304 $read_or_modify_user_bw_list->('WL', $param, $addresses, 1);
309 __PACKAGE__-
>register_method ({
313 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
314 description
=> "Show user blacklist.",
316 additionalProperties
=> 0,
318 pmail
=> $pmail_param_type,
335 return $read_or_modify_user_bw_list->('BL', $param);
338 __PACKAGE__-
>register_method ({
339 name
=> 'blacklist_add',
342 description
=> "Add user blacklist entries.",
343 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
346 additionalProperties
=> 0,
348 pmail
=> $pmail_param_type,
349 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
350 description
=> "The address you want to add.",
354 returns
=> { type
=> 'null' },
358 my $addresses = [split(',', $param->{address
})];
359 $read_or_modify_user_bw_list->('BL', $param, $addresses);
364 __PACKAGE__-
>register_method ({
365 name
=> 'blacklist_delete_base',
368 description
=> "Delete user blacklist entries.",
369 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
372 additionalProperties
=> 0,
374 pmail
=> $pmail_param_type,
375 address
=> get_standard_option
('pmg-whiteblacklist-entry-list', {
377 description
=> "The address, or comma-separated list of addresses, you want to remove.",
381 returns
=> { type
=> 'null' },
385 my $addresses = [split(',', $param->{address
})];
386 $read_or_modify_user_bw_list->('BL', $param, $addresses, 1);
392 my $quar_type_map = {
398 __PACKAGE__-
>register_method ({
402 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
403 description
=> "Get a list of receivers of spam in the given timespan (Default the last 24 hours).",
405 additionalProperties
=> 0,
407 starttime
=> get_standard_option
('pmg-starttime'),
408 endtime
=> get_standard_option
('pmg-endtime'),
409 'quarantine-type' => {
410 description
=> 'Query this type of quarantine for users.',
414 enum
=> [keys $quar_type_map->%*],
424 description
=> 'the receiving email',
433 my $rpcenv = PMG
::RESTEnvironment-
>get();
434 my $authuser = $rpcenv->get_user();
438 my $dbh = PMG
::DBTools
::open_ruledb
();
440 my $start = $param->{starttime
} // (time - 86400);
441 my $end = $param->{endtime
} // ($start + 86400);
443 my $quar_type = $param->{'quarantine-type'} // 'spam';
445 my $sth = $dbh->prepare(
446 "SELECT DISTINCT pmail " .
447 "FROM CMailStore, CMSReceivers WHERE " .
448 "time >= $start AND time < $end AND " .
449 "QType = ? AND CID = CMailStore_CID AND RID = CMailStore_RID " .
450 "AND Status = 'N' ORDER BY pmail");
452 $sth->execute($quar_type_map->{$quar_type});
454 while (my $ref = $sth->fetchrow_hashref()) {
455 push @$res, { mail
=> PMG
::Utils
::try_decode_utf8
($ref->{pmail
}) };
461 __PACKAGE__-
>register_method ({
462 name
=> 'spamstatus',
463 path
=> 'spamstatus',
465 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
466 description
=> "Get Spam Quarantine Status",
468 additionalProperties
=> 0,
475 description
=> 'Number of stored mails.',
479 description
=> "Estimated disk space usage in MByte.",
483 description
=> "Average size of stored mails in bytes.",
487 description
=> "Average spam level.",
495 my $dbh = PMG
::DBTools
::open_ruledb
();
496 my $ref = PMG
::DBTools
::get_quarantine_count
($dbh, 'S');
501 __PACKAGE__-
>register_method ({
505 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
506 description
=> "Get a list of users with whitelist/blacklist settings.",
508 additionalProperties
=> 0,
512 description
=> 'If set, limits the result to the given list.',
513 enum
=> ['BL', 'WL'],
524 description
=> 'the receiving email',
533 my $rpcenv = PMG
::RESTEnvironment-
>get();
534 my $authuser = $rpcenv->get_user();
538 my $dbh = PMG
::DBTools
::open_ruledb
();
541 if ($param->{list
}) {
542 $sth = $dbh->prepare("SELECT DISTINCT pmail FROM UserPrefs WHERE name = ? ORDER BY pmail");
543 $sth->execute($param->{list
});
545 $sth = $dbh->prepare("SELECT DISTINCT pmail FROM UserPrefs ORDER BY pmail");
549 while (my $ref = $sth->fetchrow_hashref()) {
550 push @$res, { mail
=> PMG
::Utils
::try_decode_utf8
($ref->{pmail
}) };
556 my $quarantine_api = sub {
557 my ($param, $quartype, $check_pmail) = @_;
559 my $rpcenv = PMG
::RESTEnvironment-
>get();
560 my $authuser = $rpcenv->get_user();
561 my $role = $rpcenv->get_role();
563 my $start = $param->{starttime
} // (time - 86400);
564 my $end = $param->{endtime
} // ($start + 86400);
567 my $dbh = PMG
::DBTools
::open_ruledb
();
571 if ($check_pmail || $role eq 'quser') {
572 $pmail = $verify_optional_pmail->($authuser, $role, $param->{pmail
});
573 $sth = $dbh->prepare(
574 "SELECT * FROM CMailStore, CMSReceivers WHERE pmail = ?"
575 ." AND time >= $start AND time < $end AND QType = '$quartype' AND CID = CMailStore_CID"
576 ." AND RID = CMailStore_RID AND Status = 'N' ORDER BY pmail, time, receiver"
579 $sth = $dbh->prepare(
580 "SELECT * FROM CMailStore, CMSReceivers WHERE time >= $start AND time < $end"
581 ." AND QType = '$quartype' AND CID = CMailStore_CID AND RID = CMailStore_RID"
582 ." AND Status = 'N' ORDER BY time, receiver"
586 if ($check_pmail || $role eq 'quser') {
587 $sth->execute(encode
('UTF-8', $pmail));
593 while (my $ref = $sth->fetchrow_hashref()) {
594 push @$res, $parse_header_info->($ref);
600 __PACKAGE__-
>register_method ({
604 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
605 description
=> "Get a list of quarantined spam mails in the given timeframe (default the last 24 hours) for the given user.",
607 additionalProperties
=> 0,
609 starttime
=> get_standard_option
('pmg-starttime'),
610 endtime
=> get_standard_option
('pmg-endtime'),
611 pmail
=> $pmail_param_type,
620 description
=> 'Unique ID',
624 description
=> "Size of raw email.",
628 description
=> "SMTP envelope sender.",
632 description
=> "Header 'From' field.",
636 description
=> "Header 'Sender' field.",
641 description
=> "Receiver email address",
645 description
=> "Header 'Subject' field.",
649 description
=> "Receive time stamp",
653 description
=> "Spam score.",
661 return $quarantine_api->($param, 'S', defined($param->{pmail
}));
664 __PACKAGE__-
>register_method ({
668 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit' ] },
669 description
=> "Get a list of quarantined virus mails in the given timeframe (default the last 24 hours).",
671 additionalProperties
=> 0,
673 starttime
=> get_standard_option
('pmg-starttime'),
674 endtime
=> get_standard_option
('pmg-endtime'),
675 pmail
=> $pmail_param_type,
684 description
=> 'Unique ID',
688 description
=> "Size of raw email.",
692 description
=> "SMTP envelope sender.",
696 description
=> "Header 'From' field.",
700 description
=> "Header 'Sender' field.",
705 description
=> "Receiver email address",
709 description
=> "Header 'Subject' field.",
713 description
=> "Receive time stamp",
717 description
=> "Virus name.",
725 return $quarantine_api->($param, 'V', defined($param->{pmail
}));
728 __PACKAGE__-
>register_method ({
729 name
=> 'attachment',
730 path
=> 'attachment',
732 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit' ] },
733 description
=> "Get a list of quarantined attachment mails in the given timeframe (default the last 24 hours).",
735 additionalProperties
=> 0,
737 starttime
=> get_standard_option
('pmg-starttime'),
738 endtime
=> get_standard_option
('pmg-endtime'),
739 pmail
=> $pmail_param_type,
748 description
=> 'Unique ID',
752 description
=> "Size of raw email.",
756 description
=> "SMTP envelope sender.",
760 description
=> "Header 'From' field.",
764 description
=> "Header 'Sender' field.",
769 description
=> "Receiver email address",
773 description
=> "Header 'Subject' field.",
777 description
=> "Receive time stamp",
785 return $quarantine_api->($param, 'A', defined($param->{pmail
}));
788 __PACKAGE__-
>register_method ({
789 name
=> 'virusstatus',
790 path
=> 'virusstatus',
792 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit'] },
793 description
=> "Get Virus Quarantine Status",
795 additionalProperties
=> 0,
802 description
=> 'Number of stored mails.',
806 description
=> "Estimated disk space usage in MByte.",
810 description
=> "Average size of stored mails in bytes.",
818 my $dbh = PMG
::DBTools
::open_ruledb
();
819 my $ref = PMG
::DBTools
::get_quarantine_count
($dbh, 'V');
821 delete $ref->{avgspam
};
826 my $get_and_check_mail = sub {
827 my ($id, $rpcenv, $dbh) = @_;
829 my ($cid, $rid, $tid) = $id =~ m/^C(\d+)R(\d+)T(\d+)$/;
830 ($cid, $rid, $tid) = (int($cid), int($rid), int($tid));
832 $dbh = PMG
::DBTools
::open_ruledb
() if !$dbh;
834 my $ref = PMG
::DBTools
::load_mail_data
($dbh, $cid, $rid, $tid);
836 my $authuser = $rpcenv->get_user();
837 my $role = $rpcenv->get_role();
839 if ($role eq 'quser') {
840 my $quar_username = $ref->{pmail
} . '@quarantine';
841 raise_perm_exc
("mail does not belong to user '$authuser' ($ref->{pmail})")
842 if $authuser ne $quar_username;
848 __PACKAGE__-
>register_method ({
852 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
853 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).",
855 additionalProperties
=> 0,
858 description
=> 'Unique ID',
860 pattern
=> 'C\d+R\d+T\d+',
864 description
=> "Display 'raw' eml data. Deactivates size limit.",
875 description
=> 'Unique ID',
879 description
=> "Size of raw email.",
883 description
=> "SMTP envelope sender.",
887 description
=> "Header 'From' field.",
891 description
=> "Header 'Sender' field.",
896 description
=> "Receiver email address",
900 description
=> "Header 'Subject' field.",
904 description
=> "Receive time stamp",
908 description
=> "Spam score.",
912 description
=> "Information about matched spam tests (name, score, desc, url).",
916 description
=> "Raw email header data.",
920 description
=> "Raw email data (first 4096 bytes). Useful for preview. NOTE: The 'htmlmail' formatter displays the whole email.",
928 my $rpcenv = PMG
::RESTEnvironment-
>get();
929 my $format = $rpcenv->get_format();
931 my $raw = $param->{raw
} // 0;
933 my $ref = $get_and_check_mail->($param->{id
}, $rpcenv);
935 my $res = $parse_header_info->($ref);
937 my $filename = $ref->{file
};
938 my $spooldir = $PMG::MailQueue
::spooldir
;
940 my $path = "$spooldir/$filename";
942 if ($format eq 'htmlmail') {
944 my $cfg = PMG
::Config-
>new();
945 my $viewimages = $cfg->get('spamquar', 'viewimages');
946 my $allowhref = $cfg->get('spamquar', 'allowhrefs');
948 $res->{content
} = PMG
::HTMLMail
::email_to_html
($path, $raw, $viewimages, $allowhref) // 'unable to parse mail';
950 # to make result verification happy
953 $res->{spamlevel
} = 0;
954 $res->{spaminfo
} = [];
956 # include additional details
958 # we want to get the whole email in raw mode
959 my $maxbytes = (!$raw)?
4096 : undef;
961 my ($header, $content) = PMG
::HTMLMail
::read_raw_email
($path, $maxbytes);
963 $res->{file
} = $ref->{file
};
964 $res->{spaminfo
} = decode_spaminfo
($ref->{info
});
965 $res->{header
} = $header;
966 $res->{content
} = $content;
973 my $get_attachments = sub {
974 my ($mailid, $dumpdir, $with_path) = @_;
976 my $rpcenv = PMG
::RESTEnvironment-
>get();
978 my $ref = $get_and_check_mail->($mailid, $rpcenv);
980 my $filename = $ref->{file
};
981 my $spooldir = $PMG::MailQueue
::spooldir
;
983 my $parser = PMG
::MIMEUtils
::new_mime_parser
({
986 extract_uuencode
=> 0,
990 my $entity = $parser->parse_open("$spooldir/$filename");
991 PMG
::MIMEUtils
::fixup_multipart
($entity);
992 PMG
::MailQueue
::decode_entities
($parser, 'attachmentquarantine', $entity);
997 PMG
::MIMEUtils
::traverse_mime_parts
($entity, sub {
999 my $name = PMG
::Utils
::extract_filename
($part->head) || "part-$id";
1000 my $attachment_path = $part->{PMX_decoded_path
};
1001 return if !$attachment_path || ! -f
$attachment_path;
1002 my $size = -s
$attachment_path // 0;
1007 'content-disposition' => $part->head->mime_attr('content-disposition'),
1008 'content-type' => $part->head->mime_attr('content-type'),
1010 $entry->{path
} = $attachment_path if $with_path;
1018 __PACKAGE__-
>register_method ({
1019 name
=> 'listattachments',
1020 path
=> 'listattachments',
1022 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
1023 description
=> "Get Attachments for E-Mail in Quarantine.",
1025 additionalProperties
=> 0,
1028 description
=> 'Unique ID',
1030 pattern
=> 'C\d+R\d+T\d+',
1041 description
=> 'Attachment ID',
1045 description
=> "Size of raw attachment in bytes.",
1049 description
=> "Raw email header data.",
1053 description
=> "Raw email header data.",
1062 my $dumpdir = "/run/pmgproxy/pmg-$param->{id}-$$";
1063 my $res = $get_attachments->($param->{id
}, $dumpdir);
1070 __PACKAGE__-
>register_method ({
1074 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit', 'quser'] },
1075 description
=> "Download E-Mail or Attachment from Quarantine.",
1078 additionalProperties
=> 0,
1081 description
=> 'Unique ID',
1083 pattern
=> 'C\d+R\d+T\d+',
1087 description
=> "The Attachment ID for the mail.",
1099 my $mailid = $param->{mailid
};
1100 my $attachmentid = $param->{attachmentid
};
1102 my $dumpdir = "/run/pmgproxy/pmg-$mailid-$$/";
1105 if ($attachmentid) {
1106 my $attachments = $get_attachments->($mailid, $dumpdir, 1);
1107 $res = $attachments->[$attachmentid];
1109 raise_param_exc
({ attachmentid
=> "Invalid Attachment ID for Mail."});
1112 my $rpcenv = PMG
::RESTEnvironment-
>get();
1113 my $ref = $get_and_check_mail->($mailid, $rpcenv);
1114 my $spooldir = $PMG::MailQueue
::spooldir
;
1117 'content-type' => 'message/rfc822',
1118 path
=> "$spooldir/$ref->{file}",
1122 $res->{fh
} = IO
::File-
>new($res->{path
}, '<') ||
1123 die "unable to open file '$res->{path}' - $!\n";
1125 rmtree
$dumpdir if -e
$dumpdir;
1131 PVE
::APIServer
::Formatter
::register_page_formatter
(
1132 'format' => 'htmlmail',
1134 path
=> '/quarantine/content',
1136 my ($res, $data, $param, $path, $auth, $config) = @_;
1138 if(!HTTP
::Status
::is_success
($res->{status
})) {
1139 return ("Error $res->{status}: $res->{message}", "text/plain");
1142 my $ct = "text/html;charset=UTF-8";
1144 my $raw = $data->{content
};
1146 return (encode
('UTF-8', $raw), $ct, 1);
1149 __PACKAGE__-
>register_method ({
1153 description
=> "Execute quarantine actions.",
1154 permissions
=> { check
=> [ 'admin', 'qmanager', 'quser'] },
1157 additionalProperties
=> 0,
1160 description
=> 'Unique IDs, separate with ;',
1162 pattern
=> 'C\d+R\d+T\d+(;C\d+R\d+T\d+)*',
1165 description
=> 'Action - specify what you want to do with the mail.',
1167 enum
=> ['whitelist', 'blacklist', 'deliver', 'delete'],
1171 returns
=> { type
=> "null" },
1175 my $rpcenv = PMG
::RESTEnvironment-
>get();
1176 my $action = $param->{action
};
1177 my @idlist = split(';', $param->{id
});
1179 my $dbh = PMG
::DBTools
::open_ruledb
();
1181 for my $id (@idlist) {
1183 my $ref = $get_and_check_mail->($id, $rpcenv, $dbh);
1184 my $sender = try_decode_utf8
($get_real_sender->($ref));
1185 my $pmail = try_decode_utf8
($ref->{pmail
});
1186 my $receiver = try_decode_utf8
($ref->{receiver
} // $ref->{pmail
});
1188 if ($action eq 'whitelist') {
1189 PMG
::Quarantine
::add_to_blackwhite
($dbh, $pmail, 'WL', [ $sender ]);
1190 PMG
::Quarantine
::deliver_quarantined_mail
($dbh, $ref, $receiver);
1191 } elsif ($action eq 'blacklist') {
1192 PMG
::Quarantine
::add_to_blackwhite
($dbh, $pmail, 'BL', [ $sender ]);
1193 PMG
::Quarantine
::delete_quarantined_mail
($dbh, $ref);
1194 } elsif ($action eq 'deliver') {
1195 PMG
::Quarantine
::deliver_quarantined_mail
($dbh, $ref, $receiver);
1196 } elsif ($action eq 'delete') {
1197 PMG
::Quarantine
::delete_quarantined_mail
($dbh, $ref);
1199 die "internal error, unknown action '$action'"; # should not be reached
1206 my $link_map_fn = "/run/pmgproxy/quarantinelink.map";
1207 my $per_user_limit = 60*60; # 1 hour
1209 my sub send_link_mail
{
1210 my ($cfg, $receiver) = @_;
1212 my $hostname = PVE
::INotify
::nodename
();
1213 my $fqdn = $cfg->get('spamquar', 'hostname') //
1214 PVE
::Tools
::get_fqdn
($hostname);
1216 my $port = $cfg->get('spamquar', 'port') // 8006;
1218 my $protocol = $cfg->get('spamquar', 'protocol') // 'https';
1220 my $protocol_fqdn_port = "$protocol://$fqdn";
1221 if (($protocol eq 'https' && $port != 443) ||
1222 ($protocol eq 'http' && $port != 80)) {
1223 $protocol_fqdn_port .= ":$port";
1226 my $mailfrom = $cfg->get ('spamquar', 'mailfrom') //
1227 "Proxmox Mail Gateway <postmaster>";
1229 my $ticket = PMG
::Ticket
::assemble_quarantine_ticket
($receiver);
1230 my $esc_ticket = uri_escape
($ticket);
1231 my $link = "$protocol_fqdn_port/quarantine?ticket=${esc_ticket}";
1233 my $text = "Here is your Link for the Spam Quarantine on $fqdn:\n\n$link\n";
1235 my $mail = MIME
::Entity-
>build(
1236 Type
=> "text/plain",
1239 Subject
=> "Proxmox Mail Gateway - Quarantine Link",
1243 # we use an empty envelope sender (we don't want to receive NDRs)
1244 PMG
::Utils
::reinject_local_mail
($mail, '', [$receiver], undef, $fqdn);
1247 __PACKAGE__-
>register_method ({
1251 description
=> "Send Quarantine link to given e-mail.",
1252 permissions
=> { user
=> 'world' },
1255 additionalProperties
=> 0,
1257 mail
=> get_standard_option
('pmg-email-address'),
1260 returns
=> { type
=> "null" },
1264 my $starttime = time();
1266 my $cfg = PMG
::Config-
>new();
1267 my $is_enabled = $cfg->get('spamquar', 'quarantinelink');
1269 die "This feature is not enabled\n";
1272 my $stat = File
::stat::stat($link_map_fn);
1274 if (defined($stat) && ($stat->mtime + 5) > $starttime) {
1276 die "Too many requests. Please try again later\n";
1279 my $domains = PVE
::INotify
::read_file
('domains');
1280 my $domainregex = PMG
::Utils
::domain_regex
([keys %$domains]);
1282 my $receiver = $param->{mail
};
1284 if ($receiver !~ $domainregex) {
1286 return undef; # silently ignore invalid mails
1289 PVE
::Tools
::lock_file_full
("${link_map_fn}.lck", 10, 1, sub {
1290 return if !-f
$link_map_fn;
1291 # check if user is allowed to request mail
1292 my $data = PVE
::Tools
::file_get_contents
($link_map_fn);
1293 for my $line (split("\n", $data)) {
1294 next if $line !~ m/^\Q$receiver\E (\d+)$/;
1295 if (($1 + $per_user_limit) > $starttime) {
1297 die "Too many requests for '$receiver', only one request per"
1298 ."hour is permitted. Please try again later\n";
1306 # we are allowed to send mail, lock and update file and send
1307 PVE
::Tools
::lock_file
("${link_map_fn}.lck", 10, sub {
1308 my $newdata = "$receiver $starttime\n";
1310 if (-f
$link_map_fn) {
1311 my $data = PVE
::Tools
::file_get_contents
($link_map_fn);
1312 for my $line (split("\n", $data)) {
1313 if ($line =~ m/^(?:.*) (\d+)$/) {
1314 if (($1 + $per_user_limit) > $starttime) {
1315 $newdata .= $line . "\n";
1320 PVE
::Tools
::file_set_contents
($link_map_fn, $newdata);
1324 send_link_mail
($cfg, $receiver);
1325 sleep(1); # always delay for a bit