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

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

smtp_lib/smtp_lib.c

if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) return -1;

#endif // SMTP_IS_WINDOWS memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = 0; hints.ai_protocol = IPPROTO_TCP;

if (getaddrinfo(server, port, &hints, &res0) != 0) return -1;

for (res = res0; res; res = res->ai_next)

{

smtp->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (smtp->sock < 0)

continue;

if (connect(smtp->sock, res->ai_addr, res->ai_addrlen) < 0)

{

#ifdef SMTP_IS_WINDOWS closesocket(smtp->sock);

#else // POSIX close(smtp->sock);

#endif // SMTP_IS_WINDOWS smtp->sock = -1;

}

else

break;

}

freeaddrinfo(res0);

return (smtp->sock < 0) ? -1 : 0;

}

#ifdef SMTP_OPENSSL

/** Инициализирует библиотеку TLS и устанавливает рукопожатие TLS с сервером

*через существующее соединение сокета.

*@param[in] smtp Контекст клиента SMTP.

*@param[in] server Имя сервера или IP-адрес.

* @retval

0

Удалось установить TLS-соединение с

сервером.

* @retval

-1

Не удалось установить TLS-соединение с сервером. */

static int

smtp_tls_init(struct smtp *const smtp,

const char *const server)

{

 

 

 

X509 *X509_cert_peer;

//Не нужно проверять возвращаемое значение, поскольку оно всегда возвращает 1.

SSL_library_init(); SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms();

if ((smtp->tls_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) return -1;

//Запрещаем использование SSLv2, SSLv3, и TLSv1.0.

SSL_CTX_set_options(smtp->tls_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1); SSL_CTX_set_mode(smtp->tls_ctx, SSL_MODE_AUTO_RETRY);

if ((smtp->flags & SMTP_NO_CERT_VERIFY) == 0) SSL_CTX_set_verify(smtp->tls_ctx, SSL_VERIFY_PEER, NULL);

/* Задает путь к предоставленному пользователем файлу CA

или использует пути к сертификатам по умолчанию, если они не указаны. */ if (smtp->cafile)

{

if (SSL_CTX_load_verify_locations(smtp->tls_ctx, smtp->cafile, NULL) != 1)

{

SSL_CTX_free(smtp->tls_ctx); return -1;

}

}

else

{

X509_STORE_set_default_paths(SSL_CTX_get_cert_store(smtp->tls_ctx)); if (ERR_peek_error() != 0)

{

SSL_CTX_free(smtp->tls_ctx); return -1;

}

}

if ((smtp->tls = SSL_new(smtp->tls_ctx)) == NULL)

{

SSL_CTX_free(smtp->tls_ctx); return -1;

}

if ((smtp->tls_bio = BIO_new_socket(smtp->sock, 0)) == NULL)

{

SSL_CTX_free(smtp->tls_ctx); SSL_free(smtp->tls);

return -1;

}

SSL_set_bio(smtp->tls, smtp->tls_bio, smtp->tls_bio); SSL_set_connect_state(smtp->tls);

36

smtp_lib/smtp_lib.c

if (SSL_connect(smtp->tls) != 1)

{

SSL_CTX_free(smtp->tls_ctx); SSL_free(smtp->tls);

return -1;

}

if (SSL_do_handshake(smtp->tls) != 1)

{

SSL_CTX_free(smtp->tls_ctx); SSL_free(smtp->tls);

return -1;

}

if ((smtp->flags & SMTP_NO_CERT_VERIFY) == 0)

{

if ((X509_cert_peer = SSL_get_peer_certificate(smtp->tls)) == NULL)

{

SSL_CTX_free(smtp->tls_ctx); SSL_free(smtp->tls);

return -1;

}

if (X509_check_host(X509_cert_peer, server, 0, 0, NULL) != 1)

{

SSL_CTX_free(smtp->tls_ctx); SSL_free(smtp->tls);

return -1;

}

X509_free(X509_cert_peer);

}

smtp->tls_on = 1; return 0;

}

#endif // SMTP_OPENSSL

/** Отправляет команду EHLO и анализирует ответы.

*Игнорирует все возвращаемые расширения сервера. Если сервер не поддерживает нужное нам

*расширение, то позже мы должны получить сообщение об ошибке, когда попытаемся использовать это

*расширение.

*@param[in] smtp Контекст клиента SMTP.

*@return См. @ref smtp_status_code. */

static enum smtp_status_code smtp_ehlo(struct smtp *const smtp)

{

if (smtp_puts(smtp, "EHLO smtp\r\n") == SMTP_STATUS_OK) smtp_read_and_parse_code(smtp);

return smtp->status_code;

}

/** Выполняет аутентификацию с помощью PLAIN метода.

*1. Задает для текста следующий формат: "\0<user>\0<password>",

*или как показано в строке формата: "\0%s\0%s", email, password.

*2. Base64 кодирует текст из (1).

*3. Отправляет созданный текст аутентификации из (2) на сервер:

*"AUTH PLAIN <b64><CR><NL>".

*@param[in] smtp Контекст клиента SMTP.

*@param[in] user Имя пользователя учетной записи SMTP.

*@param[in] pass Пароль учетной записи SMTP.

* @retval 0 Удалось пройти аутентификацию.

* @retval -1 Не удалось пройти аутентификацию. */

static int smtp_auth_plain(struct smtp *const smtp, const char *const user, const char *const pass)

{

size_t user_len, pass_len, login_len, login_b64_len; char *login_str, *login_b64, *login_send, *concat; // (1)

user_len = strlen(user); pass_len = strlen(pass);

// login_len = 1 + user_len + 1 + pass_len.

if (smtp_si_add_size_t(user_len, pass_len, &login_len) ||

smtp_si_add_size_t(login_len, 2, &login_len) || (login_str = malloc(login_len)) == NULL) return -1;

login_str[0] = '\0'; memcpy(&login_str[1], user, user_len); login_str[1 + user_len] = '\0';

memcpy(&login_str[1 + user_len + 1], pass, pass_len); // (2)

login_b64 = smtp_base64_encode(login_str, login_len); free(login_str);

if (login_b64 == NULL) return -1;

// (3)

login_b64_len = strlen(login_b64);

if (smtp_si_add_size_t(login_b64_len, 14, &login_b64_len) || (login_send = malloc(login_b64_len)) == NULL)

{

free(login_b64); return -1;

}

37

smtp_lib/smtp_lib.c

concat = smtp_stpcpy(login_send, "AUTH PLAIN "); concat = smtp_stpcpy(concat, login_b64); smtp_stpcpy(concat, "\r\n");

free(login_b64); smtp_puts(smtp, login_send); free(login_send);

if (smtp->status_code != SMTP_STATUS_OK) return -1;

if (smtp_read_and_parse_code(smtp) != SMTP_AUTH_SUCCESS) return -1;

return 0;

}

/** Выполняет аутентификацию с помощью метода LOGIN.

*1. Base64 кодирует имя пользователя.

*2. Отправляет строку из (1) как часть входа в систему:

*"AUTH LOGIN <b64_username><CR><NL>".

*3. Base64 кодирует пароль и отправляет его отдельно в отдельной строке:

*"<b64_password><CR><NL>".

*@param[in] smtp Контекст клиента SMTP.

*@param[in] user Имя пользователя учетной записи SMTP.

*@param[in] pass Пароль учетной записи SMTP.

* @retval 0 Удалось пройти аутентификацию.

* @retval -1 Не удалось пройти аутентификацию. */

static int smtp_auth_login(struct smtp *const smtp, const char *const user, const char *const pass)

{

char *b64_user, *b64_pass, *login_str, *concat; size_t b64_user_len;

// (1)

if ((b64_user = smtp_base64_encode(user, SIZE_MAX)) == NULL) return -1;

// (2)

b64_user_len = strlen(b64_user);

if (smtp_si_add_size_t(b64_user_len, 14, &b64_user_len) || (login_str = malloc(b64_user_len)) == NULL)

{

free(b64_user); return -1;

}

concat = smtp_stpcpy(login_str, "AUTH LOGIN "); concat = smtp_stpcpy(concat, b64_user); smtp_stpcpy(concat, "\r\n");

free(b64_user); smtp_puts(smtp, login_str); free(login_str);

if (smtp->status_code != SMTP_STATUS_OK) return -1;

if (smtp_read_and_parse_code(smtp) != SMTP_AUTH_CONTINUE) return -1;

// (3)

if ((b64_pass = smtp_base64_encode(pass, SIZE_MAX)) == NULL) return -1;

smtp_puts_terminate(smtp, b64_pass); free(b64_pass);

if (smtp->status_code != SMTP_STATUS_OK) return -1;

if (smtp_read_and_parse_code(smtp) != SMTP_AUTH_SUCCESS) return -1;

return 0;

}

/** Устанавливает тайм-аут для следующей операции чтения сокета.

*@param[in] smtp Контекст клиента SMTP.

*@param[in] seconds Тайм-аут в секундах. */

static void smtp_set_read_timeout(struct smtp *const smtp, long seconds)

{

smtp->timeout_sec = seconds;

}

/** Выполняет рукопожатие с сервером SMTP, которое включает в себя

*необязательную настройку TLS и отправку приветствия EHLO.

*На этом этапе клиент уже подключился к SMTP-серверу через свое сокетное соединение.

*В этой функции клиент:

*1. При желании преобразует соединение в TLS ( @ref SMTP_SECURITY_TLS ).

*2. Читает начальное приветствие сервера.

*3. Отправляет EHLO на сервер.

*4. При необходимости запускает STARTTLS и повторно отправляет EHLO

*( @ref SMTP_SECURITY_STARTTLS ).

*

@param[in]

smtp

Контекст клиента SMTP.

*

@param[in]

server

Имя сервера или IP-адрес.

*@param[in] connection_security См. @ref smtp_connection_security.

*@return См. @ref smtp_status_code. */

static enum smtp_status_code

smtp_initiate_handshake(struct smtp *const smtp, const char *const server, enum smtp_connection_security connection_security)

38

smtp_lib/smtp_lib.c

{

//Избежание предупреждений о неиспользуемых переменных

(void)server; (void)connection_security;

//(1)

#ifdef SMTP_OPENSSL

if (connection_security == SMTP_SECURITY_TLS && smtp_tls_init(smtp, server) < 0) return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE);

#endif // SMTP_OPENSSL

//(2)

//Получает начальное сообщение 220 - таймаут 5 минут smtp_set_read_timeout(smtp, 60 * 5);

if (smtp_getline(smtp) == STRING_GETDELIMFD_ERROR) return smtp->status_code;

//(3)

if (smtp_ehlo(smtp) != SMTP_STATUS_OK) return smtp->status_code;

#ifdef SMTP_OPENSSL // (4)

if (connection_security == SMTP_SECURITY_STARTTLS)

{

if (smtp_puts(smtp, "STARTTLS\r\n") != SMTP_STATUS_OK) return smtp->status_code;

if (smtp_read_and_parse_code(smtp) != SMTP_READY)

return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE); if (smtp_tls_init(smtp, server) < 0)

return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE); if (smtp_ehlo(smtp) != SMTP_STATUS_OK)

