Показаны сообщения с ярлыком windows. Показать все сообщения
Показаны сообщения с ярлыком windows. Показать все сообщения

воскресенье, 10 июня 2018 г.

Изменение размера NTFS-раздела в образе диска в формате VDI

В одной из прошлых заметок я описывал использование VirtualBox в Debian Stretch. При создании виртуальной машины я указал слишком маленький размер образа диска.

Чтобы изменить размер образа VDI, имеющего динамический размер, нужно в главном окне Virtual Box переключиться на инструмент "Менеджер виртуальных носителей", выбрать образ диска, указать его новый размер и нажать на кнопку "Применить".

На этом можно было бы закончить рассказ, если бы VirtualBox знал бы что-нибудь о разделах и файловых системах, находящихся внутри образа диска. Так как он ничего об этом не знает, поменять размеры всего этого нам придётся вручную. Для этого, кроме самого VirtualBox, нам понадобятся три пакета:
  1. parted - утилита для манипуляции дисковыми разделами, которая умеет не только удалять разделы и создавать новые, но и изменять существующие,
  2. ntfs-3g - в этом пакете находится утилита ntfsresize, с помощью которой можно изменить размеры файловой системы NTFS,
  3. partclone - в этом пакете имеется утилита partclone.ntfsfixboot, при помощи которой можно исправить загрузочный сектор файловой системы NTFS, так чтобы он знал геометрию диска и положение раздела NTFS на этом диске.
Если какие-то из пакетов ещё не установлены в системе, то установить их можно такой командой:
# apt-get install parted ntfs-3g partclone
Первым делом нам понадобится преобразовать образ диска виртуальной машины из формата VDI в формат, представляющий собой побайтовую копию диска. Для этого воспользуемся утилитой VBoxManage, которая поставляется совместно с VirtualBox:
$ VBoxManage clonemedium --format RAW win.vdi win.img
Несмотря на кажущуюся универсальность концепции файла в Unix, простые файлы отличаются от блочных устройств, поэтому нам нужно отобразить простой файл с образом диска в блочное устройство. Для этого воспользуемся утилитой losetup, которая как раз позволяет делать то, что нам требуется:
# losetup -f win.img
Смотрим, какому блочному устройству соответствует файл образа диска:
# losetup -a
Вывод команды:
/dev/loop0: [2051]:14947724 (/home/stupin/VirtualBox VMs/win/win.img)
Приступим к изменению размеров раздела. Воспользуемся для этого утилитой parted и для начала выведем таблицу имеющихся разделов. В качестве единицы измерения размеров и координат разделов попросим утилиту использовать секторы:
# parted /dev/loop0 unit s print
Вывод команды:
Model: Loopback device (loopback)
Disk /dev/loop0: 8388608s
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags: 

Number  Start  End       Size      Type     File system  Flags
 1      63s    4185215s  4185153s  primary  ntfs         boot
Судя по таблице разделов, в образе диска имеется только один первичный раздел, который является загрузочным. Общий размер образа - 8388608 секторов. Переместим конечный сектор первого раздела в конец образа диска, для чего укажем номер последнего сектора раздела на единицу меньше:
# parted /dev/loop0 resizepart 1 8388607s
Теперь нужно изменить размер файловой системы в первом разделе образа диска. Для начала сделаем так, чтобы операционная система узнала о разделах, имеющихся на блочном устройстве /dev/loop0. Для этого воспользуемся утилитой partprobe, которая имеется в комплекте утилиты parted:
# partprobe /dev/loop0
После поиска разделов появится файл устройства /dev/loop0p1, соответствующий первому первичному разделу в образе.

Для изменения размера файловой системы, как уже было написано выше, мы воспользуемся утилитой ntfsresize. Однако, прежде чем изменить размер файловой системы, узнаем текущий размер этой файловой системы и раздела, на котором она расположена. Сделать это можно при помощи самой утилиты ntfsresize:
# ntfsresize -i /dev/loop0p1
Вывод команды:
ntfsresize v2016.2.22AR.1 (libntfs-3g)
Device name        : /dev/loop0p1
NTFS volume version: 3.1
Cluster size       : 2048 bytes
Current volume size: 2142798336 bytes (2143 MB)
Current device size: 4294935040 bytes (4295 MB)
Checking filesystem consistency ...
100.00 percent completed
Accounting clusters ...
Space in use       : 1440 MB (67,2%)
Collecting resizing constraints ...
You might resize at 1439602688 bytes or 1440 MB (freeing 703 MB).
Please make a test run using both the -n and -s options before real resizing!
Изменяем размер файловой системы до размеров раздела:
# ntfsresize -s 4294935040 /dev/loop0p1
Вывод команды (нужно будет подтвердить её выполнение нажатием y):
ntfsresize v2016.2.22AR.1 (libntfs-3g)
Device name        : /dev/loop0p1
NTFS volume version: 3.1
Cluster size       : 2048 bytes
Current volume size: 2142798336 bytes (2143 MB)
Current device size: 4294935040 bytes (4295 MB)
New volume size    : 4294935040 bytes (4295 MB)
Checking filesystem consistency ...
100.00 percent completed
Accounting clusters ...
Space in use       : 1440 MB (67,2%)
Collecting resizing constraints ...
WARNING: Every sanity check passed and only the dangerous operations left.
Make sure that important data has been backed up! Power outage or computer
crash may result major data loss!
Are you sure you want to proceed (y/[n])? y
Schedule chkdsk for NTFS consistency check at Windows boot time ...
Resetting $LogFile ... (this might take a while)
Updating $BadClust file ...
Updating $Bitmap file ...
Updating Boot record ...
Syncing device ...
Successfully resized NTFS on device '/dev/loop0p1'.
Размер файловой системы изменён и её даже можно смонтировать, но вот попытка загрузить с неё систему приведёт к ошибке "disk read error occured". Дело в том, что мы изменили размер файловой системы, но загрузчику операционной системы об этом не сообщили.

Чтобы загрузчик узнал о новом положении файловой системы на диске и её размере, воспользуемся уже упомянутой утилитой partclone.ntfsfixboot. По умолчанию эта утилита пытается узнать геометрию жёсткого диска при помощи системного вызова ioctl. Поскольку образ диска, даже будучи отображённым в блочное устройство, не превращается в настоящий диск, то системные вызовы ioctl будут завершаться ошибкой.

Однако, у современных жёстких дисков реальная геометрия учитывается встроенным в жёсткий диск контроллером, а снаружи весь диск выглядит как линейная последовательность блоков. Этот режим адресации называется LBA - Logical Block Addressing - логическая адресация блоков. Поэтому геометрия диска, которая записана в загрузчике, в настоящее время является атавизмом и реально не используется. В поля геометрии диска, работающего в режиме LBA, записываются максимальные возможные значения: 255 головок, 63 сектора на дорожке. Самая первая по счёту дорожка на диске обычно называлась загрузочной, т.к. на ней располагался загрузочный сектор. Внутри раздела диска содержится собственный загрузочный сектор, который инициирует загрузку операционной системы, находящейся в этом разделе. Смещение загрузочного сектора относительно начала раздела тоже может быть разным для файловых систем разного типа. В случае с NTFS этот загрузчик находится в 63 секторе внутри раздела.

Вооружившись всей этой информацией, поправим загрузочный сектор файловой системы NTFS на первом разделе образа диска:
# partclone.ntfsfixboot -b -w -h 255 -t 63 -s 63 /dev/loop0p1
Задача почти выполнена. Отключаем отображение файла с образом диска в блочное устройство:
# losetup -d /dev/loop0
Преобразовываем отредактированный образ обратно в формат VDI (на всякий случай старый файл с образом VDI оставляем):
$ VBoxManage convertfromraw --format VDI win.img win2.vdi
Осталось выбрать новый файл образа в настройках виртуальной машины и попробовать загрузить виртуальную машину с нового образа диска. Если всё в порядке, то исходный VDI-файл и его побайтовую версию можно удалить.

Использованные материалы


воскресенье, 22 апреля 2018 г.

VirtualBox в Debian Stretch

Когда-то я тестировал несколько эмуляторов, о чём написал заметку Эмуляторы VirtualBox и QEmu в Debian. Недавно мне снова понадобилось воспользоваться эмулятором и я решил воспользоваться старыми наработками. Однако, ситуация с тех пор несколько изменилась. Во-первых, VirtualBox теперь отсутствует в стандартных репозиториях Debian, а во-вторых, графическая оболочка qemulator для qemu теперь обросла функциями и сменила название на virtualbricks.

VirtualBricks

Попробуем установить virtualbricks:
# apt-get install virtualbricks
Сразу после запуска программа выводит сообщение об отсутствующих компонентах:

