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

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

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