#!/usr/bin/env perl

use strict;
use warnings;

use Getopt::Long;
use Mail::Milter::Authentication;
use Mail::Milter::Authentication::Protocol::Milter;
use Mail::Milter::Authentication::Protocol::SMTP;
use Mail::Milter::Authentication::Config qw{ get_config };
use Module::Load;
use Pod::Usage;

# CONFIG
my $pid_file = '/run/authentication_milter.pid';
my $daemon = 0;
my $help   = 0;
my $prefix;

GetOptions (
    "daemon"    => \$daemon,
    "pidfile=s" => \$pid_file,
    "help:s"    => \$help,
    "prefix=s"  => \$prefix,
) or die "Error in command line arguments\n";

if ( $help eq q{} ) {
    usage();
    exit 0;
}
elsif ( $help ) {
    module_usage( $help );
    exit 0;
}

if ( $prefix ) {
    $Mail::Milter::Authentication::Config::PREFIX = $prefix;
}

Mail::Milter::Authentication::start({
    'pid_file'   => $pid_file,
    'daemon'     => $daemon,
});

sub usage {
    pod2usage( -verbose => 2 );
    return;
}

sub module_usage {
    my ( $module ) = @_;
    $module =~ s/[^a-zA-Z0-9]//g;
    my $full_module = 'Mail::Milter::Authentication::Handler::' . $module;
    eval {
        load $full_module;
    };
    if ( my $error = $@ ) {
        die "Could not find help for $module";
    }

    my $part_path = $full_module;
    $part_path =~ s/::/\//g;
    $part_path .= '.pm';

    my $module_file = $INC{ $part_path };
    if ( ! $module_file ) {
        die "Could not find help for $module";
    }
    pod2usage( -input => $module_file, -verbose => 2 );

    return;
}

__END__

=head1 NAME

  Authentication Milter

=head1 USAGE

  authentication_milter [-d|--daemon] [-p|--pidfile <file>] [-h|--help] [--prefix <dir>]

=head1 OPTIONS

=over

=item -h|--help

  Show this help.

=item -h|--help <ModuleName>

  Show help for a particular Module.

  Modules installed by default include the following.

  AddID Auth DKIM DMARC IPRev LocalIP PTR ReturnOK
  Sanitize SenderID SpamAssassin SPF TrustedIP TLS

=item -d|--daemon

  detatch from shell and run as a daemon

=item -p|--pidfile <file>

  Write the process PID to the given file.
  defaults to /run/authentication_milter.pid

=item --prefix <dir>

  Read configuration from dir rather than /etc/

=back

