среда, 24 февраля 2010 г.

JA2 во FreeBSD

Много лет назад, когда я ещё жил в общежитии университета, у нас ещё не было ни локальной сети, ни Интернета. Тогда мы с соседями убивали время за играми... Мне особенно нравились различные RPG и пошаговые стратегии. Одной из таких игр была Jagged Alliance 2. Довольно неплохая игра для того времени.

Я обнаружил её в портах FreeBSD, и что интересно, сейчас она уже распространяется с открытым исходным кодом. Захотелось посмотреть, как она будет выглядеть сейчас. Установка игры из порта games/jaggedalliance2 не вызвала никаких сложностей. Проблему вызвало только отсутствие файлов ресурсов игры - карты, озвучка, ролики и т.п. Они в состав порта не входили. Нужны были оригинальные диски с игрой. Диски когда-то у меня были (я покупал 1С-овскую "Gold" версию), но вот найти их мне не удалось, смутно припоминалось, что возможно я подарил их племяннику жены...

Ну что ж.. После успешной установки сдаваться так просто не хотелось, немного погуглив :) я нашёл нужные мне исошники. Скачал, но опять же на них ресурсы были запакованы инсталлятором. Пришлось
воспользоваться компьютером жены с Windows, где я и установил игру. :)

После этого я создал на своём ZFS пуле новый датасет установиви свойство casesensitivity в значение insensitive для того чтобы не возникло проблем с регистром символов в именах файлов и каталогов:
> sudo zfs create -o casesensitivity=insensitive -o mountpoint=/usr/local/share/ja2 zroot/data
И скопировал с компьютера жены каталог Data в созданный датасет. После этого запустил из коммандной строки ja2, игра тут же закрылась и в домашнем каталоге создался подкаталог ~/.ja2 в котором я отредактировал файл ~/.ja2/ja2.ini и указал там путь к файлам ресурсов:
#Tells ja2-stracciatella where the binary datafiles are located
data_dir = /usr/local/share/ja2
Но игра и в этот раз не запустилась, ругнувшись на файлы ресурсов... Посмотрев в содержимое архива с исходниками я обнаружил, что там в параметрах make упоминается RUSSIAN_GOLD, которого нет среди опций порта. А ведь ISO'шки с игрой у меня были именно версии Gold. Я быстренько поправил Makefile порта добавив ещё одну опцию RUSSIAN_GOLD и пересобрал игру. Запустив новый бинарник игра запустилась в окошечке и вполне неплохо выглядела. По Alt+Enter она может разворачиваться на полный экран и обратно.


После этого я решил оформить патч и отправить PR для внесения изменений в порт. Патч получился таким:
--- jaggedalliance2/Makefile     2009-06-14 12:41:39.000000000 +0400
+++ jaggedalliance2/Makefile     2010-02-01 08:27:31.825416359 +0300
@@ -7,6 +7,7 @@
 PORTNAME=       ja2
 PORTVERSION=    0.12
+PORTREVISION=   1
 CATEGORIES=     games
 MASTER_SITES=   http://deponie.yamagi.org/freebsd/distfiles/ \
                 http://tron.homeunix.org/ja2/
@@ -34,7 +35,8 @@ OPTIONS=       DUTCH   "Compile for Dutch versio
                 GERMAN  "Compile for German version (choose ONE)"       Off \
                 ITALIAN "Compile for Italian version (choose ONE)"      Off \
                 POLISH  "Compile for Polish version (choose ONE)"       Off \
-                RUSSIAN "Compile for Russian version (choose ONE)"      Off
+                RUSSIAN "Compile for Russian version (choose ONE)"      Off \
+                RUSSIAN_GOLD "Compile for Russian Gold version (choose ONE)"    Off
 .include 
@@ -52,6 +54,8 @@ MAKE_ARGS+=    LNG=ITALIAN
 MAKE_ARGS+=     LNG=POLISH
 .elifdef WITH_RUSSIAN
 MAKE_ARGS+=     LNG=RUSSIAN
