Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

7 семестр / Готовые отчеты / ММиВА. Лабораторная работа 4

.pdf
Скачиваний:
22
Добавлен:
23.12.2021
Размер:
3.5 Mб
Скачать

Таблица 5. Файл SMTP-клиента «smtp_client.c»

smtp_client.c

/** @file

*@brief SMTP client example

*@author Kovalenko Leonid

*@version 1.00

*

*Пример использования клиентской библиотеки SMTP,

*которая позволяет пользователю отправлять

*электронные письма на сервер SMTP.

*/

#include "./smtp_lib/smtp_lib.h" #include <stdio.h>

#include <stdlib.h> #include <string.h>

///Максимальная длина имени файла

#define FILENAME_LENGTH 256

///Максимальная длина параметра/его значения

#define PARAM_LENGTH 300

///Максимальная длина заголовка письма

#define SUBJECT_LENGTH 500

///Максимальная длина тела письма

#define BODY_LENGTH 2000

/** Возвращает строковое представление метода шифрования соединения.

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

*@return Вызывающая сторона не должна освобождать или изменять эту строку. */

const char *str_connection_security(const enum smtp_connection_security conn_security)

{

switch (conn_security)

{

case SMTP_SECURITY_NONE: return "NONE";

case SMTP_SECURITY_STARTTLS: return "STARTTLS";

case SMTP_SECURITY_TLS: return "TLS";

default:

return "UNKNOWN";

}

}

/** Возвращает строковое представление метода аутентификации учетной записи пользователя.

*@param[in] auth_method См. @ref smtp_authentication_method.

*@return Вызывающая сторона не должна освобождать или изменять эту строку. */

const char *str_authentication_method(const enum smtp_authentication_method auth_method)

{

switch (auth_method)

{

case SMTP_AUTH_NONE: return "NONE";

case SMTP_AUTH_PLAIN: return "PLAIN";

case SMTP_AUTH_LOGIN: return "LOGIN";

default:

return "UNKNOWN";

}

}

/** Возвращает строковое представление флагов контекста клиента SMTP.

*@param[in] flag См. @ref smtp_flag.

*@return Вызывающая сторона не должна освобождать или изменять эту строку. */ const char *str_flag(const enum smtp_flag flag)

{

if ((flag & SMTP_DEBUG) && (flag & SMTP_NO_CERT_VERIFY)) return "DEBUG and NO_CERT_VERIFY";

else if (flag & SMTP_DEBUG) return "DEBUG";

else if (flag & SMTP_NO_CERT_VERIFY) return "NO_CERT_VERIFY";

return "NONE";

}

/** Проверяет статус операции @p status .

*Если @ref SMTP_STATUS_OK , то возвращает 1.

*Если не @ref SMTP_STATUS_OK , то пишет сообщение об ошибке в stderr и возвращает 0.

*@param[in] status Статус операции.

*@retval 1 OK.

*@retval 0 Ошибка. */

int check_status(const int status)

{

if (status != SMTP_STATUS_OK)

51

smtp_client.c

{

fprintf(stderr, "smtp failed: %s\n", smtp_status_code_errstr(status)); return 0;

}

return 1;

}

/** Возвращает указатель на позицию с именем файла в @p path .

*/Folder/File.txt -> File.txt

*@param[in] path Путь к файлу.

*@return Указатель на позицию в @p path . */

const char *get_filename_from_path(const char *const path)

{

const char *prev = path, *current = NULL;

while ((current = strchr(path, (int)'\\')) || (current = strchr(path, (int)'/')))

{

prev = current; continue;

}

return prev;

}

/** Пропускает символы в потоке stdin после того,

*как на вызываемой стороне была считана строка @p str

*с максимальной длиной @p max_length .

*@param[in] str Строка.

*@param[in] max_length Максимальная длина строки @p str . */

void stdin_clear_if_needed(const char *const str, const size_t max_length)

{

if (str != NULL && strchr(str, (int)'\n') == NULL && (max_length == SIZE_MAX || strlen(str) < max_length)) while (fgetc(stdin) != '\n')

continue;

}

