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