
7 семестр / Готовые отчеты / ММиВА. Лабораторная работа 4
.pdf
smtp_lib/smtp_lib.c
const size_t MIME_TEXT_BUFSZ = 1000;
size_t data_double_dot_len = strlen(body_dd); char *data_header_and_body, *concat;
if (smtp_si_add_size_t(data_double_dot_len, MIME_TEXT_BUFSZ, &data_double_dot_len) || (data_header_and_body = malloc(data_double_dot_len)) == NULL)
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
concat = smtp_stpcpy(data_header_and_body, "MIME-Version: 1.0\r\n"
"Content-Type: multipart/mixed; boundary=");
concat = smtp_stpcpy(concat, boundary); concat = smtp_stpcpy(concat, "\r\n"
"\r\n"
"Multipart MIME message.\r\n" "--");
concat = smtp_stpcpy(concat, boundary); concat = smtp_stpcpy(concat, "\r\n"
"Content-Type: text/plain; charset=\"UTF-8\"\r\n" "\r\n");
concat = smtp_stpcpy(concat, body_dd); smtp_stpcpy(concat, "\r\n"
"\r\n"); smtp_puts(smtp, data_header_and_body); free(data_header_and_body);
return smtp->status_code;
}
/** Печатает раздел MIME, содержащий вложение.
* |
@param[in] |
smtp |
Контекст клиента SMTP. |
* |
@param[in] |
boundary |
Граничный текст MIME. |
*@param[in] attachment См. @ref smtp_attachment.
*@return См. @ref smtp_status_code. */
static enum smtp_status_code
smtp_print_mime_attachment(struct smtp *const smtp, const char *const boundary, const struct smtp_attachment *const attachment)
{
//Размер буфера для статического текста MIME, используемого ниже. const size_t MIME_TEXT_BUFSZ = 1000;
size_t name_len = strlen(attachment->name), b64_data_len = strlen(attachment->b64_data), bufsz; char *mime_attach_text, *concat;
//bufsz = SMTP_MIME_BOUNDARY_LEN + name_len + b64_data_len + MIME_TEXT_BUFSZ.
if (smtp_si_add_size_t(name_len, b64_data_len, &bufsz) || smtp_si_add_size_t(bufsz, SMTP_MIME_BOUNDARY_LEN + MIME_TEXT_BUFSZ, &bufsz) || (mime_attach_text = malloc(bufsz)) == NULL)
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM); concat = smtp_stpcpy(mime_attach_text, "--");
concat = smtp_stpcpy(concat, boundary); concat = smtp_stpcpy(concat, "\r\n"
"Content-Type: application/octet-stream\r\n" "Content-Disposition: attachment; filename=\"");
concat = smtp_stpcpy(concat, attachment->name); concat = smtp_stpcpy(concat, "\"\r\n"
"Content-Transfer-Encoding: base64\r\n" "\r\n");
concat = smtp_stpcpy(concat, attachment->b64_data); smtp_stpcpy(concat, "\r\n"
"\r\n"); smtp_puts(smtp, mime_attach_text); free(mime_attach_text);
return smtp->status_code;
}
/** Печатает двойной дефис с обеих сторон границы MIME, обозначающий конец разделов MIME.
*@param[in] smtp Контекст клиента SMTP.
*@param[in] boundary Граничный текст MIME.
*@return См. @ref smtp_status_code and @ref smtp_puts. */
static enum smtp_status_code smtp_print_mime_end(struct smtp *const smtp, const char *const boundary)
{
char *concat, mime_end[2 + SMTP_MIME_BOUNDARY_LEN + 4 + 1]; concat = smtp_stpcpy(mime_end, "--");
concat = smtp_stpcpy(concat, boundary); smtp_stpcpy(concat, "--\r\n");
return smtp_puts(smtp, mime_end);
}
/** Отправляет тело письма на SMTP-сервер. Включая разделы MIME.
* |
@param[in] |
smtp |
Контекст клиента SMTP. |
* |
@param[in] |
body_dd |
Текст письма с двойными точками ("..") в начале каждой строки. |
* @return См. @ref smtp_status_code. */
static enum smtp_status_code smtp_print_mime_email(struct smtp *const smtp, const char *const body_dd)
{
char boundary[SMTP_MIME_BOUNDARY_LEN]; smtp_gen_mime_boundary(boundary);
if (smtp_print_mime_header_and_body(smtp, boundary, body_dd) != SMTP_STATUS_OK) return smtp->status_code;
41

smtp_lib/smtp_lib.c
for (size_t i = 0; i < smtp->num_attachment; ++i)
{
struct smtp_attachment *attachment = &smtp->attachment_list[i];
if (smtp_print_mime_attachment(smtp, boundary, attachment) != SMTP_STATUS_OK) return smtp->status_code;
}
return smtp_print_mime_end(smtp, boundary);
}
/** Печатает данные электронной почты, предоставленные пользователем, без форматирования MIME.
* @param[in,out] |
smtp |
Контекст |
клиента SMTP. |
|
* |
@param[in] |
body_dd |
Текст письма с двойными точками ("..") в начале каждой строки. |
|
* |
@return |
|
См. @ref |
smtp_status_code. */ |
static enum smtp_status_code smtp_print_nomime_email(struct smtp *const smtp, const char *const body_dd)
{
return smtp_puts_terminate(smtp, body_dd);
} |
|
|
|
|
/** Отправляет тело письма на почтовый |
сервер |
|||
* @param[in,out] |
smtp |
Контекст |
клиента SMTP. |
|
* @param[in] |
body |
Текст сообщения |
электронной почты. |
|
* @return |
|
См. @ref |
smtp_status_code. */ |
static enum smtp_status_code smtp_print_email(struct smtp *const smtp, const char *const body)
{
char *body_double_dot;
/* Вставляет дополнительную точку для каждой строки, начинающейся с точки.
Это предотвратит преждевременное завершение сегмента DATA данными в параметре тела. */ if ((body_double_dot = smtp_str_replace("\n.", "\n..", body)) == NULL)
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM); if (smtp_header_exists(smtp, "Content-Type"))
smtp_print_nomime_email(smtp, body_double_dot);
else
smtp_print_mime_email(smtp, body_double_dot); free(body_double_dot);
return smtp->status_code;
}
/** Преобразует заголовок в строку в формате RFC 5322 и отправляет её на SMTP-сервер.
*Это добавит правильный перенос строк и отступ для длинных строк заголовка.
*@param[in] smtp Контекст клиента SMTP.
*@param[in] header См. @ref smtp_header.
*@return См. @ref smtp_status_code. */
static enum smtp_status_code smtp_print_header(struct smtp *const smtp,
const struct smtp_header *const header)
{
size_t key_len, value_len, concat_len; char *header_concat, *concat, *header_fmt; if (header->value == NULL)
return smtp->status_code; key_len = strlen(header->key); value_len = strlen(header->value);
// concat_len = key_len + 2 + value_len + 1.
if (smtp_si_add_size_t(key_len, value_len, &concat_len) || smtp_si_add_size_t(concat_len, 3, &concat_len) || (header_concat = malloc(concat_len)) == NULL)
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM); concat = smtp_stpcpy(header_concat, header->key);
concat = smtp_stpcpy(concat, ": "); smtp_stpcpy(concat, header->value);
header_fmt = smtp_fold_whitespace(header_concat, SMTP_LINE_MAX); free(header_concat);
if (header_fmt == NULL)
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM); smtp_puts_terminate(smtp, header_fmt);
free(header_fmt);
return smtp->status_code;
}
/** Добавляет адреса FROM, TO и CC в список заголовков электронной почты.
*В следующем примере показано, как может выглядеть окончательный заголовок,
*когда клиент отправляет электронное письмо на два адреса CC:
*Cc: mail1\@example.com, mail2\@example.com
* @param[in] smtp |
Контекст клиента SMTP. |
*@param[in] address_type См. @ref smtp_address_type.
*@param[in] key Значение ключа заголовка: FROM, TO и CC.
*@return См. @ref smtp_status_code. */
static enum smtp_status_code smtp_append_address_to_header(struct smtp *const smtp,
enum smtp_address_type address_type, const char *const key)
{
size_t num_address_in_header = 0, header_value_sz = 0, concat_len = 0; char *header_value_new = NULL, *concat = NULL, *header_value = NULL; for (size_t i = 0; i < smtp->num_address; ++i)
{
42

smtp_lib/smtp_lib.c
struct |
smtp_address *address = &smtp->address_list[i]; |
|
|
||
if (address->type == address_type) |
|
|
|
||
{ |
|
|
|
|
|
size_t name_slen = 0, email_slen = strlen(address->email); |
|
||||
if |
(address->name) |
|
|
|
|
|
name_slen = strlen(address->name); |
|
|
|
|
/* |
', "' |
NAME |
'" <' |
> \0 |
|
|
header_value_sz += 3 + |
name_slen |
+ 3 + |
email_slen + 1 + 1 */ |
if (smtp_si_add_size_t(header_value_sz, name_slen, &header_value_sz) || smtp_si_add_size_t(header_value_sz, email_slen, &header_value_sz) || smtp_si_add_size_t(header_value_sz, 3 + 3 + 1 + 1, &header_value_sz) || (header_value_new = realloc(header_value, header_value_sz)) == NULL)
{
free(header_value);
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
header_value = header_value_new; concat = header_value + concat_len; if (num_address_in_header > 0)
concat = smtp_stpcpy(concat, ", "); if (name_slen)
{
concat = smtp_stpcpy(concat, "\"");
concat = smtp_stpcpy(concat, address->name); concat = smtp_stpcpy(concat, "\" ");
}
concat = smtp_stpcpy(concat, "<");
concat = smtp_stpcpy(concat, address->email); concat = smtp_stpcpy(concat, ">"); ++num_address_in_header;
concat_len = (size_t)(concat - header_value);
}
}
if (header_value)
{
smtp_header_add(smtp, key, header_value); free(header_value);
}
return smtp->status_code;
}
/** Отправляет MAIL FROM или RCPT TO адрес заголовка.
*Примеры:
*MAIL FROM:<mail\@example.com>
*RCPT TO:<mail\@example.com>
*@param[in] smtp Контекст клиента SMTP.
*@param[in] header Либо "MAIL FROM", либо "RCPT TO".
*@param[in] address См. @ref smtp_address -> email field.
*@return См. @ref smtp_status_code. */
static enum smtp_status_code smtp_mail_envelope_header(struct smtp *const smtp, const char *const header,
const struct smtp_address *const address)
{
const char *smtputf8_opt = "", *const SMTPUTF8 = " SMTPUTF8"; const size_t SMTPUTF8_LEN = strlen(SMTPUTF8);
size_t email_len = strlen(address->email), bufsz; char *envelope_address, *concat;
// bufsz = 14 + email_len + SMTPUTF8_LEN + 1.
if (smtp_si_add_size_t(email_len, SMTPUTF8_LEN + 14 + 1, &bufsz) || (envelope_address = malloc(bufsz)) == NULL)
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM); if (smtp_str_has_nonascii_utf8(address->email))
smtputf8_opt = SMTPUTF8;
concat = smtp_stpcpy(envelope_address, header); concat = smtp_stpcpy(concat, ":<");
concat = smtp_stpcpy(concat, address->email); concat = smtp_stpcpy(concat, ">");
concat = smtp_stpcpy(concat, smtputf8_opt); smtp_stpcpy(concat, "\r\n"); smtp_puts(smtp, envelope_address); free(envelope_address);
if (smtp->status_code != SMTP_STATUS_OK) return smtp->status_code;
smtp_read_and_parse_code(smtp); return smtp->status_code;
}
/** Функция сравнения для qsort, которая сортирует заголовки в алфавитном порядке по ключу.
*@param[in] v1 Первый @ref smtp_header для сравнения.
*@param[in] v2 Второй @ref smtp_header для сравнения.
* @retval |
0 |
Если |
ключи |
совпадают. |
* @retval |
!0 |
Если |
ключи |
не совпадают. */ |
static int |
smtp_header_cmp(const void *v1, const void *v2) |
|||
{ |
|
|
|
|
const struct smtp_header *header1, *header2;
43

smtp_lib/smtp_lib.c
header1 = v1, header2 = v2;
return strcmp(header1->key, header2->key);
}
/** Проверяет символы в ключе заголовка электронного письма.
*Должен состоять только из печатаемых символов US-ASCII, кроме двоеточия.
*@param[in] key Ключ заголовка для проверки.
* @retval |
0 |
Успешная проверка. |
* @retval |
-1 |
Не удалось проверить. */ |
static int |
smtp_header_key_validate(const char *const key) |
|
{ |
|
|
size_t |
keylen = strlen(key); |
if (keylen < 1) return -1;
for (size_t i = 0; i < keylen; ++i)
{
unsigned char uc = (unsigned char)key[i]; if (uc <= ' ' || uc > 126 || uc == ':')
return -1;
}
return 0;
}
/** Проверяет символы в содержимом заголовка электронной почты.
*Должно состоять только из печатного символа, пробела или горизонтальной табуляции.
*@param[in] value Значение заголовка для проверки.
* |
@retval |
0 |
Успешная проверка. |
* |
@retval |
-1 |
Не удалось проверить. */ |
static int |
smtp_header_value_validate(const char *const value) |
{
for (size_t i = 0; value[i]; ++i)
{
unsigned char uc = (unsigned char)value[i];
if ((uc < ' ' || uc > 126) && uc != '\t' && uc < 0x80) return -1;
}
return 0;
}
/** Проверяет символы в адресе электронной почты.
*Адрес электронной почты должен состоять только из печатаемых символов,
*за исключением угловых скобок (<) и (>).
*@param[in] email Электронный адрес.
* @retval |
0 |
Успешная проверка. |
* @retval |
-1 |
Не удалось проверить. */ |
static int |
smtp_address_validate_email(const char *const email) |
|
{ |
|
|
for (size_t i = 0; email[i]; ++i) |
||
{ |
|
|
unsigned char uc = (unsigned char)email[i]; |
||
if |
(uc <= ' ' || uc == 127 || uc == '<' || uc == '>') |
|
|
return -1; |
|
} |
|
|
return |
0; |
|
} |
|
|
/** Проверяет символы в имени пользователя электронной почты.
*Имя пользователя электронной почты должно состоять
*только из печатаемых символов, за исключением символа двойной кавычки.
*@param[in] name Имя электронной почты для проверки.
* @retval |
0 |
Успешная |
проверка. |
* @retval |
-1 |
Не удалось проверить. */ |
|
static int |
smtp_address_validate_name(const char *const name) |
||
{ |
|
|
|
for (size_t i = 0; |
name[i]; ++i) |
{
unsigned char uc = (unsigned char)name[i]; if (uc < ' ' || uc == 127 || uc == '\"')
return -1;
}
return 0;
}
/** Проверяет символы в имени файла вложения.
*Должно состоять только из печатаемых символов или символа пробела,
*исключая символы кавычек (') и (").
*@param[in] name Имя файла вложения.
* @retval |
0 |
Успешная |
проверка. |
* @retval |
-1 |
Не удалось проверить. */ |
|
static int |
smtp_attachment_validate_name(const char *const name) |
||
{ |
|
|
|
for (size_t i = 0; |
name[i]; ++i) |
{
unsigned char uc = (unsigned char)name[i];
if (uc < ' ' || uc == 127 || uc == '\'' || uc == '\"')
44

smtp_lib/smtp_lib.c
return -1;
}
return 0;
}
/** Специальное значение флага для контекста SMTP, используемое для определения того, * не удалось ли при первоначальном выделении памяти создать контекст. */
#define SMTP_FLAG_INVALID_MEMORY (enum smtp_flag)(0xFFFFFFFF)
/** Эта структура ошибок используется для случая единственной ошибки, когда мы не можем изначально
*выделить память. Это упрощает распространение любых кодов ошибок при вызове других функций
*заголовка, поскольку вызывающая сторона всегда будет получать верную структуру SMTP. */ static struct smtp g_smtp_error = {
/// flags SMTP_FLAG_INVALID_MEMORY,
///sock
0,
///gdfd {/// _buf
NULL,
///_bufsz
0,
///_buf_len
0,
///line NULL,
///line_len
0,
///getdelimfd_read NULL,
///user_data
NULL,
///delim
0,
///pad {0}},
///header_list NULL,
///num_headers
0,
///address_list NULL,
///num_address
0,
///attachment_list NULL,
///num_attachment
0,
///timeout_sec
0,
///smtp_status_code status_code SMTP_STATUS_NOMEM,
///tls_on
0,
/// cafile NULL
#ifdef SMTP_OPENSSL
,
///tls NULL,
///tls_ctx NULL,
///tls_bio
NULL
#endif // SMTP_OPENSSL };
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)
{
struct smtp *snew;
if ((snew = calloc(1, sizeof(**smtp))) == NULL)
{
*smtp = &g_smtp_error;
return smtp_status_code_get(*smtp);
}
*smtp = snew; snew->flags = flags; snew->sock = -1; snew->gdfd.delim = '\n';
snew->gdfd.getdelimfd_read = smtp_str_getdelimfd_read; snew->gdfd.user_data = snew;
snew->cafile = cafile; #ifndef SMTP_IS_WINDOWS
45

smtp_lib/smtp_lib.c
signal(SIGPIPE, SIG_IGN); #endif // !(SMTP_IS_WINDOWS)
if (smtp_connect(snew, server, port) < 0)
return smtp_status_code_set(*smtp, SMTP_STATUS_CONNECT);
if (smtp_initiate_handshake(snew, server, connection_security) != SMTP_STATUS_OK) return smtp_status_code_set(*smtp, SMTP_STATUS_HANDSHAKE);
return snew->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)
{
int auth_rc;
if (smtp->status_code != SMTP_STATUS_OK) return smtp->status_code;
if (auth_method == SMTP_AUTH_PLAIN)
auth_rc = smtp_auth_plain(smtp, user, pass); else if (auth_method == SMTP_AUTH_LOGIN)
auth_rc = smtp_auth_login(smtp, user, pass); else if (auth_method == SMTP_AUTH_NONE)
auth_rc = 0;
else
return smtp_status_code_set(smtp, SMTP_STATUS_PARAM); if (auth_rc < 0)
return smtp_status_code_set(smtp, SMTP_STATUS_AUTH); return smtp->status_code;
}
enum smtp_status_code smtp_mail(struct smtp *const smtp, const char *const body)
{
int has_mail_from = 0;
struct smtp_address *address; char date[SMTP_DATE_MAX_SZ];
if (smtp->status_code != SMTP_STATUS_OK) return smtp->status_code;
// MAIL timeout 5 минут. smtp_set_read_timeout(smtp, 60 * 5);
for (size_t i = 0; i < smtp->num_address; ++i)
{
address = &smtp->address_list[i];
if (address->type == SMTP_ADDRESS_FROM)
{
if (smtp_mail_envelope_header(smtp, "MAIL FROM", address) != SMTP_STATUS_OK) return smtp->status_code;
has_mail_from = 1; break;
}
}
if (!has_mail_from)
return smtp_status_code_set(smtp, SMTP_STATUS_PARAM); // RCPT timeout 5 минут.
smtp_set_read_timeout(smtp, 60 * 5);
for (size_t i = 0; i < smtp->num_address; ++i)
{
address = &smtp->address_list[i];
if (address->type != SMTP_ADDRESS_FROM &&
smtp_mail_envelope_header(smtp, "RCPT TO", address) != SMTP_STATUS_OK) return smtp->status_code;
}
// DATA timeout 2 минут. smtp_set_read_timeout(smtp, 60 * 2);
if (smtp_puts(smtp, "DATA\r\n") != SMTP_STATUS_OK) return smtp->status_code;
//354 ответ на DATA должен быть возвращен, прежде чем мы сможем отправить сообщение. if (smtp_read_and_parse_code(smtp) != SMTP_BEGIN_MAIL)
return smtp_status_code_set(smtp, SMTP_STATUS_SERVER_RESPONSE); if (!smtp_header_exists(smtp, "Date"))
{
if (smtp_date_rfc_2822(date) < 0)
return smtp_status_code_set(smtp, SMTP_STATUS_DATE); if (smtp_header_add(smtp, "Date", date) != SMTP_STATUS_OK)
return smtp->status_code;
}
//DATA block timeout 3 минуты.
smtp_set_read_timeout(smtp, 60 * 3);
if (smtp_append_address_to_header(smtp, SMTP_ADDRESS_FROM, "From") != SMTP_STATUS_OK || smtp_append_address_to_header(smtp, SMTP_ADDRESS_TO, "To") != SMTP_STATUS_OK || smtp_append_address_to_header(smtp, SMTP_ADDRESS_CC, "Cc") != SMTP_STATUS_OK) return smtp->status_code;
for (size_t i = 0; i < smtp->num_headers; ++i)
if (smtp_print_header(smtp, &smtp->header_list[i]) != SMTP_STATUS_OK) return smtp->status_code;
if (smtp_print_email(smtp, body)) return smtp->status_code;
46

smtp_lib/smtp_lib.c
// Конец сегмента DATA.
if (smtp_puts(smtp, ".\r\n") != SMTP_STATUS_OK) return smtp->status_code;
// DATA termination timeout 250 return code - 10 минут. smtp_set_read_timeout(smtp, 60 * 10);
if (smtp_read_and_parse_code(smtp) != SMTP_DONE)
return smtp_status_code_set(smtp, SMTP_STATUS_SERVER_RESPONSE); return smtp->status_code;
}
enum smtp_status_code smtp_close(struct smtp *smtp)
{
enum smtp_status_code status_code; status_code = smtp->status_code;
if (smtp->flags == SMTP_FLAG_INVALID_MEMORY) return status_code;
if (smtp->sock != -1)
{
// Ошибка не игнорируется: нужно освободить ресурсы клиента SMTP. smtp->status_code = SMTP_STATUS_OK;
smtp_puts(smtp, "QUIT\r\n"); #ifdef SMTP_OPENSSL
if (smtp->tls_on)
{
SSL_free(smtp->tls); SSL_CTX_free(smtp->tls_ctx);
}
#endif // SMTP_OPENSSL #ifdef SMTP_IS_WINDOWS
closesocket(smtp->sock); WSACleanup();
#else // POSIX
if (close(smtp->sock) < 0 && smtp->status_code == SMTP_STATUS_OK) smtp_status_code_set(smtp, SMTP_STATUS_CLOSE);
#endif // SMTP_IS_WINDOWS
}
smtp_str_getdelimfd_free(&smtp->gdfd); smtp_header_clear_all(smtp); smtp_address_clear_all(smtp); smtp_attachment_clear_all(smtp);
if (status_code == SMTP_STATUS_OK) status_code = smtp->status_code;
free(smtp);
return status_code;
}
enum smtp_status_code smtp_status_code_get(const struct smtp *const smtp)
{
return smtp->status_code;
}
enum smtp_status_code smtp_status_code_clear(struct smtp *const smtp)
{
enum smtp_status_code old_status = smtp_status_code_get(smtp); smtp_status_code_set(smtp, SMTP_STATUS_OK);
return old_status;
}
enum smtp_status_code smtp_status_code_set(struct smtp *const smtp,
enum smtp_status_code status_code)
{
if ((unsigned)status_code >= SMTP_STATUS__LAST)
return smtp_status_code_set(smtp, SMTP_STATUS_PARAM); smtp->status_code = status_code;
return status_code;
}
const char *smtp_status_code_errstr(enum smtp_status_code status_code)
{
const char *const status_code_err_str[] = { /* SMTP_STATUS_OK */
"Success",
/* SMTP_STATUS_NOMEM */ "Memory allocation failed", /* SMTP_STATUS_CONNECT */
"Failed to connect to the mail server", /* SMTP_STATUS_HANDSHAKE */
"Failed to handshake or negotiate a TLS connection with the server", /* SMTP_STATUS_AUTH */
"Failed to authenticate with the given credentials", /* SMTP_STATUS_SEND */
"Failed to send bytes to the server", /* SMTP_STATUS_RECV */
"Failed to receive bytes from the server", /* SMTP_STATUS_CLOSE */
47

smtp_lib/smtp_lib.c
"Failed to properly close a connection", /* SMTP_STATUS_SERVER_RESPONSE */
"SMTP server sent back an unexpected status code", /* SMTP_STATUS_PARAM */
"Invalid parameter", /* SMTP_STATUS_FILE */
"Failed to read or open a local file", /* SMTP_STATUS_DATE */
"Failed to get the local date and time", /* SMTP_STATUS__LAST */
"Unknown error"};
if ((unsigned)status_code > SMTP_STATUS__LAST) status_code = SMTP_STATUS__LAST;
return status_code_err_str[status_code];
}
enum smtp_status_code smtp_header_add(struct smtp *const smtp, const char *const key, const char *const value)
{
struct smtp_header *new_header_list, *new_header; size_t num_headers_inc;
if (smtp->status_code != SMTP_STATUS_OK) return smtp->status_code;
if (smtp_header_key_validate(key) < 0)
return smtp_status_code_set(smtp, SMTP_STATUS_PARAM); if (value && smtp_header_value_validate(value) < 0)
return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
if (smtp_si_add_size_t(smtp->num_headers, 1, &num_headers_inc) || (new_header_list = smtp_reallocarray(smtp->header_list, num_headers_inc,
sizeof(*smtp->header_list))) == NULL) return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
smtp->header_list = new_header_list;
new_header = &smtp->header_list[smtp->num_headers]; new_header->key = smtp_strdup(key); new_header->value = smtp_strdup(value);
if (new_header->key == NULL || (new_header->value == NULL && value))
{
free(new_header->key); free(new_header->value);
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
smtp->num_headers = num_headers_inc;
qsort(smtp->header_list, smtp->num_headers, sizeof(*smtp->header_list), smtp_header_cmp); return smtp->status_code;
}
void smtp_header_clear_all(struct smtp *const smtp)
{
for (size_t i = 0; i < smtp->num_headers; ++i)
{
struct smtp_header *header = &smtp->header_list[i]; free(header->key);
free(header->value);
}
free(smtp->header_list); smtp->header_list = NULL; smtp->num_headers = 0;
}
enum smtp_status_code smtp_address_add(struct smtp *const smtp, enum smtp_address_type type, const char *const email, const char *const name)
{
struct smtp_address *new_address_list, *new_address; size_t num_address_inc;
if (smtp->status_code != SMTP_STATUS_OK) return smtp->status_code;
if (smtp_address_validate_email(email) < 0)
return smtp_status_code_set(smtp, SMTP_STATUS_PARAM); if (name && smtp_address_validate_name(name) < 0)
return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
if (smtp_si_add_size_t(smtp->num_address, 1, &num_address_inc)) return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
new_address_list =
smtp_reallocarray(smtp->address_list, num_address_inc, sizeof(*new_address_list)); if (new_address_list == NULL)
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM); new_address = &new_address_list[smtp->num_address]; smtp->address_list = new_address_list;
new_address->type = type; new_address->email = smtp_strdup(email); new_address->name = smtp_strdup(name);
if (new_address->email == NULL || (new_address->name == NULL && name))
{
free(new_address->email); free(new_address->name);
48

smtp_lib/smtp_lib.c
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
smtp->num_address = num_address_inc; return smtp->status_code;
}
void smtp_address_clear_all(struct smtp *const smtp)
{
for (size_t i = 0; i < smtp->num_address; ++i)
{
struct smtp_address *address = &smtp->address_list[i]; free(address->email);
free(address->name);
}
free(smtp->address_list); smtp->address_list = NULL; smtp->num_address = 0;
}
enum smtp_status_code smtp_attachment_add_path(struct smtp *const smtp, const char *const name, const char *const path)
{
char *data;
size_t bytes_read;
if (smtp->status_code != SMTP_STATUS_OK) return smtp->status_code;
errno = 0;
if ((data = smtp_file_get_contents(path, &bytes_read)) == NULL)
return smtp_status_code_set(smtp, errno == ENOMEM ? SMTP_STATUS_NOMEM : SMTP_STATUS_FILE); smtp_attachment_add_mem(smtp, name, data, bytes_read);
free(data);
return smtp->status_code;
}
enum smtp_status_code smtp_attachment_add_fp(struct smtp *const smtp, const char *const name, FILE *fp)
{
char *data;
size_t bytes_read;
if (smtp->status_code != SMTP_STATUS_OK) return smtp->status_code;
errno = 0;
if ((data = smtp_ffile_get_contents(fp, &bytes_read)) == NULL)
return smtp_status_code_set(smtp, errno == ENOMEM ? SMTP_STATUS_NOMEM : SMTP_STATUS_FILE); smtp_attachment_add_mem(smtp, name, data, bytes_read);
free(data);
return 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)
{
size_t num_attachment_inc; char *b64_encode;
struct smtp_attachment *new_attachment_list, *new_attachment; if (smtp->status_code != SMTP_STATUS_OK)
return smtp->status_code;
if (smtp_attachment_validate_name(name) < 0)
return smtp_status_code_set(smtp, SMTP_STATUS_PARAM); if (datasz == SIZE_MAX)
datasz = strlen(data);
if (smtp_si_add_size_t(smtp->num_attachment, 1, &num_attachment_inc) || (new_attachment_list = smtp_reallocarray(smtp->attachment_list, num_attachment_inc,
sizeof(*new_attachment_list))) == NULL) return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
smtp->attachment_list = new_attachment_list;
new_attachment = &new_attachment_list[smtp->num_attachment]; new_attachment->name = smtp_strdup(name);
b64_encode = smtp_base64_encode(data, datasz);
if (new_attachment->name == NULL || b64_encode == NULL)
{
free(new_attachment->name); free(b64_encode);
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
new_attachment->b64_data = smtp_chunk_split(b64_encode, SMTP_LINE_MAX, "\r\n"); free(b64_encode);
if (new_attachment->b64_data == NULL)
{
free(new_attachment->name);
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
smtp->num_attachment = num_attachment_inc; return smtp->status_code;
}
49

smtp_lib/smtp_lib.c
void smtp_attachment_clear_all(struct smtp *const smtp)
{
for (size_t i = 0; i < smtp->num_attachment; ++i)
{
struct smtp_attachment *attachment = &smtp->attachment_list[i]; free(attachment->name);
free(attachment->b64_data);
}
free(smtp->attachment_list); smtp->attachment_list = NULL; smtp->num_attachment = 0;
}
Таблица 4. Сборочный файл SMTP-библиотеки «smtp_lib/WinMakefile» /
«smtp_lib/LinuxMakefile»
smtp_lib/WinMakefile
.PHONY : clean CC = gcc
CFLAGS = -O4 -std=c99 -Waggregate-return -Wall -Wbad-function-cast -Wcast-align -Wcast-qual \ -Wdeclaration-after-statement -Wdisabled-optimization -Wdouble-promotion -Werror \ -Wextra -Wfatal-errors -Wfloat-equal -Wframe-larger-than=5000 -Winit-self \ -Winline -Winvalid-pch -Wlarger-than=10000 -Wno-deprecated-declarations \ -Wmissing-include-dirs -Wnested-externs -Wno-aggressive-loop-optimizations \
-Wold-style-definition -Wpacked -Wpedantic -pedantic-errors -Wredundant-decls -Wshadow \ -Wstack-protector -Wstrict-aliasing -Wstrict-overflow=5 -Wstrict-prototypes \ -Wswitch-default -Wswitch-enum -Wundef -Wuninitialized -Wunknown-pragmas \ -Wunused-parameter -Wvla -Wwrite-strings -Wstack-usage=5000 -Wno-format \ -Wjump-misses-init -Wlogical-op -Wnormalized=nfkc -Wtrampolines \
-Wsync-nand -Wunsuffixed-float-constants -Wvector-operation-performance \ -fstack-protector-all -fstrict-overflow -MD -DSMTP_OPENSSL
all: smtp_lib.o
smtp_lib.o: smtp_lib.c smtp_lib.h
$(CC) $(CFLAGS) ./smtp_lib.c -c -I"C:\Program Files\OpenSSL-Win64\include" \
-I"C:\Program Files\OpenSSL-Win64\include\openssl" -lbio -lerr -lssl -lx509 -lx509v3
clean:
del /s /q smtp_lib.o smtp_lib.d
smtp_lib/LinuxMakefile
.PHONY : clean CC = gcc
CFLAGS = -O4 -std=c99 -Waggregate-return -Wall -Wbad-function-cast -Wcast-align -Wcast-qual \ -Wdeclaration-after-statement -Wdisabled-optimization -Wdouble-promotion -Werror \ -Wextra -Wfatal-errors -Wfloat-equal -Wframe-larger-than=5000 -Winit-self \ -Winline -Winvalid-pch -Wlarger-than=10000 -Wno-deprecated-declarations \ -Wmissing-include-dirs -Wnested-externs -Wno-aggressive-loop-optimizations \
-Wold-style-definition -Wpacked -Wpedantic -pedantic-errors -Wredundant-decls -Wshadow \ -Wstack-protector -Wstrict-aliasing -Wstrict-overflow=5 -Wstrict-prototypes \ -Wswitch-default -Wswitch-enum -Wundef -Wuninitialized -Wunknown-pragmas \ -Wunused-parameter -Wvla -Wwrite-strings -Wstack-usage=5000 -Wno-format \ -Wjump-misses-init -Wlogical-op -Wnormalized=nfkc -Wtrampolines \
-Wsync-nand -Wunsuffixed-float-constants -Wvector-operation-performance \ -fstack-protector-all -fstrict-overflow -MD -DSMTP_OPENSSL
all: smtp_lib.o
smtp_lib.o: smtp_lib.c smtp_lib.h
$(CC) $(CFLAGS) ./smtp_lib.c -c
clean:
rm -rf smtp_lib.o smtp_lib.d
50