понедельник, 19 сентября 2011 г.

Цветной ngrep (в продолжение предпоследнего поста)

ngrep - это что-то среднее между tcpdump и wireshark. Он может показаться не таким удобным как wireshark, зато превосходит по удобству использования tcpdump, поскольку основной особенностью ngrep является вывод на экран полезного содержимого пакетов (то, что обычно называют payload), а не всевозможных сетевых деталей. Кроме того, ngrep работает в терминале, а это, на мой взгляд, огромный плюс.

Итак, сначала картинка:


А теперь настройки .hlrc:
snippet ngrep   -191 '^\s*(?:POST|GET)\s+' '^[ITU]\s+' -211 '\[[A-Z]+\]' \
                -120 '^[A-Z]\S+: ' -130 '^#+' \
                -210 '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+' \
                -140 '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' -190 '^HTTP.*'
и .hl_functions:
function ngrep()
{
    sudo `env which ngrep` $@ | hl -sngrep
}
Чтобы этот пост не получился совсем уж скучным и банальным, ниже я покажу как предоставить права на запуск ngrep обычному пользователю (заметили sudo в функции ngrep?).

Итак, в обычной ситуации обычный пользователь не имеет права на запуск ngrep, поскольку может получить доступ к информации, для него не предназначенной. Но что если нам часто требуется получать доступ к содержимому сетевых пакетов (например, при тестировании какого-нибудь веб-приложения), или мы работаем на домашнем компьютере и доступ к информации о том, что происходит в нашей сетке не только не ослабляет безопасность системы, а наоборот повышает ее? В этом случае нужно добавить себя в список sudoers. Я не стану добавлять записи в файл /etc/sudoers, а создам новый файл (назовем его users) в директории /etc/sudoers.d/. Итак, от имени суперпользователя открываем файл /etc/sudoers.d/users и записываем в него следующие строки:
Cmnd_Alias  NETTASKS = /usr/sbin/ngrep, /usr/sbin/wireshark, /usr/sbin/tcpdump

user1      ALL = NOPASSWD: NETTASKS
Вместо user1 нужно поставить ваше настоящее регистрационное имя. После завершения редактирования файла необходимо изменить его атрибуты доступа:
chmod 0440 /etc/sudoers.d/users
Если этого не сделать, sudo откажется выполнять инструкции данного файла с соответствующей диагностикой. Ввиду данного изменения, при дальнейшем редактировании /etc/sudoers.d/users придется подтверждать внесение изменений при записи на диск (в vim это делается командой :w!), но лучше всего воспользоваться командой visudo -f /etc/sudoers.d/users, которая знает, как работать с файлами sudoers

Кроме права на запуск ngrep я наделил пользователя user1 правами на запуск wireshark и tcpdump. Теперь для запуска этих двух команд данный пользователь может ввести sudo wireshark и sudo tcpdump, а для запуска ngrep достаточно просто ввести ngrep, поскольку в интерактивной среде ngrep - это функция, записанная нами в файл .hl_functions, которая через sudo вызывает настоящий ngrep.

Заметим также, что ввод пароля пользователем при запуске этих команд не требуется, так как в файле /etc/sudoers.d/users перед задачами NETTASKS  для пользователя user1 указана опция NOPASSWD. Кроме того, слово ALL указывает на то, что пользователь user1 имеет право запускать команды с любого хоста. В целях безопасности, наверное имеет смысл заменить ALL на localhost, но в этом случае user1 не сможет запускать команды из NETTASKS удаленно.

Update. Не сразу заметил, что функция ngrep() в приведенном выше виде,  работает неверно для многих достаточно типичных случаев, в частности, когда паттерн для поиска пуст (например, в случае ngrep '' 'tcp port 80'). Проблема в том, что переменная $@ внутри ngrep() удаляет все кавычки, которые в случае пустого паттерна (или паттернов, содержащих пробелы) имеют значение. К счастью, если переменную $@ взять в двойные кавычки, то информация о пустых аргументах и аргументах, содержащих пробелы, не теряется, и ее можно восстановить. Я не знаю, существует ли в sh или bash штатный способ вернуть кавычки в этом случае на место, скорее всего нет, поэтому я написал отдельную функцию quote_args_if_needed() и поместил ее в начале файла .hl_functions.
function quote_args_if_needed()
{
    local result=''
    for i in "$@" ; do
        if [[ -z "$i" || "$i" =~ ' ' ]] ; then
            result=$result" '"$i"'"
            continue
        fi
        result=$result' '$i
    done
    echo $result
}
Функция проходит по переданным ей аргументам и добавляет их (через пробел) к изначально пустой переменной result. Если очередной аргумент пуст, либо содержит пробелы, то он обертывается одинарными кавычками. Мы будем передавать в quote_args_if_needed() значение "$@". Переменная $@, взятая в двойные кавычки представляет собой правильный массив переданных аргументов, в котором те аргументы, которые были заключены в кавычки, представляют единое целое (в отличие от бескавычечной версии, в которой все содержимое переданных аргументов разбивается по пробельным символам). Теперь нужно использовать новую функцию в ngrep():
function ngrep()
{
    eval sudo `env which ngrep` `quote_args_if_needed "$@"` | hl -sngrep
}
Как видите, мне пришлось обернуть все в eval. Это потому, что кавычки для аргументов, которые нам удалось вернуть с помощью quote_args_if_needed(), будут переданы без eval в `env which ngrep` дословно, и он будет ругаться на syntax error. Команда eval очистит нашу строку от кавычек, и при этом сохранит пустые аргументы и аргументы, содержащие пробелы.

