#!/usr/bin/perl -I/usr/local/realadm/lib
use File::Slurp;
use File::Tail;
use RealEd;
use Aia::Logger ':noinit';

(my $pgm = $0) =~ s%^.*/%%;
my $log =  $^P ? \*STDERR : Aia::Logger->new(logfile => '/var/log/realed-fwmond.log');
!$^P and $0 = "$0";

# Check if running as root
$< != 0 and die "This script must be run as root\n";

# Check if auditd is running
system(qw(systemctl is-active --quiet auditd)) != 0 and die "Auditd service is not running. Please start it with: systemctl start auditd\n";

my @want = qw(
    INPUT
    rechain
    screen
);

use constant ipwhite => '/etc/modsecurity/ip-whitelist.txt';

my $want = '(?:' . join('|', @want) . ')';
my %want = map(($_ => 1), @want);

print $log "Setting up audit rules...\n";
setup_audit_rules();

# Set up file monitoring for audit log
my $audit_log = '/var/log/audit/audit.log';
-f $audit_log or die "Can't find audit log at $audit_log\n";

print $log "Monitoring $audit_log for iptables changes...\n";

my $file = File::Tail->new(
    name => $audit_log,
    interval => 1,
    adjustafter => 5,
    maxinterval => 1,
    tail => 0,           # Start at current position
    resetafter => 60,    # Reset internal state after 60 seconds of inactivity
    start_position => -s $audit_log  # Start at end of file
);

# Handle cleanup on script termination
$SIG{INT} = $SIG{TERM} = sub {exit 0};

$SIG{ALRM} = \&update_whitelist;

update_whitelist();
while ($_ = $file->read) {
    if (/type=EXECVE .*(?:ipset|iptables)/) {
        $_ = join ' ', / a\d+="([^"]*)"/g; #"
        my ($x, $table) = /-([AID]) (\S+)/ or next;
        /ipset| -[AID] $want / and alarm 1;
    }
}

# Normalize and IP address
sub ipnorm {
    return join('.', map(sprintf("%03s", $_), split(/\./, $_[0])));
}

# Sort ip addresses
sub ipsort {
    return ipnorm($a) cmp ipnorm($b);
}

# Sort ip addresses
# Set up audit rules
sub setup_audit_rules {
    my @binaries = qw(
        /usr/sbin/iptables
        /usr/sbin/iptables-legacy
        /usr/sbin/iptables-nft
        /usr/sbin/xtables-nft-multi
        /usr/sbin/ipset
    );
    
    # First remove any existing iptables-changes rules
    open my $audit_list, '-|', qw(auditctl -l) or die "Can't run auditctl: $!";
    while (<$audit_list>) {
        if (/-w\s+([^\s]+).*iptables-changes/) {
            system qw(auditctl -W), $1, qw(-p x -k iptables-changes);
        }
    }
    close($audit_list);
    
    # Add our watch rules
    for my $binary (@binaries) {
        system qw(auditctl -w), $binary, qw(-p x -k iptables-changes) ;
        print $log "Added audit watch for $binary\n";
    }
}

# Update whitelist
sub update_whitelist {
    alarm(0);
    open my $fh, '-|', '/usr/local/realadm/bin/fw';
    my @whitelist;
    while (<$fh>) {
        my @fields = split ' ';
        (my $ip = shift @fields) =~ s%/32$%%o;
        my $what = shift @fields;
        if ($what eq 'ACCEPT' && $ip !~ m{/}) {
            my $t = pop @fields;
            $want{$t} and push @whitelist, $ip . "\n";
        }
    }
    close $fh;
    @whitelist = sort ipsort @whitelist;
    my @owhitelist = sort ipsort read_file(ipwhite, err_mode => 'quiet');
    if (join('', @owhitelist) ne join('', @whitelist)) {
        print $log 'updating ' . ipwhite . "\n";
        write_file(ipwhite, @whitelist);
        system qw(/bin/systemctl reload apache2);
    }
}

END {
    # Cleanup just our rules on script exit
    open my $audit_list, '-|', qw(auditctl -l) or die "Can't run auditctl: $!";
    while (<$audit_list>) {
        if (/-w\s+([^\s]+).*iptables-changes/) {
            system qw(auditctl -W), $1, qw(-p x -k iptables-changes);
        }
    }
    close $audit_list;
}
