Blog. Just Blog

Отправка писем через SMTP-сервер на Ассемблере

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Отправка писем через SMTP-сервер на Ассемблере
Отправка писем через SMTP-сервер на Ассемблере

Простое обращение к стороннему серверу мы недавно попробовали, теперь можно сделать что-нибудь поинтереснее и посложнее. В этой статье я расскажу, как отправлять почту через SMTP-сервер с авторизацией с помощью Ассемблера.

Перед тем, как отправлять почту, надо указать SMTP-сервер, порт подключения (обычно 25), логин и пароль для доступа к ящику отправителя, адрес получателя и текст письма. В статье они все заполнены тестовыми значениями, в реальной программе все должно быть заполнено корректными данными. Само тело письма, скорее всего, тоже будет формироваться динамически, возможно даже с аттачами, но это уже выходит за рамки статьи. Проще всего открыть заголовки любого электронного письма в вашем почтовом клиенте и посмотреть, как и из каких частей оно складывается. Команды для передачи SMTP-серверу менять не надо, они остаются как есть.
  1. ; Адрес SMTP-сервера
  2. smtp    db 'mail.example.test',0
  3. ; Порт
  4. port    db 25
  5. ; Логин от почтового ящика
  6. login   db 'email@example.test',0
  7. ; Пароль от почтового ящика
  8. passw   db 'supa-dupa-password',0
  9. ; От кого
  10. sfrom   db 'email@example.test',0
  11. ; Кому
  12. sto     db 'my_dear_friend@another.domain',0
  13. ; Тело письма
  14. message db 'To: My Dear Friend <my_dear_friend@another.domain>',13,10
  15.         db 'Subject: Test message',13,10
  16.         db 'Hello from FASM!',0
  17.  
  18. ; Данные для подключения к серверу
  19. wsadata WSADATA
  20. saddr   sockaddr_in
  21. hSocket dd ?
  22.  
  23. ; Массивы для принятых и отправленных данных
  24. s_data  rb 100h
  25. r_data  rb 100h
  26. ; Вспомогательный буфер для разных операций
  27. buff    rb 100h
  28.  
  29. IPPROTO_TCP  = 6
  30. SOCKET_ERROR = -1
Для "общения" с SMTP-сервером используются следующие команды. Это не все команды, которые поддерживаются протоколом, а их минимально необходимый список.
  1. ; Команды для передачи SMTP-серверу
  2. szEHLO  db 'EHLO localhost',13,10,0
  3. szAUTH  db 'AUTH LOGIN',13,10,0
  4. szLINE  db '%s',13,10,0
  5. szFROM  db 'MAIL FROM:<%s>',13,10,0
  6. szTO    db 'RCPT TO:<%s>',13,10,0
  7. szDATA  db 'DATA',13,10,0
  8. szEOD   db 13,10,'.',13,10,0
  9. szQUIT  db 'QUIT',13,10,0