суббота, 10 сентября 2011 г.

Переносы русских слов в texlive

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

Итак, после обновления texlive открываем файл /usr/share/texlive/texmf/tex/generic/config/language.dat (конкретный путь зависит от дистрибутива и репозитория) и добавляем в него строки
ruseng ruenhyph.tex
=russian
=english
сразу же за подобными определениями для english. После этого запускаем в терминале от имени root
fmtutil-sys --all
Теперь переносы должны работать.

Использование пользовательской подсветки команд в терминале (на примере make)

Сформулируем задачу следующим образом: мы хотим обеспечить собственные правила подсветки вывода произвольной команды в терминале. Для простоты будем считать, что наш терминал поддерживает 256 цветов (что уже давно справедливо и для vte, и для konsole, и для xterm) и мы используем bash. Задача абсолютно нетривиальная хотя бы потому, что нормального полноценного решения ее, насколько я знаю, пока не существует. Если бы вышеперечисленные терминалы поддерживали систему плагинов, то решить ее было бы, наверное, просто, но на данный момент это не так.

Стоит отметить, что существует целый ряд решений, которые могут помочь с расцвечиванием консоли для разных программ: это colordiff, GNU souce-highlight (который я часто использую для подсветки кода в этом блоге) и другие, а также использование различных цветовых опций общеизвестных команд, таких как ls, tree, grep и т.д., например в моем файле .bashrc определены алиасы
alias ls='ls -F --color=tty'
alias tree='tree -C'
Недостатки этих программ очевидны: собственно, они не решают поставленную нами задачу, так как предназначены для подсветки вывода заранее определенных программ по заранее определенным правилам.

Совсем недавно я прочитал про программу grc (generic colouriser). Точный ресурс сейчас точно не вспомню, но из свеженайденных - например здесь. Это уже что-то: в конфигах grc можно задать правила подсветки для вывода произвольной программы, основанные на регулярных выражениях. Я не стал устанавливать данную программу, поскольку имею собственное решение, очень похожее по смыслу и, надеюсь, не уступающее по возможностям. На нем и остановимся поподробнее.

Программа была написана на perl еще в 2007 или 2006 году. Она состоит из двух частей: модуля Term::Highlight, реализующего основную работу по поиску вхождений, соответствующих заданным регулярным выражениям, и вставке цветовых тегов на их границы, и скрипта hl, в котором определяются режимы работы программы и куда передаются аргументы, определяющие подсветку. В пакет программы входят справочные страницы man, поэтому разобраться в ней будет несложно. Скачать программу можно с sourceforge.net (здесь), или с cpan.org (здесь).

Изначально я планировал использовать Term::Highlight как фильтр для подсветки слов или регулярных выражений в длинном выводе программ, например так:
$ cat some_long_file | hl -100 some_word -101 another_word
Здесь осуществляется вывод на экран длинного файла some_long_file, при этом слова some_word и another_word будут подсвечены цветами, соответствующими значениям 100 и 101 в 256-цветной палитре терминала. Term::Highlight прекрасно справляется в ситуации, когда участки текста, соответствующие заданным словам или регулярным выражениям в hl оказываются вложены или пересекаются (причем многократно) друг с другом, например в случае
$ cat some_long_file | hl -100 some_word -101 'w\w+'
внутри подсвеченного цветом 100 слова some_word будет подсвечен цветом 101 участок, соответствующий выражению 'w\w+', т.е. слово word. Кроме этого, Term::Highlight поддерживает 8-цветные консоли, поиск в двоичных файлах, установку жирного текста, поиск без учета регистра и др. В дальнейшем я реализовал режим цветного grep и поддержку сниппетов.

Теперь подробнее о сниппетах. Очевидно, что для сложной подсветки, например синтаксиса какого-нибудь языка, нам понадобится передать в hl огромное число аргументов - это не удобно, а главное, система скорее всего не справится с такой длинной строкой и придется использовать xargs, а это очень неудобно. Поэтому в  версии 1.7 была добавлена возможность записывать именованные сниппеты в файле $HOME/.hlrc. Сниппеты содержат аргументы подсветки hl и их можно передавать по имени с помощью опции -s. Ниже мы напишем сниппет по имени make для подсветки вывода одноименной команды.

