public inbox for pmg-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: pmg-devel@lists.proxmox.com
Subject: [pmg-devel] [PATCH pmg-api 3/3] api2/quarantine: add global sendlink api call
Date: Tue, 17 Nov 2020 09:05:12 +0100	[thread overview]
Message-ID: <20201117080513.15046-4-d.csapak@proxmox.com> (raw)
In-Reply-To: <20201117080513.15046-1-d.csapak@proxmox.com>

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@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





  parent reply	other threads:[~2020-11-17  8:05 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-11-17  8:05 [pmg-devel] [PATCH pmg-api/gui] add quarantine self service button Dominik Csapak
2020-11-17  8:05 ` [pmg-devel] [PATCH pmg-api 1/3] refactor domain_regex to Utils Dominik Csapak
2020-11-17 13:18   ` Stoiko Ivanov
2020-11-17  8:05 ` [pmg-devel] [PATCH pmg-api 2/3] add 'quarantinelink' to admin config Dominik Csapak
2020-11-17 13:20   ` Stoiko Ivanov
2020-11-17  8:05 ` Dominik Csapak [this message]
2020-11-17  8:05 ` [pmg-devel] [PATCH pmg-gui 1/1] add 'Request Quarantine Link' Button to LoginView Dominik Csapak
2020-11-17 13:27   ` Stoiko Ivanov
2020-11-17 13:16 ` [pmg-devel] [PATCH pmg-api/gui] add quarantine self service button Stoiko Ivanov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201117080513.15046-4-d.csapak@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=pmg-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal