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

       

Подпрограммы для создания и удаления учетных записей в Unix



Подпрограммы для создания и удаления учетных записей в Unix

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

Зачем нужна эта очевидная попытка отвертеться? Этот метод приемлем, поскольку известно, что программы, входящие в состав операционной системы, хорошо «уживаются» с другими компонентами. В частности, этот метод:

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

Применение внешних программ для создания и удаления учетных записей обладает такими недостатками:

Различия операционных систем

В каждую операционную систему входит свой собственный набор программ, расположенных в разных местах и принимающих несколько различные аргументы. Это редкий пример совместимости, однако практически во всех распространенных вариантах Unix (включая Linux, но исключая BSD) используются максимально совместимые программы для удаления и создания пользователей: useradd и user-del. В вариантах BSD применяются adduser и rmuser, две программы со сходным назначением, но совершенно разными аргументами. Подобные различия могут значительно усложнить наш код.

Соображения безопасности

Вызываемые программы с переданными им аргументами будут видны всем, кто употребляет команду ps. Если создавать учетные записи только на защищенной машине (например, на основном сервере), риск утечки данных значительно снизится.

Зависимость от программы.

Если внешняя программа почему-либо изменится или будет удалена, то нашей системе учетных записей настанет «полный капут».

Потеря контроля

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

Эти программы редко делают все

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

В случае с нашей демонстрационной системой учетных записей преимущества перевешивают недостатки, поэтому посмотрим на примеры кодов, в которых используется вызов внешних программ. Чтобы ничего не усложнять, мы покажем пример программы, работающей только на локальной машине с Linux и Solaris, и проигнорируем все трудности, вроде NIS и вариаций BSD. Если вам хочется посмотреть на более сложный пример этого метода в действии, поищите семейство модулей Cf gTie Рэнди Мааса (Randy Maas).

Вот основная программа, необходимая для создания учетной записи:

# На самом деле эти переменные надо определить в центральном

# конфигурационном файле

сluseraddex = "/usr/sbin/useradd"; ft путь к useradd $passwdex = "/bin/passwd";



# путь к passwd $homel)nixdirs = "/home"; ft корневой каталог

# домашних каталогов Sskeldir = "/home/skel"; ft прототип домашнего

# каталога $defshell = "/bin/zsh"; ft интерпретатор no

# умолчанию

