Материал: ММиВА. Лабораторная работа 4

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

smtp_lib/smtp_lib.h

enum smtp_address_type

{

///Адрес отправителя.

SMTP_ADDRESS_FROM = 0,

///Адрес получателя.

SMTP_ADDRESS_TO = 1,

///Адрес получателя копии письма.

SMTP_ADDRESS_CC = 2,

/** Адрес получателя скрытой копии письма (BCC).

*Получатели не должны видеть ни один из адресов BCC в заголовке письма.

*Однако некоторые реализации SMTP-сервера могут копировать эту информацию

*в заголовок, поэтому она не всегда будет скрыта.

*Если адреса BCC строго не должны отображаться получателям, то в

*таком случае следует отправить по одному отдельному письму каждой

*стороне BCC. */

SMTP_ADDRESS_BCC = 3

};

/// Методы шифрования соединения (с шифрованием TLS / без шифрования). enum smtp_connection_security

{

/** STARTTLS. Сначала подключение без шифрования, затем установка

*зашифрованного соединения (команда STARTTLS). Обычно используется

*при подключении к почтовому серверу через порты 25 и 587. */

SMTP_SECURITY_STARTTLS = 0,

/** TLS. Подключение с шифрованием. Обычно используется при * подключении к почтовому серверу через порт 465. */

SMTP_SECURITY_TLS = 1,

/// Без шифрования. Не рекомендуется при нелокальном подключении.

SMTP_SECURITY_NONE = 2

};

/// Методы аутентификации учетной записи пользователя на сервере. enum smtp_authentication_method

{

/** Без аутентификации.

* Некоторые серверы поддерживают эту опцию при локальном подключении. */

SMTP_AUTH_NONE = 0,

///base64 аутентификация с использованием пользователя и пароля.

SMTP_AUTH_PLAIN = 1,

///base64 аутентификация, похожая на SMTP_AUTH_PLAIN.

SMTP_AUTH_LOGIN = 2

};

/// Специальные флаги контекста клиента SMTP. enum smtp_flag

{

/// Печать лога коммуникации клиента и сервера в поток stderr.

SMTP_DEBUG = 1,

/** Не проверять TLS сертификат.

*По умолчанию функция подтверждения TLS проверяет, истек ли

*срок действия сертификата или используется самозаверяющий сертификат.

*Любое из этих условий приведет к сбою соединения. Эта опция позволяет

*продолжить соединение, даже если эти проверки не пройдут. */

SMTP_NO_CERT_VERIFY = 2

};

/** Открывает соединение с SMTP-сервером и возвращает контекст.

* @param[in]

server Имя сервера или IP адрес.

 

* @param[in]

port Порт сервера.

 

* @param[in]

connection_security См. @ref smtp_connection_security.

* @param[in]

flags См. @ref smtp_flag.

 

* @param[in]

cafile Путь к файлу сертификата или NULL (NULL: сертификат в пути по умолчанию).

* @param[out]

smtp Указатель на новый контекст

SMTP.

*

По завершении вызывающая сторона должна освободить

*

этот контекст с помощью

@ref smtp_close.

* @return См. @ref smtp_status_code. */

enum smtp_status_code smtp_open(const char *const server, const char *const port, enum smtp_connection_security connection_security,

enum smtp_flag flags, const char *const cafile, struct smtp **smtp);

/**

*Аутентифицирует пользователя, используя один из методов, перечисленных в

*@ref smtp_authentication_method.

* @param[in] smtp

Контекст клиента

SMTP.

*

@param[in]

auth_method См. @ref smtp_authentication_method.

*

@param[in]

user

Имя пользователя

SMTP.

*@param[in] pass Пароль пользователя SMTP.

*@return См. @ref smtp_status_code. */

enum smtp_status_code smtp_auth(struct smtp *const smtp,

enum smtp_authentication_method auth_method, const char *const user, const char *const pass);

/** Отправляет электронное письмо с адресами, вложениями и заголовками, * определенными в текущем контексте SMTP.

21

smtp_lib/smtp_lib.h

*Перед этим вызывающая сторона должна вызвать функцию @ref smtp_open.

*Заголовок "Date" будет автоматически создан в этой функции,

*если он еще не был установлен с помощью @ref smtp_header_add.

*Если приложение переопределяет заголовок "Content-Type" по умолчанию,

*то эта функция будет выводить @p body в виде необработанных данных сразу под заголовками

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

*smtp_attachment_add_*.

*Другими словами, приложение должно создавать свои собственные разделы

*MIME (если необходимо) при переопределении заголовка Content-Type.

*@param[in] smtp Контекст клиента SMTP.

*@param[in] body Тело письма (строка с '\0' в конце).

*@return См. @ref smtp_status_code. */

