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
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/execlineb -P
|
||||
/usr/bin/env perl /src/api/bin/app.psgi
|
|
@ -0,0 +1 @@
|
|||
longrun
|
|
@ -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
|
|
@ -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-
|
|
@ -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-*' },
|
||||
);
|
|
@ -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;
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
appname: "Email::SpoofingDemo::API::Sender"
|
||||
charset: "UTF-8"
|
||||
serializer: JSON
|
|
@ -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";
|
||||
};
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
|
@ -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…
Reference in New Issue