Если проигнорировать это сообщение, создать виртуальную машину и попытаться перейти к её настройке, выводится сообщение о том, что virtualbricks не смог распознать версию qemu:

Если попытаться установить недостающие компоненты, сообщений об отсутствующих компонентах станет меньше.
# apt-get install vde2-cryptcab qemu ksmtuned

Однако делу это совершенно не помогает: если после создания виртуальной машины попытаться перейти к её настройке, то программа по-прежнему продолжает выводить сообщение о нераспознанной версии qemu. Как видно, попытка нарастить функциональности при помощи груды кода на Python, не пошла проекту на пользу. На этом я решил закончить эксперименты с virtualbricks, удалил всё установленное и решил снова попытать судьбу с VirtualBox.

Установка VirtualBox

На официальном сайте проекта в разделе скачивания Download VirtualBox for Linux Hosts имеется рекомендация устанавливать VirtualBox в Debian Jessie из репозитория https://download.virtualbox.org/virtualbox/debian. Пропишем его в файл /etc/apt/sources.list:
deb http://download.virtualbox.org/virtualbox/debian stretch contrib
Обратите внимание, что на официальном сайте указана ссылка, использующая протокол https. Эта ссылка не работает. Чтобы репозиторий подключился без ошибок, нужно поменять в ссылке протокол https на http, как это и было сделано в строчке выше.

Сразу же скачаем и установим в систему PGP-ключи репозитория:
# wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -
# wget -q https://www.virtualbox.org/download/oracle_vbox.asc -O- | sudo apt-key add -
Обновим списки пакетов, доступных через репозитории:
# apt-get update
И установим пакет с VirtualBox:
# apt-get install virtualbox-5.2
При попытке запуска созданной виртуальной машины может появиться сообщение об ошибке следующего вида:

Для работы VirtualBox использует собственный модуль ядра vboxdrv. Если этот модуль не загружен, то и VirtualBox не будет работать. Для устранения ошибки нужно подгрузить модуль ядра:
# modprobe vboxdrv
Чтобы этот модуль автоматически загружался при загрузке системы, его можно вписать в файл /etc/modules:
vboxdrv
Если в системе отсутствует модуль ядра vboxdrv для текущей версии ядра, то modprobe будет ругаться следующим образом:
# modprobe vboxdrv
modprobe: FATAL: Module vboxdrv not found in directory /lib/modules/4.9.0-6-amd64
В частности, такая ошибка может появиться, если вы обновляли систему и было установлено ядро новой версии. В таком случае нужно собрать модуль ядра снова. Для этого надо выполнить команду vboxconfig:
# vboxconfig
Если в системе не установлены заголовочные файлы ядра, то команда сообщит об ошибке сборки модуля ядра вот таким образом:
vboxdrv.sh: Building VirtualBox kernel modules.
This system is currently not set up to build kernel modules.
Please install the Linux kernel "header" files matching the current kernel
for adding new hardware support to the system.
The distribution packages containing the headers are probably:
    linux-headers-amd64 linux-headers-4.9.0-6-amd64
Чтобы решить эту проблему, нужно установить пакет с заголовочными файлами ядра:
# apt-get install linux-headers-amd64
Таким образом, нужно сначала установить заголовочные файлы ядра, потом собрать модуль ядра vboxdrv при помощи команды vboxconfig, затем подгрузить модуль ядра и прописать его автозагрузку в файл /etc/modules:
# apt-get install linux-headers-amd64
# vboxconfig
# modprobe vboxdrv
# vim /etc/modules
Последняя команда в списке запускает текстовый редактор для редактирования файла /etc/modules, куда нужно вписать одну строчку:
vboxdrv

Настройка виртуальной машины

После установки запустим VirtualBox при помощи команды virtualbox, создадим и настроим виртуальную машину:

Виртуальная машина в целом создана, теперь просмотрим и отредактируем остальные её свойства:

Для установки Windows в виртуальную машину уберём из виртуальной машины дисковод для дискет и вставим в CD-ROM виртуальной машины образ установочного диска:

Настройка общей папки, через которую впоследствии можно будет обмениваться файлами между компьютером и виртуальной машиной:

Установка Windows и Oracle VM VirtualBox Guest Additions

Запустим созданную виртуальную машину:

На этом экране можно видеть сводку свойств виртуальной машины:

После установки Windows в виртуальную машину (не показано), стоит поставить в неё Oracle VM VirtualBox Guest Additions - дополнительные драйверы и программы, делающие работу с виртуальной машиной более комфортной:



После установки Oracle VM VirtualBox Guest Additions заработал общий каталог с хост-системой, ради чего, преимущественно, эти дополнения и ставились:

воскресенье, 9 октября 2016 г.

Хакинг TV Motion Detector VG

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

Для начала несколько слов о самой программе. Программа предназначена для мониторинга аналоговых телеканалов и, насколько я могу судить, довольно популярна у операторов кабельного телевидения. Программа поочерёдно переключает каналы на ТВ-тюнере и проверяет, есть ли на канале звук, двигается ли изображение. Программа умеет вызывать другие программы в случае если проблема была только что обнаружена, обнаружена повторно или произошло восстановление вещания. Подробнее о TV Motion Detector VG можно почитать на официальном сайте. Ещё один немаловажный момент - программа бесплатная. Пусть это будет ещё одним аргументом в пользу того, что эта заметка написана не за деньги и не является скрытой рекламой :)

TV Motion Detector VG использует файл scanfreq.txt со списком частот каналов и файл channels.sqlite со списком каналов в таблице channels, журналом событий в таблице log и списком запланированных задач в таблице scheduler. Имеется база данных, в которой инженеры головной станции поддерживают актуальный список каналов с их номерами, частотами и названиями. Специальная самописная программа запрашивает список каналов из базы данных, наполняемой инженерами головной станции, формирует эти два файла и через Zabbix API создаёт нужные элементы данных и триггеры для каждого канала. В сформированном файле channels.sqlite в таблице scheduler создаются записи, которые предписывают каждый час вызывать bat-файл, который завершает TV Motion Detector VG, вызывает программу, обновляющую Zabbix и эти два файла, а потом запускает TV Motion Detector VG снова. В таблице channels из файла channels.sqlite для каждого канала указываются скрипты, которые надо вызывать в случае проблем на канале, в случае продолжающихся проблем и в случае восстановления канала. В качестве таких скриптов используется утилита zabbix_sender. Программа TV Motion Detector VG прописана в автозагрузку Windows. В Zabbix кроме состояния каналов контролируется ещё и момент, когда в последний раз приходили данные от TV Motion Detector VG. Таким образом получаем практически полностью автономно работающую систему из трёх звеньев - базы каналов, TV Motion Detector VG и Zabbix.

Ручное вмешательство бывает нужно, если компьютер с TV Motion Detector VG был перезагружен. В этом случае в Zabbix срабатывает триггер о том, что давно не поступали данные из TV Motion Detector VG. Дежурный инженер заходит на компьютер с Windows по RDP, TV Motion Detector VG автоматически запускается, а дежурный инженер отсоединяется от сеанса. Дальше всё продолжает работать.

Бывает, что требуется ручное вмешательство другого рода. Если в частотном плане появляются новые частоты или пропадают старые, то TV Motion Detector VG (или драйвер ТВ-тюнера) сходит с ума и реально настраивается не на те каналы, которые соответствуют частоте канала в таблице channels из файла channels.sqlite. В таком случае помогает простое лечение - нажать кнопку Scan, дождаться окончания сканирования, после чего можно обновить файлы channels.sqlite, scanfreq.txt и запустить TV Motion Detector VG снова.

Вмешательства такого рода требуются довольно редко. А вот следующий недостаток изрядно подрывает всю кропотливо выстроенную систему.


Если приглядеться, то в нижней строчке под списком каналов программы можно увидеть текст "count: 50". Дело в том, что после перезапуска программа считает, что есть только 50 каналов. Только по ним она и проходится циклически, оставляя за пределами контроля ещё 11 каналов. Если вручную потянуть линейку прокрутки в списке каналов до конца, то программа отображает остальные каналы и после этого уже начинает проходиться циклически по каждому из 61 каналов. Но, напомню, программа в описанной выше схеме перезапускается каждый час. Поэтому ручное вмешательство не годится.

Первым делом я подумал, что программа при запуске выполняет SQL-запрос на получение списка каналов с ограничением LIMIT 50. Я открыл EXE-файл в шестнадцатеричном редакторе и нашёл все запросы SELECT. Был среди них такой запрос "SELECT COUNT(inc) as countinc, MAX(number) as maxnumber FROM channels;". Я проверил, что он возвращает и убедился, что обнаруживается ровно 61 канал.

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

