Однострочник месяца на Perl: Дело о злобных спамботах
Автор: Ben Okopnik
Перевод: Павел Соколов


ПРИМЕЧАНИЕ РЕПОРТЁРА

Чтобы предвосхитить жалобы, которые обязательно появятся, я хотел бы подчеркнуть необходимость установки последней версии Perl (по крайней мере 5.8.0, на дату этого документа), чтобы иметь возможность играться со скриптами, показанными в этих статьях. Однострочники в гораздо большей степени, чем нормальные (человеческие ;) - Прим. пер.) скрипты, основываются на новых и необычных особенностях языка, а языки имеют склонность "отращивать" новые способности и отбрасывать старые с ростом номера версии. Perl, на 17-ом году роста и развития, тоже не исключение.

Одна из многочисленных проблем с однострочниками - их хрупкость, особенно тех (а их большинство), которые зависят от криптоконтекста, побочных эффектов и недокументированных особенностей, которые вероятно (а скорее всего обязательно) изменятся без предварительного предупреждения. Однострочники - программы, которые демонстрируют некий хитрый ход или особенность, которые предусматривают использование всего вышеперечисленного. Помните - это забавные игрушки, которые (надеюсь) приведут к лучшему пониманию Perl. Попытки их использования в нормальном, устойчивом коде будет серьёзной ошибкой. Если вы не понимаете основ Perl, это не то место, где стоит начинать.

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

 -- Брайан Керниган (Brian W. Kernighan)


Caveat Lector (Да остережётся читатель).

Ben Okopnik
На борту парохода "Улисс", Сент-Августин, Флорида


Фринк Ублик заснул прямо на клавиатуре. Перед этим он то игрался, то пытался разобраться в игре "угадай число", которую написал Вумерт. Первое получилось легко, второе не получилось совсем. В действительности, его последняя неоконченная партия ещё виднелась на экране:


perl -wlne'BEGIN{$b=rand$=}$a=qw/Up exit Down/[($_<=>int$b)+1];print eval$a'
50
Down
25
Up
37
Up
44
Up

В чём был секрет? Как она работала? [1] Сны Фринка были полны плавающих кусков кода, которые спирально уходили вдаль или мутировали в монстрообразные образы, угрожающие поглотить весь мир. Поэтому рука, потрясшая его за плечо, принесла ему долгожданное облегчение. Вумерт нетерпеливо стоял поблизости.

- Просыпайся, Фринк, просыпайся! Лентяй, дела горят; пошли!

- А...Ммм...Я, аххм, проснулся. Что стряслось?

- В гостиную. Давай, давай, нельзя терять ни минуты!

Первый взгляд на их посетителя заставил Фринка остолбенеть. Привыкнув работать с рабочим людом - сисадминами, техниками и т.п. - он ожидал обычный типаж "неряшливый, но компетентный", возможно завершённый туристическими ботинками; однако он увидел парня в костюме в узкую полоску, хрустящей белой сорочке, красном "волевом" галстуке и чёрных лакированных туфлях. Он нетерпеливо расшагивал по сторонам и значительно просветлел при виде Фринка.

- А, это, должно быть, второй член команды в вашей организационной иерархии! Великолепно. Теперь мы можем перейти к актуализации тех волевых стратегий, которые реорганизуют этот, м-м-м, непредвиденный случай в дополнительное увеличение строки доходов отчёта о прибылях и убытках. Итак, вот как мы это представим: обеспечение безопасности вычислительных ресурсов основывается на использовании вовлечения...

Стараясь держать их посетителя в пределах зрения, Фринк сдавленно прошипел Вумерту: "Что он говорит? И на каком языке?"

- Это маркетоидный. [2] Тебе надо изучить хотя бы его основы; нет, конечно люди, подписывающие чеки, на нём не говорят - у них нет времени на подобные упражнения, но тебе придётся столкнуться с ним в бизнес-мире. Так что лучше подготовиться. Хотя обычно большинство этих людей всё ещё могут говорить на английском; посмотрим, помнит ли этот парень, как это делается. М-р Виббли!

