
7 семестр / Готовые отчеты / ММиВА. Лабораторная работа 4
.pdf
|
smtp_lib/smtp_lib.c |
* |
находятся в середине многобайтового символа. |
* |
@retval strlen(s) Если длина |
@p s имеет меньше байтов, чем @p maxlen , или такое же количество |
* |
байтов, |
как @p maxlen . |
*@retval maxlen Если длина @p s имеет больше байтов, чем @p maxlen.
*@retval -1 Если @p s содержит недопустимую последовательность байтов UTF-8. */ static size_t smtp_strnlen_utf8(const char *s, size_t maxlen)
{
size_t i = 0;
for (size_t utf8_len; *s && i < maxlen; i += utf8_len)
{
utf8_len = smtp_utf8_charlen(*s); if (utf8_len == 0)
return SIZE_MAX;
for (size_t utf8_i = 0; utf8_i < utf8_len; ++utf8_i)
{
if (!*s)
return SIZE_MAX;
++s;
}
}
return i;
}
/** Возвращает смещение следующего блока пробелов.
*Если в строке нет пробелов перед @p maxlen , то индекс будет возвращен после @p maxlen .
*Т.е. может вернуть индекс символа '\0', если он умещается в следующем блоке.
*Функция пропустит любые начальные пробелы, даже если это означает превышение @p maxlen .
*Примеры:
*smtp_fold_whitespace_get_offset("Subject: Test WS", 1/2/8/9/10/13) -> 8
*smtp_fold_whitespace_get_offset("Subject: Test WS", 14/15) -> 13
*smtp_fold_whitespace_get_offset("Subject: Test WS", 17/18) -> 16
*@param[in] s Строка для получения смещения.
*@param[in] maxlen Количество байтов для каждой подстроки в строке (мягкий предел).
* @return size_t Индекс в @p s . */
static size_t smtp_fold_whitespace_get_offset(const char *const s, unsigned int maxlen)
{
size_t i = 0, offset_i = 0;
while (s[i] == ' ' || s[i] == '\t') ++i;
while (s[i])
{
if (s[i] == ' ' || s[i] == '\t')
{
do
{
++i;
} while (s[i] == ' ' || s[i] == '\t'); --i;
if (i < maxlen || !offset_i) offset_i = i;
else
break;
}
++i;
}
if (!offset_i || i < maxlen) offset_i = i;
return offset_i;
}
/** Строки заголовка электронного письма должны содержать не более 78 символов (мягкий предел) * и не более 998 символов (жесткий предел). */
#define SMTP_LINE_MAX 78
/** Складывает строку по пробелам.
*Функция пытается сохранить общее количество символов в строке до @p maxlen , но не
*гарантирует этого. Для действительно длинного текста без пробелов строка все равно будет
*выходить за пределы @p maxlen и, возможно, за пределы RFC (как определено в SMTP_LINE_MAX).
*Это сделано специально для упрощения реализации алгоритма. Пользователи, отправляющие длинные
*заголовки без пробелов, не должны предполагать, что это сработает, но современные почтовые
*системы могут правильно обрабатывать эти заголовки. Строки складываются путем
*добавления [CR][LF], а затем двух пробелов [WS][WS] в начале следующей строки.
*Например, эта строка темы:
*Subject: Email[WS][WS]Header
*Сложится так (предполагая небольшой @p maxlen ):
*Subject: Email[WS][CR][LF][WS][WS]Header
*@param[in] s Строка, которую нужно сложить.
*@param[in] maxlen Количество байтов для каждой подстроки в строке (мягкий предел).
* |
Минимальное значение этого параметра |
-- 3, |
|
* |
и он |
будет принудительно равен 3, |
|
* |
если |
предоставленное значение меньше. |
*@retval char* Указатель на строку, содержимое которой разбито на отдельные подстроки.
*По завершении вызывающая сторона должна освободить эту память.
*@retval NULL Не удалось выделить память. */
static char *smtp_fold_whitespace(const char *const s, unsigned int maxlen)
31

