ОСВЕДОМЛЕННОСТЬ ПОЛЬЗОВАТЕЛЯ
activ | показать активность терминалов |
info | показать информацию о паролях пользователей |
uchk | посмотреть процессы других пользователей |
watch | наблюдать за регистрацией в системе |
whox | команда who с дополнительными возможностями |
| | root tty01 01:23 | sage tty05 . | batch tty12 old | nuucp tty16 . |
Важным вопросом является то, как система узнает, когда кто-либо работает с клавиатурой? Поскольку терминальные устройства являются файлами, мы можем собрать информацию о терминале путем просмотра состояния файла, связанного с ним. В данном случае искомая информация - время последнего использования клавиатуры.
То, что мы ищем, уже есть в системе. Это опция -u команды who. Эта опция, однако, существует только в системе AT&T UNIX System V, поэтому команда activ, по крайней мере в представленном виде, может быть реализована только в этой системе. Activ берет выход команды "who -u" и "отрезает" некоторые данные, не имеющие отношения к нашей цели.
Путем проб и ошибок, а затем проверки исходного кода я обнаружил, что значение, используемое в команде "who -u", является временем "модификации" файла устройства, которое сообщает системный вызов stat(2). На страницах руководства по stat дается полная таблица системных вызовов, которые изменяют временные характеристики файлов. В табл. 6.1 представлены эти характеристики:
Таблица 6.1
Временные характеристики файла
Временные характеристики | Системные вызовы, изменяющие временные характеристики |
Время доступа | creat, mknod, pipe, utime, read |
Время модификации | creat, mknod, pipe, utime, write |
Время создания | creat, mknod, pipe, utime, write, chmod, chown, link |
Однако в системе UNIX имеется третье время - время создания. Система не позволяет вам изменить это время при помощи обычных команд. Системный вызов utime(2) обеспечивает изменение только времени доступа и модификации. Команда touch(1) также может изменить время доступа и модификации, но не время создания.
Команда touch строится только на системном вызове. Она может делать не больше того, что обеспечивает системный вызов. Команда fsdb(1) (отладчик файловой системы) является единственным способом преодоления защиты от изменения времени создания. Но даже fsdb не может управлять временем создания. Вы должны выйти за пределы поддерживаемых fsdb требований безопасности. Как это сделать, показано в последующих главах.
В случае изменения регистрационной записи, рассмотренном выше, вы могли бы посмотреть на время создания и увидеть, что оно очень близко к текущему времени. Если же кто-либо использовал fsdb для подделки времени создания, вы никогда не будете иметь уверенности в том, что файл не был изменен.
Какое же все это имеет отношение к определению того, делал ли пользователь что-либо недавно за своим терминалом? Время модификации изменяется вызовом write(2). Поэтому представляется разумным, что запись на терминал будет иметь место, когда драйвер читает символ, а затем посылает его обратно на экран. По мере того, как кто-то нажимает на клавиши, время модификации постоянно обновляется при эхо-отображении символов. Когда символы перестают поступать с клавиатуры, приостанавливается запись в файл терминала. Формула для определения последнего времени работы за терминалом (last_activity) такова:
last_activity = time(NULL) - mod_time,
где mod_time - время модификации. Эта формула закодирована внутри команды who(1). Отметим, что вызов команды time со значением NULL возвращает истинное текущее время.
Команда activ не имеет опций. Если вы используете опции, напечатается сообщение об ошибке.
ПОЯСНЕНИЯ
В строках 4-8 выполняется проверка на наличие ошибок. Если число аргументов командной строки больше нуля, на стандартное устройство регистрации ошибок выводится сообщение и программа завершается с неудачным статусом.
Строка 10 - это команда, выполняющая вывод. Команда who вызвана с опцией -u для получения основных данных. Затем ее выход по конвейеру передается команде cut, которая отображает колонки 1-17 и 38-42. Тем самым печатается только три поля, как показано в нашем предыдущем примере.
ИМЯ: info
info Вывод на экран информации о пароле пользователя
НАЗНАЧЕНИЕ
Печатает информацию поля комментария из файла /etc/ passwd для указанного пользователя.
ФОРМАТ ВЫЗОВА
info login_name [ login_name ... ]
ПРИМЕР ВЫЗОВА
info russ Печатает информацию, которая хранится о пользователе russ
ТЕКСТ ПРОГРАММЫ
1 : 2 # @(#) info v1.0 Display password info on a user Author: Russ Sage 2а Отобразить парольную информацию пользователя 4 for NAME in $@ 5 do 6 USER=`grep "^${NAME}:" /etc/passwd` 7 echo "$NAME:\t`echo ${USER}|cut -d: -f6`\t` echo ${USER}|cut -d: -f5`" 8 done
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
NAME | Каждое имя, указанное в командной строке |
USER | Полная запись в файле /etc/passwd для данного имени |
Зачем нам нужен командный файл info?
Система UNIX использует конфигурационные файлы для хранения основной информации о пользователях и другой системной информации. Одними из наиболее популярных конфигурационных файлов являются /etc/group, /etc/passwd и /etc/inittab. Для получения информации о пользователях нам необходимо заглянуть в эти файлы.
В системе Berkeley имеется команда finger, которая получает информацию о пользователях из их регистрационных каталогов и из файла паролей. В System V не так много программ, выполняющих такого рода работу, поэтому нам необходимо разработать их.
Что делает info?
Info - это командный файл, который получает информацию из регистрационного каталога и комментарий о пользователе из файла /etc/passwd. Результат выглядит так:
| | name: home dir comments | имя: регистрационный каталог комментарии |
Если в вашей системе используется это поле комментариев, то эта информация может быть полезной. "Вручную" выполнить просмотр пользователей в файле паролей можно следующим образом:
grep login_name /etc/passwd
При этом распечатывается вся строка со всеми полями данных. Info берет эти необработанные данные и выделяет из них каталог регистрации пользователя и поле комментария.
В командной строке можно указать несколько регистрационных имен. Каждое имя берется по порядку из командной строки.
ПРИМЕР
$ for NAME in `cat /etc/passwd | cut -d: -f1` > do > NAMELIST="$NAMELIST $NAME" > done; info $NAMELIST
Имя каждого пользователя в файле паролей добавляется к списку имен. Затем этот список передается в командную строку info, которая печатает все данные. Смысл команды: "Дай мне информацию обо всех пользователях системы".
ПОЯСНЕНИЯ
Строки 4-8 - это цикл for, который обрабатывает все имена, переданные в командной строке. Для каждого переданного имени выполняются строки 6 и 7.
В строке 6 в переменную USER заносится результат команды grep, заключенной между символами ударения (`). Начиная с начала строки (обозначено символом ^), grep ищет имя, за которым следует символ двоеточия (:). Такое указание заставляет выполнять поиск образца только в первом поле файла паролей.
В строке 7 мы отображаем нашу выходную строку командой echo, внутри которой вложены другие команды echo. Мы могли бы получать элементы информации отдельно, присваивая их значения отдельным переменным, и затем создавать необходимый формат вывода, используя значения этих переменных. Однако помещение всей информации в командную строку работает быстрее, и текст программы более компактный, хотя и не очень читабельный. Мы можем также использовать символы форматирования в команде echo для форматирования нашей распечатки.
Сначала отображается имя в том же виде, как оно получено из командной строки. Затем выводится табуляция (\t). За первой табуляцией следует поле номер шесть из файла паролей. Поскольку мы еще не имеем этих данных, мы должны выделить их из значения переменной USER, которую мы уже имеем из предыдущей строки. Чтобы сделать это, мы командой echo выводим всю строку и выделяем шестое поле, используя разделяющие двоеточия. После этого поля мы выводим еще одну табуляцию и затем пятое поле файла паролей. Мы получили это поле таким же образом, как и шестое поле - эхо-отображением и выделением.
Такая техника получения данных медленная, поскольку вовлечены все процессы, но это самый быстрый путь сделать это на языке shell. Команда awk была бы понятнее и, возможно, быстрее, но наша реализация демонстрирует гибкость языка shell. Язык shell может выполнять почти все, но не всегда лучшим образом. Вот почему в некоторых случаях мы используем язык Си, в чем вы убедитесь по мере продвижения по нашей книге.
ИМЯ: uchk
uchk Проверка процессов, запущенных другим пользователем
НАЗНАЧЕНИЕ
Отобразить все процессы каждого пользователя, указанного в командной строке
ФОРМАТ ВЫЗОВА
uchk [-a] login_name [...]
ПРИМЕР ВЫЗОВА
uchk -a Вывод всех процессов, запущенных администраторами
ТЕКСТ ПРОГРАММЫ
1 : 2 # @(#) uchk v1.0 Check processes of another user Author: Russ Sage 2а Проверка процессов другого пользователя 4 trap "rm /tmp/ps$$ 2> /dev/null" 0 1 2 3 15 6 if [ $# -eq 0 ] 7 then echo "uchk: argument error" >&2 8 echo "usage: uchk [-a] login_name [ ... ]" >&2 9 exit 1 10 fi 12 if [ "`echo $1 | cut -c1`" = "-" -a "$1" != "-a" ] 13 then echo "uchk: invalid argument $1" >&2 14 echo "usage: uchk [-a] login_name [ ... ]" >&2 15 exit 1 16 fi 18 ADMIN="administrators names go here" 19 if [ "$1" = "-a" ] 20 then shift 21 set $ADMIN $@ 22 fi 24 ps -ef > /tmp/ps$$ 25 for NAME 26 do 27 echo 28 fgrep "$NAME" /tmp/ps$$ 29 done
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
ADMIN | Строка с именами пользователей, являющихся администраторами в вашей системе |
NAME | Содержит каждое имя, считываемое из командной строки |
Зачем нам нужен командный файл uchk?
Поскольку UNIX является многопользовательской системой, множество задач выполняются одновременно. Единственный способ следить за такими задачами - с помощью команды ps. Ps - это довольно специфичная команда в том смысле, что она должна обращаться к памяти (/dev/mem) и проходить по связанному списку структур процесса. Это тяжелая вещь, поэтому нелегко сделать свою собственную команду для вывода такого рода информации. Вдобавок память защищена, и доступ к ней имеет только пользователь root.
Так что мы должны или удовлетвориться опциями, которые дает команда ps, или модифицировать вывод этой команды. Uchk скорее относится к последней категории. Мне хотелось получить список состояний процессов по имени пользователя, а не смесь информации, относящейся к разным пользователям. Хотя то, что я хотел, не соответствует ни одной из стандартных опций команды ps, я смог модифицировать ее выход для получения того, что мне надо.
Что делает uchk?
Uchk - это средство, которое генерирует, анализирует и сообщает о процессах, запущенных каждым указанным пользователем. За один раз вы можете проверить столько пользователей, сколько вам нужно. Все процессы для каждого пользователя печатаются вместе.
Чтобы делать это, uchk должен работать с временными файлами. Система UNIX весьма элегантно управляет временными файлами. Shell позволяет использовать идентификатор процесса для создания уникальных имен файлов для каждого запуска. После того как утилита запущена, временные файлы очищаются путем использования команды trap в shell'е. Команда trap удаляет файл, если что-либо прервало выполнение командного файла или когда программа завершается. Это прекрасная особенность, которая исключает накопление случайных файлов в системе.
Если uchk была вызвана без аргументов или была использована недопустимая опция, то печатается сообщение об ошибке и выполнение завершается.
Uchk имеет переменную ADMIN, которая определяет всех администраторов вашей системы. Отредактируйте символьную строку, присваиваемую переменной ADMIN, указав имена администраторов, которых вы хотите проверить. Имена должны быть разделены пробелами. Это позволит вам проверять процессы ваших администраторов, указывая опцию -a в командной строке. Все остальные имена должны быть указаны в командной строке. Естественно, вы таким же образом могли бы установить другие группы пользователей. Слежение за администраторами поможет вам оценить, как они управляют сохранностью информации, а также распознать административные задачи, которые имеют склонность загружать процессор.
ПРИМЕР
$ uchk -a russ uucp
Показывает процессы всех администраторов, мои собственные, и процессы uucp по порядку. Все сообщения об ошибках выводятся на стандартное устройство регистрации ошибок, а списки процессов выводятся на стандартное устройство вывода.
ПОЯСНЕНИЯ
Строка 4 - это оператор trap. Символьная строка между двойными кавычками содержит команды, которые должны быть выполнены, когда происходит прерывание. В этом случае мы удаляем временный файл и переадресовываем все выходные данные на нулевое устройство. Когда команда rm пытается удалить несуществующий файл, выводятся сообщения об ошибках. Поскольку мы не знаем, какие файлы могут быть в наличии в тот момент, когда возникнет прерывание, то мы хотим избавиться от сообщений об ошибках. Оператор trap активизируется по выходу из программы (program exit, сигнал 0), разрыву линии (hangup, сигнал 1), прерыванию (interrupt, сигнал 2), выходу (quit, сигнал 3) или программному завершению (software termination, сигнал 15).
В строках 6-10 проверяется, переданы ли какие-либо аргументы. Если вы вызвали uchk без аргументов, выводится сообщение об ошибке и uchk завершается.
В строках 12-16 проверяется, указана ли какая-то опция со знаком минус и является ли она опцией -a - единственно допустимой опцией. Командный файл делает это, применяя команды проверки для сравнения двух различных случаев. Первая проверка вырезает первый символ первого позиционного параметра и смотрит, является ли он символом "-". Следующая проверка делается для того, чтобы увидеть, что первый позиционный параметр не является опцией -a. Поскольку обе проверки соединены операцией AND, то для получения значения "истина" они обе должны быть истинными. Если они обе истинны, выводится сообщение об ошибке и uchk завершается.
Почему мы должны выполнять такую сложную проверку? Проблема в том, что у нас нет иного способа определить, является первый позиционный параметр опцией или нет. Он может быть опцией, а может быть именем, которое нужно искать. Таким образом, мы должны задать вопрос: "Является ли это опцией, и если да, то допустима ли эта опция?"
В строке 18 переменная ADMIN инициализируется символьной строкой, следующей за ней. Это строка, которую вы должны модифицировать в соответствии с вашей системой. Все, что вы должны сделать - это использовать редактор vi и вставить в эту строку имена администраторов, разделенные пробелами. Ниже мы используем переменную ADMIN для обработки аргументов командной строки.
В строках 19-22 проверяется, была ли указана в командной строке опция -a. Если да, эта опция удаляется из командной строки с целью избавления от нее. Строка 21 использует команду set для того, чтобы поместить символьную строку ADMIN в позиционные параметры. Команда set вставляет значение переменной ADMIN, начиная с первого позиционного параметра, и сдвигает все настоящие параметры вправо. Тем самым в цикл for передается множество имен, которые должны быть обработаны.
В строке 24 выполняется команда ps для всех пользователей. Эта команда использует опцию f для вывода большого количества данных. Результат помещается во временный файл, в имени которого применяется идентификационный номер процесса. Этот один большой файл представляет собой источник для остальных распечаток. Возможно, все это немного отстает от реального времени, но программа выполняется гораздо быстрее, чем при многократном вызове команды ps.
Строки 25-29 представляют собой цикл for, который выполняется от $1 до $x, где x - последний позиционный параметр. Для каждого имени выполняется следующее: печатается пустая строка для разделения листинга (строка 27), затем командой fgreps (для ускорения) выбираются из временного файла все процессы, принадлежащие данному пользователю. Благодаря применению команды fgrep для каждого имени пользователя, все процессы данного пользователя печатаются за один раз. Когда закончится проверка всех имен, указанных в командной строке, цикл завершится, завершится командный файл и сработает оператор trap, который удаляет временный файл.
ИМЯ: watch
watch Наблюдение за регистрацией указанных пользователей
НАЗНАЧЕНИЕ
Следит за тем, кто работает в системе, и сообщает о регистрации указанных пользователей.
ФОРМАТ ВЫЗОВА
watch [-k] [login_name ...]
ПРИМЕР ВЫЗОВА
watch Наблюдение за регистрацией всех пользователей, указанных во внутренней переменной LIST
ТЕКСТ ПРОГРАММЫ
1 : 2 # @(#) watch v1.0 Watch for specific logins Author: Russ Sage 2а Наблюдение за регистрацией пользователей 4 if [ "`echo $1 | cut -c1`" = "=" -a "$1" != "-k" ] 5 then echo "watch: invalid argument $1" >&2 6 echo "usage: watch [-k] [login_name ...]" >&2 7 echo " -k kill background process" 8 exit 1 9 fi 11 if [ "$1" = "-k" ] 12 then if [ -s $HOME/.watch ] 13 then echo "killed `cat $HOME/.watch`" 14 kill `cat $HOME/.watch` 15 rm $HOME/.watch 16 exit 0 17 fi 18 fi 20 echo $$ > $HOME/.watch 22 LIST="root sys bin administrator1 administrator2 $*" 24 while : 25 do 26 for NAME in `who | cut -d" " -f1` 27 do 28 for PERSON in $LIST 29 do 30 if [ "$NAME" = $PERSON" ] 31 then echo ONLINE: $NAME 32 fi 33 done 34 done 35 sleep 10 36 done &
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
HOME | Полный маршрут к вашему регистрационному каталогу |
LIST | Список имен пользователей системы, разделенных пробелами |
NAME | Содержит имена зарегистрированных в настоящий момент пользователей |
PERSON | Отдельное имя из списка имен в переменной LIST |
Зачем нам нужен командный файл watch?
В течение рабочего дня множество людей входят в систему и выходят из нее. Иногда единственный способ связаться с человеком - через машину. Нам нужно средство, которое автоматически сообщит нам, что нужное лицо на связи.
Что делает watch?
Подразумевается, что watch является фоновой задачей, которая постоянно следит за тем, кто зарегистрировался. Когда лицо или лица, предварительно вами отмеченные, регистрируются в системе, на ваш экран выводится сообщение о том, что они на связи.
Количество имен, за которыми вы можете следить, не ограничено. Общий перечень имен - это объединение имен, указанных в командной строке, и списка системных пользователей, т.е. пользователя root и всех сопровождающих его администраторов. В командном файле watch список системных имен ВСЕГДА включен. Это вызвано тем, что это важные пользователи и вы хотите всегда знать об их входе в систему и выходе из нее. Это отличает watch от uchk, поскольку последний требует указания опции -a для включения списка с системными именами.
Хотя мы устанавливаем watch с точки зрения пользователя, который желает следить за администраторами и другими пользователями, администраторы тоже могут иметь список "критических пользователей" или предполагаемых нарушителей защиты информации и использовать watch для получения сигнала о регистрации таких пользователей.
За кем бы мы ни следили, мы сталкиваемся с одной проблемой. После регистрации в системе указанного лица сообщение об этом поступает на ваш экран, не обращая внимания на то, чем вы в данное время занимались, что не очень приятно. Единственный способ остановить вывод сообщения - аварийно завершить watch командой kill. Это легко сделать путем помещения идентификатора этого процесса в файл $HOME/.watch. Затем этот номер может быть использован в операторе kill для остановки выполнения командного файла. Для того чтобы попроще избавиться от watch, возможность аварийного завершения оформлена в виде опции -k данного командного файла.
ПРИМЕРЫ
1. $ LIST="root bin" watch daemon
Если переменная LIST не была инициализирована внутри самого командного файла watch, то мы можем инициализировать ее на командном уровне shell'а, а затем вызвать watch. Список пользователей для watch будет такой: root, bin и daemon по порядку, поскольку watch добавляет имена, указанные в ее командной строке к именам, имеющимся в переменной LIST. Такой способ указания имен более гибок, чем жесткое программирование части списка в тексте командного файла watch, но он требует помнить больше информации и больше нужно набирать на клавиатуре.
2. echo "Watch (y/n): \c" read ANS if [ "$ANS" = "y" ] then watch fi
Это фрагмент, который вы можете вставить в ваш .profile. Когда вы регистрируетесь, вам автоматически предлагается запустить watch. Если вы ответите "y", watch станет фоновой задачей и запустится по умолчанию (просматривая пользователей согласно списку в переменной LIST). Если будет введен любой символ, отличный от y, watch не запустится.
ПОЯСНЕНИЯ
Строки 4-9 выполняют проверку на наличие ошибок в опциях командной строки. Для первого позиционного параметра проверяется, что он имеет тире и не является единственной допустимой опцией "-k". Если результатом проверки является истина, печатается сообщение об ошибке и командный файл завершается.
Строки 11-16 проверяют, что первый позиционный параметр - это -k. Если это так, значит пользователь хочет уничтожить уже запущенный процесс watch. В этом случае выводится сообщение, указывающее идентификатор процесса, который будет уничтожен, и выполнение продолжается. В строке 12 мы смотрим, существует ли в нашем регистрационном каталоге файл с именем .watch. Если нет, то это означает, что предыдущий экземпляр watch предположительно уже уничтожен и нет необходимости пытаться сделать это снова, поэтому происходит переход к оставшейся части программы и выполнение watch происходит так, как будто опция "-k" не была указана.
Если же файл .watch имеется, то в строке 14 используется команда kill для уничтожения фонового процесса watch. Напомним, что при использовании опции -k мы подразумеваем, что watch был вызван ранее, поэтому файл $HOME/.watch имеет идентификационный номер самого процесса watch. В строке 15 удаляется временный файл watch и в строке 16 происходит выход из программы. Теперь командный файл watch более не выполняется как фоновый.
Строка 20 выполняется, если опция -k не была указана или если нет файла .watch. (Последнее может произойти, если пользователь пытается уничтожить процесс, забыв, что он уже был уничтожен.) Если опция -k не была указана, мы можем считать, что watch был вызван, чтобы стать фоновым процессом и выполнять свою работу. Для того чтобы сделать это, текущий процесс отображает свой идентификатор процесса в файл .watch. Этот файл остается в вашем регистрационном каталоге до тех пор, пока он не будет удален вручную или же изменен путем повторного запуска watch.
В строке 22 инициализируется переменная LIST. Ее значением является символьная строка с именами, разделенными пробелами. Вам нужно вручную отредактировать переменную LIST перед запуском в вашей системе. Просто уберите ее нынешнее содержимое и вставьте туда имена администраторов вашей системы. Если в командной строке будут указаны дополнительные имена пользователей, они будут добавлены в переменную LIST посредством символов расширения параметров $*. Тем самым переменная LIST станет основным списком всех имен пользователей, за которыми будет вестись наблюдение.
Строки 24-36 выполняют цикл постоянного наблюдения. В начале каждой итерации с помощью команды who создается список имен пользователей, который передается циклу for в строке 26. Цикл for использует командную подстановку для получения списка слов, образованного из первого поля команды who. Каждое зарегистрированное имя сравнивается со списком предварительно определенных имен, за которыми мы наблюдаем. Обратите внимание, что внешний цикл while сам себя помещает на выполнение в фоновом режиме. Это означает, что вам нет необходимости вводить это с клавиатуры.
Строки 29-33 управляют внутренним циклом, который проходит по именам, содержащимся в нашем основном списке, и сравнивает их с именами, полученными от команды who. Когда имя, полученное от команды who (имя зарегистрированного пользователя) совпадает с именем в нашем списке, на экран выводится сообщение о том, что данное лицо зарегистрировалось.
После того как все имена проверены, командный файл watch приостанавливается на 10 секунд (строка 35). Когда он снова пробуждается, выполняется следующая итерация вечного цикла while. Все зарегистрированные имена вновь сравниваются со списком. Это будет продолжаться до тех пор, пока вы не прекратите выполнение watch. Как отмечалось ранее, watch можно легко уничтожить с помощью опции -k или же вручную путем ввода команды "kill `cat $HOME/.watch`".
МОДИФИКАЦИИ
Watch выполняет довольно мало работы и использует какую-то часть времени центрального процессора. Вы можете поэкспериментировать с увеличением интервала паузы (sleep), чтобы watch запускался не так часто. Большинство пользователей находятся в системе по крайней мере минуту, поэтому вы можете попробовать значение sleep(60). Вы по-прежнему можете обнаружить регистрацию всех интересующих вас пользователей?
ИМЯ: whox
whox Команда who с дополнительными возможностями
НАЗНАЧЕНИЕ
Предоставляет много дополнений к выходу команды who и позволяет применять данные who для других приложений.
ФОРМАТ ВЫЗОВА
whox [-f] [-n] [-m] [-p] [-t] [-w] [-x]
где
-f указывает каждого зарегистрированного пользователя
-n сортирует выход команды who по именам
-m передает почту каждому пользователю
-p выводит информацию о паролях пользователей
-t сортирует выход команды who по времени (умолчание)
-w показывает возможность записи на зарегистрированные терминальные устройства
-x дополнительная информация о регистрационном каталоге и паролях
ПРИМЕР ВЫЗОВА
whox -w
Показывает права доступа к файлу (возможность чтения и записи) для каждого зарегистрированного терминального устройства
ТЕКСТ ПРОГРАММЫ
1 : 2 # @(#) whox v1.0 Who with expanded options Author: Russ Sage 2а Команда who с дополнительными опциями 4 XTRA="no" 5 SORT="sort -b +2" 6 DISPLAY="norm" 8 CUT1="cut -d' ' -f1" 9 CUT5="cut -d: -f5" 10 CUT6="cut -d: -f6" 12 for ARG in $@ 13 do 14 case $ARG in 15 -f) DISPLAY="finger" 16 COMMAND="finger \$NAME; echo";; 17 -n) SORT="sort";; 18 -m) DISPLAY="mail";; 19 -p) DISPLAY="pass" 20 COMMAND="grep \"^\$NAME:\" /etc/passwd";; 21 -t) SORT="sort -b +2";; 22 -w) DISPLAY="write";; 23 -x) XTRA="yes";; 24 *) echo "whox: invalid option $ARG" 25 echo "usage: whox [-f] [-n] [-m] [-p] [-t] [-w] [-x]" 26 echo " -f finger users" 27 echo " -n sort by name" 28 echo " -m mail to each user" 29 echo " -p password info on users" 30 echo " -t sort by time (default)" 31 echo " -w show writeability of devices" 32 echo " -x extra home dir and gcos info" 33 exit 1;; 34 esac 35 done 37 if [ "$XTRA" = "yes" ] 38 then EXTRA="| while read LINE; do \ 39 NAME=\`echo \$LINE | cut -d' ' -f1\`;\ 40 ENTRY=\`grep \"^\$NAME:\" /etc/passwd\`;\ 41 echo \"\$LINE\t\`echo \$ENTRY|\$CUT6\`\t\`echo \$ENTRY|\$CUT5\` \";done" 42 else EXTRA="" 43 fi 45 case $DISPLAY in 46 norm) eval "who | $SORT $EXTRA";; 47 finger|pass) for NAME in `who | $SORT | cut -d' ' -f1` 48 do 49 eval $COMMAND 50 done;; 51 mail) who | cut -d' ' -f1 | while read NAME 52 do 53 echo "mail to $NAME (y/n): \c" 54 KB=`line < /dev/tty` 55 if [ "$KB" = "y" ] 56 then mail $NAME < /dev/tty 57 fi 58 done;; 59 write) ls -il `who | sed "s/...........\(.......\).* /\/dev\/\1/"`;; 60 esac
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
ARG | Аргументы командной строки |
COMMAND | Команда, которую следует выполнить при использовании команды who со списком имен |
CUT1 | Содержит синтаксис для выделения первого поля строки |
CUT5 | Содержит синтаксис для выделения пятого поля строки |
CUT6 | Содержит синтаксис для выделения шестого поля строки |
DISPLAY | Определяет, какой режим отображения использовать |
ENTRY | Запись в файле паролей для указанного пользователя |
EXTRA | Данная переменная содержит полный цикл shell-команд, хранимых в виде одной строки |
KB | Входные данные от клавиатуры, полученные в цикле |
NAME | Содержит в каждый данный момент времени одно имя из списка всех регистрационных имен |
SORT | Содержит выполняемый тип сортировки |
XTRA | Флаг, определяющий, должны ли быть активизированы дополнительные опции |
Зачем нам нужен командный файл whox?
Как уже ранее обсуждалось в других местах этой книги, система UNIX стремится обеспечить минимум возможностей в любой заданной области интересов. Это не значит, что UNIX плохая система. Наоборот, она делает гораздо больше, чем большинство других операционных систем. Но очень часто мы хотим сделать немного больше того, что нам предоставляет базовая система.
Получение информации о том, кто использует машину, может быть применено для многих целей. Основная информация предоставляется командой who, но она может быть не в том виде, который вам нужен для конкретной цели. В любом случае, все, что делает команда who - дает вам моментальную информацию о том, кто зарегистрировался. Нам необходим некоторый способ автоматического доступа к этому списку зарегистрированных имен и использования его для наблюдения, общения или других целей.
Что делает whox?
Whox - это инструментальное средство, расширяющее возможности команды who. Оно не только может переупорядочить в соответствии с вашими требованиями список, полученный от команды who, но также может указать каждого зарегистрированного пользователя, передать почтовое сообщение любому зарегистрированному пользователю, просмотреть парольную информацию всех зарегистрированных пользователей и показать информацию, взятую из индексного дескриптора файла, соответствующего терминальному устройству каждого зарегистрированного пользователя.
По умолчанию действие whox заключается в печати обычного выхода команды who в порядке времени регистрации от более раннего до более позднего. Опция -x добавляет к этому списку информацию из регистрационного каталога и поле комментария из файла паролей. Если эта опция -x кажется вам знакомой, то так оно и есть, поскольку это то же самое, что и команда info, представленная ранее.
Whox имеет четыре различных режима отображения. Первый это формат обычного выхода команды who. Whox позволяет вам сортировать его двумя разными способами. Опция -n сортирует по именам, а опция -t (которую не нужно указывать, поскольку она используется по умолчанию) сортирует по времени регистрации.
Второй режим отображения состоит из режимов указания и паролей, включаемых опциями -f и -p. Основное отличие от первого режима заключается в том, что выход команды who не печатается, а используется для генерации списка имен пользователей, который применяется для других целей. Мы указываем каждого пользователя или печатаем парольную запись каждого пользователя. Выполняемая команда хранится в переменной, поэтому мы можем иметь общий цикл, использующий особым образом переменные. (Команда finger имеется в системе Berkeley UNIX и в некоторых других, но не во всех реализациях. Посмотрите руководство, чтобы выяснить, что выводится на экран по этой команде.)
Третий режим - это почтовый режим, в котором вы имеете возможность посылки почтового сообщения каждому зарегистрированному пользователю. Вам задается вопрос о том, действительно ли вы хотите сделать это. Больше от вас ничего не требуется. Этот режим выбирает опция -m.
Последний режим - это режим записи на терминал. Режим записи (опция -w) показывает информацию о файле терминала для каждого зарегистрированного терминального устройства. Эта информация полезна, если вы хотите использовать команду UNIX'а write. Посмотрев на права доступа к файлу устройства пользователя, вы можете сказать, имеется ли у вас возможность записать текст на его экран. Некоторые пользователи, которые не хотят, чтобы их прерывали, закрывают право записи на их терминал, выполняя команду "mesg n". Вопрос о праве записи касается любого способа посылки текста в другой файл, а не только с использованием команды write. Право записи также защищает от таких вещей, как "echo hello > /dev/tty00".
Способ обработки аргументов в командной строке приводит к несколько странному обращению с данной утилитой. Каждый аргумент проверяется по порядку и устанавливает внутренние флаги. Если вы поставите в конце списка какую-либо опцию, меняющую флаг, установленный одной из предыдущих опций, то вы получите действие последней опции. (Другими словами, некоторые опции взаимно исключают друг друга. Лучший способ изучить это - внимательно прочитать исходный текст командного файла и выполнить несколько экспериментов с различными наборами опций.)
Например, мы хотим указать каждого пользователя. Мы используем опцию -f. Опция -f устанавливает в качестве режима отображения режим указания. Если мы поместим опцию -w справа от -f, как в команде "whox -f -w", то установится режим записи на терминал. Команда whox будет считать, что вы вообще не указывали опцию -f. На самом деле это не представляет большую проблему, если вы знаете, что делает каждая опция. Случайно смешивая их в одной команде, вы можете получить несколько странные выходные данные.
ПРИМЕРЫ
1. $ sh -x whox -x
Запуск интерпретатора shell в отладочном режиме выполнения, подача ему командного файла whox в качестве данных, передача опции -x для whox. Отладочный режим показывает присвоение значений переменным и вызовы команд. (Мы видели это ранее.) 2. $ whox -n -x
Печать выходных данных команды who, отсортированных по именам и выдача дополнительных данных.
ПОЯСНЕНИЯ
В строках 4-10 выполняется инициализация переменных. Переменная XTRA устанавливается в значение "no". Эта переменная используется для построения командной строки при использовании опции -x. Установка XTRA в значение "no" означает, что действие по умолчанию - не получать дополнительную информацию.
Умолчанием для сортировки является сортировка по времени регистрации. Это указывается сортировкой по колонке, стоящей после второй (+2) и игнорированием ведущих пробелов (-b) в строке 5. Такой же синтаксис применен ниже с опцией -t. Опция -t здесь лишняя, но она делает более понятной командную строку. Опция -n также изменяет синтаксис сортировки, чтобы просматривать первую колонку, которая является списком имен.
Строка 6 инициализирует обычный режим отображения, которым является печать стандартного выхода команды who. Если присутствуют другие опции, соответственно изменяется переменная DISPLAY.
Строки 8,9 и 10 инициализируют некоторые переменные для команд вырезки, что делает более компактными последующие команды. Если некоторые командные строки оказываются слишком длинными, вы можете поместить нужный текст в переменные и подставить их при необходимости. Переменная CUT1 выглядит так, как будто она должна работать, но она не работает в моей системе. Почему она не работает, объясняется ниже. Просто запомните, что эта строка никогда не используется в данной программе. Мы пока оставляем ее, чтобы поговорить о ней позже. Вы можете убрать ее, если хотите.
Строки 12-35 обрабатывают аргументы командной строки. Цикл for подставляет в переменную ARG каждый параметр по порядку и выполняет оператор case по значению ARG.
Если опцией является -f, строка 15 изменяет переменную DISPLAY в режим указания. Она также подставляет в переменную COMMAND команду finger, которая выполнится в следующем цикле. Причина, по которой нам нужно иметь переменную, содержащую команду, заключается в том, что данный цикл является общим циклом, применяемым для двух разных целей. Для того чтобы один и тот же цикл справился с двумя различными задачами, мы помещаем эти задачи в переменную и выполняем эту переменную.
Это значительно сокращает общее количество текста, хотя привносит некоторую дополнительную работу. Обратите внимание в строке 16, что символ $ экранирован в операторе присваивания. Это необходимо, ведь мы хотим, чтобы переменная COMMAND содержала символьную строку $NAME, а не значение, которое имеет переменная NAME после присваивания. Значение NAME расшифровывается в цикле во время его выполнения.
Строка 17 обрабатывает опцию -n. Все, что здесь требуется - изменить способ сортировки выхода команды who, чтобы отразить порядок по именам. Поскольку имя находится в первой колонке выхода команды who, а команда sort сортирует по умолчанию по первой колонке, то используется команда sort без опций.
Строка 18 обрабатывает опцию -m для передачи почтовых сообщений. Здесь мы должны изменить режим отображения на почтовый. Все, что нужно для этого, находится в цикле mail, и не требуется инициализировать никакие переменные.
Строки 19 и 20 справляются с опцией -p. Опция паролей изменяет режим отображения на парольный режим и устанавливает команду, которую мы вызываем, находясь в общем цикле. В данном случае мы используем команду grep для получения парольной записи из файла /etc/passwd. Обратите внимание, что в строке 20 мы используем внутренние двойные кавычки. Для этого мы вынуждены экранировать их символами обратной косой черты. Напомним, что обратная косая черта используется для отмены специального значения особых shell-символов.
Строка 21 управляет опцией -t. Как уже упоминалось ранее, опция -t в действительности не требуется в данной программе. Поскольку она является умолчанием, требуемые для нее действия уже были предприняты в начале программы - была выполнена точно такая же инициализация. Синтаксис команды sort точно такой же, как и в строке 5.
Строка 22 обрабатывает опцию -w для показа возможности записи в файлы терминалов. Единственное, что нужно здесь сделать - изменить режим работы терминала.
Строка 23 управляет опцией -x. Поскольку для получения дополнительной информации требуется довольно сложная инициализация, то мы только устанавливаем в этом месте флаг XTRA, показывающий, что мы хотим выполнить эту инициализацию позже.
Строка 24 - это улавливатель для обработки ошибок. Символ * соответствует любому символу, который не был распознан ранее. Печатается сообщение об ошибке и синтаксическая подсказка, и whox завершается.
Строки 37-43 устанавливают переменные, используемые в опции дополнительной информации. Строка 37 проверяет, установлена ли переменная XTRA в состояние "yes", что имеет место только тогда, когда в командной строке имелась опция -x. Если это так, то в переменную EXTRA заносится много всяких вещей, которые мы рассмотрим позже. В противном случае в переменную EXTRA заносится пустая строка, так что она никак не проявляется на стадии фактического выполнения.
Переменная EXTRA здесь очень важна и на самом деле делает небольшой фокус. Мы собираемся поместить в переменную некоторый код, требуемый для обработки опции -x. Поскольку дополнительная информация, которую мы хотим получить, требует предварительно некоторой обработки, то мы помещаем в переменную некоторый командный текст. Как только эта переменная начинает выполняться, выполняется и этот командный текст. Это похоже на макрокоманду, только ее текст находится фактически в исполняемой программе.
Строки 38-41 вставлены внутрь переменной EXTRA. Это сделано путем взятия в двойные кавычки всех четырех строк. Все специальные символы, которые должны быть частью данных в этой переменной, должны быть экранированы символами обратной косой черты. В строке 38 в переменную EXTRA заносится символ конвейера (|) и начало цикла while. В конце строки 38 имеется символ косой черты, указывающий интерпретатору shell, что присваивание продолжается после символа конца строки (возврата каретки или перевода строки).
Строка 39 присваивает переменной NAME значение поля, вырезанного из данных, читаемых в цикле while. Напомним, что весь данный оператор помещается внутрь переменной EXTRA. Когда я выше упоминал, что в строке с переменной CUT1 есть проблемы, то как одно из таких проблемных мест я имел в виду именно это. Когда я попытался использовать переменную CUT1 в этом операторе вместо указания команды cut, shell не смог правильно распознать этот оператор. Одинарные кавычки, отмечающие символ-разделитель для вырезки, не были распознаны. В результате команда cut считала, что символом-разделителем является символ ' и после этого аварийно завершалась, поскольку второй символ ' представлял собой недопустимое описание списка для опции -f. Строка с опцией -f шла позже, но команда cut этого никогда не узнавала, поскольку аварийно завершалась до этого. Когда я заменил переменную CUT1 просто командой cut, эта проблема исчезла.
Давайте рассмотрим, как я отлаживал эту часть. Я использовал shell с опцией -x, поэтому я мог следить за тем, что происходит. Как вы можете видеть, когда переменная CUT1 была инициализирована, одинарные кавычки находились все еще в операторе, но когда выполнялась настоящая команда cut, одинарные кавычки уходили при синтаксическом расширении. Для генерации такого списка данных я выполнил следующий вызов: sh -x whox -x. Вот что я увидел:
XTRA=no SORT=sort -b +2 DISPLAY=norm CUT1=cut -d' ' -f1 <- Одинарные кавычки все еще здесь. Основная проблема. CUT5=cut -d: -f5 CUT6=cut -d: -f6 XTRA=yes + who + read LINE + sort -b +2 + echo russ console Jun 20 14:11 + cut -d -f1 <- Теперь выполняется правильно. Кавычек нет.
Это сокращенная распечатка. Она показывает, что когда выполнялась команда cut, она не имела одинарных кавычек. Когда же запускалась переменная CUT1, она имела одинарные кавычки. Я не мог представить, как избавиться от кавычек, поэтому я просто вставил вызов самой команды cut обратно на это место. Может быть какой-нибудь молодой растущий мастер сможет себе это представить.
Во всяком случае, вы можете видеть полезность отладки.
Цикл, выполняющий такое же присваивание, имеет такой вид при обычном стиле записи на языке shell:
| while read LINE do NAME=`echo $LINE | cut -d' ' -f1` ENTRY=`grep "^$NAME:" /etc/passwd` echo "$LINE\t\`echo $ENTRY|$CUT6\`\t\`echo $ENTRY|$CUT5\`\" done
Для того чтобы поместить такой же цикл в переменную, мы должны экранировать в этом тексте все специальные символы.
Строки 45-60 - это оператор case, который реализует различные режимы отображения. Строка 46 выполняет обычный режим отображения команды who. Поскольку в обычном режиме имеется возможность использовать переменную EXTRA, нам необходимо произвести повторный разбор командной строки командой eval, чтобы эта переменная приняла свое истинное значение во время исполнения. Обратите внимание, что в команде eval имеются кавычки, заключающие всю командную строку. Это необходимо потому, что вся строка является одним набором входных данных для команды eval. Без кавычек команда eval не работала бы. Переменная EXTRA не подвергается повторному разбору.
Строки 47-50 управляют режимами указания пользователя и выдачи информации из файла паролей. Оба эти режима используют один и тот же цикл. Цикл for использован для установки переменной NAME в значение первого поля каждой строки, полученной от команды who. Для каждого имени, вырезанного из результата работы команды who, выполняется повторный синтаксический разбор командой eval переменной COMMAND (которая была установлена в операторе case, выполнявшем разбор аргументов). Тем самым повторно анализируются и выполняются команды, находящиеся в переменной COMMAND. Для режима указания пользователя переменная COMMAND содержит команду finger, а для режима паролей в COMMAND хранится команда grep.
Строки 51-58 похожи на режим указания пользователя. Этот цикл тоже требует имена от команды who, но вместо использования оператора for мы используем метод прямой пересылки по конвейеру. Результат работы команды who по конвейеру передается команде cut (переменная CUT1 и здесь бы не работала), которая по конвейеру передает данные в цикл чтения while. Обратите внимание, что в этом месте нет никакой сортировки. По умолчанию результат команды who выводится в порядке номеров терминальных устройств. Я не думаю, однако, что порядок вывода этих данных имеет большое значение.
Для каждого имени пользователя выводится запрос о том, хотите ли вы передать ему почтовое сообщение. При чтении ответа в строке 54 должна быть использована команда UNIX'а line. Почему? Потому что весь цикл использует оператор read для чтения имен. Оператор read читает только со стандартного ввода, который в данном случае привязан к конвейеру. Для получения входных данных с клавиатуры мы должны использовать команду line, которая получает их из файла /dev/tty. Это распространенный способ чтения данных с клавиатуры из переадресованного цикла.
Строка 55 проверяет, является ли ответом символ y. Если да, вызывается команда UNIX'а mail, и снова ввод переадресовывается из файла /dev/tty (поскольку строки почтового сообщения мы должны вводить с клавиатуры.) В данном случае мы фактически переадресовываем стандартный ввод для вызова подчиненного shell-процесса, выполняющего команду mail. Без выполнения переадресации команда mail читает из файла /dev/null, что нарушает выполнение всего цикла whox.
Строка 59 управляет режимом показа возможности записи на терминал. Цель здесь такова - использовать одну команду ls и, применяя подчиненный процесс, извлечь файлы терминальных устройств из выходных данных команды who. Эти файлы являются вторым полем результата команды who. Сначала запускается команда who, которая по конвейеру передает свои данные команде sed.
Затем sed использует команду подстановки для отбрасывания всего, кроме того, что ограничено символами \( и \). Последующая часть команды подстановки ссылается на этот ограниченный участок с помощью обозначения \1. Используя символ . как соответствующий любому символу распечатки, мы должны всего лишь посчитать столбцы, которые нам нужно вырезать. Кроме того, имена устройств в команде who не имеют префикса /dev/, который нам необходим. Команда sed вставляет его перед текстом, вырезанным из команды who. В результате команде ls дается список полных маршрутных имен ко всем файлам устройств зарегистрированных пользователей. Затем это выводится на экран.