Ключевые слова:smtp, auth, perl, (найти похожие документы)
From: Денис Назаров <marsden at mail.ru>
Newsgroups: email
Date: Mon, 30 Aug 2008 18:21:07 +0000 (UTC)
Subject: Отправка писем с авторизацией на SMTP-сервере из Perl
Введение
Возникла задача - отправка письма без использования sendmail, без настройки
своего smtp-сервера - то есть на минимальной десктопной системе. Осложнялась
задача тем, что к письму необходимо было прицеплять вложения. Отсутсвие
своего почтового сервера привело к решению использовать ящик на одном
из бесплатных почтовых серверов, в частности - mail.ru. Готовых примеров
в интернете нашлось немало, но почти у всех были недостатки - использование
sendmail или других сторонних программ и самый главный - отсутствие авторизации
на smtp сервере. Нашелся, впрочем, в котором довольно подробно описан процесс
взаимодействия с сервером, в том числе авторизация. В результате получился
небольшой скрипт на perl, который можно легко доработать под свои нужды.
Результат
#!/usr/bin/perl
use MIME::Base64; #для кодирования авторизационных параметров, темы и тела письма
use IO::Socket; #для общения с SMTP-сервером
use Text::Iconv; #для перекодирования текста
my $mailbox = 'xxxxxx@mail.ru'; # ящик-отправитель
my $mailpwd = 'xXxYyYzZz'; # пароль
my $mailrcpt = 'yyyyyyy@mail.ru'; # ящик-получатель
my $subj = 'Привет, это я!';
my $mail = 'Отправляю тебе свою фотку с Черного моря :)';
my $attachment = 'foto.jpg'; # файл-вложение
my $attachpath = '/home/user/Documents/'; # путь к нему
# поскольку данный скрипт я пишу в кодировке UTF-8,
# а письмо хочу отправлять в кодировке Windows, то мне просто необходим
# перекодировщик для кодирования из UTF-8(юникод) в CP1251(Windows)
my $cnv = Text::Iconv->new('UTF8','CP1251');
my $reply; # код ответа сервера
my $message; # текст ответа сервера
#открываем сокет к SMTP-серверу
my $socket = IO::Socket::INET->new('smtp.mail.ru:25');
defined $socket or die "ERROR: $!\n";
# читаем ответ
if(ReadReply() ne 220){print "Ошибка установки связи = $message\n"; $socket->close(); exit}
# здороваемся с сервером - не стал использовать ehlo,
# потому что расширенные возможности не требуются
$socket->print ("helo lo\n");
# получаем ответ и проверяем код
if(ReadReply() != 250){print "Ошибка приветствия сервера = $message\n"; $socket->close(); exit}
# теперь проводим авторизацию
$socket->print("AUTH LOGIN\n");
# получаем ответ
if(ReadReply() ne 334){print "Ошибка авторизации = $message\n"; $socket->close(); exit}
# кодируем логин-пароль
$socket->print(encode_base64($mailbox).encode_base64($mailpwd));
# после авторизации выдается две строчки
ReadReply();
if(ReadReply() ne 235){print "Ошибка авторизации = $message\n"; $socket->close(); exit}
# начинаем транзакцию - даем команду отправки письма
$socket->print('mail from: '."$mailbox\n");
if(ReadReply() ne 250){print "Ошибка в почтовом ящике отправителя = $message\n"; $socket->close(); exit}
# указываем получателя
$socket->print("rcpt to: $mailrcpt\n");
if(ReadReply() ne 250){print "Ошибка в почтовом ящике получателя = $message\n"; $socket->close(); exit}
# теперь начинаем формировать письмо
$socket->print("data\n");
if(ReadReply() ne 354){print "Ошибка при начале формирования письма = $message\n"; $socket->close(); exit}
# теперь сформируем тему письма, перекодировав ее из юникода в ср-1251
# и потом закодировав все это в base64.
# Таким образом, в теме письма можно нормально писать по русски
# Если вы используете другую кодировку в системе и/или при подготовке
# этого скрипта под свои нужды - скорректируйте создание конвертера в начале
# или вообще откажитесь от него
$subj = encode_base64($cnv->convert($subj));
$subj =~ s/\n//ig; # уберем символы перевода строки
$subj =~ s/\r//ig; # и возврата каретки, поскольку они все ломают :)
$subj = '=?Windows-1251?B?'.$subj.'?=';
# создадим тело письма
$msg = encode_base64($cnv->convert($mail));
# здесь формируем заголовок, минимальная версия
$body = "Mime-Version: 1.0\n";
$body .= "Content-Type: multipart/mixed; boundary=\"-\"\n\n";
# вставляем тело письма
$body .= "---\nContent-Type: text/plain;\n\tcharset=\"Windows-1251\"\nContent-Transfer-Encoding: base64\n\n$msg\n";
# и прицепляем файл-вложение
$body .= "---\nContent-Type: application/octet-stream; name=\"$attachment\"\n";
$body .= "Content-Transfer-Encoding: base64\n";
$body .= "Content-Disposition: inline; filename=\"$attachment\"\n\n";
# чтобы сформировать вложение, открываем файл
# в двоичном режиме, считываем его в память и кодируем в base64
$txt = '';
open f,"$attachpath$attachment";
binmode f;
while($str=<f>){$txt.=$str};
close f;
$body .= encode_base64($txt)."\n---\n";
# и наконец соберем письмо в одну переменную :)
$mailmessage = "From:$mailbox\nTo:$mailrcpt\nSubject:$subj\n$body\n.\n";
# скинем письмо серверу
$socket->print($mailmessage);
# и посмотрим что получилось
if(ReadReply() ne 250){print "Ошибка при отправке письма = $message\n"; $socket->close(); exit}
# если дошли до этого места, значит письмо ушло
$socket->close();
print "Письмо отправлено\n";
sub ReadReply{
# процедура чтения ответа от сервера
# цикл используется для того, чтобы прочитать многострочный ответ
# например, при выдаче ответа на команду EHLO
# формат строк
# Трехзначное число-пробел или дефис-текстовое сообщение
# причем, если ответ многострочный, то дефис используется во всех
# строках, кроме последней, в которой используется пробел
# именно по этому признаку и будет определятся конец цикла
# и возвращаемый код будет браться также из последней строки
$val = 1;
while($val eq 1){
$r = <$socket>;
$val = $r =~ m/^\d{3}-/g;
}
($reply,$message) = split(/ /,$r,2);
return $reply;
}
Ссылки
пример на php - http://webi.ru/webi_articles/6_10_f.html
пример использования сокетов - http://www.opennet.dev/base/dev/perl_sendmail.txt.html
Денис Назаров
marsden at mail.ru
август 2008
ага, только в процессе использования выяснилось, что надо бы еще одну переменную ввести, например $mailuser и использовать ее при авторизации, потому что не на всех почтовых серверах в качестве логина применяется адрес ящика, потом кодирование темы и самого тела письма тоже можно вытащить вверх до авторизации, чтобы сам процесс отправки можно было затолкать в цикл. А так вполне жизнеспособная вещь получилась :) В общем-то сделалось все за пару дней, так что можно еще шлифовать