Их посетитель только что закончил то, что, очевидно, считал объяснением проблемы, выключил переносной LCD проектор, отложил свою лазерную указку, и теперь ожидающе смотрел на них. Определённо, он был знаком с репутацией Вумерта и полагался на Реалистичного Компьютерного Детектива, спсобного разобраться с... ну, с тем, что бы это ни было.

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

Посетитель вздохнул и упал в ближайшее кресло.

- Да, конечно. Вы знаете, они собирались послать одного из системных администраторов, чтобы поговорить с вами, но я, конечно, настоял на личном проведении презентации, так как я слышал о проблеме. Кроме того, ни у кого из них даже не появилась бы мысль использовать для слайдов текстурированный оранжево-розовый фон, а ведь это сегодня самое модное сочетание! Так или иначе, я действительно получил от него записку, объясняющую проблему его собственными словами. Она грубая и неутончённая, там вообще не используются маркетинговые техники, но, мне кажется, вы её поймёте...

Мятая салфетка, вся в пятнах от кофе, большая часть которой была покрыта расчётами, напоминаниями и чем-то, сходным с правилами для брандмауэра, содержала короткую записку, обведённую красным маркером:

Вумерт, спамботы собирают адреса e-mail с нашего сайта (мы применили к ним "плюс хак", [3], так что мы знаем, откуда информация); количество получаемого нами спама растёт не по дням, а по часам. Нам надо иметь адреса на странице - это наша контактная информация для жалоб о проблемах с сайтом и т.п. Но нам надо как-то остановить спамботов! Я уже написал CGI, чтобы обрабатывать линки, но нам надо оставить правильные адреса на страницах, а боты их оттуда вытаскивают. Идеи? Страница расположена на http://xxxxxxxxxxxx.xxx. Я создал для вас учётную запись, просто идите по адресу ssh://xxxxxxxxx.xxx/xxx, пароль "xxxxxxxxxx". Спасибо!
- Инт Мейн

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

- Что ты собираешься делать, Вумерт? Есть идеи?

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

...

Снова Вумерт и Фринк оказались в окружении знакомых видов и звуков работающего вебсайта. Они могли видеть, как веб-сервер с лёгкостью отпочковывал треды, при этом совершенно не загружая процессор. Определённо, местный сисадмин установил mod_perl [4]. Тут и там проносились потоки данных, в целом всё работало, как хорошо смазанная и отлаженная машина.

Неожиданная тень заставила Фринка посмотреть вверх. - Что за...- Прежде, чем он смог закончить, ужасающее создание, состоящее только из щупалец, линз и недобрых намерений [5], выскочило на платформу, единым махом присосалось к копиям всех HTML файлов и растворилось в нигде в мгновение ока.

- Что это было, Вумерт? Спамбот?"

- Ага. Эти штуки ходят по всей Сети, собирая адреса e-mail и сообщая их своим грязным хозяевам - спамерам. Исходя из природы Сети, ты не сможешь их остановить, но ты можешь сделать их гораздо менее эффективными. Спамеры тупы, их боты - ещё тупее, на это мы и будем полагаться. Напомню, что что бы мы ни делали, это будет лишь временным решением. Со временем, спамеры (или нанятые ими техники) разберутся с этим конкретным методом, но к тому моменту мы реализуем другие решения.

Подойдя к удобному терминалу, Вумерт натянул любимые перчатки для печати и выдал быструю очередь.


perl -MRFC::RFC822::Address=valid -wne'/[\w-]+@[\w.-]+/||next;print valid$&' *html

Строка из "1" появилась на экране, Вумерт улыбнулся и его пальцы опять заскользили по клавиатуре.


perl -i -wlpe's=[\w-]+@[\w.-]+=join"",map{sprintf"&#%s;",ord}split//,$&=e' *html

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


perl -we'map{printf"&#%s;",ord}split//,pop' user@host.com

- Вот и всё, Фринк, наша работа здесь завершена. Дом, милый дом, мы уже идём!

...

Тихонько попыхивал старомодный самовар на угле [6]. Заварка [7] из великолепного грузинского чая давала изумительный запах. Поблизости стояла тарелка с канапе, чьё разнообразие простиралось от лучшего российского масла и черничного варенья на свежеиспечённом пухлом белом хлебе до белужьей икры на слегка натёртом чесноком ржаном хлебе грубого помола. Вумерт и Фринк просто паслись на этом поле для гурманов. Наконец, они откинулись, наполненные хорошей едой, и теперь любопытство Фринка больше уже ничем не сдерживалось.

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