+.elifdef WITH_RUSSIAN_GOLD
+MAKE_ARGS+=     LNG=RUSSIAN_GOLD
 .else
 IGNORE= no language has been chosen. Please choose the appropriate language for your game CD
 .endif
Вроде все изменения тривиальные и объяснять нечего... Сам PR можно посмотреть здесь. Через три недели патч был одобрен и изменения были внесены. Такая большая задержка получилась из-за ожидания ответа мэйнтейнера, который так и не ответил...

суббота, 20 февраля 2010 г.

Переопределение настроек DHCP на клиенте

Когда появляется задача использовать в локальной сети какие-то дополнительные настройки помимо тех, которые предлагает использовать DHCP сервер, есть несколько способов сделать это.
На мой взгляд наиболее правильный и простой способ (ну или, по крайней мере, один из них) - использовать возможности dhclient.

Вот так, к примеру, выглядит /etc/dhclient.conf на нетбуке, с которого я пишу это сообщение:
interface "wlan0" {
 send host-name "btr-nb.properlan.net";
 prepend domain-name-servers 10.0.0.3;
 append classless-routes 29,192,168,212,0,10,0,12,12;
 request subnet-mask, broadcast-address,
  classless-routes, routers, domain-name,
  domain-name-servers, host-name;
}
Здесь есть две дополнительные команды prepend и append, которые позволяют изменить настройки, выдаваемые DHCP сервером. В моём случае это адрес DNS сервера и дополнительный статический маршрут.

В общем-то, вроде ничего сложного, об этих командах можно прочитать в руководстве dhclient.conf(5). Команда append добавляет указанное значение к выбранной опции, а prepend добавляет к указанному значению значение, выдаваемое сервером. Есть и ещё одна команда - supersede, она переопределяет значение указанной опции. Сложность может вызвать разве что выбор формата, в котором записать значение опции. Даже в приведённом мной примере не для каждого покажется очевидным, что запись "29,192,168,212,0,10,0,12,12" означает "192.168.212.0/29 10.0.12.12". Мне в какой-то степени это проще, ведь я добавлял поддержку этой опции в dhclient. :)

Формат каждой опции можно узнать в исходном коде dhclient'а. К счатью, это вполне по силам любому, знание языков программирования для этого не нужно. Для этого достаточно просмотреть файл /usr/src/sbin/dhclient/tables.c:
/*
 * DHCP Option names, formats and codes, from RFC1533.
 *
 * Format codes:
 *
 * e - end of data
 * I - IP address
 * l - 32-bit signed integer
 * L - 32-bit unsigned integer
 * s - 16-bit signed integer
 * S - 16-bit unsigned integer
 * b - 8-bit signed integer
 * B - 8-bit unsigned integer
 * t - ASCII text
 * f - flag (true or false)
 * A - array of whatever precedes (e.g., IA means array of IP addresses)
 */

struct universe dhcp_universe;
struct option dhcp_options[256] = {
        { "pad", "",                                    &dhcp_universe, 0 },
        { "subnet-mask", "I",                           &dhcp_universe, 1 },
        { "time-offset", "l",                           &dhcp_universe, 2 },
        { "routers", "IA",                              &dhcp_universe, 3 },
В комментарии перечислены буквенные коды возможных форматов опций, например "I" - IP-адрес, а "IA" - список IP адресов. Так, для опции domain-name-servers указан код формата "IA". Это значит, что в dhclient.conf эту опцию можно задать списком IP адресов, перечисленных через запятую. Для опции classless-routes указан код формата "BA" - список беззнаковых чисел. Но для того чтобы знать, какие числа указывать, нужно изучить RFC 3442. Если в кратце - сначала идёт маска сети, затем значащие цифры номера сети и потом адрес шлюза. Но длинна номера сети может различаться в зависимости от маски, поэтому лучше всётаки изучить RFC.
Ну и в качестве дополнительного бонуса, в файле tables.c можно найти все поддерживаемые опции.

четверг, 18 февраля 2010 г.

Баг с отображением активных наборов правил в ipfw

Общаясь в IRC RusNet узнал, что в 8-ой версии FreeBSD странно работает команда ipfw set show, которая должна показывать, номера активных и заблокированных наборов правил. А именно - не смотря на явное отключение наборов, вывод команды не изменяется. Т.е. другими словами, команда всегда показывает одно и то же - все наборы активны.

После небольшого исследования исходного кода подтвердилось наличие проблемы. Немного порывшись в истории коммитов нашёлся и источник - r200855. Проблема заключается в том, что код операции IP_FW_GET был переписан (в рамках подготовки к вливанию новой версии ipfw3), но не достаточно оттестирован, как это обычно и случается :).