Снова попробовал поискать что-нибудь в шестнадцатеричном редакторе. Естественно, таких мест оказалось довольно много. Не знаю, что натолкнуло меня на мысль попробовать ArtMoney, потому что изначально было ясно, что даже если я и найду эти счётчики в оперативной памяти, то как потом мне найти и исправить их в файле EXE? Если они не хранятся в секции .BSS или подобной ей, то найти нужные константы в ассемблерном коде будет непросто. Да и даже если они хранятся в такой секции - у меня нет опыта правки EXE-файлов. Всё же решил для любопытства найти этот счётчик при помощи ArtMoney.

Попытка поиска увенчалась успехом - я нашёл аж две ячейки памяти, в которых хранились 32-битные числа, соответствовавшие количеству каналов в списке. Попытки исправить это число на 61 давали эффект ровно до того момента, когда программа заканчивала просмотр пятидесятого канала и пыталась приступить к следующему. Дальше программа ежесекундно выбрасывала окошки с сообщениями об ошибке до тех пор, пока я не возвращал в обе ячейки прежнее число 50.

Потом я сделал перерыв на обед. Пока шёл в столовую, подумал что свойства компонентов на форме могут оказаться прописаны в ресурсах Windows, вшитых в EXE-файл. Когда вернулся с обеда, решил проверить это предположение. В прошлом я доставал из EXE- и DLL-файлов встроенные в них пиктограммы, курсоры и картинки с помощью программы ResEdit. Скачал программу с официальной страницы. Из всех ресурсов наиболее многообещающим выглядел ресурс с именем TFORMMAIN. Но ResEdit отображал этот ресурс как блок двоичных данных.

Интересующий меня ресурс начинался с сигнатуры TPF0. Я поискал в интернете и понял, что это формы Delphi. В проекте Delphi у файлов этих форм бывает расширение DFM. Решил поискать, чем можно открыть такой файл на просмотр и редактирование. Вышел на программу ResourceHacker, которую можно скачать на странице разработчика - Ангуса Джонсона. Скачал программу и открыл интересующий меня ресурс на просмотр и редактирование. Стал изучать, какие компоненты присутствуют на форме и какие их свойства можно было бы поправить.

В процессе изучения, сопровождавшегося поиском в интернете, понял, что программа для доступа к SQLite использует компоненты AnyDAC Дмитрия Арефьева. К сожалению, документацию на AnyDAC найти в Интернете мне не удалось. Дело в том, что AnyDAC была продана фирме Embarcadero и превратилась в FireDAC. Удалось найти документацию только на библиотеку FireDAC. Оставалась слабая надежда на то, что библиотека изменилась не очень сильно и опции из FireDAC подойдут для AnyDAC.

В результате изучения документации, примеров кода, чтения форумов удалось составить примерное понимание того, как взаимодействуют между собой компоненты AnyDAC. Насколько я понял, AnyDAC на уровне интерфейсов компонентов совместима или очень похожа на наборы компонентов, аналогичных по функциям: BDE и ADO.

Верное направление для поисков было нащупано в ветке форума на www.sql.ru, где обсуждался FireDAC. Список всех возможных значений нашёлся в статье документации FireDAC.Stan.Option.TFDFetchOptions.RecordCountMode. На каком-то этапе я попробовал вставить в описание объекта ADTableChannels после 1738 строчки декомпилированного ресурса TFORMMAIN свойство FetchOptions.RecordCountMode = cmTotal:

Затем скомпилировал изменения нажав на зелёный треугольник и перезаписал файл EXE. Когда запустил программу, то увидел в строчке count вожделенное значение 61. Оставалось убедиться, что после 50 канала программа переключится на 51 канал и дойдёт до конца списка. Всё сработало, как надо:

В этот момент я испытал облегчение, радость и удивление от того, что поиски и эксперименты в конечном итоге привели к успеху. С одной стороны, я не надеялся на успех. Уж слишком были призрачными надежды на него. У читателей этого блога может сложиться впечатление, что все задачи, которые я перед собой ставлю, в конечном итоге завершаются успешно. Однако это не всегда бывает так. Иногда эксперименты заходят в тупик и очередная задача так и остаётся нерешённой. Раз нет решения, то и писать не о чем. С другой стороны, пока голова не перестала генерировать новые предположения и пока они все не проверены, остаётся надежда на успех. В этот раз мне повезло :)

P.S. Несколько замечаний о законности описанной выше модификации программы.
УК РФ, Статья 272. Неправомерный доступ к компьютерной информации.

(в ред. Федерального закона от 07.12.2011 N 420-ФЗ)

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

наказывается штрафом в размере до двухсот тысяч рублей или в размере заработной платы или иного дохода осужденного за период до восемнадцати месяцев, либо исправительными работами на срок до одного года, либо ограничением свободы на срок до двух лет, либо принудительными работами на срок до двух лет, либо лишением свободы на тот же срок.
Источник - сайт компании "КонсультантПлюс". УК РФ, Статья 272. Неправомерный доступ к компьютерной информации

Однако, в комментариях к УК РФ имеются следующие оговорки:
1. Модификация информации - внесение изменений в компьютерную информацию (в том числе изменение её параметров) без согласия её законного обладателя. Действующим законодатесльством разрешены следующие виды легальной модификации программ, баз данных лицами, правомерно владеющими этой информацией: а) модификация в виде исправления явных ошибок; б) модификация в виде внесения изменений в программы, базы данных для их функционирования на технических средствах пользователя; в) модификация в виде частной декомпиляции программы для достижения способности к взаимодействию с другими программами.
Источник - книга "Комментарий к Уголовному кодексу Российской Федерации (постатейный), Том 2" под редакцией Бриллиантова А.В. Глава 28. Преступления в сфере компьютерной информации. Статья 272. Неправомерный доступ к компьютерной информации

Ещё один источник - сайт адвокатского бюро "Домкины и партнёры". Методические рекомендации по осуществлению прокурорского надзора за исполнением законов при расследовании преступлений в сфере компьютерной информации

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

воскресенье, 29 мая 2016 г.

Установка Windows XP в Xen

Возникла задача поставить Windows для запуска программы, которая не работает в Wine. Решил попробовать установить Windows под управлением Xen. Ранее я уже писал небольшую заметку о настройке Xen: Памятка по настройке Xen. В той статье для управления Xen использовалась утилита xm, поскольку писалась она применительно к Debian Wheezy. В этот раз будет использоваться утилита xl, поскольку дело происходит в Debian Jessie. Однако, несмотря на это различие, всё написанное в прошлой статье будет работать и сейчас. Надо всего лишь вместо xm использовать xl.

1. Установка операционной системы на виртуальную машину

Создадим LVM-раздел, куда будем устанавливать Windows:
# lvcreate -L10G -n winxp-disk
Создадим файл конфигурации /etc/xen/winxp.cfg, который первоначально будет использоваться для установки системы:
builder = 'hvm'
device_model_version = 'qemu-xen'
memory = 1024
vcpus = 1
pae = 0
acpi = 0
apic = 0
name = 'winxp'
disk = ['phy:/dev/vg0/winxp-disk,ioemu:hda,w',
        'phy:/home/stupin/WindowsXP_SP2.iso,ioemu:hdb:cdrom,r']
boot = 'd'

on_poweroff = 'destroy'
on_reboot = 'destroy'
on_crash = 'destroy'
on_xend_start = 'ignore'
on_xend_stop = 'ignore'

fullscreen = 1
sdl = 0
vnc = 1
vncconsole = 1
vncpasswd = ''
keymap = 'ru'
stdvga = 0
serial = 'pty'

usb = 1
usbdevice = 'tablet'
Особо стоит отметить следующие моменты в этом файле:
  • Опции acpi (интерфейс управления электропитанием) и apic (улучшенный контроллер прерываний) на время установки отключены,
  • В опции disk первым прописан LVM-раздел, в который будем устанавливать систему, а вторым прописан ISO-образ установочного диска с Windows,
  • Опция boot выставлена в значение d - загрузка "с диска D:", то есть - с компакт-диска, в роли которого выступает ISO-образ установочного диска,
  • Опция keymap позволяет в дальнейшем не следить за раскладкой на системе, где запущен VNC-клиент. Без этой опции в сеансе VNC русские буквы удастся ввести только в случае, если на системе с VNC-клиентом включена английская раскладка,
  • Включена поддержка usb, а в списке USB-устройств в опции usbdevice настроен "планшет". Эта настройка позволяет в дальнейшем без проблем манипулировать курсором мыши. Без неё при подключении через VNC будет два курсора мыши, слабо связанных между собой, так что управляться ими будет непросто.