Развалившись в своём любимом кресле, Вумерт улыбнулся.

- Напротив, почему бы тебе не рассказать, какую часть ты понял? Я хотел бы знать, насколько ты продвинулся, Фринк. Это было бы для меня удовольствием, увидеть, что ты подхватываешь разные тонкие моменты. А затем я продолжу.

- Ну, ладно... Начнём с первого:


perl -MRFC::RFC822::Address=valid -wne'/[\w-]+@[\w.-]+/||next;print valid$&' *html

Я узнал все ключи командной строки:

-Mmodule Использовать указанный модуль "module"
-w Включить предупреждения
-n Непечатающий цикл
-e Выполнить следующие команды

Однако, я не смог разобраться с таким "-MRFC::RFC822::Address=valid" синтаксисом - что это было?

(Чтобы найти нужный вам модуль, отправляйтесь на http://www.cpan.org и оттуда по ссылкам в раздел "Модули Perl". В разделе "Mail", http://www.cpan.org/modules/by-module/Mail/, вы найдёте файл Mail-RFC822-Address-0.3.tar.gz Его содержимое вам нужно скопировать, например, в /usr/local/lib/perl5/site_perl/5.8.0/Mail/RFC822 (/usr/local/lib/perl5/site_perl/5.8.0/ - это один из путей, по которым Perl версии 5.8.0 ищет модули). Единственное, что нужно, так это исправить имя модуля Perl, загружаемого ключом "-M". В результате однострочник принимает следующий вид:

perl -MMail::RFC822::Address=valid -wne'/[\w-]+@[\w.-]+/||next;print valid$&' *html
Прим.ред)

- А, как нам говорит "perldoc perlvar" в статье про "-M", это "синтаксическая конфетка" [8]. "-MBar=foo" это краткий вариант для "use Bar qw/foo/", которая импортирует указанную функцию "foo" из модуля "Bar". Продолжай, у тебя неплохо получается.

Фринк прочистил горло.

- В этом случае, думаю, я разобрался...почти. Дай-ка я взгляну на "perldoc perlvar" и "perldoc RFC::RFC822::Address"... Да, как я и думал - теперь я разобрался! Регулярное выражение в начале -

/[\w-]+@[\w.-]+/

пытается подобрать адреса e-mail - оно не совершенно, но должно более-менее справиться. Оно говорит "подбери любой символ из [a-zA-Z0-9-], повторяющийся один или более раз, за которым следует "@", за которым следует любой символ из [a-zA-Z0-9.-], повторяющийся один или более раз". Если совпадение не найдено, сработает оператор "||" - логическое или, и программа перейдёт к следующей строке.

- Великолепно, Фринк! Что происходит дальше?

- Если совпадение найдено, "next" пропускается и вызывается "print valid$&". Документация к модулю сказала мне, что функция "valid" тестирует адрес e-mail на соответствие RFC822 (спецификация e-mail), а затем возвращает истину или ложь в зависимости от правильности. "$&", по словам "perldoc perlvar", это последнее совпадение с шаблоном - другими словами, то, что подошло под регулярное выражение. Так как ты увидел только "1" и никаких ошибок, то есть совпадений, которые не соответствовали бы RFC822 и которые вернули бы что-то вроде "Use of uninitialized value in print at -e line 1 (Использование неинициализированного значения в инструкции print в строке 1 ключа -e)" - все совпадения были правильными. Ты просто проверял, что твоё регулярное выражение находит только фактические адреса. Ну как?

