Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

7 семестр / Готовые отчеты / ММиВА. Лабораторная работа 4

.pdf
Скачиваний:
22
Добавлен:
23.12.2021
Размер:
3.5 Mб
Скачать

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, &copy_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