Теперь запустим виртуальную машину:
# xl create /etc/xen/winxp.cfg
На всякий случай, если вдруг виртуальная машина зависнет, приведу команду для её принудительного выключения:
# xl destroy winxp
Напоминаю, что список запущенных виртуальных машин можно посмотреть такой командой:
# xl list
После запуска можно подключиться к виртуальной машине при помощи VNC-клиента на TCP-порт 5900 по IP-адресу 5900. Я воспользовался Remmina, о которой писал в одноимённой заметке - Remmina:

Если Вы хотите подключиться по сети не из домена dom0 или у Вас имеется несколько подобных доменов domU, к которым Вы хотите подключаться по протоколу VNC, можно настроить прослушиваемый адрес и порт при помощи опции vnclisten. Например, значение 192.168.0.1:2 будет соответствовать ожиданию подключений на TCP-порт 5902 и адрес 192.168.0.1.

Настроить переключение раскладки по нажатию Alt+Shift мне не удалось - это сочетание в сеанс VNC не попадало, поэтому пришлось настроить непривычное для меня сочетание Ctrl+Shift.

2. Изменение настроек после установки

После установки Windows нужно выключить виртуальную машину, воспользовавшись пунктом меню "Выключить компьютер", доступным по нажатию кнопки "Пуск":

Теперь нужно немного изменить конфигурацию в файле /etc/xen/winxp.cfg. Включим опции acpi и apic, уберём из списка дисков ISO-образ установочного компакт-диска и поменяем значение опции boot на c - загрузка "с диска C:":
acpi = 1
apic = 1
disk = ['phy:/dev/vg0/winxp-disk,ioemu:hda,w']
boot = 'c'
3. Проброс USB-устройств вовнутрь виртуальной машины

Ещё одна вещь, которая мне понадобилась - это пробросить флешку вовнутрь виртуальной машины. Для начала я вставил её в компьютер и нашёл её в списке имеющихся в системе USB-устройств при помощи команды:
$ lsusb -v | less
В несколько сокращённом виде, нужная мне флешка выглядела следующим образом:
Bus 001 Device 005: ID 0930:6534 Toshiba Corp. TravelDrive
Device Descriptor:
  ...
  idVendor           0x0930 Toshiba Corp.
  idProduct          0x6534 TravelDrive
  bcdDevice            1.00
  iManufacturer           1 Kingston
  iProduct                2 DataTraveler 2.0
  iSerial                 3 0201101728450
  bNumConfigurations      1
  Configuration Descriptor:
    ...
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           3
      bInterfaceClass         8 Mass Storage
Чтобы флешка автоматически пробрасывалась в виртуальную машину, нужно отредактировать список USB-устройств в файле конфигурации /etc/xen/winxp.cfg:
usbdevice = ['tablet', 'host:0930:6534']
Если в системе имеется несколько устройств с одинаковыми идентификаторами производителя и модели, то можно указать номер шины и устройства. В нашем случае это будет выглядеть вот так:
usbdevice = ['tablet', 'host:1.5']
Однако стоит учесть, что при отключении и повторном включении устройство может получить другой номер. Использовать идентификаторы производителя и модели.

В случае с утилитой xm можно было бы вставлять и удалять устройства налету, вот так:
# xm usb-add winxp host:0930:6534
# xm usb-del winxp host:0930:6534
Однако, в утилите xl подобная функциональность пока не реализована и для введения настроек в силу понадобится завершить работу виртуальной машины и запустить её снова, с использованием исправленной конфигурации.

4. Использованные материалы

понедельник, 19 марта 2012 г.

Запросы и обновления Active Directory

Эта статься взята из журнала Windows IT Pro/RE за июнь 2006 года. Текст статьи можно найти по ссылке http://www.osp.ru/win2000/2006/04/2578905/, однако там отсутствуют листинги программ - возможно они потерялись при изменении структуры сайта. Найти копии этой статьи с листингами в интернете мне не удалось, поэтому нашёл сам журнал (на торрентах) и набрал листинги сам. Если правообладатели выложат на сайте листинги, готов убрать статью со своего блога.

Чем меня привлекла эта статья? Неделю назад я написал на Perl небольшой скрипт с использованием Net::LDAP. Документация на английском очень хороша, но мне приятнее обращаться к документации как к справочнику, а усваивать что-то новое проще всё-таки на русском. Хорошим учебником по этой теме может послужить книга Дэвида Бланк-Эдельмана "Perl для системного администрирования", в которой есть глава, посвящённая использованию модулей Net::LDAP и Mozilla::LDAP. Недостаток главы из этой книги заключается в том, что там оба модуля рассматриваются параллельно и читать из-за этого её не очень удобно. В этой статье сжато рассмотрены все основные операции по работе с каталогом LDAP применительно только к модулю Net::LDAP.

Хочу ещё отметить, что я совсем не ожидал увидеть такую статью в журнале, целиком посвящённом использованию Windows. Perl, как мне кажется, чужд системным администраторам Windows.

Запросы и обновления Active Directory

Использование модулей Perl Net::LDAP

Для автоматизации и организации программного доступа к Active Directory (AD) обычно выбирают интерфейс API, называемый Microsoft Active Directory Service Interfaces, ADSI. ADSI достаточно прост в использовании и имеет понятный интерфейс, позволяющий легко управлять объектами в AD. Благодаря комбинации ADSI и ADO для осуществления запросов можно выполнять практически любые действия, автоматизирующие использование AD.

Поскольку ADSI построен на модели COM, разработанной и применяемой только Microsoft, воспользоваться ADSI на отличных от Windows платформах не удастся. Использование ADSI для разработки сценариев или приложений для других операционных систем, а также разработка независимых от платформы сценариев или приложений затруднены. К счастью, существует альтернатива: можно использовать Lightweight Directory Access Protocol (LDAP) и его API.

Записи журналов Microsoft для поддерживаемых стандартов не всегда хороши, но с появлением AD специалисты Microsoft внесли изменения, которые заметно улучшили положение. AD поддерживает не только LDAP, но и другие стандарты, такие как DNS, Simple Network Time Protocol (SNTP), Secure Sockets Layer (SSL), Transport Layer Security (TLS) и Kerberos. Одна из причин взаимодействия AD с LDAP состоит в том, что LDAP делает AD более независимой от платформы с точки зрения клиента. Поскольку LDAP является стандартом, вы не ограничены Windows-клиентами и Windows-платформой. LDAP существует уже достаточно давно и имеет клиентов практически для всех платформ. Это означает, что вы можете писать и использовать сценарии для клиентов, поддерживающих LDAP и осуществляющих запросы и обновления AD с выбранной платформы. Такая возможность будет полезна для администраторов, которым необходимо разрабатывать сценарии и приложения для отличных от Windows платформ, а также для тех, кто пишет кроссплатформенные приложения и сценарии, и пользующие AD.