return smtp->status_code;

}

#endif // SMTP_OPENSSL return smtp->status_code;

}

/** Максимальный размер строки даты RFC 2822.

@verbatim

Thu, 21 May 1998 05:33:29 -0700 12345678901234567890123456789012

10 20 30 32 (bytes) @endverbatim

Добавляет больше байтов к максимальному размеру 32, чтобы отключить предупреждение компилятора о вычисленном смещении. */

#define SMTP_DATE_MAX_SZ (32 + 15)

#ifndef SMTP_IS_WINDOWS

/** Преобразует системное время в секундах в дату и всемирное координированное время (UTC).

*Результат помещается в структуру типа tm, на которую указывает @p __tp .

*Функция возвращает указатель на эту структуру. */

extern struct tm *gmtime_r(const time_t *__restrict __timer, struct tm *__restrict __tp) __THROW;

/** Преобразует системное время в секундах в дату и местное время.

*Результат помещается в структуру типа tm, на которую указывает @p __tp .

*Функция возвращает указатель на эту структуру. */

extern struct tm *localtime_r(const time_t *__restrict __timer, struct tm *__restrict __tp) __THROW; #endif

/** Преобразует время в строку в формате RFC 2822.

*Пример формата даты:

*Thu, 21 May 1998 05:33:29 -0700

*@param[out] date Буфер размером не менее @ref SMTP_DATE_MAX_SZ байт.

* @retval

0

Успешно установлена текущая дата в буфер.

* @retval

-1

Не удалось установить текущую дату в буфер или произошла ошибка формата вывода. */

