From cacba346f691da7d9de41115a7f38b543b623c79 Mon Sep 17 00:00:00 2001 From: Marc van der Wal Date: Wed, 25 Oct 2023 15:50:23 +0200 Subject: [PATCH] API REST pour le destinataire de mails --- recipient/Dockerfile | 53 ++++++++- recipient/etc/s6-overlay/s6-rc.d/api/run | 2 + recipient/etc/s6-overlay/s6-rc.d/api/type | 1 + .../s6-overlay/s6-rc.d/user/contents.d/api | 0 recipient/web-api/.dancer | 0 recipient/web-api/MANIFEST | 24 ++++ recipient/web-api/MANIFEST.SKIP | 17 +++ recipient/web-api/Makefile.PL | 26 +++++ recipient/web-api/bin/app.psgi | 9 ++ recipient/web-api/config.yml | 4 + recipient/web-api/cpanfile | 11 ++ .../web-api/environments/development.yml | 14 +++ recipient/web-api/environments/production.yml | 15 +++ .../lib/Email/SpoofingDemo/API/Recipient.pm | 44 +++++++ .../lib/Email/SpoofingDemo/PostfixConfig.pm | 110 ++++++++++++++++++ recipient/web-api/public/dispatch.cgi | 16 +++ recipient/web-api/public/dispatch.fcgi | 18 +++ 17 files changed, 361 insertions(+), 3 deletions(-) create mode 100644 recipient/etc/s6-overlay/s6-rc.d/api/run create mode 100644 recipient/etc/s6-overlay/s6-rc.d/api/type create mode 100644 recipient/etc/s6-overlay/s6-rc.d/user/contents.d/api create mode 100644 recipient/web-api/.dancer create mode 100644 recipient/web-api/MANIFEST create mode 100644 recipient/web-api/MANIFEST.SKIP create mode 100644 recipient/web-api/Makefile.PL create mode 100644 recipient/web-api/bin/app.psgi create mode 100644 recipient/web-api/config.yml create mode 100644 recipient/web-api/cpanfile create mode 100644 recipient/web-api/environments/development.yml create mode 100644 recipient/web-api/environments/production.yml create mode 100644 recipient/web-api/lib/Email/SpoofingDemo/API/Recipient.pm create mode 100644 recipient/web-api/lib/Email/SpoofingDemo/PostfixConfig.pm create mode 100644 recipient/web-api/public/dispatch.cgi create mode 100644 recipient/web-api/public/dispatch.fcgi diff --git a/recipient/Dockerfile b/recipient/Dockerfile index 1b832c9..51119e9 100644 --- a/recipient/Dockerfile +++ b/recipient/Dockerfile @@ -51,6 +51,54 @@ RUN apk add \ s6-overlay \ vim +# Dependencies for REST API +RUN apk add \ + gcc \ + libc-dev \ + make \ + perl-app-cpanminus \ + perl-clone \ + perl-config-any \ + perl-data-optlist \ + perl-dev \ + perl-exporter-tiny \ + perl-extutils-config \ + perl-extutils-helpers \ + perl-extutils-installpaths \ + perl-file-sharedir \ + perl-file-sharedir-install \ + perl-file-slurp \ + perl-file-which \ + perl-hash-merge-simple \ + perl-hash-multivalue \ + perl-http-date \ + perl-http-headers-fast \ + perl-import-into \ + perl-json-maybexs \ + perl-module-build \ + perl-module-build-tiny \ + perl-module-implementation \ + perl-module-runtime \ + perl-moo \ + perl-params-util \ + perl-params-validate \ + perl-path-tiny \ + perl-plack \ + perl-readonly \ + perl-ref-util \ + perl-role-tiny \ + perl-safe-isa \ + perl-sub-exporter \ + perl-sub-install \ + perl-sub-quote \ + perl-template-toolkit \ + perl-type-tiny \ + perl-yaml + +RUN cpanm -n -v \ + Dancer2 \ + Module::Pluggable::Object + COPY --from=roundcube-build --chown=root:www-data \ /var/www/roundcubemail /var/www/roundcubemail RUN chown apache /var/www/roundcubemail/logs /var/www/roundcubemail/temp @@ -62,9 +110,6 @@ RUN newaliases # TODO faire en sorte que Dovecot logue dans syslog -# TODO finir de configurer postfix pour qu’il fasse les contrôles -# SPF/DKIM/DMARC si on le lui demande - # TODO rendre le mot de passe de destinataire@destinataire.example # configurable @@ -90,4 +135,6 @@ COPY etc/opendmarc/opendmarc.conf /etc/opendmarc/opendmarc.conf RUN doveadm pw -p "PasSecretDuTout" | \ awk '{ print "destinataire:" $1 }' > /etc/dovecot/users +COPY web-api /src/api + ENTRYPOINT ["/init"] diff --git a/recipient/etc/s6-overlay/s6-rc.d/api/run b/recipient/etc/s6-overlay/s6-rc.d/api/run new file mode 100644 index 0000000..2d9058d --- /dev/null +++ b/recipient/etc/s6-overlay/s6-rc.d/api/run @@ -0,0 +1,2 @@ +#!/bin/execlineb -P +/usr/bin/env perl /src/api/bin/app.psgi \ No newline at end of file diff --git a/recipient/etc/s6-overlay/s6-rc.d/api/type b/recipient/etc/s6-overlay/s6-rc.d/api/type new file mode 100644 index 0000000..1780f9f --- /dev/null +++ b/recipient/etc/s6-overlay/s6-rc.d/api/type @@ -0,0 +1 @@ +longrun \ No newline at end of file diff --git a/recipient/etc/s6-overlay/s6-rc.d/user/contents.d/api b/recipient/etc/s6-overlay/s6-rc.d/user/contents.d/api new file mode 100644 index 0000000..e69de29 diff --git a/recipient/web-api/.dancer b/recipient/web-api/.dancer new file mode 100644 index 0000000..e69de29 diff --git a/recipient/web-api/MANIFEST b/recipient/web-api/MANIFEST new file mode 100644 index 0000000..aada075 --- /dev/null +++ b/recipient/web-api/MANIFEST @@ -0,0 +1,24 @@ +MANIFEST +MANIFEST.SKIP +.dancer +Makefile.PL +config.yml +cpanfile +views/index.tt +views/layouts/main.tt +lib/Email/SpoofingDemo/API/DNS.pm +t/002_index_route.t +t/001_base.t +environments/production.yml +environments/development.yml +bin/app.psgi +public/500.html +public/dispatch.cgi +public/dispatch.fcgi +public/favicon.ico +public/404.html +public/javascripts/jquery.js +public/css/error.css +public/css/style.css +public/images/perldancer-bg.jpg +public/images/perldancer.jpg diff --git a/recipient/web-api/MANIFEST.SKIP b/recipient/web-api/MANIFEST.SKIP new file mode 100644 index 0000000..bbfb365 --- /dev/null +++ b/recipient/web-api/MANIFEST.SKIP @@ -0,0 +1,17 @@ +^\.git\/ +maint +^tags$ +.last_cover_stats +Makefile$ +^blib +^pm_to_blib +^.*.bak +^.*.old +^t.*sessions +^cover_db +^.*\.log +^.*\.swp$ +MYMETA.* +^.gitignore +^.svn\/ +^Email-SpoofingDemo-API-DNS- diff --git a/recipient/web-api/Makefile.PL b/recipient/web-api/Makefile.PL new file mode 100644 index 0000000..3f34704 --- /dev/null +++ b/recipient/web-api/Makefile.PL @@ -0,0 +1,26 @@ +use strict; +use warnings; +use ExtUtils::MakeMaker; + +# Normalize version strings like 6.30_02 to 6.3002, +# so that we can do numerical comparisons on it. +my $eumm_version = $ExtUtils::MakeMaker::VERSION; +$eumm_version =~ s/_//; + +WriteMakefile( + NAME => 'Email::SpoofingDemo::API::DNS', + AUTHOR => q{Marc van der Wal }, + VERSION_FROM => 'lib/Email/SpoofingDemo/API/Recipient.pm', + ABSTRACT => 'Email spoofing demo: REST API for recipient', + ($eumm_version >= 6.3001 + ? ('LICENSE'=> 'all-rights-reserved') + : ()), + PL_FILES => {}, + PREREQ_PM => { + 'Test::More' => 0, + 'YAML' => 0, + 'Dancer2' => 0.300000, + }, + dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, + clean => { FILES => 'Email-SpoofingDemo-API-DNS-*' }, +); diff --git a/recipient/web-api/bin/app.psgi b/recipient/web-api/bin/app.psgi new file mode 100644 index 0000000..774750f --- /dev/null +++ b/recipient/web-api/bin/app.psgi @@ -0,0 +1,9 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Email::SpoofingDemo::API::Recipient; +Email::SpoofingDemo::API::Recipient->to_app; diff --git a/recipient/web-api/config.yml b/recipient/web-api/config.yml new file mode 100644 index 0000000..bf8e980 --- /dev/null +++ b/recipient/web-api/config.yml @@ -0,0 +1,4 @@ + +appname: "Email::SpoofingDemo::API::Recipient" +charset: "UTF-8" +serializer: JSON diff --git a/recipient/web-api/cpanfile b/recipient/web-api/cpanfile new file mode 100644 index 0000000..28436b4 --- /dev/null +++ b/recipient/web-api/cpanfile @@ -0,0 +1,11 @@ +requires "Dancer2" => "0.300000"; + +recommends "YAML" => "0"; +recommends "URL::Encode::XS" => "0"; +recommends "CGI::Deurl::XS" => "0"; +recommends "HTTP::Parser::XS" => "0"; + +on "test" => sub { + requires "Test::More" => "0"; + requires "HTTP::Request::Common" => "0"; +}; diff --git a/recipient/web-api/environments/development.yml b/recipient/web-api/environments/development.yml new file mode 100644 index 0000000..0887b4b --- /dev/null +++ b/recipient/web-api/environments/development.yml @@ -0,0 +1,14 @@ + +logger: "console" +log: "core" + +# should Dancer2 consider warnings as critical errors? +warnings: 1 + +# should Dancer2 show a stacktrace when an 5xx error is caught? +# if set to yes, public/500.html will be ignored and either +# views/500.tt, 'error_template' template, or a default error template will be used. +show_errors: 1 + +# print the banner +startup_info: 1 diff --git a/recipient/web-api/environments/production.yml b/recipient/web-api/environments/production.yml new file mode 100644 index 0000000..648cce2 --- /dev/null +++ b/recipient/web-api/environments/production.yml @@ -0,0 +1,15 @@ + +# only log warning and error messsages +log: "warning" + +# log message to a file in logs/ +logger: "file" + +# don't consider warnings critical +warnings: 0 + +# hide errors +show_errors: 0 + +# disable server tokens in production environments +no_server_tokens: 1 diff --git a/recipient/web-api/lib/Email/SpoofingDemo/API/Recipient.pm b/recipient/web-api/lib/Email/SpoofingDemo/API/Recipient.pm new file mode 100644 index 0000000..8e01158 --- /dev/null +++ b/recipient/web-api/lib/Email/SpoofingDemo/API/Recipient.pm @@ -0,0 +1,44 @@ +package Email::SpoofingDemo::API::Recipient; + +use Dancer2; + +use Email::SpoofingDemo::PostfixConfig qw(spf_dkim_dmarc_status + set_spf_dkim_dmarc_status); + +our $VERSION = '0.1'; + +get '/' => sub { return "Welcome"; }; + +get '/status' => sub { + my $status = spf_dkim_dmarc_status(); + my %result = map { + $_ => ($status->{$_} ? 'enabled' : 'disabled') + } (keys %$status); + return \%result; +}; + +put '/status' => sub { + my $spf_enabled = body_parameters->get('spf'); + my $dkim_enabled = body_parameters->get('dkim'); + my $dmarc_enabled = body_parameters->get('dmarc'); + + unless (defined $spf_enabled + and defined $dkim_enabled + and defined $dmarc_enabled) { + status 400; + return; + } + + set_spf_dkim_dmarc_status( + $spf_enabled eq 'enabled', + $dkim_enabled eq 'enabled', + $dmarc_enabled eq 'enabled' + ); + return "OK"; +}; + +any qr{.*} => sub { status 'not_found'; return "Invalid route" }; + +dance; + +true; diff --git a/recipient/web-api/lib/Email/SpoofingDemo/PostfixConfig.pm b/recipient/web-api/lib/Email/SpoofingDemo/PostfixConfig.pm new file mode 100644 index 0000000..56bc9f2 --- /dev/null +++ b/recipient/web-api/lib/Email/SpoofingDemo/PostfixConfig.pm @@ -0,0 +1,110 @@ +package Email::SpoofingDemo::PostfixConfig; + +use strict; +use warnings; +use v5.10; +use utf8; + +use Exporter 'import'; + +our @EXPORT_OK = qw(spf_dkim_dmarc_status + set_spf_dkim_dmarc_status); + +my $CHECK_SPF_POLICY = 'check_policy_service unix:private/policy'; +my $DKIM_MILTER = 'inet:127.0.0.1:8891'; +my $DMARC_MILTER = 'inet:127.0.0.1:8893'; + +my $POSTCONF = '/usr/sbin/postconf'; + +sub safe_system { + system @_; + my $exit_status = ($? >> 8); + die "$_[0] exited with status $exit_status" unless $exit_status == 0; + return; +} + +sub reload_postfix { + safe_system(qw(postfix reload)); +} + +sub postconf_read { + my ($variable) = @_; + + my $output = ''; + + open (my $fh, '-|', $POSTCONF, '-h', $variable) or die "postconf: $!"; + while (<$fh>) { + chomp; + $output .= $_; + } + close($fh); + + my $exit_status = ($? >> 8); + die "postconf failed" unless $exit_status == 0; + + if (wantarray) { + return split(/,\s*/, $output); + } + else { + return $output; + } +} + +sub postconf_set { + die "Need an even number of parameters" if scalar(@_) % 2 != 0; + + my @vars_to_set; + + while (@_) { + my $parameter = shift; + my $value = shift; + push(@vars_to_set, "$parameter=$value"); + } + + my ($parameter, $value) = @_; + + safe_system($POSTCONF, '-e', @vars_to_set); +} + +sub smtpd_recipient_restrictions { + my ($enabled) = @_; + + return ($enabled) ? $CHECK_SPF_POLICY : ''; +} + +sub spf_dkim_dmarc_status { + my @smtpd_recipient_restrictions = postconf_read('smtpd_recipient_restrictions'); + my @smtpd_milters = postconf_read('smtpd_milters'); + + return { + spf => scalar(grep { $_ eq $CHECK_SPF_POLICY } @smtpd_recipient_restrictions), + dkim => scalar(grep { $_ eq $DKIM_MILTER } @smtpd_milters), + dmarc => scalar(grep { $_ eq $DMARC_MILTER } @smtpd_milters) + }; +} + +sub smtpd_milters { + my ($dkim_enabled, $dmarc_enabled) = @_; + + my @milters; + push @milters, $DKIM_MILTER if $dkim_enabled; + push @milters, $DMARC_MILTER if $dmarc_enabled; + + return join(', ', @milters); +} + +sub set_spf_dkim_dmarc_status { + my ($spf_enabled, $dkim_enabled, $dmarc_enabled) = @_; + + say STDERR "Setting configuration"; + postconf_set( + smtpd_recipient_restrictions => smtpd_recipient_restrictions($spf_enabled), + smtpd_milters => smtpd_milters($dkim_enabled, $dmarc_enabled) + ); + + say STDERR "Reloading Postfix"; + reload_postfix(); + say STDERR "Done"; +} + +1; diff --git a/recipient/web-api/public/dispatch.cgi b/recipient/web-api/public/dispatch.cgi new file mode 100644 index 0000000..706ba0c --- /dev/null +++ b/recipient/web-api/public/dispatch.cgi @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +BEGIN { $ENV{DANCER_APPHANDLER} = 'PSGI';} +use Dancer2; +use FindBin '$RealBin'; +use Plack::Runner; + +# For some reason Apache SetEnv directives don't propagate +# correctly to the dispatchers, so forcing PSGI and env here +# is safer. +set apphandler => 'PSGI'; +set environment => 'production'; + +my $psgi = path($RealBin, '..', 'bin', 'app.psgi'); +die "Unable to read startup script: $psgi" unless -r $psgi; + +Plack::Runner->run($psgi); diff --git a/recipient/web-api/public/dispatch.fcgi b/recipient/web-api/public/dispatch.fcgi new file mode 100644 index 0000000..ad42deb --- /dev/null +++ b/recipient/web-api/public/dispatch.fcgi @@ -0,0 +1,18 @@ +#!/usr/bin/env perl +BEGIN { $ENV{DANCER_APPHANDLER} = 'PSGI';} +use Dancer2; +use FindBin '$RealBin'; +use Plack::Handler::FCGI; + +# For some reason Apache SetEnv directives don't propagate +# correctly to the dispatchers, so forcing PSGI and env here +# is safer. +set apphandler => 'PSGI'; +set environment => 'production'; + +my $psgi = path($RealBin, '..', 'bin', 'app.psgi'); +my $app = do($psgi); +die "Unable to read startup script: $@" if $@; +my $server = Plack::Handler::FCGI->new(nproc => 5, detach => 1); + +$server->run($app);