Многие думают, что LDAP — это всего-навсего протокол. Но, в отличие от большинства стандартов для протоколов, LDAP имеет определенный стандартом IETF) RFC программный интерфейс API. Он описан в RFC 1823 (http://www.ietf.org/rfc/rfc1823.txt) и обычно именуется C-style LDAP API. API содержит базовый набор функций для осуществления запросов и обновления основанного на LDAP каталога. Специалисты Microsoft создали соответствующий пакет разработчика, SDK, который используется для работы с LDAP API.

C-style LDAP API, являющийся практически стандартным для использования с LDAP, поначалу кажется удобным. Его недостаток заключается в том, что он не является объектно-ориентированным и недостаточно хорошо взаимодействует с некоторыми языками, такими как Java. Поэтому Sun Microsystems разработала собственный LDAP API, известный как Java Naming and Directory Interface, JNDI (см. http://java.sun.com/products/jndi). JNDI обладает расширенными возможностями по сравнению с LDAP API. JNDI является также интерфейсом к DNS, по идеологии похожим на ADSI, и может использоваться в качестве общего интерфейса к службе каталога.

В сообществе разработчиков Perl были созданы наборы Perl-модулей для LDAP, в основе которых лежит Netscape LDAP SDK, более известный как PerLDAP. В имени модулей PerLDAP имеется префикс Mozilla::LDAP. К сожалению, такие модули Perl требуют для использования дополнительной установки Netscape LDAP SDK. Другая группа разработчиков создала основанную на чистом Perl реализацию таких же модулей Perl, известных как perl-ldap. По именам они не пересекаются с модулями Netscape PerLDAP. Модули perl-ldap именуются префиксом Net::LDAP. Большое преимущество модулей Net::LDAP заключается в том, что вы можете установить модули Net::LDAP практически на любую систему, поддерживающую Perl. В результате появляется возможность написания основанных на LDAP клиентов без использования каких-либо внешних SDK или дополнительного программного обеспечения. Модули Net::LDAP используют большинство имен функций C-style API, но в отличие от них являются объектно-ориентированными и более простыми в применении.

В этой статье я объясню, как установить и использовать модули Net::LDAP для осуществления запросов к AD. Поскольку статья адресована пользователям, имеющим опыт работы с Perl, я не буду описывать процедуру установки. Мы обсудим использование модулей Net::LDAP для создания и обновления объектов в AD.

Установка Net::LDAP

У опытных пользователей Perl установка модулей не вызывает затруднений. При использовании для установки Net:: LDAP среды Comprehensive Perl Archive Network (CPAN) можно выполнить следующую команду:
> perl -MCPAN -e shell
cpan> install Net::LDAP
Если вы никогда не использовали среду CPAN, рекомендую изучить ее возможности как можно тщательнее, поскольку это значительно упростит процедуру установки и обновления модулей. Модуль CPAN.pm поставляется со многими дистрибутивами Perl. Когда пользователь запускает этот модуль в первый раз, программа установки проводит его через установку среды CPAN. Инструкции о том, как установить модули CPAN на различные платформы, можно найти на сайтах, посвященных CPAN (например, http://www.cpan.org/modules/INSTALL.html).

Ссылки на последнюю версию библиотеки perl-ldap вместе с документацией есть на домашней странице perl-ldap (http://perl-ldap.sourceforge.net). Версия кода perl-ldap, использованного в этой статье, perl-ldap-0.26.

Начало работы с Net::LDAP

Начнем с простого примера использования модулей Net::LDAP. Листинг 1 содержит код, возвращающий основную информацию из каталога. В этой программе я задействовал процедуру new() для создания нового соединения с контроллером домена (DC), имеющим имя dc1.

Листинг 1. Код, показывающий информацию из RootDSE
# Программа, показывающая информацию из RootDSE
use strict;
use Net::LDAP;
my $ldap = Net::LDAP->new('dc1') or die $@;
my $rootdse = $ldap->root_dse(attrs => ['defaultNamingContext']);
print $rootdse->get_value('defaultNamingContext');
После этого я вызвал процедуру root_dse для получения нужных атрибутов из Root DSE, который является хранилищем (репозитарием) информации о контроллере домена DC. В этом примере мне нужно получить атрибуты, определяющие контекст именования домена, поэтому я установил значение параметра attrs в defaultNamingContext. Чтобы получить все атрибуты в Root DSE, необходимо установить значение параметра attrs в (*). Затем я использовал процедуру get_value() для получения значений нужных атрибутов. Например, значение, возвращаемое для домена mycorp.com будет dc=mycorp, dc=com.

Заметим, что листинг 1 не содержит какого-либо кода для аутентификации. Root DSE доступен по анонимному доступу, чтобы приложения имели некую стартовую точку, определив ее из основной информации о каталоге. Для выполнения более сложных запросов понадобится включить в приложение аутентификационный код, подобный показанному в листинге 2. В этом коде я вызывал процедуру bind() и задавал требуемое полное имя distinguished name (DN) и пароль для учетной записи, от имени которой хотел регистрироваться. Метод bind() возвращает объект — сообщение Net::LDAP::Message, который я использую для определения того, были ли случаи ошибок во время аутентификации. Если ошибки были, метод code() возвращает код ошибки, а метод error() возвращает текстовое сообщение о ней. Для окончания сессии авторизации я использую процедуру unbind().

Листинг 2. Код аутентификации
# Код аутентификации
use strict;
use Net::LDAP;
my $dc = 'dc1';
my $user = 'cn=administrator, cn=users, dc=mycorp, dc=com';
my $passwd = 'Adminpasswd';
my $ldap = Net::LDAP->new($dc) or die $@;
my $rc = $ldap->bind($user, password => $password);
die $rc->error if $rc->code;
$ldap->unbind;

Запросы к AD

Для тех, кому хорошо известны параметры, использующиеся для поиска в LDAP, предпочтительным будет поиск при помощи Net::LDAP. Для выполнения поиска в LDAP обычно задаются три параметра; начальное полное имя DN, границы (scope) и фильтр. Параметр base DN задает точку, с которой начинается поиск. Параметр scope (границы) определяет границы (диапазон) поиска. Можно использовать одно из следующих значений:
  • base - соответствует только объекту, который задан в начальном DN,
  • onelevel или one - соответствует объектам, расположенным на один уровень ниже начального DN (т. е. прямые потомки родителя), исключая начальный DN,
  • subtree или sub - соответствует любым объектам, расположенным ниже начального DN, исключая начальный DN.
Параметр filter является префиксом строки, определяющей критерии, которым должны соответствовать объекты. Синтаксис фильтра определен в RFC 2254 (http://www.ietf.org/rfc/rfc2254.txt). В таблице 1 показано несколько простых фильтров поиска.

Таблица 1. Примеры фильтров поиска
Фильтр поискаКритерий поиска
(&(objectclass=user)(objectcategory=Person))Все пользовательсякие объекты в AD
(&(objectclass=user)(objectcategory=Precon)(sn=Allen)) Все объекты типа пользователь, имеющие фамилию Allen
(&(objectclass=computer)(objectcategory=Computer)(name=w2k*))Все объекты типа компьютер, начинающиеся с w2k
(&(objectclass=contact)(objectcategory=Person)(!description=*)))Все объекты контакты, не имеющие заполненного атрибута Description
В листинге 3 показан код программы, выполняющей запросы к AD для поиска всех пользователей, фамилия которых Allen. Заметьте, что переменная $user расположена в программном коде, выделенном в блоке A. Вместо задания полного имени пользователя user DN, я задаю основное имя пользователя user principal name (UPN), которое является идентификатором в стиле электронной почты и которое пользователь вводит при регистрации. Соответствие пользовательских имен UPN адресам электронной почты — достаточно распространенная практика. Если DNS-имя леса не соответствует DNS-суффиксу, используемому в e-mail-адресах, можно создать дополнительный суффикс UPN, выполнив шаги, описанные в статье Microsoft «HOW TO: Add UPN Suffixes to a Forest» (http://support.microsoft.com/?kbid=243629). Если вы используете UPN, вам нет необходимости задавать в программе полное DN-имя пользователя.

Листинг 3. Программа, выполняющая запросы к AD
# Программа, выполняющая запросы к AD
use strict;
use Net::LDAP;
# BEGIN CALLOUT A
# BEGIN COMMENT
# Задание соединения и параметров регистрации
# END COMMENT
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
# BEGIN COMMENT
# Определение параметров поиска
# END COMMENT
my $base = "cn=users, dc=mycorp, dc=com";
my $scope = "subtree";
my $filter = "(&(objectclass=user)(objectcategory=user)(sn=Allen))";
my $ldap = Net::LDAP->new($dc) or die $@;
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# END CALLOUT A
# BEGIN CALLOUT B
my $search = $ldap->search(
  base => $base,
  scope => $scope,
  filter => $filter
);
die $search->error if $search->code;
# END CALLOUT B
# BEGIN CALLOUT C
foreach my $entry ($search->entries) {
  $entry->dump;
}
# END CALLOUT C
$ldap->unbind;
Как видно из программного кода в блоке B листинга 3, я использовал для осуществления запросов процедуру search(). Были заданы три параметра (т. е. base DN, scope и filter), которые мы рассмотрели выше. Дополнительно был задан параметр attrs, задающий массив атрибутов, которые необходимо вывести. Если не задать параметр attrs, операция поиска возвратит все атрибуты, имеющие значение. Если в результате поиска возвращается большое число атрибутов, возможно, кто-то захочет уменьшить количество возвращаемых данных, использовав для этого параметр.

Как видно из программного кода в блоке C листинга 3, я использовал процедуру entries() для последовательного отображения возвращаемых процедурой поиска данных. Этот метод возвращает массив объектов Net::LDAP::Entry. Для каждого из таких объектов я использовал процедуру dump() для вывода всех возвращаемых атрибутов. Если необходим доступ к определенному атрибуту, можно использовать процедуру get_value() так, как это было продемонстрировано в листинге 1.

Создание утилиты поиска в LDAP

Теперь, зная основы использования модулей Net::LDAP для выполнения операций поиска, давайте рассмотрим код простой утилиты командной строки ldapsearch.pl, показанной в листинге 4. При помощи этой утилиты можно производить запросы к AD. Ldapsearch.pl основывается на широко используемой утилите поиска в LDAP (ldapsearch), доступной в большинстве пакетов SDK для LDAP и серверов каталога LDAP (за исключением AD).

Листинг 4. Ldapsearch.pl
use strict;
use Net::LDAP;
use Net::LDAP::Filter;
use Getopt::Std;
# BEGIN CALLOUT A
# BEGIN COMMENT
# Получение и проверка заданных параметров.
# END COMMENT
my %opts;
getopt('bshpDwfa', \%opts);
my($err_str) = validate_options(\%opts);
if ($err_str) {
  print "ldapsearch.pl: $err_str\n";
  print usage();
  exit;
}
# END CALLOUT A
# BEGIN CALLOUT B
# BEGIN COMMENT
# Соединение, затем привязка к хосту
# END COMMENT
my $ldap = Net::LDAP->new($opts{h}, port => $opts{p} || 389) or die "$@\n";
my $rc->error if $rc->code;
# END CALLOUT B
# BEGIN CALLOUT C
# BEGIN COMMENT
# Выполнение поиска и отображение результатов
# END COMMENT
my $search = $ldap->search {
  base => $opts{b},
  scope => $opts{s},
  filter => $opts{f},
  attrs => [split /,/, $opts{a}],
);
die $search->error if $search->code;
foreach my $entry ($search->entires) {
  $entry->dump;
}
$ldap->unbind;
exit;
# END CALLOUT C
# BEGIN CALLOUT D
# BEGIN COMMENT
# Различные функции.
# END COMMENT
sub usage {
  return qq(
    usage: ldapsearch.pl [options]
    -b basedn     base dn for search
    -s scope      one of base,one, or sub (search scope)
    -h host       ldap server
    -D binddn     bind dn
    -w passwd     bind passwd (for authentication)
    -f filter     RFC-2254 compliant LDAP search filter
    -a attributes comma-separated list of attributes to retrieve
    -p port       port on ldap server
  );
}
sub validate_options {
  my $opts_ref = $_[0];
  foreach (qw(b s h D w f a)) {
    return("-$_ required") unless $opts_ref->{$_};
  }
  my $filter = Net::LDAP::Filter->new($opts_ref->{f});
  return("Bad search filter") unless ref $filter;
  return;
}
# END CALLOUT D
Основной код ldapsearch.pl по большей части не отличается от программного кода в листинге 3, кроме ключей командной строки, семь из которых являются обязательными. Вы уже знакомы с некоторыми из этих ключей: ключи -b, -s и -f задают, соответственно, параметры для поиска base DN (базовое полное имя), scope (границы, диапазон) и filter (фильтр). Дополнительные ключи -D и -w задают полное имя пользователя и пароль и применяются в целях авторизации. Ключ -h задает имя сервера LDAP. Ключ -a определяет атрибуты, которые нужно запросить. Необходимо задать атрибуты в виде списка, разделенного запятыми. Последний ключ -p является необязательным параметром. Рассмотрим его назначение.

Чтобы выполнить обработку ключей командной строки, я использовал модуль Perl с именем GetOpt::Std. Этот модуль обеспечивает базовую функциональность для обработки ключей. Поскольку большинство ключей обязательные, ldapsearch.pl включает в себя программный код, проверяющий наличие ключей. Этот программный код показан в блоке A листинга 4.

Блок B листинга 3 выделяет программный код, выполняющий установку соединения. Разница между этим кодом и кодом в листинге 2 — в использовании ключа port (-p). В командной строке можно использовать ключ -p для задания альтернативного порта, например 3268 для глобального каталога Global Catalog (GC). Если ключ -p не используется, сценарий по умолчанию задействует порт 389, который является стандартным портом LDAP.

Программный код в блоке C листинга 4 выполняет поиск и вывод соответствующих атрибутов. Этот программный код также похож на представленный в листинге 3, за исключением параметра attr в процедуре поиска, поскольку введен ключ -а, задающий атрибуты для вывода. Я использовал функцию split для включения в массив списка, разделенного запятыми.

На экране 1 показаны результаты работы сценария ldapsearch.pl. Первые три строки содержат параметры, используемые для запуска сценария из командной строки. Как видно по этим параметрам, я запускал сценарий для хоста dc1 (ключ -h). Сценарий выполняет поиск в контейнере cn=computers, dc=mycorp, dc=com (ключ -b) и в подконтейнерах (ключ -s) для всех объектов «компьютер», имеющих имя, начинающееся с app (ключ -f ). Результаты запроса определяют строки, следующие за параметрами. Поскольку я задал возвращение только атрибута cn для каждого из объектов (ключ -a ), результатом будет показ полного имени DN соответствующего объекта и его атрибута cn.

Экран 1. Результаты работы ldapsearch.pl
> perl ldapsearch.pl -b cn=computers,dc=mycorp,dc=com -s sub
-h dc1 -D rallen@mycorp.com -w MyPasswd
- "(&(objectclass=computer)(objectcategory=computer)(cn=app*)" -a cn
----------------------------------------------------------------------
dn:CN=APP01,CN=Computers,DC=mycorp,DC=com

cn:APP01
----------------------------------------------------------------------
dn:CN=APP02,CN=Computers,DC=mycorp,DC=com

cn:APP02
----------------------------------------------------------------------
dn:CN=APP03,CN=Computers,DC=mycorp,DC=com

cn:APP03
----------------------------------------------------------------------
dn:CN=APP04,CN=Computers,DC=mycorp,DC=com

cn:APP04

Добавление объектов

Использование Net::LDAP для добавления объектов имеет свои преимущества, например, листинг 5 содержит код, который добавляет объект John Doe. Блок A в листинге 5 выводит параметры, которые необходимо изменить для получения работающей программы. Для переменной $dc требуется задать контроллер домена, на котором будут выполняться операции добавления. Переменным $user и $passwd следует присвоить соответствующее имя и пароль, под которыми нужно подключиться к заданному контроллеру домена. Переменная $parent_dn должна содержать полное имя DN родительского контейнера, в котором предполагается разместить объект John Doe. Программный код после блока A в листинге 1 подключается к заданному контроллеру домена и использует введенные имя и пароль для соединения с ним.

Листинг 5. Программа, добавляющая объект Contact
# Программа, добавляющая объект Contact
use strict;
use Net::LDAP;
# BEGIN CALLOUT A
# BEGIN COMMENT
# Настройте для вашего окружения
# END COMMENT
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
my $parent_dn = "cn=users,dc=mycorp, dc=com";
# END CALLOUT A
# BEGIN COMMENT
# Соединение и аутентификация
# END COMMENT
my $ldap = Net::LDAP->new($dc) or die "$@\n";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# BEGIN CALLOUT B
# BEGIN COMMENT
# Добавление объекта contact John Doe.
# END COMMENT
$rc = $ldap->add("cn=mycontact, $parent_dn",
  attrs => [
    objectclass => 'contact',
    displayName => 'John Doe',
    sn => 'Doe',
    givenName => 'John',
    telephoneNumber => '555-123-4567',
  ]);
# END CALLOUT B
if ($rc->code) {
  print "Add failed: ", $rc->error, "\n";
}
else {
  print "Add successful\n";
}
$ldap->unbind;
Код блока B листинга 5 вызывает процедуру add(). Первым параметром является полное имя DN нового добавляемого объекта. Второй параметр attrs указывает на массив ссылок, содержащий атрибуты, которые присваиваются новому объекту. Необходимо включить некоторые обязательные параметры, например objectClass, которые не имеют значения по умолчанию, или добавить методы обработки на случай ошибки. Например, чтобы добавить объект «пользователь», необходимо, как минимум, задать user для параметра objectClass и имя пользователя для параметра sAMAccountName.

Метод add() возвращает объект Net::LDAP::Message, включающий процедуру code(), которая позволяет определить, были ли ошибки. Когда метод code() объекта Net::LDAP::Message возвращает значение 0, это означает, что контакт успешно добавлен. Если же метод code() возвращает иное значение, следовательно, была обнаружена ошибка, и метод error() отобразит соответствующее сообщение.

Листинг 5 добавляет в AD только один объект. Этот код можно расширить для добавления тысяч объектов. Можно использовать модули Perl Database Interface (DBI) для осуществления запросов к базе данных и пополнения AD восстановленной информацией. DBI-модули есть на сайте CPAN (http://www.perl.com/CPAN-local/modules/by-module/DBI).

Удаление объектов

Удалять объекты, используя Net::LDAP, даже проще, чем добавлять их. Все, что необходимо для проведения операции, это ввести полное имя объекта DN, который требуется удалить при помощи процедуры Net::LDAP delete(). Например, листинг 6 содержит код, удаляющий все объекты контактов в организационном подразделении ou=Contacts, dc=mycorp, dc=com.

Листинг 6. Код удаления объектов
# Код удаления объектов
use strict;
use Net::LDAP;
# BEGIN CALLOUT A
# BEGIN COMMENT
# Настройте для вашего окружения
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
my $parent_dn = 'ou=Contacts, dc=mycorp, dc=com';
# END CALLOUT A
# BEGIN COMMENT
# Соединение и аутентификация
# END COMMENT
my $ldap = Net::LDAP->new($dc) or die "$@\n";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# BEGIN CALLOUT B
# BEGIN COMMENT
# Найти все объекты типа контакт в родительском DN
# END COMMENT
my $search = $ldap->search(
  base => $parent_dn,
  scope => 'one',
  filter => "(objectClass=contact)",
  attrs => ['cn']
};
die $search->error if $search->code;
# END CALLOUT B
# BEGIN CALLOUT C
# BEGIN COMMENT
# Удалить все подходящие объекты
# END COMMENT
my $count = 0;
foreach my $entry ($search->entries) {
  $rc = $ldap->delete($entry->dn);
  if ($rc->code) {
    print "Delete failed for ", $entry->get_value('cn'), ": ", $rc->error, "\n";
  }
  else {
    $count++;
    print "Delete successful: " $entry->get_value('cn'), "\n";
  }
}
print "Total successfully deleted: $count=n";
# END CALLOUT C
$ldap->unbind;
Программный код блока A показывает параметры, которые необходимо задать. Программный код блока B листинга 6 осуществляет поиск объектов для удаления. Ранее мы рассмотрели, как используется метод search(), включая процесс установки каждого из параметров. В нашем случае мы устанавливаем параметр base для задания полного имени DN-контейнера, содержащего объекты, подлежащие удалению. Нам требуется удалить все объекты в этом контейнере, но не сам контейнер. Для этого присвоим параметру scope значение 'one', чтобы исключить начальное DN из поиска. Поиск будет вестись на уровне, следующем за начальным DN. Параметр filter содержит фильтр поиска, который задает просмотр объектов контактов. Параметр attrs содержит массив ссылок на отдельный атрибут 'cn'. Метод search() возвращает соответствующие объекты как массив объектов Net::LDAP::Entry.

Программный код блока C в листинге 6 использует процедуру объекта Net::LDAP::Entry dn() для получения DN для каждого из соответствующих объектов, так чтобы метод delete() мог удалить объект. Метод code() проверяет результат на предмет того, была ли обнаружена ошибка, и выводит соответствующее сообщение.

В некоторых случаях вместо удаления каждого из объектов в OU, возможно, понадобится удалить родительский OU и все его дочерние объекты за одну операцию. К сожалению, выполнить операцию удаления такого типа при помощи простой команды delete не удастся. Придется использовать расширение протокола Lightweight Directory Access Protocol (LDAP), называемое control, для информирования сервера об удалении отдельных контейнеров и всех его дочерних объектов. Применение расширения Net::LDAP controls выходит за рамки данной статьи и послужит темой для последующих публикаций. А если кому-то уже сейчас необходимо узнать о функциях controls, можно ознакомиться с документацией на сайте, посвященном perl-ldap (http://perl-ldap.sourceforge.net).

Изменение объектов

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

Подобно методам add() и delete(), метод modify() в качестве первого параметра имеет полное имя DN объекта, подлежащего изменению. Вторым является параметр, задающий тип выполняемой модификации. Метод modify() имеет несколько режимов работы и можно выбрать наиболее подходящий.

Параметр add. Этот параметр добавляет или устанавливает значение атрибута, который до этого не имел присвоенного значения. Можно использовать параметр add для добавления нового значения в атрибут, имеющий несколько параметров. Параметр add использует ссылки на хеш (функции хеша). Ключ хеша содержит имя атрибута, а значение ключа хеша содержит значение атрибута. Например, для того чтобы добавить атрибут mail и присвоить ему значение jdoe@mycorp.com, можно воспользоваться следующим программным кодом:
$ldap->modify($dn, add =>
  {mail => 'jdoe@mycorp.com'});
Параметр delete. Параметр удаляет определенное значение, связанное с атрибутом. Он также использует ссылку на хеш, содержащий соответствующие пары ключевых значений для удаления, подобно параметру add, либо ссылается на массив атрибутов, для которого нужно удалить все значения. Например, для удаления всех значений, связанных с атрибутами mail и displayname, можно воспользоваться следующим программным кодом:
$ldap->modify($dn, delete =>
  ['mail', 'displayname']);
Параметр replace. Параметр replace используется для изменения существующих параметров атрибута заданными значениями. Он задействует те же параметры, что и add. Так, например, если ввести неправильный адрес email при добавлении атрибута mail, можно изменить его на правильный следующим программным кодом:
$ldap->modify($dn, replace =>
  {mail => 'joe@mycorp.com'});
Параметр changes. Параметр используется для группировки набора параметров add, delete и replace в единый вызов. Параметр changes использует ссылки на массив, содержащий пары «имя параметра — значение». Например, для удаления значения, связанного с атрибутом mail, и добавления атрибутов givenname и sn, значения которых соответственно John и Doe, можно воспользоваться кодом:
$ldap->modify($dn,
  changes => [
    add => [givenname => "John"],
    add => [sn => "Doe"],
    delete => ['mail']]);
Листинг 7 использует параметры, о которых было рассказано, для изменения объекта «пользователь». Подобно методам add() и delete(), метод modify() возвращает объект Net::LDAP::Message, метод code() которого используется для определения факта возникновения ошибки.

Листинг 7. Программа, изменяющая атрибуты объекта "пользователь"
# Программа, изменяющая атрибуты объекта "пользователь"
use strict;
use Net::LDAP;
# Настройте для вашего окружения
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
my $dn = "cn=jdoe, cn=users, dc=mycorp, dc=com";
# Соединение и аутентификация
my $ldap = Net::LDAP->new($dc) or die "$@\n";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# Изменение нескольких атрибутов
pring "Setting givename, sn and mail...\n";
$rc = $ldap->modify($dn,
  changes => [
    add => [givename => "Johnny"],
    add => [sn => "Doh"],
    add => [mail => 'jdoe@mycorp.com'],
  ]
);
die $rc->error if $rc->code'
print "Changing givename to John...\n";
$rc = $ldap->modify($dn,
  replace => {givename => "John" });
die $rc->error if $rc->code;
print "Deleting the mail attribute...\n";
$rc = $ldap->modify($dn,
  delete => ['mail']);
die $rc->error if $rc->code;
print "Setting the telephoneNumber and sn...\n";
$rc = $ldap->modify($dn,
  changes => [
    add => [telephoneNumber => '555-123-4567'],
    replace => [sn => 'Doe'],
  ]
);
die $rc->error if $rc->code;
print "\nModifications successful\n";
$ldap->unbind;

Переименование и перемещение объектов

Чтобы иметь полный набор возможностей для управления объектами AD, необходима функция переименования и перемещения объектов. Метод Net::LDAP moddn() позволяет выполнить оба названных действия. Подобно методам, описанным выше, метод moddn() также имеет в качестве первого параметра полное имя объекта (DN), который предполагается переименовать или переместить. Второй параметр может состоять из одного или нескольких.

Параметры newrdn и deleteoldrdn. В AD вы идентифицируете объект по его полному имени DN, которое включает относительное имя relative distinguished name (RDN). RDN определяет имя объекта. Возьмем, например, объект с полным именем DN cn=jdoe, cn=users, dc=mycorp, dc=com. В этом случае относительным именем, RDN, будет jdoe. Параметр newrdn можно использовать для присвоения объекту нового RDN. Значение newrdn должно включать не только имя объекта, (например, jsmith), но и его идентификатор (например, cn=).

Листинг 8 содержит программный код, переименовывающий объект user cn=jdoe, cn=users, dc=mycorp, dc=com в cn=jsmith, cn=users, dc=mycorp, dc=com. Блок A листинга 8 выделяет код, включающий параметр newrdn. Этот код содержит в себе и параметр deleteoldrdn. Установив параметр deleteoldrdn в 1 (т. е. true), вы тем самым удаляете объект, имеющий старое имя RDN. Если же не включать параметр deleteoldrdn или установить его значение в 0 (т. е. false), старый объект, будучи переименованным или перемещенным, останется. Для того чтобы избежать такой ситуации, следует использовать параметр deleteoldrdn со значением 1.

Листинг 8. Программа, переименовывающая и удаляющая объект
# Программа, переименовывающая и удаляющая объект
use strict;
use Net::LDAP;
# BEGIN COMMENT
# Настройте для вашей среды
# END COMMENT
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
my $dn = "cn=jdoe, cn=users, dc=mycorp, dc=com";
my $new_rdn = "cn=jsmith";
# BEGIN COMMENT
# Соединение и аутентификация
# END COMMENT
my $ldap = Net::LDAP->new($dc) or die "$@\n";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# BEGIN CALLOUT A
# BEGIN COMMENT
# Переименование jdoe в jsmith
# END COMMENT
$rc = $ldap->moddn($dn, newrdn => $new_rdn, deleteoldrdn => 1);
# END CALLOUT A
if ($rc->code) {
  print "Error renaming user: ", $rc->error, "\n";
}
else {
  print "Succesfully renamed user\n";
}
$ldap->unbind;
Параметр newsuperior. Этот параметр применяется для перемещения объекта в другой родительский контейнер. Данным параметром задается для полного имени новый родительский контейнер. Например, листинг 9 содержит код, перемещающий все пользовательские объекты, атрибут department которых имеет значение Sales в OU Sales. Как показано в листинге 9, переменная $old_parent определяет родительский контейнер, в котором вы ведете поиск объектов, а переменная $new_parent задает родительский контейнер, в который предстоит перенести объекты. В листинге 9 в блоке B выделен код, ведущий поиск $old_parent для всех объектов пользователей с атрибутом department, равным Sales. Программный код в блоке C листинга 4 использует оператор foreach для перемещения всех подобных объектов в $new_parent.

Листинг 9. Программа, перемещающая объекты
# Программа, перемещающая объекты
use strict;
use Net::LDAP;
# BEGIN COMMENT
# Настройте для вашего окружения
# END COMMENT
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
# BEGIN CALLOUT A
my $old_parent = "cn=users, dc=mycorp, dc=com";
my $new_parent = "ou=sales, dc=mycorp, dc=com";
# END CALLOUT A
# BEGIN COMMENT
# Подключение и аутентификация
# END COMMENT
my $ldap = Net::LDAP->new($dc) or die "$@\n";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# BEGIN CALLOUT B
# BEGIN COMMENT
# Поиск всех объектов user, чей атрибут department = Sales
# END COMMENT
my $search = $ldap->search(
  base => $old_parent,
  scope => 'one',
  filter => "(&(objectclass=user)(objectcategory=Person)(department=Sales))",
  attrs => ['cn']
);
die $search->error if $search->code;
# END CALLOUT B
# BEGIN CALLOUT C
# BEGIN COMMENT
# Перемещение соответствующих пользователей в Sales OU
# END COMMENT
my $count = 0;
foreach my $entry($search->entries) {
  $rc = $ldap->modrdn($entry->dn,
    newrdn => 'cn=' . $entry->get_value('cn'),
    newsuperior => $new_parent,
    deleteoldrdn => 1);
  if ($rc->code) {
    print "Move failed for ", $entry->get_value('cn'), ": ", $rc->error, "\n";
  }
  else {
    $count++;
    print "Move successful: ", $entry->get_value('cn'), "\n";
  }
}
print "Total successfully moved: $count\n";
# END CALLOUT C
$ldap->unbind;
Итак, в этой статье мы рассмотрели, как использовать модули Net::LDAP для управления объектами в AD. При использовании этих модулей Perl можно выполнять все необходимые манипуляции для управления данными в AD.

Робби Аллен - Технический руководитель в компании Cisco Systems. MVP по Windows Server Directory Services. rallen@rallenhome.com

пятница, 11 ноября 2011 г.

Перенос Windows XP на другой диск средствами Linux

Для начала хочу предупредить, что это не точная инструкция как и что делать. Это описание ключевых моментов, для правильного применения которых нужно ещё приложить собственную голову. Если вы не уверены в своих силах, то лучше воспользуйтесь специализированными программами (Acronis True Image или Norton Ghost), а не этим способом, который описывает только средства из Linux.

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

У меня стоит загрузчик GRUB2, но почему-то не стояло очень важного компонента - os-prober. Он умеет при подсовывании ему дискового раздела определять, что за операционная система там стоит. Его наличие позволит упростить настройку GRUB2. Также, поскольку мы будем переносить систему Windows XP с раздела NTFS на раздел NTFS, нам потребуется пакет ntfsprogs. Ну и если у вас ещё нет раздела на диске, в который вы хотите перенести Windows, вам могут потребоваться fdisk и/или parted. Полезным может оказаться и модуль ntfs-3g для монтирования NTFS-раздела в режиме записи.
# apt-get install os-prober ntfsprogs hexedit
Останавливаться на создании раздела для Windows я не стану, т.к. это дело тривиальное и, на мой взгляд, не требующее объяснений.

Первым делом скопируем NTFS-раздел. Делается это командой ntfsclone:
# ntfsclone --overwrite /dev/sdb3 /dev/sda1
/dev/sda1 - это исходный раздел с Windows,
/dev/sdb3 - это целевой раздел, на который мы переносим систему.
БУДЬТЕ ВНИМАТЕЛЬНЫ, ничего не перепутайте! Посмотреть, какие разделы есть на дисках, можно с помощью команды:
# fdisk -l
Если возникают сомнения, смонтируйте разделы и убедитесь в том, что вы точно знаете, с какого на какой раздел нужно перенести систему. У меня таких проблем не было, т.к. диска было всего два, на одном из дисков было аж 6 разделов, а на другом - только 1, поэтому перепутать их было трудно.

После клонирования мы получим раздел в точности повторяющий исходный. Ловушка заключается в том, что загрузчик Windows, располагающийся в начале раздела (NTFS Boot Record), содержит в себе информацию о геометрии диска, положении раздела и о серийном номере этого раздела.

На данном этапе мне помогла статья Relocating Windows to a new Hard Drive, в которой есть ссылка на исходный текст простенькой программы, которая исправляет загрузчик NTFS так, чтобы он узнал о новой геометрии диска. Эту программу я на всякий случай скопировал себе и разместил тут: ntfsreloc.c

Соберём её (для этого может потребоваться установить компилятор языка Си, который у меня уже стоял, т.к. я иногда на нём пишу сам):
$ gcc -o ntfsreloc ntfsreloc.c
Теперь её нужно запустить:
# ntfsreloc -w -p /dev/sdb3
Затем нужно смонтировать диск и исправить в файле boot.ini в корневом каталоге номер раздела, с которого будет грузиться Windows. Раньше у меня это был раздел №1, а теперь - №3.
[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(3)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(3)\WINDOWS="Microsoft Windows XP Professional RU" /noexecute=optin /fastdetect
Теперь нужно исправить карту дисков GRUB, которая находится в файле /boot/grub/device.map:
(fd0)   /dev/fd0
(hd0)   /dev/disk/by-id/ata-WDC_WD10EADS-65L5B1_WD-WCAU4A888959
(hd1)   /dev/disk/by-id/ata-ST34313A_6CR0J30R
Диском (hd0) должен быть тот, на который мы переносим систему. Или можно исправить номер диска в файле boot.ini Windows, так чтобы номер загрузочного диска совпадал с номером диска в device.map. Полезно также перед этим настроить привязку дисков в /etc/fstab к UUID раздела или к метке раздела, чтобы в случае путаницы с номерами дисков Linux всё-таки загрузился. Практически во всех современных системах Linux это уже так, т.к. с заменой драйвера IDE на унифицированный драйвер дисков, номер, под которым определится диск, стал непредсказуем.

Дальше нужно обновить конфигурацию GRUB2:
# update-grub
И можно перезагружаться. При первой загрузке нам потребуются оба диска, т.к. в реестре Windows сохранилась привязка к идентификатору прежнего диска. После загрузки системы запускаем regedit и редактируем раздел HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices. В нём нужно поменять буквы дисков C и того, который по содержимому идентичен диску C. У меня это были диски C и D. Сделал я это в три приёма:
C -> Z
D -> C
Z -> D
После этого завершаем работу, отключаем старый диск и пробуем загрузиться снова. Если всё было сделано правильно, система загрузится с нового диска.

Я таким образом смог отключить противно свистящий старый диск, на котором у меня стоял Windows XP. Сама эта заметка является, по сути, продолжением предыдущей: Настройка fancontrol - демона управления вентиляторами компьютера.

Желаю вам удачного переноса Windows!

P.S. 21 декабря 2013 года. Вместо программы ntfsreloc можно воспользоваться программой partclone.ntfsfixboot из пакета partclone. В этом случае вместо команды
# ntfsreloc -w -p /dev/sdb3
можно воспользоваться командой
# partclone.ntfsfixboot -w /dev/sdb3