#include #include #include #include "server_internal.hxx" struct SocketData { asio::streambuf socket_buf; std::istream socket_istream{&socket_buf}; std::string last_send; }; struct Exception : public std::exception { explicit Exception(std::string msg) : msg(std::move(msg)) {} [[nodiscard]] const char* what() const noexcept override { return msg.c_str(); } std::string msg; }; template void expect_code(SocketData &data, Socket &s, const std::string& code) { std::string line; do { std::string buf; asio::read_until(s, data.socket_buf, "\n"); std::getline(data.socket_istream, line, '\n'); } while (line[3] == '-'); if (std::string_view{line}.substr(0, 3) != code) throw Exception{"Request: '" + data.last_send + "' Expected: " + code + " got: " + line}; } template void send(SocketData &data, Socket &s, std::string l) { data.last_send = l.substr(0, l.find_first_of('\r')); l += "\r\n"; asio::write(s, asio::buffer(l, l.size())); } std::string get_date() { char buf[80]; auto t = std::time(nullptr); auto ti = localtime (&t); std::strftime(buf, sizeof(buf), "%a, %d %B %Y %T %z", ti);//Format time as string return std::string{buf}; } std::string get_hostname() { try { return asio::ip::host_name(); } catch (std::exception &_) { return ""; } } struct CredMan : public Botan::Credentials_Manager { Botan::System_Certificate_Store str; CredMan() = default; std::vector trusted_certificate_authorities(const std::string&, const std::string&) override { return {&str}; } }; struct Policy : public Botan::TLS::Strict_Policy { [[nodiscard]] bool require_cert_revocation_info() const override { return false; } }; void Server::send_mail(const std::string &email, const std::string &title, const std::string &body) { static std::string host_name = get_hostname(); try { std::string msg = fmt::format( "Date: {}\r\n" "To: <{}>\r\n" "From: MFileserver <{}>\r\n" "Subject: {}\r\n" "\r\n" "{}\r\n.", get_date(), email, config.smtp_from, title, body ); asio::io_service ctx; auto ssl_ctx = std::make_shared( std::make_shared(), #if defined(BOTAN_HAS_SYSTEM_RNG) std::make_unique(), #else std::make_unique(), #endif std::make_shared(), std::make_shared() ); asio::ip::tcp::socket s{ctx}; asio::ip::tcp::resolver res{ctx}; SocketData data; asio::connect(s, res.resolve(config.smtp_host, std::to_string(config.smtp_port))); expect_code(data, s, "220"); send(data, s, "EHLO " + host_name); expect_code(data, s, "250"); send(data, s, "STARTTLS"); expect_code(data, s, "220"); // switch_to_ssl Botan::TLS::Stream ss(std::move(s), ssl_ctx); ss.handshake(Botan::TLS::Connection_Side::Client); send(data, ss, "EHLO " + host_name); expect_code(data, ss, "250"); send(data, ss, "AUTH LOGIN"); expect_code(data, ss, "334"); send(data, ss, Botan::base64_encode((std::uint8_t*)config.smtp_user.data(), config.smtp_user.size())); expect_code(data, ss, "334"); send(data, ss, Botan::base64_encode((std::uint8_t*)config.smtp_pass.data(), config.smtp_pass.size())); expect_code(data, ss, "235"); send(data, ss, "MAIL FROM:<" + config.smtp_from + ">"); expect_code(data, ss, "250"); send(data, ss, "RCPT TO:<" + email + ">"); expect_code(data, ss, "250"); send(data, ss, "DATA"); expect_code(data, ss, "354"); send(data, ss, msg); expect_code(data, ss, "250"); send(data, ss, "QUIT"); expect_code(data, ss, "221"); } catch (const std::exception &e) { spdlog::error("Failed to send mail"); spdlog::error(e.what()); } }