- Замечательно, мой дорогой Фринк, ты хорошо продвигаешься. Как дополнение, лучше избегать использования $&, $`, и $', а также "use English" в скриптах. Их использование значительно понижает производительность программы (см. "perldoc perlvar"). Однако, здесь у нас был совсем маленький список совпадений, так что я оставил всё как есть. Продолжай, посмотрим, как у тебя получится со следующим.

- М-м-м...следующий, конечно. Хорошо, я понял его часть:


perl -i -wpe's=[\w-]+@[\w.-]+=join"",map{sprintf"&#%s;",ord}split//,$&=e' *html

-i Редактирование на месте (изменить указанный файл [s])
-w Включить предупреждения
-p Печатающий цикл
-e Выполнить следующие команды

Хмм... Я тут немного потерялся, Вумерт. Я вижу то регулярное выражение, которым ты воспользовался ранее, но что за кусок "s="?

- Это одна из тех удобных штучек, которые предоставляет Perl - хотя надо признать, что основная идея была украдена у "sed". Это просто другой разделитель для оператора "s" (substitute - заменить). Иногда случается, что использование стандартного разделителя ("/") неудобно и ведёт к "аду зубочисток", например, когда подбираешь шаблон для имени директории:

s/\/path\/to\/my\/directory/my home directory/

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

s#/path/to/my/directory#my home directory#

Поскольку этот символ не цифра, не буква и не пробел, он сработает. Есть, конечно, специальные случаи, но они все разумны. Использование одиночной кавычки отключает обработку символов шаблона и замены (правила см. в "perldoc perlop", а использование фигурных, квадратных и обычных скобок требует довольно очевидного синтаксиса:

s{a}{b}
s(a)(b)
s[a][b]

Многие любят использовать "#" в качестве разделителя; я предпочитаю "=", так как "#" часто появляется в HTML и комментариях. С остальным сможешь разобраться?

- Боюсь, нет. Ты ищешь адреса e-mail как и раньше и заменяешь их чем-то, но не пойму чем.

- Хорошо, это действительно сложно. Та часть выражения, на которую заменяют, это нормальный код Perl. Мы можем это сделать благодаря модификатору "e" (evaluate, оценить, выполнить) на конце оператора "s". давай разберём код справа налево:

join"",map{sprintf"&#%s;",ord}split//,$&

Мы знаем, что "$&" содержит адрес e-mail; следующее, что мы делаем, это используем функцию "split", которая конвертирует скаляр в список, разбивая его по содержащемуся между разделителями символу. Однако в этом случае разделитель пуст -- поэтому возвращаемый список содержит символы адреса в качестве элементов. Теперь мы передаём этот список в функцию "map", которая выполнит код, приведённый в {блоке}, для каждого элемента списка, а затем вернёт результат - в виде другого списка.

Внутри самого блока каждый символ используется в качестве аргумента функции "ord", которая возвращает ASCII-код этого символа, что в свою очередь становится аргументом функции "sprintf", возвращающей строчки, отформатированные подобным образом:

&#<ASCII_код>;

для каждого ASCII-кода. После того, как все символы в списке были обработаны, мы используем функцию "join", чтобы преобразовать список обратно в скаляр, который будет использован оператором замены в качестве подстановки вместо исходного адреса e-mail. (Вот такой вот паровоз функций ;-) Перев. ) Что когда-то было "foo@bar.com", теперь выглядит вот так:

&#102;&#111;&#111;&#64;&#98;&#97;&#114;&#46; &#99;&#111;&#109;

А это, ты должен признать, совершенно не похоже на адрес e-mail, так что спамботы не смогут его прочитать!

Фринк выглядел озадаченным.

- Вумерт, не хотел бы тебе говорить...но люди не смогут это прочитать тоже!

Вумерт ещё глотнул чая и улыбнулся.

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

&#<ASCII_код>;

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


<html><head><title></title></head><body>
&#87;&#111;&#111;&#109;&#101;&#114;&#116;&#32;&#70;&#111;&#111;&#110;&#108;&#121;
</body></html>

Понимаешь, что я имею в виду?

Несколько мгновений спустя Фринк оторвался от клавиатуры.

- Вумерт, что за замечательное решение! Твой клиент сможет показывать адреса без того, чтобы их собирали, а страничка выглядит так же, как и раньше. По аналогии, я могу сказать, что последний однострочник:


perl -we'map{printf"&#%s;",ord}split//,pop' user@host.com

просто позволит сисадмину преобразовывать новые адреса перед их вставкой в HTML. Великолепно!

- Большей частью полного решения, конечно, был CGI, который написал администратор, а он больше, чем один однострочник, хотя и не сильно, особенно принимая во внимание мощь модуля CGI. Запомни, Фринк: по мере роста твоей мощи, убедись, что ты примкнул к стороне Добра, а не Зла. Это не просто правильно; больше вероятность, что у окружающих тебя людей будут мозги!



[1] Довольно странно, но мой таинственный корреспондент не привёл решения, возможно считая его достаточно простым(!) для публики, чтобы разобраться, или (что я считаю наиболее вероятным сценарием) он ещё не разобрался сам. Читателям предлагается написать свои собственные идеи... но на данный момент, внутренний механизм игры Вумерта остаётся загадкой.

[2] Тарабарщина -- это письменная форма Маркетоидного. Ранее на нём разговаривали тарабары, которые вымерли из-за своей полной неспособности что-либо делать (в противоположность разговорам об этом). Он настолько же понятен, как и его разговорный вариант, хотя многие их смешивают: "это же маректоидная тарабарщина" -- очень тавтологичное заявление.

[3] Некоторые обычно используемые почтовые клиенты проигнорируют всё, что следует за знаком плюса в имени пользователя, например, <smith+yahoo@joe.com> будет отправлен абсолютно туда же, как и <smith@joe.com>. Это может быть очень удобным механизмом нахождения источника спама: "приплюснутый" (plus-hacked. Хороший термин, не так ли? Прим. перев.)адрес, по которому приходит слишком много спама, может быть направлен в "/dev/null" и заменён новым (скажем, <smith+yahoo1@joe.com> - по нему почта тоже будет направлена на <smith@joe.com>.)

[4] A.K.A. "Апач на стероидах". Из документации к mod_perl:

Проект по интеграции Apache/Perl соединяет полную мощь языка
программирования Perl и HTTP сервера Apache. Это достигается
связыванием библиотеки времени выполнения Perl с сервером и
предоставлением объектно-ориентированного интерфейса Perl к C
API сервера.

Эти части бесшовно склеиваются плагином сервера "mod_perl",
позволяя писать модули Apache полностью на Perl. Вдобавок,
встроенный в сервер интерпретатор обнуляет затраты на запуск
внешней программы интерпретатора, а также снижает затраты
времени на компиляцию и исполнение Perl-скриптов.

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

[5] Если вы видели "Матрицу", просто вспомните Охотников. Ежели не видели, вам винить нужно только себя :)

[6] См. "Russian Tea HOWTO", DАniel Nagy, в котором описывается правильный способ приготовления и сервировки русского чая. Этот человек знает, о чём говорит.

[7]В английском варианте - samovar и zavarka. Откуда? См. информацию об авторе. Прим. перев.

[8] syntactic sugar (англ. жарг). - Различные надстройки над "базовым" синтаксисом языка, приближающие его к естественному и облегчающие написание кода. Но не отладку =) Прим. перев.

( - )

Бен -- сотрудничающий редактор Linux Gazette и член Банды ответчиков (в смысле, они отвечают на возникающие вопросы читателей. Прим. перев.). (Гм... Интересно, а девиз у этой банды, наверное, "за базар отвечу"? ;-) -- Прим.ред.)

Бен родился в Москве в 1962 г. В шесть лет заинтересовался электричеством -- продемонcтрировав это, воткнув вилку в розетку и вызвав пожар. С тех пор неоднократно проваливался в технологические люки. Он начал работать с компьютерами ещё в старые, добрые времена, когда их приходилось собирать из деталей и припаивать на печатные платы, а программы должны были умещаться в 4k памяти. Он с радостью заплатил бы внушительную сумму любому психологу, способному излечить его от вызванных этим кошмаров.

Последующий опыт Бена включает создание программ практически на дюжине языков, поддержку сетей и баз данных во время приближающегося урагана, а также написание статей для публикаций в разных местах: от журналов по парусному спорту до техножурналов. Завершив недавно семилетний круиз по Атлантике/Карибскому морю под парусом, он на данный момент пришвартовался в Балтиморе, где работает техническим инструктором в Sun Microsystems.

Бен работает с Linux с 1997, и считает, что именно из-за него у Бена полностью пропал интерес к развязыванию атомной войны в разных частях северо-запада Тихого океана.


Copyright (c) 2003, Ben Okopnik. Copying license http://www.linuxgazette.com/copying.h tml
Published in Issue 86 of Linux Gazette, January2003


Вернуться на главную страницу