/** Заменяет символ @p from на символ @p to в строке @p str .

*@param[in,out] str Строка для изменения.

*@param[in] from Символ, который нужно заменить.

*@param[in] to Символ, на который нужно заменить. */

void replace_char(char *const str, const char from, const char to)

{

char *current;

while ((current = strchr(str, (int)from)) != NULL) *current = to;

}

/** Считывает строку @p buffer с максимальной длиной @p max_length с консоли.

*@param[out] buffer Результирующая строка.

*@param[in] max_length Максимальная длина результирующей строки. */ void read_string_from_console(char *const buffer, const size_t max_length)

{

fgets(buffer, max_length, stdin); stdin_clear_if_needed(buffer, max_length); replace_char(buffer, '\n', '\0');

}

/** Считывает текст @p buffer с максимальной длиной @p max_length с консоли

*с учетом максимального числа пустых строк @p max_blank_lines .

*@param[out] buffer Результирующий текст.

*@param[in] max_length Максимальная длина результирующего текста.

*@param[in] max_blank_lines Максимальное число пустых строк. */

void read_text_from_console(char *const buffer, const size_t max_length, const int max_blank_lines)

{

char *current_pos = buffer;

size_t remains_to_write = max_length;

for (int blank_line = 0; blank_line < max_blank_lines && remains_to_write > 0;)

{

const size_t length = strlen(fgets(current_pos, remains_to_write, stdin)); if (length == 1 && current_pos[0] == '\n')

++blank_line;

else

blank_line = 0; remains_to_write -= length; current_pos += length;

}

if (remains_to_write == 0)

stdin_clear_if_needed(current_pos - (current_pos == buffer), -1);

}

/** Ввод сервера с консоли.

*@param[out] argument Аргумент для записи.

*@param[in] max_length Максимальная длина. */

void change_server(char *const argument, const size_t max_length)

{

printf("Enter server: ");

52

smtp_client.c

read_string_from_console(argument, max_length);

}

/** Ввод порта с консоли.

*@param[out] argument Аргумент для записи.

*@param[in] max_length Максимальная длина. */

void change_port(char *const argument, const size_t max_length)

{

do

{

printf("Enter port (10-999: 25 and 587 for STARTTLS | 465 for TLS): "); read_string_from_console(argument, max_length);

} while (

argument[0] < '0' || argument[0] > '9' || argument[1] < '0' || argument[1] > '9' || (argument[2] != '\0' && (argument[2] < '0' || argument[2] > '9' || argument[3] != '\0')));

}

/** Ввод имени пользователя с консоли.

*@param[out] argument Аргумент для записи.

*@param[in] max_length Максимальная длина. */

void change_name(char *const argument, const size_t max_length)

{

printf("Enter name: "); read_string_from_console(argument, max_length);

}

/** Ввод email пользователя с консоли.

*@param[out] argument Аргумент для записи.

*@param[in] max_length Максимальная длина. */

void change_user(char *const argument, const size_t max_length)

{

printf("Enter user email: "); read_string_from_console(argument, max_length);

}

/** Ввод пароля пользователя с консоли.

*@param[out] argument Аргумент для записи.

*@param[in] max_length Максимальная длина. */

void change_pass(char *const argument, const size_t max_length)

{

printf("Enter user password: "); read_string_from_console(argument, max_length);

}

/** Ввод метода шифрования соединения.

* @param[out] argument Аргумент для изменения. */

void change_connection_security(enum smtp_connection_security *const argument)

{

char buffer[PARAM_LENGTH]; while (1)

{

printf("Choose connection security:\n"); printf("-- STARTTLS (25 and 587 ports)\n"); printf("-- TLS (465 port)\n");

printf("-- NONE\n"); printf("#> ");

read_string_from_console(buffer, sizeof(buffer) / sizeof(buffer[0])); if (strcmp(buffer, "STARTTLS") == 0 || strcmp(buffer, "starttls") == 0)

{

*argument = SMTP_SECURITY_STARTTLS; return;

}

else if (strcmp(buffer, "TLS") == 0 || strcmp(buffer, "tls") == 0)

{

*argument = SMTP_SECURITY_TLS; return;

}

else if (strcmp(buffer, "NONE") == 0 || strcmp(buffer, "none") == 0)

{

*argument = SMTP_SECURITY_NONE; return;

}

}

}

