Сравнение значений возвращаемых функцией stat( )
Таблица 10.1. Сравнение значений, возвращаемых функцией stat( )
№ | Описание поля в Unix | Действительно в NT/2000 |
Действительно в MacOS |
0 | Номер устройства файловой системы | Да (порядковый но- мер диска) |
Да (но является vRefNum) |
1 | Inode | Нет (всегда 0) | Да (но filelD/dirlD) |
2 | Режим файла (тип и права) | Да | Да (но 777 для каталогов и приложений, 666 для незаблокированных документов, 444 для заблокированных документов) |
3 | Количество (жестких) ссылок на файл | Да (для NTFS) | Нет (всегда 1) |
4 | Численный идентификатор владельца файла | Нет (всегда 0) | Нет (всегда 0) |
5 | Численный идентификатор группы владельца файла | Нет (всегда 0) | Нет (всегда 0) |
6 | Идентификатор устройства (только для специальных файлов) | Да (порядковый номер диска) | Нет (всегда null) |
7 | Размер файла в байтах | Да (но не включает размер каких-либо альтернативных потоков данных) |
Да (но возвращает только размер данных) |
8 | Время последнего доступа относительно начала эпохи | Да | Да (только эпоха начинается на 66 лет раньше, чем в Unix, то есть 1/1/1904, и значение то же, что и для поля №9) " |
9 | Время последней модификации относительно начала эпохи | Да | Да (только эпоха начинается 1/1/1904 и значение то же, что и для поля №8) |
10 | Время последнего изменения inode относительно начала эпохи | Да (но время создания файла) | Да (только эпоха начинается 1/1/1904, и это время создания файла) |
11 | Предпочтительный размер блока для ввода/вывода | Нет (всегда null) | Да |
12 | Количество занятых блоков | Нет (всегда null) | Да |
Для возвращения атрибутов, специфичных для операционной системы, в других He-Unix-версиях Perl помимо stat() и lstat() используются специальные функции. Рассказ о таких функциях, как Perl::Getr41oInf u() и Win32: :FileSecunу : ujt.(), можно найти в главе 2 «Файловые системы».
После того как с помощью stat() для файла будут получены значения, на следующем шаге надо будет сравнить «интересные» значения с уже известными. Если они изменились, значит, изменилось и что-то в этом файле. Ниже приведена программа, которая генерирует строку значений ista l () и проверяет для файлов некоторые из этих значений. Мы намеренно исключили 8-е поле (время последнего доступа), потому что оно меняется при каждом прочтении файла.
Программа принимает либо аргумент -р filename, чтобы вывести значения lstat() для заданного файла, либо аргумент -с filename, чтобы проверить значения lstat() для всех файлов, перечисленных в filename.
use Getopt::Std;
используем это для создания более симпатичного вывода позже в &pnntchanged()
@statnames = qw(dev ino mode nlink uid gid rdev size mtime ctime blksize blocks);
getopt('p:c:');
die "Использование: $0 [-p <filename>|-c <filena:iie>]\n" unless ($opt_p or $opt_c);
if ($opt_p)(
die "Невозможно получить информацию о файле $opt._p:
unless (-с $opt_p): print $opt_p."|",]OinC |',(lstat($opt_p))[0..7,9..12]),"\n":
exit:
if ($opt_c){
oper.(CFILE,$opt_c) or
die "Невозможно открыть файл $opt_c:$!\": while(<CFILE>){ cho;:!p:
ssavecstats = spl-'('\:' ); die Неверное количество полей в строке, начинающейся с
$savedstats[C']\'T jniess (Sttsaveustits == 12): s<,i.r rentstats = (Isratv $savedstat3[0]) )[0. 7,9. 12]
}
close(CFILE);
}
sub printchanged{
my($saved. $curre!it)= ®_:
выводим имя файла после того. выбрасываем его из массива, прочитанного из файла
prin: shift §{$saved}.":\n":
for (my $i=0; $1 < $#{$saved};$!++){
if ($saved->[$i] ne $current->[$i]){
print "\t".$statnames[$i]." is now ".$current->[$i];
print " (should be ".$saved->[$i].")\n":
} }
Для использования этой программы можно набрать checkfile -p /etc/passwd » checksumfile. В файле checksumfile теперь будет храниться строка, которая выглядит так:
/etc/passwd|1792|11427]33060)1|0|0|24959|607|921016509|921016509|8192|2
Этот шаг нужно повторить для каждого файла, за которым мы наблюдаем. Затем вызов сценария с аргументом checkfile -с checksumfile будет сообщать обо всех изменениях. Например, если я удалю один символ из /etc/passwd, сценарию это не понравится, и он выведет такое сообщение:
/etc/passwd:
size is now 606 (should be 607)
mtime is now 921020731 (should be 921016509)
сtime is now 921020731 (should be 921016509)
Перед тем как двигаться дальше, необходимо сказать об одном приеме, который мы применили в программе. В следующей строке проверяется равенство двух списков (сделано это на скорую руку):
if ("®savedstats[1..12]' ne "@currentstats"):
Perl автоматически преобразовывает список в строку, склеивая элементы списка через пробел:
joint" ",ssavedstats[1. . 12]))
и затем уже сравнивает получившиеся строки. Этот прием хорошо работает для коротких списков, в которых имеет значение порядок и количество элементов. В большинстве других случаев необходимо использовать итеративный подход или хэши, как описано в списках часто задаваемых вопросов perlfaq, входящих в состав Perl.
Теперь, когда вы выяснили атрибуты файлов, я вынужден вас огорчить. Проверка того, что атрибуты файлов не изменились, - это хорошая идея, но не больше. Не представляет большого труда изменить файл, оставив неизменными такие атрибуты, как время доступа и модификации. В Perl даже есть функция, предназначенная для изменения времени доступа и модификации. Так что пришло время применить более мощные инструменты.
Обнаружение изменений в данных - это одна из сильных сторон алгоритмов, известных как криптографические хэш-функции («message-di-gest algorithms»). Вот как Рон Райвест (Ron Rivest) описывает алгоритм «RSA Data Security, Inc. MD5 Message-Digest Algorithm» в RFC1321:
Алгоритм на вводе принимает сообщение произвольной длины и создает подпись (message digest или fingerprint) длиной 128 бит. Считается, что просто невозможно создать два сообщения, у которых совпадали бы подписи; также невозможно создать сообщение, подпись которого совпадала бы с заранее заданной.
Для нас это означает, что если применить к файлу алгоритм MD5, то он будет снабжен уникальной подписью. Если данные из этого файла изменятся, то независимо от того, насколько они незначительны, подпись файла тоже будет изменена. Самый простой способ воспользоваться
этой чудесной возможностью из Perl - применить модуль LUgos, : : МУ:> из семейства модулей Digest.
Использовать модуль Digest:: MD5 просто. Нужно создать объект, добавить в него данные при помощи методов add () miHaridfile(), а затем попросить модуль создать подпись.
Можно сделать нечто подобное для подсчета подписи MD5 для файла паролей в Unix:
use Digest: :MD5 qw(rnd5); $md5 = new Digest::MD5:
open(PASSWD. "/Gtc/f.'asswd") or die "Невозмонсть открыть pass,vd$' ":
acdf :Ie(PASSWD):
ciose(PASSlvD)
prit 3r!d5->hexd:g-2s: . "v "",
В документации по Digest: :MD5 сказано, что для создания более компактных программ можно связывать несколько методов вместе. Так, предыдущую программу можно переписать:
Файлы perlfaql. pod, perlfaq2.pod ... perlfaq[N].pod.
use Digest::MD5 qw(md5);
open(PASSWD. Vei-c/pabswri") or 'I:e "Ненот.' print Digest: :MD5--'neiA->addfi](](PASSWD) close(PASSWD):
Обе программы выводят следующее:
a6f905e6h45a65a7e03dOS09448b501c
Если в файл внести незначительные изменения, то вывод станет другим. Вот что получилось, когда я поменял местами всего два символа в файле паролей:
335679с4с97а381523034331a06df3e7
Теперь любые изменения становятся очевидными. Давайте расширим предыдущую программу проверки атрибутов и добавим к ней MD5:
use Getopt::Std;
use Digest::MD5 qw(md5);
@statnames =
qw(dev ino mode nlink uid gid rdev size mtime ctinie blksize blocks md5):
getopt('p:c:');
die "Использование: 0 [-p <filename>|-c <fileriame>]\n"
unless ($opt_p or $opt_c):
if ($opt^.p){
die "Невозможно получить информацию о файле $opt_p:$'\n"
unless (e $opt_p);
open(F.$opt_p) or die "Невозможно открыть $opt_p:$'\r";
$d.igest = Digest: ;MD5->new-^addfile(F)->hexaigest:
ciose(F):
print $opt__p, "|", ]oin
"|$digest", "\n":
exit:
}
if ($ppt_c){
open(CFILE.$opt_c) or
die "Невозможно открыть Файл;.
wnilc (<CFILE>){
c'-.o^p:
ssavedstats = spli r(
far,, rp'.tstats = (lstat($s;ivenstars[OJ))[0 . Л9..121:
doSi:( h )
&pr unchanged (\3savudstats. Vicur: и its: a; .1)
if ("«?savedstars[1 13]"
close(CFILE):
}
sub printcharigcd {
my($saved,$cnrrent)= ®_:
print shift @{$saved).":\n";
for (my $i=0; $1 <= $(({$saved}; $!++){
if ($saved->[$i] ne $current->[$i]){
print " P\$statnames[$i]." is now ", $current->[$i ]:
print " (".$saved->[$i].")\n";
}