Perl для системного администрирования

       

Отслеживание спама Теперь когда


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

Можно использовать такой черный список, чтобы разобраться, проходило ли сообщение через узлы, известные своим спамерством. Ясно, что в черном списке нет сервера, передавшего нам почту (иначе с ним просто не было бы установлено соединение), но любой из остальных почтовых серверов, указанный в заголовках Received:, вполне может там быть.

Не существует способа написать одну программу, проверяющую все возможные черные списки агентов передачи почты, поскольку разные агенты хранят эту информацию в различных форматах. Большая часть узлов в Интернете в настоящее время применяет в качестве агента передачи почты sendmail, так что в нашем примере будет применяться его формат черного списка. В новых версиях sendmail черный спи- сок хранится в базе данных при помощи библиотек Berkeley DB 2.X, доступных на http://www.sleepycat.com.

Поль Маркес (Paul Marquess) написал модуль BerkeleyDB, специально предназначенный для работы с библиотеками Berkeley 2.x/3.x. Это может сбить с толку, поскольку в документации по DB_File, еще одному известному модулю Маркеса, входящему в состав дистрибутива Perl, также рекомендуется применять библиотеки 2.х/З.х. DB_File использует библиотеки Berkeley DB 2.х/З.х в «режиме совместимости» (в частности, библиотека собирается с ключом --enable-compat185, так что доступен API версии 1.x API). Модуль BerkeleyDB позволяет программисту на Perl применять расширенные возможности из API версии 2.х/З.х.

Агент передачи sendmail использует формат BerkeleyDB 2.х/З.х, так что нужно включить модуль BerkeleyDB. Вот пример, который выводит содержимое локального черного списка:

Sblacklist = "/etc/mail/blacklist.db"; use BerkeleyDB;

Ясвяжем хэш %blist с черным списком, используя Berkeley DB

# для получения значений

tie %blist, 'BerkeleyDB::Hash', -Filename => Sblacklist or die

"Невозможно открыть файл $filenane: $! SBerkeleyDB::Error\n" ;

# обходим в цикле каждый ключ и значение из этого файла, и

выводя только записи REJECT while(($key,$value) = each %blist){

в списке также могут быть записи "OK", "RELAY" и др. next

if ($value ne "REJECT");



print "$key\n": }

Принимая за основу этот код, можно написать подпрограмму, проверяющую, находится ли данный узел или домен (содержащий этот узел) в черном списке. Если нужно узнать об узле mallserver.spam-mer.com, следует обойти в цикле все записи из черного списка (в котором могут находиться mailserver.spammer.com, spammer.com или даже просто spammer), чтобы проверить, содержатся ли в имени узла какие-либо записи из него.

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

вообразите себе внешний цикл, в котором этот код вызывается

множество раз

foreach Smatch (qw(alewife davis porter harvard central кепааН park))

{

Sstation =" /Smatch/ and print "found our station stop'": }

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

Вот пример первой технологии, созданной для решения указанной проблемы:

use BerkeleyDB;

Sblacklist = "/etc/mail/blacklist.db";

&loadblist;

и принимаем имя узла в качестве аргумента командной строки и

сообщаем, если оно есть в черном списке

if (defined &checkblist($ARGV[0])){

print "*** $found найден в черном списке \п"; }

И

загружаем черный список в массив анонимных подпрограмм sub loadblist{

tie %blist, 'BerkeleyDB::Hash', -Filename => Sblacklist or die

"Невозможно открыть Sfilename:

$! $BerkeleyDB::ErrorXn" ;

while(my($key,$value) = each %blist){

# в черном списке могут быть "OK", "RELAY" и пр. next if ($value ne "REJECT");

push(@blisttests, eval 'sub {$_[0] =~ \Q$key/o and $key}'); } }

sub checkblist{

my($line) = shift:

foreach Ssubref (@blisttests){

return Sfound if (Sfound = &$subref($line)); }

return undef: } В этом примере используются анонимные подпрограммы - технология, продемонстрированная в книге Джозефа Хола (Joseph Hall) «Effective Perl Programming» (Эффективное программирование на Perl) (Addison Wesley). Для каждой записи из черного списка создается анонимная подпрограмма. Каждая подпрограмма сверяет переданные ей данные с одним из элементов черного списка. Если они совпадают, такая запись возвращается. Ссылки на эти подпрограммы хранятся в списке. Вот строка, в которой создается подпрограмма и ссылка на нее добавляется к списку:

push(@blisttests, eval 'sub <$_[0] =" /\0$key/o and $key}');

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

sub {

$_[0] =" /\Qspammer/o and "spammer"; }

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

Позже в программе будет обойден в цикле список ссылок на код и выполнена каждая маленькая подпрограмма для переданных данных. Если результатом каких-либо из этих вычислений окажется значение «истина», мы вернем код возврата подпрограммы:

return $found if (Sfound = &$subref($line));

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

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

sub loadblist{

tie %blist, 'BerkeleyDB::Hash', -Filename => $blacklist or die

"Невозможно открыть файл $Шегше:

$BerkeleyDB: :Error\n" ;

while(my($key,$vaiue) = eac^- %blist){

# в черном списке могут бьть запис/ "OK". "RELAY" и пр. next

(Svalue ne "PEJECT") push('SDlisttests, [qr/\Q$i<ey/. $кеу]): }

sub checkblisu

my($iine) = shift;

foreach my Stest (§blisttests){

my($re,$kr;v) = a{$test}

return $key i* ($line =" /$re/): }

return undef; }

На этот раз ссылка была перенесена на анонимный массив в@blist test. Первый элемент этого массива - скомпилированное регулярное выражение, созданное с применением нового синтаксиса qr/Y. Это позволяет сохранить регулярное выражение после его компиляции. Такая форма обработки значительно увеличивает скорость выполнения программы при дальнейшем поиске соответствия. Второй элемент анонимного массива - сама запись из черного списка, которая будет возвращена при найденном соответствии скомпилированному регулярному выражению.



Содержание раздела