А вот и сам код отправки письма. Он в основном прокомментирован, поясню лишь некоторые моменты. Проверка ответа сервера выполняется по первому символу. Этого вполне достаточно, чтобы отловить все 500-е и 400-е ошибки сервера, а также 200-е коды успешных ответов. На моей практике более детальный разбор ни разу не понадобился. В тестовом примере отправка выполняется только одному адресату, в реальной ситуации получателей может быть больше. В этом случае код отправки команды RCPT TO должен быть выполнен для каждого адреса реципиента. Обычно SMTP-сервер требует авторизации, но есть и такие, где авторизация не требуется. В этом случае замените значения логина и пароля пустыми строками.
  1. ;----------------------------------------------------------------
  2. ; Отправка письма через SMTP-сервер
  3. ;----------------------------------------------------------------
  4.         invoke  WSAStartup,0101h,wsadata
  5.  
  6.         invoke  gethostbyname,smtp
  7.         or      eax,eax
  8.         jz      loc_ret
  9.  
  10.         mov     eax,[eax+hostent.h_addr_list]
  11.         mov     eax,[eax]
  12.         mov     eax,[eax]
  13. @@:
  14.         mov     [saddr.sin_addr],eax
  15.         mov     al,00
  16.         mov     ah,[port]
  17.         mov     [saddr.sin_port],ax
  18.         mov     [saddr.sin_family],AF_INET
  19.  
  20.         ; Создать сокет
  21.         invoke  socket,AF_INET,SOCK_STREAM,IPPROTO_TCP
  22.         cmp     eax,SOCKET_ERROR
  23.         jne     @f
  24.  
  25.         invoke  WSACleanup
  26.         jmp     loc_ret
  27. @@:
  28.         mov     [hSocket], eax
  29.  
  30.         ; Соединение с SMTP-сервером
  31.         invoke  connect,[hSocket],saddr,sizeof.sockaddr_in
  32.         cmp     eax,SOCKET_ERROR
  33.         jne     @f
  34.  
  35.         invoke  closesocket,[hSocket]
  36.         invoke  WSACleanup
  37.         jmp     loc_ret
  38. @@:
  39.         invoke  RtlZeroMemory,r_data,100h
  40.  
  41.         ; Получить данные после соединения
  42.         invoke  recv,[hSocket],r_data,100h,0
  43.         cmp     eax,SOCKET_ERROR
  44.         je      loc_close_socket
  45.         invoke  lstrcat,szLog,r_data
  46.         ; После соединения сервер может вернуть
  47.         ; код 2xx или не вернуть ничего
  48.         cmp     byte [r_data],0
  49.         je      @f
  50.         cmp     byte [r_data],'2'
  51.         jne     loc_close_socket
  52. @@:
  53.         ; EHLO
  54.         stdcall send_string,szEHLO
  55.         ; Проверить ответ сервера
  56.         or      eax,eax
  57.         jz      loc_close_socket
  58.         cmp     byte [r_data],'5'
  59.         je      loc_close_smtp
  60.         cmp     byte [r_data],'4'
  61.         je      loc_close_smtp
  62.  
  63.         ; Если не задан логин и/или пароль, то
  64.         ; считаем, что сервер не использует авторизацию
  65.         cmp     byte [login],0
  66.         je      loc_no_auth
  67.         cmp     byte [passw],0
  68.         je      loc_no_auth
  69.  
  70.         ; AUTH LOGIN
  71.         stdcall send_string,szAUTH
  72.         ; Проверить ответ сервера
  73.         or      eax,eax
  74.         jz      loc_close_socket
  75.         cmp     byte [r_data],'5'
  76.         je      loc_close_smtp
  77.         cmp     byte [r_data],'4'
  78.         je      loc_close_smtp
  79.  
  80.         ; -> login в base64
  81.         stdcall base64_encode,login,buff
  82.         invoke  wsprintf,s_data,szLINE,buff
  83.         add     esp,12
  84.         stdcall send_string,s_data
  85.         ; Проверить ответ сервера
  86.         or      eax,eax
  87.         jz      loc_close_socket
  88.         cmp     byte [r_data],'5'
  89.         je      loc_close_smtp
  90.         cmp     byte [r_data],'4'
  91.         je      loc_close_smtp
  92.  
  93.         ; -> password в base64
  94.         stdcall base64_encode,passw,buff
  95.         invoke  wsprintf,s_data,szLINE,buff
  96.         add     esp,12
  97.         stdcall send_string,s_data
  98.         ; Проверить ответ сервера
  99.         or      eax,eax
  100.         jz      loc_close_socket
  101.         cmp     byte [r_data],'2'
  102.         jne     loc_close_smtp
  103.  
  104. loc_no_auth:
  105.         ; MAIL FROM
  106.         invoke  wsprintf,s_data,szFROM,sfrom
  107.         add     esp,12
  108.         stdcall send_string,s_data
  109.         ; Проверить ответ сервера
  110.         or      eax,eax
  111.         jz      loc_close_socket
  112.         cmp     byte [r_data],'2'
  113.         jne     loc_close_smtp
  114.  
  115.         ; RCPT TO
  116.         ; Если получателей несколько, то эта команда
  117.         ; передается для каждого адреса email отдельно
  118.         invoke  wsprintf,s_data,szTO,sto
  119.         add     esp,12
  120.         stdcall send_string,s_data
  121.         ; Проверить ответ сервера
  122.         or      eax,eax
  123.         jz      loc_close_socket
  124.         cmp     byte [r_data],'2'
  125.         jne     loc_close_smtp
  126.  
  127.         ; DATA
  128.         stdcall send_string,szDATA
  129.         ; Проверить ответ сервера
  130.         or      eax,eax
  131.         jz      loc_close_socket
  132.         cmp     byte [r_data],'5'
  133.         je      loc_close_smtp
  134.         cmp     byte [r_data],'4'
  135.         je      loc_close_smtp
  136.  
  137.         ; -> message
  138.         invoke  lstrcat,szLog,message
  139.         invoke  lstrlen,message
  140.         invoke  send,[hSocket],message,eax,0
  141.         cmp     eax,SOCKET_ERROR
  142.         je      loc_close_socket
  143.  
  144.         ; EOD
  145.         stdcall send_string,szEOD
  146.         ; Проверить ответ сервера
  147.         or      eax,eax
  148.         jz      loc_close_socket
  149.         cmp     byte [r_data],'2'
  150.         jne     loc_close_smtp
  151.  
  152. loc_close_smtp:
  153.         ; QUIT
  154.         stdcall send_string,szQUIT
  155.         ; Проверить ответ сервера
  156.         or      eax,eax
  157.         jz      loc_close_socket
  158.         cmp     byte [r_data],'2'
  159.         jne     loc_close_socket
  160.  
  161.         ; Сообщение успешно отправлено
  162.  
  163. loc_close_socket:
  164.         invoke  closesocket,[hSocket]
  165.         invoke  WSACleanup
