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;