244 lines
6.9 KiB
Perl
244 lines
6.9 KiB
Perl
#
|
||
# SPDX-FileCopyrightText: 2023 Afnic
|
||
#
|
||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||
#
|
||
|
||
package Email::SpoofingDemo::Web;
|
||
use Dancer2;
|
||
use Dancer2::Plugin::Deferred;
|
||
|
||
use JSON;
|
||
use REST::Client;
|
||
|
||
our $VERSION = '0.1';
|
||
|
||
sub call_api {
|
||
my ($method, $target, $url, $body_parameters) = @_;
|
||
|
||
my $host = config->{'api'}{$target};
|
||
die "Invalid target: $target" unless defined $host;
|
||
|
||
my $client = REST::Client->new();
|
||
$client->setHost($host);
|
||
$client->setTimeout(5);
|
||
$client->addHeader('Accept' => 'application/json');
|
||
$client->addHeader('Content-Type' => 'application/json');
|
||
|
||
my $body_data;
|
||
$body_data = encode_json($body_parameters) if defined $body_parameters;
|
||
|
||
$client->request($method, $url, $body_data);
|
||
|
||
my $status = $client->responseCode();
|
||
if ($status =~ /^2\d\d$/) {
|
||
my $response;
|
||
if ($client->responseContent() ne '') {
|
||
$response = decode_json($client->responseContent());
|
||
}
|
||
return ($response, $status);
|
||
}
|
||
else {
|
||
warn "API request returned $status";
|
||
return ($client->responseContent(), $status);
|
||
}
|
||
}
|
||
|
||
get '/' => sub {
|
||
template 'index' => {
|
||
'title' => 'Démonstration de SPF, DKIM et DMARC',
|
||
'no_nav' => 1
|
||
};
|
||
};
|
||
|
||
get '/dashboard' => sub {
|
||
my $sender_domain = 'expediteur.example';
|
||
my ($sender_spf, undef) = call_api(GET => 'dns', "/zone/$sender_domain/spf");
|
||
my ($sender_dkim, undef) = call_api(GET => 'dns', "/zone/$sender_domain/domainkey");
|
||
my ($sender_dmarc, undef) = call_api(GET => 'dns', "/zone/$sender_domain/dmarc");
|
||
my ($recipient_status, undef) = call_api(GET => 'recipient', '/status');
|
||
|
||
template 'dashboard' => {
|
||
'title' => 'Tableau de bord',
|
||
'sender' => {
|
||
'spf_policies' => $sender_spf,
|
||
'dkim_domains' => [keys(%$sender_dkim)],
|
||
'dmarc_policies' => $sender_dmarc
|
||
},
|
||
'recipient' => $recipient_status
|
||
};
|
||
};
|
||
|
||
get '/dns/zone-edit/:zone' => sub {
|
||
my $zone = route_parameters->get('zone');
|
||
if (not (grep { $_ eq $zone } @{config->{'editable_zones'}})) {
|
||
pass;
|
||
}
|
||
|
||
my $zone_contents = deferred 'zone_contents';
|
||
|
||
if (not defined $zone_contents) {
|
||
my ($response, $status) = call_api(GET => 'dns', "/zone/${zone}/file");
|
||
$zone_contents = $response->{contents};
|
||
}
|
||
|
||
template 'dns/zone-edit' => {
|
||
'title' => 'Éditeur de zone DNS',
|
||
'zone_to_edit' => $zone // '',
|
||
'zone_contents' => $zone_contents // '',
|
||
};
|
||
};
|
||
|
||
post '/dns/zone-edit/:zone' => sub {
|
||
my $zone = route_parameters->{'zone'};
|
||
unless (grep { $_ eq $zone } @{config->{'editable_zones'}}) {
|
||
pass;
|
||
}
|
||
|
||
my $contents = body_parameters->{'zone-contents'};
|
||
my ($data, $status) = call_api(PUT => 'dns', "/zone/${zone}/file", { contents => $contents });
|
||
|
||
if ($status ne '200') {
|
||
$data = decode_json($data);
|
||
}
|
||
deferred status => {
|
||
outcome => $data->{outcome},
|
||
messages => $data->{messages},
|
||
};
|
||
deferred zone_contents => $data->{contents};
|
||
|
||
redirect "/dns/zone-edit/$zone", 303;
|
||
};
|
||
|
||
get '/sender/dkim-keys' => sub {
|
||
my ($installed_keys, $status) = call_api(GET => 'sender', '/installed-keys');
|
||
($status eq '200') or die "API returned $status";
|
||
|
||
template 'sender/dkim-keys' => {
|
||
active_role => 'sender',
|
||
title => 'Gestion des clefs DKIM',
|
||
installed_keys => $installed_keys,
|
||
};
|
||
};
|
||
|
||
get '/dkim-generator/sender' => sub {
|
||
template 'dkim-generator/sender' => {
|
||
title => 'Générateur de clefs DKIM'
|
||
};
|
||
};
|
||
|
||
post '/dkim-generator/sender' => sub {
|
||
my $api_params = {
|
||
domain => body_parameters->get('domain'),
|
||
selector => body_parameters->get('selector'),
|
||
key_size => body_parameters->get('key-size')
|
||
};
|
||
|
||
my ($response, $status) = call_api(POST => 'sender', '/generate-dkim-key', $api_params);
|
||
($status eq 200) or die "API returned $status";
|
||
|
||
template 'dkim-generator/sender' => {
|
||
title => 'Générateur de clefs DKIM',
|
||
txt_record => $response->{'txt_record'}
|
||
};
|
||
};
|
||
|
||
get '/attacker/spoof' => sub {
|
||
my ($config, $status) = call_api(GET => 'attacker', '/config');
|
||
($status eq 200) or die "Could not get attacker’s configuration";
|
||
|
||
template 'attacker/spoof' => {
|
||
title => 'Usurpateur d’identité de courriel',
|
||
default_helo => $config->{default_helo},
|
||
my_mail_from => $config->{my_mail_from},
|
||
scenarios => $config->{templates}
|
||
};
|
||
};
|
||
|
||
post '/attacker/spoof' => sub {
|
||
my $scenario = body_parameters->get('scenario');
|
||
my $replace_mail_from = (body_parameters->get('rfc5321-mailfrom') eq 'replace');
|
||
my $helo = body_parameters->get('helo');
|
||
|
||
my %api_params = (
|
||
scenario => $scenario,
|
||
replace_mail_from => ($replace_mail_from ? JSON::true : JSON::false),
|
||
helo => $helo
|
||
);
|
||
|
||
my ($logs, $status) = call_api(POST => 'attacker', '/spoof', \%api_params);
|
||
|
||
deferred 'logs' => $logs;
|
||
deferred 'selected_scenario' => $scenario;
|
||
deferred 'replace_mail_from' => $replace_mail_from;
|
||
|
||
redirect '/attacker/spoof', 303;
|
||
};
|
||
|
||
get '/sender/send-email' => sub {
|
||
my %template_params = (
|
||
title => 'Envoi de messages légitimes',
|
||
email_data => [
|
||
{
|
||
what => 'Confirmation de commande',
|
||
from => 'support@expediteur.example',
|
||
url => 'confirmation_email'
|
||
},
|
||
{
|
||
what => 'Newsletter',
|
||
from => 'info@newsletter.expediteur.example',
|
||
url => 'newsletter'
|
||
}
|
||
]
|
||
);
|
||
|
||
my $success = query_parameters->get('success');
|
||
if (defined $success) {
|
||
$template_params{success} = ($success eq 'success') ? 'success' : 'failure';
|
||
}
|
||
template 'sender/send-email' => \%template_params;
|
||
};
|
||
|
||
get '/recipient/settings' => sub {
|
||
my ($system_status, $http_code) = call_api(GET => 'recipient', '/status');
|
||
die if $http_code ne '200';
|
||
|
||
template 'recipient/settings' => {
|
||
title => 'Paramètres du système destinataire',
|
||
system_status => $system_status
|
||
};
|
||
};
|
||
|
||
post '/recipient/settings' => sub {
|
||
my %api_params = map {
|
||
$_ => body_parameters->{"$_-status"} ? 'enabled' : 'disabled'
|
||
} qw(spf dkim dmarc);
|
||
|
||
my (undef, $status) = call_api(PUT => 'recipient', '/status', \%api_params);
|
||
my $success = ($status eq 200) ? 'success' : 'failure';
|
||
redirect "/recipient/settings?success=$success", 303;
|
||
};
|
||
|
||
get '/sender/send-email/:email' => sub {
|
||
my $email = route_parameters->get('email');
|
||
|
||
my (undef, $response) = call_api(POST => 'sender', "/send-email/${email}");
|
||
my $success = ($response =~ /^2\d\d$/) ? 'success' : 'failure';
|
||
|
||
redirect "/sender/send-email?success=$success", 303;
|
||
};
|
||
|
||
get '/recipient/webmail' => sub {
|
||
template 'recipient/webmail' => {
|
||
title => 'Courriels'
|
||
};
|
||
};
|
||
|
||
any qr{.*} => sub {
|
||
template '404';
|
||
};
|
||
|
||
dance;
|
||
|
||
true;
|