enum smtp_status_code smtp_mail(struct smtp *const smtp, const char *const body);

/** Закрывает соединение SMTP и освобождает все ресурсы, удерживаемые контекстом SMTP.

*@param[in] smtp Контекст клиента SMTP.

*@return См. @ref smtp_status_code. */

enum smtp_status_code smtp_close(struct smtp *smtp);

/** Возвращает текущий код ошибки.

*@param[in] smtp Контекст клиента SMTP.

*@return См. @ref smtp_status_code. */

enum smtp_status_code smtp_status_code_get(const struct smtp *const smtp);

/** Очищает текущий код ошибки,

установленный в контексте клиента SMTP.

*

@param[in,out] smtp Контекст

клиента SMTP.

*

@return

Код предыдущей ошибки перед очисткой. */

enum smtp_status_code smtp_status_code_clear(struct smtp *const smtp);

/** Устанавливает код ошибки в контексте клиента SMTP и возвращает его же.

*Это позволяет вызывающей стороне очистить код ошибки до @ref SMTP_STATUS_OK ,

*чтобы предыдущие ошибки перестали распространяться. Однако это будет работать

*правильно только для всех ошибок до границы @ref SMTP_STATUS__LAST .

*Не сбрасывает код ошибки >= @ref SMTP_STATUS__LAST .

*@param[in] smtp Контекст клиента SMTP.

*@param[in] new_status_code См. @ref smtp_status_code.

*@return См. @ref smtp_status_code. */

enum smtp_status_code smtp_status_code_set(struct smtp *const smtp,

enum smtp_status_code new_status_code);

/** Преобразует код состояния клиента SMTP в описательную строку.

*@param[in] status_code Код состояния.

*@return Строка, содержащая описание @p status_code .

*Вызывающая сторона не должна освобождать или изменять эту строку. */ const char *smtp_status_code_errstr(enum smtp_status_code status_code);

/** Добавляет заголовок "ключ-значение" в список заголовков в контексте SMTP.

*При добавлении заголовка с существующим ключом он будет вставлен вместо

*замены существующего заголовка.

*@param[in] smtp Контекст клиента SMTP.

*@param[in] key Имя ключа для нового заголовка.

*

Оно должно

состоять

только из печатаемых

*

символов

US-ASCII, кроме двоеточия.

* @param[in] value Значение для

нового

заголовка.

*

Оно должно

состоять

только из печатаемых

*

символов

US-ASCII, пробела или горизонтальной табуляции.

*

Может быть

установлено в NULL.

* @return См. @ref smtp_status_code. */

enum smtp_status_code smtp_header_add(struct smtp *const smtp, const char *const key, const char *const value);

/** Освобождает всю память, связанную с заголовками сообщений электронной почты. * @param[in] smtp Контекст клиента SMTP. */

void smtp_header_clear_all(struct smtp *const smtp);

/** Добавляет адрес назначения FROM, TO, CC или BCC в контекст SMTP.

*Некоторые SMTP-серверы могут отклонять более 100 получателей.

*@param[in] smtp Контекст клиента SMTP.

*@param[in] type См. @ref smtp_address_type.

*@param[in] email Электронный адрес стороны.

*

Должен

состоять только

из печатаемых символов,

*

за

исключением угловых скобок (<) и (>).

* @param[in] name Название или описание стороны.

*

Должно

состоять только

из печатаемых символов,

*

за

исключением символов кавычек.

*

Может быть установлено

в NULL.

* @return См. @ref smtp_status_code. */

enum smtp_status_code smtp_address_add(struct smtp *const smtp, enum smtp_address_type type, const char *const email, const char *const name);

/** Освобождает всю память, относящуюся к списку адресов. * @param[in] smtp Контекст клиента SMTP. */

void smtp_address_clear_all(struct smtp *const smtp);

/** Добавляет вложение (файл) по пути.

22

smtp_lib/smtp_lib.h

*@param[in] smtp Контекст клиента SMTP.

*@param[in] name Имя файла вложения, отправляемого получателям.

*Должно состоять только из печатаемых символов ASCII,

*

за исключением кавычек (') и (").

*@param[in] path Путь к файлу.

*@return См. @ref smtp_status_code. */

enum smtp_status_code smtp_attachment_add_path(struct smtp *const smtp, const char *const name, const char *const path);

