Sound Firewall

Last Updated or created 2023-01-04

See also 2nd Firewall 2013 and Led Firewall
https://www.henriaanstoot.nl/2013/08/03/ohm-2013-hackerevent/

While partying @ HAL2001, a hackers event, Venom and I made a Soundfirewall.

We had a little DMZ, with our servers.
This was protected by a iptables firewall.

Our idea was to get a sound notification on every (interesting) network packet the firewall dropped.

So we made this: (At the bottom are the sound definitions.)
Example of the sound we heared whole day.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#use strict;
# @(#) First Edit: Bas
# @(#) Last Edit: Fash
use POSIX ":sys_wait_h";
use vars qw(%Msg_Rec);
$SIG{'TERM'} = $SIG{'HUP'} = 'goodbye';
$SIG{'CHLD'} = 'IGNORE';
## Constants
my $BELL = "";
my $MAILER = "/usr/sbin/sendmail";
my $WRITE = "/usr/bin/write";
$/ = "
";
autoflush STDOUT;
sub goodbye {
$| = 0;
close_pipe_if_open();
exit(0);
}
#
# in_range($range, $number)
# returns 1 if $number is inside $range, 0 if not
#
sub in_range {
my $range = shift;
my $num = shift;
foreach my $f (split(/,/, $range)) {
if ($f =~ /-/) {
my ($low,$high) = split(/-/, $f);
return 1 if ($low <= $num and $num <= $high);
} elsif ($f == $num) {
return 1;
}
}
return 0;
}
#
# inside_time_window($days,$hours)
# returns 1 if inside window, 0 if outside window
#
sub inside_time_window {
my $range = shift;
my($days, $hours) = split(/:/, $range);
my ($hr, $wday) = (localtime(time))[2,6];
if (($days eq '*' or in_range($days, $wday))
and ($hours eq '*' or in_range($hours, $hr))) {
return 1;
} else {
return 0;
}
}
print "\n*** swatch-3.0.1 (pid:6826) started at " . `/bin/date` . "\n";
use Date::Calc qw(:all);
sub parse_dot {
my $message = shift;
my $dot_loc = shift;
my @dot = ();
my @ranges = split(/:/, $dot_loc);
foreach my $range (0..$#ranges) {
if ($ranges[$range] != -1) {
my ($begin, $end) = split(/-/, $ranges[$range]);
$dot[$range] = substr($message, $begin, ($end - $begin + 1));
}
}
return @dot;
}
my ($date_loc, $time_loc) = ("-1:0-2:4-5", "7-8:10-11:13-14");
my %months = (
Jan => 1,
Feb => 2,
Mar => 3,
Apr => 4,
May => 5,
Jun => 6,
Jul => 7,
Aug => 8,
Sep => 9,
Oct => 10,
Nov => 11,
Dec => 12
);
# Returns an array of year, month, day, hours, minutes, and seconds.
#
sub YMDHMS {
my $string = shift;
my $year_today = (Today())[0];
my ($y, $m, $d) = parse_dot($string, $date_loc);
my ($hrs, $mins, $secs) = parse_dot($string, $time_loc);
if (length($y) eq 0) { $y = (Today())[0] };
return ($y, $months{$m}, $d, $hrs, $mins, $secs);
}
sub new_msg {
my $use = shift;
my $msg = shift;
my $count = shift;
my @delta = @_;
my $delta;
if ($delta[0] == 0) {
$delta = sprintf("%d:%.2d:%.2d", $delta[1], $delta[2], $delta[3]);
} else {
$delta = sprintf("$delta[0] day%s %d:%.2d:%.2d", $delta[0] > 1 ? 's' : '',
$delta[1], $delta[2], $delta[3]);
}
if ($use eq 'regex') {
return "$count $msg regular expressions in $delta";
} else {
return "$count in $delta: $msg";
}
}
#
# Stores message information in
# $Msg_Rec = (
# {<truncated message>|<pattern>} => {
# dhms => [ array ], # days,hours,minutes,seconds
# count => integer,
sub throttle {
my %opts = (
KEY => $_,
CUT_MARKS => [ "0:16" ], # not used yet
USE => 'message',
@_
);
my $msg = $opts{'KEY'};
my $use = $opts{'USE'};
my @ymdhms = YMDHMS($msg);
my $key;
my @min_dhms_delta = split(/(\s+|:)/, $opts{'MIN_DELTA'});
foreach my $i (0..$#min_dhms_delta) {
# strip out unwanted element
splice (@min_dhms_delta, $i, 1) if ($min_dhms_delta[$i] eq ":");
}
if ($use eq 'regex') {
$key = $opts{'REGEX'};
} else {
$key = substr($msg, 16);
$key =~ s/\[\d+\]/[PID]/;
}
while ($#min_dhms_delta < 3) {
unshift(@min_dhms_delta, 0); # make sure that the dhms array is full
}
if (exists $Msg_Rec{$key} and defined $Msg_Rec{$key}->{ymdhms}) {
my $passed = 1;
$Msg_Rec{$key}->{count}++;
if ($ymdhms[1] > $Msg_Rec{$key}->{ymdhms}[1]) { $ymdhms[0]--; }
my @delta_dhms = Delta_DHMS(@{$Msg_Rec{$key}->{ymdhms}}, @ymdhms);
foreach my $i (0..$#min_dhms_delta) {
$passed = 0 if ($delta_dhms[$i] < $min_dhms_delta[$i]);
last unless ($delta_dhms[$i] == $min_dhms_delta[$i]);
}
if ($passed) {
my $new = '';
$new = new_msg($use, $key, $Msg_Rec{$key}->{count}, @delta_dhms);
$Msg_Rec{$key}->{ymdhms} = [ @ymdhms ];
$Msg_Rec{$key}->{count} = 1;
return $new;
} else {
return '';
}
} else {
my $rec;
$rec->{ymdhms} = [ @ymdhms ];
$Msg_Rec{$key} = $rec;
return $msg;
}
}
##
## ACTION SUBROUTINES
##
my %text_modes = (
"black" => "\033[30;1m",
"red" => "\033[31;1m",
"green" => "\033[32;1m",
"yellow" => "\033[33;1m",
"blue" => "\033[34;1m",
"magenta" => "\033[35;1m",
"cyan" => "\033[36;1m",
"white" => "\033[37;1m",
"black_h" => "\033[40;1m",
"red_h" => "\033[41;1m",
"green_h" => "\033[42;1m",
"yellow_h" => "\033[43;1m",
"blue_h" => "\033[44;1m",
"magenta_h" => "\033[45;1m",
"cyan_h" => "\033[46;1m",
"white_h" => "\033[47;1m",
"bold" => "\033[1m",
"blink" => "\033[5m",
"inverse" => "\033[7m",
"normal" => "\033[0m",
"underscore" => "\033[4m",
);
sub echo {
my %args = (
'MODES' => [ 'normal' ],
@_
);
return if (exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'}));
if ($args{'MODES'}[0] eq 'random') {
my @mode_names = keys %text_modes;
print $text_modes{$mode_names[rand $#mode_names]};
} else {
foreach my $mode (@{$args{'MODES'}}) {
print $text_modes{$mode};
}
}
print $args{'MESSAGE'};
print $text_modes{'normal'};
print "\n";
}
#
# ring_bell(args) -- send x number of control-G characters to the output.
#
sub ring_bell {
my %args = (
'RINGS' => 1,
@_
);
my $sun_terminal = (`uname -s` eq 'SunOS\n');
return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'});
my $bells = $args{'RINGS'};
for ( ; $bells > 0 ; $bells-- ) {
print $BELL;
sleep 1 if $sun_terminal; # SunOS needed this. Not sure about Solaris though
}
}
#
# exec_command(args) -- fork and execute a command
#
sub exec_command {
my %args = (@_);
my $exec_pid;
my $command;
if (exists $args{'COMMAND'}) {
$command = $args{'COMMAND'};
} else {
warn "$0: No command was specified in exec action.\n";
return;
}
return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'});
EXECFORK: {
if ($exec_pid = fork) {
waitpid(-1, WNOHANG);
return;
} elsif (defined $exec_pid) {
exec($command);
} elsif ($! =~ /No more processes/) {
# EAGAIN, supposedly recoverable fork error
sleep 5;
redo EXECFORK;
} else {
warn "$0: Can't fork to exec $command: $!\n";
}
}
return;
}
{
my $pipe_is_open;
my $current_command_name;
#
# send_message_to_pipe -- send text to a pipe.
#
# usage: &send_message_to_pipe($program_to_pipe_to_including_the_vertical_bar_symbol,
# $message_to_send_to_the_pipe);
#
sub send_message_to_pipe {
my %args = (@_);
my $command;
if (exists $args{'COMMAND'}) {
$command = $args{'COMMAND'};
} else {
warn "$0: No command was specified in pipe action.\n";
return;
}
return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'});
# open a new pipe if necessary
if ( !$pipe_is_open or $current_command_name ne $command ) {
# first close an open pipe
close(PIPE) if $pipe_is_open;
$pipe_is_open = 0;
open(PIPE, "| $command")
or warn "$0: cannot open pipe to $command: $!\n" && return;
PIPE->autoflush(1);
$pipe_is_open = 1;
$current_command_name = $command;
}
# send the text
print PIPE "$args{'MESSAGE'}";
if (not exists $args{'KEEP_OPEN'}) {
close(PIPE) if $pipe_is_open;
$pipe_is_open = 0;
}
}
#
# close_pipe_if_open -- used at the end of a script to close a pipe
# opened by &pipe_it().
#
# usage: &close_pipe_if_open();
#
sub close_pipe_if_open {
if ($pipe_is_open) {
close(PIPE);
}
}
}
#
# send_email -- send some mail using $MAILER.
#
# usage: &send_email($addresses_to_mail_to);
#
sub send_email {
my $login = (getpwuid($<))[0];
my %args = (
'ADDRESSES' => $login,
'SUBJECT' => 'Message from Swatch',
@_
);
return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'});
my $addresses = $args{'ADDRESSES'};
$addresses =~ s/:/,/g;
if ($MAILER eq '') {
warn "ERROR: $0 cannot find a mail delivery program\n";
return;
}
open(MAIL, "| $MAILER $addresses")
or warn "$0: cannot open pipe to $MAILER: $!\n" && return;
print MAIL "To: $addresses\n";
print MAIL "Subject: $args{SUBJECT}\n\n";
print MAIL "$args{'MESSAGE'}\n";
close(MAIL);
}
#
# write_message -- use $WRITE to send a message logged on users.
#
sub write_message {
my %args = (@_);
return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'});
if ($WRITE eq '') {
warn "ERROR: $0 cannot find the write(1) program\n";
return;
}
if (exists($args{'USERS'})) {
foreach my $user (split(/:/, $args{'USERS'})) {
send_message_to_pipe(COMMAND => "$WRITE $user 2>/dev/null",
MESSAGE => "$args{'MESSAGE'}\n");
}
}
}
use File::Tail;
my $Filename = '/var/log/ulog/syslogemu.log';
my $File = File::Tail->new(name=>$Filename, maxinterval => 1, interval => 1);
if (not defined $File) {
die "/usr/local/bin/swatch: cannot read input \"$Filename\": $!\n";
}
LOOP: while (defined($_=$File->read)) {
chomp;
my $sanitized_ = $_;
@_ = split;
# quote all special shell chars
$sanitized_ =~ s/([;&\(\)\|\^><\$`'\\])/\\$1/g;
my @sanitized_ = split(/\s+/, $sanitized_);
if (/INVALID|REPEATED|INCOMPLETE:LOGIN/) {
echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
ring_bell('RINGS' => "3", );
next;
}
if (/(panic|halt)/) {
echo('MESSAGE' => "$_", );
ring_bell();
next;
}
if (/Regents/) {
echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
ring_bell();
next;
}
if (/ipfw:.*Deny ICMP:8.0/) {
echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
exec_command('COMMAND' => "mpg123 -q /root/wavs/drip.wav &", );
next;
}
if (/ipfw:.*Deny TCP .*:6000/) {
echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
exec_command('COMMAND' => "mpg123 -q /root/wavs/camera.wav &", );
next;
}
if (/ipfw:.*Deny UDP .*:513/) {
echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
exec_command('COMMAND' => "mpg123 -q /root/wavs/flush.wav &", );
next;
}
if (/ipfw:.*Deny TCP .*:21/) {
echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
exec_command('COMMAND' => "mpg123 -q /root/wavs/vault.wav &", );
next;
}
if (/PROTO=TCP .*DPT=80/) {
echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
exec_command('COMMAND' => "mpg123 /root/wavs/tcp80.mp3 &", );
next;
}
if (/PROTO=TCP .*DPT=23/) {
echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
exec_command('COMMAND' => "mpg123 /root/wavs/tcp23.mp3 &", );
next;
}
if (/UDP .*=1300007/) {
echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
exec_command('COMMAND' => "mpg123 /root/wavs/udp137.mp3 &", );
next;
}
if (/PROTO=ICMP/) {
echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
exec_command('COMMAND' => "mpg123 /root/wavs/ping.mp3 &", );
next;
}
if (/.*/) {
echo('MESSAGE' => "$_", );
next;
}
}
#use strict; # @(#) First Edit: Bas # @(#) Last Edit: Fash use POSIX ":sys_wait_h"; use vars qw(%Msg_Rec); $SIG{'TERM'} = $SIG{'HUP'} = 'goodbye'; $SIG{'CHLD'} = 'IGNORE'; ## Constants my $BELL = ""; my $MAILER = "/usr/sbin/sendmail"; my $WRITE = "/usr/bin/write"; $/ = " "; autoflush STDOUT; sub goodbye { $| = 0; close_pipe_if_open(); exit(0); } # # in_range($range, $number) # returns 1 if $number is inside $range, 0 if not # sub in_range { my $range = shift; my $num = shift; foreach my $f (split(/,/, $range)) { if ($f =~ /-/) { my ($low,$high) = split(/-/, $f); return 1 if ($low <= $num and $num <= $high); } elsif ($f == $num) { return 1; } } return 0; } # # inside_time_window($days,$hours) # returns 1 if inside window, 0 if outside window # sub inside_time_window { my $range = shift; my($days, $hours) = split(/:/, $range); my ($hr, $wday) = (localtime(time))[2,6]; if (($days eq '*' or in_range($days, $wday)) and ($hours eq '*' or in_range($hours, $hr))) { return 1; } else { return 0; } } print "\n*** swatch-3.0.1 (pid:6826) started at " . `/bin/date` . "\n"; use Date::Calc qw(:all); sub parse_dot { my $message = shift; my $dot_loc = shift; my @dot = (); my @ranges = split(/:/, $dot_loc); foreach my $range (0..$#ranges) { if ($ranges[$range] != -1) { my ($begin, $end) = split(/-/, $ranges[$range]); $dot[$range] = substr($message, $begin, ($end - $begin + 1)); } } return @dot; } my ($date_loc, $time_loc) = ("-1:0-2:4-5", "7-8:10-11:13-14"); my %months = ( Jan => 1, Feb => 2, Mar => 3, Apr => 4, May => 5, Jun => 6, Jul => 7, Aug => 8, Sep => 9, Oct => 10, Nov => 11, Dec => 12 ); # Returns an array of year, month, day, hours, minutes, and seconds. # sub YMDHMS { my $string = shift; my $year_today = (Today())[0]; my ($y, $m, $d) = parse_dot($string, $date_loc); my ($hrs, $mins, $secs) = parse_dot($string, $time_loc); if (length($y) eq 0) { $y = (Today())[0] }; return ($y, $months{$m}, $d, $hrs, $mins, $secs); } sub new_msg { my $use = shift; my $msg = shift; my $count = shift; my @delta = @_; my $delta; if ($delta[0] == 0) { $delta = sprintf("%d:%.2d:%.2d", $delta[1], $delta[2], $delta[3]); } else { $delta = sprintf("$delta[0] day%s %d:%.2d:%.2d", $delta[0] > 1 ? 's' : '', $delta[1], $delta[2], $delta[3]); } if ($use eq 'regex') { return "$count $msg regular expressions in $delta"; } else { return "$count in $delta: $msg"; } } # # Stores message information in # $Msg_Rec = ( # {<truncated message>|<pattern>} => { # dhms => [ array ], # days,hours,minutes,seconds # count => integer, sub throttle { my %opts = ( KEY => $_, CUT_MARKS => [ "0:16" ], # not used yet USE => 'message', @_ ); my $msg = $opts{'KEY'}; my $use = $opts{'USE'}; my @ymdhms = YMDHMS($msg); my $key; my @min_dhms_delta = split(/(\s+|:)/, $opts{'MIN_DELTA'}); foreach my $i (0..$#min_dhms_delta) { # strip out unwanted element splice (@min_dhms_delta, $i, 1) if ($min_dhms_delta[$i] eq ":"); } if ($use eq 'regex') { $key = $opts{'REGEX'}; } else { $key = substr($msg, 16); $key =~ s/\[\d+\]/[PID]/; } while ($#min_dhms_delta < 3) { unshift(@min_dhms_delta, 0); # make sure that the dhms array is full } if (exists $Msg_Rec{$key} and defined $Msg_Rec{$key}->{ymdhms}) { my $passed = 1; $Msg_Rec{$key}->{count}++; if ($ymdhms[1] > $Msg_Rec{$key}->{ymdhms}[1]) { $ymdhms[0]--; } my @delta_dhms = Delta_DHMS(@{$Msg_Rec{$key}->{ymdhms}}, @ymdhms); foreach my $i (0..$#min_dhms_delta) { $passed = 0 if ($delta_dhms[$i] < $min_dhms_delta[$i]); last unless ($delta_dhms[$i] == $min_dhms_delta[$i]); } if ($passed) { my $new = ''; $new = new_msg($use, $key, $Msg_Rec{$key}->{count}, @delta_dhms); $Msg_Rec{$key}->{ymdhms} = [ @ymdhms ]; $Msg_Rec{$key}->{count} = 1; return $new; } else { return ''; } } else { my $rec; $rec->{ymdhms} = [ @ymdhms ]; $Msg_Rec{$key} = $rec; return $msg; } } ## ## ACTION SUBROUTINES ## my %text_modes = ( "black" => "\033[30;1m", "red" => "\033[31;1m", "green" => "\033[32;1m", "yellow" => "\033[33;1m", "blue" => "\033[34;1m", "magenta" => "\033[35;1m", "cyan" => "\033[36;1m", "white" => "\033[37;1m", "black_h" => "\033[40;1m", "red_h" => "\033[41;1m", "green_h" => "\033[42;1m", "yellow_h" => "\033[43;1m", "blue_h" => "\033[44;1m", "magenta_h" => "\033[45;1m", "cyan_h" => "\033[46;1m", "white_h" => "\033[47;1m", "bold" => "\033[1m", "blink" => "\033[5m", "inverse" => "\033[7m", "normal" => "\033[0m", "underscore" => "\033[4m", ); sub echo { my %args = ( 'MODES' => [ 'normal' ], @_ ); return if (exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'})); if ($args{'MODES'}[0] eq 'random') { my @mode_names = keys %text_modes; print $text_modes{$mode_names[rand $#mode_names]}; } else { foreach my $mode (@{$args{'MODES'}}) { print $text_modes{$mode}; } } print $args{'MESSAGE'}; print $text_modes{'normal'}; print "\n"; } # # ring_bell(args) -- send x number of control-G characters to the output. # sub ring_bell { my %args = ( 'RINGS' => 1, @_ ); my $sun_terminal = (`uname -s` eq 'SunOS\n'); return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'}); my $bells = $args{'RINGS'}; for ( ; $bells > 0 ; $bells-- ) { print $BELL; sleep 1 if $sun_terminal; # SunOS needed this. Not sure about Solaris though } } # # exec_command(args) -- fork and execute a command # sub exec_command { my %args = (@_); my $exec_pid; my $command; if (exists $args{'COMMAND'}) { $command = $args{'COMMAND'}; } else { warn "$0: No command was specified in exec action.\n"; return; } return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'}); EXECFORK: { if ($exec_pid = fork) { waitpid(-1, WNOHANG); return; } elsif (defined $exec_pid) { exec($command); } elsif ($! =~ /No more processes/) { # EAGAIN, supposedly recoverable fork error sleep 5; redo EXECFORK; } else { warn "$0: Can't fork to exec $command: $!\n"; } } return; } { my $pipe_is_open; my $current_command_name; # # send_message_to_pipe -- send text to a pipe. # # usage: &send_message_to_pipe($program_to_pipe_to_including_the_vertical_bar_symbol, # $message_to_send_to_the_pipe); # sub send_message_to_pipe { my %args = (@_); my $command; if (exists $args{'COMMAND'}) { $command = $args{'COMMAND'}; } else { warn "$0: No command was specified in pipe action.\n"; return; } return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'}); # open a new pipe if necessary if ( !$pipe_is_open or $current_command_name ne $command ) { # first close an open pipe close(PIPE) if $pipe_is_open; $pipe_is_open = 0; open(PIPE, "| $command") or warn "$0: cannot open pipe to $command: $!\n" && return; PIPE->autoflush(1); $pipe_is_open = 1; $current_command_name = $command; } # send the text print PIPE "$args{'MESSAGE'}"; if (not exists $args{'KEEP_OPEN'}) { close(PIPE) if $pipe_is_open; $pipe_is_open = 0; } } # # close_pipe_if_open -- used at the end of a script to close a pipe # opened by &pipe_it(). # # usage: &close_pipe_if_open(); # sub close_pipe_if_open { if ($pipe_is_open) { close(PIPE); } } } # # send_email -- send some mail using $MAILER. # # usage: &send_email($addresses_to_mail_to); # sub send_email { my $login = (getpwuid($<))[0]; my %args = ( 'ADDRESSES' => $login, 'SUBJECT' => 'Message from Swatch', @_ ); return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'}); my $addresses = $args{'ADDRESSES'}; $addresses =~ s/:/,/g; if ($MAILER eq '') { warn "ERROR: $0 cannot find a mail delivery program\n"; return; } open(MAIL, "| $MAILER $addresses") or warn "$0: cannot open pipe to $MAILER: $!\n" && return; print MAIL "To: $addresses\n"; print MAIL "Subject: $args{SUBJECT}\n\n"; print MAIL "$args{'MESSAGE'}\n"; close(MAIL); } # # write_message -- use $WRITE to send a message logged on users. # sub write_message { my %args = (@_); return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'}); if ($WRITE eq '') { warn "ERROR: $0 cannot find the write(1) program\n"; return; } if (exists($args{'USERS'})) { foreach my $user (split(/:/, $args{'USERS'})) { send_message_to_pipe(COMMAND => "$WRITE $user 2>/dev/null", MESSAGE => "$args{'MESSAGE'}\n"); } } } use File::Tail; my $Filename = '/var/log/ulog/syslogemu.log'; my $File = File::Tail->new(name=>$Filename, maxinterval => 1, interval => 1); if (not defined $File) { die "/usr/local/bin/swatch: cannot read input \"$Filename\": $!\n"; } LOOP: while (defined($_=$File->read)) { chomp; my $sanitized_ = $_; @_ = split; # quote all special shell chars $sanitized_ =~ s/([;&\(\)\|\^><\$`'\\])/\\$1/g; my @sanitized_ = split(/\s+/, $sanitized_); if (/INVALID|REPEATED|INCOMPLETE:LOGIN/) { echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", ); ring_bell('RINGS' => "3", ); next; } if (/(panic|halt)/) { echo('MESSAGE' => "$_", ); ring_bell(); next; } if (/Regents/) { echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", ); ring_bell(); next; } if (/ipfw:.*Deny ICMP:8.0/) { echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", ); exec_command('COMMAND' => "mpg123 -q /root/wavs/drip.wav &", ); next; } if (/ipfw:.*Deny TCP .*:6000/) { echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", ); exec_command('COMMAND' => "mpg123 -q /root/wavs/camera.wav &", ); next; } if (/ipfw:.*Deny UDP .*:513/) { echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", ); exec_command('COMMAND' => "mpg123 -q /root/wavs/flush.wav &", ); next; } if (/ipfw:.*Deny TCP .*:21/) { echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", ); exec_command('COMMAND' => "mpg123 -q /root/wavs/vault.wav &", ); next; } if (/PROTO=TCP .*DPT=80/) { echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", ); exec_command('COMMAND' => "mpg123 /root/wavs/tcp80.mp3 &", ); next; } if (/PROTO=TCP .*DPT=23/) { echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", ); exec_command('COMMAND' => "mpg123 /root/wavs/tcp23.mp3 &", ); next; } if (/UDP .*=1300007/) { echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", ); exec_command('COMMAND' => "mpg123 /root/wavs/udp137.mp3 &", ); next; } if (/PROTO=ICMP/) { echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", ); exec_command('COMMAND' => "mpg123 /root/wavs/ping.mp3 &", ); next; } if (/.*/) { echo('MESSAGE' => "$_", ); next; } }
#use strict;
#       @(#)    First Edit: Bas
#       @(#)    Last Edit: Fash

use POSIX ":sys_wait_h";
use vars qw(%Msg_Rec);

$SIG{'TERM'} = $SIG{'HUP'} = 'goodbye';
$SIG{'CHLD'} = 'IGNORE';

## Constants
my $BELL   = "";
my $MAILER = "/usr/sbin/sendmail";
my $WRITE  = "/usr/bin/write";
$/ = "
";

autoflush STDOUT;

sub goodbye {
  $| = 0;

  close_pipe_if_open();
  exit(0);
}

#
# in_range($range, $number) 
# returns 1 if $number is inside $range, 0 if not
#
sub in_range {
  my $range = shift;
  my $num = shift;

  foreach my $f (split(/,/, $range)) {
    if ($f =~ /-/) {
      my ($low,$high) = split(/-/, $f);
      return 1 if ($low <= $num and $num <= $high);
    } elsif ($f == $num) {
      return 1;
    }
  }
  return 0;
}

# 
# inside_time_window($days,$hours)
# returns 1 if inside window, 0 if outside window
#
sub inside_time_window {
  my $range = shift;
  my($days, $hours) = split(/:/, $range);

  my ($hr, $wday) = (localtime(time))[2,6];

  if (($days eq '*' or in_range($days, $wday))
      and ($hours eq '*' or in_range($hours, $hr))) {
    return 1;
  } else {
    return 0;
  }
}

  
print "\n*** swatch-3.0.1 (pid:6826) started at " . `/bin/date` . "\n";

  
use Date::Calc qw(:all);

sub parse_dot {
  my $message = shift;
  my $dot_loc = shift;
  my @dot = ();
  my @ranges = split(/:/, $dot_loc);

  foreach my $range (0..$#ranges) {
    if ($ranges[$range] != -1) {
      my ($begin, $end) = split(/-/, $ranges[$range]);
      $dot[$range] = substr($message, $begin, ($end - $begin + 1));
    }
  }

  return @dot;
}

my ($date_loc, $time_loc) = ("-1:0-2:4-5", "7-8:10-11:13-14");
my %months = (
              Jan => 1,
              Feb => 2,
              Mar => 3,
              Apr => 4,
              May => 5,
              Jun => 6,
              Jul => 7,
              Aug => 8,
              Sep => 9,
              Oct => 10,
              Nov => 11,
              Dec => 12
             );

# Returns an array of year, month, day, hours, minutes, and seconds.
#
sub YMDHMS {
  my $string = shift;
  my $year_today = (Today())[0];

  my ($y, $m, $d) = parse_dot($string, $date_loc);
  my ($hrs, $mins, $secs) = parse_dot($string, $time_loc);
  if (length($y) eq 0) { $y = (Today())[0] };

  return ($y, $months{$m}, $d, $hrs, $mins, $secs);
}

sub new_msg {
  my $use = shift;
  my $msg = shift;
  my $count = shift;
  my @delta = @_;
  my $delta;
  if ($delta[0] == 0) {
    $delta = sprintf("%d:%.2d:%.2d", $delta[1], $delta[2], $delta[3]);
  } else {
    $delta = sprintf("$delta[0] day%s %d:%.2d:%.2d", $delta[0] > 1 ? 's' : '',
                    $delta[1], $delta[2], $delta[3]);
  }
  if ($use eq 'regex') {
    return "$count $msg regular expressions in $delta";
  } else {
    return "$count in $delta: $msg";
  }
}

#
# Stores message information in 
#    $Msg_Rec = (
#      {<truncated message>|<pattern>} => {
#        dhms => [ array ], # days,hours,minutes,seconds
#        count => integer,

sub throttle {
  my %opts = (
              KEY       => $_,
              CUT_MARKS => [ "0:16" ], # not used yet
              USE       => 'message',
              @_
             );

  my $msg = $opts{'KEY'};
  my $use = $opts{'USE'};
  my @ymdhms = YMDHMS($msg);
  my $key;
  my @min_dhms_delta = split(/(\s+|:)/, $opts{'MIN_DELTA'});

  foreach my $i (0..$#min_dhms_delta) {
    # strip out unwanted element
    splice (@min_dhms_delta, $i, 1) if ($min_dhms_delta[$i] eq ":");
  }

  if ($use eq 'regex') {
    $key = $opts{'REGEX'};
  } else {
    $key = substr($msg, 16);
    $key =~ s/\[\d+\]/[PID]/;
  }

  while ($#min_dhms_delta < 3) {
    unshift(@min_dhms_delta, 0); # make sure that the dhms array is full
  }

  if (exists $Msg_Rec{$key} and defined $Msg_Rec{$key}->{ymdhms}) {
    my $passed = 1;
    $Msg_Rec{$key}->{count}++;
    if ($ymdhms[1] > $Msg_Rec{$key}->{ymdhms}[1]) { $ymdhms[0]--; }

    my @delta_dhms = Delta_DHMS(@{$Msg_Rec{$key}->{ymdhms}}, @ymdhms);
    foreach my $i (0..$#min_dhms_delta) {
      $passed = 0 if ($delta_dhms[$i] < $min_dhms_delta[$i]);
      last unless ($delta_dhms[$i] == $min_dhms_delta[$i]);
    }    
    if ($passed) {
      my $new = '';
      $new = new_msg($use, $key, $Msg_Rec{$key}->{count}, @delta_dhms);
      $Msg_Rec{$key}->{ymdhms} = [ @ymdhms ];
      $Msg_Rec{$key}->{count} = 1;
      return $new;
    } else {
      return '';
    }
  } else {
    my $rec;
    $rec->{ymdhms} = [ @ymdhms ];
    $Msg_Rec{$key} = $rec;
    return $msg;
  }
}


##
## ACTION SUBROUTINES
##

my %text_modes = (
  "black"       => "\033[30;1m",
  "red"         => "\033[31;1m",
  "green"       => "\033[32;1m",
  "yellow"      => "\033[33;1m",
  "blue"        => "\033[34;1m",
  "magenta"     => "\033[35;1m",
  "cyan"        => "\033[36;1m",
  "white"       => "\033[37;1m",
  "black_h"     => "\033[40;1m",
  "red_h"       => "\033[41;1m",
  "green_h"     => "\033[42;1m",
  "yellow_h"    => "\033[43;1m",
  "blue_h"      => "\033[44;1m",
  "magenta_h"   => "\033[45;1m",
  "cyan_h"      => "\033[46;1m",
  "white_h"     => "\033[47;1m",
  "bold"        => "\033[1m",
  "blink"       => "\033[5m",
  "inverse"     => "\033[7m",
  "normal"      => "\033[0m",
  "underscore"  => "\033[4m",
);
  
sub echo {
  my %args = (
              'MODES' => [ 'normal' ],
              @_
             );

  return if (exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'}));
  
  if ($args{'MODES'}[0] eq 'random') {
    my @mode_names = keys %text_modes;
    print $text_modes{$mode_names[rand $#mode_names]};
  } else {
    foreach my $mode (@{$args{'MODES'}}) {
      print $text_modes{$mode};
    }
  }
  print $args{'MESSAGE'};
  print $text_modes{'normal'};
  print "\n";
}

#
# ring_bell(args) -- send x number of control-G characters to the output.
#
sub ring_bell {
  my %args = (
              'RINGS' => 1,
              @_
             );
  my $sun_terminal = (`uname -s` eq 'SunOS\n');
  
  return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'});
  
  my $bells = $args{'RINGS'};
  for ( ; $bells > 0 ; $bells-- ) {
    print $BELL;
    sleep 1 if $sun_terminal; # SunOS needed this. Not sure about Solaris though
  }
}

#
# exec_command(args) -- fork and execute a command
#
sub exec_command {
  my %args = (@_);
  my $exec_pid;
  my $command;

  if (exists $args{'COMMAND'}) {
    $command = $args{'COMMAND'};
  } else {
    warn "$0: No command was specified in exec action.\n";
    return;
  }

  return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'});

 EXECFORK: {
    if ($exec_pid = fork) {
      waitpid(-1, WNOHANG);
      return;
    } elsif (defined $exec_pid) {
      exec($command);
      } elsif ($! =~ /No more processes/) {
        # EAGAIN, supposedly recoverable fork error
        sleep 5;
        redo EXECFORK;
      } else {
        warn "$0: Can't fork to exec $command: $!\n";
      }
  }
  return;
}


{
  my $pipe_is_open;
  my $current_command_name;
  #
  # send_message_to_pipe -- send text to a pipe.
  #
  # usage: &send_message_to_pipe($program_to_pipe_to_including_the_vertical_bar_symbol,
  #               $message_to_send_to_the_pipe);
  # 
  
  sub send_message_to_pipe {
    my %args = (@_);
    my $command;

    if (exists $args{'COMMAND'}) {
      $command = $args{'COMMAND'};
    } else {
      warn "$0: No command was specified in pipe action.\n";
      return;
    }

    return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'});

    # open a new pipe if necessary
    if ( !$pipe_is_open or $current_command_name ne $command ) {
      # first close an open pipe
      close(PIPE) if $pipe_is_open;
      $pipe_is_open = 0;
      open(PIPE, "| $command") 
        or warn "$0: cannot open pipe to $command: $!\n" && return;
      PIPE->autoflush(1);
      $pipe_is_open = 1;
      $current_command_name = $command;
    }
    # send the text
    print PIPE "$args{'MESSAGE'}";

    if (not exists $args{'KEEP_OPEN'}) {
      close(PIPE) if $pipe_is_open;
      $pipe_is_open = 0;
    }
  }

  #
  # close_pipe_if_open -- used at the end of a script to close a pipe
  #     opened by &pipe_it().
  #
  # usage: &close_pipe_if_open();
  #
  sub close_pipe_if_open {
    if ($pipe_is_open) {
      close(PIPE);
    }
  }
}


#
# send_email -- send some mail using $MAILER.
#
# usage: &send_email($addresses_to_mail_to);
#
sub send_email {
  my $login = (getpwuid($<))[0];
  my %args = (
              'ADDRESSES' => $login,
              'SUBJECT' => 'Message from Swatch',
              @_
             );

  return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'});

  my $addresses = $args{'ADDRESSES'};
  $addresses =~ s/:/,/g;

  if ($MAILER eq '') {
    warn "ERROR: $0 cannot find a mail delivery program\n";
    return;
  }

  open(MAIL, "| $MAILER $addresses")
    or warn "$0: cannot open pipe to $MAILER: $!\n" && return;

  print MAIL "To: $addresses\n";
  print MAIL "Subject: $args{SUBJECT}\n\n";
  print MAIL "$args{'MESSAGE'}\n";
  close(MAIL);
}


#
# write_message -- use $WRITE to send a message logged on users.
#
sub write_message {
  my %args = (@_);

  return if exists($args{'WHEN'}) and not inside_time_window($args{'WHEN'});

  if ($WRITE eq '') {
    warn "ERROR: $0 cannot find the write(1) program\n";
    return;
  }

  if (exists($args{'USERS'})) {
    foreach my $user (split(/:/, $args{'USERS'})) {
      send_message_to_pipe(COMMAND => "$WRITE $user 2>/dev/null", 
                           MESSAGE => "$args{'MESSAGE'}\n");
    }
  }
}

use File::Tail;
my $Filename = '/var/log/ulog/syslogemu.log';
my $File = File::Tail->new(name=>$Filename, maxinterval => 1, interval => 1);
if (not defined $File) {
    die "/usr/local/bin/swatch: cannot read input \"$Filename\": $!\n";
}

LOOP: while (defined($_=$File->read)) {

    chomp;
    my $sanitized_ = $_;
    @_ = split;
    
    # quote all special shell chars
    $sanitized_ =~ s/([;&\(\)\|\^><\$`'\\])/\\$1/g;
    my @sanitized_ = split(/\s+/, $sanitized_);
    if (/INVALID|REPEATED|INCOMPLETE:LOGIN/) {
        echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
        ring_bell('RINGS' => "3", );
        next;
    }

    if (/(panic|halt)/) {
        echo('MESSAGE' => "$_", );
        ring_bell();
        next;
    }

    if (/Regents/) {
        echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
        ring_bell();
        next;
    }

    if (/ipfw:.*Deny ICMP:8.0/) {
        echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
        exec_command('COMMAND' => "mpg123 -q /root/wavs/drip.wav &", );
        next;
    }

    if (/ipfw:.*Deny TCP .*:6000/) {
        echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
        exec_command('COMMAND' => "mpg123 -q /root/wavs/camera.wav &", );
        next;
    }

    if (/ipfw:.*Deny UDP .*:513/) {
        echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
        exec_command('COMMAND' => "mpg123 -q /root/wavs/flush.wav &", );
        next;
    }

    if (/ipfw:.*Deny TCP .*:21/) {
        echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
        exec_command('COMMAND' => "mpg123 -q /root/wavs/vault.wav &", );
        next;
    }

    if (/PROTO=TCP .*DPT=80/) {
        echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
        exec_command('COMMAND' => "mpg123 /root/wavs/tcp80.mp3 &", );
        next;
    }
    if (/PROTO=TCP .*DPT=23/) {
        echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
        exec_command('COMMAND' => "mpg123 /root/wavs/tcp23.mp3 &", );
        next;
    }

    if (/UDP .*=1300007/) {
        echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
        exec_command('COMMAND' => "mpg123 /root/wavs/udp137.mp3 &", );
        next;
    }

    if (/PROTO=ICMP/) {
        echo('MODES' => [ "bold", ], 'MESSAGE' => "$_", );
        exec_command('COMMAND' => "mpg123 /root/wavs/ping.mp3 &", );
        next;
    }

    if (/.*/) {
        echo('MESSAGE' => "$_", );
        next;
    }

}

Spread the love