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>S:</b> 220 novicemachine Microsoft ESMTP MAIL Service, Version: 6.0.3790.1830 ready at Mon, 14 Jul 12:02:42 +0400<br/>
<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-сервера)

Теперь приведем пример с авторизацией:

<b>S:</b> 220 novicemachine Microsoft ESMTP MAIL Service, Version: 6.0.3790.1830 ready at
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));
    }
?>

У меня при запуске скрипт выдал:

Connect to 'localhost:25' ... OK<br/>
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





Читайте также:



37 Ответов на “SMTP: Отправка писем с авторизацией своими руками”

  1. Ха, оч интересно :) . Вот только телнетиться к smtp.mail.ru че-то не получается. Нужно у себя на линуксе подымать смтп :) .

  2. Skill00 коннектиться не получается или работать?
    У меня коннектится, но нафиг посылает

    user@user-desktop:~$ telnet smtp.mail.ru 25
    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:~$
  3. novice

    2 VolCh: не коннектится из-за команды EHLO и HELO.

    Попробуй после EHLO или HELO написать какое-нибудь слово (это имя пользователя):

    EHLO user
    HELO server
    и т.п.

  4. […] SMTP: отправка писем с авторизацией своими руками Рубрика: PHP  |  Отзывы (RSS)  |  Трекбек […]

  5. EHLO smtp.mail.ru - после EHLO надо писать адрес smtp-сервера, а не логин или другое слово. Тогда всё ОК.

  6. Ещё надо добавить:

    $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}?=”;

    Иначе письма приходят в кривой кодировке.

  7. novice

    Да, тут зависит от того, на каком языке отправляем письмо. Если на русском, то конечно кодировать нужно.

  8. Игорь

    Вопрос - а как отправить сразу несколько писем? Например в копиях?

  9. novice

    Для этого нужно добавить заголовок “cc:”. В примере к данному посту - в переменную $message. Ищите более подробную информацию о заголовках электронных писем в гугле 😉

  10. Игорь

    Помгите люди, не понимаю, какой программой открыть этот скрипт у себя на компьютере? Эксплоэром не получается, Дреамвеамером можно, но не понятно как с эотго кода письма отправлять…
    И можно ли этим скрпитом скажем отправить 10 писем за раз?
    Ответься пожалуйстона е маил
    Всем кто неполенится написать, скину на е маил кое что очнеь интерененькое )

  11. Игорь, этот скрипт должен исполняться на стороне сервера и установленным как минимум php.

    Вопрос к автору, не знаете ли Вы безопасных онлайн сервисов для кодирования и декодирования в base64 т.к. не всегда под рукой есть php.

  12. novice

    Петро, поищите в гугле 😉

  13. Хорошая статья, очень помогла. Однако заработало не сразу, а как написал дядя Миша, вместо $login подставил имя сервера в строке EHLO. Без этого выдавал ошибку “Domain not found”.

  14. Alexx

    Всё работает, спасибо автору и комментам дяди Миши :)

    Вот только один вопрос: мне нужно к примеру отправить не 1 а 2 письма - подтверждение юзеру и текст себе. Было просто 2 ф-ции mail, а как тут ? 2 раза одно и тоже с разными переменными ?

  15. novice

    Можно и так. Но лучше использовать заголовок “Cc:”, отвечающий за копии.

  16. Alexx

    Да нет, не копии !
    Тут 2 разных переменных $to и 2 разных $message !
    Одна с благодарностями для кл-та, другая мне с текстом из формы. НЕ КОПИИ !!!

  17. novice

    Тогда конечно это будут две разные функции для отправки.

  18. Я посмотрел логи клиента(The Bat) и там была комманда: EHLO [мой ип], так что я думаю, что вместо ‘EHLO ‘ . $login, надо прописать ‘EHLO ‘ . $_SERVER [‘REMOTE_ADDR’]

  19. maxim


    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, попробую заменить ими.

  20. Борис

    А что такое Сс? Я в этом первый раз разбираюсь, но очень надо. Поделитесь пожалуйста!

  21. Борис

    А может быть можно для рассылки копий на разные email адреса пропустить это все через do - while ? есле не прав не ругайте я учусь :)

  22. Подскажите пожалуйста!
    Если в тексте сообщения попадается двоеточие

    $message = ‘Бла-бла-бла : бла-бла-бла'; // текст сообщения

    , то письмо приходит пустым (текста сообщения нет вообще). Как побороть?

  23. Подскажите пожалуйста!
    Если в тексте сообщения попадается двоеточие
    $message = ‘Бла-бла-бла : бла-бла-бла'; // текст сообщения, то письмо приходит пустым (текста сообщения нет вообще). Как побороть?

  24. > А может быть можно для рассылки копий на разные email адреса пропустить это все через do – while ? есле не прав не ругайте я учусь

    Конечно можно, и даже нужно. Коннектимся и здороваемся )), отправляем все письма по очереди, прощаем и дисконнектимся. Это значительно эффективнее, и поэтому те, кто отправляет много писем приходят к этому методу, вместо mail.

  25. Хм, модератор, а очистку формы после отправки сообщения - вполне себе легко сделать 😉

  26. > вероятно мой пхп5 не понимает функций socket_create

    Не понимает, нужно устанавливать соответствующий модуль. Но скорее всего придётся воспользоваться другим методом отправки через sendmail, с помощью curl и т.п..

  27. Рустам

    Здрасьте. Не получается подключится к smtp.yandex.ru:25: приконнектился, отправляю сообщение HELO, и пытаюсь прочитать ответ, секунд через 20, считывает ответ time exceeded.Не понимаю в чем проблема.

  28. Арсен

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

  29. Спасибо автору прямо по буковке разложил что к чему

  30. stim

    Хочу заметить, что адрес отправителя должен быть также в теле сообщения

    $message = “From: ;\r\n”.$message;

  31. Настя

    Спасибо большое автору за скрипт!
    у меня все отлично работает.
    Есть вопрос по поводу самого письма. у меня в письме есть картинки, которые подгружаются с сервера. Как можно послать картинки в аттаче и в самом письме ссылаться уже на cid с аттача?

  32. Илюша

    Пишет ошибку в 18 строке, что это может бытЬ?

  33. Сергей

    Автору - большая благодарность! Три дня потратил бесплодно, а тут скрипт заработал с полоборота. :)

  34. Данил

    Если у вас линь, то почти наверняка sendmail ставится с пол-оборота. В этом случае, $address можно указать localhost (т.к. почтовым сервером теперь будет ваш копьютер), логины и пароли видимо, по умолчанию отключены, их убрать из скрипта, и готово.
    Сам долго не мог понять, что же такое почтовый домен вашего провайдера (один “умник” в наверно, знакомой многим статье, выпендрился, и не написал это)

  35. Какую команду надо добавить, чтобы можно было добавить Reply-to - отвечать на такой-то адрес?

  36. Светлана

    У меня все работает спасибо. кучу сайтов перерыла, наконец здесь нашла

  37. Сергей

    Спасибо автору и дяде Мише. Кто скажет как сделать чтоб можно было html теги отправлять, ну и почтовый сервис их понимал ) Заранее спасибо


© Copyright. . I-Novice. All Rights Reserved. Terms | Site Map