#ifndef __SendMail_h__ #define __SendMail_h__ #include "DKIMSigner.h" #include "File.h" #include "MString.h" #include #include namespace mail { /// /// SendMail delivers one message to one remote recipient over /// outbound SMTP (RFC 5321): resolves the recipient domain's MX /// records - falling back to the domain's own address when none /// exist, per RFC 5321 5.1 - tries each candidate host in preference /// order until one accepts the message, and runs the client side of /// the SMTP conversation (EHLO/HELO, MAIL FROM, RCPT TO, DATA with /// dot-stuffing) over a plain blocking socket. /// /// send() is a plain, synchronous, blocking call - it does not spawn /// a thread or manage this object's lifetime itself, so the exact /// same class serves both of this server's real callers even though /// they want opposite things: SMTPServer::inCreate() (queued mail /// found via inotify) wants fire-and-forget, so it wraps its own /// heap-allocated SendMail in a detached thread and deletes it when /// send() returns; main.cpp's "sendmail2" CLI-injector mode wants to /// block and report the result back to its caller (matching the /// classic local sendmail(1) contract other programs shell out to), /// so it just calls send() directly on the stack. Baking a threading /// policy into this class would have made one of those two wrong. /// /// Every line sent and every line the remote server responds with is /// appended, as it happens (not buffered to be written only at the /// end - a hung or very slow remote host would otherwise leave no /// trace at all until it finally times out), to one log file per /// message under sendLogPath, shared across every recipient of that /// message so its full delivery history reads as one transcript. /// class SendMail { public: // dkimSigner is optional (nullptr = don't sign) and not owned - // the caller keeps it alive for as long as any SendMail built // from it might still be sending (a shared signer is fine to // reuse across many messages; it holds no per-message state). SendMail(std::string hostName, std::string sendLogPath, std::string messageId, coreutils::MString from, coreutils::MString recipient, coreutils::MString mailData, DKIMSigner *dkimSigner = nullptr); /// Blocking. Returns true if some MX host for the recipient's /// domain accepted the message, false if DNS resolution or every /// host it tried failed. bool send(); private: bool deliverToHost(const std::string &host, coreutils::File &log); std::vector lookupMX(const std::string &domain); std::string domainOf(coreutils::MString &address); std::string readLine(int fd); int readResponse(int fd, coreutils::File &log, std::string &fullText); void sendLine(int fd, const std::string &line, coreutils::File &log); std::string hostName; std::string sendLogPath; std::string messageId; coreutils::MString from; coreutils::MString recipient; coreutils::MString mailData; DKIMSigner *dkimSigner; }; } #endif