#                                      Copyright 2024 WebPros International, LLC
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited.
#
package Cpanel::UpdateGatherer::modules::EximMainlog;

use lib '/var/cpanel/perl';
use Cpanel::UpdateGatherer::Std;

use constant EXIM_LOG_TIME_PIECE_FORMAT => '%Y-%m-%d %k:%M:%S';

sub compile ( $self, $meta_data ) {
    $meta_data->{exim_mainlog_stats} = _add_exim_mainlog_stats();
    return 1;
}

sub _add_exim_mainlog_stats {

    my %eximlog_stats;

    _get_exim_log_stats( \%eximlog_stats );

    return \%eximlog_stats;
}

# A lot of this is taken from the SpamAssassin.pm module, but intended to be more open to finding general information
sub _get_exim_log_stats ($eximlog_stats_hr) {
    my $exim_mainlog = _exim_mainlog();
    return if !-e $exim_mainlog;

    require Time::Piece;
    require File::ReadBackwards;

    my $one_day_ago = _one_day_ago();
    my $bwfh        = File::ReadBackwards->new($exim_mainlog);

    # Set some defaults for expected/known transports and routes
    my %known_routers;
    my %known_transports;

    $known_routers{'dkim_lookuphost'}                      = 1;
    $known_routers{'autoreply_dkim_lookuphost'}            = 1;
    $known_routers{'enforce_mail_permissions'}             = 1;
    $known_routers{'fail_remote_domains'}                  = 1;
    $known_routers{'lookuphost'}                           = 1;
    $known_routers{'reject_forwarded_mail_marked_as_spam'} = 1;
    $known_routers{'virtual_aliases'}                      = 1;
    $known_routers{'virtual_user'}                         = 1;
    $known_transports{'dkim_remote_smtp'}                  = 1;
    $known_transports{'remote_smtp'}                       = 1;
    $known_transports{'dovecot_virtual_delivery'}          = 1;

    foreach my $router ( keys %known_routers ) {
        $eximlog_stats_hr->{'sent_ok'}{'routers'}{$router}     = 0;
        $eximlog_stats_hr->{'failed_send'}{'routers'}{$router} = 0;
    }
    foreach my $transport ( keys %known_transports ) {
        $eximlog_stats_hr->{'sent_ok'}{'transports'}{$transport}     = 0;
        $eximlog_stats_hr->{'failed_send'}{'transports'}{$transport} = 0;
    }

    # Catch-alls for anything we don't specify
    $eximlog_stats_hr->{'sent_ok'}{'routers'}{'other'}        = 0;
    $eximlog_stats_hr->{'sent_ok'}{'transports'}{'other'}     = 0;
    $eximlog_stats_hr->{'failed_send'}{'routers'}{'other'}    = 0;
    $eximlog_stats_hr->{'failed_send'}{'transports'}{'other'} = 0;

    while ( defined( my $line = $bwfh->readline ) ) {

        # Currently we aren't using all of these captures, but it's good to have them readily available in the future
        if ( $line =~ m/^(\d{4}\-\d{2}\-\d{2})\s(\d{2}\:\d{2}\:\d{2})\s([a-zA-Z0-9-]{23})\s(.{2,3})\s(.+)/ ) {
            my $ymd       = $1;
            my $hms       = $2;
            my $msg_id    = $3;
            my $line_flag = $4;
            my $log_str   = $5;

            my $log_time = Time::Piece->strptime( $ymd . ' ' . $hms, EXIM_LOG_TIME_PIECE_FORMAT() );

            last if $log_time < $one_day_ago;

            # We are only concerned with remote deliveries
            if ( ( $line_flag eq '=>' || $line_flag eq '>>' ) && $log_str =~ m/T=(remote_smtp|dkim_remote_smtp)/ ) {
                if ( $log_str =~ m/\sC=\"([1-5]\d\d)\s/ ) {
                    $eximlog_stats_hr->{'sent_ok_codes'}{$1}++;
                }
                if ( $log_str =~ m/\sR=(.+?)\:?\s/ ) {
                    if ( defined $known_routers{$1} ) {
                        $eximlog_stats_hr->{'sent_ok'}{'routers'}{$1}++;
                    }
                    else {
                        $eximlog_stats_hr->{'sent_ok'}{'routers'}{'other'}++;
                    }
                }
                if ( $log_str =~ m/\sT=(.+?)\:?\s/ ) {
                    if ( defined $known_transports{$1} ) {
                        $eximlog_stats_hr->{'sent_ok'}{'transports'}{$1}++;
                    }
                    else {
                        $eximlog_stats_hr->{'sent_ok'}{'transports'}{'other'}++;
                    }
                }
            }
            elsif ( $line_flag eq '**' ) {

                # If it's a failure, get the router, and transport if available
                $eximlog_stats_hr->{'send_failures'}++;
                if ( $log_str =~ m/retry timeout exceeded$/ ) {
                    $eximlog_stats_hr->{'failed_send'}{'retrytimeout'}++;
                    next;
                }
                if ( $log_str =~ m/\sR=(.+?)\:?\s/ ) {
                    if ( defined $known_routers{$1} ) {
                        $eximlog_stats_hr->{'failed_send'}{'routers'}{$1}++;
                    }
                    else {
                        $eximlog_stats_hr->{'failed_send'}{'routers'}{'other'}++;
                    }
                }
                if ( $log_str =~ m/\sT=(.+?)\:?\s/ ) {
                    if ( defined $known_transports{$1} ) {
                        $eximlog_stats_hr->{'failed_send'}{'transports'}{$1}++;
                    }
                    else {
                        $eximlog_stats_hr->{'failed_send'}{'transports'}{'other'}++;
                    }
                }
            }
        }
        elsif ( $line =~ m/^(\d{4}\-\d{2}\-\d{2})\s(\d{2}\:\d{2}\:\d{2})\sSMTP connection from[^(]+\((.*)\).*closed by QUIT$/ ) {
            my $ymd       = $1;
            my $hms       = $2;
            my $smtp_host = $3;

            my $log_time = Time::Piece->strptime( $ymd . ' ' . $hms, EXIM_LOG_TIME_PIECE_FORMAT() );

            last if $log_time < $one_day_ago;

            next if $smtp_host =~ /localhost$/;
            next if $smtp_host =~ /\.localdomain$/;
            next if $smtp_host =~ /local$/;
            next if $smtp_host =~ /^\[/;
            next if $smtp_host =~ /^[^.]+$/;

            my $lc_smtp_host = lc $smtp_host;
            require Cpanel::Hostname;
            next if $lc_smtp_host eq Cpanel::Hostname::gethostname();

            # Section modified from Cpanel::ZoneFile::Tld::guess_root_domain which doesn't exist before 2024, but Cpanel::PublicSuffix does.
            require Cpanel::PublicSuffix;
            my $non_tld_parts_to_add = 1;    # https://metacpan.org/pod/IO::Socket::SSL::PublicSuffix#$self-%3Epublic_suffix(-$host|\@host,-[-$add-]-)

            my @suffix_reply = Cpanel::PublicSuffix->get_io_socket_ssl_publicsuffix_handle()->public_suffix( $lc_smtp_host, $non_tld_parts_to_add );

            my $smtp_root_host = $suffix_reply[-1];

            next if !length $smtp_root_host;

            $eximlog_stats_hr->{'smtp_root_host_connections'}{$smtp_root_host}++;
        }
    }
    $bwfh->close;

    return;
}

sub _exim_mainlog {
    return '/var/log/exim_mainlog';
}

sub _one_day_ago {
    my $now = Time::Piece->new();
    return $now - 24 * 60 * 60;
}

1;