/** Ввод метода аутентификации учетной записи пользователя. * @param[out] argument Аргумент для изменения. */

void change_authentication_method(enum smtp_authentication_method *const argument)

{

char buffer[PARAM_LENGTH]; while (1)

{

printf("Choose authentication method:\n"); printf("-- NONE\n");

printf("-- PLAIN\n");

53

smtp_client.c

printf("-- LOGIN\n"); printf("#> ");

read_string_from_console(buffer, sizeof(buffer) / sizeof(buffer[0])); if (strcmp(buffer, "NONE") == 0 || strcmp(buffer, "none") == 0)

{

*argument = SMTP_AUTH_NONE; return;

}

else if (strcmp(buffer, "PLAIN") == 0 || strcmp(buffer, "plain") == 0)

{

*argument = SMTP_AUTH_PLAIN; return;

}

else if (strcmp(buffer, "LOGIN") == 0 || strcmp(buffer, "login") == 0)

{

*argument = SMTP_AUTH_LOGIN; return;

}

}

}

/** Ввод флагов контекста клиента SMTP.

* @param[out] argument Аргумент для изменения. */ void change_flag(enum smtp_flag *const argument)

{

char buffer[PARAM_LENGTH]; while (1)

{

printf("Debug flag (true|false): ");

read_string_from_console(buffer, sizeof(buffer) / sizeof(buffer[0])); if (strcmp(buffer, "TRUE") == 0 || strcmp(buffer, "true") == 0)

{

*argument |= SMTP_DEBUG; break;

}

else if (strcmp(buffer, "FALSE") == 0 || strcmp(buffer, "false") == 0)

{

*argument &= ~SMTP_DEBUG; break;

}

}

while (1)

{

printf("No cert. verify flag (true|false): "); read_string_from_console(buffer, sizeof(buffer) / sizeof(buffer[0])); if (strcmp(buffer, "TRUE") == 0 || strcmp(buffer, "true") == 0)

{

*argument |= SMTP_NO_CERT_VERIFY; break;

}

else if (strcmp(buffer, "FALSE") == 0 || strcmp(buffer, "false") == 0)

{

*argument &= ~SMTP_NO_CERT_VERIFY; break;

}

}

}

/** Точка входа программы-примера

* @return Код ошибки */ int main(void)

