spf-dkim-dmarc-demo/console/web-api/lib/Email/SpoofingDemo/Web.pm

244 lines
6.9 KiB
Perl
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#
# 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 attackers configuration";
template 'attacker/spoof' => {
title => 'Usurpateur didentité 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;