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

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

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

'" <'

EMAIL

> \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