{

// Инициализация параметров int r;

struct smtp *smtp;

char cfg_mail_server[PARAM_LENGTH] = "smtp.yandex.ru", cfg_mail_port[4] = "465", cfg_mail_name[PARAM_LENGTH] = "Robert Wayne ALX", subject[SUBJECT_LENGTH], cfg_mail_user[PARAM_LENGTH] = "robert.wayne.alx@yandex.ru", attachment[FILENAME_LENGTH], cfg_mail_pass[PARAM_LENGTH] = "Robw12345", body[BODY_LENGTH], command[PARAM_LENGTH], cfg_mail_to_user[PARAM_LENGTH], continue_y_n[2] = "n";

const char *const cafile = NULL;

enum smtp_connection_security cfg_conn_security = SMTP_SECURITY_TLS; enum smtp_authentication_method cfg_auth_method = SMTP_AUTH_PLAIN; enum smtp_flag cfg_flag = (SMTP_DEBUG | SMTP_NO_CERT_VERIFY); printf("=== Simple program for sending mail messages ===\n");

while (/* always true */ 1)

{

// Вывод параметров printf("----------------------------------------------\n"); printf("Server: %s | Port: %s\n", cfg_mail_server, cfg_mail_port);

printf("Name: %s | Email: %s | Password: %s\n", cfg_mail_name, cfg_mail_user, cfg_mail_pass);

printf("Connection security: %s | Authentication method: %s\n", str_connection_security(cfg_conn_security), str_authentication_method(cfg_auth_method));

54

smtp_client.c

printf("Flag: %s\n", str_flag(cfg_flag)); printf("-------------\nCommands:\n"); printf("-- start\n");

printf("-- change server|port|name|email|pass|conn_security|auth_method|flag\n"); printf("-- exit\n");

printf("-------------\n#>");

read_string_from_console(command, sizeof(command) / sizeof(command[0]));

//Параметры можно менять, вводя соответствующие команды if (strcmp(command, "change server") == 0)

change_server(cfg_mail_server, sizeof(cfg_mail_server) / sizeof(cfg_mail_server[0])); else if (strcmp(command, "change port") == 0)

change_port(cfg_mail_port, sizeof(cfg_mail_port) / sizeof(cfg_mail_port[0])); else if (strcmp(command, "change name") == 0)

change_name(cfg_mail_name, sizeof(cfg_mail_name) / sizeof(cfg_mail_name[0])); else if (strcmp(command, "change email") == 0)

change_user(cfg_mail_user, sizeof(cfg_mail_user) / sizeof(cfg_mail_user[0])); else if (strcmp(command, "change pass") == 0)

change_pass(cfg_mail_pass, sizeof(cfg_mail_pass) / sizeof(cfg_mail_pass[0])); else if (strcmp(command, "change conn_security") == 0)

change_connection_security(&cfg_conn_security); else if (strcmp(command, "change auth_method") == 0)

change_authentication_method(&cfg_auth_method); else if (strcmp(command, "change flag") == 0)

change_flag(&cfg_flag);

//Выход из программы -- exit

else if (strcmp(command, "exit") == 0) return EXIT_SUCCESS;

// После настройки параметров можно отправлять письма -- start else if (strcmp(command, "start") == 0)

{

while (/* always true */ 1)

{

// Открываем соединение с SMTP сервером printf("-------------\n");

r = smtp_open(cfg_mail_server, cfg_mail_port, cfg_conn_security, cfg_flag, cafile, &smtp);

if (!check_status(r))

break; // Если не получилось, то выход в начало // Проходим аутентификацию по email и паролю

r = smtp_auth(smtp, cfg_auth_method, cfg_mail_user, cfg_mail_pass); if (!check_status(r))

break; // Если не получилось, то выход в начало

//Добавляем адрес и имя пользователя в письмо в качестве данных отправителя r = smtp_address_add(smtp, SMTP_ADDRESS_FROM, cfg_mail_user, cfg_mail_name); if (!check_status(r))

continue; // Если не получилось, то повтор цикла

//Добавление адресов получателей

for (size_t i = 1; /* always true */ 1;)

{

printf("Address to [%zu] or none: ", i); read_string_from_console(cfg_mail_to_user, sizeof(cfg_mail_to_user) /

sizeof(cfg_mail_to_user[0]));

// Если число символов <= 5 или символ '@' не найден, то прерывание цикла

if (strlen(cfg_mail_to_user) <= 5 || strchr(cfg_mail_to_user, (int)'@') == NULL) break;

r = smtp_address_add(smtp, (i == 1) ? SMTP_ADDRESS_TO : SMTP_ADDRESS_CC, cfg_mail_to_user, cfg_mail_to_user);

if (check_status(r)) // Если ОК, то

++i; // Переходим к следующему потенциальному адресу else // Иначе сбрасываем состояние и повторяем цикл

smtp_status_code_clear(smtp);

}

// Добавление скрытых адресов получателей for (size_t i = 1; /* always true */ 1;)

{

printf("BCC Address to [%zu] or nil: ", i); read_string_from_console(cfg_mail_to_user, sizeof(cfg_mail_to_user) /

sizeof(cfg_mail_to_user[0]));

// Если число символов <= 5 или символ '@' не найден, то прерывание цикла

if (strlen(cfg_mail_to_user) <= 5 || strchr(cfg_mail_to_user, (int)'@') == NULL) break;

r = smtp_address_add(smtp, SMTP_ADDRESS_BCC, cfg_mail_to_user, cfg_mail_to_user);

if (check_status(r)) // Если ОК, то

++i; // Переходим к следующему потенциальному адресу else // Иначе сбрасываем состояние и повторяем цикл

smtp_status_code_clear(smtp);

}

//Ввод темы письма printf("Subject: ");

read_string_from_console(subject, sizeof(subject) / sizeof(subject[0])); r = smtp_header_add(smtp, "Subject", subject);

if (!check_status(r))

continue; // Если не получилось, то повтор цикла

//Добавление вложений в письмо

55

smtp_client.c

for (size_t i = 1; /* always true */ 1;)

{

printf("Attachment [%zu] or nil: ", i); // Чтение названия файла с консоли read_string_from_console(attachment,

sizeof(attachment) / sizeof(attachment[0])); if (strlen(attachment) <= 3)

break; // Если длина пути файла <= 3, то прерывание цикла

r = smtp_attachment_add_path(smtp, get_filename_from_path(attachment), attachment);

if (check_status(r)) // Если ОК, то

++i; // Переходим к следующему потенциальному вложению else // Иначе сбрасываем состояние и повторяем цикл

smtp_status_code_clear(smtp);

}

// Ввод тела письма с консоли

printf("Body (3 blank lines mean the end):\n"); read_text_from_console(body, sizeof(body) / sizeof(body[0]),

/* max_blank_lines */ 3); r = smtp_mail(smtp, body); // Отправление письма if (!check_status(r))

break; // Если не получилось, то выход в начало

//Завершение соединения r = smtp_close(smtp); if (!check_status(r))

break; // Если не получилось, то выход в начало

//Нужно ли отослать ещё одно письмо (y)

printf("Send another email? [y/n]: "); read_string_from_console(&continue_y_n[0],

sizeof(continue_y_n) / sizeof(continue_y_n[0])); if (continue_y_n[0] != 'y' && continue_y_n[0] != 'Y')

break;

}

 

printf("----------------------------------------------

\n");

printf("----------------------------------------------

\n");

}

 

else

 

printf("Unknown command\n");

 

}

 

return EXIT_SUCCESS;

 

}

56

Таблица 6. Общий сборочный файл «WinMakefile» / «LinuxMakefile»

WinMakefile

.PHONY : clean remake CC = gcc

CFLAGS = -O2 -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/smtp_lib.o smtp_client.o

./smtp_lib/smtp_lib.o: ./smtp_lib/smtp_lib.h ./smtp_lib/smtp_lib.c

cd smtp_lib && make --file=WinMakefile && cd ../ && make remake --file=WinMakefile

.c.o: .c

$(CC) $(CFLAGS) -c -o $(?:.c=.o) $? -I./smtp_lib/

$(CC) $(CFLAGS) -o $(?:.c=.exe) $(?:.c=.o) ./smtp_lib/smtp_lib.o \ -L"C:\Program Files\OpenSSL-Win64\lib" -llibssl -llibcrypto -lWs2_32

clean:

del /s /q smtp_client.o smtp_client.d smtp_client.exe remake: clean all

LinuxMakefile

.PHONY : clean remake CC = gcc

CFLAGS = -O2 -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/smtp_lib.o smtp_client.o

./smtp_lib/smtp_lib.o: ./smtp_lib/smtp_lib.h ./smtp_lib/smtp_lib.c

cd smtp_lib && make --file=LinuxMakefile && cd ../ && make remake --file=LinuxMakefile

.c.o: .c

$(CC) $(CFLAGS) -c -o $(?:.c=.o) $? -I./smtp_lib/

$(CC) $(CFLAGS) -o $(?:.c=) $(?:.c=.o) ./smtp_lib/smtp_lib.o -lssl -lcrypto

clean:

rm -rf smtp_client.o smtp_client.d smtp_client remake: clean all

57