sub CreateUnixAccount{

my ($account,$record) = @_;

# конструируем командную строку, используя:

ft -с = поле комментария

ft -d = домашний каталог

и -д = группа (считаем равной типу пользователя)

ft -m = создать домашний каталог

ft -k = и скопировать файлы из каталога-прототипа

» -s = интерпретатор по умолчанию

# (можно также использовать -G group, group, group для

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

my @cmd = (Suseraddex,

"-с", $record->{"fullname"},

"-d", "$homeUnixdirs/$account",

"-g", $record->{"type"},

"-m",

"-k", $skeldir,

"-s", Sdefshell,

laccount);

print STOERR "Creating account..."; ^

my $result = Oxff & system @cmd;

# код возврата 0 в случае успеха и не 0 при неудаче,

и поэтому необходимо инвертирование

if (!$result){

print STDERR "failed.\n";

return "Suseraddex failed"; } else {

print STDERR "succeeded.\n"; }

print STDERR "Changing passwd...";

unless ($result = &InitUnixPasswd($account,$record->{"password"»){

print STDERR "succeeded.\n";

return ""; > else {

print STDERR "failed.\n";

return $result; } >

В результате необходимая запись будет добавлена в файл паролей, будет создан домашний каталог для учетной записи и скопированы некоторые файлы окружения (.profile, .tcshrc, .zshrc, и т.д.) из каталога-прототипа.

Обратите внимание, что мы используем отдельный вызов для установки пароля. Команда useradd на некоторых операционных системах (например, Solaris) оставляет учетную запись заблокированной до тех пор, пока для этой учетной записи не будет вызвана программа pass-wd. Подобный процесс требует известной ловкости рук, поэтому мы оформим данный шаг как отдельную подпрограмму, чтобы оставить в стороне подробности. Об этой подпрограмме мы еще поговорим, а пока рассмотрим «симметричный» код, удаляющий учетные записи:

# На самом деле эти переменные надо устанавливать в центральном

# конфигурационном файле

$userdelex = "/usr/sbin/userdel";

# путь к userdel

sub DeleteUnixAccount{

my ($account,Srecord) = @_;

# конструируем командную строку, используя:

# -г - удалить домашний каталог

my @cmd = (Suserdelex, "-r", $account);

print STDERR "Deleting account.,.";

my $result - Oxffff & system tJcmd;

tt код возврата 0 соответствует успеху, не 0 - неудаче,

№ поэтому необходимо инвертирование

if ('$result){

print STDERR "succeeded.\n";

return ""; } else {

print STDERR "failed.\n":

return "Suserdelex failed";

}

}

Перед тем как перейти к операциям с учетными записями в NT, разберемся с подпрограммой InitUmxPasswdO, о которой упоминалось раньше. Чтобы завершить создание учетной записи (по крайней мере, в Solaris), необходимо изменить ее пароль при помощи стандартной команды passwd. Обращениеpasswd <accountname> изменит пароль для этой учетной записи.

Звучит просто, но тут затаилась проблема. Команда passwd запрашивает пароль у пользователя. Она принимает меры предосторожности, чтобы убедиться, что общается с настоящим пользователем, взаимодействуя напрямую с его терминалом. В результате следующий код работать не будет:

и такой код РАБОТАТЬ НЕ БУДЕТ open(PW,"|passwd Saccount")

print PW $olcipasswd, "\n"; print PW Snewpasswd,"\n";

На этот раз мы должны быть искуснее, чем обычно; нам нужно как-то заставить команду passed думать, что она имеет дело с человеком, а не программой на Perl. Этого можно достичь, если использовать модуль Expect.pm, написанный Остином Шутцом (Austin Schutz), - ведь он устанавливает псевдотерминал (pty), внутри которого выполняется другая программа. Expect.pm основан на известной Tel-программе Expect Дона Либеса (Don Libes). Этот модуль входит в семейство модулей, взаимодействующих с программами. В главе 6 мы рассмотрим его близкого «родственника», модуль Net: :Telnet Джея Роджерса (Jay Rogers).

Эти модули действуют в соответствии со следующей моделью: они ждут вывода программы, посылают ей на ввод данные, ждут ответа, посылают некоторые данные и т. д. Приведенная ниже программа запускает команду passed в псевдотерминале и ждет до тех пор, пока та запросит пароль. Поддержание «разговора» с passwd не должно требовать усилий:

use Expect;

sub InitUnixPasswd {

my (Saccount,Spasswd) = @_;

ft вернуть объект

my $pobj - Expect->spawn($passwdex, Saccount);

die "Unable to spawn $passwdex:$!\n" unless (defined SpoDj):

it не выводить данные на стандартный вывод (т. е.

# работать молча)

$pobj->log_stdout(0);

# Подождать запроса на ввод пароля и запроса на повторение

# пароля, ответить. $pobj->expect(10,"New password: ");

и Linux иногда выводит подсказки раньше, чем он готов к вводу,

print $pob] "$passwd\r"

$pobi->expect(10, "Re-enter new password: "); print $pob] "$passwd\r";

it работает"

Sresul: = (defined ($pobj-~ expectdO.

"successfully changeo")) ? "" : "password crarac.

failed"): в закрываем обьект, ждем 15 секунд, пока процесс завершится

$pobj-'suft_close();

return $resuJ t.:

}

Модуль Expect.pm очень хорошо подходит для этой подпрограммы, но стоит отметить, что он годится для куда более сложных операций. Подробную информацию можно найти в документации и руководстве по модулю Expect.pm.




Подпрограммы для создания и удаления учетных записей в Windows NT/2000

Процесс создания и удаления учетных записей в Windows NT/2000 несколько проще, чем в Unix, поскольку стандартные вызовы API для этой операции существуют в NT. Как и в Unix, мы могли бы вызвать внешнюю программу, чтобы выполнить подобную работу (например, вездесущую команду net с ключом USERS/ADD), но проще использовать API-вызовы из многочисленных модулей, о некоторых из которых мы уже говорили. Функции для создания учетных записей есть, например, в Win32::NetAdmin, Win32: :UserAdmin, Win32API::Net и Win32::Lanman. Пользователям Windows 2000 лучше ознакомиться с материалом по ADSI в главе 6.

Выбор одного из этих модулей, в основном, дело вкуса. Чтобы разобраться в отличиях между ними, рассмотрим существующие вызовы для создания пользователей. Эти вызовы описаны в документации Network Management SDK на http://msdn.microsoft. com (если вы ничего не можете найти, поищите «NetUserAdd»). NetllserAdd() и другие вызовы принимают в качестве параметра информационный уровень данных. Например, если информационный уровень равен 1, структура данных на С, передаваемая вызову для создания пользователя, выглядит так:

typedef struct JJSER_INFO_1 {

LPWSTR usri1_name;

LPWSTR usri1_oassworc';

DWORD usril_passwora_age:

DWORD usril_oriv:

LPWSTR usril_home_dir;

LPWSTR usri1_comment;

DWORD usri1_flags:

LPWSTR usri1_script_pat!i:

}

Если используется информационный уровень, равный 2, структура значительно расширится:

typedef struct _UbER_INrG;

LPWSTR usn2_name;
LPWSTP lisri? password:
DWORD usri2_password_age:
DWORD usn2_priv:
LPWSTR Lisri2_home_dir;
LPWSTR usri2_conwient :
DWORD usri2_flags;
LPWSTR usri2_scnpt_path;
DWORD usri2_auth_f lags;
LPWSTR usri2_fiJll_name:
LPWSTR usri2_usr_comment;
LPWSTR usri2_parms:
LPWSTR usri2_workstations:
DWORD usri2_last_logon;
DWORD usn2_last_logoff ;
DWORD usri2_acct_expires;
DWORD usri2_max_storage;
DWORD usri2_units_per_week;
PBYTE usri2_logon_hours;
DWORD usri2_bad_pw^count;
DWORD usri2_num_logons;
LPWSTR usri2_logon_server;
DWORD usri2_country_code;
DWORD usri2_code_page;
He обязательно много знать об этих параметрах или даже вообще о С, чтобы понять, что при изменении уровня увеличивается количество информации, которое можно передать при создании пользователя. Кроме того, каждый последующий уровень является надмножеством предыдущего.

Какое это имеет отношение к Perl? Каждый упомянутый модуль требует принять два решения:

  1. Нужно ли объяснять программистам на Perl, что такое «информационный уровень»?
  2. Какой информационный уровень (т. е. сколько параметров) может использовать программист?
Модули Win32API: :Net и Win32: :UserAdmin позволяют программисту выбрать информационный уровень. Win32; : NetAdmin и Win32: : Lanrnan этого не делают. Из всех этих модулей Win32; :NetAdmin применяет наименьшее число параметров; в частности, вы не можете определить поле на этапе создания пользователя. Если вы решите применять модуль Win32; :NetAcmin, вам, скорее всего, придется дополнить его вызовами из другого модуля, чтобы установить те параметры, которые он устанавливать не позволяет. Если вы остановитесь на комбинации Win32 : : NetAarn.in и Win32 : : AaminMisc, вам стоит обратиться к многократно упомянутой книге Рота, поскольку это отличный справочник по модулю Win32: : NetAdmin, по которому нет достаточного количества документации.

Теперь читателю должно быть понятно, почему выбор модуля - это де ло личных предпочтений. Хорошей стратегией было бы сначала решить, какие параметры важны для вас, а затем найти модуль, который их поддерживает. Для наших демонстрационных подпрограмм мы выбираем модуль Win32: : Lanman. Вот какой код можно применить для создания и удаления пользователей в нашей системе учетных записей:

use Win32: :Lanman; tt для создания учетной записи

use Win32::Perms; # для установки прав на домашний каталог

$homeNTdirs = "\\\\homeserver\\home"; # корневой каталог

# домашних каталогов

sub CreateNTAccount{

my ($account,$record) = @_;

П создаем учетную запись на локальной машине

# (т. е., первый параметр пустой)

$result = Win32::Lanman::NetUserAdd("",

{'name' => Saccount,

'password' => $record->{password},

'home_dir' => "$homeNTdirs\\$account",

'full_name' => $record->{fullname}});

return Win32::Lanman::6etLastError() unless ($result);

добавляем в нужную ЛОКАЛЬНУЮ группу

(предварительно мы Я получаем SID учетной записи)

# Мы считаем, что имя группы совпадает с типом учетной

die "SID lookup error: ".Win32::Lanman::6etLastError()."\n"

unless (Win32: :Lanman: :LsalookupNames("", [$account],

\@info)); $result = Win32::Lanman::NetLocalGroupAddMember("",

$record->{type), ${$info[0]){sid»;

return Win32::Lanman::GetLastError() unless (Sresult);

# создаем домашний каталог

mkdir "$homeNTdirs\\$account",0777 or

return "Unable to make honedir:$!";

№ устанавливаем ACL и владельца каталога

$acl = new Win32::Perms("$homeNTdirs\\$account");

$acl->0wner($account);

# мы предоставляем пользователю полный контроль за

# каталогом и всеми файлами, которые будут в нем созданы

# (потому и два различных вызова)

DIRECTORY | СОНТШЕВ_ШЕИТ_АСЕ);

$acl->Allow($account, FULL, -

FILE|OBJECT_INHERIT_ACE|IMHERIT_ONLY_ACE);

$result = $acl->Set(); $acl->Close();

return($result ? "" : Sresult); }

Программа для удаления пользователей выглядит так:

use Win32: iLanman;

для удаления учетной записи

use File::Path;

для рекурсивного удаления каталогов

sub DeleteNTAccount{

my($account,$record) = @_;

# удаляем пользователя только из ЛОКАЛЬНЫХ групп.

Если мы № хотим удалить их и из глобальных групп, мы можем убрать

слово "Local" из двух вызовов Win32::Lanman::NetUser

(например, NetUserGetGroups)

die "SID lookup error: ".Win32::Lanman::GetLastError()."\n"

unless (Win32::Lanman::LsaLookupNames("",

[Saccount], \@info));

Win32::Lanman::NetUserGetLocalGroups($server, Saccount, ",

\@groups); foreach $group (@groups){

print "Removing user from local group ".

$group->{name}."...";

print(Win32::Lanman::NetLocalGroupDelMember("",

$group->{name}, ${$info[0]}{sid})?

"succeeded\n" : "FAILED\n"); }

tt удалить эту учетную запись с локальной машины

(т. е., перый параметр пустой) Sresult = Win32::Lanman::NetUserDel("", Saccount);

return Win32::Lanman::GetLastError() if ($result);

удалить домашний каталог и его содержимое

Sresult = rmtree("$homeNTdirs\\$account",0,1);

rmtree возвращает число удаленных файлов, так что если мы

удалили более нуля элементов, то скорее всего все прошло 8 успешно return Sresult;

}

Заметьте, для удаления домашнего каталога здесь используется переносимый модуль File: :Path. Если бы мы хотели сделать что-то специфичное для Win32, например, переместить домашний каталог в корзину, то могли бы сделать это при помощи модуля Win32: :File Op Йенды Крыники (Jenda Krynicky), который можно найти на http://jen da.krynicky.cz/. В таком случае мы применили бы Wi n32: ; F: 1еОр и изменили бы строку, включающую rmtrc-e(), на:

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

# действие пользователем, если для этой учетной записи

# необходимо подтверждать такие операции

$result = Recycle("$homeNTdirs\\$account");

В данном модуле есть функция Delete(), которая выполняет то же, что и rmtree() менее переносимым (правда, более быстрым) способом.

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