static int

smtp_date_rfc_2822(char *const date)

{

 

 

time_t

t,

t_local, t_utc;

struct

tm

tm_local, tm_utc;

long offset_utc;

double

diff_local_utc;

int rc;

 

 

const char weekday_abbreviation[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; const char month_abbreviation[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",

"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

if ((t = time(NULL)) == (time_t)(-1)) return -1;

#ifdef SMTP_IS_WINDOWS

if (localtime_s(&tm_local, &t) || gmtime_s(&tm_utc, &t)) return -1;

#else // POSIX

#ifdef SMTP_TIME_NO_REENTRANT struct tm *tm;

//localtime() не потокобезопасный. if ((tm = localtime(&t)) == NULL)

return -1;

memcpy(&tm_local, tm, sizeof(tm_local));

//gmtime() не потокобезопасный.

39

smtp_lib/smtp_lib.c

if ((tm = gmtime(&t)) == NULL) return -1;

memcpy(&tm_utc, tm, sizeof(tm_utc));

#else // Реентерабельные версии: localtime_r() and gmtime_r().

if (localtime_r(&t, &tm_local) == NULL || gmtime_r(&t, &tm_utc) == NULL) return -1;

#endif // SMTP_TIME_NO_REENTRANT #endif // SMTP_IS_WINDOWS

if ((t_local = mktime(&tm_local)) == (time_t)(-1)) return -1;

if ((t_utc = mktime(&tm_utc)) == (time_t)(-1)) return -1;

/* После вычисления смещения оно будет содержать максимум 4 цифры. Например, часовой пояс PST будет иметь смещение -800,

которое будет отформатировано как -0800 в приведенном ниже вызове sprintf. */ diff_local_utc = difftime(t_local, t_utc);

offset_utc = (long)diff_local_utc; offset_utc = offset_utc / 60 / 60 * 100;

rc = sprintf(date, "%s, %02d %s %d %02d:%02d:%02d %0+5ld", weekday_abbreviation[tm_local.tm_wday], tm_local.tm_mday, month_abbreviation[tm_local.tm_mon], tm_local.tm_year + 1900, tm_local.tm_hour, tm_local.tm_min, tm_local.tm_sec, /* 0 - 60 (leap second) */

offset_utc);

return (rc + 1 != SMTP_DATE_MAX_SZ - 15) ? -1 : 0; // См. SMTP_DATE_MAX_SZ для -5.

}

/** Функция поиска, используемая bsearch, позволяющая вызывающей стороне

*проверять заголовки с существующими ключами.

*@param v1 Строка для поиска в списке.

*@param v2 @ref smtp_header для сравнения.

* @retval

0

Если ключи совпадают.

* @retval

!0

Если ключи не совпадают. */

static int

smtp_header_cmp_key(const void *const v1, const void *const v2)

{

 

 

const char *key;

const struct smtp_header *header2;

key = v1;

 

header2 =

v2;

return

strcmp(key, header2->key);

}

 

 

/** Определяет, был ли уже определен ключ заголовка в этом контексте. * @param[in] smtp Контекст клиента SMTP.

* @param[in] key Ключ заголовка для поиска.

*@retval 1 Если заголовок уже существует в этом контексте.

*@retval 0 Если заголовок не существует в этом контексте. */

static int smtp_header_exists(const struct smtp *const smtp, const char *const key)

{

return bsearch(key, smtp->header_list, smtp->num_headers, sizeof(*smtp->header_list), smtp_header_cmp_key) != NULL;

}

/** Минимальная длина буфера, необходимая для проведения граничного теста MIME.

*mimeXXXXXXXXXX

*123456789012345

* 1 10 15 bytes */ #define SMTP_MIME_BOUNDARY_LEN 15

/** Создает граничное текстовое поле MIME и сохраняет его в пользовательском буфере.

*Например:

*mimeXXXXXXXXXX

*где каждому X присваивается псевдослучайный символ ASCII в верхнем регистре.

*Здесь используется простой генератор псевдослучайных чисел.

*@param[out] boundary Буфер размером не менее @ref SMTP_MIME_BOUNDARY_LEN байт. */ static void smtp_gen_mime_boundary(char *const boundary)

{

unsigned int seed = (unsigned int)time(NULL); srand(seed);

strcpy(boundary, "mime");

for (size_t i = 4; i < SMTP_MIME_BOUNDARY_LEN - 1; ++i)

//Смещение по модулю в порядке, так как нам нужно только предотвратить случайную коллизию. boundary[i] = rand() % 26 + 'A';

boundary[SMTP_MIME_BOUNDARY_LEN - 1] = '\0';

}

/** Печать заголовка MIME и раздела MIME, содержащего тело письма.

* @param[in] smtp

Контекст клиента SMTP.

*

@param[in]

boundary

Граничный текст MIME.

*

@param[in]

body_dd

Текст письма с двойными точками ("..") в начале каждой строки.

* @return См. @ref smtp_status_code. */

static enum smtp_status_code smtp_print_mime_header_and_body(struct smtp *const smtp, const char *const boundary, const char *const body_dd)

{

// Размер буфера для статического текста MIME, используемого ниже.

40