Я так подозреваю, что среди вас найдется те, кто скажет, что этот пост боян и незачет. Действительно, описание сетевых протоколов появляется в интернете довольно часто, да и по запросу «описание протокола SMTP» в Google находится немало релевантных страниц.

Тем не менее, сегодня мне потребовалось на скорую руку написать скрипт отправки электронной почты с аттачем , а подходящих библиотек и документации под рукой не оказалось. Тут же началось диагональное чтение RFC, поиск по форумам, разыскивание старых исходных кодов, паника и нервы.

Потому я решил начать публиковать описания сетевых протоколов в этом блоге, чтобы в будущем они всегда были у меня под рукой. Так что готовьтесь — POP3 , IMAP, DNS, FTP , HTTP, IRC и о чем я там забыл — все на подходе (ссылки появляются по мере написания соответствующих постов).

А пока поговорим об SMTP. Если кто-то не знает или вдруг забыл, Simple Mail Transfer Protocol — это протокол, с помощью которого происходит отправка электронной почты в интернете на протяжении последних лет сорока. Полное его описание занимает почти два десятка документов RFC, и это как бы намекает, что протокол не прост. Однако по всей видимости здесь применимо правило 10/90 в нетрадиционной трактовке — «90% времени мы пользуемся частью протокола, описанной на 10% документации».

Для отправки письма воспользуемся почтовым сервером smtp.mail.ru и утилитой telnet, входящую в состав любой unixlike операционной системы.

$ telnet smtp.mail.ru 25
Trying 94.100.177.1…
Connected to smtp.mail.ru.
Escape character is ‘^]’.
220 mail.ru ESMTP Wed, 18 Nov 2009 20:44:13 +0300
HELO some.host.ru
250 mx30.mail.ru Hello some.host.ru [123.45.67.89]
AUTH LOGIN
334 VXNlcm5hbWU6
dXNlckBtYWlsLnJ1
334 UGFzc3dvcmQ6
cXdlcnR5
235 Authentication succeeded

Тут мы коннектимся к SMTP-серверу, он приветствует нас кодом 220, в ответ мы называем имя своего хоста HELO some.host.ru . Мы можем выдать себя за кого угодно, например за localhost , но не факт, что сервер нам поверит. Он может не полениться сделать resolve доменного имени и сравнить его с IP клиента, потому честность приветствуется.

Далее мы говорим, что хотели бы пройти аутентификацию ( AUTH LOGIN ). LOGIN — это тип аутентификации, по логину и паролю. Есть и другие способы, но они нас мало интересуют, особенно если вспомнить про существование SSL. Следом за этой командой мы шлем имя пользователя и пароль в кодировке Base64. В этом примере я использовал имя пользователя user@mail.ru и пароль qwerty . Если сервер отвечает нам кодом 235, значит аутентификация прошла успешно.

Тут следует обратить внимание на следующее.

  • Имя пользователя может не равняться его почтовому адресу. Я знаю, что на некоторых хостингах используется имя пользователя типа user#mail.ru. И вообще оно может быть любым и никак не связанным с почтовым адресом.
  • Аутентификация является необязательным шагом в протоколе. Есть много причин, по которым smtp-сервер должен обслуживать анонимных пользователей. Например, сервер должен принимать почту от других smtp-серверов, если он указан в MX-записи домена. Но об этом мы поговорим в посте о DNS.

Следующий шаг — непосредственно посылка сообщения. Тут есть два варианта, простой и сложный. Для начала разберем простой:

MAIL FROM: Test User <user@mail.ru>
250 OK
RCPT TO: Gmail User <user@gmail.com>
250 Accepted
DATA
354 Enter message, ending with «.» on a line by itself
From: Test User <user@mail.ru>
Subject: Hello, Gmail User!

How are you?
.
250 OK id=1NAof5-000BeN-00

Командами MAIL FROM и RCPT TO мы сообщаем серверу, кто является отправителем, а кто — получателем сообщения. Да, одной учетной записи может соответствовать много почтовых адресов, а если сервер неправильно настроен (или принимает письмо от другого SMTP сервера), мы можем с легкостью слать письма от чужого имени.

Затем мы говорим DATA , вводим текст письма и обозначаем его конец, введя одну точку. Вы спросите, что делать, если в тексте письма есть строка, содержащая одну точку? Все просто — ввести две точки.

Кроме текста сообщения в теле письма мы можем также указать (помимо прочего) тему сообщения (Subject) и отправителя (точно, еще раз!). Последнее, в частности, используется Gmail — см «Настройки → Аккаунты и импорт → Отправить сообщение с другого адреса».

Если все сделано правильно, сервер ответит нам 250 и сообщит id сообщения. Немного отступая от темы — а вы знали, что согласно спецификации SMTP, письмо может идти до получателя в течение 48 часов?

Но вернемся к нашим баранам. А точнее — второму, «сложному» способу отправки сообщения. Рассмотрим пример:

DATA
354 Enter message, ending with «.» on a line by itself
From: Test User <user@mail.ru>
Subject: Hello, Gmail User!
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=»aaaaaaaaaa»
—aaaaaaaaaa
Content-Type: text/html; charser=»koi8-r»

Привет, как твои дела?
—aaaaaaaaaa
Content-Type: application/octet-stream; name=»file.txt»
Content-Transfer-Encoding: base64

z/Do4uXyLCDs6PAh
—aaaaaaaaaa—
.
250 OK id=1NAof5-000BeN-00

Следующая строчка:

Content-Type: multipart/mixed; boundary=»aaaaaaaaaa»

говорит серверу о том, что сообщение состоит из частей, а в качестве разделителя используется строка из 10-и латинских букв а .

Дополнение: Еще одна интересная возможность MIME: Alternative Subtype .

Остальное, на мой взгляд, должно быть понятно. Сообщение состоит из двух частей — текста сообщения в формате HTML и кодировке KOI8-R и аттача в кодировке Base64. Отмечу только, что текст сообщения также может передаваться в Base64.

После отправки сообщения мы можем отправить еще несколько сообщений, после чего нам предстоит попрощаться:

QUIT
221 mx76.mail.ru closing connection
Connection closed by foreign host.

Да, кстати, любой ответ сервера может состоять из нескольких строк. Тогда он выглядит примерно так (см пояснения в описании протокола FTP ):

HELO some.host.name
250-mx.google.com at your service, [123.45.67.89]
SIZE 35651584
8BITMIME
AUTH LOGIN PLAIN
ENHANCEDSTATUSCODES
250 PIPELINING

Вот и все, что я хотел сегодня написать. Надеюсь, вы узнали что-нибудь новое или хотя бы освежили старые знания.

EnglishRussianUkrainian