ПОЯСНЕНИЯ
Первое, что делает access (в строках 4-8) - проверяет, правильно ли она была вызвана. Поскольку опций не предусмотрено, в командной строке ничего не должно быть. Если количество аргументов в командной строке больше нуля, то на стандартное устройство регистрации ошибок выдается сообщение об ошибке и командный файл завершается.
Оператор в строке 10 выполняет поиск в парольном файле. Применяется утилита grep, т.к. мы используем в этой команде выражение. Если бы мы использовали фиксированную строку, более предпочтительной была бы утилита fgrep, потому что она быстрее. Выражение, задающее поиск, означает следующее: начиная с начала строки, найти все символы, отличные от двоеточия, вплоть до обнаружения двух двоеточий подряд. Если вы заглянете в файл /etc/passwd, то увидите, что первое поле представляет собой имя (от начала строки до первого двоеточия). Затем между первым и вторым двоеточием размещается пароль. Если пароль отсутствует, то после первого двоеточия сразу же следует второе - именно это соответствует нашему шаблону поиска. Поиск выполнятся в файле /etc/passwd. Если grep успешно обнаружил хотя бы одну запись, то код возврата нулевой. Если grep ничего не обнаружил, то код возврата единица. Тогда активизируется последняя часть строки 10 и выводится сообщение о том, что все записи о входе в систему защищены.
ИМЯ: chkset
chkset
Первым делом chkset инициализирует две переменные - FORM и SORT. Переменная FORM содержит команду для выдачи результата работы команды find, а переменная SORT - команду, определяющую, что нужно сортировать.
В строке 7 проверяется, является ли первый позиционный параметр опцией. Если да, то оператор case (строки 8-14) смотрит, какая это опция. Если это опция "-l", то подготавливается команда для распечатки результата (это мы обсудим позже). Команда для утилиты sort формируется так, чтобы сортировка шла по полю владельца. Опция убирается из командной строки, потому что все последующие аргументы должны быть каталогами и мы захотим получить к ним доступ с помощью "$#". Если попалась опция, отличная от "-l", то это ошибка, выдается сообщение об ошибке (строка 12), и командный файл завершается.
Если осталось более нуля аргументов, когда мы попадаем в строку 17, то они проверяются в цикле, чтобы убедиться, что все они являются каталогами. Если это не каталоги, на стандартное устройство регистрации ошибок выдается сообщение об ошибке и командный файл завершается. Если имеются параметры (т.е. каталоги), то в строке 18 в переменную SRC заносятся все каталоги. Если же параметров нет, то в переменную SRC заносится значение "/", т.е. корневой каталог, чтобы обеспечить подразумеваемую стартовую точку для поиска.
Вся работа этого командного файла выполняется фактически в операторе find. Команда find допускает множественное указание каталогов, которые поступают в результате чтения их из командной строки и занесения в переменную SRC.
После того как мы указали команде find, откуда начинать поиск, мы указываем ей, что нужно искать. В данном случае нас интересуют все файлы, которые имеют включенный бит установки пользовательского либо группового идентификатора. Мы объясняем это команде find путем указания прав доступа, которые требуется искать. Строка "-perm -4000" означает поиск всех файлов, имеющих права доступа со включенным битом установки пользовательского идентификатора и с любыми другими включенными битами. Вы можете понимать эту запись как применение символов -заменителей - 4???. Мы ищем как установку пользовательского идентификатора (-4000), так и установку группового идентификатора (-2000), поэтому две строки прав доступа соединены опцией -o, означающей "or" ("или"). (Более полное описание прав доступа в символической и восьмеричной форме приведено в chmod(1).)
Следующая задача - добавить строку, хранимую в переменной FORM, в командную строку. Если опция -l не была использована, то в переменной FORM хранится строка "-print", а это значит, что find будет печатать маршрутные имена найденных файлов. Если же -l использовалась, то переменная FORM содержит строку "-exec ls -ld {} ;". Опция -exec это очень гибкая опция команды find, позволяющая применить любые команды, которые за ней следуют, к каждому найденному файлу. В данном случае эти команды выполняют распечатку в длинном формате (-l), причем для каждого каталога (-d) выводится только его имя (а не содержимое). Именно здесь происходят наибольшие затраты ресурсов центрального процессора, так как для опции -l требуется системный вызов stat.
Из-за того, что для каждого файла требуется команда ls, она каждый раз загружается в память и выполняется. Производится также доступ к индексному дескриптору файла на диске. Это приводит к большим накладным расходам.
Затем весь поток данных пропускается через утилиту sort. На самом деле мы хотим сделать сортировку по восьмому полю (вы можете проверить это, выполнив команду "ls -l" и изучив ее результат). Утилита sort ПРОПУСКАЕТ указанное число полей, начиная с поля 1, являющегося по умолчанию стартовой точкой, поэтому использование записи +7 означает переход к восьмому полю, которым является имя файла.
В самом начале программы определяются и инициализируются все переменные и списки. В строке 14 определены два флага: alert (сигнал тревоги) и mail (почта). Они имеют значение TRUE или FALSE. В этой программе используются два файла: протокольный файл команды su, который вы выбираете, и временный файл. Поскольку мы собираемся применять некоторые подпрограммы стандартного ввода-вывода (stdio), мы пользуемся указателями на файлы fp1 и fp2, а не дескрипторами файлов. Применяется два буфера: один для зачитывания в него данных из протокольного файла команды su, а другой - для хранения имени временного файла. Первоначально именем протокольного файла является sulog. Если вместо него используется другое имя файла, то переменная log переустанавливается на это имя.
Затем инициализируются предопределенные списки разрешенных суперпользователей и администраторов. В нашем примере двумя разрешенными суперпользователями являются sage (sage-root) и root (root-root). Для того чтобы приспособить это для вашей системы, поместите здесь имена людей, которым вы хотите разрешить пользоваться командой su для превращения в суперпользователей. Список администраторов также должен содержать соответствующие имена. В данном примере в список администраторов входит одно имя - sage. Это значит, что sage будет единственным, кто получит почту, если указана почтовая опция.
Подразумеваемое состояние рассылки почты устанавливается в строке 23 на значение FALSE, т. е. почты нет. Это делается для того, чтобы после разбора командной строки переменная mail имела правильное значение.
Далее выполняется проверка на ошибки. Первая проверка выглядит несколько странной, но на самом деле вполне понятна. Переменная argc возвращает число аргументов командной строки, а argv указывает на массив, содержащий сами аргументы, каждый из которых сам является массивом символов. Если командная строка вообще имеет какие-либо аргументы (argc > 1) и первым символом первого аргумента (argv[1][0]) является дефис, то проверяется, корректная ли это опция. С целью проверки второго символа первого аргумента используется оператор case. Если аргументом является символ m, то флаг mail устанавливается в состояние TRUE, число аргументов (argc) уменьшается на единицу, а указатель на массив аргументов (argv) на единицу увеличивается. Это служит той же цели, что и удаление аргументов из командной строки в командных файлах интерпретатора shell. Напомним, что argv является в действительности массивом указателей, а массивы трактуются точно так же, как строки, с базовым адресом и смещением. Поэтому argv[0] == argv, и argv[1] == ++argv. Если опция отличается от -m, то в стандартный вывод печатается сообщение об ошибке и программа завершается. В строке 39 проверяется счетчик аргументов, чтобы понять, есть ли еще один аргумент. Напомним, что argv всегда на единицу отстает от argc, потому что само имя команды является первым аргументом массива. Следовательно, для того чтобы получить второй аргумент, мы должны перейти к следующей позиции (*++argv). Если аргумент имеется, он должен быть именем протокольного файла, и это имя заносится в переменную log. Далее мы пытаемся открыть файлы, используемые в программе. Сначала открывается для чтения протокольный файл. Если это не срабатывает (возвращен нулевой указатель), то печатается сообщение об ошибке и программа завершается. Затем в строке 49 создается имя временного файла с добавлением к нему идентификатора процесса, чтобы гарантировать уникальность имени файла. Этот файл открывается на чтение и запись. Если эта операция не удается, печатается сообщение об ошибке и выполнение завершается.
В строках 57-103 заключен главный цикл. Управляющая часть главного цикла определяется тем, все ли данные прочитаны из протокольного файла. Если больше никакие байты прочитать нельзя, то fgets (строка 57) возвращает нулевой указатель, что завершает цикл while. Когда строка данных читается из протокольного файла, эти данные помещаются в массив line. Мы применяем указатель на символ для прохода вдоль строки и поиска записи о нарушителе.
Изучая запись в вашем файле sulog, вы можете увидеть, где находятся интересующие нас поля. Сначала указатель смещается на 15 символов от начала строки. Это позиция флага, определяющего, была ли успешной команда su. Если она была успешной, указатель увеличивается на два, чтобы пропустить пробел и установить указатель на имени терминала. Это имя имеет переменную длину, поэтому мы ориентируемся по пробелу в конце имени. В строках 63-64 применяется цикл while, который заставляет указатель пропустить все символы, отличные от пробела. Затем он увеличивается на единицу еще раз, чтобы пропустить пробел между именем терминала и строкой имени пользователя.
В этот момент указатель находится в последнем поле строки. Указатель на это поле помещается в переменную uname, чтобы затем ее можно было использовать для сравнения строк. Следующий цикл while (строки 66-67) проходит по строке, пока не попадет на символ дефиса. Цикл while выполняется до тех пор, пока указатель не указывает на конец строки (т.е. на нелевой указатель, *p == '\0') и еще не указывает на дефис. Поскольку в цикле используется p++, то когда мы попадаем на нулевой указатель, цикл завершается, причем p указывает на следующий символ.
Выполняется проверка, был ли суперпользователем тот, кто применил команду su. Если был, то взводится флаг alert, и мы должны проверить, разрешенный ли это суперпользователь. Цикл for (строки 72-77) пробегает по всем именам в массиве, содержащем имена разрешенных пользователей, до тех пор, пока мы не обнаружим последнюю запись, которая является пустой. Строка с именем пользователя, установленная предварительно, сверяется с каждой записью в массиве разрешенных имен. Если они совпадают, то мы можем предположить, что с данным применением команды su все в порядке. Тогда мы выключаем флаг alert и завершаем сравнение имен. Если же по окончании цикла флаг alert все еще взведен, значит имя не содержится в списке разрешенных - это, возможно, нарушитель. Вся запись целиком записывается во временный файл и управление передается назад в начало цикла для следующей операции чтения.
Когда все данные прочитаны, цикл while завершается. В строке 84 проверяется, взведен ли флаг mail. Если да, то временный файл закрывается (чтобы сработала команда cat) и выполняется еще один цикл for (строки 87-91). Он перебирает всех администраторов и завершается, когда попадает на нулевой указатель в конце массива. Для каждого из обозначенных администраторов конструируется команда mail и посылается в систему UNIX при помощи системного вызова (строка 90). Метод отправки по почте временного файла заключается в применении к этому файлу команды cat и передаче данных по конвейеру команде mail.
Если флаг mail выключен, то временный файл нужно напечатать в стандартный вывод, а не отправить по почте. Для того чтобы сделать это как можно быстрее, временный файл (который пока еще открыт) "перематывается" (мы позиционируемся в его начало) и посимвольно пропускается по циклу. Обратите внимание, что данный цикл является сердцевиной команды cat, как описано на странице 153 книги Кернигана и Ритчи "Язык программирования Си" (B.W.Kernighan, D.M.Ritchie. The C Programming Language). После того как временный файл напечатан, он закрывается. Наконец, закрывается протокольный файл и удаляется временный файл.