/** Добавляет вложение с помощью файлового указателя.

*@param[in] smtp Контекст клиента SMTP.

*@param[in] name Имя файла вложения, отправляемого получателям.

*Должно состоять только из печатаемых символов ASCII,

*

за исключением кавычек (') и (").

*@param[in] fp Уже открытый на чтение файловый указатель.

*@return См. @ref smtp_status_code. */

enum smtp_status_code smtp_attachment_add_fp(struct smtp *const smtp, const char *const name, FILE *fp);

/** Добавляет в SMTP-контекст MIME-вложение с данными, полученными из памяти.

*@param[in] smtp Контекст клиента SMTP.

*@param[in] name Имя файла вложения, отправляемого получателям.

*

Должно состоять только из

печатаемых

символов ASCII,

*

за исключением кавычек

(') и (").

 

*@param[in] data Данные вложения, хранящиеся в памяти.

*@param[in] datasz Число байт в @p data , или -1 если есть '\0' в @p data .

*@return См. @ref smtp_status_code. */

enum smtp_status_code smtp_attachment_add_mem(struct smtp *const smtp, const char *const name, const void *const data, size_t datasz);

/** Удаляет все вложения из контекста SMTP-клиента. * @param[in] smtp Контекст клиента SMTP. */

void smtp_attachment_clear_all(struct smtp *const smtp);

#endif // SMTP_LIB_H

Таблица 3. Файл SMTP-библиотеки «smtp_lib/smtp_lib.c»

smtp_lib/smtp_lib.c

/** @file

*@brief SMTP client library

*@author Kovalenko Leonid && James Humphrey

*@version 1.00

*

*Клиентская библиотека SMTP.

*Позволяет пользователю отправлять электронные письма на сервер SMTP.

*/

#if defined(_WIN32) || defined(WIN32) /** Если ОС Windows. */

#define SMTP_IS_WINDOWS #endif // SMTP_IS_WINDOWS

#ifdef SMTP_IS_WINDOWS #include <winsock2.h> #include <ws2tcpip.h> #else // POSIX #include <netdb.h>

#include <netinet/in.h> #include <sys/select.h> #include <sys/socket.h> #include <unistd.h> #endif // SMTP_IS_WINDOWS

#include <errno.h> #include <limits.h> #include <signal.h> #include <stdarg.h> #include <stdlib.h> #include <string.h> #include <time.h>

#ifdef SMTP_OPENSSL #include <openssl/bio.h> #include <openssl/err.h> #include <openssl/ssl.h> #include <openssl/x509.h> #include <openssl/x509v3.h> #endif // SMTP_OPENSSL

#include "smtp_lib.h"

23

smtp_lib/smtp_lib.c

/// Коды SMTP, возвращаемые сервером и анализируемые клиентом. enum smtp_result_code

{

///Внутренняя ошибка (код ошибки клиента, сервером не устанавливается).

SMTP_INTERNAL_ERROR = -1,

///Возвращается, когда готово начать обработку следующего шага.

SMTP_READY = 220,

///Возвращается в ответ на QUIT.

SMTP_CLOSE = 221,

///Возвращается, если клиент успешно аутентифицируется.

SMTP_AUTH_SUCCESS = 235,

///Возвращается после успешного завершения некоторых команд.

SMTP_DONE = 250,

/** Возвращается для некоторых механизмов многострочной аутентификации, * где этот код указывает следующий этап на этапе аутентификации. */

SMTP_AUTH_CONTINUE = 334,

///Возвращается в ответ на команду DATA.

SMTP_BEGIN_MAIL = 354

};

/** Используется для анализа ответов от SMTP-сервера.

*Например, если сервер отправляет обратно "250-STARTTLS",

*тогда code будет установлен в 250, more в 1, text в STARTTLS. */ struct smtp_command

{

///Код результата (целое число).

enum smtp_result_code code;

/** Указывает, последуют ли другие серверные команды.

*Будет установлен в 1, если четвертый символ в строке ответа содержит '-',

*иначе будет установлен в 0. */

int more;

/// Текст, отображаемый после кода состояния. const char *text;

};

/** Коды возврата для интерфейса getdelim,

*который позволяет вызывающей стороне проверить,

*могут ли быть обработаны другие строки с разделителями. */ enum str_getdelim_retcode

{

///Ошибка при обработке getdelim.

STRING_GETDELIMFD_ERROR = -1,

///Обнаружил новую строку и может обработать больше строк при следующем вызове.

STRING_GETDELIMFD_NEXT = 0,

///Обнаружил новую строку и в настоящее время не может читать больше строк.

STRING_GETDELIMFD_DONE = 1

};

