#!/usr/bin/perl
###################################################################
# jcf_tripwire
#
# Chris Farris <chrisf@comtrends.net>
#
# This script will take an MD5 hash of each file recursivly in a 
# directory and store it in a flat file.
# This script can then be run to compare the current version's hash
# with the stored hash and will alert the user of any changes.
#
# Usage: jcf_tripwire.pl --build | --check <dbname> <dir>
#
# Copyright (C) 2000 Chris Farris
#
# 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.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
###################################################################

use Digest::MD5;

sub debug {
  my ($mesg) = @_;
#  print $mesg;
}

if (@ARGV != 3) {
  print "Usage: jcf_tripwire.pl --build | --check <dbname> <dir>\n";
  exit 0;
}

if ($ARGV[0] eq "--build") {
  build_md5_db($ARGV[1], $ARGV[2]);
} elsif ($ARGV[0] eq "--check") {
  check_md5_db($ARGV[1], $ARGV[2]);
} else {
  print "Usage: jcf_tripwire.pl --build | --check <dbname> <dir>\n";
  exit 0;
}

exit 0;

########################## DB Build Functions #######################
sub check_md5_db {
  my ($dbname, $dir) = @_;

  # Sanity Checks
  if (! -d $dir) {
    die "$dir is not a directory!";
  }

  # Open DB for reading.
  if ((! -e $dbname) || ((-e $dbname) && (-r $dbname))) {
    open (DB, "< $dbname") || die "Cant open file: $dbname: $!";
  } else {
    die "file: $dbname exists and is not readable";
  }

  while (<DB>) {
    chomp;
    if (!/^::/) {
      my ($hash, $file) = split('\0');
debug "file: $file\n";
      open(FILE, $file) or die "Can't open '$file': $!";
      binmode(FILE);
      my $newhash = Digest::MD5->new->addfile(*FILE)->hexdigest;
      if ($newhash ne $hash) {
        print "Danger, Danger Will Robinson. File $file has changed\n";
      }
      close FILE;
    }
  }

  close DB;
} # end check_md5_db


########################## DB Build Functions #######################
sub build_md5_db {
  my ($dbname, $dir) = @_;

  # Sanity Checks
  if (! -d $dir) {
    die "$dir is not a directory!";
  }

  # Open DB for writing.
  if ((! -e $dbname) || ((-e $dbname) && (-w $dbname))) {
    open (DB, "> $dbname") || die "Cant open file: $dbname: $!";
  } else {
    die "file: $dbname exists and is not writable";
  }

  process_dir($dir, "");

  close DB;
} # end build_md5_db

sub process_dir {
  my ($thisdir, $path) = @_;
  my $file;
  my @dir;	# define list of dirs we find.
  $path .= "$thisdir/";

  if ((!-d $thisdir) || (!-x $thisdir) || (!-r $thisdir)) {
    print "Dir $thisdir cannot be opened\n";
    return();
  }

  debug "Path: $path\n";
  chdir($thisdir) || die "Cant chdir: $thisdir: $!";
  while (defined($file = <*>)) {
    if (-f $file) {
      debug "$file is a file\n";
      if (-r $file) {
        open(FILE, $file) or die "Can't open '$file': $!";
        binmode(FILE);
        print DB Digest::MD5->new->addfile(*FILE)->hexdigest, "\0$path$file\n";
      } else {
        print DB "::$path$file cannot be read\n";
      }
    }
    if ((-d $file) && (!-l $file)) {
      debug "$file is a dir\n";
      @dir = (@dir, $file);
    }
    if (-l $file) {
      print DB "::$path$file is a symlink\n";
    }
    if (  (-S $file) 
       || (-p $file) 
       || (-b $file) 
       || (-c $file) ) {
      print DB "::$path$file is a special file/device\n";
    }
  } # end while

  my $dir;
  debug "\n\nDirectories:\n";
  foreach $dir (@dir) {
    debug "$dir\n";
    process_dir($dir, $path);
  }
  chdir("..");
} # end process_dir
