[pmg-devel] [PATCH pmg-api 3/3] api2/quarantine: add global sendlink api call

Dominik Csapak d.csapak at proxmox.com
Tue Nov 17 09:05:12 CET 2020


this api call takes an email, checks it against the relay domains,
and prepares a custom quarantinelink for that email  and sends it there

this has to happen unauthenticated, since the idea is that the user
want to access the quarantine but has no current ticket (and no
old spam report with a ticket)

to prevent abuse, we let the api call take 3 seconds, even if
it would fail due to an invalid e-mail address, so that an
potential attacker cannot probe for e-mails/relay domains.

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 src/PMG/API2/Quarantine.pm | 87 ++++++++++++++++++++++++++++++++++++++
 src/PMG/HTTPServer.pm      |  1 +
 2 files changed, 88 insertions(+)

diff --git a/src/PMG/API2/Quarantine.pm b/src/PMG/API2/Quarantine.pm
index 73fb0ec..2b18b0c 100644
--- a/src/PMG/API2/Quarantine.pm
+++ b/src/PMG/API2/Quarantine.pm
@@ -8,6 +8,9 @@ use Data::Dumper;
 use Encode;
 use File::Path;
 use IO::File;
+use MIME::Entity;
+use URI::Escape;
+use Time::HiRes qw(usleep gettimeofday tv_interval);
 
 use Mail::Header;
 use Mail::SpamAssassin;
@@ -195,6 +198,7 @@ __PACKAGE__->register_method ({
 	    { name => 'attachment' },
 	    { name => 'listattachments' },
 	    { name => 'download' },
+	    { name => 'sendlink' },
 	];
 
 	return $result;
@@ -1239,4 +1243,87 @@ __PACKAGE__->register_method ({
 	return undef;
     }});
 
+__PACKAGE__->register_method ({
+    name =>'sendlink',
+    path => 'sendlink',
+    method => 'POST',
+    description => "Send Quarantine link to given e-mail.",
+    permissions => { user => 'world' },
+    protected => 1,
+    parameters => {
+	additionalProperties => 0,
+	properties => {
+	    mail => get_standard_option('pmg-email-address'),
+	},
+    },
+    returns => { type => "null" },
+    code => sub {
+	my ($param) = @_;
+
+	my $cfg = PMG::Config->new();
+
+	my $hostname = PVE::INotify::nodename();
+
+	my $is_enabled = $cfg->get('admin', 'quarantinelink');
+	if (!$is_enabled) {
+	    die "This feature is not enabled\n";
+	}
+
+	my $fqdn = $cfg->get('spamquar', 'hostname') //
+	    PVE::Tools::get_fqdn($hostname);
+
+	my $port = $cfg->get('spamquar', 'port') // 8006;
+
+	my $protocol = $cfg->get('spamquar', 'protocol') // 'https';
+
+	my $protocol_fqdn_port = "$protocol://$fqdn";
+	if (($protocol eq 'https' && $port != 443) ||
+	    ($protocol eq 'http' && $port != 80)) {
+	    $protocol_fqdn_port .= ":$port";
+	}
+
+	my $mailfrom = $cfg->get ('spamquar', 'mailfrom') //
+	    "Proxmox Mail Gateway <postmaster>";
+
+	my $domains = PVE::INotify::read_file('domains');
+	my $domainregex = PMG::Utils::domain_regex([keys %$domains]);
+
+	my $receiver = $param->{mail};
+
+	my $starttime = [gettimeofday];
+	my $delay = 0;
+
+	if ($receiver !~ $domainregex) {
+	    # make all calls to this take the same duration
+	    $delay = 3 - tv_interval($starttime);
+	    $delay = 0 if $delay < 0;
+	    sleep $delay;
+	    return undef; # silently ignore invalid mails
+	}
+
+	my $ticket = PMG::Ticket::assemble_quarantine_ticket($receiver);
+	my $esc_ticket = uri_escape($ticket);
+	my $link = "$protocol_fqdn_port/quarantine?ticket=${esc_ticket}";
+
+	my $text = "Here is your Link for the Spam Quarantine on $fqdn:\n\n$link\n";
+
+	my $top = MIME::Entity->build(
+	    Type    => "text/plain",
+	    To      => $receiver,
+	    From    => $mailfrom,
+	    Subject => "Proxmox Mail Gateway - Quarantine Link",
+	    Data    => $text,
+	);
+
+	# we use an empty envelope sender (we dont want to receive NDRs)
+	PMG::Utils::reinject_mail ($top, '', [$receiver], undef, $fqdn);
+
+	# make all calls to this take the same duration
+	$delay = 3 - tv_interval($starttime);
+	$delay = 0 if $delay < 0;
+	sleep $delay;
+
+	return undef;
+    }});
+
 1;
diff --git a/src/PMG/HTTPServer.pm b/src/PMG/HTTPServer.pm
index eb48b5f..3dc9655 100755
--- a/src/PMG/HTTPServer.pm
+++ b/src/PMG/HTTPServer.pm
@@ -58,6 +58,7 @@ sub auth_handler {
 
     # explicitly allow some calls without auth
     if (($rel_uri eq '/access/domains' && $method eq 'GET') ||
+	($rel_uri eq '/quarantine/sendlink' && ($method eq 'GET' || $method eq 'POST')) ||
 	($rel_uri eq '/access/ticket' && ($method eq 'GET' || $method eq 'POST'))) {
 	$require_auth = 0;
     }
-- 
2.20.1





More information about the pmg-devel mailing list