API REST pour l’émetteur de mails
On peut générer des clefs DKIM mais on ne peut pas encore choisir quel sélecteur utiliser pour signer les mails sortants. Une fois DKIM activé pour un domaine, on ne peut pas non plus le désactiver.
This commit is contained in:
parent
31f08bb329
commit
30cf2e5a9f
@ -14,6 +14,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
|
||||
|
||||
RUN newaliases
|
||||
|
||||
RUN install -m 0700 -o opendkim -g opendkim -d /run/opendkim
|
||||
@ -23,6 +71,8 @@ COPY etc/s6-overlay /etc/s6-overlay
|
||||
COPY etc/postfix /etc/postfix
|
||||
COPY etc/opendkim /etc/opendkim
|
||||
|
||||
COPY web-api /src/api
|
||||
|
||||
ENTRYPOINT ["/init"]
|
||||
|
||||
# Ne pas positionner USER, ou sinon les services ne démarreront pas de manière
|
||||
|
@ -17,6 +17,6 @@ SendReports yes
|
||||
## Il vaut donc mieux paramétrer une SigningTable (qui liste les expéditeurs
|
||||
## pour lesquels on signe) et une KeyTable (qui liste les emplacements des
|
||||
## clefs privées).
|
||||
#
|
||||
# SigningTable file:/etc/opendkim/signing_table
|
||||
# KeyTable file:/etc/opendkim/key_table
|
||||
|
||||
SigningTable file:/etc/opendkim/signing_table
|
||||
KeyTable file:/etc/opendkim/key_table
|
||||
|
2
sender/etc/s6-overlay/s6-rc.d/api/run
Normal file
2
sender/etc/s6-overlay/s6-rc.d/api/run
Normal file
@ -0,0 +1,2 @@
|
||||
#!/bin/execlineb -P
|
||||
/usr/bin/env perl /src/api/bin/app.psgi
|
1
sender/etc/s6-overlay/s6-rc.d/api/type
Normal file
1
sender/etc/s6-overlay/s6-rc.d/api/type
Normal file
@ -0,0 +1 @@
|
||||
longrun
|
0
sender/etc/s6-overlay/s6-rc.d/user/contents.d/api
Normal file
0
sender/etc/s6-overlay/s6-rc.d/user/contents.d/api
Normal file
0
sender/web-api/.dancer
Normal file
0
sender/web-api/.dancer
Normal file
24
sender/web-api/MANIFEST
Normal file
24
sender/web-api/MANIFEST
Normal file
@ -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
|
17
sender/web-api/MANIFEST.SKIP
Normal file
17
sender/web-api/MANIFEST.SKIP
Normal file
@ -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-
|
26
sender/web-api/Makefile.PL
Normal file
26
sender/web-api/Makefile.PL
Normal file
@ -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 <marc.vanderwal@afnic.fr>},
|
||||
VERSION_FROM => 'lib/Email/SpoofingDemo/API/Sender.pm',
|
||||
ABSTRACT => 'Email spoofing demo: REST API for Sender',
|
||||
($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-Sender-*' },
|
||||
);
|
9
sender/web-api/bin/app.psgi
Normal file
9
sender/web-api/bin/app.psgi
Normal file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
|
||||
use Email::SpoofingDemo::API::Sender;
|
||||
Email::SpoofingDemo::API::Sender->to_app;
|
4
sender/web-api/config.yml
Normal file
4
sender/web-api/config.yml
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
appname: "Email::SpoofingDemo::API::Sender"
|
||||
charset: "UTF-8"
|
||||
serializer: JSON
|
11
sender/web-api/cpanfile
Normal file
11
sender/web-api/cpanfile
Normal file
@ -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";
|
||||
};
|
14
sender/web-api/environments/development.yml
Normal file
14
sender/web-api/environments/development.yml
Normal file
@ -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
|
16
sender/web-api/environments/production.yml
Normal file
16
sender/web-api/environments/production.yml
Normal file
@ -0,0 +1,16 @@
|
||||
# configuration file for production environment
|
||||
|
||||
# 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
|
74
sender/web-api/lib/Email/SpoofingDemo/API/Sender.pm
Normal file
74
sender/web-api/lib/Email/SpoofingDemo/API/Sender.pm
Normal file
@ -0,0 +1,74 @@
|
||||
package Email::SpoofingDemo::API::Sender;
|
||||
|
||||
use Dancer2;
|
||||
|
||||
use Email::SpoofingDemo::DKIM qw(read_signing_table read_key_table
|
||||
write_signing_table write_key_table
|
||||
generate_dkim_key);
|
||||
|
||||
our $VERSION = '0.1';
|
||||
|
||||
my $signing_table = "/etc/opendkim/signing_table";
|
||||
my $key_table = "/etc/opendkim/key_table";
|
||||
my $key_dir = "/etc/opendkim/keys";
|
||||
|
||||
get '/' => sub { return "Welcome"; };
|
||||
|
||||
get '/installed-keys' => sub {
|
||||
my $signing_table = read_signing_table($signing_table);
|
||||
my $key_table = read_key_table($key_table);
|
||||
|
||||
my @result;
|
||||
|
||||
for my $domain (sort keys %$key_table) {
|
||||
push @result, {
|
||||
domain => $domain,
|
||||
available_keys => $key_table->{$domain},
|
||||
current_key => $signing_table->{$domain}
|
||||
};
|
||||
}
|
||||
|
||||
return \@result;
|
||||
};
|
||||
|
||||
post '/generate-dkim-key' => sub {
|
||||
my $domain = body_parameters->get('domain');
|
||||
my $selector = body_parameters->get('selector');
|
||||
my $key_size = body_parameters->get('key_size');
|
||||
|
||||
# Generate key
|
||||
my $txt_data = generate_dkim_key($domain, $selector, $key_size,
|
||||
$key_table, $key_dir, $signing_table);
|
||||
|
||||
my $txt_record = sprintf("%-30s. TXT %s",
|
||||
qq{$selector._domainkey.$domain},
|
||||
$txt_data);
|
||||
|
||||
return {
|
||||
txt_record => $txt_record
|
||||
};
|
||||
};
|
||||
|
||||
post '/send-email/confirmation_email' => sub {
|
||||
system("/home/expediteur/scripts/send_confirmation_email.sh");
|
||||
my $status = ($? >> 8);
|
||||
if ($status != 0) {
|
||||
status(500);
|
||||
return "E-mail script exited with status $status";
|
||||
}
|
||||
};
|
||||
|
||||
post '/send-email/newsletter' => sub {
|
||||
system("/home/expediteur/scripts/send_newsletter.sh");
|
||||
my $status = ($? >> 8);
|
||||
if ($status != 0) {
|
||||
status(500);
|
||||
return "E-mail script exited with status $status";
|
||||
}
|
||||
};
|
||||
|
||||
any qr{.*} => sub { status 'not_found'; return "Invalid route" };
|
||||
|
||||
dance;
|
||||
|
||||
true;
|
157
sender/web-api/lib/Email/SpoofingDemo/DKIM.pm
Normal file
157
sender/web-api/lib/Email/SpoofingDemo/DKIM.pm
Normal file
@ -0,0 +1,157 @@
|
||||
package Email::SpoofingDemo::DKIM;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use v5.10;
|
||||
use utf8;
|
||||
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT_OK = qw(read_signing_table read_key_table
|
||||
write_signing_table write_key_table
|
||||
generate_dkim_key);
|
||||
|
||||
sub generate_dkim_key {
|
||||
my ($domain, $selector, $key_size, $key_table_name, $key_dir, $signing_table_name) = @_;
|
||||
|
||||
die if $domain =~ /\.\./;
|
||||
|
||||
my $key_domain_dir = "$key_dir/$domain";
|
||||
|
||||
# Generate the key
|
||||
system("mkdir", "-p", $key_domain_dir);
|
||||
system("opendkim-genkey",
|
||||
"-D", $key_domain_dir,
|
||||
"-d", $domain,
|
||||
"-s", $selector,
|
||||
"-b", $key_size);
|
||||
system("chown", "-R", "opendkim", $key_domain_dir);
|
||||
|
||||
# Read in the public key
|
||||
my $public_key_file = "$key_domain_dir/$selector.txt";
|
||||
open(my $fh, '<', $public_key_file) or die "$key_domain_dir: $!";
|
||||
my $data = eval {
|
||||
local $/ = undef;
|
||||
my $raw_record = <$fh>;
|
||||
my ($owner, $class, $type, $data) = split(" ", $raw_record, 4);
|
||||
$data =~ s/\s*;.*$//;
|
||||
return $data;
|
||||
};
|
||||
close($fh);
|
||||
|
||||
# Update key table
|
||||
my $key_table = read_key_table($key_table_name);
|
||||
push @{$key_table->{$domain}}, $selector;
|
||||
write_key_table($key_table_name, $key_dir, $key_table);
|
||||
|
||||
# Update signing table if it’s the first key for the domain
|
||||
my $signing_table = read_signing_table($signing_table_name);
|
||||
if (not exists $signing_table->{$domain}) {
|
||||
$signing_table->{$domain} = $selector;
|
||||
write_signing_table($signing_table_name, $signing_table);
|
||||
}
|
||||
|
||||
# Done!
|
||||
reload_opendkim();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
sub read_signing_table {
|
||||
my ($filename) = @_;
|
||||
|
||||
my %sign_table;
|
||||
|
||||
open(my $fh, '<', $filename) or die "$filename: $!";
|
||||
while (<$fh>) {
|
||||
chomp;
|
||||
s/#.*$//;
|
||||
next if /^\s*$/;
|
||||
|
||||
my ($domain_or_email, $key_id) = split(" ", $_, 2);
|
||||
my $domain = ($domain_or_email =~ s/^.*@//r);
|
||||
my $selector = ($key_id =~ s/\._domainkey.$domain$//r);
|
||||
|
||||
$sign_table{$domain} = $selector;
|
||||
}
|
||||
close($fh);
|
||||
|
||||
return \%sign_table;
|
||||
}
|
||||
|
||||
sub write_signing_table {
|
||||
my ($filename, $contents) = @_;
|
||||
|
||||
open(my $fh, '>', $filename) or die "$filename: $!";
|
||||
binmode($fh, ':utf8');
|
||||
print $fh <<'EOF';
|
||||
##
|
||||
## FORMAT DE LA TABLE
|
||||
##
|
||||
## <domaine ou adresse mail> <identifiant>
|
||||
##
|
||||
## L’adresse mail peut être un wildcard (ex. *@expediteur.example).
|
||||
##
|
||||
|
||||
EOF
|
||||
for my $domain (sort keys %$contents) {
|
||||
my $selector = $contents->{$domain};
|
||||
my $key_id = "$selector._domainkey.$domain";
|
||||
printf $fh "%-30s %s\n", $domain, $key_id;
|
||||
}
|
||||
close($fh);
|
||||
}
|
||||
|
||||
sub read_key_table {
|
||||
my ($filename) = @_;
|
||||
|
||||
# We only care about the list of keys that exist for a given domain.
|
||||
# The rest of the data can be deduced from that mapping.
|
||||
|
||||
my %key_table;
|
||||
|
||||
open(my $fh, '<', $filename) or die "$filename: $!\n";
|
||||
while (<$fh>) {
|
||||
chomp;
|
||||
s/#.*$//;
|
||||
next if /^\s*$/;
|
||||
|
||||
my ($key_id, $key_spec) = split(" ", $_, 2);
|
||||
my ($domain, $selector, $key_location) = split(":", $key_spec, 3);
|
||||
|
||||
push @{$key_table{$domain}}, $selector;
|
||||
}
|
||||
|
||||
return \%key_table;
|
||||
}
|
||||
|
||||
sub write_key_table {
|
||||
my ($filename, $key_dir, $contents) = @_;
|
||||
|
||||
open(my $fh, '>', $filename) or die "$filename: $!\n";
|
||||
binmode($fh, ':utf8');
|
||||
print $fh <<EOF;
|
||||
##
|
||||
## FORMAT DE LA TABLE
|
||||
##
|
||||
## <identifiant> <domaine>:<sélecteur>:<fichier>
|
||||
##
|
||||
|
||||
EOF
|
||||
for my $domain (sort keys %$contents) {
|
||||
for my $selector (@{$contents->{$domain}}) {
|
||||
my $key_id = "$selector._domainkey.$domain";
|
||||
my $key_file = "$key_dir/$domain/$selector.private";
|
||||
|
||||
printf $fh "%-30s %s:%s:%s\n", $key_id, $domain, $selector, $key_file;
|
||||
}
|
||||
}
|
||||
close($fh);
|
||||
}
|
||||
|
||||
sub reload_opendkim {
|
||||
system(qw(killall -USR1 opendkim));
|
||||
return (($? >> 8) == 0);
|
||||
}
|
||||
|
||||
1;
|
16
sender/web-api/public/dispatch.cgi
Normal file
16
sender/web-api/public/dispatch.cgi
Normal file
@ -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);
|
18
sender/web-api/public/dispatch.fcgi
Normal file
18
sender/web-api/public/dispatch.fcgi
Normal file
@ -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);
|
Loading…
x
Reference in New Issue
Block a user