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 9200360482 for ; Tue, 17 Nov 2020 09:05:16 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 90682CFEB for ; Tue, 17 Nov 2020 09:05:16 +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 1A9C0CFD0 for ; Tue, 17 Nov 2020 09:05:15 +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 D87CF43756 for ; Tue, 17 Nov 2020 09:05:14 +0100 (CET) From: Dominik Csapak To: pmg-devel@lists.proxmox.com Date: Tue, 17 Nov 2020 09:05:12 +0100 Message-Id: <20201117080513.15046-4-d.csapak@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201117080513.15046-1-d.csapak@proxmox.com> References: <20201117080513.15046-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.353 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 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 08:05:16 -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..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 "; + + 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