#!/usr/bin/perl
use strict;
use warnings;

use Mail::DMARC;
use Mail::DMARC::HTTP;

my $dmarc = Mail::DMARC->new();
$dmarc->is_public_suffix('tnpi.net');
my $report = $dmarc->report;
my $http   = Mail::DMARC::HTTP->new;
$http->dmarc_httpd($report);
exit;

__END__

=pod

=head1 NAME

dmarc_httpd: a web server for DMARC validation and report viewing

=head1 SYNOPSIS

A HTTP interface for:

=over 4

=item * local DMARC reports

=item * DMARC validator service

=back

Start the HTTP server:

    dmarc_httpd

Connect with a web browser to L<http://localhost:8080|http://localhost:8080/>.

=head1 DESCRIPTION

Serves the report viewer and a JSON API. The viewer is one HTML file, a
stylesheet and a few JavaScript modules in the share directory, with no
external dependencies, so it works on a host with no route to the internet.

=head2 Endpoints

=over 4

=item * / - the report viewer

=item * /dmarc/json/validate - POST a JSON request, get a DMARC result. See
dmarc_http_client for a usage example.

=item * /dmarc/json/report - the report list

=item * /dmarc/json/row - one report's rows

=item * /dmarc/json/domains - domains reports arrived for

=item * /dmarc/json/summary - volume by alignment and disposition, with the
preceding window for comparison

=item * /dmarc/json/timeseries - the same, one row per UTC day

=item * /dmarc/json/sources - one row per sending IP, ranked by failing
volume, each bucketed C<aligned>, C<forwarded> or C<failing>

=item * /dmarc/json/source - one IP in detail: the From domains, and the SPF
and DKIM it presented. Buckets it C<aligned>, C<broken> or C<unauthenticated>,
which separates a misconfigured sender from a spoof.

=back

=head2 Parameters

C<since> and C<until> are epoch seconds, compared against the report window
start, defaulting to the last 30 days. C<from_domain> and C<author> narrow to
one domain or reporter. C<source_ip> selects one source, required by /source.
/sources also takes C<start>, C<length> and C<sort_col>. Times in these
responses are epoch seconds. Volumes are weighted by each record's message
count; a record with no count is one message.

=head2 Received and outgoing reports

A store also queues the reports this host is preparing to send, which are
about other people's domains. The summary, timeseries, sources and domain
views count only reports received from others; the report list shows both.
Pass C<reports=outgoing> or C<reports=all> to change that. They are told apart
by author, so C<org_name> must match the name this host reports under.

=head1 REVERSE DNS

Ticking "Resolve hostnames" looks up the PTR of each source IP on display. It
is off by default because it sends those IPs to a third party DNS-over-HTTPS
resolver. Cloudflare and Google are offered, and "Other" accepts any DoH
server answering JSON with CORS headers. A hand-entered resolver is checked as
you type and adopted only once it answers. Both settings are per browser.

Lookups happen in the browser so the many IPs with no PTR cannot stall
dmarc_httpd.

Web server settings are in the [http] and [https] sections of mail-dmarc.ini.
C<post_max> caps the size of a POST body, in bytes, defaulting to 10MB. It
cannot be lifted: 0, and anything that is not a byte count, is refused with
a warning at startup and the default is used instead. A body over twice the
cap is answered by closing the connection rather than with a JSON error.

Databases created before Mail::DMARC 2.2 lack indexes the aggregate views
need. F<share/mail_dmarc_indexes.*> adds them, one file per engine.

=head1 THANKS

jQuery - http://www.jquery.com/

jqGrid - http://www.trirand.com/blog/

=head1 AUTHORS

=over 4

=item *

Matt Simerson <msimerson@cpan.org>

=item *

Davide Migliavacca <shari@cpan.org>

=item *

Marc Bradshaw <marc@marcbradshaw.net>

=back

=cut
