2023-10-20 13:02:21 +02:00
|
|
|
#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>(),
|
2023-10-23 18:30:35 +02:00
|
|
|
#if defined(BOTAN_HAS_SYSTEM_RNG)
|
|
|
|
std::make_unique<Botan::System_RNG>(),
|
|
|
|
#else
|
|
|
|
std::make_unique<Botan::AutoSeeded_RNG>(),
|
|
|
|
#endif
|
2023-10-20 13:02:21 +02:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|