smtp_lib/smtp_lib.c
{
const char |
*const |
SMTP_LINE_FOLD_STR = "\r\n "; |
size_t end_slen = |
strlen(SMTP_LINE_FOLD_STR), s_i = 0, buf_i = 0, bufsz = 0; |
|
char *buf = NULL, |
*buf_new; |
|
if (maxlen |
< 3) |
|
maxlen = 3; while (1)
{
size_t ws_offset = smtp_fold_whitespace_get_offset(&s[s_i], maxlen - 2); // bufsz += ws_offset + end_slen + 1.
if (smtp_si_add_size_t(bufsz, ws_offset, &bufsz) ||
smtp_si_add_size_t(bufsz, end_slen, &bufsz) || smtp_si_add_size_t(bufsz, 1, &bufsz) || (buf_new = realloc(buf, bufsz)) == NULL)
{
free(buf); return NULL;
}
buf = buf_new;
memcpy(&buf[buf_i], &s[s_i], ws_offset); buf[buf_i + ws_offset] = '\0';
if (s[s_i + ws_offset] == '\0')
break; |
|
|
buf_i |
+= ws_offset; |
|
strcat(&buf[buf_i], SMTP_LINE_FOLD_STR); |
||
buf_i |
+= end_slen; |
|
s_i += ws_offset + 1; |
||
} |
|
|
return buf; |
|
|
} |
|
|
/** Разбивает |
строку на фрагменты, добавляя @p end к каждому фрагменту в конец. |
|
* @param[in] |
s |
Исходная строка. |
*@param[in] chunklen Количество байтов для каждого фрагмента в строке.
*@param[in] end Завершающая фрагмент строка.
*@retval char* Указатель на выделенную строку, содержимое которой разделено
* |
на отдельные фрагменты. |
*По завершении вызывающая сторона должна освободить эту память.
*@retval NULL Ошибка выделения памяти. */
static char *smtp_chunk_split(const char *const s, size_t chunklen, const char *const end)
{
char *snew;
size_t bodylen, bodylen_inc, endlen, endlen_inc, snewlen, snew_i = 0, body_i = 0, body_copy_len; if (chunklen < 1)
{
errno = EINVAL; return NULL;
}
bodylen = strlen(s); endlen = strlen(end); if (bodylen < 1)
return smtp_strdup(end);
// snewlen = bodylen + (endlen + 1) * (bodylen / chunklen + 1) + 1 (\0) if (smtp_si_add_size_t(endlen, 1, &endlen_inc) ||
smtp_si_add_size_t(bodylen, 1, &bodylen_inc) || smtp_si_mul_size_t(endlen_inc, bodylen / chunklen + 1, &snewlen) ||
smtp_si_add_size_t(snewlen, bodylen_inc, &snewlen) || (snew = calloc(1, snewlen)) == NULL) return NULL;
for (size_t chunk_i = 0, limit = bodylen / chunklen + 1; chunk_i < limit; ++chunk_i)
{
body_copy_len = smtp_strnlen_utf8(&s[body_i], chunklen); if (body_copy_len == SIZE_MAX)
{
free(snew); return NULL;
}
memcpy(&snew[snew_i], &s[body_i], body_copy_len); snew_i += body_copy_len;
if (s[body_i] == '\0') ++snew_i;
body_i += body_copy_len; if (endlen > 0)
memcpy(&snew[snew_i], end, endlen); snew_i += endlen;
}
return snew;
}
/** Читает все содержимое файлового потока и сохраняет данные в динамически выделяемый буфер. * @param[in] stream Уже открытый вызывающей стороной на чтение файловый поток.
*@param[out] bytes_read Количество байтов, хранящихся в буфере возврата.
*@retval char* Динамически выделяемый буфер, который содержит все содержимое @p stream .
*По завершении вызывающая сторона должна освободить эту память.
*@retval NULL Ошибка выделения памяти или чтения файла. */
static char *smtp_ffile_get_contents(FILE *stream, size_t *bytes_read)
{
32

smtp_lib/smtp_lib.c
char *read_buf = NULL, *new_buf;
size_t bufsz = 0, bufsz_inc, bytes_read_loop; const size_t BUFSZ_INCREMENT = 512;
if (bytes_read) *bytes_read = 0;
do
{
if (smtp_si_add_size_t(bufsz, BUFSZ_INCREMENT, &bufsz_inc) || (new_buf = realloc(read_buf, bufsz_inc)) == NULL)
{
free(read_buf); return NULL;
}
read_buf = new_buf; bufsz = bufsz_inc; bytes_read_loop =
fread(&read_buf[bufsz - BUFSZ_INCREMENT], sizeof(char), BUFSZ_INCREMENT, stream); if (bytes_read)
*bytes_read += bytes_read_loop; if (ferror(stream))
{
free(read_buf); return NULL;
}
} while (!feof(stream)); return read_buf;
}
/** Читает все содержимое файла по заданному пути и сохраняет данные в динамически выделяемый буфер. * @param[in] filename Путь к файлу для открытия и чтения.
*@param[out] bytes_read Количество байтов, хранящихся в буфере возврата.
*@retval char* Динамически выделяемый буфер, содержимое файла которого находится под именем
*@p filename .
*По завершении вызывающая сторона должна освободить эту память.
*@retval NULL Ошибка выделения памяти или чтения файла. */
static char *smtp_file_get_contents(const char *const filename, size_t *bytes_read)
{
FILE *fp;
char *read_buf;
if ((fp = fopen(filename, "rb")) == NULL) return NULL;
read_buf = smtp_ffile_get_contents(fp, bytes_read); if (fclose(fp) == EOF)
{
free(read_buf); read_buf = NULL;
}
return read_buf;
}
/** Разбивает строку ответа сервера в структуру данных @ref smtp_command .
* |
@param[in] |
line |
Строка ответа сервера. |
* |
@param[out] |
cmd |
Структура, содержащая данные ответа сервера, разбитые на отдельные компоненты. |
* @return См. @ref smtp_result_code. */
static int smtp_parse_cmd_line(char *const line, struct smtp_command *const cmd)
{
char *ep, code_str[4];
size_t line_len = strlen(line); unsigned long int ulcode;
if (line_len < 5)
{
cmd->code = SMTP_INTERNAL_ERROR; cmd->more = 0;
cmd->text = line; return cmd->code;
}
cmd->text = &line[4]; memcpy(code_str, line, 3); code_str[3] = '\0';
ulcode = strtoul(code_str, &ep, 10);
cmd->code = (*ep != '\0' || ulcode > SMTP_BEGIN_MAIL) ? SMTP_INTERNAL_ERROR
: (enum smtp_result_code)ulcode;
cmd->more = (int)(line[3] == '-'); return cmd->code;
}
/** Печатает лог коммуникации клиента и сервера в stderr, только если установлен флаг отладки.
*@param[in] smtp Контекст клиента SMTP.
*@param[in] prefix Печатает этот префикс перед текстом основной строки отладки.
*@param[in] str Отладочный текст для печати. */
static void smtp_puts_dbg(struct smtp *const smtp, const char *const prefix, const char *const str)
{
char *sdup;
if (smtp->flags & SMTP_DEBUG)
{
33

smtp_lib/smtp_lib.c
if ((sdup = smtp_strdup(str)) == NULL) return;
// Заменяет возврат каретки и символ новой строки на пробел для печати в stderr. for (size_t i = 0; sdup[i]; ++i)
if (sdup[i] == '\r' || sdup[i] == '\n') sdup[i] = ' ';
fprintf(stderr, "[smtp %s]: %s\n", prefix, sdup); free(sdup);
}
}
/** Читает строку ответа сервера.
*@param[in] smtp Контекст клиента SMTP.
*@return См. @ref str_getdelim_retcode. */
static enum str_getdelim_retcode smtp_getline(struct smtp *const smtp)
{
enum str_getdelim_retcode rc; errno = 0;
rc = smtp_str_getdelimfd(&smtp->gdfd); if (errno == ENOMEM)
{
smtp_status_code_set(smtp, SMTP_STATUS_NOMEM); return rc;
}
else if (rc == STRING_GETDELIMFD_ERROR)
{
smtp_status_code_set(smtp, SMTP_STATUS_RECV); return STRING_GETDELIMFD_ERROR;
}
if (smtp->gdfd.line_len > 0)
{
smtp->gdfd.line[smtp->gdfd.line_len - 1] = '\0'; smtp_puts_dbg(smtp, "Server", smtp->gdfd.line);
}
return rc;
}
/** Итерируется по всем строкам ответа сервера до последней строки,
*а затем возвращает код состояния из последней строки ответа.
*@param[in] smtp Контекст клиента SMTP.
*@return См. @ref smtp_result_code. */
static int smtp_read_and_parse_code(struct smtp *const smtp)
{
struct smtp_command cmd;
enum str_getdelim_retcode rc; do
{
rc = smtp_getline(smtp);
if (rc == STRING_GETDELIMFD_ERROR) return SMTP_INTERNAL_ERROR;
smtp_parse_cmd_line(smtp->gdfd.line, &cmd);
} while (rc != STRING_GETDELIMFD_DONE && cmd.more); return cmd.code;
}
/** Отправляет данные на SMTP-сервер.
*Записывает буфер длиной @p len либо в незашифрованный сокет TCP, либо в зашифрованный сокет TLS,
*в зависимости от текущего базового режима сокета.
*@param[in] smtp Контекст клиента SMTP.
*@param[in] buf Данные для отправки на SMTP-сервер.
*@param[in] len Количество байтов в @p buf .
*@return См. @ref smtp_status_code. */
static enum smtp_status_code smtp_write(struct smtp *const smtp, const char *const buf, size_t len)
{
size_t bytes_to_send = len; long bytes_sent;
const char *buf_offset = buf; smtp_puts_dbg(smtp, "Client", buf); while (bytes_to_send)
{
if (bytes_to_send > INT_MAX)
return smtp_status_code_set(smtp, SMTP_STATUS_SEND); if (smtp->tls_on)
{
#ifdef SMTP_OPENSSL
// bytes_to_send <= INT_MAX.
int ssl_bytes_to_send = (int)bytes_to_send;
bytes_sent = SSL_write(smtp->tls, buf_offset, ssl_bytes_to_send); if (bytes_sent <= 0)
return smtp_status_code_set(smtp, SMTP_STATUS_SEND); #else // !(SMTP_OPENSSL)
bytes_sent = 0; #endif // SMTP_OPENSSL
}
else
34

smtp_lib/smtp_lib.c
{
bytes_sent = send(smtp->sock, buf_offset, bytes_to_send, 0); if (bytes_sent < 0)
return smtp_status_code_set(smtp, SMTP_STATUS_SEND);
}
bytes_to_send -= (size_t)bytes_sent; buf_offset += bytes_sent;
}
return smtp->status_code;
}
/** Отправляет на SMTP-сервер строку с '\0'.
*@param[in] smtp Контекст клиента SMTP.
*@param[in] s Строка с '\0' для отправки на SMTP-сервер.
*@return См. @ref smtp_status_code и @ref smtp_write . */
static enum smtp_status_code smtp_puts(struct smtp *const smtp, const char *const s)
{
return smtp_write(smtp, s, strlen(s));
}
/** То же, что и smtp_puts, за исключением того, что эта функция также добавляет "\r\n".
*@param[in] smtp Контекст клиента SMTP.
*@param[in] s Строка с завершающим нулем для отправки на SMTP-сервер.
*@return См. @ref smtp_status_code and @ref smtp_puts . */
static enum smtp_status_code smtp_puts_terminate(struct smtp *const smtp, const char *const s)
{
enum smtp_status_code rc; char *line, *concat; size_t slen, allocsz; slen = strlen(s);
if (smtp_si_add_size_t(slen, 3, &allocsz) || (line = malloc(allocsz)) == NULL) return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
concat = smtp_stpcpy(line, s); smtp_stpcpy(concat, "\r\n"); rc = smtp_puts(smtp, line); free(line);
return rc;
}
#ifndef SMTP_IS_WINDOWS
/// Структура, содержащая информацию об адресе поставщика услуг. struct addrinfo
{
///Флаги ввода. int ai_flags;
///Семейство протоколов для сокета. int ai_family;
///Тип сокета.
int ai_socktype;
///Протокол для сокета. int ai_protocol;
///Длина адреса сокета. socklen_t ai_addrlen;
///Адрес сокета.
struct sockaddr *ai_addr;
///Каноническое название места обслуживания. char *ai_canonname;
///Указатель на следующую структуру.
struct addrinfo *ai_next;
};
/** Переводит имя местоположения службы и / или имя службы в набор адресов сокетов. * Эта функция является точкой возможной отмены и поэтому не отмечена __THROW. */
extern int getaddrinfo(const char *__restrict __name, const char *__restrict __service,
const struct addrinfo *__restrict __req, struct addrinfo **__restrict __pai);
/// Освобождает __ai, которую функция getaddrinfo динамически выделяет extern void freeaddrinfo(struct addrinfo *__ai) __THROW;
#endif
/** Подключается к серверу через стандартный сокет TCP.
*Эта функция переводит имя сервера в его IP-адрес, а затем подключается к этому IP,
*используя обычное TCP-соединение.
*@param[in] smtp Контекст клиента SMTP.
*@param[in] server Имя или IP-адрес почтового сервера.
*@param[in] port Номер порта почтового сервера.
* @retval |
0 |
Удалось подключиться к серверу. |
* @retval |
-1 |
Не удалось подключиться к серверу. */ |
static int |
smtp_connect(struct smtp *const smtp, const char *const server, const char *const port) |
|
{ |
|
|
struct |
addrinfo hints, *res0, *res; |
//Windows требует инициализации библиотеки сокетов перед вызовом каких-либо функций сокетов.
#ifdef SMTP_IS_WINDOWS
//Структура данных глобального сетевого сокета Windows.
WSADATA wsa_data;
35

smtp_lib/smtp_lib.c
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) return -1;
#endif // SMTP_IS_WINDOWS memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = 0; hints.ai_protocol = IPPROTO_TCP;
if (getaddrinfo(server, port, &hints, &res0) != 0) return -1;
for (res = res0; res; res = res->ai_next)
{
smtp->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (smtp->sock < 0)
continue;
if (connect(smtp->sock, res->ai_addr, res->ai_addrlen) < 0)
{
#ifdef SMTP_IS_WINDOWS closesocket(smtp->sock);
#else // POSIX close(smtp->sock);
#endif // SMTP_IS_WINDOWS smtp->sock = -1;
}
else
break;
}
freeaddrinfo(res0);
return (smtp->sock < 0) ? -1 : 0;
}
#ifdef SMTP_OPENSSL
/** Инициализирует библиотеку TLS и устанавливает рукопожатие TLS с сервером
*через существующее соединение сокета.
*@param[in] smtp Контекст клиента SMTP.
*@param[in] server Имя сервера или IP-адрес.
* @retval |
0 |
Удалось установить TLS-соединение с |
сервером. |
* @retval |
-1 |
Не удалось установить TLS-соединение с сервером. */ |
|
static int |
smtp_tls_init(struct smtp *const smtp, |
const char *const server) |
|
{ |
|
|
|
X509 *X509_cert_peer;
//Не нужно проверять возвращаемое значение, поскольку оно всегда возвращает 1.
SSL_library_init(); SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms();
if ((smtp->tls_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) return -1;
//Запрещаем использование SSLv2, SSLv3, и TLSv1.0.
SSL_CTX_set_options(smtp->tls_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1); SSL_CTX_set_mode(smtp->tls_ctx, SSL_MODE_AUTO_RETRY);
if ((smtp->flags & SMTP_NO_CERT_VERIFY) == 0) SSL_CTX_set_verify(smtp->tls_ctx, SSL_VERIFY_PEER, NULL);
/* Задает путь к предоставленному пользователем файлу CA
или использует пути к сертификатам по умолчанию, если они не указаны. */ if (smtp->cafile)
{
if (SSL_CTX_load_verify_locations(smtp->tls_ctx, smtp->cafile, NULL) != 1)
{
SSL_CTX_free(smtp->tls_ctx); return -1;
}
}
else
{
X509_STORE_set_default_paths(SSL_CTX_get_cert_store(smtp->tls_ctx)); if (ERR_peek_error() != 0)
{
SSL_CTX_free(smtp->tls_ctx); return -1;
}
}
if ((smtp->tls = SSL_new(smtp->tls_ctx)) == NULL)
{
SSL_CTX_free(smtp->tls_ctx); return -1;
}
if ((smtp->tls_bio = BIO_new_socket(smtp->sock, 0)) == NULL)
{
SSL_CTX_free(smtp->tls_ctx); SSL_free(smtp->tls);
return -1;
}
SSL_set_bio(smtp->tls, smtp->tls_bio, smtp->tls_bio); SSL_set_connect_state(smtp->tls);
36

smtp_lib/smtp_lib.c
if (SSL_connect(smtp->tls) != 1)
{
SSL_CTX_free(smtp->tls_ctx); SSL_free(smtp->tls);
return -1;
}
if (SSL_do_handshake(smtp->tls) != 1)
{
SSL_CTX_free(smtp->tls_ctx); SSL_free(smtp->tls);
return -1;
}
if ((smtp->flags & SMTP_NO_CERT_VERIFY) == 0)
{
if ((X509_cert_peer = SSL_get_peer_certificate(smtp->tls)) == NULL)
{
SSL_CTX_free(smtp->tls_ctx); SSL_free(smtp->tls);
return -1;
}
if (X509_check_host(X509_cert_peer, server, 0, 0, NULL) != 1)
{
SSL_CTX_free(smtp->tls_ctx); SSL_free(smtp->tls);
return -1;
}
X509_free(X509_cert_peer);
}
smtp->tls_on = 1; return 0;
}
#endif // SMTP_OPENSSL
/** Отправляет команду EHLO и анализирует ответы.
*Игнорирует все возвращаемые расширения сервера. Если сервер не поддерживает нужное нам
*расширение, то позже мы должны получить сообщение об ошибке, когда попытаемся использовать это
*расширение.
*@param[in] smtp Контекст клиента SMTP.
*@return См. @ref smtp_status_code. */
static enum smtp_status_code smtp_ehlo(struct smtp *const smtp)
{
if (smtp_puts(smtp, "EHLO smtp\r\n") == SMTP_STATUS_OK) smtp_read_and_parse_code(smtp);
return smtp->status_code;
}
/** Выполняет аутентификацию с помощью PLAIN метода.
*1. Задает для текста следующий формат: "\0<user>\0<password>",
*или как показано в строке формата: "\0%s\0%s", email, password.
*2. Base64 кодирует текст из (1).
*3. Отправляет созданный текст аутентификации из (2) на сервер:
*"AUTH PLAIN <b64><CR><NL>".
*@param[in] smtp Контекст клиента SMTP.
*@param[in] user Имя пользователя учетной записи SMTP.
*@param[in] pass Пароль учетной записи SMTP.
* @retval 0 Удалось пройти аутентификацию.
* @retval -1 Не удалось пройти аутентификацию. */
static int smtp_auth_plain(struct smtp *const smtp, const char *const user, const char *const pass)
{
size_t user_len, pass_len, login_len, login_b64_len; char *login_str, *login_b64, *login_send, *concat; // (1)
user_len = strlen(user); pass_len = strlen(pass);
// login_len = 1 + user_len + 1 + pass_len.
if (smtp_si_add_size_t(user_len, pass_len, &login_len) ||
smtp_si_add_size_t(login_len, 2, &login_len) || (login_str = malloc(login_len)) == NULL) return -1;
login_str[0] = '\0'; memcpy(&login_str[1], user, user_len); login_str[1 + user_len] = '\0';
memcpy(&login_str[1 + user_len + 1], pass, pass_len); // (2)
login_b64 = smtp_base64_encode(login_str, login_len); free(login_str);
if (login_b64 == NULL) return -1;
// (3)
login_b64_len = strlen(login_b64);
if (smtp_si_add_size_t(login_b64_len, 14, &login_b64_len) || (login_send = malloc(login_b64_len)) == NULL)
{
free(login_b64); return -1;
}
37

smtp_lib/smtp_lib.c
concat = smtp_stpcpy(login_send, "AUTH PLAIN "); concat = smtp_stpcpy(concat, login_b64); smtp_stpcpy(concat, "\r\n");
free(login_b64); smtp_puts(smtp, login_send); free(login_send);
if (smtp->status_code != SMTP_STATUS_OK) return -1;
if (smtp_read_and_parse_code(smtp) != SMTP_AUTH_SUCCESS) return -1;
return 0;
}
/** Выполняет аутентификацию с помощью метода LOGIN.
*1. Base64 кодирует имя пользователя.
*2. Отправляет строку из (1) как часть входа в систему:
*"AUTH LOGIN <b64_username><CR><NL>".
*3. Base64 кодирует пароль и отправляет его отдельно в отдельной строке:
*"<b64_password><CR><NL>".
*@param[in] smtp Контекст клиента SMTP.
*@param[in] user Имя пользователя учетной записи SMTP.
*@param[in] pass Пароль учетной записи SMTP.
* @retval 0 Удалось пройти аутентификацию.
* @retval -1 Не удалось пройти аутентификацию. */
static int smtp_auth_login(struct smtp *const smtp, const char *const user, const char *const pass)
{
char *b64_user, *b64_pass, *login_str, *concat; size_t b64_user_len;
// (1)
if ((b64_user = smtp_base64_encode(user, SIZE_MAX)) == NULL) return -1;
// (2)
b64_user_len = strlen(b64_user);
if (smtp_si_add_size_t(b64_user_len, 14, &b64_user_len) || (login_str = malloc(b64_user_len)) == NULL)
{
free(b64_user); return -1;
}
concat = smtp_stpcpy(login_str, "AUTH LOGIN "); concat = smtp_stpcpy(concat, b64_user); smtp_stpcpy(concat, "\r\n");
free(b64_user); smtp_puts(smtp, login_str); free(login_str);
if (smtp->status_code != SMTP_STATUS_OK) return -1;
if (smtp_read_and_parse_code(smtp) != SMTP_AUTH_CONTINUE) return -1;
// (3)
if ((b64_pass = smtp_base64_encode(pass, SIZE_MAX)) == NULL) return -1;
smtp_puts_terminate(smtp, b64_pass); free(b64_pass);
if (smtp->status_code != SMTP_STATUS_OK) return -1;
if (smtp_read_and_parse_code(smtp) != SMTP_AUTH_SUCCESS) return -1;
return 0;
}
/** Устанавливает тайм-аут для следующей операции чтения сокета.
*@param[in] smtp Контекст клиента SMTP.
*@param[in] seconds Тайм-аут в секундах. */
static void smtp_set_read_timeout(struct smtp *const smtp, long seconds)
{
smtp->timeout_sec = seconds;
}
/** Выполняет рукопожатие с сервером SMTP, которое включает в себя
*необязательную настройку TLS и отправку приветствия EHLO.
*На этом этапе клиент уже подключился к SMTP-серверу через свое сокетное соединение.
*В этой функции клиент:
*1. При желании преобразует соединение в TLS ( @ref SMTP_SECURITY_TLS ).
*2. Читает начальное приветствие сервера.
*3. Отправляет EHLO на сервер.
*4. При необходимости запускает STARTTLS и повторно отправляет EHLO
*( @ref SMTP_SECURITY_STARTTLS ).
* |
@param[in] |
smtp |
Контекст клиента SMTP. |
* |
@param[in] |
server |
Имя сервера или IP-адрес. |
*@param[in] connection_security См. @ref smtp_connection_security.
*@return См. @ref smtp_status_code. */
static enum smtp_status_code
smtp_initiate_handshake(struct smtp *const smtp, const char *const server, enum smtp_connection_security connection_security)
38

smtp_lib/smtp_lib.c
{
//Избежание предупреждений о неиспользуемых переменных
(void)server; (void)connection_security;
//(1)
#ifdef SMTP_OPENSSL
if (connection_security == SMTP_SECURITY_TLS && smtp_tls_init(smtp, server) < 0) return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE);
#endif // SMTP_OPENSSL
//(2)
//Получает начальное сообщение 220 - таймаут 5 минут smtp_set_read_timeout(smtp, 60 * 5);
if (smtp_getline(smtp) == STRING_GETDELIMFD_ERROR) return smtp->status_code;
//(3)
if (smtp_ehlo(smtp) != SMTP_STATUS_OK) return smtp->status_code;
#ifdef SMTP_OPENSSL // (4)
if (connection_security == SMTP_SECURITY_STARTTLS)
{
if (smtp_puts(smtp, "STARTTLS\r\n") != SMTP_STATUS_OK) return smtp->status_code;
if (smtp_read_and_parse_code(smtp) != SMTP_READY)
return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE); if (smtp_tls_init(smtp, server) < 0)
return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE); if (smtp_ehlo(smtp) != SMTP_STATUS_OK)
return smtp->status_code;
}
#endif // SMTP_OPENSSL return smtp->status_code;
}
/** Максимальный размер строки даты RFC 2822.
@verbatim
Thu, 21 May 1998 05:33:29 -0700 12345678901234567890123456789012
10 20 30 32 (bytes) @endverbatim
Добавляет больше байтов к максимальному размеру 32, чтобы отключить предупреждение компилятора о вычисленном смещении. */
#define SMTP_DATE_MAX_SZ (32 + 15)
#ifndef SMTP_IS_WINDOWS
/** Преобразует системное время в секундах в дату и всемирное координированное время (UTC).
*Результат помещается в структуру типа tm, на которую указывает @p __tp .
*Функция возвращает указатель на эту структуру. */
extern struct tm *gmtime_r(const time_t *__restrict __timer, struct tm *__restrict __tp) __THROW;
/** Преобразует системное время в секундах в дату и местное время.
*Результат помещается в структуру типа tm, на которую указывает @p __tp .
*Функция возвращает указатель на эту структуру. */
extern struct tm *localtime_r(const time_t *__restrict __timer, struct tm *__restrict __tp) __THROW; #endif
/** Преобразует время в строку в формате RFC 2822.
*Пример формата даты:
*Thu, 21 May 1998 05:33:29 -0700
*@param[out] date Буфер размером не менее @ref SMTP_DATE_MAX_SZ байт.
* @retval |
0 |
Успешно установлена текущая дата в буфер. |
* @retval |
-1 |
Не удалось установить текущую дату в буфер или произошла ошибка формата вывода. */ |
static int |
smtp_date_rfc_2822(char *const date) |
|
{ |
|
|
time_t |
t, |
t_local, t_utc; |
struct |
tm |
tm_local, tm_utc; |
long offset_utc; |
||
double |
diff_local_utc; |
|
int rc; |
|
|
const char weekday_abbreviation[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; const char month_abbreviation[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
if ((t = time(NULL)) == (time_t)(-1)) return -1;
#ifdef SMTP_IS_WINDOWS
if (localtime_s(&tm_local, &t) || gmtime_s(&tm_utc, &t)) return -1;
#else // POSIX
#ifdef SMTP_TIME_NO_REENTRANT struct tm *tm;
//localtime() не потокобезопасный. if ((tm = localtime(&t)) == NULL)
return -1;
memcpy(&tm_local, tm, sizeof(tm_local));
//gmtime() не потокобезопасный.
39

smtp_lib/smtp_lib.c
if ((tm = gmtime(&t)) == NULL) return -1;
memcpy(&tm_utc, tm, sizeof(tm_utc));
#else // Реентерабельные версии: localtime_r() and gmtime_r().
if (localtime_r(&t, &tm_local) == NULL || gmtime_r(&t, &tm_utc) == NULL) return -1;
#endif // SMTP_TIME_NO_REENTRANT #endif // SMTP_IS_WINDOWS
if ((t_local = mktime(&tm_local)) == (time_t)(-1)) return -1;
if ((t_utc = mktime(&tm_utc)) == (time_t)(-1)) return -1;
/* После вычисления смещения оно будет содержать максимум 4 цифры. Например, часовой пояс PST будет иметь смещение -800,
которое будет отформатировано как -0800 в приведенном ниже вызове sprintf. */ diff_local_utc = difftime(t_local, t_utc);
offset_utc = (long)diff_local_utc; offset_utc = offset_utc / 60 / 60 * 100;
rc = sprintf(date, "%s, %02d %s %d %02d:%02d:%02d %0+5ld", weekday_abbreviation[tm_local.tm_wday], tm_local.tm_mday, month_abbreviation[tm_local.tm_mon], tm_local.tm_year + 1900, tm_local.tm_hour, tm_local.tm_min, tm_local.tm_sec, /* 0 - 60 (leap second) */
offset_utc);
return (rc + 1 != SMTP_DATE_MAX_SZ - 15) ? -1 : 0; // См. SMTP_DATE_MAX_SZ для -5.
}
/** Функция поиска, используемая bsearch, позволяющая вызывающей стороне
*проверять заголовки с существующими ключами.
*@param v1 Строка для поиска в списке.
*@param v2 @ref smtp_header для сравнения.
* @retval |
0 |
Если ключи совпадают. |
* @retval |
!0 |
Если ключи не совпадают. */ |
static int |
smtp_header_cmp_key(const void *const v1, const void *const v2) |
|
{ |
|
|
const char *key; |
||
const struct smtp_header *header2; |
||
key = v1; |
|
|
header2 = |
v2; |
|
return |
strcmp(key, header2->key); |
|
} |
|
|
/** Определяет, был ли уже определен ключ заголовка в этом контексте. * @param[in] smtp Контекст клиента SMTP.
* @param[in] key Ключ заголовка для поиска.
*@retval 1 Если заголовок уже существует в этом контексте.
*@retval 0 Если заголовок не существует в этом контексте. */
static int smtp_header_exists(const struct smtp *const smtp, const char *const key)
{
return bsearch(key, smtp->header_list, smtp->num_headers, sizeof(*smtp->header_list), smtp_header_cmp_key) != NULL;
}
/** Минимальная длина буфера, необходимая для проведения граничного теста MIME.
*mimeXXXXXXXXXX
*123456789012345
* 1 10 15 bytes */ #define SMTP_MIME_BOUNDARY_LEN 15
/** Создает граничное текстовое поле MIME и сохраняет его в пользовательском буфере.
*Например:
*mimeXXXXXXXXXX
*где каждому X присваивается псевдослучайный символ ASCII в верхнем регистре.
*Здесь используется простой генератор псевдослучайных чисел.
*@param[out] boundary Буфер размером не менее @ref SMTP_MIME_BOUNDARY_LEN байт. */ static void smtp_gen_mime_boundary(char *const boundary)
{
unsigned int seed = (unsigned int)time(NULL); srand(seed);
strcpy(boundary, "mime");
for (size_t i = 4; i < SMTP_MIME_BOUNDARY_LEN - 1; ++i)
//Смещение по модулю в порядке, так как нам нужно только предотвратить случайную коллизию. boundary[i] = rand() % 26 + 'A';
boundary[SMTP_MIME_BOUNDARY_LEN - 1] = '\0';
}
/** Печать заголовка MIME и раздела MIME, содержащего тело письма.
* @param[in] smtp |
Контекст клиента SMTP. |
||
* |
@param[in] |
boundary |
Граничный текст MIME. |
* |
@param[in] |
body_dd |
Текст письма с двойными точками ("..") в начале каждой строки. |
* @return См. @ref smtp_status_code. */
static enum smtp_status_code smtp_print_mime_header_and_body(struct smtp *const smtp, const char *const boundary, const char *const body_dd)
{
// Размер буфера для статического текста MIME, используемого ниже.
40