Add per-domain DKIM signing, admin console TLS, log rotation, and resolver timeout scoping
- DKIM signing was hardcoded to one domain; now keyed per-domain off the sending address, fixing mail for every domain but the original. - Admin console (port 1027) is now TLS-wrapped instead of plaintext. - barantmail.log now rotates by size (config-driven) instead of growing unbounded (previously hit 23GB). - ResolverThreadScope.h: a <setenv> scoping fix for name resolution inside a <try> block that previously did not survive its own container. - IMAPArgs.cpp: fixed "UID STORE 1:*" wrongly requiring UID 1 to exist, which silently made empty-folder operations delete nothing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5f3e6ed2e6
commit
407cc2e915
11 files changed
+284
-81
No files matched your search
@@ -12,6 +12,7 @@
|
||||
#include "SendMail.h"
|
||||
#include "BARANTMailConfig.h"
|
||||
#include "DKIMSigner.h"
|
||||
#include <map>
|
||||
#include "__ADMIN_DOMAIN.h"
|
||||
#include "__ADMIN_MAILBOX.h"
|
||||
#include "__ADMIN_ALIAS.h"
|
||||
@@ -21,6 +22,7 @@
|
||||
#include <iostream>
|
||||
#include <signal.h>
|
||||
#include <sstream>
|
||||
#include "ResolverThreadScope.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// Every socket write in this process (outbound relay in
|
||||
@@ -61,14 +63,19 @@ int main(int argc, char **argv) {
|
||||
// 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
|
||||
// an empty "{}") just means getDkimEntries() is empty and this
|
||||
// continues unsigned, not a hard failure.
|
||||
mail::DKIMSigner *dkimSigner = nullptr;
|
||||
std::map<std::string, mail::DKIMSigner*> *dkimSigners = nullptr;
|
||||
try {
|
||||
mail::BARANTMailConfig config("barantmail.cfg");
|
||||
if (config.hasDkim())
|
||||
dkimSigner = new mail::DKIMSigner(config.getDkimDomain(), config.getDkimSelector(),
|
||||
config.getDkimPrivateKeyPath());
|
||||
coreutils::Log::maxLogSize = config.getLogMaxSizeBytes();
|
||||
coreutils::Log::maxLogBackups = config.getLogMaxBackups();
|
||||
auto dkimEntries = config.getDkimEntries();
|
||||
if (!dkimEntries.empty()) {
|
||||
dkimSigners = new std::map<std::string, mail::DKIMSigner*>();
|
||||
for (auto &entry : dkimEntries)
|
||||
(*dkimSigners)[entry.domain] = new mail::DKIMSigner(entry.domain, entry.selector, entry.privateKeyPath);
|
||||
}
|
||||
} catch (...) {
|
||||
// Config missing hostName/mailPath/smtp.port (all required -
|
||||
// see BARANTMailConfig's own doc comment) - fine, this
|
||||
@@ -85,7 +92,7 @@ int main(int argc, char **argv) {
|
||||
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);
|
||||
coreutils::MString(argv[1]), coreutils::MString(argv[2]), message, dkimSigners);
|
||||
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");
|
||||
@@ -101,6 +108,8 @@ int main(int argc, char **argv) {
|
||||
|
||||
std::string configPath = (argc > 1) ? argv[1] : "barantmail.cfg";
|
||||
mail::BARANTMailConfig config(configPath);
|
||||
coreutils::Log::maxLogSize = config.getLogMaxSizeBytes();
|
||||
coreutils::Log::maxLogBackups = config.getLogMaxBackups();
|
||||
|
||||
std::string hostName = config.getHostName();
|
||||
std::string ipAddress = "0.0.0.0";
|
||||
@@ -112,16 +121,37 @@ int main(int argc, char **argv) {
|
||||
mail::MailFileSystem mailFileSystem(mailPath);
|
||||
|
||||
core::EPoll ePoll;
|
||||
ePoll.start(4, 1000);
|
||||
// glibc's resolver state (_res, touched by res_query()/
|
||||
// getaddrinfo() inside SPFChecker/DMARCChecker) is thread-local -
|
||||
// confirmed directly (a standalone test program) that a fresh
|
||||
// thread's _res starts zeroed and its own first resolver call
|
||||
// lazily auto-initializes it to glibc's compiled-in defaults
|
||||
// (RES_TIMEOUT=5s, RES_DFLRETRY=2 tries), regardless of anything
|
||||
// set on a different thread beforehand. An earlier fix attempt
|
||||
// set _res.retrans/_res.retry once here in main() before
|
||||
// start() - a pure no-op for every one of these 4 worker
|
||||
// threads, none of which ever saw it. See ResolverThreadScope's
|
||||
// own doc comment for the full reasoning; same shape as
|
||||
// MysqlThreadScope's per-thread mysql_thread_init() need just
|
||||
// below.
|
||||
static mail::ResolverThreadScope resolverThreadScope;
|
||||
ePoll.start(4, 1000, &resolverThreadScope);
|
||||
|
||||
// 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());
|
||||
// One map of signers, shared by every outbound-relaying listener -
|
||||
// each DKIMSigner holds no per-message state (see its own doc
|
||||
// comment), so there's no reason for the plaintext and TLS SMTP
|
||||
// listeners to each load their own copies of the same keys. Built
|
||||
// once here regardless of how many domains are configured -
|
||||
// SendMail picks the entry matching each individual outbound
|
||||
// message's own sender domain at send time (see its own doc
|
||||
// comment) rather than this server having one fixed identity.
|
||||
std::map<std::string, mail::DKIMSigner*> *dkimSigners = nullptr;
|
||||
auto dkimEntries = config.getDkimEntries();
|
||||
if (!dkimEntries.empty()) {
|
||||
dkimSigners = new std::map<std::string, mail::DKIMSigner*>();
|
||||
for (auto &entry : dkimEntries)
|
||||
(*dkimSigners)[entry.domain] = new mail::DKIMSigner(entry.domain, entry.selector, entry.privateKeyPath);
|
||||
}
|
||||
|
||||
// Port 25 always runs unencrypted and always owns the mail queue
|
||||
// watch (see SMTPServer's own doc comment on watchQueue) - an
|
||||
@@ -130,13 +160,13 @@ int main(int argc, char **argv) {
|
||||
// needs regardless of what else is configured.
|
||||
mail::SMTPServer smtpServer(ePoll, hostName, mailFileSystem,
|
||||
core::IPAddress(ipAddress, config.getSmtpPort()), nullptr, true,
|
||||
dkimSigner);
|
||||
dkimSigners);
|
||||
|
||||
mail::SMTPServer *smtpTLSServer = nullptr;
|
||||
if (config.hasSmtpTLS())
|
||||
smtpTLSServer = new mail::SMTPServer(ePoll, hostName, mailFileSystem,
|
||||
core::IPAddress(ipAddress, config.getSmtpTLSPort()),
|
||||
config.getTlsInfo(), false, dkimSigner);
|
||||
config.getTlsInfo(), false, dkimSigners);
|
||||
|
||||
mail::POP3Server *pop3Server = nullptr;
|
||||
if (config.hasPop3())
|
||||
@@ -167,7 +197,7 @@ int main(int argc, char **argv) {
|
||||
if (config.hasAdmin()) {
|
||||
std::string adminUser = config.getAdminUser();
|
||||
std::string adminPasswordValue = config.getAdminPassword();
|
||||
consoleServer = new core::ConsoleServer(ePoll, core::IPAddress(ipAddress, config.getAdminPort()), nullptr,
|
||||
consoleServer = new core::ConsoleServer(ePoll, core::IPAddress(ipAddress, config.getAdminPort()), config.getTlsInfo(),
|
||||
[adminUser, adminPasswordValue](std::string user, std::string password) {
|
||||
return user == adminUser && password == adminPasswordValue;
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user