HTML page

Глава 18 Протоколы Интернета

18.5, Чтение почты на серверах РОРЗ

Проблема

Требуется принять почту с сервера РОРЗ. Например, программа может получать данные о непрочитанной почте, перемещать ее с удаленного сервера в локальный почтовый ящик или переключаться между Интернетом и локальной почтовой системой.

Решение

Воспользуйтесь модулем Net::POP3 с CPAN:
$рор = Net::POP3->new($mail_server)
or die "Can't open connection to $mail_server : $!\n";
$pop->login($username, $password)
or die "Can't authenticate: $!\n";
$messages = $pop->list
or die "Can't get list of undeleted messages: $!\n";
foreach $msgid (keys %$messages) {
$message = $pop->get($msgid);
unless (defined $message) {
warn "Couldn't fetch $msgid from server: $!\n";
next;
}
# $message - ссылка на массив строк
$pop->delete($msgid);
}

Комментарий

Традиционно в доставке почты участвовали три стороны: МТА (транспортный почтовый агент - системная программа типа sendmail) доставляет почту в накопитель (spool), а затем сообщения читаются с помощью MUA (пользовательские почтовые агенты - программы типа mail). Такая схема появилась в те времена, когда почта хранилась на больших серверах, а пользователи читали сообщения на простейших терминалах. По мере развития PC и сетевых средств появилась потребность в MUA (таких, как Pine), которые бы работали на пользовательских компьютерах (а не на том компьютере, где находится накопитель). Протокол POP (Post Office Protocol) обеспечивает эффективное чтение и удаление сообщений во время сеансов TCP/IP. Модуль Net::POP3 от CPAN обслуживает клиентскую сторону POP. Иначе говоря, он позволяет программе на Perl выполнять функции MUA. Работа с Net::POP3 начинается с создания нового объекта Net::POP3. Конструктору new передается имя сервера РОРЗ:
$рор = net: :pop3->new( "pop.myisp.-com" )
or die "Can't connect to pop.myisp.com: $!\n";

При возникновении ошибок все функции Net::POP3 возвращают undef или пустой список в зависимости от контекста вызова. При этом переменная $! может содержать осмысленное описание ошибки (а может и не содержать).
Кроме того, конструктору new можно передать дополнительные аргументы и определить тайм-аут (в секундах) для сетевых операций:
$рор = net::pop3->new( "pop.myisp.com",
Timeout => 30 ) or die "Can't connect to pop,myisp.corn : $!\n";

Метод login выполняет аутентификацию на сервере РОРЗ. Он получает дв;1 аргумента - имя пользователя и пароль, но оба аргумента являются необязательными. Если пропущено имя пользователя, используется текущее имя. Если пропущен пароль, Net::POP3 пытается определить пароль с помощью модуля Net::Netrc:
$pop->login("gnat", "S33kr1T Pa55wOrD")
or die "Hey, my username and password didn't work!\n";
$pop->login( "midget" ) # Искать пароль с помощью Net::Netrc
or die "Authentication failed.\n";
$pop->login() # Текущее имя пользователя и Net::Netrc
or die "Authentication failed. Miserably.\n";

При вызове метода login пароль пересылается по сети в виде обычного текста. Это нежелательно, поэтому при наличии модуля MD5 от CPAN можно воспользоваться методом арор. Он полностью идентичен login за исключением того, что пароль пересылается в текстовом виде:
$рор->арор( $username, $password )
or die "Couldn't authenticate: $!\n";

После аутентификации методы list, get и delete используются для работы с накопителем. Метод list выдает список неудаленных сообщений, хранящихся в накопителе. Он возвращает хэш, где ключом является номер сообщения, а ассоциированное значение - размер сообщения в байтах:
%undeleted = $pop->list();
foreach $msgnum (keys %undeleted) {
print "Message $msgnum is $undeleted{$msgnum} bytes long.\n";
}

Чтобы принять сообщение, вызовите метод get с нужным номером. Метод возвращает ссылку на массив строк сообщения:
print "Retrieving $msgnum : ";
$message = $pop->get($msgnum);
if ($message) {
# succeeded
print "\n";
print @$message; # Вывести сообщение
} else {
# failed
print "failed ($!)\n";
}

