Perl - SysGuard

I had a problem with web crackers changing some of my pages, adding some nasty JavaScript at the bottom. This script I wrote checks a database defined list of directories and files for various types of change. It can detect changes like file size, inode change, owner, group owner, permissions, etc. If the scan is running on a directory, it detects for any missing / added files. It is to be run from the bash shell, or from CRON.

Running it with result in this help screen:

1
2
3
4
5
6
7
8
9
10
SysGuard - Bray Almini 2009-06-28.
Keeps an eye on files and folders, detecting new files and changes.

usage: ./sysguard.pl [funtion] [file path]

Functions:
--scan Check all files and folders in DB for changes
--update Update data on old record, or create a new record
--list Display all files and folders being watched
--clear Delete a record from the DB

The MySQL table setup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CREATE TABLE IF NOT EXISTS `sysguard_table` (
`dev` int(12) NOT NULL,
`ino` int(12) NOT NULL,
`mode` int(12) NOT NULL,
`nlink` int(12) NOT NULL,
`uid` int(12) NOT NULL,
`gid` int(12) NOT NULL,
`rdev` int(12) NOT NULL,
`size` int(12) NOT NULL,
`atime` int(12) NOT NULL,
`mtime` int(12) NOT NULL,
`ctime` int(12) NOT NULL,
`blksize` int(12) NOT NULL,
`blocks` int(12) NOT NULL,
`file` varchar(256) NOT NULL,
`stamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
`ls` text,
PRIMARY KEY (`file`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

Perl code:

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
#!/usr/bin/perl

#########################
# Settings
$cfg->{'sqlserv'} = 'localhost';
$cfg->{'sqluser'} = 'user';
$cfg->{'sqlpass'} = 'password';
$cfg->{'sqlbase'} = 'database';
$cfg->{'sqltabl'} = 'sysguard_table';

$cfg->{'verbose'} = 0;
#$cfg->{'reallydb'} = 1;
$cfg->{'thisfile'} = "/path/to/this/file/sysguard.pl"

#########################
# Setup
use DBI;

$file = $ARGV[1]; # MAKE THIS CLEAN!

our @DEFS = (
"device number of filesystem",
"inode",
"type or permissions",
"number of (hard) links to the file",
"user owner",
"group owner",
"the device identifier",
"total size",
"last access time",
"last modify time",
"inode change",
"preferred block size",
"actual number of blocks allocated"
);

if ($ARGV[0] eq '--scan'){
$dbh = dbconnect();
my $sth_inbox = $dbh->prepare("SELECT * FROM `$cfg->{'sqltabl'}` where 1");
$sth_inbox->execute();
while (my $ref = $sth_inbox->fetchrow_hashref()) {
push(@listok,$ref);
}
foreach my $current(@listok){
my $changes=0;
@is = getinfo($current->{'file'});
@was = (
$current->{'dev'},$current->{'ino'},$current->{'mode'},$current->{'nlink'},
$current->{'uid'},$current->{'gid'},$current->{'rdev'},$current->{'size'},
$current->{'atime'},$current->{'mtime'},$current->{'ctime'},$current->{'blksize'},
$current->{'blocks'}
);
for($i = 0; $i < 13; $i++) {
next if ($i == "8");
next if ($i == "9");

if ($was[$i] != $is[$i]){
$changes=1;
print "$DEFS[$i] changed on file $current->{'file'}\n";
print "$was[$i]\t!=\t$is[$i]\n" if ($cfg->{'verbose'});
}else{
print "$was[$i]\t==\t$is[$i]\n" if ($cfg->{'verbose'});
}
}

# if it's a directory, also check what files it for new / missing files
$thefirst = substr($is[2], 0, 1);
if ($thefirst == '1'){
$ll = `ls -a1 $current->{'file'}`;
@now_ll = split(/\n/, $ll);
@was_ll = split(/\n/, $current->{'ls'});
my @ll_new;
my @ll_rem;
foreach $item (@now_ll){
push(@ll_new, $item) unless grep(/^$item$/, @was_ll);
}
foreach $item (@was_ll){
push(@ll_rem, $item) unless grep(/^$item$/, @now_ll);
}
if (@ll_rem){
$changes=1;
print "Missing files in $current->{'file'}:";
print "\n";
foreach my $mis_ll1(@ll_rem){
print "\t $mis_ll1\n";
}
}
if (@ll_new){
$changes=1;
print "New files in $current->{'file'}:";
print "\n";
foreach my $add_ll1(@ll_new){
print "\t- $add_ll1\n";
}
}
}
if ($changes){
exec("$cfg->{'thisfile'} --update $current->{'file'}");
$changes=0;
}
}
$dbh->disconnect() if ($dbh);
exit;
}

if ($ARGV[0] eq '--update'){
if (-e '$file') {
print "ERROR: File $file does not exist.\n";
die;
}
$dbh = dbconnect();
@is = getinfo($file);
# if it's a directory, also check what files it for new / missing files
$thefirst = substr($is[2], 0, 1);
if ($thefirst == '1'){
$ll = `ls -a1 $file`;
}

$dbh->do("INSERT INTO `$cfg->{'sqltabl'}`
(`dev`,`ino`,`mode`,`nlink`,`uid`,`gid`,`rdev`,`size`,
`atime`,`mtime`,`ctime`,`blksize`,`blocks`,`file`,`stamp`,`ls`)
VALUES
('$is[0]','$is[1]','$is[2]','$is[3]','$is[4]','$is[5]','$is[6]','$is[7]',
'$is[8]','$is[9]','$is[10]','$is[11]','$is[12]','$file',NOW(),'$ll' )
ON DUPLICATE KEY UPDATE
`dev`='$is[0]',`ino`='$is[1]',`mode`='$is[2]',`nlink`='$is[3]',`uid`='$is[4]',
`gid`='$is[5]',`rdev`='$is[6]',`size`='$is[7]',`atime`='$is[8]',`mtime`='$is[9]',
`ctime`='$is[10]',`blksize`='$is[11]',`blocks`='$is[12]',`file`='$file',`stamp`=NOW(),`ls`='$ll';
") or print $dbh->errstr;# if $config->{'reallydb'};
$dbh->disconnect() if ($dbh);
exit;
}

if ($ARGV[0] eq '--clear'){
if (-e '$file') {
print "ERROR: File $file does not exist.\n";
die;
}
print "Clearing file: $file\n";
$dbh = dbconnect();
$dbh->do("DELETE FROM `".$cfg->{'sqltabl'}."` WHERE `file` = '$file'")
or die("file didn't exist in DB\n");
$dbh->disconnect() if ($dbh);
exit;
}

if ($ARGV[0] eq '--list'){
$dbh = dbconnect();
my $sth_inbox = $dbh->prepare("SELECT * FROM `$cfg->{'sqltabl'}` where 1");
$sth_inbox->execute();
print "sysguard is watching the following files:\n";
while (my $ref = $sth_inbox->fetchrow_hashref()) {push(@listok,$ref);}
foreach my $current(@listok){
print " $current->{'file'}\n";
}
$dbh->disconnect() if ($dbh);
exit;
}

sub dbconnect{
our $cfg;
my $dbh = DBI->connect("dbi:mysql:".$cfg->{'sqlbase'}.':'.$cfg->{'sqlserv'}.":3306",
"$cfg->{sqluser}", "$cfg->{sqlpass}") or die $DBI::errstr; # connect to db
return $dbh;
}

sub getinfo($path){
my @newinfo=stat($_[0]);
if (!@newinfo){
print "Can't stat $path $_[0], $!\n";
return 0;
}
return (@newinfo);
}

sub safekeys {
$_ = $_[0];
s/â€/"/g;
s/”/"/g;
s/´/'/g;
s/’/'/g;
s/‘/'/g;
s/`/'/g;
s/[^[:alnum:].:!@#\$%^&*()_+-=\[\]<>'"\/\\{}|?\n]/ /go;
return $_;
}

print "\nSysGuard - Bray Almini 2009-06-28.\n";
print "Keeps an eye on files and folders, detecting new files and changes.\n";
print "\n";
print "usage: $0 [funtion] [file path]\n";
print "\n\tFunctions:\n";
print "\t--scan\tCheck all files and folders in DB for changes\n";
print "\t--update\tUpdate data on old record, or create a new record\n";
print "\t--list\tDisplay all files and folders being watched\n";
print "\t--clear\tDelete a record from the DB\n";
print "\n";