#!/usr/freeware/bin/perl # perldoc fw_securitycam.pl or scroll to bottom my $VERSION = "1.0" ; my $host = `hostname` ; chomp ($host) ; my $date = localtime; my $appname = ($0 =~ m/\/([0-9A-Za-z_.-]+)$/g)[-1]; ## # SET THESE AS NECESSARY!!!! # I run this script as a non-root user, so ensure you set-up $sl_dir as # writable as your user. # # mailer settings # my $to = "rob\@dsvr.net" ; my $msg = "Security Camera Alert $host $date\n\n" ; my $subj = "Security Camera Alert $host $date" ; # # securitylite settings, -c and -p can be passed from the command line (see -h) # my $securitylite = "/usr/freeware/bin/securitylite" ; my $sl_dir = "/mnt/more/security/" ; #must have trailing slash my $sl_log = ">> $ENV{HOME}/securitylitelog" ; my $sl_change = "-c 10" ; my $sl_pixel = "-p 25" ; my $sl_extraopts = "-v 1 -n -d $sl_dir $sl_log" ; my $dumpster = "$ENV{HOME}/dumpster" ; my $hup_time = 3600 ; # default 1 hour (ish) my $exec_time = 12 ; # iterations of main loop - ie total runtime 12 * 3600secs my $extra_tests = 1 ; # set to zero to stop performing extra tests, which # might be sensible if you think they wont complete # in less then 1 second, or you think df is a bad thing my $auto_cleanup = 1 ; # automatically purge old security files (you may want # to manage this yourself, change to 0) my $auto_cleanup_days = 2 ; # files this old are moved to dumpster, if # $auto_cleanup = 1 my $quiet = 0 ; # see -q my $sendmail = "/usr/lib/sendmail -oi -t -f $to" ; # smtp relays worth their # salt check From header # is resolvable which it # might not be if your # machine is behind a router # so specify -f $| = 1; # autoflush on ## # Parse options # with the absence of Getopt::Long we do it the # manual way - probably unsafe, but this isn't # a 100% robust script # if ( @ARGV ) { my %opts ; my $prior_opt ; foreach ( @ARGV ) { if ( /^-/ ) { $opts{$_} ++ ; # simply set to something $prior_opt = $_; } else { $opts{$prior_opt} = $_ ; } } if ( exists ($opts{-q}) ) { $quiet ++ ; } if ( exists ($opts{-h}) ) { usage () ; } if ( exists ($opts{-p}) ) { $sl_pixel = "-p $opts{-p}" ; } if ( exists ($opts{-k}) ) { killmyself() ; # kill that errant process exit 0 ; } if ( exists ($opts{-c}) ) { $sl_change = "-c $opts{-c}" ; } if ( exists ($opts{-i}) ) { $exec_time = $opts{-i} ; } if ( exists ($opts{-s}) ) { $hup_time = $opts{-s} ; } } killmyself() ; # autoclean up prior running processes ... saves addition of a -k for crons print ("$appname security monitor ver $VERSION starting up ...\n") ; $securitylite .= " $sl_change $sl_pixel $sl_extraopts" ; printf ("\n [ exec: $securitylite ]\n [ time: $exec_time iterations of $hup_time seconds; shutdown in %.2f hours ]\n", (($exec_time * $hup_time) / (60 * 60))) unless $quiet ; for ( $exec_i = 0 ; $exec_i < $exec_time ; $exec_i ++ ) { my $pid ; if ( $pid = fork ) { my $then ; my $now ; my $sent_mail = 0 ; # send once per iteration for ( $i = 0 ; $i < $hup_time ; $i ++ ) { my %warn ; if ( $sent_mail == 0 ) { # if already sent email on this run, don't # bother checking again (otherwise we'd be spewing # emails ( $then, $now ) = watch_files ( $then, $now, $sl_dir ) ; foreach my $f ( keys %$now ) { if ( ( $now->{$f} ne $then->{$f} ) && ( $then->{$f} ne "" ) ) { $warn{$f} ++ } } if ( %warn ) { $send_msg = $msg ; foreach my $f ( keys %warn ) { $send_msg .= "CHANGED: $warn{$f} $then->{$f} to $now->{$f}\n" ; $send_msg .= "/usr/freeware/bin/securityviewer $f" ; } send_mail ( $sendmail, $to, $subj, $send_msg ) ; $sent_mail ++ ; } } sleep( 1 ) ; # regiment checking to once a second # so we don't thrash the system } print (" [ child: stopping securitylite in child $exec_i ]\n" ) unless $quiet ; kill 9, $pid ; # kill $securitylite waitpid ( $pid, 0 ) ; # perform some other nice tests, before starting again other_nice_tests ( $sendmail, $to, $subj, $sl_dir ) unless ! $extra_tests; auto_cleanup ( $sendmail, $to, $subj, $sl_dir, $dumpster, $auto_cleanup_days ) unless ! $auto_cleanup ; } else { die "cannot fork: $!" unless defined $pid ; print (" [ child: starting securitylite in child $exec_i ]\n" ) unless $quiet ; exec ($securitylite) ; } } print ("\n... $appname shutting down\n") ; sub watch_files { my $then = shift ; my $now = shift ; my $sl_dir = shift ; # find files in $sl_dir and stat them, building two # hashes which can be compared my @watch = `find $sl_dir -type f` ; foreach my $f ( @watch ) { chomp ($f) ; my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($f); $then->{$f} = $now->{$f} ; $now->{$f} = $size ; } return ( $then, $now ) ; } sub send_mail { my $sendmail = shift ; my $to = shift ; my $subj = shift ; my $msg = shift ; # simple mail sender, using sendmail print ("\t [ sending mail $subj to $to ]\n" ) ; open( SENDMAIL, "| $sendmail" ); print SENDMAIL "Subject: $subj\n"; print SENDMAIL "To: $to\n"; print SENDMAIL "\n$msg\n"; close(SENDMAIL); } sub other_nice_tests { my $sendmail = shift ; my $to = shift ; my $subj = shift ; my $sl_dir = shift ; # do clever tests, such as df to ensure we have free space my $msg = "" ; my $min_usage = 85 ; # percent my $cmd = "df $sl_dir" ; my @df = `$cmd` ; # print ("\tops:\n checking $cmd\n") ; foreach my $df_line ( @df ) { chomp ( $df_line ) ; my @df_split = split ( /\s+/, $df_line ) ; # field 5 is percent # field 6 is path if ( $sl_dir =~ /$df_split[6]/ ) { if ( $df_split[5] > $min_usage ) { $msg .= "WARNING: found mount point $df_split[0] is $df_split[5] % full" ; } } } if ( $msg ne "" ) { send_mail ( $sendmail, $to, "WARN: ".$subj, $msg ) ; } } sub auto_cleanup { my $sendmail = shift ; my $to = shift ; my $subj = shift ; my $sl_dir = shift ; my $dumpster = shift ; my $days = shift ; # automatic cleanup of old security files. # choose files older than specified time # and move them to Dumpster (or remove) # With the absence of Date::Manip we can use mtime # in find my $msg = "" ; my $cmd = "find $sl_dir -type f -mtime +$days" ; my @files = `$cmd` ; if ( @files > 0 ) { print (" [ autocleanup: ... ") unless $quiet ; foreach my $f ( @files ) { print ( "\n\tcleaning up $f" ) unless $quiet ; $msg .= "CLEANING file $f\n" ; chomp ( $f ) ; my @cmd_args = ("mv", $f, $dumpster) ; system(@cmd_args) == 0 or $msg .= "FAILED @cmd_args failed: $? $!\n" ; } print (" ... done ]\n") unless $quiet ; $msg .= "\nCheck the dumpster out\n" ; send_mail ( $sendmail, $to, "CLEANUP: $subj", $msg ) ; } } sub usage { # Getopt::Long would be sooo useful here warn < 0 ) { print ("killing securitylite ... ") ; `killall securitylite` ; # sick, because i duno enough about sig handling childs print ("cleaned up from previous run\n\n") ; } } =head1 NAME fw_securitycam.pl - A securitylite monitoring script =head1 SYNOPSYS fw_securitycam.pl [-h] [-p] [-c] [-i] [-s] [-k] [-q] =head1 DESCRIPTION A simple script to invoke securitylite, monitor it's output directory and email you when something happens. ./fw_securitycam.pl -h The script passes -p and -c options directly into securitylite to manage sensitivity - see securitylite -h for further info. -s and -i options dictate how often the securitylite system is tested for and restarted. We restart periodically to manage the output files in cases where lots of movement causes large files. YMMV adjust the var's to give the most acceptable results. I have found running it from cron to be best, giving full cover with preprogrammed, variable sensitivity. See the examples section for more information. As an update, the script will now detect and kill a previous run of itself. Why: I wrote this script so that my Octane can watch the back of my house while I'm at work. =head1 TODO (if ever) More real-world testing/feedback Fix killing child process when -k kills parent =head1 KNOWN ISSUES The 'time' features are deliberately loose insofar as the main looping should be 1 second long on anything but the slowest box (untested). This is because we sleep for 1 second during normal looping, but then adhoc checks and cleanups such as df and moving old files to the dumpster could take an unknown amount of time - especially if they are large files. It's for this reason we kill previous runs when we start up, to avoid clashing. There are probably unsafe var usage but this isn't a script for an unsafe multi user environment. There are probably some cases where errors aren't graciously trapped, caught or reported. The -k option is awful. Rather than spew emails out everytime movement was detected, email only once per iteration. Cleanup ops and potential emails are still once per sec =head1 REQUIRES fw_perl fw_securitylite working sendmail - see configmail setup =head1 EXAMPLES run once, 12 iterations of 1 hour (3600 seconds) with average sensitivity ./fw_securitycam.pl -p 25 -c 40 -i 12 -s 3600 run once for several hours from init script with adjustable sensitivity ./fw_securitycam.pl -p 50 -c 30 -i 8 -s 3600 ; ./fw_securitycam.pl -c 10 -p 25 -i 16 -s 3600 run it from a cron every day, all day (first, low sensitivity 4 x 2 hours, second higher sensitivity 16 x 1 hour) : 14 08 * * * ./fw_securitycam.pl -p 50 -c 30 -i 4 -s 7200 ; ./fw_securitycam.pl -p 20 -c 10 -i 16 -s 3600 ; =head1 AUTHOR Rob Fielding (F) =head1 COPYRIGHT Copyright (C) 2003 Rob Fielding This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. =head1 VERSION 1.0 =cut 1;