Буквально вчера я внес в Term::Highlight изменение, связанное с безопасным использованием hl в пайпах. Новая версия имеет номер 1.7.1. Отныне hl знает, куда направлен ее вывод, и если это не stdout, то цветовые теги вставляться не будут. Это поможет использовать hl для подсветки вывода произвольных программ и не беспокоиться в том случае, если вывод подсвечиваемой программы направлен через пайп на вход другой программы. Напомню, что точно так же работает ls --color=tty: если ее  вывод перенаправить в файл или на вход другой программы, или раздвоить с помощью tee , то цветовые теги исчезнут.

Итак, теперь нужно сделать так, чтобы при наборе make в командной строке терминала вместо реальной программы make подставлялось нечто, фильтрующее ее вывод с помощью hl. Первое, что приходит на ум - использовать алиас - не годится. Алиасы bash очень примитивны и мы не сможем с их помощью передать все возможные аргументы make, а затем через пайп установить фильтр hl. Поэтому будем использовать функцию с именем make, которая будет подменять настоящий make в интерактивной среде.

Я предлагаю сделать так: в файле $HOME/.bash_profile установить переменную HL_ALIASES, которая будет указывать на путь к файлу, содержащему нашу функцию. Пусть этот файл называется .hl_functions. Тогда в .bash_profile записываем строку
export HL_ALIASES=$HOME/.hl_functions
Затем в $HOME/.bashrc считываем содержимое этого файла:
[ -n "$HL_ALIASES" ] && [ -f "$HL_ALIASES" ] && . $HL_ALIASES
В файл .hl_functions поместим функцию make (в дальнейшем туда же можно поместить функции, определяющие подсветку вывода других программ):
function make()
{
    `env which make` $@ 2>&1 | hl -smake
}
Все очень просто: передаем stdout и stderr настоящего make (`env which make`) со всеми аргументами на вход команды hl, которая преобразует его на основании сниппета make (hl -smake). Такой подход безопасен в случае использования вывода подсвечиваемой программы для передачи на вход другой: hl не будет подставлять цветовые теги в этом случае. Более того, поскольку функция make() определена в .bashrc, то скриптам она будет неизвестна, поскольку объекты .bashrc определены для интерактивного использования. Соответственно можно совершенно не беспокоиться, если подсвечиваемая команда будет использоваться в каком-нибудь скрипте: в этом случае всегда будет вызываться настоящая программа. (Если вы действительно хотите использовать функции подсветки в скриптах, чего я делать крайне не рекомендую, то сорсить .hl_functions следует не в .bashrc, а в .bash_profile, а затем экспортировать определенные функции с помощью export -f).

Теперь приведем определение сниппета make, которое следует поместить в $HOME/.hlrc:
snippet make    -b -215 '^\s*gcc\b' '^\s*g\+\+(?=\s)' '^\s*libtool:\s*\w+:' \
                '^s*\/bin\/sh\s+[\w/.]+' -rb -108 '[\w/.-]+\.c\b(?!:)' \
                -119 '[\w/.-]+\.(cc|cpp|cxx|c\+\+)\b(?!:)' \
                -30 '\s*[\w/.*-]+\.o\b' -203 '(?<=\s-o)\s*[^-][\w/.-]*' \
                -42 '^\s*make\[\d+\]' -64 '(?:^|\s+)\-l[\w/+-]+' \
                -50 '^[\w/.-]+\.\w+:(?:\d+:\d+:)?' \
                -204 '^\s*(?:rm|mv|ln|cp)\s+(?:-\w+)?' \
                -196 '(?:О|о)шибка( \d+)?' -202 '(?:П|п)редупреждение( \d+)?' \
                -120 'Выход из.*$' 'Вход в.*$'
С помощью числовых тегов здесь определены цвета для следующих за ними регулярных выражений, на которых я останавливаться не буду - безусловно, для того чтобы эффективно использовать этот подход, вам самим необходимо знать, как составляются регулярные выражения perl. Тег (или опция) -b устанавливает, что следующие за ним слова и выражения нужно печатать жирным шрифтом, тег -rb отменяет жирное начертание. Подробнее о различных опциях hl можно прочитать на странице справки man hl. В приведенных выше правилах сниппета make задаются цвета подсветки команд gcc, g++, libtool и др., исходников C и C++, объектных файлов с суффиксом .o, предупреждений и ошибок компилятора (на русском языке), сообщений о входе и выходе make в директории, подключаемых с помощью опции -l библиотек, названий файлов, получаемых на выходе, которые задаются с помощью опции компилятора -o и других артефактов. Самое замечательное, что данные опции можно изменять и добавлять и они сразу становятся эффективным без ре-сорсинга файла .hl_functions.

А теперь главное - картинка. Это пример работы функции make при компиляции исходников sane-backends (картинка кликабельна):