SMTP: Отправка писем с авторизацией своими руками
Продолжаю тему сокетов и в этой статье я хотел бы привести практический пример отправки электронной почты через SMTP-сервер с авторизацией из скрипта PHP. Я думаю, Вы знаете, что такое SMTP - Simple Mail Transfer Protocol, поэтому останавливаться на нем не буду. |
У меня есть комп с установленной на нем Windows Server. А в этой системе легко настроить встроенные SMTP- и POP3-сервер и экспериментировать на них локально, без использования инета. Посылаешь себе же на свой комп сообщение, потом его от себя же и принимаешь Так я и сделал, чтобы не тревожить лишний раз smtp.mail.ru
Итак, сначала рассмотрим процесс общения с SMTP-сервером без авторизации. В списке команд ниже буквой C я обозначил запросы клиента (т.е. мои запросы), а буквой S - ответы сервера. Чтобы пообщаться с SMTP, достаточно воспользоваться командой telnet в Windows:
telnet localhost 25 - подключаемся к себе на хост на 25-ый порт
Или для mail.ru: telnet smtp.mail.ru 25
Кстати, там по-моему для каждого домена свой smtp: smtp.bk.ru, smtp.list.ru и т.п.
Я буду приводить здесь процессы общения с моим локальным сервером:
<b>C:</b> HELO<br/>
<b>S:</b> 250 novicemachine Hello [127.0.0.1]<br/>
<b>C:</b> MAIL FROM:<novice@localhost.ru><br/>
<b>S:</b> 250 2.1.0 novice@localhost.ru....Sender OK<br/>
<b>C:</b> RCPT TO:<novice@localhost.ru><br/>
<b>S:</b> 250 2.1.5 novice@localhost.ru<br/>
<b>C:</b> DATA<br/>
<b>S:</b> 354 Start mail input; end with <CRLF>.<CRLF><br/>
<b>C:</b> Message text<br/>
<b>C:</b> .<br/>
<b>S:</b> 250 2.6.0 <NOVICEMACHINEMpmZAW800000003@novicemachine> Queued mail for delivery<br/>
<b>C:</b> QUIT<br/>
<b>S:</b> 221 2.0.0 novicemachine Service closing transmission channel
novicemachine - это имя компьютера (smtp-сервера)
Теперь приведем пример с авторизацией:
Mon, 14 Jul 12:10:19 +0400<br/>
<b>C:</b> EHLO<br/>
<b>S:</b> 250-novicemachine Hello [127.0.0.1]<br/>
<b>S:</b> 250-AUTH=LOGIN<br/>
<b>S:</b> 250-AUTH LOGIN<br/>
<b>S:</b> 250-TURN<br/>
<b>S:</b> 250-SIZE 2097152<br/>
<b>S:</b> 250-ETRN<br/>
<b>S:</b> 250-PIPELINING<br/>
<b>S:</b> 250-DSN<br/>
<b>S:</b> 250-ENHANCEDSTATUSCODES<br/>
<b>S:</b> 250-8bitmime<br/>
<b>S:</b> 250-BINARYMIME<br/>
<b>S:</b> 250-CHUNKING<br/>
<b>S:</b> 250-VRFY<br/>
<b>S:</b> 250 OK<br/>
<b>C:</b> AUTH LOGIN<br/>
<b>S:</b> 334 VXNlcm5hbWU6<br/>
<b>C:</b> Y3Jhc2g=<br/>
<b>S:</b> 334 UGFzc3dvcmQ6<br/>
<b>C:</b> Y3Jhc2g=<br/>
<b>S:</b> 235 2.7.0 Authentication successful<br/>
<b>C:</b> MAIL FROM:<novice@localhost.ru><br/>
<b>S:</b> 250 2.1.0 novice@localhost.ru....Sender OK<br/>
<b>C:</b> RCPT TO:<novice@localhost.ru><br/>
<b>S:</b> 250 2.1.5 novice@localhost.ru<br/>
<b>C:</b> DATA<br/>
<b>S:</b> 354 Start mail input; end with <CRLF>.<CRLF><br/>
<b>C:</b> Message text<br/>
<b>C:</b> .<br/>
<b>S:</b> 250 2.6.0 <NOVICEMACHINEILH4ekA00000004@novicemachine> Queued mail for delivery<br/>
<b>C:</b> QUIT<br/>
<b>S:</b> 221 2.0.0 novicemachine Service closing transmission channel
Т.е. мы в обоих случаях сначала приветствуем SMTP-сервера.
В первом случае это делается командой HELO. Во втором - EHLO. Команда EHLO говорит серверу о том, что нужно вывести список всех доступных расширений (в том числе AUTH, если в сервере поддерживается аутентификация, что мы и видим).
В случае без авторизации все просто: приветствуем сервера, говорим адрес отправителя, получателя, пишем сообщение и отсоединяемся.
При авторизации процесс немного сложнее: приветствуем сервера, авторизуемся (говорим логин и пароль), говорим адрес отправителя, получателя, пишем сообщение и отсоединяемся.
Чтобы начать авторизацию, нужно задать команду AUTH LOGIN, хотя это не всегда именно LOGIN. В некоторых случаях может быть PLAIN и т.д., т.е. тут задается механизм авторизации, который зависит от сервера. В нашем случае это LOGIN (и в случае mail.ru кстати тоже).
Далее сервер нам ответил закодированным (алгоритмом base64) сообщением VXNlcm5hbWU6 с кодом 334 (коды, начинающиеся на 2 или 3, говорят об успешности предыдущего запроса клиента). Это сообщение, если его раскодировать: «Username:».
Дальше мы ему сказали: Y3Jhc2g=, т.е. это логин «novice», закодированный base64. После этого он спросил пароль: 334 UGFzc3dvcmQ6. Мы ему ответили тем же: Y3Jhc2g=.
Дальше сервер нас обрадовал: 235 2.7.0 Authentication successful - аутентификация прошла успешно. Ну а потом уже процесс аналогичен отправке письма без аутентификации.
А теперь самое вкусное - напишем скрипт для отправки письма:
header('Content-Type: text/plain;');
error_reporting(E_ALL ^ E_WARNING);
ob_implicit_flush();
$address = 'localhost'; // адрес smtp-сервера
$port = 25; // порт (стандартный smtp - 25)
$login = 'novice'; // логин к ящику
$pwd = 'novice'; // пароль к ящику
$from = 'novice@localhost.ru'; // адрес отправителя
$to = 'novice@localhost.ru'; // адрес получателя
$subject = 'Message subject'; // тема сообщения
$message = 'Message text'; // текст сообщения
try {
// Создаем сокет
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket < 0) {
throw new Exception('socket_create() failed: '.socket_strerror(socket_last_error())."\n");
}
// Соединяем сокет к серверу
echo 'Connect to \''.$address.':'.$port.'\' ... ';
$result = socket_connect($socket, $address, $port);
if ($result === false) {
throw new Exception('socket_connect() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo "OK\n";
}
// Читаем информацию о сервере
read_smtp_answer($socket);
// Приветствуем сервер
write_smtp_response($socket, 'EHLO '.$login);
read_smtp_answer($socket); // ответ сервера
echo 'Authentication ... ';
// Делаем запрос авторизации
write_smtp_response($socket, 'AUTH LOGIN');
read_smtp_answer($socket); // ответ сервера
// Отравляем логин
write_smtp_response($socket, base64_encode($login));
read_smtp_answer($socket); // ответ сервера
// Отравляем пароль
write_smtp_response($socket, base64_encode($pwd));
read_smtp_answer($socket); // ответ сервера
echo "OK\n";
echo "Check sender address ... ";
// Задаем адрес отправителя
write_smtp_response($socket, 'MAIL FROM:<'.$from.'>');
read_smtp_answer($socket); // ответ сервера
echo "OK\n";
echo "Check recipient address ... ";
// Задаем адрес получателя
write_smtp_response($socket, 'RCPT TO:<'.$to.'>');
read_smtp_answer($socket); // ответ сервера
echo "OK\n";
echo "Send message text ... ";
// Готовим сервер к приему данных
write_smtp_response($socket, 'DATA');
read_smtp_answer($socket); // ответ сервера
// Отправляем данные
$message = "To: $to\r\n".$message; // добавляем заголовок сообщения "адрес получателя"
$message = "Subject: $subject\r\n".$message; // заголовок "тема сообщения"
write_smtp_response($socket, $message."\r\n.");
read_smtp_answer($socket); // ответ сервера
echo "OK\n";
echo 'Close connection ... ';
// Отсоединяемся от сервера
write_smtp_response($socket, 'QUIT');
read_smtp_answer($socket); // ответ сервера
echo "OK\n";
} catch (Exception $e) {
echo "\nError: ".$e->getMessage();
}
if (isset($socket)) {
socket_close($socket);
}
// Функция для чтения ответа сервера. Выбрасывает исключение в случае ошибки
function read_smtp_answer($socket) {
$read = socket_read($socket, 1024);
if ($read{0} != '2' && $read{0} != '3') {
if (!empty($read)) {
throw new Exception('SMTP failed: '.$read."\n");
} else {
throw new Exception('Unknown error'."\n");
}
}
}
// Функция для отправки запроса серверу
function write_smtp_response($socket, $msg) {
$msg = $msg."\r\n";
socket_write($socket, $msg, strlen($msg));
}
?>
У меня при запуске скрипт выдал:
Authentication ... OK<br/>
Check sender address ... OK<br/>
Check recipient address ... OK<br/>
Send message text ... OK<br/>
Close connection ... OK
Чтобы подстроить этот скрипт под себя, Вам надо изменить всего 5 переменных: $address (например, smtp.mail.ru), $login, $pwd, $from, $to. Пробуйте, проверяйте. У меня работает Кстати, процесс общения с сервером POP3 (для проверки и доставки почты) тоже довольно простой. Может быть я даже расскажу о нем позже.
Исходник скрипта можно скачать здесь.
Статья в тему: Сокеты в PHP
Ха, оч интересно . Вот только телнетиться к smtp.mail.ru че-то не получается. Нужно у себя на линуксе подымать смтп .
Skill00 коннектиться не получается или работать?
У меня коннектится, но нафиг посылает
Trying 194.67.23.111...
Connected to smtp.mail.ru.
Escape character is '^]'.
220 mail.ru ESMTP Sat, 26 Jul 12:50:16 +0400
EHLO
501 Syntactically invalid EHLO argument(s)
HELO
501 Syntactically invalid HELO argument(s)
421 mx34.mail.ru: SMTP command timeout - closing connection
Connection closed by foreign host.
user@user-desktop:~$
2 VolCh: не коннектится из-за команды EHLO и HELO.
Попробуй после EHLO или HELO написать какое-нибудь слово (это имя пользователя):
EHLO user
HELO server
и т.п.
[…] SMTP: отправка писем с авторизацией своими руками Рубрика: PHP | Отзывы (RSS) | Трекбек […]
EHLO smtp.mail.ru - после EHLO надо писать адрес smtp-сервера, а не логин или другое слово. Тогда всё ОК.
Ещё надо добавить:
$message = iconv(“cp1251″,”KOI8-R”,$message);
$message = “Content-Type: text/plain; charset=\”koi8-r\”\r\nContent-Transfer-Encoding: 8bit\r\n\r\n”.$message;
$subject=base64_encode(iconv(“cp1251″,”KOI8-R”,$subject));
$subject=”=?KOI8-R?B?{$subject}?=”;
Иначе письма приходят в кривой кодировке.
Да, тут зависит от того, на каком языке отправляем письмо. Если на русском, то конечно кодировать нужно.
Вопрос - а как отправить сразу несколько писем? Например в копиях?
Для этого нужно добавить заголовок “cc:”. В примере к данному посту - в переменную $message. Ищите более подробную информацию о заголовках электронных писем в гугле 😉
Помгите люди, не понимаю, какой программой открыть этот скрипт у себя на компьютере? Эксплоэром не получается, Дреамвеамером можно, но не понятно как с эотго кода письма отправлять…
И можно ли этим скрпитом скажем отправить 10 писем за раз?
Ответься пожалуйстона е маил
Всем кто неполенится написать, скину на е маил кое что очнеь интерененькое )
Игорь, этот скрипт должен исполняться на стороне сервера и установленным как минимум php.
Вопрос к автору, не знаете ли Вы безопасных онлайн сервисов для кодирования и декодирования в base64 т.к. не всегда под рукой есть php.
Петро, поищите в гугле 😉
Хорошая статья, очень помогла. Однако заработало не сразу, а как написал дядя Миша, вместо $login подставил имя сервера в строке EHLO. Без этого выдавал ошибку “Domain not found”.
Всё работает, спасибо автору и комментам дяди Миши
Вот только один вопрос: мне нужно к примеру отправить не 1 а 2 письма - подтверждение юзеру и текст себе. Было просто 2 ф-ции mail, а как тут ? 2 раза одно и тоже с разными переменными ?
Можно и так. Но лучше использовать заголовок “Cc:”, отвечающий за копии.
Да нет, не копии !
Тут 2 разных переменных $to и 2 разных $message !
Одна с благодарностями для кл-та, другая мне с текстом из формы. НЕ КОПИИ !!!
Тогда конечно это будут две разные функции для отправки.
Я посмотрел логи клиента(The Bat) и там была комманда: EHLO [мой ип], так что я думаю, что вместо ‘EHLO ‘ . $login, надо прописать ‘EHLO ‘ . $_SERVER [‘REMOTE_ADDR’]
Fatal error: Call to undefined function socket_create() in W:\home\exp\www\example_smtp.php on line 21
выдало такую ошибку.. вероятно мой пхп5 не понимает функций socket_create а также как мне кажется когда дело пойдет дальше то и функций socket_read и socket_write.
Хотя есть аналогичные команды которые у меня работают соответственно: fsockopen, fgets и fputs, попробую заменить ими.
А что такое Сс? Я в этом первый раз разбираюсь, но очень надо. Поделитесь пожалуйста!
А может быть можно для рассылки копий на разные email адреса пропустить это все через do - while ? есле не прав не ругайте я учусь
Подскажите пожалуйста!
Если в тексте сообщения попадается двоеточие
$message = ‘Бла-бла-бла : бла-бла-бла'; // текст сообщения
, то письмо приходит пустым (текста сообщения нет вообще). Как побороть?
Подскажите пожалуйста!
Если в тексте сообщения попадается двоеточие
$message = ‘Бла-бла-бла : бла-бла-бла'; // текст сообщения, то письмо приходит пустым (текста сообщения нет вообще). Как побороть?
> А может быть можно для рассылки копий на разные email адреса пропустить это все через do – while ? есле не прав не ругайте я учусь
Конечно можно, и даже нужно. Коннектимся и здороваемся )), отправляем все письма по очереди, прощаем и дисконнектимся. Это значительно эффективнее, и поэтому те, кто отправляет много писем приходят к этому методу, вместо mail.
Хм, модератор, а очистку формы после отправки сообщения - вполне себе легко сделать 😉
> вероятно мой пхп5 не понимает функций socket_create
Не понимает, нужно устанавливать соответствующий модуль. Но скорее всего придётся воспользоваться другим методом отправки через sendmail, с помощью curl и т.п..
Здрасьте. Не получается подключится к smtp.yandex.ru:25: приконнектился, отправляю сообщение HELO, и пытаюсь прочитать ответ, секунд через 20, считывает ответ time exceeded.Не понимаю в чем проблема.
Уважаемый автор, позвольте сказать вам огромное человеческое спасибо! Целый день провел в поисках адекватного скрипта, наконец-то, благодаря вам, поиск увенчался успехом.
Спасибо автору прямо по буковке разложил что к чему
Хочу заметить, что адрес отправителя должен быть также в теле сообщения
$message = “From: ;\r\n”.$message;
Спасибо большое автору за скрипт!
у меня все отлично работает.
Есть вопрос по поводу самого письма. у меня в письме есть картинки, которые подгружаются с сервера. Как можно послать картинки в аттаче и в самом письме ссылаться уже на cid с аттача?
Пишет ошибку в 18 строке, что это может бытЬ?
Автору - большая благодарность! Три дня потратил бесплодно, а тут скрипт заработал с полоборота.
Если у вас линь, то почти наверняка sendmail ставится с пол-оборота. В этом случае, $address можно указать localhost (т.к. почтовым сервером теперь будет ваш копьютер), логины и пароли видимо, по умолчанию отключены, их убрать из скрипта, и готово.
Сам долго не мог понять, что же такое почтовый домен вашего провайдера (один “умник” в наверно, знакомой многим статье, выпендрился, и не написал это)
Какую команду надо добавить, чтобы можно было добавить Reply-to - отвечать на такой-то адрес?
У меня все работает спасибо. кучу сайтов перерыла, наконец здесь нашла
Спасибо автору и дяде Мише. Кто скажет как сделать чтоб можно было html теги отправлять, ну и почтовый сервис их понимал ) Заранее спасибо