Метод delete помечает сообщение как удаленное. При вызове метода quit, завершающего сеанс РОРЗ, помеченные сообщения удаляются из почтового ящика. Метод reset отменяет все вызовы delete, сделанные во время сеанса. Если сеанс завершается из-за того, что объект Net::POP3 уничтожен при выходе из области действия, метод reset будет вызван автоматически. Возможно, вы заметили, что мы ничего не сказали об отправке почты. РОРЗ поддерживает только чтение и удаление существующих сообщений. Новые сообщения приходится отправлять с помощью программ типа mail или sendmail или протокола SMTP. Другими словами, рецепт 18.3 все равно пригодится. Основная задача РОРЗ - подключение почтовых клиентов к почтовым серверам - также выполняется протоколом IMAP. IMAP обладает более широкими возможностями и чаще используется на очень больших узлах.

> Смотри также -------------------------------
Документация по модулю Net::POP3 с CPAN; RCS 1734, "РОРЗ AUTHentication command"; RFC 1957, "Some Observations on Implementations of the Post Office Protocol".

18.6. Программная имитация сеанса telnet

Проблема

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

Решение

Воспользуйтесь модулем Net::Telnet с CPAN:
use Net::Telnet;
$t = net: :telnet->new( Timeout => 10,
Prompt => '/%/', Host => $hostname );
$t->logln($username, $password);
@files = $t->cmd("ls"):
$t->print("top");
(undef, $process_string) = $t->waitfor('/\d+ processes/');
$t->close;

Комментарий

