#!/usr/bin/perl
#	firewall-easy-mon 0.06	Mon firewall log to stdout or tty
#
#	Copyright (C) 2002:	Manel Marin <manel3@wanadoo.es>
#	Licence:		GNU GPL version >= 2
#
#	Use:	firewall-mon-iptables
#		firewall-mon-iptables tty9
#
#
# 0.02 16.2.02 Added log rotation detection and beep on packet log line,
#		 removed server name in packet lines to always fit in 80 chars
# 0.03 24.2.02 Studied stat issue, replaced strings glued with . by "$a $b"
#		 added an initial message
# 0.04  2.3.02 Proper 2.4 icmp report, date 1 or 2 numbers fixed
# 0.05 26.10.02 Bug exiting with "file was rotated" and error (var $log dupl.)
# 0.06  9.11.02 Bug with icmp packets having "packet [another packet]" info


# ESTRATEGIA:
#	LEER/MONITORIZAR /var/log/messages (NO HAY QUE TOCAR syslog)
#	ENVIAR A: stdout o al tty especificado
#
# STRATEGY:
#	READ/MON /var/log/messages (syslog NOT MODIFIED)
#	SEND TO: stdout or to specified tty


$[ = 1;		# Set array base to 1 (so $p[1] = $1 in awk)
$, = ' ';	# Set output field separator
$\ = "\n";	# Set output record separador

# ARCHIVO DE LOG /LOG FILE
$log="/var/log/messages";

# ASCII BEEP
$beep="\a";


# LEER ARGUMENTOS (array @ARGV) / READ ARGS
if( @ARGV ) {
    $tty = "$ARGV[1]";
    $tty =~ s|.*tty|/dev/tty|;	# Complet tty9 to /dev/tty9
}
else { $tty = ""; }

# REDIRIGIR STDOUT A TTY SI SE NECESITA / REDIR STDOUT TO TTY IF NEEDED
if ( $tty ne "" ) {
    print "Output to $tty";
# "+<" = do not create a new file if it does not exist...
    open(STDOUT, "+< $tty") || die("Can not open tty: $!");
}

# INIT VAR
$last = "YES"; # If first lines of log are "last message repeated" show them


# EXAMINAR LOG Y PAQUETES QUE VAYAN LLEGANDO (parecido al comando "tail") 
# PARSE LOG AND MON PACKETS ARRIVING (like "tail" but perl done)
while ( 1 ) {

# ABRIR LOG / OPEN LOG FILE
open(LOG, "< $log") ||
    die("Can not open $log file: $!");

# MENSAJE / MESSAGE

print "<> Monitoring $log...";


# DETECTAR POR INODE CTIME SI HUBO ROTACION DE LOG PARA ABRIR EL NUEVO ARCHIVO
# DETECT BY INODE CHANGE TIME VARIATION IF LOG ROTATED TO OPEN THE NEW FILE
# * TRUE:					(ctime LOG == ctime filename)
# ctime does not change when reading file (in both stats)
# ctime changes when writting to the file (but the same in both stats)
# * FALSE:					(ctime LOG != ctime filename)
# ctime is NUL when renaming the file (in both, but == does not match)
# ctime is NUL in LOG and is a number in file stat when mv & touch || mv & cp
# ctime is NUL in LOG and is a number in file when cp other file to file
#
# In theory checking if ctime LOG is not NUL is enough, but comparing with the
# ctime of filename stat does not hurt
#
# NOTE: I do not use mtime because it does not keep the last write time
# each stat gives a new time value.
#
 while ( (stat(LOG))[10] == (stat($log))[10] ) {
    #print "stat LOG = " . (stat(LOG))[10] . "   stat $log " . (stat($log))[10];
  while ( <LOG> ) {	# Bucle para cada linea ($_ o implicito)

    chomp;		# Evitar \n en el ultimo campo / Avoid last \n

    # INICIALIZAR VAR / INIT VAR
	$shown = "no";

# example of ipchains log line:
#1:Jan 25 16:54:26 deneb kernel: Packet log: input DENY eth0 PROTO=6
#12:192.168.0.1:1024 192.168.0.2:22 L=60 S=0x00 I=4053 F=0x4000 T=64 SYN (#7)
    # NO SALTAR LOG DE PAQUETES / DO NOT SKIP PACKET LOG - ipchains
	if( /Packet log/ ) {
    # GET DATE
    # we use this instead of next split because date can be 1 or 2 numbers
	    if( /^(\w*\s*\d*\s*\d*:\d*:\d*)/ ) { $date=$1; }
    # SPLIT (FIX PARAMETERS)
	    ($m,$d,$t,$s,$tag,$word_packet,$word_log,
		$chain,$action,$iface,$proto,$from,$to) = split;
    # TRANSLATE COMMON PROTOCOLS
	    if( $proto eq "PROTO=6" ) { $proto = "tcp"; }
	    if( $proto eq "PROTO=17" ) { $proto = "udp"; }
	    if( $proto eq "PROTO=1" ) { $proto = "icmp"; }

	    print "$date   $action $proto $chain $iface $from > $to $beep";
	    $shown = "YES";
	}

# example of iptables log line (tcp):
#
#Jan 28 22:22:06 sirius kernel: DROP->IN=ppp0 OUT= MAC= SRC=213.186.34.136 
# DST=62.36.26.12 LEN=40 TOS=0x10 PREC=0x00 TTL=55 ID=38927 DF PROTO=TCP
# SPT=80 DPT=1139 WINDOW=32696 RES=0x00 ACK FIN URGP=0
#
# another (icmp):
#Mar  2 17:52:50 sirius kernel: DROP->IN=ppp0 OUT= MAC= SRC=62.36.128.243
# DST=62.36.26.49 LEN=60 TOS=0x00 PREC=0x00 TTL=58 ID=23538 PROTO=ICMP
# TYPE=8 CODE=0 ID=256 SEQ=37405
#
# icmp with packet [another packet] info
#Nov  9 09:13:51 sirius kernel: DROP->IN= OUT=lo SRC=127.0.0.1 DST=127.0.0.1
# LEN=368 TOS=0x00 PREC=0xC0 TTL=255 ID=36127 PROTO=ICMP TYPE=3 CODE=3
# [SRC=127.0.0.1 DST=127.0.0.1 LEN=340 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF
# PROTO=UDP SPT=53 DPT=1057 LEN=320 ] 

    # NO SALTAR LOG DE PAQUETES / DO NOT SKIP PACKET LOG - iptables
	if( /IN=.*OUT=/ ) {
    # INIT VARS
	    $date = $action = $proto = $in = $out = $src = $dst = "";
    # GET DATE
	    if( /^(\w*\s*\d*\s*\d*:\d*:\d*)/ ) { $date=$1; }

    # GET PARAMETERS
    # As we can have a variable word log prefix in iptables
    # instead of fix split we will "isolate" parameters at one by one basis
    # NOTE: (.*?) does a minimal match
	    if( /.*?kernel: (.*?)->IN=.*/ ) { $action = $1; }
	    if( /.*?PROTO=(.*?) .*/ ) { $proto = lc $1; }	# lc=lowercase

    # If "IN=" (empty) print nothing, if not print "IN=ppp0", same for OUT
	    if( /.*?IN=(.*?) .*/ ) {
		if( $1 ) { $in = "IN=$1"; }
	    }
	    if( /.*?OUT=(.*?) .*/ ) {
		if( $1 ) { $out = "OUT=$1"; }
	    }
	    if( /.*?SRC=(.*?) .*/ ) { $src = $1; }
	    if( /.*?SPT=(.*?) .*/ ) { $src = "$src:$1"; }
	    if( /.*?DST=(.*?) .*/ ) { $dst = $1; }
	    if( /.*?DPT=(.*?) .*/ ) { $dst = "$dst:$1"; }

	    if( /.*?TYPE=(.*?) .*/ ) { $proto = "$proto$1"; }	# e.g.: icmp8

	    print "$date   $action $proto $in$out $src > $dst $beep";
	    $shown = "YES";
	}

    # MOSTRAR IP PPP / SHOW PPP IP
	if( /local  IP address/ ) {
	    s/\[.*\]//;			# No process id
#		print $_ . $beep;
	    print;
	    $shown = "YES";
	}

    # MOSTRAR FIN PPP / SHOW PPP END
	if( /pppd.*Terminating/ ) {
	    s/\[.*\]//;			# No process id
      # Ayuda visual / Visual help
	    s/Terminating/------------------->Terminating/;
#		print $_ . $beep;
	    print;
	    $shown = "YES";
	}

    # SOLO SALTAR "last message repeated" SI SE HA SALTADO LA LINEA ANTERIOR
    # ONLY SKIP "last message repeated" IF LAST LINE SKIPPED
	if( /last message repeated/ ) {
	    if( $last eq "YES" ) {
		print $_ . $beep;
		$shown = "YES";
	    }
	}

    # RECORDAR ULTIMA ACCION / REMEMBER LAST ACTION
	$last = "$shown";

  }
  # ESPERAR NUEVAS LINEAS AADIDAS AL LOG / WAIT TO NEW LINES ADDED TO FILE
  sleep 1;
 }
 # LOG ROTADO CERRAR EL VIEJO / LOG ROTATED CLOSE THE OLD ONE
    #print "stat LOG = " . (stat(LOG))[10] . "   stat $log " . (stat($log))[10];
 close(LOG);
 print "$log file was rotated, changing to the new file...";
 print "----------------------------------------------------------------------";
 print "";
 sleep 5;	# provide time to rename and creation of another file same name
}

close(STDOUT);
print "Bye...";
exit;