=head1 CONFIGURATION

  The milter reads configuration from /etc/authentication_milter.json

  The configuration file format is as follows...

  {
    "debug"     : 0,                                    | Verbose debugging output
    "dryrun"    : 0,                                    | Dryrun (do not alter or reject mail)
    "logtoerr"  : 0,                                    | Also write logs to STDERR
    "error_log" : "/var/log/authentication_milter.err", | Capture STDERR to logfile

    "connection"             : "inet:12345@localhost",  | The connection to use 
    "umask"                  : "0000",                  | Set umask (for unix socket)

    "connections" : {                                   | Other than the default connection, also bind to
                                                        | these connections.

        "name_two" : {                                  | Name of connection
            "connection"   : "unix:/var/sock/a.sock",   | The connection to use 
            "umask"        : "0000",                    | Set umask
        }
        "name_one" : {                                  | Name of connection
            "connection"   : "inet:12346@localhost",    | The connection to use 
        }
    },


    "runas"                  : "nobody",                | Drop privs and run as this user (root only)
    "rungroup"               : "nogroup",               | Drop privs and run as this group (root daemon only)
    "chroot"                 : "/path/to/chroot"        | Set chroot before forking (root only)
                                                        | N.B. This path will need to be setup with all required
                                                        | files or the server WILL segfault.
    "listen_backlog"         : 20,                      | socket listen backlog limit (default 20)
    "min_children"           : 20,                      | Max number of children to pre fork
    "max_children"           : 200,                     | Max number of children to pre fork
    "min_spare_children"     : 10,                      | Min number of spare children to maintain 
    "max_spare_children"     : 20,                      | Max number of spare children to maintain
    "max_requests_per_child" : 200,                     | Max number of requests per child process (prefork)

    "protocol"               : "milter",                | The protocol the milter is to use
                                                        | can be either milter or smtp


    "smtp" : {                                          | Parameters for use when protocol is smtp
        "server_name"    : "scan.example.com",          | The server name to use for the server
        "sock_type"      : "inet",                      | Socket type (inet or unix)
        "sock_host"      : "localhost",                 | Host to connect to (when inet)
        "sock_port"      : "2525",                      | Port to connect to (when inet)
        "sock_path"      : "/var/run/smtp.sock",        | Socket path to connect to (when unix)
        "timeout_in"     : "10",                        | Timeout when waiting for inbound SMTP data
        "timeout_out"    : "10",                        | Timeout when waiting for outbound SMTP data
        "pipeline_limit" : "50",                        | Limit the number of transactions accepted in an SMTP pipeline

        "tcp:12346" : {                                 | Outbound SMTP details can be set per inbound port/socket
                                                        | This allows outbound SMTP to be routed differently for
                                                        | different inbound ports. The key is the inbound port specified
                                                        | as unix:<socket path> or inet:<port>
                                                        | It is not currently possible to set based on listening host.
                                                        | If a specific config set is not found them we use the default
                                                        | set as defined above.
            "server_name" : "scan.example.com",         | The server name to use for the server
            "sock_type"   : "inet",                     | Socket type (inet or unix)
            "sock_host"   : "localhost",                | Host to connect to (when inet)
            "sock_port"   : "2526",                     | Port to connect to (when inet)
            "timeout_in"  : "10",                       | Timeout when waiting for inbound SMTP data
            "timeout_out" : "10"                        | Timeout when waiting for outbound SMTP data
        },
        "unix:/var/sock/a.sock" : {
            "server_name" : "util.example.com",
            "sock_type"   : "unix",
            "sock_path"   : "/var/run/smtp.sock",
            "timeout_in"  : "10",
            "timeout_out" : "10"
        }

    },

                                                        | Timeouts for callbacks, should be slightly lower
                                                        | than the corresponding timeouts in Postfix
                                                        | Timeouts are ignored if missing.
    "connect_timeout"       : 30,                       | Timeout for Connect callbacks
    "command_timeout"       : 30,                       | Timeout for Helo,Mail,Rcpt,Data and Unknown callbacks
    "content_timeout"       : 300,                      | Timeout for Header,Eoh, Body and Eom callbacksa

    "dns_resolvers"         : [                         | Explicit list of DNS resolvers to use
        "8.8.8.8",
        "127.0.0.1"
    ],
    "dns_timeout"           : 10,                       | Timeout for DNS lookups
    "dns_cache_timeout"     : 240,                      | Timeout for cached DNS lookups - 0 to disable cache
    "dns_retry"             : 2,                        | Number of times a lookup will retry per call
    "dns_cache_error_limit" : 3,                        | Number of errors before we use the cache

                                                        | A static DNS override is proviced for testing, this
                                                        | should not be used in production unless you know what
                                                        | you are doing!
    "dns_static_cache"      : {                         | Array of static encoded DNS reply packets
                                                        | intended to be used in either a test scenario, or
                                                        | when there are frequent known lookups which do not change.
        "query:search.example.com:AAAA" : [             | Cached results for lookup_type:search_string:rr_type
            "encoded reply packet",                     | Base 64 Encoded raw reply packet from Net::DNS
            "error string"                              | Error string
        ],
        "search:foo.bar.com:MX" : [
            "encoded reply packet", "error string"
        ]
    },

    "tempfail_on_error"               : "1",            | Tempfail on errors
    "tempfail_on_error_authenticated" : "0",            | Tempfail on errors for Authenticated IP Connections
    "tempfail_on_error_local"         : "0",            | Tempfail on errors for Local IP Connections
    "tempfail_on_error_trusted"       : "0",            | Tempfail on errors for Trusted IP Connections

    "handlers" : {                                      | Config for each handlers, can be prefixed with !
                                                        | to disable that handler without having to remove
                                                        | its config.

        "TLS" : {},                                     | TLS Detection Module. No config required.

        "SPF" : {                                       | Config for the SPF Module
            "hide_none" : 0                             | Hide auth line if the result is 'none'
        },

        "DKIM" : {                                      | Config for the DKIM Module
            "hide_none"         : 0,                    | Hide auth line if the result is 'none'
            "check_adsp"        : 1,                    | Also check for ADSP
            "show_default_adsp" : 0,                    | Show the default ADSP result
            "adsp_hide_none"    : 0                     | Hide auth ADSP if the result is 'none'
        },

        "DMARC" : {                                     | Config for the DMARC Module
                                                        | Requires DKIM and SPF
            "hide_none"      : 0,                       | Hide auth line if the result is 'none'
            "detect_list_id" : "1",                     | Detect a list ID and modify the DMARC authentication header
                                                        | to note this, useful when making rules for junking email
                                                        | as mailing lists frequently cause false DMARC failures.
            "report_skip_to" : [                        | Do not send DMARC reports for emails to these addresses.
                "dmarc@yourdomain.com",                 | This can be used to avoid report loops for email sent to
                "dmarc@example.com"                     | your report from addresses.
            ],
            "no_report" : "1"                           | If set then we will not attempt to store DMARC reports.
        },

        "PTR" : {},                                     | Config for the PTR Module
                                                        | Check PTR records against HELO domain
                                                        | Requires IPRev

        "SenderID" : {                                  | Config for the SenderID Module
            "hide_none" : 1                             | Hide auth line if the result is 'none'
        },

        "IPRev" : {},                                   | Config for the IPRev Module
                                                        | Check the reverse IP for the HELO host

        "Auth" : {},                                    | Config for the Auth Module
                                                        | Check for authenticated connections

        "LocalIP" : {},                                 | Config for the LocalIP Module
                                                        | Check for Local IP Addresses

        "TrustedIP" : {                                 | Config the the TruetedIP Module
                                                        | Check for TrustedIP Addresses
            "trusted_ip_list" : [                       | List of IP Addresses considered to be trusted
                "100.200.100.2",                        | CIDR Ranges are valid syntax
                "2001:44c2:3881:aa00::/56",
                "2001:44b8:3021:123:dead:beef:abcd:1234"
            ],
        },

        "!AddID" : {},                                  | Config for the AddID Module (shown disabled)
                                                        | Example module, adds a header

        "ReturnOK" : {},                                | Config for the ReturnOK Module
                                                        | Check that From addresses have a valid MX

        "Sanitize" : {                                  | Config for the Sanitize Module
                                                        | Remove conflicting Auth-results headers from inbound mail
            "hosts_to_remove" : [                       | Hostnames (including subdomains thereof) for which we
                "example.com",                          | want to remove existing authentication results headers.
                "example.net"
            ],
            "remove_headers" : "yes"                    | Remove headers with conflicting host names (as defined above)
                                                        | "no" : do not remove
                                                        | "yes" : remove and add a header for each one
                                                        | "silent" : remove silently
                                                        | Does not run for trusted IP address connections
        }
    }
  }

=head1 DMARC

This milter uses Mail::DMARC as a backend for DMARC checks, this module requires that a configuration file is setup.

You should create and populate /etc/mail-dmarc.ini

For DMARC reporting you are also required to setup a datastore, including creating a basic table structure.
The detauls of this are to be found in the Mail::DMARC documentation.

At this time forensic reports are not supported by Mail::DMARC or this milter. Only aggregate reports will be generated.

To check reports please use the dmarc_view_reports command, to send reports please use the dmarc_send_reports command.
These are included with the Mail::DMARC module.

=head1 AUTHORS

Marc Bradshaw E<lt>marc@marcbradshaw.netE<gt>

=head1 COPYRIGHT

Copyright 2015

This library is free software; you may redistribute it and/or
modify it under the same terms as Perl itself.