Модуль Net::Telnet поддерживает объектно-ориентированный интерфейс к протоколу telnet. Сначала вы устанавливаете соединение методом Net:: Telnet->new, a затем взаимодействуете с удаленным компьютером, вызывая методы полученного объекта. Метод new вызывается с несколькими параметрами, передаваемыми в хэш-по-добной записи (параметр => значение). Мы упомянем лишь некоторые из многих допустимых параметров. Самый важный, Host, определяет компьютер, к которому вы подключаетесь. По умолчанию используется значение localhost. Чтобы использовать порт, отличный от стандартного порта telnet, укажите его в параметре Port. Обработка ошибок выполняется функцией, ссылка па которую передается в параметре Errmode.
Еще один важный параметр - Prompt. При регистрации или выполнении команды модуль Net::Telnet по шаблону Prompt определяет, завершилась ли регистрация или выполнение команды. По умолчанию Prompt совпадает со стандартными приглашениями распространенных командных интерпретаторов:
/[\$%">] $/

Если на удаленном компьютере используется нестандартное приглашение'. вам придется определить собственный шаблон. Не забудьте включить в него символы /. Параметр Timeout определяет продолжительность (в секундах) тайм-аута при сетевых операциях. По умолчанию тайм-аут равен 10 секундам.
Если в модуле Net::Telnet происходит ошибка или тайм-аут, по умолчанию инициируется исключение. Если не перехватить его, исключение выводит сообщение в STDERR и завершает работу программы. Чтобы изменить это поведение, передайте в параметре Errmode ссылку на подпрограмму. Если вместо ссылки Errmode содержит строку "return", то при возникновении ошибок методы возвращают undef (в скалярном контексте) или пустой список (в списковом контексте); при этом сообщение об ошибке можно получить с помощью метода о' 'rmsg:
$telnet = net::telnet->new( Errmode => sub { main::log(@_) }, ... );

Метод login передает имя пользователя и пароль на другой компьютер. Успешное завершение регистрации определяется по шаблону Prompt; если хост не выдал приглашения, происходит тайм-аут:
$telnet->login($username, $password)
or die "Login failed: @{[ $telnet->errmsg() ]}\n";
Для запуска программы и получения ее вывода применяется метод cmd. Он получает командную строку и возвращает выходные данные программы. В списковом контексте возвращается список, каждый элемент которого соответствует отдельной строке. В скалярном контексте возвращается одна длинная строка. Перед возвратом метод ожидает выдачи приглашения, определяемого по шаблону Prompt.
Пара методов, print и waitfor, позволяет отделить отправку команды от получения ее выходных данных, как это было сделано в решении. Метод waitfor получает либо набор именованных аргументов, либо одну строку с регулярным выражением Perl:
$telnet->waitfor('/--more--/')

Параметр Timeout определяет новый тайм-аут, отменяя значение по умолчанию. Параметр Match содержит оператор совпадения (см. выше), a String - искомую строковую константу:
$telnet->waitfor(Strlng => 'greasy smoke', Timeout => 30)

В скалярном контексте waitfor возвращает true, если шаблон или строка были успешно найдены. В противном случае выполняется действие, определяемое параметром Errmode. В списковом контексте метод возвращает две строки: весь текст до совпадения и совпавший текст.

> Смотри также ------------------------------
Документация по модулю Net::Telnet с CPAN; RFC 854-856 и дополнения в последующих RFC.

18.7. Проверка удаленного компьютера

Проблема

Требуется проверить доступность сетевого компьютера. Сетевые и системные программы часто используют для этой цели программу ping.

Решение

Воспользуйтесь стандартным модулем Net::Ping:
use Net::Ping;
$p = net::ping->new()
or die "Can't create new ping object: $!\n";
print "$host is alive" if $p->ping($host);
$p->close;

Комментарий

Проверить работоспособность компьютера сложнее, чем кажется. Компьютер может реагировать на команду ping даже при отсутствии нормальной фунциона.тьно-сти; это не только теоретическая возможность, но, как ни печально, распространенное явление. Лучше рассматривать утилиту ping как средство для проверки доступности компьютера, а не выполнения им своих функций. Чтобы решить последнюю задачу, вы должны попытаться обратиться к его демонам (telnet, FTP, Web, NFS и т. д.).
В форме, показанной в решении, модуль Net::Ping пытается подключиться к эхо-порту UDP (порт 7) на удаленном компьютере, отправить датаграмму и получить эхо-ответ. Компьютер считается недоступным, если не удалось установить соединение, если отправленная датаграмма не была получена или если ответ отличался от исходной датаграммы. Метод ping возвращает true, если компьютер доступен, и false в противном случае.
Чтобы использовать другой протокол, достаточно передать его имя при вызове new. Допустимыми являются протоколы tcp, udp и icmp (записываются в нижнем регистре). При выборе протокола TCP программа пробует подключиться эхо-порту TCP (порт 7) удаленного компьютера и возвращает true при усиеппк установке соединения и false в противном случае (в отличие от UDP Пересылка данных не выполняется). При выборе ICMP будет использован протокол ICMP, как в команде ping(8). На компьютерах UNIX протокол ICMP может быть выбран только ривилегированным пользователем:
# Использовать ICMP при наличии привилегий и TCP в противном случае
$pong = net::ping->new( $> ? "tcp" : "icmp" );
(defined $pong)
or die "Couldn't create Net::Ping object: $!\n":
if ($pong->ping("kingkong.com")) {
print "The giant ape lives!\n";
} else {
print "All hail mighty Camera, friend of children!\n";
}

Ни один из этих способов не является абсолютно надежным. Маршрутизато ры некоторых узлов отфильтровывают протокол ICMP, поэтому Net::Ping сочтп такие компьютеры недоступными даже при возможности подключения по другим протоколам. Многие компьютеры запрещают эхо-сервис TCP и UDP, что приводит к неудачам при опросе через TCP и UDP. Запрет службы или фильтра цию протокола невозможно отличить от неработоспособности компьютера.

[> Смотри также --------------------------------
Документация по модулю Net::Ping; страницы руководства ping(8), tcp(3), udp(4) и icmp(4) вашей системы (если есть); RFC 792 и 950.

18.8. Применение whois для получения данных от InterNIC

Проблема

Вы хотите узнать, кому принадлежит домен (по аналогии с командой UNIX whois).

Решение

Воспользуйтесь модулем Net::Whois с CPAN:
use Net::Whois;
$domain_obj = net::whois::domain->new($domain_name)
or die "Couldn't get information on $domain_name: $!\n";
# Вызвать методы объекта
$domain_obj # для получения имени, тега, адреса и т. д.

Комментарий

Сервис whois предоставляется службой регистрации доменных имен и предназначается для идентификации владельца имени. Исторически в системах семейства UNIX эти данные получались с помощью программы whois(\), которая возвращала около 15 строк информации, включая имена, адреса и телефоны административных, технических и финансовых контактных лиц домена. Модуль Net::Whois, как и whois(i), является клиентом службы whois. Он подключается к серверу whois (по умолчанию используется whois.intemic.net, главный сервер доменов ".corn", ".org.", ".net" и ".edu"). Доступ к данным осуществляется с помощью методов, вызываемых для объекта. Чтобы получить информацию о домене, создайте объект Net::Whois::Domain. Например, для получения данных о perl.org объект создается так:
$d = net::whois::domain->new( "perl.org" )
or die "Can't get information on perl.org\n";

Гарантируется только получение имени домена и тега - уникального идентификатора домена в учетных записях NIC:
print "The domain is called ", $d->domain, "\n";
print "Its tag is ", $d->tag, "\n";

Также могут присутствовать следующие данные: название организации, которой принадлежит домен (например, "The Perl Institute"); адрес компании в виде списка строк (например, ("221В Baker Street", "London")) и страна (например, "United Kingdom" или двухбуквенное сокращение "uk").
print "Mail for ", $d->name, " should be sent to:\n";
print map { "\t$_\n" } $d->address;
print "\t", $d->country, "\n";

Кроме информации о самом домене также можно получить сведения о контактных лицах домена. Метод contact возвращает ссылку на хэш, в котором тип контакта (например, "Billing" или "Administrative") ассоциируется с массивом строк.
$contact_hash = $d->contacts;
if ($contact_hash) { print "Contacts:\n";
foreach $type (sort keys %$contact_hash) { print " $type:\n";
foreach $line (@{$contact_hash->{$type}}) { print " $line\n";
} } } else {
print "No contact information.\n";
}


[> Смотри также --------------------------------
Документация по модулю Net::Whois с CPAN; man-страница whois(8) вашей системы (если есть); RFC 812 и 954.

18.9. Программа: ехрn и vrfy

Программа ехрп напрямую общается с сервером SMTP и проверяет адрес с помощью команд EXPN и VRFY. Она не идеальна, поскольку ее работа зависит от получения достоверной информации с удаленного сервера командами EXPN и VRFY. Программа использует модуль Net::DNS, если он присутствует, но может работать и без него.
Программа узнает, как она была вызвана, с помощью неременной $0 (имя программы). При запуске под именем ехрп используется команда EXPN; при запуске под именем vrfy используется команда VRFY. Установка команды под двумя разными именами осуществляется с помощью ссылок:
% cat > ехрn
#!/usr/bin/perl -w
"D % In ехрn vrfy

Передайте программе адрес электронной почты, и она сообщит результаты проверки адреса командой EXPN или VRFY. Если у вас установлен модуль Net::DNS, программа проверяет все хосты пересылки почты (mail exchangers), перечисленные в записи DNS данного адреса. Без Net::DNS результаты выглядят так:
% ехрn gnat@frii.com Expanding gnat at frii.com (gnat@frii.com):
calisto.frii.com Hello coprolith.frii.com [207.46.130.14],
pleased to meet you
При установленном модуле Net::DNS получен следующий результат:
% ехрп gnat@frii.com Expanding gnat at mail.frii.net (gnat@frii.com):
deimos.frii.com Hello coprolith.frii.com [207.46.130.14],
pleased to meet you Nathan Torkington
Expanding gnat at mx1.frii.net (gnat@frii.com):
phobos.frii.com Hello coprolith.frii.com [207.46.130.14],
pleased to meet you
Expanding gnat at mx2.frii.net (gnat@frii.com):
europa.frii.com Hello coprolith.frii.com [207.46.130.14],
pleased to meet you
Expanding gnat at mx3.frii.net (gnat@frii.com):
ns2.winterlan.com Hello coprolith.frii.com [207.46.130.14],
pleased to meet you 550 gnat... User unknown
Исходный текст программы приведен в примере 18.3. Пример 18.3. ехрп
#!/usr/bin/perl -w
# ехрn - расширение адресов через SMTP use strict;
use 10::Socket;
use Sys::Hostname;
my $fetch_mx = 0;
# Попытаться загрузить модуль, но не огорчаться в случае неудачи
eval {
require Net::DNS;
Net::DNS->import('mx');
$fetch mx = 1;
};
my $selfname = hostname();
die "usage: $0 address\@host ,,,\n" unless @ARGV;
# Определить имя программы - "vrfy" или "ехрп".
my $VERB = ($0 =~ /ve?ri?fy$/i) ? 'vrfy' : 'expn' my $multi = @argv > 1;
my $ remote;
# Перебрать адреса, указанные в командной строке
foreach my $combo (@ARGV) {
my ($name, $host) = split(/\@/, $combo);
my @hosts;
$host ||= 'localhost';
@hosts = map { $_->exchange } mx($host)
if $fetch_mx;
@hosts = ($host) unless @hosts;
foreach my $host (@hosts) {
print $VERB eq 'VRFY' ? "Verify" : "Expand", "ing $name at $host ($combo):";
$remote = 10::socket::inet->new( Proto => "tcp", PeerAddr => $host, PeerPort =>
"smtp(25)",
};
unless ($remote) {
warn "cannot connect to $host\n";
next;
}
print "\n";
$ remote->autoflush(1);
# Использовать сетевые разделители строк CRLF
print $remote "HELO $selfname\015\012";
print $remote "$VERB $name\015\012":
print $remote "quit\015\012";
while (<$remote>) {
/~220\b/ && next;
/"221\b/ && last;
s/250\b[\-\s]+//;
print;
}
close($remote) or die "can't close socket: $!
print "\n";

copyright 2000 Soft group