File: file_change.monitor

package info (click to toggle)
mon 1.2.0-6
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 1,832 kB
  • sloc: perl: 15,847; ansic: 774; sh: 330; makefile: 46
file content (294 lines) | stat: -rwxr-xr-x 8,000 bytes parent folder | download | duplicates (9)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#!/usr/bin/perl
#
# mon monitor to watch for file changes
#
# Jon Meek - April 2001 - <meekj@ieee.org>
#
$RCSid = q{$Id: file_change.monitor,v 1.2 2004/11/15 14:45:19 vitroth Exp $};
#
# Copyright (C) 2001 Jon Meek
#
#    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
#
=head1 NAME

file_change.monitor

=head1 SYNOPSIS

I<file_change.monitor -d -r -b base_directory file1 file2 ... fileN>

=head1 DESCRIPTION

B<file_change.monitor> will watch specified files in a directory and
trigger an alert when any monitored file changes, or is missing. File
changes can optionally be logged using RCS.

This monitor was designed to monitor B<copies> of the actual files.
The files will often be copies of files mirrored from remote systems
such as firewalls, routers, mail gateways, etc.

File changes are detected using MD5 checksums. Current file
information is stored in the mon state directory in a file that
corresponds to the base directory (with '/' replaced by '_'). The
file contains a time stamp (in UNIX seconds), the MD5 checksum (in
hexadecimal) and the name of the file. This file is generated
automatically if it does not already exist.

=head1 OPTIONS

=over 5

=item B<-b base_directory>

The base directory for the files. All filenames are relative to this
directory.

=item B<-r>

Log file changes using RCS. Each directory containing files to be
checked should have an RCS subdirectory. The mon user needs access
(possibly both file permissions and RCS authorization) to the RCS
files. B<file_change.monitor> will leave the file locked so that it
will be able to check in the next revision.

This option should NOT be used when the original files are being
monitored directly since there will be permission issues and because
the current checkin process re-writes the original file (via I<ci -l>).

=item B<-d>

Provide debugging information.

=back

=head1 EXAMPLE MON CONFIGURATION

 hostgroup fw_configs pr-ifw/pr-ifw1.html pt-ifw/pt-ifw.html
                      rd-ifw/rd-ifw1.html rd-ifw/rd-ifw2.html

 watch fw_configs
       service file_change
       interval 5m
       monitor file_change.monitor -r -b /home/httpd/html/sys_status
       period wd {Sun-Sat}
       alert mail.alert meekj@ieee.org

=head1 HANDLING RCS PERMISSION ISSUES

Files must be initially checked-in before the monitor can take over the task.

 sudo -u netmon ci -l file.cfg

If file_change.monitor ever runs as a different user, such as root,
there will be problems. To correct the situation, perform the
following on each file:

 sudo ci -u -mfile_change.monitor file.cfg
 sudo -u netmon co -l file.cfg

=head1 BUGS

B<file_change.monitor> does not currently recognize any file locking
mechanism. There could be a problem if a file is being modified when
the monitor runs. Using a program like rsync to copy files to the
monitor directory should nearly eliminate this problem since copies
are usually made to a temporary file and the rename is an atomic
operation.

=head1 AUTHOR

Jon Meek <meekj@ieee.org>

=cut

use Getopt::Long;
use Digest::MD5;

$TimeNow = time;

GetOptions(
	   "b=s" => \$BaseDir,
           "d" => \$Debug,
           "r" => \$RCS,
 );

$StateFile = $BaseDir;
$StateFile =~ s/\//_/g; # Change / to _ to make filename
$StateFile = "$ENV{MON_STATEDIR}/$StateFile";

$CI = 'ci';          # Assume that RCS's ci is in the path

print "Will use RCS: $RCS\n" if $Debug;

#
# Read the previous checksums if the State File exists
#
if (-e $StateFile) {
  print "Existing $StateFile\n" if $Debug;
  open(F, $StateFile);
  while ($in = <F>) {
    ($t, $md5, $f) = split(' ', $in);
    if ($md5 eq 'LastCheck') { # May add a LastCheck time later
      $LastCheck = $md5;
    } else {
      $PrevChangeTime{$f} = $t;
      $PrevMD5{$f} = $md5;
    }
  }
  close F;
  $StateFileExists = 1; # Remember that there is a existing State File
} else {
  $StateFileExists = 0; # or not
  print "No Existing $StateFile\n" if $Debug;
}

$NewFile = 0;
@Failures = ();

