#include #include "EPoll.h" #include "ConsoleServer.h" #include "Exception.h" #include "File.h" #include "Log.h" #include "IPAddress.h" #include "MailFileSystem.h" #include "SMTPServer.h" #include "POP3Server.h" #include "IMAPServer.h" #include "SendMail.h" #include "BARANTMailConfig.h" #include "DKIMSigner.h" #include #include #include #include int main(int argc, char **argv) { // /var/log is root:syslog-owned and not writable by an unprivileged // user - this constructor throws immediately (before anything else // in main() runs) rather than silently falling back, when run as // the ordinary account this is actually deployed under. coreutils::Log(new coreutils::File("barantmail.log", O_WRONLY | O_APPEND | O_CREAT, 0644)); coreutils::Log(coreutils::LOG_INFO) << "BMAMAIL called with link " << argv[0]; std::string arg = argv[0]; if(arg.substr(arg.find_last_of("\\/") + 1) == "sendmail2") { try { coreutils::Log(coreutils::LOG_INFO) << "Starting Mail Injector. Build " << __DATE__ << " " << __TIME__; char buffer[1000000]; int index = 0; int len = 0; while((len = read(0, &buffer[index], 1000000)) > 0) index += len; coreutils::MString message(buffer, index); coreutils::Log(coreutils::LOG_DEBUG_1) << "Number of bytes read: " << index; coreutils::ZString mailPath("/var/mail"); mail::MailFileSystem mailFileSystem(mailPath); // Same config file the server branch defaults to - loaded here // too (this branch never reads barantmail.cfg otherwise) purely // for DKIM: mail injected locally through this CLI path is just // as much "outbound mail from this server" as anything queued // through SMTP, and should be signed the same way. A missing // config file here (BARANTMailConfig's own JFile falls back to // an empty "{}") just means hasDkim() is false and this // continues unsigned, not a hard failure. mail::DKIMSigner *dkimSigner = nullptr; try { mail::BARANTMailConfig config("barantmail.cfg"); if (config.hasDkim()) dkimSigner = new mail::DKIMSigner(config.getDkimDomain(), config.getDkimSelector(), config.getDkimPrivateKeyPath()); } catch (...) { // Config missing hostName/mailPath/smtp.port (all required - // see BARANTMailConfig's own doc comment) - fine, this // branch doesn't need any of those, only dkim is optional. } // A local, one-off message id for this injection's own log // file - matches __SMTP_DATA::generateMailFileName()'s scheme // (nanosecond timestamp) so both codepaths' logs sit in // .sendlog/ with the same naming convention. struct timespec timex; clock_gettime(CLOCK_REALTIME, &timex); std::stringstream idStream; idStream << timex.tv_sec << "." << std::setfill('0') << std::setw(9) << timex.tv_nsec; mail::SendMail mail("localhost", mailFileSystem.getSendLogPath().str(), idStream.str(), coreutils::MString(argv[1]), coreutils::MString(argv[2]), message, dkimSigner); bool ok = mail.send(); coreutils::Log(coreutils::LOG_DEBUG_1) << "send result: " << (ok ? "delivered" : "failed"); std::cout << (ok ? "250 OK\n" : "550 delivery failed\n"); } catch(coreutils::Exception exception) { std::cout << exception.text << " Error reason is '" << strerror(exception.errorNumber) << "' in file " << exception.file << " at line " << exception.line << std::endl; } } else { try { coreutils::Log(coreutils::LOG_INFO) << "BMAMail Server starting. Build " << __DATE__ << " " << __TIME__; std::string configPath = (argc > 1) ? argv[1] : "barantmail.cfg"; mail::BARANTMailConfig config(configPath); std::string hostName = config.getHostName(); std::string ipAddress = "0.0.0.0"; // ZString's std::string constructor takes a non-const reference, // which can't bind to a temporary - a named local is required. std::string mailPathStr = config.getMailPath(); coreutils::ZString mailPath(mailPathStr); mail::MailFileSystem mailFileSystem(mailPath); core::EPoll ePoll; ePoll.start(4, 1000); // One signer, shared by every outbound-relaying listener - it // holds no per-message state (see DKIMSigner's own doc comment), // so there's no reason for the plaintext and TLS SMTP listeners // to each load their own copy of the same private key. mail::DKIMSigner *dkimSigner = nullptr; if (config.hasDkim()) dkimSigner = new mail::DKIMSigner(config.getDkimDomain(), config.getDkimSelector(), config.getDkimPrivateKeyPath()); // Port 25 always runs unencrypted and always owns the mail queue // watch (see SMTPServer's own doc comment on watchQueue) - an // inbound peer MTA relaying mail in has no TLS-first negotiation // to fall back on, so this is the one listener every deployment // needs regardless of what else is configured. mail::SMTPServer smtpServer(ePoll, hostName, mailFileSystem, core::IPAddress(ipAddress, config.getSmtpPort()), nullptr, true, dkimSigner); mail::SMTPServer *smtpTLSServer = nullptr; if (config.hasSmtpTLS()) smtpTLSServer = new mail::SMTPServer(ePoll, hostName, mailFileSystem, core::IPAddress(ipAddress, config.getSmtpTLSPort()), config.getTlsInfo(), false, dkimSigner); mail::POP3Server *pop3Server = nullptr; if (config.hasPop3()) pop3Server = new mail::POP3Server(ePoll, hostName, mailFileSystem, core::IPAddress(ipAddress, config.getPop3Port()), config.getTlsInfo()); mail::IMAPServer *imapServer = nullptr; if (config.hasImap()) imapServer = new mail::IMAPServer(ePoll, hostName, mailFileSystem, core::IPAddress(ipAddress, config.getImapPort()), config.getTlsInfo()); core::ConsoleServer consoleServer(ePoll, core::IPAddress(ipAddress, 1027), nullptr); consoleServer.commands.add(consoleServer.commands, "help"); consoleServer.commands.add(smtpServer.commands, "smtp"); while(true) sleep(3600); ePoll.stop(); } catch(coreutils::Exception exception) { std::cout << exception.text << " Error reason is '" << strerror(exception.errorNumber) << "' in file " << exception.file << " at line " << exception.line << std::endl; } } }