Perl для системного администрирования

       

Информация о пользователях в Unix


При обсуждении этой темы мы будем иметь дело лишь с несколькими ключевыми файлами, поскольку в них хранится постоянная информация о пользователе. Говоря «постоянная», я имею в виду атрибуты, которые существуют до тех пор, пока существует пользователь, сохраняясь даже тогда, когда пользователь не зарегистрирован в системе. Иначе я буду называть это учетной записью. Если в системе у вас есть учетная запись, вы можете зарегистрироваться и стать пользователем данной системы.

Существование пользователя в системе начинается с того момента, когда информация о нем впервые заносится в файл паролей (или же служба каталогов обеспечивает аналогичную информацию). Пользователь «уходит со сцены», когда эта запись удаляется. Рассмотрим подробнее, как хранится эта информация.

Классический файл паролей в Unix

Начнем мы с «классического» формата файла паролей, а затем перейдем к более сложным вопросам. Я называю данный формат классическим, потому что на нем основаны все существующие в настоящее время форматы файлов паролей в Unix. Более того, он и сейчас встречается во многих вариантах Unix, включая SunOS, Digital Unix и Linux. Обычно это файл /etc/passwd, содержащий последовательность текстовых ASCII-строк, причем каждая строка соответствует одной учетной записи или является ссылкой на другую службу каталогов. Любая строка файла состоит из нескольких полей, разделенных двоеточиями. Мы внимательно рассмотрим все эти поля, после того как научимся их получать.

Вот пример строки из файла /etc/passwd:

dnb:fMP.olmno4jGA6:6700:520:David N. Blank-Edelman:/home/dnb:/bin/zsh

Существует по крайней мере два способа получать подобную информацию средствами Perl:

1. К файлу можно обратиться «вручную», рассматривать его как обычный текстовый и соответствующим образом анализировать:

Spasswd = "/etc/passwd":

open(PW, Spasswd) or die "Невозможно открыть $passwd:$' '-n":

while (<PW>){

($name,$passwd,$iiid.$gid. $дсоз. $dir. $srelL ,) = split (/:/):

<далее следует ваша программа>






}

close(PW);

2. Другой способ позволяет «предоставить все полномочия системе». В этом случае нам будут доступны некоторые библиотечные вызовы Unix, которые

проанализируют файл за нас. Тогда последний пример можно переписать так:

while( ($пагле, Spasswd, $uid,$gid,$gcos,$dir,$shell) = getpwent( ) ){

<далее следует ваша программа>

}

endpwent();

Употребление системных вызовов содержит еще одно преимущество: их можно автоматически использовать с любой из служб имен (например, NIS). Вскоре мы рассмотрим и другие системные вызовы (включая более простой способ применения getpwent( )), а пока разберемся с полями, которые получили в наших примерах:1

Имя

В этом поле хранится короткое (обычно не длиннее восьми символов), уникальное в пределах системы регистрационное имя пользователя. Функция getpwent( ), которую мы уже видели в предыдущем примере в списочном контексте, возвращает значения данного поля, если вызывается в скалярном контексте:

$name = getpwent( ),

Идентификатор пользователя (UID)

В Unix-системах идентификатор пользователя (UID) зачастую более важен, чем регистрационное имя. Все файлы в системе принадлежат пользователю с каким-либо идентификатором, а не регистрационным именем. Если в файле /etc/passwd

поменять регистрационное имя пользователя, обладающего идентификатором 2397, с danielr на drinehart, то мгновенно владельцем всех его файлов станет пользователь drinehart. Для операционной системы идентификатор пользователя - постоянная информация. При выделении ресурсов и выяснении прав ядро и файловые системы следят за идентификаторами, а не регистрационными именами. Регистрационное имя можно считать информацией, внешней для операционной системы; эта информация существует, чтобы упростить жизнь пользователя.

Вот простой пример, позволяющий установить очередной доступный уникальный идентификатор в файле паролей. Достаточно выяснить максимальный идентификатор и использовать его для создания следующего номера:

Spasswd = "/etc/passwd";



open(PW,$passwd) or die "Невозможно открыть $passwd:$!\n";

while (<PW>){

@fields = splitC/:/);

Shighestuid = (Shighestuid < $fields[2]) ? $fields[2] : $highestuid:

}

close(PW);

print "Следующий доступный идентификатор: " . ++$highestuid . "\n";

Ниже перечислены другие полезные функции и переменные, имеющие отношение к именам и идентификаторам пользователей (табл. 3.1).

Таблица 3.1. Переменные и функции, имеющие отношение к именам и идентификаторам пользователей




Функция/ Переменная



Использование

getpwnam($name)

В скалярном контексте возвращает идентификатор, соответствующий этому регистрационному имени; в списочном контексте возвращает все поля данной записи из файла паролей

getpwuid(Suid)

В скалярном контексте возвращает регистрационное имя, соответствующее данному идентификатору; в списочном контексте возвращает все поля данной записи из файла паролей

$>

Соответствует эффективному идентификатору пользователя текущей выполняющейся программы на Perl


$<

Соответствует реальному идентификатору пользователя текущей выполняющейся программы на Perl
Идентификатор первичной группы (GID)

В многопользовательских системах пользователи их группы часто работают с файлами и другими ресурсами совместно. В Unix существует механизм, позволяющий работать с группами пользователей. Учетная запись в системе может входить в несколько групп, но при этом принадлежать она должна только одной главной группе

(primary group). Поле GID в файле паролей соответствует как раз первичной группе для данной учетной записи.

Имена групп, их идентификаторы и члены группы обычно перечислены в файле /etc/group. Чтобы включить учетную запись в несколько групп, необходимо просто указать ее в нескольких местах данного файла. В некоторых операционных системах существует жесткое ограничение на число групп, которым может принадлежать учетная запись (а значит, и пользователь). Чаще всего ограничение равно 8. Вот пример пары строк из файла /etc/group:



bin : : 2:root,bin,daemon

sys: iSiroot.bin.sys.adu

Первое поле - это имя группы, второе - пароль ( в некоторых системах может употребляться пароль для присоединения к группе), третье - идентификатор группы и последнее поле - список пользователей в группе.

Способы объединения пользователей в группы зависят от конкретного узла, поскольку границы (и административные, и границы проектов) везде разные. Таким образом, группы можно создавать для разделения различных пользователей (студенты, продавцы и т. д.), по выполняемым действиям (операторы резервных копий, сетевые администраторы и т. д.) либо по назначению учетных записей (резервные учетные записи и пр.).

Работа с файлами групп средствами Perl очень похожа на процесс разбора файла passwd из предыдущих примеров. Его можно считать обычным текстовым файлом либо применять специальные функции для выполнения подобной задачи. Посмотрите на функции и переменные, имеющие отношение к группам (табл. 3.2).

Таблица 3.2. Переменные и функции, имеющие отношение к именам и идентификаторам групп




Функция/ Переменная



Используется

getgrent()

В скалярном контексте возвращает имя группы; в списочном контексте возвращает поля: Sname, Soasswd, $gia, Sm'Xiers

get.grnarfi(Snane)

В скалярном контексте возвращает идентификатор группы; в списочном контексте возвращает те же поля, что и функция getgi-ent( )

getgrgio($gid)

В скалярном контексте возвращает имя группы; в списочном контексте возвращает те же поля, что и функция дё-^"-'


$)

Соответствует эффективному идентификатору группы текущей выполняемой программы