Небольшая вспомогательная функция для отправки строки на сервер и получения результата. Можно обойтись и без нее, оставив все вызовы и проверки в основном листинге, но так код получается заметно компактнее.
  1. proc send_string szStr:DWORD
  2. locals
  3.         result dd ?
  4. endl
  5.         pusha
  6.  
  7.         ; По умолчанию результат - ошибка
  8.         mov     [result],0
  9.  
  10.         invoke  RtlZeroMemory,r_data,100h
  11.  
  12.         ; Добавить в лог передаваемые данные
  13.         invoke  lstrcat,szLog,[szStr]
  14.  
  15.         invoke  lstrlen,[szStr]
  16.         invoke  send, [hSocket], [szStr], eax, 0
  17.         cmp     eax,SOCKET_ERROR
  18.         je      @f
  19.         invoke  recv, [hSocket], r_data, 100h, 0
  20.         cmp     eax,SOCKET_ERROR
  21.         je      @f
  22.  
  23.         ; Добавить в лог полученный ответ
  24.         invoke  lstrcat,szLog,r_data
  25.  
  26.         ; Данные отправлены и получены успешно
  27.         mov     [result],1
  28. @@:
  29.         popa
  30.  
  31.         ; Вернуть результат
  32.         mov     eax,[result]
  33.         ret
  34. endp
Логин и пароль передается на сервер закодированным в виде строки base64, поэтому используется еще одна вспомогательная функция - перевод строки в base64. В исходнике она есть, тут я ее дублировать не буду.

В приложении исходный код программы, которая отправляет письмо через SMTP-сервер. Готового exe-шника нет, так как почтовый сервер, логины и пароли вы должны указать свои. После отправки письма будет показан полный лог "общения" с SMTP-сервером.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Send.Email.via.SMTP.Demo.zip (2,708 bytes)


Поделиться ссылкой ВКонтакте Поделиться ссылкой на Facebook Поделиться ссылкой на LiveJournal Поделиться ссылкой в Мой Круг Добавить в Мой мир Добавить на ЛиРу (Liveinternet) Добавить в закладки Memori Добавить в закладки Google
Просмотров: 1580 | Комментариев: 3

Метки: Assembler, сеть
Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
zdm (16.06.2016 в 21:42):
Да уж. Было бы очень интересно посмотреть на логику программы при всяких там STARTTLS и различных схемах авторизации кроме PLAIN и LOGIN.
ManHunter (13.04.2016 в 08:19):
wetal, корпоративная почта, например. Или почта на виртуальном хостинге.

Надо будет на досуге покурить мануалы по отправке через защищенное соединение.
wetal (13.04.2016 в 07:35):
Опоздали Вы с кодом лет эдак на пять!
Даже не знаю, на какой реально работающей почте это можно проверить? Сейчас обычно защита соединения SSL.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2017
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.06 сек. / MySQL: 2 (0.0053 сек.) / Память: 4.5 Mb
Наверх