|
|
|
урок: предыдущий следующий
Информация о вашем пользовательском имени и идентификаторе, которая имеется в системе UNIX, практически открыта. По сути дела, любая программа, которая не сочтет за труд заглянуть в файл /etc/passwd, поможет вам увидеть почти все, кроме незашифрованного пароля. Этот файл имеет особый формат, определяемый в passwd(5), и выглядит приблизительно так:
|
name:pa3swd:uid:gid:gcos:dir:shell
|
Поля определены следующим образом:
- name - Регистрационное имя пользователя
- passwd - Зашифрованный пароль или что-нибудь простое, если используется теневой файл паролей
- uid - Идентификатор пользователя (для пользователя root — 0, для обычных пользователей — ненулевое число)
- gid - Регистрационная группа по умолчанию (группа 0 может быть привилегированной, но не обязательно)
- gcos - Как правило, содержит полное имя пользователя, за которым через запятую следует и другая информация
- dir - Начальный каталог (каталог, в который вы переходите, когда даете команду cd без аргументов, и в котором хранится большинство ваших файлов, имена которых начинаются с точки)
- shell - Ваш регистрационный shell, как правило, /bin/sh или /bin/csh (а, может быть, даже /usr/bin/perl, если вы большой оригинал)
Типичные элементы файла паролей выглядят так:
|
fred:*:123:15:Fred Flintstone,,,:/home/fred:/bin/csh barney:*:125:15:Barney Rubble,,,:/home/barney:/bin/csh
|
Сейчас в Perl достаточно инструментов для того, чтобы можно было легко выполнить разбор такой строки (например, с помощью функции split), не прибегая к специальным программам. Тем не менее в библиотеке UNIX все же есть набор специальных программ: getpwent(3), getpwuid(3), gelpwnam(3) и т.д. Эти программы доступны в Perl под теми же именами, с похожими аргументами и возвращаемыми значениями.
Например, программа getpwnam в Perl становится функцией getpwnam. Ее единственный аргумент — пользовательское имя (например, fred или barney), а возвращаемое значение — строка файла /etc/passwd, преобразованная в массив со следующими значениями:
|
($name, $passwd, $uid, $gid, $quota, $cominent, $gcos, $dir, $shell)
|
Обратите внимание: здесь несколько больше значений, чем в файле паролей. Обычно в UNIX-системах, по крайней мере в тех, которые мы видели, поле $quota всегда пусто, а поля $comment и $gcos часто оба содержат персональную информацию о пользователе (поле GCOS). Так, для старины Фреда мы получаем
|
("fred", "*", 123, 15, "", "Fred Flintstone,,,", "Fred Flintstone,,,", "/home/gred"," /bin/csh")
|
посредством любого из следующих вызовов:
getpwuid(123);
getpwnam("fred");
|
Отметим, что в качестве аргумента функция getpwuid принимает идентификатор пользователя, a getpwnam — регистрационное имя.
При вызове в скалярном контексте функции getpwnam и getpwuid также имеют возвращаемое значение — данные, которые вы запросили с их помощью. Например:
$idnum = getpwuid("daemon");
$login = getpwnam (25);
|
Возможно, вам захочется получить эти результаты по отдельности, используя некоторые из уже знакомых вам операций, проводимых над списками. Один способ — получить часть списка, используя для этого срез списка, например получить для Фреда только начальный каталог:
|
($fred_home) = (getpwnam ("fred"))[7]; # начальный каталог Фреда
|
Как просмотреть весь файл паролей? Для этого можно было бы поступить, к примеру, так:
for($id = 0; $id <= 10000; $id++) {
@stuff = getpwuid $id;
}
### не рекомендуется!
|
Это, однако, неверный путь. Наличие нескольких способов само по себе еще не означает, что все они в равной степени эффективны.
Функции getpwuid и getpwnam можно считать функциями произвольного доступа; они извлекают конкретный элемент по его ключу, поэтому для начала у вас должен быть ключ. Другой метод доступа к файлу паролей — последовательный, т.е. поочередное получение его записей.
Программами последовательного доступа к файлу паролей являются функции setpwent, getpwent и endpwent. В совокупности эти три функции выполняют последовательный проход по всем записям файла паролей. Функция setpwent инициализирует просмотр. После инициализации каждый вызов getpwent возвращает следующую запись файла паролей. Если данных для обработки больше нет, getpwent возвращает пустой список. Наконец, вызов endpwent освобождает ресурсы, используемые программой просмотра; это делается автоматически и при выходе из программы.
Приведенное описание может сказаться не совсем понятным без примера, поэтому дадим его:
setpwent(); # инициализировать просмотр
while (@list = getpwent ()) { # выбрать следующий элемент
($login,$home) = @list[0,7]; # получить регистрационное имя
# и начальный каталог
print "Home directory for $login is $home\n"; # сообщить это
}
endpwent(); # все сделано
|
Эта программа сообщает имена начальных каталогов всех пользователей, перечисленные в файле паролей. А если вы хотите расставить начальные каталоги в алфавитном порядке? В предыдущей главе мы изучили функцию sort, давайте воспользуемся ею:
setpwent(); # инициализировать просмотр
while (@list = getpwent()) { # выбрать следующий элемент
($login,$home) = @list[0,7]; # получить регистрационное имя # и начальный каталог
$home{$login} = $home; # сохранить их
}
endpwent(); # все сделано
@keys = sort {$home{$a} cmp $home{$b}} keys %home;
foreach $login (@keys) { # пройти по рассортированным именам
print "home of $login is $home{$login}\n";
}
|
Этот несколько более длинный фрагмент иллюстрирует важную особенность последовательного просмотра файла паролей: вы можете сохранять соответствующие фрагменты данных в структурах данных, выбираемых по своему усмотрению. Первая часть примера — это код просмотра всего файла паролей с созданием хеша, в котором ключ — регистрационное имя, а значение — начальный каталог, соответствующий этому регистрационному имени. Строка sort получает ключи хеша и сортирует их в соответствии со строковым значением. Завершающий цикл — это проход по рассортированным ключам и поочередный вывод всех значений.
В общем случае для просмотра небольшого количества значений рекомендуется использовать программы произвольного доступа (getpwuid и getpwnam). Если значений много или необходим просмотр всех значений, проще выполнить проход с последовательным доступом (с помощью функций setpwent, getpwent и endpwent) и поместить конкретные значения, которые вы будете искать, в хеш*.
Доступ к файлу /etc/group осуществляется аналогичным образом. Последовательный доступ обеспечивается вызовами функций setgrent, getgrent и endgrent. Вызов getgrent возвращает значения в следующем формате:
|
($name, $passwd, $gid, $members)
|
Эти четыре значения примерно соответствуют четырем полям файла /etc/group, поэтому за подробной информацией обращайтесь к описаниям, приведенным на man-страницах, относящихся к формату этого файла. Соответствующие функций произвольного доступа — getgrgid (по идентификатору группы) и getgrnam (по имени группы).
* Если у вас узел с большой NIS-картой, то по соображениям производительности такой способ предобработки файла паролей лучше не использовать.
Данные о паролях и группах удобно использовать в текстовом виде. Информацию в других системных базах данных более естественно представлять иначе. Например, IP-адрес интерфейса обрабатывается внутренними механизмами как четырехбайтовое число. Хотя его часто представляют в текстовом виде (как четыре небольших целых числа, разделенных точками), такое преобразование — пустая трата времени, если эти данные в промежутке между преобразованиями не выводятся на экран пользователя.
По этой причине написанные на Perl сетевые программы, ожидающие или возвращающие IP-адрес, используют четырехбайтовую строку, одному символу которой соответствует один байт в памяти. Хотя конструирование и интерпретация такой байтовой строки — довольно простая задача, решаемая с помощью функций chr и ord (здесь не представленных), в Perl используется более эффективное решение, которое в равной степени применимо и к более сложным структурам.
Функция pack по принципу работы немного похожа на функцию sprintf. Она получает строку, задающую формат, и список значений и упаковывает значения в одну строку. Однако в pack строка, задающая формат, предназначена для создания двоичной структуры данных. Например, вот как можно взять четыре небольших целых числа и упаковать их в виде последовательности байтов без знака в строке:
|
$buf = pack("CCCC", 140, 186, 65, 25);
|
Здесь строка формата pack — четыре буквы С. Каждая С соответствует отдельному значению, взятому из приведенного следом списка (подобно тому, что делает спецификация % в функций sprintf). Формат С (согласно man-страницам Perl, краткому справочнику, книге Programming Perl, HTML-файлам и даже видеоролику Perl: The Motion Picture) обозначает один байт, вычисляемый из символьного значения без знака (короткого целого). Отрока-результат в переменной $buf представляет собой четырехсимвольную строку, в которой каждый символ задан одним байтом. Эти байты имеют значения 140, 186, 65 и 25 соответственно.
Аналогичным образом формат 1 генерирует длинное значение со знаком. На многих машинах это четырехбайтовое число, хотя этот формат зависит от конкретной машины. На четырехбайтовой "длинной" машине оператор
|
$buf = pack("l",0x41424344);
|
генерирует четырехсимвольную строку, состоящую из символов abcd или dcba - в зависимости от того, какой порядок хранения байтов используется на данной машине: "младший в младшем" или "старший в младшем" (либо что-то совершенно иное, если эта машина "не говорит" на ASCII). Это объясняется тем, что мы упаковываем одно значение в четыре символа (для представления длинного целого отводится четыре байта), а это одно значение как раз состоит из байтов, представляющих коды ASCII первых четырех букв алфавита. Аналогичным образом,
|
$buf = pack("ll", 0x41424344, 0x45464748);
|
создает восьмибайтовую строку, состоящую из букв abcdefgh или dcbahgfe, опять-таки в зависимости от того, какой порядок хранения байтов используется в данной машине — "младший в младшем" или "старший в младшем".
Полный перечень различных форматов, используемых для упаковки, приведен в справочной документации (perlfunc(l) или Programming Perl). Мы приведем некоторые из них как примеры, но все, конечно, давать не будем.
Допустим, вам дали восьмибайтовую строку abcdefgh и сказали, что она является представлением хранящихся в памяти (один символ — один байт) двух длинных (четырехбайтовых) значений со знаком. Как ее интерпретировать? Нужно воспользоваться функцией, обратной функции pack,— функцией unpack. Она берет строку управления форматом (как правило, идентичную той, которую вы указывали в функции pack) и строку данных и возвращает список значений, которые хранятся в соответствующих ячейках памяти. Давайте, например, распакуем такую строку:
|
($val1,$val2) = unpack("ll","ABCDEFGH");
|
Это даст нам в переменной $vall нечто вроде 0х41424344, а может быть, и 0х44434241 (в зависимости от порядка хранения байтов). По сути дела, по возвращаемым значениям мы можем определить, на какой машине работаем — с порядком "младший в младшем" или "старший в младшем".
Пробельные символы в строке, задающей формат, игнорируются и используются лишь для удобочитаемости. Число в этой строке, как правило, задает повторение предыдущей спецификации соответствующее количество раз. Например, сссс можно записать как С4 или С2С2, смысл от этого не изменится. (Однако в некоторых спецификациях число, указанное после символа, задающего формат, является частью спецификации, поэтому их подобным образом записывать нельзя.)
После символа формата может стоять также звездочка, которая задает повторное применение данного формата до тех пор, пока не обработана остальная часть списка значений или пока не создана остальная часть строки, содержащей двоичное представление (в зависимости от того, что выполняется — упаковка или распаковка). Вот еще один способ упаковки четырех символов без знака в одну строку:
|
$buf = pack("C*", 140, 186, 65, 25);
|
Здесь указанные четыре значения полностью обрабатываются одной спецификацией формата. Если бы вам требовались два коротких целых и "максимально возможное количество символов без знака", то можно было бы написать примерно так:
|
$buf = pack("s2 C*", 3141, 5926, 5, 3, 5, 8, 9, 7, 9, 3, 2);
|
Здесь мы получаем первые два значения как короткие (и генерируем, вероятно, четыре или восемь символов), а остальные девять — как символы без знака (и генерируем, почти наверняка, девять символов).
Функция unpack со звездочкой в качестве спецификации может формировать список элементов, длина которых заранее не определена. Например, при распаковке с использованием формата с* создается один элемент списка (число) для каждого символа строки. Так, оператор
|
@values = unpack("C*", "hello, world!\n");
|
позволяет сформировать список из 14 элементов, по одному для каждого символа строки.
Perl поддерживает сетевое программирование средствами, которые хорошо знакомы тем, кто писал программы для сетевых приложений на С. По сути дела, большинство функций Perl, обеспечивающих доступ к сети, имеют и те же имена, что их С-коллеги, и похожие параметры. В этой главе мы не можем привести полную информацию по сетевому программированию, поэтому просто рассмотрим фрагмент сетевого приложения.
Один из параметров, который вам приходится часто определять,— это IP-адрес, соответствующий сетевому имени (или наоборот). В С вы преобразуете сетевое имя в сетевой адрес с помощью программы gethostbyname(3). Затем, используя полученный адрес, вы устанавливаете связь между своей программой и другой программой, которая работает где-то в другом месте.
В Perl функция, преобразующая хост-имя в адрес, имеет то же имя, что и С-программа, и похожие параметры. Выглядит она так:
($name, $aliases, $addrtype, $length, @addrs) = gethostbyname($name);
# основная форма функций gethostbyname
|
Параметр этой функций — имя хоста, например, slate.bedrock.com, а возвращаемое значение — список из четырех и более параметров (в зависимости от того, сколько адресов связано с данным именем). Если имя хоста недействительно, функция возвращает пустой список.
Когда gethostbyname вызывается в скалярном контексте, возвращается только первый адрес.
Если gethostbyname завершается успешно, то переменной $name в качестве значения присваивается каноническое имя, которое, если входное имя — псевдоним, отличается от входного имени. Значение переменной $aliases — это список разделенных пробелами имен, под которыми данный хост известен в сети. Переменная $addrtype содержит кодовое обозначение формата представления адреса. Для имени slate. bedrock. corn мы можем предположить, что это значение указывает на IP-адрес, обычно представляемый как четыре числа из диапазона от 1 до 256, разделенных точками. Переменная $length содержит количество адресов. Это лишняя информация, так как в любом случае можно посмотреть на размер массива @addrs.
Наиболее полезная часть возвращаемого значения — массив @addrs. Каждый элемент данного массива — это отдельный IP-адрес, представленный во внутреннем формате и обрабатываемый в Perl как четырехсимвольная строка*. Эта четырехсимвольная строка представлена в форме, понятной для других сетевых Perl-функций. Однако предположим, что нам требуется вывести результат в виде, удобном для пользователя. В данном случае нам нужно с помощью функции unpack и еще нескольких операций преобразовать возвращаемое значение в удобочитаемый формат. Вот код, который обеспечивает вывод одного из ІР-адресов хоста slate.bedrock.com:
($addr) = (gethostbyname("slate.bedrock.com"))[4];
print "Slate's address is ",
join(".",unpack ("C4", $addr)),"\n";
|
Функция unpack получает четырехбайтовую строку и возвращает четыре числа. Оказывается, они стоят именно в том порядке, который нужен функции join для того, чтобы она вставила между каждой парой чисел точку и представила таким образом все это в удобочитаемой форме. Информация о простых программах-клиентах приведена в приложении В.
Ответы к упражнениям приведены в приложении А.
- Напишите программу, которая создает таблицу соответствия идентификаторов пользователей и реальных имен из записей файла паролей, а затем с помощью этой таблицы выводит список реальных имен, принадлежащих каждой группе, упомянутой в файле групп. (Включает ли ваш список тех пользователей, у которых в записи файла паролей стоит группа по умолчанию, но в записи файла групп явного упоминания этой группы нет? Если не включает, как это сделать?)
|
|
|