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

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

 

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