Получение почты
Обсуждая в этом разделе получение почты, мы не будем говорить о ее сборе (fetching). Передача почты с одной машины на другую не представляет особого интереса. Модули Mail: :POP3Client Сина Дауда (Sean Dowd) и Mail: :Cclient Малколма Битти (Malcolm Beattie) легко могут передать почту по протоколам POP (Post Office Protocol) или ШАР (Internet Message Access Protocol). Гораздо интереснее посмотреть, что с этой почтой делать после ее получения, и именно на этом мы и остановимся.
Начать следует с основ, поэтому рассмотрим инструменты, позволяющие разбить как отдельные сообщения, так и почтовые ящики. Чтобы рассмотреть первое, вновь обратимся к пакету MailTools Грэма Бара (Graham Barr), на этот раз прибегнем к модулям Mail : :Interne: и Mail : :Header.
Разбиение отдельных сообщений
Модули Mail: :Internet и Mail: :Header предлагают удобный способ разбить заголовки почтового сообщения, соответствующего RFC822. RFC822 определяет формат почтового сообщения, включая имена допустимых заголовков и их форматов.
Модулю Mail: : Internet необходимо передать либо файловый дескриптор файла с сообщением, либо ссылку на массив, содержащий его строки:
use Hail::Internet: $messagefile = "mail";
open(MESSAGE,"Smessagefile") or die "Невозможно открыть $messagefile:$!\n";
Smessage = new Mail::Internet VMESSAGE;
close(MESSAGE);
Если мы хотим анализировать сообщение, поступающее в стандартный поток ввода (т. е. переданное через конвейер на стандартный ввод), можно поступить так:
use Mail::Internet;
Smessage = new Mail: :Internet VSTDIN;
Mail: : Internet возвращает экземпляр объекта сообщения. Чаще всего с этим экземпляром объекта будет применяться один из двух методов: body() и head(). Метод body() возвращает ссылку на анонимный массив, содержащий строки тела сообщения. head() более интересен и предлагает плавное продолжение модуля Mail: : Header.
При загрузке Mail::Internet неявно загружается Mail::Header. Если вызвать метод head() модуля Mail: :Internet, он вернет экземпляр объекта заголовка Mail: :Header. Это будет тот же экземпляр объекта, который можно получить, если использовать не Mail: :Internet, а напрямую Mail: :Header:
use Mail::Header; Smessagefile = "mail";
open(MESSAGE,"Smessagefile") or die "Невозможно открыть $messagefile:$!\n"
$header = new Mail:: Header VMESSAGE;
close(MESSAGE):
Объект $header содержит заголовки сообщения и предлагает несколько удобных способов для получения данных. Например, чтобы вывести
отсортированный список встречающихся имен заголовков (которые в модуле называются «тегами»), можно добавить такую строчку в конец предыдущего примера:
print join("\n",sort $header->tags);
В зависимости от сообщения можно увидеть что-то подобное нижеследующему:
Сс
Date
From
Message-Id
Organization
Received
Reply-To
Sender
Subject
To
Необходимо получить все заголовки Received: из сообщения. Вот как это можно сделать:
received = $header->get("Received");
Часто методы Mail: :Header используются вместе с объектом Mail: : Internet. Если применять Mail: : Internet для возвращения объекта, содержащего и тело и заголовки сообщения, можно сцепить вместе некоторые методы из этих модулей:
©received = $message->head->get("Received"):
Обратите внимание, что get()вызывается в списочном контексте. В скалярном контексте метод вернул бы только первое вхождение этого тега, в случае, если бы мы не задали вторым аргументом порядковый номер тега. Например, get("Received", 2) вернет вторую строку Receivec: из сообщения. Модуль Mail: : Header предоставляет и другие методы для удаления и добавления тегов в заголовки; подробную информацию можно найти в документации.
Разбиение почтового ящика
Перейти на следующий уровень, где мы разобьем на части почтовые ящики, довольно просто. Если почта хранится в формате «классического mbox» или qmail (еще один агент передачи почты, подобный sendma.il), можно использовать модуль Mail : : Folder Кевина Джонсона (Kevin Johnson). Многие из распространенных почтовых агентов (не в Unix), таких как Eudora, тоже хранят почту в классическом формате Unix mbox, так что этот модуль может быть полезным на многих платформах.
Что-то подобное мы уже видели:
use Mail': Folder: :Mbox: для классическое фермата Unix mbox
Sfolder = new Mail::Folder('mbox',"filename1 )
Конструктор new() принимает тип формата почтового ящика и имя файла для анализа. Он возвращает экземпляр объекта folder, через который можно запрашивать, добавлять, удалять и изменять сообщения. Чтобы получить шестое сообщение, нужно применить следующее:
Smessage = $folder->get_message(6);
Теперь Smessage содержит экземпляр объекта Mail: : Internet. С этим экземпляром объекта можно применять все только что обсужденные методы. Если вам нужен только заголовок сообщения, можно использовать:
$header = $folder->get_header(6);
Здесь нет никаких сюрпризов; возвращается ссылка на экземпляр объекта Mail: .'Header. Чтобы узнать о других доступных методах, загляните в документацию по Mail:: Folder.
Отслеживание спама
Теперь, когда мы знаем, как можно разбить на части сообщение, посмотрим, что делать с этим умением на практике. Первое, что можно рассмотреть, - это непрошеная коммерческая электронная почта, или спам. Большинству пользователей не нравится получать такие сообщения. Системные администраторы не любят спам за то, что он попусту забивает каталоги почтовых очередей и журналы регистрации. Кроме того, каждое непрошеное коммерческое письмо вызывает недовольство пользователей, так что в почтовом ящике системного администратора на каждое письмо «из спама» зачастую приходится более десятка возмущенных писем от пользователей.
Лучший способ сдерживать подобный напор- создать такую «атмосферу», в которой спам недопустим и, помимо этого, его очень сложно посылать. Жалобы интернет-провайдеру спамера (у большинства из которых определены строгие правила на этот счет) часто приводят к тому, что виновнику отказывают в услугах. Если это происходит вновь и вновь, то со временем спамеру все сложнее найти провайдера. А чем сложнее спамеру продолжать свое дело, тем меньше вероятность, что он в нем останется.
Жалобы провайдерам затруднены по следующим причинам:
С помощью Perl можно проанализировать сообщение и найти его источник. Начнем мы с малого и затем перейдем к более сложным вопросам, применяя то, что узнали в главе 5 «Службы имен TCP/IP» и главе 6. Если читателю хочется посмотреть на очень сложный Perl-сценарий для борьбы со спамом, советую взглянуть на
adcomplain Билла Макфад-дена (Bill McFadden), который можно найти на http://www.rdrop.com/ users /billmc/'adcomplain.html.
Вот копия настоящего спамерского сообщения, тело которого изменено, чтобы не доставить его автору удовольствия:
Received: from isiteinc.com (www.isiteinc.com [206.136.243.2])
by mailhost.exarnple.com (8.8.6/8.8.6) with ESMTP id NAA14955 f
or <webadmin@example.com>; Fri, 7 Аид 1998 13:55:41 -0400 (EOT)
From: responses@example.com
Received: from extreme (host-209-214-9-150.mia.bellsouth.net [209.214.9.150])
by isiteinc.com (8.8.3/8.8.3) with SMTP id KAA19050 for
webadmin@example.com; Fri, 7 Аид 1998 10:48:09 -0700 (EOT) Date:
Fri, 7 Аид 1998 10:48:09 -0700 (EOT)
Received: from login_0246.whynot.net mx.whynot.net[206.212.231.88])
by whynot.net (8.8.5/8.7.3) with SMTP id XAA06927 for <webadmin@example.com>;
Fri, 7 August 1998 13:48:11 -0700 (EOT) To: <webadmin@example.com>
Subject: ***ADVERTISE VACATION RENTALS - $25/year*»* - http://www.example.coT Reply-To:
sample@whynot.net X-PMFLAGS: 10322341.10 X-UIDL: 10293287J92832.222
Comments: Authenticated Sender is <user122@whynot.net>
Message-Id: <77126959_36550609>
Мы рады представить вам новый веб-сайт http://www.example.com от Extreme Technologies, Inc.
Наш новый сайт, посвященный путешествиям, содержит список некоторых интересных путешествий, которые только можно отыскать в WWto.вы легко найдете информацию об аренде и продаже жилья, об услугах и многом другом. Наш список сопровождается цветными фотографиями, анимированной графикой, точными описаниями и информацией о том, как напрямую связаться с арендатором/поставщиком.
Давайте критически оценим это сообщение. Во-первых, большинство его заголовков подозрительны. Как вы видели в предыдущем разделе этой главы, большинство заголовков (То:, From: и т. д.) передаются почтовой программе в разделе DATA передачи сообщения. Это единственный набор заголовков, которые трудно подделать и которые добавляются агентом передачи почты, когда сообщение проходит через почтовую систему.
В особенности нужно внимательно посмотреть на заголовки Received:.
Спамер может подделать строки Received:, но он не сумеет убрать те из них, которые были добавлены последующими почтовыми системами. Можно вставить фальшивые, но это потребует определенных и довольно серьезных знаний (например, как подделать TCP/IP-пакет или сфабриковать записи в DNS), которыми рядовые спамеры очень редко обладают.
Начнем с того, что выделим заголовки Received: из сообщения и представим их на экране в более читаемом виде. Мы выведем их в той последовательности, в которой передавалось сообщение, начиная с первого почтового сервера, получившего сообщение, и заканчивая местом назначения (нашим сайтом):
use Mail::Header;
Sheader = new Mail-Header \*STDIN;
$header->unfold('Received1); received = $header->get('Received');
for (reverse @received){
chomp;
parseline($_J;
if (Idefined $ehelo and Idefined $validname and I defined $validip){ print "$_\n";
}
else { write;
> }
format STDOUT =
(«««« ««««« 5«««««««« 5«<««««««
$ehelo,$validname,Svalidip sub parseline { my $line = $_;
и "нормальный" -- от НЕЮ (REAL [IP])
if (/from\s+(\w\S+)\s-\((\S+)\s*\[(\d+V\d+V \d+\.\d+)/){
($ehelo.$validname,$validip) = ($1.$2. $3); >
невозможно выполнить обратное разыменование -- от
НЕЮ ([IP]) elsif (/from\s+(\w\S+)\s+\(\[(\d+V\d+\.\d+V \d+)\]/)!
(Sehelo,Svalidname,$validip) = ($1,undef, $2); }
ft exim -- из [IP] (helo=[HELO IP]) elsif
(/ from\s+\[(\d+\.\d+\.\d+\.\d+)\]\s+\(helo=\[(\dA.\d+\Ad+\.\d+)\]/){
($validip,$ehelo,$validname) = ($1,$2, undef); >
ft Sun Internet Mail Server -- из [IP] by HELD
elsif (/from\s+\[(\d+V\d+V\d+V\d+)\]\s+by\s+(\S+)/){
($validip,$ehelo,$validname) = ($1,$2, undef); }
ft Microsoft SMTPSVC -- из HELD - (IP) elsif
(/from\s+(\S+)\s+-\s+(\d+V\d+V\d+V\d+)\s+/){
($ehelo,$validname,$validip) = ($1,$2, $3); } else { ft punt!
$ehelo = Svalidname = $validip = undef; }
return [Sehelo,$validname,Svalidip]; }
Первым делом из сообщения выбираются (при помощи unf old()) заголовки
Received:. Метод unf old () удаляет из заданных строк символы новой строки и символы продолжения. Это делается для упрощения анализа.
Все строки просматриваются в обратном порядке (по сравнению с тем, как они были найдены в сообщении). По существу, движение идет от центра сообщения к периферии, т. к. каждая система, через которую оно проходило, добавляла еще один уровень заголовков Received:. Большая часть работы выполняется подпрограммой &parseline. Используя несколько регулярных выражений, попробуем выделить следующее из заголовков Received::
Имя узла HELO/EHLO
Имя, представленное в обмен на HELO или EHLO при SMTP-«беседе».
«Действительный» IP-адрес
IP-адрес клиента, замеченный агентом передачи почты во время соединения. Он, скорее всего, будет «действительным», поскольку
использует информацию, не зависящую от того, что предоставил клиент во время SMTP-диалога. Это важно, поскольку клиент спамера, скорее всего, будет заядлым лжецом. Слово действительный берется в кавычки потому, что существуют способы сфабриковать и эту информацию.
«Действительное» имя
Имя клиента, найденное агентом передачи почты при обратном разыменовании IP-адреса через DNS. Эта информация, как и предыдущая, поступает не от клиента (хотя она тоже может быть фальшивой).
Формат правильной строки Received: определяется в RFC821 и RFC822. Однако, если просмотреть доставленную почту (как сделал я, создавая использованные регулярные выражения), можно заметить, что не все агенты передачи почты следуют данному правилу. В нашей программе будут применяться наиболее распространенные форматы, но существуют и другие способы, которые обязательно надо учитывать, если есть намерение в дальнейшем расширять код. Чтобы иметь представление о возможных форматах, посмотрите в сценарий adcomplain.
Вот как выглядит вывод программы, выполненной для сообщения, приведенного ранее:
login_0246.whynot.net mx.whynot.net 206.212.231.88
extreme host-209-214-9-150.mia 209.214.9.150
isiteinc.com www.isiteinc.com 206.136.243.2
В первом столбце перечислены имена, используемые машинами при идентификации; во втором - имена этих машин, указанные серверу при соединении; а в третьем - IP-адреса инициаторов данного соединения. Как уже отмечалось, в построенном списке последняя строка соответствует машине, передавшей сообщение на наш почтовый сервер.
Спамеры не могут удалить строки Received:, но они могут изменить их содержимое, задав фальшивое имя во время приветствия HELO или EHLO. Подтверждение этому можно увидеть во второй строке примера, поскольку имя узла из первого столбца не имеет ничего общего с именем из второго столбца, который более достоверен.
Но предположим, что они совпадают. Как же тогда узнать, что строка
Received: была подделана? Один из способов - сверять «действительный» IP-адрес из каждой строки Received: с «действительным» именем узла и сообщать об отклонениях. Приведенная ниже подпрограмма вернет значение (1), если разыменованное имя не совпадет с IP-адресом, в противном случае - (0). Вскоре этот код будет добавлен к основной программе:
my($ip,Sname) = @_;
return 0 unless ($ip and $name);
my $namelook = gethostbyaddr(inet_atoi($ip).AF_INET):
my SiplooK = gethostbyname(Sname):
Siplook = inet_ntoa($iplook) if Siplook;
ft может быть записан в различных регистрах
if ( Siplook eq Sip and Ic Snarnelook eq Ic Sname){
return 0; } else {
return 1; } }
Такая проверка не совсем надежна, потому что, как ни обидно, у вполне законных узлов может отсутствовать информация для обратного разыменования. Кроме того, серверам имен может быть предписано отвечать неверно, предоставляя фиктивную информацию (т. е. на gethostbyaddr() нельзя безоговорочно полагаться).
Много о чем можно догадаться из заголовков Received:, прежде чем отследить владельцев каждого хопа (hop). Например, кем разделяется мнение, что любой из хопов является известным источником спама?
Поиск в локальном черном списке
На некоторых сайтах ведутся локальные черные списки узлов, известных как распространители спама. Такая тактика была взята на вооружение, когда спам только начал появляться, и некоторые провайдеры отказывались принимать меры даже против самых закоренелых клиентов-спамеров. В ответ на это в основных агентах передачи почты появились механизмы, отвергающие соединения от узлов и доменов, входящих в список антисоциальных.
Можно использовать такой черный список, чтобы разобраться, проходило ли сообщение через узлы, известные своим спамерством. Ясно, что в черном списке нет сервера, передавшего нам почту (иначе с ним просто не было бы установлено соединение), но любой из остальных почтовых серверов, указанный в заголовках
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. Это позволяет сохранить регулярное выражение после его компиляции. Такая форма обработки значительно увеличивает скорость выполнения программы при дальнейшем поиске соответствия. Второй элемент анонимного массива - сама запись из черного списка, которая будет возвращена при найденном соответствии скомпилированному регулярному выражению.
Поиск в черном списке для всего Интернета
В последнем примере программы на вопрос «Спамер ли это?» мы отвечали, руководствуясь собственным мнением об узле или домене, не принимая во внимание опыт остальных пользователей Интернета. Существуют несколько спорные службы, предлагающие простой доступ к глобальным черным спискам спамеров или известных узлов, открыто допускающих ретрансляцию почты. Две хорошо известные службы такого типа - Realtime Blackhole List (RBL) от Mail Abuse Prevention System и Open Relay Behaviour-modification System (ORBS). Для получения доступа к этим спискам:
Если вы получите положительный ответ (т. е. запись о ресурсах А), это означает, что данный IP-адрес находится в черном списке.
Несколько менее спорным является список Dial-up User List, также поддерживаемый силами специалистов из Mail Abuse Prevention System. Это список диапазонов IP-адресов, динамически присваиваемых модемным пулам. Теоретически, SMTP-соединения не должны исходить от какого-либо из этих узлов. Почта с таких узлов должна отправляться через почтовый сервер провайдера (которого нет в этом списке).
Вот один из способов проверить, находится ли IP-адрес в каком-либо из этих списков:
sub checkaddr{
my($ip,Sdomain) = @_;
return undef unless (defined Sip);
my $lookupip = join('.',reverse split(/\./,$ip));
if (gethostbyname($lookupip.$domain)){
return Sip; } else {
return undef; } }
Очень скоро эта подпрограмма будет добавлена в предпоследний пример этого раздела. А пока, располагая существенно большим объемом информации о каждом из заголовков Received:, попробуем вычислить человека или людей, ответственных за администрирование каждой машины из списка. Модуль Net: ;Whois, уже рассмотренный в главе 6, вероятно, первым будет использоваться для решения этой проблемы.
К сожалению, этот модуль специализируется только на получении информации о связи имен и доменов (name-to-domain information). Кроме того, он «предполагает», что информация будет представлена в виде, используемом InterNIC. Нам могут понадобиться сведения о связи IP-адресов и доменов (IP address-to-domain information) от WHOIS-серверов на http://whois.arin.net
(American Registry for Internet Numbers), http://whois.ripe.net (European IP Address Allocations) и http:// whois.apnic.net (Asia Pacific Address Allocations). Отсутствие соответствующего модуля - первое препятствие, которое необходимо преодолеть.
Но даже если бы мы знали, как соединиться со всеми этими реестрами и обработать их различные форматы вывода, было бы неясно, к какому из них нужно обращаться для поиска информации о данном IP-адресе. Нам необходимо определить, к какому серверу нужно обратиться, и это второе препятствие. К счастью, если обратиться к ARIN с запросом по адресу, не принадлежащему его базе данных, он направит нас на нужный реестр. Так что, если мы спросим ARIN об адресе из Японии, он отправит нас на APNIC.
Для преодоления первого препятствия можно использовать модуль общего назначения, подобный Net: :Telnet из главы 6. Другой путь - уже рассмотренный модуль 10: :Socket. Что выбрать- дело личных предпочтений, ну и, конечно, необходима возможность доступа к нему с вашей платформы.
Служба WHOIS работает на порту 43 TCP, хотя ее имя будет использоваться только в целях предосторожности. С WHOIS-сервером очень просто общаться. Необходимо соединиться, выполнить запрос (в нашем случае это IP-адрес) и получить ответ. Программа, запрашивающая произвольный WHOIS-сервер, очень проста:
sub getwhois{
my($ip) = shift; my(Sinfo);
$cn = new Net::Telnet(Host => Swhoishost, Port => 'wliois',
Errmode => "return", Timeout => 30)
or die "Невозможно установить -соединении с
Swhoishost connection:$!\n";
unless ($cn->print($ip."\n")){
$cn->close;
die "Невозможно послать
$ip на Swhoishost: ".$cn->errmsg."Vv }
while ($ret = $cn->get){
Sinfo ,=$ret; };
$cn->close;
return $info; 1
Для преодоления второго препятствия, состоящего в выборе нужного реестра, есть, по крайней мере, две возможности. Можно послать запрос к http://whois.arin.net и проанализировать ответ. Например, вот запись диалога с ARIN по поводу IP-адреса японской машины. Жирный шрифт используется для выделения текста, введенного человеком:
X telnet whois.arin.net 43
Trying 192.149.252.22 ..
Connected to whois. arm. not Escape character is '"] 210.161.92.226
Asia Pacific Network Information Center (NETBLK-APNIC-CIDR-BLK)
Level 1 - 33 Park Roan1 Milton, 4064 AU
Netname: APNIC-CIDR-BLK2
Netblock: 210.0.0.0 - 211.255.255.0
Coordinator;
Administrator, System (SA90-ARIN)
sysadn®APNIC,NET +61-7-3367-0490
Domain System inverse mapping provided by:
SVC01.APNIC.NET 202.12.28.131
NS.TELSTRA.NET 203.50.0.137
NS.KRNIC.NET 202.30.64.21
NS.RIPE.NET 193.0.0.193
-** please refer to whois.apnic.net for more information *«*
*«* before contacting APNIC
*-« use whois -h whois.apnic.net <object> *»*
Record last updated on 04-Mar-99.
Database last updated on 19-Apr-99 16:14:16 EOT.
Подобные результаты означают, что запрос нужно послать к http:// whois.apnic.net.
Другой способ - послать запрос к «умному» WHOIS-серверу, который сделает всю работу сам. Из них мне больше всего нравится сервер -http://whois.geektools.com. «Умник» проанализирует ваш запрос, отправит его на нужный WHOIS-сервер и вернет результаты. Тому, кто пользуется этой службой, не нужно беспокоиться о том, на каком именно сервере хранится информация.
Чтобы код программы сильно не разрастался, а мы не отвлекались от обсуждаемой темы, будем пользоваться вторым (более простым) способом.
Поместим все эти маленькие запросы в одну большую программу и запустим ее. Итак, если нужно выполнить все приведенные выше подпрограммы с нашим примером сообщения:
use Mail:'Header: use Socket; use BerkeleyDB;
use Net::Telnet;
$header = new Mail-Header \*STDIN;
Sheader ->unfold('Received'); @received = $header->get('Received'):
Srbldomain = ".rbl.maps.vix.com" Sorbsdomain = ".relays.orbs.org";
Sduldomain = ".dul.maps.vix.com": Sblacklist = "/etc/mail/blacklist.db":
Swhoishost = "whois.geektools.com";
&loadblist;
for (reverse @received){ chomp;
parseline($_);
if (!defined $ehelo and !defined Svalidname and !defined $validip){ print "$Дп";
>
else {
Sflags = (&checkaddr($validip:$rbldomain) ? "R" : ""):
# в RBI/' $flags .= (&checkaddr($validip,$orbsdomairi) ? "0" : "");
в ORBS9 Sflags ,= (&checkaddr($validip. Sduldomain) ? "D......);
$flags .= (&checkblist($_) 9 "B" : ""): # в нашем спи
$flags .= (&checkrev($validip,$validname) ? "L" "");
(I rev-.;o.;k: push(@iplist,Svalidip);
write; } }
for (@iplist){
print "\nWHOIS info for $_:\n"; print &getwhois($_); >
format STDOUT =
@««««««««<«« @<«««««<'<««««««<«««««
Sehelo.Svalidname.Svalidip.Sflags
то будут получены такие результаты (слегка сокращенные):
login_0246.whynot.net mx.whynot.net 206.212.231 88 ;
extreme host-209-214-9-150 :r.a 209.214.9.150 Or
isiteinc.cori www.isiteinc.com 206,136 243 2
WHOIS info for 206,212.231.88:
WHOIS info for 209.214.9.150;
BellSouth.net Inc. (NETBLK-BELLSNET-BLK4)
1100 Ashwood Parkway
Atlanta. GA 30338
Netname: BELLSNET-BLK4
Netblock: 209.214.0.0 - 209.215.255.255
Maintainer: BELL
Coordinator-...
WHOIS info for 206.136.243.2:
Brainsell Incorporated (NET-ISITEINC)
4105-R Laguna St.
Coral Gaoles, FL 33146
US
Netname: ISITEINC Netnumber: 206.136.243.0
Coordinator:...
Гораздо лучше! Теперь нам известно:
Perl помог разобраться с непрошеной коммерческой почтой.
Впрочем, спам это очень неприятная вещь. Лучше перейдем к более приятной теме, например, к взаимодействию пользователей средствами электронной почты.
Увеличение почты в службу поддержки
Даже если у вас нет официальной службы поддержки, наверняка существует несколько адресов, куда пользователи могут посылать свои вопросы и сообщения о трудностях. У электронной почты, как средства для связи по вопросам поддержки, есть несколько преимуществ:
Мое любимое письмо в службу поддержки приведу точно в таком виде, в каком оно было получено, за исключением имени автора:
Date: Sat, 28 Sep 1996 12:27:35 -0400 (EOT) From:
Special User <user@example.com> To:
systems@example.com Subject: [Req. 89531] printer help
something is wrong and I
have know idea what (что-то случилось, и я не имею понятия, что именно)
Если бы пользователь не упомянул слово «принтер» в теме сообщения, не было бы никаких указаний на то, с чего начать, и нам, вероятно, пришлось бы думать, что и впрямь случилось нечто ужасное. Конечно, это самый крайний случай. Чаще вы будете получать примерно такую почту:
From: Another user <user2®examplecom>
Subject: [Req 814563] broken macine
To. systems§exarriple com
Date: Wed, 11 Mar 1998 10:59:42 -0500 (EST)
С малиной krakatoa. example, com
Пользователи посылают подобные письма, лишенные содержания, не со зла. Мне кажется, что корень всех бед в полном несоответствии представлений о компьютерной среде у пользователей и системных администраторов.
Для большинства пользователей видимая структура компьютерной среды ограничена клиентской машиной, на которой они работают, соседним принтером и их хранилищем данных (т. е. домашним каталогом). Для системного администратора структура совсем иная. Он видит ряд серверов, предоставляющих услуги клиентам, у каждого из которых может быть множество периферийных устройств. На каждой машине может быть установлено различное программное обеспечение и все они могут находиться в различном состоянии (системная загрузка, конфигурация и т. д.).
Для пользователя вопрос «С какой машиной проблемы?» кажется странным. Они говорят об одном компьютере, о том, на котором работают сейчас.
Неужели это не очевидно? Системному администратору столь же странной кажется просьба «помогите с принтером»; в конце концов, он следит за многими принтерами.
Также обстоит дело и со спецификой проблемы. Системные администраторы всего мира каждый день скрежещут зубами, получая почту, в которой сказано: «Мой компьютер не работает, не могли бы вы помочь мне?». Они знают, что «не работает» может относиться к множеству симптомов, у каждого из которых есть свои причины. Для пользователя, столкнувшегося с тремя зависаниями компьютера за неделю, слова «не работает» выглядят вполне конкретными.
Один из способов разобраться с таким расхождением - четко определить, какие данные посылать в сообщениях. На некоторых сайтах пользователь должен послать сообщение о проблеме, употребляя определенную форму или приложение. Беда такого подхода в том, что пользователи редко испытывают удовольствие от лишних движений мышью и нажатий клавиш, необходимых только для того, чтобы сообщить о проблеме или задать вопрос. Чем больше усилий нужно приложить, тем меньше вероятность, что кто-то воспользуется таким механизмом. Неважно, насколько хорошо продумана форма и какой у нее дизайн, если никто не захочет ею пользоваться. Вопросы в коридорах снова станут нормой. Снова вернулись назад?
Что ж, если применить Perl, может быть и нет. Perl наверняка поможет увеличить количество нормальной почты и поучаствовать в процессе поддержки. Один из первых шагов системного администратора выяснить местоположение: «Где случилась проблема? С каким принтером? С каким компьютером?». И так далее.
Вот костяк программы, которую я назвал suss, представляющая собой «скелет» для сбора информации. Программа изучает сообщение и пытается выяснить, с какой машиной оно связано. В результате часто
можно определить имя узла для писем из категории «С моим компьютером проблемы», не вступая ради этого в дальнейшую беседу с рассеянным пользователем. Имя узла - хорошая отправная точка в процессе поиска возникших проблем.
Программа suss применяет очень простой алгоритм для разгадывания имени узла (обычно, поиск в хэше для каждого слова из сообщения). Сначала изучается тема сообщения, затем его тело и, наконец, выполняется поиск по заголовкам
Received:. Вот упрощенная версия, считывающая файл /etc/hosts, чтобы определить имена узлов:
use Mail::Internet; $localdomain = ".example.com";
ft считываем файл /etc/hosts
open(HOSTS,"/etc/hosts") or die "He могу открыть файл узлов\п";
while(defined($_ = <HOSTS>)){
next if /"ft/; ft пропускаем комментарии
next if /"$/; ft пропускаем пустые строки
next if /monitor/i; ft пример вводящего в заблуждение узла
ft выделяем первое имя узла и переводим его в нижний регистр
Smachine = lc((split)[1]);
$machine =~ s/\Q$localdomain\E$//oi;
t удаляем имя домена
$macriines{$machine}++ unless $macnines{$machine}; }
ft анализируем сообщение
$message = new Mail: :Internet VSTDIN;
$message->head->unfold();
ft проверяем тему сообщения
my Ssubject = $message->head->get('Subject');
Ssubject =' s/[.,;?]//g;
for (split(/\s+/,Ssubject)) {
if (exists $machines{lc $_}) {
print "subject: $_\n";
$found++; } } exit if $found;
ft проверяем тело сообщения
chomp(my @body = @{$message->body()}):
my Sbody = join(" ",@body);
$body =" s/t"\w\s]/ /g: и удаляем знак
@body{split( ', lc $bcdy)i = ().
for (keys %body) {
if (exists $machines!ic $_}) {
print "body: $_\n";
$*ound+-i-; } }
exit if $found,
# последняя надежда: проверяем последнюю строку
Received: Sreceived -(reverse $message->head->get('Received'))[0];
Sreceived =" s/\0$localdomain\E//g; for (split(/\s+/,Sreceived)) {
if (exists $machines{lc $_}) { print "received: $_\n";
}
Несколько комментариев к программе:
Теперь применим эту программу. Вот два настоящих сообщения в службу поддержки:
Received: from strontium.example.com
(strontium.example.com [192.168.1.114])
by mailhub.example.com (8.8.4/8.7.3) with ESMTP id RAA27043
for <systems>; Thu, 27 Mar 1997 17:07:44 -0500 (EST)
From: User Person <user@example.com>
Received: (user@localhost)
by strontium.example.com (8.8.4/8.6.4) id RAA10500 for systems;
Thu, 27 Mar 1997 17:07:41 -0500 (EST)
Message-Id: <199703272207.RAA10500@strontium.example.com
Subject: [Req #11509] Monitor
To: systems@example.com
Date: Thu, 27 Mar 1997 17:07:40 -0500 (EST)
Hi,
My monitor is flickering a little bit and it is tiresome
when working with it to much.
Is it possible to fix it or changing the monitor?
Thanks. User,
Received: from example.com (user2@example.com [192.168.1.7])
by mailhost.example.com (8.8.4/8.7.3) with SMTP id SAA00732
for <systems@example.com>: Thu. 27 Mar 1997 18:34:54 -0500 (EST)
Date: Thu, 27 Mar 1997 18:34:54 -0500 (EST)
From: Another User <user2@example.com>
To: systems@example.com
Subject: [Req 811510] problems with two computers
Message-Id: <Pine.SUN.3.95.970327183117,23440A-100000ffiexamole.com>
In Jenolen (in room 292), there is a piece of a disk stuck in it.
In intrepid, there is a disk with no cover
(or whatever you call that silver thing) stuck in it.
We tried to turn off intrepid, but it wouldn't work.
Wo (the proctor on duty and I) tried to get the disk piece out, but it didn't. work.
The proctor in charge decided to put signs on them saying 'out of order'
AnotherUser После запуска программы для этих двух сообщений мы получили:
received: strontium и:
body: jenolen body: intrepid
Оба узла были найдены верно и для этого понадобился лишь небольшой отрывок простого кода. Шагнем дальше и предположим, что поступило такое письмо:
Received: from [192.168.1.118]
(buggypeak.example.com [192.168.1.118])
by mailhost.example.com (8.8.6/8.8.6)
with SMTP id JAA16638 for <systems>;
Tue, 4 Aug 1998 09:07:15 -0400 (EOT)
Message-Id: <v02130502b1ecb78576a9@[192.168.1.118]>
Date: Tue, 4 Aug 1998 09:07:16 -0400
To: systems@example.com
From: user@example.com (Nice User)
Subject: [Req «15746] printer
Could someone please persuade my printer to behave and print like
printer should9 Thanks much :)
-Nice User.
Пользователь, должно быть, не знает, что вы «пасете стадо» из 30 принтеров. Но можно применить Perl и чуть-чуть наблюдательности,
чтобы сделать умные догадки. Пользователи стараются печатать на принтерах, расположенных ближе всего к тому компьютеру, за которым в данный момент работают. Если бы можно было определить машину, с которой отправлена почта, вероятно, удалось бы вычислить и принтер. Существует много способов получить информацию о связи компьютер-принтер, например, из отдельного файла, из поля в базе данных узлов, о которой упоминалось в главе 5, или даже из службы каталогов LDAP. Вот простой пример, в котором используется простая база данных компьютеров и связанных с ними принтеров:
use Mail: .'Internet; use DB_File;
Slocaldomain = ".example.com";
# printdb - это файл Berkeley DB.
Ключи - имена узлов, значения - принтеры Sprintdb = "printdb";
п анализируем сообщение
Smessage = new Mail::Internet \*STDIN;
$message->head->unfold();
# проверяем тему сообщения
my Ssubject = $<nessage->head->get('Subject');
if (Ssubject =" /print(er|ing)?/i){
ff ищем машину-отправителя (ситаем, что используется формат заголовков Sendmail)
$received = (reverse $rr,essage->head->get( 'Received' ))[0];
($host) =
$received =" /"from \S+ \((?:\S+@)?(\S+)\Q$localdomain\E \[/; }
tie %printdb, "DB_File",Sprintdb or die "Невозможно подключиться к базе данных
Sprintdb:$!\n";
print "Проблема на машине Shost может быть связана с принтером " .
$pnntdb{$host} . "Дп";
untie %printdb;
Если в теме сообщения упоминаются слова «печать», «принтер» или «напечатать», мы выделяем имя узла из заголовка Received:. Для получения этой информации можно применить одно регулярное выражение, т. к. известен формат, используемый для заголовков Received: в нашей сети. Зная имя узла, нетрудно найти связанный с ним принтер в базе данных Berkeley DB. Конечный результат выглядит так:
Проблема на машине buggypea* может быть связана с niroshige. Потратив время на изучение структуры своего окружения, вы найдете разные способы получать больше пользы от почты, доставленной в службу поддержки. Приведенные в этом разделе примеры невелики и созданы для того, чтобы заставить вас задуматься о возможностях. Как еще могут помочь программы, читающие почту (возможно, это почта, отправленная другими программами)? Perl предоставляет много способов проанализировать почту, рассмотреть ее в широком контексте и затем использовать найденную информацию.