foreach $f (@ARGV) {
  if ($f =~ /\*/) {
    push(@Files, glob("$BaseDir/$f"));
  } else {
    push(@Files, "$BaseDir/$f");
  }
}

#@Files = @ARGV; # File names are left on the command line after Getopt

$md5 = new Digest::MD5;

foreach $f (@Files) {      # Check each file

#  if (defined $BaseDir) {
#    $rdfile = "$BaseDir/$f";
#  } else {
#    $rdfile = "$f";
#  }

#  if (open(F, $rdfile)) {
  if (open(F, $f)) {
    seek(F, 0, 0);        # Compute MD5 checksum
    $md5->reset();
    $md5->addfile(F);
    $fileMD5 = $md5->hexdigest();
    close F;

    print "File: $f\n NewMD5:      $fileMD5\n PreviousMD5: $PrevMD5{$f}\n" if $Debug;

    $CurrentMD5{$f} = $fileMD5;

    if (exists $PrevMD5{$f}) {
      if ($CurrentMD5{$f} ne $PrevMD5{$f}) {
	push(@Failures, $f);
	$CurrentChangeTime{$f} = $TimeNow;
	$dTime = $TimeNow - $PrevChangeTime{$f};
	$fmtTimeDiff = &TimeDisplayScale($dTime);
	$ResultString{$f} = "File changed, previous change $fmtTimeDiff";
	print " File: $f changed, previous change $dTime s\n" if $Debug;
	if ($RCS) { # Check new version of file into RCS
	  $Command = "$CI -l -mfile_change.monitor $f 2>&1"; # Save STDOUT & STDIN from ci
	  print " ci command: $Command\n" if $Debug;
	  $OpenResult = open(CI, "$Command |") or warn "Can't fork $Command: $!";
	  print " command open result: $CmdResult\n" if $Debug;
	  while ($in = <CI>) {
	    $CiOutput .= $in;
	  }
	  close CI or warn "Can't close ci: $!/$?";;
	  print " ci output: $CiOutput\n" if $Debug;
	}
      } else {
	$CurrentChangeTime{$f} = $PrevChangeTime{$f};
      }
    } else { # Here if no state file entry exists for this $f
      $CurrentChangeTime{$f} = $TimeNow;
      $NewFile++;
    }

  } else { # The file does not exist, report as a failure
    push(@Failures, $f);
    $ResultString{$f} = 'File Does Not Exist';
  }
}
print "\n" if $Debug;

#
# Report results, or keep quiet if all is well
#
print "New: $NewFile   Fail: @Failures $StateFile\n" if $Debug;

if ($NewFile || @Failures) { # Need to write new state file
  print "Writing a new $StateFile\n" if $Debug;
  open(F, ">$StateFile");
  foreach $f (sort keys %CurrentMD5) {
    print F "$CurrentChangeTime{$f} $CurrentMD5{$f} $f\n";
  }
  close F;
}

if (@Failures == 0) { # No files changed
    exit 0;
}

#
# Handle file change notification
#
print "@Failures\n";

foreach $f (sort @Failures) {
    print "$f: $ResultString{$f}\n\n";
}
print "\n";

print "$CiOutput\n";

#print "$CiOutput\n" if $Debug;

exit 1; # Indicate failures

sub TimeDisplayScale { # Scale the time from seconds to minutes/hours/days
  my($current_time, $past_time) = @_;
  my($dt, $ret, $idt, $dts);

  $dt = $current_time - $past_time;
  $dts = $dt;
  if ($dt < 60) { # Arbitrary seconds/minutes boundry
    $ret = "$dt seconds";
  } else {
    $dt = $dt / 60;
    $idt = int ($dt + 0.5);
    $ret = sprintf("%d minutes (%d s)", $idt, $dts);
  }

  if ($dt > 120) { # Arbitrary minutes/hours boundry
    $dt = $dt / 60;
    $ret = sprintf("%0.1f hours (%d s)", $dt, $dts);
  }

  if ($dt > 48) { # Arbitrary hours/day boundry
    $dt = $dt / 24;
    $ret = sprintf("%0.1f days (%d s)", $dt, $dts);
  }

  return $ret;
}

__END__

file_change.monitor -d -b /home/httpd/html/sys_status pr-ifw/pr-ifw1.html pt-ifw/pt-ifw.html

~/lab/mon/file_change.monitor -r -d -b /home/httpd/html/sys_status rd-ifw/rd-ifw2.html