From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 1D091606BC for ; Tue, 17 Nov 2020 15:58:17 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 144AB12AC6 for ; Tue, 17 Nov 2020 15:57:47 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 294CF12AAF for ; Tue, 17 Nov 2020 15:57:45 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id E7AC64373D for ; Tue, 17 Nov 2020 15:57:44 +0100 (CET) From: Dominik Csapak To: pmg-devel@lists.proxmox.com Date: Tue, 17 Nov 2020 15:57:42 +0100 Message-Id: <20201117145743.10561-4-d.csapak@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201117145743.10561-1-d.csapak@proxmox.com> References: <20201117145743.10561-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.346 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [httpserver.pm, quarantine.pm] Subject: [pmg-devel] [PATCH pmg-api v2 3/3] api2/quarantine: add global sendlink api call X-BeenThere: pmg-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Mail Gateway development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 17 Nov 2020 14:58:17 -0000 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 --- 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..cfedc84 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('spamquar', '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 "; + + 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