
7 семестр / Готовые отчеты / ММиВА. Лабораторная работа 4
.pdf
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 |

smtp_lib/smtp_lib.c
*@retval 1 Заем совершен.
*@retval 0 Заем не совершен. */
static int smtp_si_sub_size_t(const size_t a, const size_t b, size_t *const result)
{
int wraps = (int)(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, результат не записывается. |
*@retval 1 Перенос совершен.
*@retval 0 Перенос не совершен. */
static int smtp_si_mul_size_t(const size_t a, const size_t b, size_t *const result)
{
int wraps = (int)(b != 0 && a > SIZE_MAX / b); if (result)
*result = a * b; return wraps;
}
/** Ожидает, пока на стороне чтения сокета не станет доступно больше данных.
*@param[in] smtp Контекст клиента SMTP.
*@retval SMTP_STATUS_OK Если данные доступны для чтения на сокете.
*@retval SMTP_STATUS_RECV Если соединение прерывается до того,
* как в сокете появятся какие-либо данные. */
static enum smtp_status_code smtp_str_getdelimfd_read_timeout(struct smtp *const smtp)
{
fd_set readfds;
struct timeval timeout; FD_ZERO(&readfds); FD_SET(smtp->sock, &readfds); timeout.tv_sec = smtp->timeout_sec; timeout.tv_usec = 0;
if (select(smtp->sock + 1, &readfds, NULL, NULL, &timeout) < 1) return smtp_status_code_set(smtp, SMTP_STATUS_RECV);
return SMTP_STATUS_OK;
}
/** Эта функция вызывается интерфейсом smtp_str_getdelimfd, когда ему нужно прочитать больше данных.
*Читает, используя либо простое соединение сокета, если шифрование не включено,
*либо читает, используя OpenSSL, если у него есть активное соединение TLS.
* @param[in] |
gdfd |
См. @ref str_getdelimfd. |
||
* |
@param[out] |
buf |
Указатель на |
буфер для хранения прочитанных байтов. |
* |
@param[in] |
count |
Максимальное |
количество байтов для чтения. |
*@retval >=0 Количество прочитанных байтов.
*@retval -1 Не удалось прочитать из сокета. */
static long smtp_str_getdelimfd_read(struct str_getdelimfd *const gdfd, void *buf, size_t count)
{
struct smtp *smtp; long bytes_read = 0; smtp = gdfd->user_data;
if (smtp_str_getdelimfd_read_timeout(smtp) != SMTP_STATUS_OK) return -1;
if (smtp->tls_on)
{
#ifdef SMTP_OPENSSL do
{
/* Count никогда не будет иметь значение больше SMTP_GETDELIM_READ_SZ, поэтому мы можем безопасно преобразовать его в int. */
bytes_read = SSL_read(smtp->tls, buf, (int)count);
} while (bytes_read <= 0 && BIO_should_retry(smtp->tls_bio)); #endif // SMTP_OPENSSL
}
else
bytes_read = recv(smtp->sock, buf, count, 0); return bytes_read;
}
/**
* Возвращает расположение символа-разделителя в буфере поиска.
* @param[in] |
buf |
Буфер поиска, используемый для поиска разделителя. |
* @param[in] |
buf_len Количество байтов для поиска в buf. |
|
* @param[in] |
delim |
Разделитель для поиска в buf. |
* @param[out] |
delim_pos Если разделитель найден в buf, возвращает позицию разделителя |
|
* |
|
в этом параметре, в противном случае 0. |
* |
|
Этот аргумент не должен быть равен NULL. |
*@retval 1 Если символ-разделитель найден.
*@retval 0 Если символ-разделитель не найден. */
static int smtp_str_getdelimfd_search_delim(const char *const buf, size_t buf_len, int delim,
26

smtp_lib/smtp_lib.c
size_t *const delim_pos)
{
*delim_pos = 0;
for (size_t i = 0; i < buf_len; ++i) if (buf[i] == delim)
{
*delim_pos = i; return 1;
}
return 0;
}
/** Инициализирует внутренний строчный буфер.
*@param[in] gdfd См. @ref str_getdelimfd.
*@param[in] copy_len Количество байтов для копирования во внутренний строчный буфер.
* @retval 0 Данные успешно размещены и скопированы в буфер новой строки. * @retval -1 Не удалось выделить память для буфера новой строки. */
static int smtp_str_getdelimfd_set_line_and_buf(struct str_getdelimfd *const gdfd, size_t copy_len)
{
size_t copy_len_inc, nbytes_to_shift, new_buf_len; if (gdfd->line)
{
free(gdfd->line); gdfd->line = NULL;
}
if (smtp_si_add_size_t(copy_len, 1, ©_len_inc) || smtp_si_add_size_t((size_t)gdfd->_buf, copy_len_inc, NULL) || smtp_si_sub_size_t(gdfd->_buf_len, copy_len, &nbytes_to_shift) || (gdfd->line = calloc(1, copy_len_inc)) == NULL)
return -1;
memcpy(gdfd->line, gdfd->_buf, copy_len); gdfd->line_len = copy_len;
memmove(gdfd->_buf, gdfd->_buf + copy_len_inc, nbytes_to_shift); if (smtp_si_sub_size_t(nbytes_to_shift, 1, &new_buf_len) == 0)
gdfd->_buf_len = new_buf_len; return 0;
}
/** Освобождает память @p gdfd .
* @param[in] gdfd См. @ref str_getdelimfd. */
static void smtp_str_getdelimfd_free(struct str_getdelimfd *const gdfd)
{
free(gdfd->_buf); free(gdfd->line); gdfd->_buf = NULL; gdfd->_bufsz = 0; gdfd->_buf_len = 0; gdfd->line = NULL; gdfd->line_len = 0;
}
/** Освобождает память @p gdfd и возвращает код ошибки @ref STRING_GETDELIMFD_ERROR .
*@param[in] gdfd См. @ref str_getdelimfd.
*@return См. @ref str_getdelim_retcode. */
static enum str_getdelim_retcode smtp_str_getdelimfd_throw_error(struct str_getdelimfd *const gdfd)
{
smtp_str_getdelimfd_free(gdfd); return STRING_GETDELIMFD_ERROR;
}
/** Величина, на которую увеличивается буфер чтения, если разделитель не найден. */
#define SMTP_GETDELIM_READ_SZ 1000
/** Читает и анализирует строку с разделителями, используя настраиваемую функцию чтения.
*Этот интерфейс обрабатывает всю логику для расширения буфера, анализа разделителя в буфере и
*возврата каждой "строки" вызывающей стороне для обработки.
*@param[in] gdfd См. @ref str_getdelimfd.
*@return См. @ref str_getdelim_retcode. */
static enum str_getdelim_retcode smtp_str_getdelimfd(struct str_getdelimfd *const gdfd)
{
size_t delim_pos, buf_sz_remaining, buf_sz_new; long bytes_read = -1;
void *read_buf_ptr; char *buf_new;
if (gdfd->getdelimfd_read == NULL) return STRING_GETDELIMFD_ERROR;
while (1)
{
if (smtp_str_getdelimfd_search_delim(gdfd->_buf, gdfd->_buf_len, gdfd->delim, &delim_pos))
{
if (smtp_str_getdelimfd_set_line_and_buf(gdfd, delim_pos) < 0) return smtp_str_getdelimfd_throw_error(gdfd);
return STRING_GETDELIMFD_NEXT;
}
else if (bytes_read == 0)
27

smtp_lib/smtp_lib.c
{
if (smtp_str_getdelimfd_set_line_and_buf(gdfd, gdfd->_buf_len) < 0) return smtp_str_getdelimfd_throw_error(gdfd);
return STRING_GETDELIMFD_DONE;
}
if (smtp_si_sub_size_t(gdfd->_bufsz, gdfd->_buf_len, &buf_sz_remaining)) return smtp_str_getdelimfd_throw_error(gdfd);
if (buf_sz_remaining < SMTP_GETDELIM_READ_SZ)
{
if (smtp_si_add_size_t(buf_sz_remaining, SMTP_GETDELIM_READ_SZ, &buf_sz_new)) return smtp_str_getdelimfd_throw_error(gdfd);
buf_new = realloc(gdfd->_buf, buf_sz_new); if (buf_new == NULL)
return smtp_str_getdelimfd_throw_error(gdfd); gdfd->_buf = buf_new;
gdfd->_bufsz = buf_sz_new;
}
if (smtp_si_add_size_t((size_t)gdfd->_buf, gdfd->_buf_len, NULL)) return smtp_str_getdelimfd_throw_error(gdfd);
read_buf_ptr = gdfd->_buf + gdfd->_buf_len;
bytes_read = (*gdfd->getdelimfd_read)(gdfd, read_buf_ptr, SMTP_GETDELIM_READ_SZ); if (bytes_read < 0 ||
smtp_si_add_size_t(gdfd->_buf_len, (size_t)bytes_read, &gdfd->_buf_len)) return smtp_str_getdelimfd_throw_error(gdfd);
}
}
/** Копирует строку @p s2 в @p s1 .
*Эта функция ведет себя почти аналогично функции strcpy(), но
*возвращает указатель на конец скопированного буфера в целевой строке.
*Эта функция всегда добавляет '\0' в конец строки.
*@param[in] s1 Целевая строка.
*@param[in] s2 Строка с завершающим нулем для копирования в @p s1 .
*@return Указатель на место в @p s1 после последнего скопированного байта. */ static char *smtp_stpcpy(char *s1, const char *s2)
{
size_t i = 0; do
{
s1[i] = s2[i];
} while (s2[i++] != '\0'); return &s1[i - 1];
}
/** Перераспределяет память с проверкой беззнакового переполнения.
*@param[in] ptr Существующий буфер выделения или NULL при выделении нового буфера.
*@param[in] nmemb Количество выделяемых элементов.
*@param[in] size Размер каждого элемента в @p nmemb .
*@retval void* Указатель на перераспределенный буфер, содержащий
*@p nmemb * @p size bytes.
*@retval NULL Не удалось перераспределить память. */
static void *smtp_reallocarray(void *ptr, size_t nmemb, size_t size)
{
void *alloc; size_t size_mul;
if (smtp_si_mul_size_t(nmemb, size, &size_mul)) alloc = NULL, errno = ENOMEM;
else
alloc = realloc(ptr, size_mul); return alloc;
}
/** Копирует строку в новый динамически выделяемый буфер.
*Возвращает динамически выделяемую строку с тем же содержимым, что и входная строка.
*По завершении вызывающая сторона должна освободить возвращенную строку.
* @param[in] s Строка для дублирования.
*@retval char* Указатель на новую динамически выделяемую строку, дублированную из @p s .
*@retval NULL Не удалось выделить память для новой строки. */
static char *smtp_strdup(const char *s)
{
if (s != NULL)
{
char *dup;
size_t dup_len, slen = strlen(s);
if (smtp_si_add_size_t(slen, 1, &dup_len)) dup = NULL, errno = ENOMEM;
else if ((dup = malloc(dup_len)) != NULL) memcpy(dup, s, dup_len);
return dup;
}
return NULL;
}
/** Находит все подстроки в строке и заменяет каждое вхождение строкой замены. * @param[in] search Подстрока для поиска в @p s .
28

smtp_lib/smtp_lib.c
*@param[in] replace Строка, на которую заменяется @p search .
*@param[in] s Строка с '\0' для поиска и замены.
*@retval char* Динамически выделяемая строка с замененными вхождениями, как описано выше.
*По завершении вызывающая сторона должна освободить выделенную память.
*@retval NULL Ошибка выделения памяти. */
static char *smtp_str_replace(const char *const search, const char *const replace, const char *const s)
{
size_t search_len = strlen(search), replace_len = strlen(replace), replace_len_inc, slen = strlen(s), slen_inc, s_idx = 0, snew_len = 0, snew_len_inc, snew_sz = 0, snew_sz_dup, snew_sz_plus_slen, snew_replace_len_inc;
char *snew = NULL, *stmp;
if (smtp_si_add_size_t(replace_len, 1, &replace_len_inc) || smtp_si_add_size_t(slen, 1, &slen_inc))
return NULL; if (s[0] == '\0')
return smtp_strdup(""); else if (search_len < 1)
return smtp_strdup(s); while (s[s_idx])
{
if (smtp_si_add_size_t(snew_len, 1, &snew_len_inc) || smtp_si_add_size_t(snew_sz, snew_sz, &snew_sz_dup) || smtp_si_add_size_t(snew_sz, slen, &snew_sz_plus_slen))
{
free(snew); return NULL;
}
if (strncmp(&s[s_idx], search, search_len) == 0)
{
if (smtp_si_add_size_t(snew_len, replace_len_inc, &snew_replace_len_inc))
{
free(snew); return NULL;
}
if (snew_replace_len_inc >= snew_sz)
{
// snew_sz += snew_sz + slen + replace_len + 1.
if (smtp_si_add_size_t(snew_sz_dup, slen_inc, &snew_sz) || smtp_si_add_size_t(snew_sz, replace_len, &snew_sz) || (stmp = realloc(snew, snew_sz)) == NULL)
{
free(snew); return NULL;
}
snew = stmp;
}
memcpy(&snew[snew_len], replace, replace_len); snew_len += replace_len;
s_idx += search_len;
}
else
{
if (snew_len_inc >= snew_sz)
{
// snew_sz += snew_sz + slen + snew_len + 1.
if (smtp_si_add_size_t(snew_sz_dup, slen, &snew_sz) || smtp_si_add_size_t(snew_sz, snew_len_inc, &snew_sz) || (stmp = realloc(snew, snew_sz)) == NULL)
{
free(snew); return NULL;
}
snew = stmp;
}
snew[snew_len] = s[s_idx]; ++s_idx;
snew_len = snew_len_inc;
}
}
snew[snew_len] = '\0'; return snew;
}
/** Таблица поиска, используемая для кодирования данных в base64.
*base64 берет шесть бит данных и кодирует эти биты с помощью этой таблицы.
*Поскольку 2^6 = 64, этот массив имеет 64 записи, которые напрямую отображаются
*из 6-битного значения в соответствующее значение массива. */
static char g_base64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/** Кодирует двоичные данные в строку base64.
*@param[in] buf Двоичные данные для кодирования в base64.
*@param[in] buflen Количество байтов в параметре @p buf или -1, если завершается '\0'.
*@retval char* Динамически выделяемая строка в кодировке base64.
29

smtp_lib/smtp_lib.c
*По завершении вызывающая сторона должна освободить эту строку.
*@retval NULL Ошибка выделения памяти. */
static char *smtp_base64_encode(const char *const buf, size_t buflen)
{
char *b64;
size_t b64_sz, buf_i = 0, b64_i = 0, remaining_block_sz, buf_block_sz; if (buflen == SIZE_MAX)
buflen = strlen(buf);
if (smtp_si_mul_size_t(buflen, 4, NULL)) return NULL;
/* base64 увеличивает размер на 33%
+1 -- для округления в большую сторону
+2 -- для заполнения '='
+1 -- '\0' */
b64_sz = (4 * buflen / 3) + 1 + 2 + 1; if ((b64 = calloc(1, b64_sz)) == NULL)
return NULL; if (buflen == 0)
return b64; remaining_block_sz = buflen; while (remaining_block_sz > 0)
{
unsigned char inb[3] = {0}, in_idx[4] = {0}; char outb[5] = {'=', '=', '=', '=', '\0'};
buf_block_sz = (remaining_block_sz >= 3) ? 3 : remaining_block_sz; memcpy(inb, &buf[buf_i], buf_block_sz);
in_idx[0] = ((inb[0] >> 2)) & 0x3F;
in_idx[1] = ((inb[0] << 4) | ((inb[1] >> 4) & 0xF)) & 0x3F; in_idx[2] = ((inb[1] << 2) | ((inb[2] >> 6) & 0x3)) & 0x3F; in_idx[3] = ((inb[2])) & 0x3F;
for (size_t i = 0; i < 4; i++)
{
if (i < buf_block_sz + 1)
outb[i] = g_base64_encode_table[in_idx[i]]; b64[b64_i + i] = outb[i];
}
buf_i += 3; b64_i += 4;
remaining_block_sz -= buf_block_sz;
}
return b64;
}
/** Возвращает длину символа UTF-8 в байтах.
*Функция предполагает, что пользователь предоставляет допустимую последовательность байтов UTF-8.
*Функция получает длину из первого байта в последовательности и не проверяет никакие другие байты.
*@param[in] c Первый байт в допустимой последовательности символов UTF-8.
*@retval >0 Количество байтов для текущей последовательности символов UTF-8.
*@retval -1 Неверная последовательность байтов. */
static size_t smtp_utf8_charlen(char c)
{
unsigned char uc = (unsigned char)c; if ((uc & 0x80) == 0)
return 1; /* 0XXXXXXX */ else if ((uc & 0xE0) == 0xC0)
return 2; /* 110XXXXX */ else if ((uc & 0xF0) == 0xE0)
return 3; /* 1110XXXX */ else if ((uc & 0xF8) == 0xF0)
return 4; /* 11110XXX */
else
return 0; /* invalid */
}
/** Проверяет, содержит ли строка не-ASCII UTF-8 символы.
*Использует простой алгоритм из @ref smtp_utf8_charlen для проверки символов UTF-8,
*отличных от ASCII.
*@param[in] s Строка UTF-8.
*@retval 1 Строка содержит символы UTF-8, отличные от ASCII.
*@retval 0 Строка содержит только символы ASCII. */
static int smtp_str_has_nonascii_utf8(const char *const s)
{
for (size_t i = 0; s[i]; ++i)
{
size_t charlen = smtp_utf8_charlen(s[i]); if (charlen != 1)
return 1;
}
return 0;
}
/** Получает число байтов в строке UTF-8 или более короткое число, если строка превышает
*максимальную указанную длину.
*@param[in] s Строка UTF-8 с '\0'.
*@param[in] maxlen Не проверяет более @p maxlen байтов строки @p s , кроме случаев, когда они
30