fileserver/src/server/mail.cxx

140 lines
4.2 KiB
C++
Raw Normal View History

#include <asio.hpp>
#include <botan_asio/asio_stream.h>
#include <spdlog/spdlog.h>
#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<typename Socket>
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<typename Socket>
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<Botan::Certificate_Store*> 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<Botan::TLS::Context>(
std::make_shared<CredMan>(),
#if defined(BOTAN_HAS_SYSTEM_RNG)
std::make_unique<Botan::System_RNG>(),
#else
std::make_unique<Botan::AutoSeeded_RNG>(),
#endif
std::make_shared<Botan::TLS::Session_Manager_Noop>(),
std::make_shared<Policy>()
);
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<asio::ip::tcp::socket> 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());
}
}