/** Структура данных для буфера чтения и синтаксического анализа строк. * Помогает получить и проанализировать строки ответа сервера. */

struct str_getdelimfd

{

///Буфер чтения, который может включать байты после разделителя. char *_buf;

///Количество выделенных байтов в буфере чтения.

size_t _bufsz;

///Количество фактически сохраненных байтов в буфере чтения. size_t _buf_len;

///Текущая строка, содержащая текст до разделителя.

char *line;

///Количество хранимых байтов в строке line. size_t line_len;

/** Указатель функции на пользовательскую функцию чтения для интерфейса smtp_str_getdelimfd. Этот прототип функции имеет семантику, аналогичную функции чтения.

Параметр @p gdfd позволяет настраиваемой функции извлекать информацию user_data

из структуры str_getdelimfd, которая может содержать указатель файла, соединение сокета...

*/

long (*getdelimfd_read)(struct str_getdelimfd *const gdfd, void *buf, size_t count);

///Пользовательские данные, которые отправляются в функцию-обработчик чтения.

void *user_data;

///Разделитель символов. int delim;

///Заполнение для выравнивания. char pad[4];

};

/// Данные адреса электронной почты. struct smtp_address

{

///Электронный адрес без специального форматирования. Например, mail@example.com. char *email;

///Имя пользователя электронного адреса.

char *name;

/// Определяет FROM, TO, CC или BCC. enum smtp_address_type type;

24

smtp_lib/smtp_lib.c

/// Заполнение для выравнивания. char pad[4];

};

/// Данные вложения, которые помещаются в раздел MIME. struct smtp_attachment

{

///Имя файла вложения. char *name;

///Данные файла в кодировке base64. char *b64_data;

};

/// Данные заголовка письма. struct smtp_header

{

///Имя заголовка (список заголовков сортируется по имени заголовка). char *key;

///Содержимое соответствующего ключа заголовка.

char *value;

};

/// Основная структура данных, которая содержит контекст клиента SMTP. struct smtp

{

///Побитовый список флагов, управляющих поведением SMTP-клиента. enum smtp_flag flags;

///Описатель сокета.

int sock;

///Буфер чтения и структура синтаксического анализа строк. struct str_getdelimfd gdfd;

///Список заголовков письма.

struct smtp_header *header_list;

///Количество заголовков в header_list. size_t num_headers;

///Список адресов электронной почты FROM, TO, CC и BCC. struct smtp_address *address_list;

///Количество адресов в address_list.

size_t num_address;

/// Список вложений для отправки.

struct smtp_attachment *attachment_list;

///Количество вложений в списке вложений. size_t num_attachment;

/** Тайм-аут в секундах для ожидания перед возвратом с ошибкой.

* Это относится как к записи, так и к чтению из сетевого сокета. */ long timeout_sec;

///Код состояния, указывающий на успех / неудачу.

enum smtp_status_code status_code;

/** Указывает, есть ли в этом контексте активное соединение TLS.

*0 означает, что TLS-соединение неактивно.

*1 означает, что TLS-соединение активно. */ int tls_on;

/** Путь к файлу сертификата при использовании самозаверяющего или ненадежного сертификата,

*не находящегося в хранилище сертификатов по умолчанию. */

const char *cafile; #ifdef SMTP_OPENSSL

///OpenSSL TLS объект. SSL *tls;

///OpenSSL TLS контекст. SSL_CTX *tls_ctx;

///OpenSSL TLS I/O абстракция. BIO *tls_bio;

#endif // SMTP_OPENSSL };

/** Проверяет,

приведет ли

сложение значений size_t к переносу.

* @param[in]

a

Складывает это

значение

с

@p

b .

* @param[in]

b

Складывает это

значение

с

@p

a .

* @param[out]

result

Сохраняет результат сложения

в этот буфер.

*

 

Если

установлено значение

NULL, результат не записывается.

*@retval 1 Перенос совершен.

*@retval 0 Перенос не совершен. */

static int smtp_si_add_size_t(const size_t a, const size_t b, size_t *const result)

{

int wraps = (int)(SIZE_MAX - a < b);

if (result)

 

 

*result = a +

b;

return wraps;

 

}

 

 

/** Проверяет,

приведет ли вычитание значений size_t к заему.

* @param[in]

a

Вычитает из этого значения @p b .

* @param[in]

b

Вычитает это значение из @p a .

* @param[out]

result

Сохраняет результат вычитания в этот буфер.

*

 

Если установлено значение NULL, результат не записывается.

 

 

25