Перевод Anton Petrusevich <casus@mail.ru> и Alex Ott <ott@phtd.tpu.edu.ru>
Perl имеет много операций вне его назначения, которые делают выполнение многих задач более легким. В этой колонке, мы покажем как операции манипуляции с битами (``bitops'') могут помочь вам в связи с правами доступа к файлам.
Сначала, небольшое введение. Право доступа к файлу (или режим) интерпретируется как 12-битное значение, объединенное в четыре группы по три бита в каждой. Первыми тремя битами являются биты setuid, setgid и sticky, о которых мы не будем здесь говорить. Три другие группы соответственно контролируют права доступа для владельца файла (``пользователь, user''), членов группы-владельца файла (``группа, group'') и всех остальных (``другие, other''). Каждая группа битов контролирует права доступа на запись, чтение и выполнение файла для каждой из групп пользователей.
Мы можем получить число, обозначающее права доступа к конкретному
файлу, используя оператор stat
, например таким образом:
$mode = (stat "/etc/motd")[2];
print $mode;
Но этот код на моей машине печатает непонятное число 33188. Как я могу превратить это число в что-то понятное? Во первых, самым простым способом будет заставить Perl напечатать число в восьмеричном виде, так что группы по три бита появятся как восьмеричные значения 0-7:
$mode = (stat "/etc/motd")[2];
printf "%o\n", $mode;
На моей машине этот код выдает значение ``100644''. Чем здесь
является дополнительная цифра 1? Этот код показывает, что
/etc/motd
является файлом, ен похожем на устройство или
каталог. Мы не хотим видеть его, так что здесь должны использоваться
битовые операторы. Давайте выполним побитовую операцию "И" нашего
значения с числом 07777, показывая только права доступа, а не
закодированный тип:
$mode = (stat "/etc/motd")[2];
printf "%o\n", $mode & 07777;
Это является хорошо знакомым значением ``644'', которое мы передаем команде chmod. Теперь как я могу сделать, чтобы файл был доступен для записи для всех? (Не рекомендуется... просто для примера...) Нам необходимо выполнить операцию or (или) над битами, которые представляют право записи для пользователя, группы и остальных, и которые записываются как 0222 (другое восьмеричное значение):
$mode = (stat "/etc/motd")[2];
$newmode = $mode | 0222;
chmod $newmode, "/etc/motd";
Здесь использование ``|'' заставляет выполнить операцию ``или'' с заданными битами, и мы получаем доступный для записи всеми пользователями файл сообщений (если вы конечно администратор).
Как мы можем просто выделить группу битов? Для этого нам необходимо выполнить операцию сдвига, вместе с выполнением операции ``и'', например так:
$mode = (stat "/etc/motd")[2];
$groupmode = ($mode & 070) >> 3;
print "$groupmode\n";
Этот код на моей машине выдает ``4''. Заметьте, что я не использую
функцию printf
, поскольку восьмеричные 0-7 выдаются также как
и десятичные числа, в том же самом диапазоне.
Так, что вместо продолжения показа частичных примеров, давайте применим это новое значение в конкретном куске кода. Эта программа бедет проходить по вашим путям поиска, в поиске каталогов, которые могут быть изменены пользователями, к которым нет доверия. Этот код начинается с чего-то подобного этому коду:
#!/usr/bin/perl
use File::stat;
Модуль File::stat
позволяет нам получить именованный доступ
к некоторым полям stat
. Смотрите применение этого в дальнейшем
коде. Далее у нас имеется код:
sub trust_user { $_[0] < 100 }
sub trust_group { $_[0] < 100 }
Эти подпрограммы определяют номера пользователей и групп, которым имеют нужные права. Предполагается, что каталог доступный для записи таким пользователям, является безопасным. Далее, разделим пути поиска на понятные компоненты с помощью кода:
my @path = split /:/, $ENV{PATH};
И затем нам необходимо пройти по путям поиска, по каждому из элементов:
for (@path) {
(my $statinfo = stat $_) or
(warn "cannot stat $_: $!"), next;
Заметьте, что мы сначала помещаем каждое из имен в переменную
$_
, а затем пытаемся определить ее параметры. Вышеприведенный
оператор use
переопределяет функцию stat
, так что она
будет возвращать объект, который отвечает на вызов функций, названных
как имена полей функции stat
. Так, что возвращаемое значение
является скаляром, а не списком.
Далее мы устанавливаем некоторые переменные:
my @reasons;
my $uid = $statinfo->uid;
my $gid = $statinfo->gid;
my $mode = $statinfo->mode;
Переменная @reasons
содержит причины, по которым каталог
считается плохим. В начале, это список пуст. Другие три переменные
содержат номер владельца, группы-владельца и значение прав доступа и
типа, как это описано выше. Далее давайте проверим права доступа
владельца:
if ($uid != $<
and not trust_user($uid)
and $mode & 0200) {
push @reasons, "non-owned, write by owner ".
getpwuid($uid);
}
Здесь, если владелец каталога не является человеком, запустившем
скрипт, и если владелец каталога не входит в список доверенных
пользователей, то содержимое каталога может быть изменено кем-то, если
он также имеет права записи в этот каталог. Так, что мы проверим это
путем выполнения операции ``и'' между $mode
и числом 0200,
результат которой будет ненулевым, если установлен бит доступа на
запись для пользователя.
Если это так, то мы добавляем человека, к которому нет доверия к
списку причин, используя операцию push
для текстового
сообщения. Вызов getpwuid
возвращает нам имя пользователя
вместо его номера.
Далее, выполняется тоже самое (приблизительно) с учетом бита права доступа на запись для пользователя:
if (not trust_group($gid)
and $mode & 020) {
push @reasons, "write by group ".
getgrgid($gid);
}
И затем для бита записи для остальных пользователей:
if ($mode & 02) {
push @reasons, "write by world";
}
В заключение, после всех причин, которые могут быть неправильными, настало время показать найденные ошибки:
if (@reasons) {
print
"danger: $_ is dangerous because ",
"of the following reasons:\n",
map " $_\n", @reasons;
}
}
В этом эпизоде, если @reasons
является не пустым, то пришло
время протеста. Оператор map
превращает каждую из причин в
сообщение с отступом, которые затем предваряются общим сообщением об
ошибке. А также приведена дополнительная закрывающая скобка для
соответствия оператору цикла foreach
, который начат ранее.
Так, что если вы поместите этот код в файл и запустите его, то вы увидите все места, в которых вам могут нанести ущерб. Убедите, что вы правильно настроили список доверенных пользователей и групп для соответствия вашей настройке.
А сейчас, давайте рассмотрим еще одну небольшую программу. Я попытался сделать из нее эквивалент команде:
chmod -R go=u-w /some/dir /other/dir
которую я выполняю очень часто, поскольку она устанавливает все права доступа в правильные значения для ``опубликованного'' дерева каталогов, независимо от того, являются ли они исполнимыми файлами, или чем-то другим.
Но меня раздражало то, что команда chmod не имеет ключа для ``многословного'' режима, который позволил бы не узнать о том, что идет неправильно, и что должно быть исправлено. Я также хотел отметить некоторые каталоги как ``не затрагиваемые'', что является ключом, которого также не имеет стандартная команда chmod.
Но никогда не бойтесь... прием (hack) на языке Perl спасет вас! Эта маленькая программа начинается со строк:
#!/usr/bin/perl
$| = 1;
use File::Find;
Эти строки отключают буферизацию STDOUT
и подключают модуль
File::Find
. Далее мы используем код верхнего уровня. Просто
одна строка:
find \&wanted, @ARGV;
Эта строка вызывает функцию find
, определенную в модуле
File::Find
, передавая ей подпрограмму, определенную ниже и
аргументы командной строки. Я описывал File::Find
в предыдущих
выпусках. Подпрограмма &wanted
будет вызываться для
каждого из путевых имен, и начинается со следующего кода:
sub wanted {
if (-d && -e "$_/.PRIVATE") {
print "## skipping contents of $File::Find::name\n";
return $File::Find::prune = 1;
}
Сначала мы определяем каждый каталог, который содержит каталог с
именем .PRIVATE
. Так я называю собственные ``не
затрагиваемые'' области. Установка переменной
$File::Find::prune
в значение 1 заставляет
File::Find::find
прекратить нисходящий переход в эту
область. Далее мы пропускаем, символические ссылки и все не являющееся
файлами или каталогами:
return if -l;
return unless -f or -d;
И также определяем то, для чего не можем выполнить функцию
stat
:
my @stat = stat or return;
Далее мы получаем различные биты режима:
my $mode = $stat[2] & 07777;
my $user_rwx = ($mode & 00700) >> 6;
my $target_bits = $user_rwx & 05;
my $group_rwx = ($mode & 00070) >> 3;
my $other_rwx = ($mode & 00007);
Здесь... $user_rwx
является правами пользователя, в то
время как $target_bits
являются тем, чем должны быть права
доступа для группы и остальных пользователей. Давайте посмотрим, если
они являются:
if ($target_bits != $group_rwx
or $target_bits != $other_rwx) {
Далее, если эта проверка не проходит, то нам необходимо вычислить новый режим:
my $new_mode = (($mode & 07700) |
($target_bits << 3) |
($target_bits));
Заметьте использование здесь побитовой операции ``или'' для создания нового режима из частей. В заключение, давайте сообщим о том, что мы делаем и сделаем это:
printf
"chmod %o %s # was %o\n",
$new_mode, $File::Find::name, $mode;
chmod $new_mode, $_;
}
}
И заключительный скобки закрывают определение подпрограммы.
Таким образом вы можете подружиться с битовыми операциями. Узнайте и используйте их. И возможно в следующий раз, когда вы будете проклинать стандартные утилиты Unix за то, что они не имеют ключа ``многословного'' режима, то вы сможете заменить эти утилиты 10-строковой программой на Perl, которая выполняет ту же самую работу, с практически той же скоростью. Наслаждайтесь!