
7 семестр / Готовые отчеты / ММиВА. Лабораторная работа 4
.pdf
Таблица 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