$(

Соответствует реальному идентификатору группы текущей выполняемой программы
«Зашифрованный» пароль

Мы уже рассмотрели три основных поля, в которых содержится информация о пользователе в Unix. Следующее поле не является частью хранимой информации, но оно подтверждает права, обязанности и привилегии, присущие пользователю с конкретным идентификатором. Именно так компьютер узнает, что тому, кто выдает себя за пользователя mguerre, позволено присвоить конкретный идентификатор. Существуют и другие, лучшие формы авторизации (например, использование криптографических методов с открытым ключом), но этот способ унаследован от ранних версий Unix.



Очень часто в этом поле в файле паролей можно увидеть лишь звездочку (*). Подобный знак применяется для запрещения регистрации пользователя в системе, без удаления при этом самой учетной записи.

Работа с паролями пользователей - это отдельная тема. Ей будет посвящена глава 10 «Безопасность и наблюдение за сетью».

Поле GCOS

Поле GCOS самое бесполезное (с точки зрения компьютера). Обычно в этом поле записано полное имя пользователя (например «Рой Дж. Бив»). Часто люди добавляют туда должность и/или номер телефона.

Системные администраторы, заботящиеся о приватности пользователей (чему и следует быть), должны проверять содержимое данного поля. Это стандартный путь для определения соответствия между реальным именем пользователя и его регистрационным именем. В большинстве Unix-систем такое поле находится в файле /etc/passwd, доступном всем для чтения, следовательно, эта информация может попасть в руки кого угодно в системе. Многие программы, почтовые клиенты и демоны finger-запросов обращаются к этому полю при добавлении регистрационного имени пользователя к какой-то информации. Если у вас есть необходимость скрыть реальные имена пользователей от других людей (например, если речь идет о политических диссидентах, федеральных свидетелях или известных персонах), вы обязательно должны следить за этим полем.

В качестве дополнительной информации: если вы поддерживаете сайт с менее развитой пользовательской базой, было бы неплохо запретить пользователям изменять их поле GCOS на случайные строки (по тем же причинам, по которым выбранные пользователями регистрационные имена могут вызвать проблемы). Вряд ли вы придете в восторг, увидев в своем файле паролей бранные выражения или иную непрофессиональную информацию.

Домашний каталог

Следующее поле содержит имя

домашнего каталога пользователя. Это тот каталог, откуда начинается работа с системой. Обычно здесь хранятся файлы, определяющие настройки пользователя.

В целях безопасности очень важно, чтобы запись в домашний каталог была разрешена только его владельцу. Домашние каталоги, доступные для записи всем, открывают возможность хакерских действий. Правда, существуют ситуации, когда домашние каталоги, доступные для записи только самим владельцам, тоже вызывают проблемы. Например, в случае с ограниченными интерпретаторами (если пользователи могут регистрироваться в системе только для выполнения определенных задач без права изменять что-либо в системе) домашние каталоги, доступные для записи пользователю, категорически запрещены.



Вот пример кода на Perl, который позволяет убедиться, что все домашние каталоги пользователей принадлежат своим владельцам и недоступны для записи остальным:

use User::pwent; use File::stat;

ft замечание: этот код очень сильно загрузит машину, если

# домашние каталоги монтируются автоматически

while($pwent = getpwent()){

# убеждаемся, что это действительно каталог, даже если

# он спрятан за символическими ссылками

Sdirinfo = stat($pwent->dir."/."); unless (defined $dirinfo){

warn "Невозможно получить информацию о ".$pwent->dir.": $!\n"; next;

}

warn «Домашний каталог пользователя ".$pwent->name." не имеет в

ладельца с корректным uid (". $dirinfo->uid." вместо ".$pwent->uid.")!\n"

# ($dirinfo->uid != $pwent->uid);

# каталог может быть доступным всем для записи, если

# у него установлен «бит-липучка" (т. е. 01000),

# подробности в странице руководства по chraod

warn $pwent->name."'s homedir is world-writable!\n"

if ($dirinfo->mode & 022 and (!$stat->mode & 01000));

}

endpwent();

Этот пример на вид несколько отличается от предыдущих, поскольку в нем используются два замысловатых модуля Тома Кристиансе-на (Tom Christiansen): User: :cweit и FiJe: :stai. Эти модули изменяют функции getpwcrH() и stat(), заставляя их возвращать значения, отличные от ранее упомянутых. Когда загружены модули Fi le: : stat, эти функции возвращают объекты вместо списков или скалярных значений. У каждого объекта есть метод, названный по имени поля, которое было бы возвращено в списочном контексте. Поэтому такой код:

$gid = (stat("filena(ne"))[5]:

можно переписать гораздо понятнее :

use File;:stat;

$stat = stat("filename"):

$gid = $stat->gid:

или даже так:

use File;:stat;

$gid = stat("filename")->gid;

Командный интерпретатор пользователя

Последнее поле классического файла паролей - это поле, соответствующее командному интерпретатору пользователя. Обычно это один из интерпретаторов (sh, csh, tcsh, ksh, zsh), но это может быть и путь к любой исполняемой программе или сценарию. Время от времени, некоторые ради шутки (но наполовину всерьез) устанавливают в качестве своего командного интерпретатора по умолчанию интерпретатор Perl. По крайней мере, в один интерпретатор (zsh) хотят всерьез встроить интерпретатор Perl, но этого пока еще не случилось. Тем не менее, были предприняты серьезные попытки создать командный интерпретатор Perl shell (http:/ /www.focusrese-arch.com/gregor/psh/), а также встроить Perl в редактор Emacs, который легко может заменить целую операционную систему (http:// john-edwin-tobey.org/perlmacs/1).



Бывают ситуации, когда необходимо указать в этом поле нечто отличное от стандартного командного интерпретатора. Например, если вы хотите создать учетную запись, работающую с системой меню, вы можете поместить в данное поле имя такой программы. В этом случае стоит принять некоторые меры предосторожности, чтобы пользователь, применяющий эту учетную запись, не получил бы доступ к командному интерпретатору, иначе не миновать бед. Часто встречаемая ошибка - включение в такое меню почтовой программы, которая позволяет запускать редактор или инструмент постраничного просмотра для чтения или редактирования почты. Оба эти средства могут иметь возможность выхода в интерпретатор.

Список доступных в системе стандартных командных интерпретаторов часто хранится в файле /etc/shells, видимо, для удобства демона FTP. Большинство FTP-демонов не позволят обычному пользователю подсоединиться к системе, если их командный интерпретатор, заданный в /etc/passwd (или сетевом файле паролей), не присутствует в /etc/shells. Вот пример на Perl, который докладывает об учетных записях с неподтвержденными командными интерпретаторами:

use User::pwert:

Sshells = "/etc/shells";

open (SHELLS,Sshells) or die "Невозможно окрыть;

while(<SHELLS>){

chomp:

$oksnell{$_}++;

}

close(SHELLS);

while($pwent = getpwent()){

warn $pwent->name." has a bad shell (".$pwent->shell.")!\n"

unless (exists $okshell{$pwent->shell});

 }

endpwent();



Дополнительные поля в файлах паролей в BSD 4.4



При смене BSD (Berkeley Software Distribution) с версии 4.3 на 4.4 к классическому формату файла паролей были добавлены две характерные особенности: дополнительные поля и формат двоичных баз данных, используемых для хранения информации об учетных записях.

В BSD 4.4 в файле паролей между полями GID и GCOS появились новые поля. Первым было добавлено поле class. Оно позволяет системному администратору разбить все учетные записи системы на отдельные классы (например, для различных классов учетных записей могут существовать различные ограничения ресурсов, таких как время использования процессора). Кроме того, были добавлены поля change и expire, в которых хранятся данные о сроке продолжительности пароля и времени действия учетной записи. Подобные поля встретятся также и в формате следующего файла паролей в Unix.



При компиляции в операционной системе, поддерживающей эти дополнительные поля, Perl включает их содержимое в значение, возвращаемое функциями типа getpwent(). Это одна из причин, по которой стоит употреблять в программах getpwent(), а не разбирать файл паролей вручную при помощи sрlit ().



Формат двоичных баз данных в BSD 4.4



Вторая характерная черта BSD - использование баз данных, а не обычного текста для хранения информации о паролях. В BSD файлы паролей хранятся в формате DB - значительно улучшенной версии старых библиотек DBM (Database Management). Это изменение позволяет cиcтеме быстро обращаться к информации о паролях.

Программа pwd mkdb в качестве аргумента принимает имя текстового файла паролей, создает и переносит в нужное место два файла баз данных, а затем перемещает исходный текстовый файл в /etc/mas-ter.passwd. Две базы данных позволяют обеспечить механизм теневых паролей - они отличаются правами на чтение и содержимым поля, в котором хранится зашифрованный пароль. Более подробно мы поговорим об этом в следующем разделе.

Perl может напрямую работать с DB-файлами (операции с самим форматом встречаются в главе 9 «Журналы»), но обычно я не рекомендую напрямую редактировать базы данных, пока система используется. Дело тут в блокировке: необходимо убедиться, что другие программы не читают и не записывают данные в файл паролей в тот момент, когда вы собираетесь редактировать его как базу данных. Стандартные программы, подобные chpasswd, выполняют необходимую блокировку самостоятельно. Ловкий прием, заключающийся в использовании переменной EDITOR, который мы употребили при работе с квотами в главе 2 «Файловые системы», можно применить и при вызове chpasswd.



Теневые пароли



Не следует забывать, насколько важна защита содержимого поля GCOS, т. к. целым рядом различных механизмов эта информация доступна для всех. Другая, менее доступная, но довольно уязвимая информация - это список зашифрованных паролей всех пользователей системы. И хотя эти пароли зашифрованы, одно то, что они хранятся в файле, доступном всем для чтения, вносит изрядную степень риска. Некоторые части файла паролей должны быть доступны всем для чтения (например, связь между регистрационным именем и идентификатором пользователя), но не весь файл. Нет никакой необходимости выставлять на всеобщее обозрение список зашифрованных паролей, т. к. пользователи могут попытаться запустить программы для взлома паролей.



Одна из возможных альтернатив - перенести все пароли в специальный файл, читать который сможет только суперпользователь. Этот второй файл известен как файл «теневых паролей», т. к. в нем хранятся строки, затеняющие записи из обычного файла паролей.

Вот как все это работает: оригинальный файл паролей остается нетронутым, за одним небольшим исключением. Вместо зашифрованного пароля в это поле помещается специальный символ или символы, которые говорят о том, что используется механизм затенения паролей. Обычно это символ х, но в BSD используется *.

Я слышал, что существуют некоторые пакеты (для поддержки теневых паролей), вставляющие в это поле специальную строку символов, которая выглядит обычной. Если ваш файл паролей попадет в руки злоумышленника, то он потратит много времени, взламывая случайные строки, не имеющие ничего общего с настоящими паролями.

Большинство операционных систем используют файл теневых паролей для хранения дополнительной информации об учетной записи. Такой формат включает дополнительные поля, которые мы видели в BSD-файлах, и в них хранится информация об истечении срока действия учетной записи и информация о смене пароля.

В большинстве случаев обычные Perl-функции, подобные getcwenb(), могут работать с файлами теневых паролей. Если стандартные библиотеки С, входящие в состав операционной системы, делают то, что нужно., то Perl тоже будет делать все верно. Говоря «делать то, что нужно», я подразумеваю, что если ваши сценарии на Perl запускаются с подходящими привилегиями (с привилегиями суперпользователя), то эти функции будут возвращать зашифрованный пароль. В остальных случаях пароль этим функциям не доступен.

Значительно хуже, если вы захотите получить дополнительные поля из файла теневых паролей. Perl может и не вернуть их вам. Эрик Истабрукс (Eric Estabrooks) написал модуль Passwd: :Solar is, но он будет полезен только при работе в Solaris. Если эти поля имеют для вас принципиальное значение или вы хотите действовать наверняка, то, как ни грустно (это противоречит моим рекомендациям использовать getpwent()), но часто проще открыть файл shadow и получить нужные значения вручную.






Содержание раздела