О проблеме сразу же сообщил виновнику, но воз и ныне там, так что нужно будет заняться исправлением, как только времени побольше появится...

суббота, 13 февраля 2010 г.

Потенциально опасное использование ldd(1)

Прочитал тут на RSDN интересную тему об особенностях работы ldd(1). Там же, в одном из сообщений обнаружил ссылку на статью "ldd arbitrary code execution". Если в кратце, то опасность заключается в возможности исполнения кода программы, путь к которой передаётся в параметрах ldd(1). Решил проверить во FreeBSD, так ли всё опасно на самом деле? Оказалось - да.

Действительно, если скомпилировать программу с указанием специального загрузчика для неё, то получаем не совсем то, что хочется увидеть при исследовании её зависимостей. Так как на самом деле зависимости показывает не сама утилита ldd(1), а загрузчик, который эти зависимости ищет и загружает при нормальном запуске программы. ldd(1) всего лишь выставляет переменную окружения, при обнаружении которой ld-elf.so.1 выводит список зависимостей и завершает выполнение программы. Как написано в статьях по ранее приведённым ссылкам, в linux ldd вообще представляет собой shell скрипт.

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

Там же, в статье, есть пример сценария по которому, можно "уговорить" системного администратора запустить эту программу. И если он будет так неосторожен, что сделает это с правами суперпользователя, то...

Я не стал тестировать с uClibc, просто скопировал себе в домашний каталог исходники rtld. Слегка подправив Makefile и код rtld.c, (по аналогии с тем, как это сделано в статье) и взяв пример программы для теста, вот что я получил:

> ./test
Nothing.
> ldd ./test
./test:
All your box are belong to me.

Из этого я для себя сделал такой вывод: прежде чем что-то проверять утилитой ldd(1), сначала надо воспользоваться утилитой readelf(1) или objdump(1). Например так:

> readelf -l ./test

Elf file type is EXEC (Executable file)
Entry point 0x80483a0
There are 6 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4
  INTERP         0x0000f4 0x080480f4 0x080480f4 0x0001e 0x0001e R   0x1
      [Requesting program interpreter: /home/butcher/bin/ld-elf.so.1]
  LOAD           0x000000 0x08048000 0x08048000 0x005c3 0x005c3 R E 0x1000
  LOAD           0x0005c4 0x080495c4 0x080495c4 0x000f4 0x000fc RW  0x1000
  DYNAMIC        0x0005d4 0x080495d4 0x080495d4 0x000b0 0x000b0 RW  0x4
  NOTE           0x000114 0x08048114 0x08048114 0x00018 0x00018 R   0x4

> objdump -sj .interp ./test

./test:     file format elf32-i386-freebsd

Contents of section .interp:
 80480f4 2f686f6d 652f6275 74636865 722f6269  /home/butcher/bi
 8048104 6e2f6c64 2d656c66 2e736f2e 3100      n/ld-elf.so.1
Обращать внимение нужно на выделенные строки, если они отличаются от /libexec/ld-elf.so.1, то лучше не испытывать судьбу и не использовать ldd(1) с этой программой.
А так же вывод №2: никогда не запускать утилиту ldd(1) от суперпользователя.

суббота, 6 февраля 2010 г.

Начало.

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