block/allow instead of black/white
[ghsmtp.git] / Session.cpp
blob6ff77353476823357856a37b126e1f8a4292414b
1 #include <algorithm>
2 #include <charconv>
3 #include <iomanip>
4 #include <iostream>
5 #include <string>
6 #include <vector>
8 #include "DNS.hpp"
9 #include "Domain.hpp"
10 #include "IP.hpp"
11 #include "IP4.hpp"
12 #include "IP6.hpp"
13 #include "MessageStore.hpp"
14 #include "Session.hpp"
15 #include "esc.hpp"
16 #include "iequal.hpp"
17 #include "osutil.hpp"
19 #include <fmt/format.h>
20 #include <fmt/ostream.h>
22 #include <boost/algorithm/string/classification.hpp>
23 #include <boost/algorithm/string/split.hpp>
25 #include <boost/xpressive/xpressive.hpp>
27 #include <syslog.h>
29 DEFINE_string(selector, "ghsmtp", "DKIM selector");
31 using namespace std::string_literals;
33 namespace Config {
34 char const* wls[]{
35 "list.dnswl.org",
39 <https://www.dnswl.org/?page_id=15#query>
41 Return codes
43 The return codes are structured as 127.0.x.y, with “x” indicating the category
44 of an entry and “y” indicating how trustworthy an entry has been judged.
46 Categories (127.0.X.y):
48 2 – Financial services
49 3 – Email Service Providers
50 4 – Organisations (both for-profit [ie companies] and non-profit)
51 5 – Service/network providers
52 6 – Personal/private servers
53 7 – Travel/leisure industry
54 8 – Public sector/governments
55 9 – Media and Tech companies
56 10 – some special cases
57 11 – Education, academic
58 12 – Healthcare
59 13 – Manufacturing/Industrial
60 14 – Retail/Wholesale/Services
61 15 – Email Marketing Providers
62 20 – Added through Self Service without specific category
64 Trustworthiness / Score (127.0.x.Y):
66 0 = none – only avoid outright blocking (eg large ESP mailservers, -0.1)
67 1 = low – reduce chance of false positives (-1.0)
68 2 = medium – make sure to avoid false positives but allow override for clear
69 cases (-10.0) 3 = high – avoid override (-100.0).
71 The scores in parantheses are typical SpamAssassin scores.
73 Special return code 127.0.0.255
75 In cases where your nameserver issues more than 100’000 queries / 24 hours, you
76 may be blocked from further queries. The return code “127.0.0.255” indicates
77 this situation.
81 char const* bls[]{
82 "b.barracudacentral.org",
83 "psbl.surriel.com",
84 "zen.spamhaus.org",
87 /*** Last octet from A record returned by blocklists ***
89 From <http://uribl.com/about.shtml#implementation>
91 X Binary On List
92 ---------------------------------------------------------
93 1 00000001 Query blocked, possibly due to high volume
94 2 00000010 black
95 4 00000100 grey
96 8 00001000 red
97 14 00001110 black,grey,red (for testpoints)
99 From <https://www.spamhaus.org/faq/section/Spamhaus%20DBL#291>
101 Return Codes Data Source
102 127.0.1.2 spam domain
103 127.0.1.4 phish domain
104 127.0.1.5 malware domain
105 127.0.1.6 botnet C&C domain
106 127.0.1.102 abused legit spam
107 127.0.1.103 abused spammed redirector domain
108 127.0.1.104 abused legit phish
109 127.0.1.105 abused legit malware
110 127.0.1.106 abused legit botnet C&C
111 127.0.1.255 IP queries prohibited!
113 From <http://www.surbl.org/lists#multi>
115 last octet indicates which lists it belongs to. The bit positions in
116 that last octet for membership in the different lists are:
118 8 = listed on PH
119 16 = listed on MW
120 64 = listed on ABUSE
121 128 = listed on CR
125 char const* uribls[]{
126 "multi.uribl.com",
127 "dbl.spamhaus.org",
128 "multi.surbl.org",
131 constexpr auto greeting_wait = std::chrono::seconds{2};
132 constexpr int max_recipients_per_message = 100;
133 constexpr int max_unrecognized_cmds = 20;
135 // Read timeout value gleaned from RFC-1123 section 5.3.2 and RFC-5321
136 // section 4.5.3.2.7.
137 constexpr auto read_timeout = std::chrono::minutes{5};
138 constexpr auto write_timeout = std::chrono::seconds{30};
139 } // namespace Config
141 #include <gflags/gflags.h>
143 DEFINE_bool(test_mode, false, "ease up on some checks");
145 DEFINE_bool(immortal, false, "don't set process timout");
147 DEFINE_uint64(max_read, 0, "max data to read");
148 DEFINE_uint64(max_write, 0, "max data to write");
150 DEFINE_bool(rrvs, false, "support RRVS à la RFC 7293");
152 Session::Session(fs::path config_path,
153 std::function<void(void)> read_hook,
154 int fd_in,
155 int fd_out)
156 : config_path_(config_path)
157 , res_(config_path)
158 , sock_(fd_in, fd_out, read_hook, Config::read_timeout, Config::write_timeout)
159 , send_(config_path, "smtp")
160 , srs_(config_path)
162 auto accept_db_name = config_path_ / "accept_domains";
163 auto allow_db_name = config_path_ / "allow";
164 auto block_db_name = config_path_ / "block";
165 auto forward_db_name = config_path_ / "forward";
167 accept_domains_.open(accept_db_name);
168 allow_.open(allow_db_name);
169 block_.open(block_db_name);
170 forward_.open(forward_db_name);
172 if (sock_.has_peername() && !IP::is_private(sock_.us_c_str())) {
173 auto fcrdns = DNS::fcrdns(res_, sock_.us_c_str());
174 for (auto const& fcr : fcrdns) {
175 server_fcrdns_.emplace_back(fcr);
179 server_identity_ = [this] {
180 auto const id_from_env{getenv("GHSMTP_SERVER_ID")};
181 if (id_from_env)
182 return std::string{id_from_env};
184 auto const hostname{osutil::get_hostname()};
185 if (hostname.find('.') != std::string::npos)
186 return hostname;
188 if (!server_fcrdns_.empty()) {
189 // first result should be shortest
190 return server_fcrdns_.front().ascii();
193 auto const us_c_str = sock_.us_c_str();
194 if (us_c_str && !IP::is_private(us_c_str)) {
195 return IP::to_address_literal(us_c_str);
198 LOG(FATAL) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
199 return ""s;
200 }();
202 send_.set_sender(server_identity_);
204 max_msg_size(Config::max_msg_size_initial);
207 void Session::max_msg_size(size_t max)
209 max_msg_size_ = max; // number to advertise via RFC 1870
211 if (FLAGS_max_read) {
212 sock_.set_max_read(FLAGS_max_read);
214 else {
215 auto const overhead = std::max(max / 10, size_t(2048));
216 sock_.set_max_read(max + overhead);
220 void Session::bad_host_(char const* msg) const
222 if (sock_.has_peername()) {
223 // On my systems, this pattern triggers a fail2ban rule that
224 // blocks connections from this IP address on port 25 for a few
225 // days. See <https://www.fail2ban.org/> for more info.
226 syslog(LOG_MAIL | LOG_WARNING, "bad host [%s] %s", sock_.them_c_str(), msg);
228 std::exit(EXIT_SUCCESS);
231 void Session::reset_()
233 // RSET does not force another EHLO/HELO, the one piece of per
234 // transaction data saved is client_identity_:
236 // client_identity_.clear(); <-- not cleared!
238 reverse_path_.clear();
239 forward_path_.clear();
240 spf_received_.clear();
241 fwd_path_.clear();
242 fwd_from_.clear();
243 rep_info_.clear();
245 binarymime_ = false;
246 smtputf8_ = false;
247 // prdr_ = false;
249 if (msg_) {
250 msg_.reset();
253 max_msg_size(max_msg_size());
255 state_ = xact_step::mail;
256 send_.rset();
259 // Return codes from connection establishment are 220 or 554, according
260 // to RFC 5321. That's it.
262 void Session::greeting()
264 CHECK(state_ == xact_step::helo);
266 if (sock_.has_peername()) {
267 close(2); // if we're a networked program, never send to stderr
269 std::string error_msg;
270 if (!verify_ip_address_(error_msg)) {
271 // no glog message at this point
272 bad_host_(error_msg.c_str());
275 /******************************************************************
276 <https://tools.ietf.org/html/rfc5321#section-4.3.1> says:
278 4.3. Sequencing of Commands and Replies
280 4.3.1. Sequencing Overview
282 The communication between the sender and receiver is an alternating
283 dialogue, controlled by the sender. As such, the sender issues a
284 command and the receiver responds with a reply. Unless other
285 arrangements are negotiated through service extensions, the sender
286 MUST wait for this response before sending further commands. One
287 important reply is the connection greeting. Normally, a receiver
288 will send a 220 "Service ready" reply when the connection is
289 completed. The sender SHOULD wait for this greeting message before
290 sending any commands.
292 So which is it?
294 “…the receiver responds with a reply.”
295 “…the sender MUST wait for this response…”
296 “One important reply is the connection greeting.”
297 “The sender SHOULD wait for this greeting…”
299 So is it MUST or SHOULD? I enforce MUST.
300 *******************************************************************/
302 // Wait a bit of time for pre-greeting traffic.
303 if (!(ip_allowed_ || fcrdns_allowed_)) {
304 if (sock_.input_ready(Config::greeting_wait)) {
305 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush;
306 // no glog message at this point
307 bad_host_("input before any greeting");
309 // Give a half greeting and wait again.
310 out_() << "220-" << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush;
311 if (sock_.input_ready(Config::greeting_wait)) {
312 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush;
313 // LOG(INFO) << "half greeting got " << client_;
314 bad_host_("input before full greeting");
317 LOG(INFO) << "connect from " << client_;
320 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush;
322 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
323 alarm(2 * 60); // initial alarm
327 void Session::flush() { out_() << std::flush; }
329 void Session::last_in_group_(std::string_view verb)
331 if (sock_.input_ready(std::chrono::seconds(0))) {
332 LOG(WARNING) << "pipelining error; input ready processing " << verb;
336 void Session::lo_(char const* verb, std::string_view client_identity)
338 last_in_group_(verb);
339 reset_();
341 if (client_identity_ != client_identity) {
342 client_identity_ = client_identity;
344 std::string error_msg;
345 if (!verify_client_(client_identity_, error_msg)) {
346 // no glog message at this point
347 bad_host_(error_msg.c_str());
351 if (*verb == 'H') {
352 out_() << "250 " << server_id_() << "\r\n";
355 if (*verb == 'E') {
356 extensions_ = true;
358 if (sock_.has_peername()) {
359 out_() << "250-" << server_id_() << " at your service, " << client_
360 << "\r\n";
362 else {
363 out_() << "250-" << server_id_() << "\r\n";
366 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
367 out_() << "250-8BITMIME\r\n"; // RFC 6152
369 if (FLAGS_rrvs) {
370 out_() << "250-RRVS\r\n"; // RFC 7293
373 // out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
375 if (sock_.tls()) {
376 // Check sasl sources for auth types.
377 // out_() << "250-AUTH PLAIN\r\n";
378 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
380 else {
381 // If we're not already TLS, offer TLS
382 out_() << "250-STARTTLS\r\n"; // RFC 3207
384 out_() << "250-ENHANCEDSTATUSCODES\r\n" // RFC 2034
385 "250-PIPELINING\r\n" // RFC 2920
386 //-----------------------------------------------
387 // Disable this right now, nobody uses it anyhow,
388 // but this might break DKIM signing for relay.
389 // "250-BINARYMIME\r\n" // RFC 3030
390 //-----------------------------------------------
391 "250-CHUNKING\r\n" // RFC 3030
392 "250 SMTPUTF8\r\n"; // RFC 6531
395 out_() << std::flush;
397 if (sock_.has_peername()) {
398 if (std::find(begin(client_fcrdns_), end(client_fcrdns_),
399 client_identity_) != end(client_fcrdns_)) {
400 LOG(INFO) << verb << " " << client_identity << " from "
401 << sock_.them_address_literal();
403 else {
404 LOG(INFO) << verb << " " << client_identity << " from " << client_;
407 else {
408 LOG(INFO) << verb << " " << client_identity;
412 void Session::mail_from(Mailbox&& reverse_path, parameters_t const& parameters)
414 switch (state_) {
415 case xact_step::helo:
416 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
417 LOG(WARNING) << "'MAIL FROM' before HELO/EHLO"
418 << (sock_.has_peername() ? " from " : "") << client_;
419 return;
420 case xact_step::mail: break;
421 case xact_step::rcpt:
422 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
423 LOG(WARNING) << "nested MAIL command"
424 << (sock_.has_peername() ? " from " : "") << client_;
425 return;
426 case xact_step::data:
427 case xact_step::bdat:
428 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush;
429 LOG(WARNING) << "nested MAIL command"
430 << (sock_.has_peername() ? " from " : "") << client_;
431 return;
432 case xact_step::rset:
433 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
434 LOG(WARNING) << "error state must be cleared with a RSET"
435 << (sock_.has_peername() ? " from " : "") << client_;
436 return;
439 if (!verify_from_params_(parameters)) {
440 return;
443 std::string error_msg;
444 if (!verify_sender_(reverse_path, error_msg)) {
445 LOG(WARNING) << "verify sender failed: " << error_msg;
446 bad_host_(error_msg.c_str());
449 reverse_path_ = std::move(reverse_path);
450 fwd_path_.clear();
451 fwd_from_.clear();
452 forward_path_.clear();
453 out_() << "250 2.1.0 MAIL FROM OK\r\n";
454 // No flush RFC-2920 section 3.1, this could be part of a command group.
456 fmt::memory_buffer params;
457 for (auto const& [name, value] : parameters) {
458 fmt::format_to(params, " {}", name);
459 if (!value.empty()) {
460 fmt::format_to(params, "={}", value);
463 LOG(INFO) << "MAIL FROM:<" << reverse_path_ << ">" << fmt::to_string(params);
465 state_ = xact_step::rcpt;
468 bool Session::forward_to_(std::string const& forward, Mailbox const& rcpt_to)
470 // If we're already forwarding or replying, reject
471 if (!fwd_path_.empty() || !rep_info_.empty()) {
472 out_() << "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n"
473 << std::flush;
474 LOG(WARNING) << "failed to forward to <" << forward
475 << "> already forwarding or replying for: " << rcpt_to;
476 return false;
479 fwd_path_ = Mailbox(forward);
480 fwd_from_ = rcpt_to;
482 // New bounce address
483 SRS0::from_to bounce;
484 bounce.mail_from = reverse_path_.as_string(Mailbox::domain_encoding::ascii);
486 auto const new_bounce = srs_.enc_bounce(bounce, server_id_().c_str());
488 auto const mail_from = Mailbox(new_bounce);
490 std::string error_msg;
491 if (!send_.mail_from_rcpt_to(res_, mail_from, fwd_path_, error_msg)) {
492 out_() << error_msg << std::flush;
493 LOG(WARNING) << "failed to forward <" << fwd_path_ << "> " << error_msg;
494 return false;
497 LOG(INFO) << "RCPT TO:<" << rcpt_to << "> forwarding to == <" << fwd_path_
498 << ">";
499 return true;
502 bool Session::reply_to_(SRS0::from_to const& reply_info, Mailbox const& rcpt_to)
504 // If we're already forwarding or replying, reject
505 if (!fwd_path_.empty() || !rep_info_.empty()) {
506 out_() << "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n"
507 << std::flush;
508 LOG(WARNING) << "failed to reply to <" << reply_info.mail_from
509 << "> already forwarding or replying for: " << rcpt_to;
510 return false;
513 rep_info_ = reply_info;
515 Mailbox const from(rep_info_.rcpt_to_local_part, server_identity_);
516 Mailbox const to(rep_info_.mail_from);
518 std::string error_msg;
519 if (!send_.mail_from_rcpt_to(res_, from, to, error_msg)) {
520 out_() << error_msg << std::flush;
521 LOG(WARNING) << "failed to reply from <" << from << "> to <" << to << "> "
522 << error_msg;
523 return false;
526 LOG(INFO) << "RCPT TO:<" << rcpt_to << "> is a reply to "
527 << rep_info_.mail_from << " from " << rep_info_.rcpt_to_local_part;
528 return true;
531 void Session::rcpt_to(Mailbox&& forward_path, parameters_t const& parameters)
533 switch (state_) {
534 case xact_step::helo:
535 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
536 LOG(WARNING) << "'RCPT TO' before HELO/EHLO"
537 << (sock_.has_peername() ? " from " : "") << client_;
538 return;
539 case xact_step::mail:
540 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
541 LOG(WARNING) << "'RCPT TO' before 'MAIL FROM'"
542 << (sock_.has_peername() ? " from " : "") << client_;
543 return;
544 case xact_step::rcpt:
545 case xact_step::data: break;
546 case xact_step::bdat:
547 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
548 LOG(WARNING) << "'RCPT TO' during BDAT transfer"
549 << (sock_.has_peername() ? " from " : "") << client_;
550 return;
551 case xact_step::rset:
552 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
553 LOG(WARNING) << "error state must be cleared with a RSET"
554 << (sock_.has_peername() ? " from " : "") << client_;
555 return;
558 if (!verify_rcpt_params_(parameters))
559 return;
561 if (!verify_recipient_(forward_path))
562 return;
564 if (forward_path_.size() >= Config::max_recipients_per_message) {
565 out_() << "452 4.5.3 too many recipients\r\n" << std::flush;
566 LOG(WARNING) << "too many recipients <" << forward_path << ">";
567 return;
569 // no check for dups, postfix doesn't
570 forward_path_.emplace_back(std::move(forward_path));
572 Mailbox const& rcpt_to_mbx = forward_path_.back();
574 auto const rcpt_to_str =
575 rcpt_to_mbx.as_string(Mailbox::domain_encoding::ascii);
577 if (auto reply = srs_.dec_reply(rcpt_to_mbx.local_part()); reply) {
578 if (!reply_to_(*reply, rcpt_to_mbx))
579 return;
581 else if (auto const forward = forward_.find(rcpt_to_str.c_str()); forward) {
582 if (!forward_to_(*forward, rcpt_to_mbx))
583 return;
585 else {
586 LOG(INFO) << "RCPT TO:<" << rcpt_to_str << ">";
589 // No flush RFC-2920 section 3.1, this could be part of a command group.
590 out_() << "250 2.1.5 RCPT TO OK\r\n";
592 state_ = xact_step::data;
595 // The headers Received and Received-SPF are returned as a string.
597 std::string Session::added_headers_(MessageStore const& msg)
599 auto const protocol{[this]() {
600 if (smtputf8_)
601 return sock_.tls() ? "UTF8SMTPS" : "UTF8SMTP";
602 else if (extensions_)
603 return sock_.tls() ? "ESMTPS" : "ESMTP";
604 else
605 return sock_.tls() ? "SMTPS" : "SMTP";
606 }()};
608 fmt::memory_buffer headers;
610 // <https://tools.ietf.org/html/rfc5321#section-4.4>
611 fmt::format_to(headers, "Received: from {}", client_identity_.utf8());
612 if (sock_.has_peername()) {
613 fmt::format_to(headers, " ({})", client_);
615 fmt::format_to(headers, "\r\n\tby {} with {} id {}", server_identity_.utf8(),
616 protocol, msg.id());
617 if (forward_path_.size()) {
618 fmt::format_to(headers, "\r\n\tfor <{}>", forward_path_[0]);
619 for (auto i = 1u; i < forward_path_.size(); ++i)
620 fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
622 std::string const tls_info{sock_.tls_info()};
623 if (tls_info.length()) {
624 fmt::format_to(headers, "\r\n\t({})", tls_info);
626 fmt::format_to(headers, ";\r\n\t{}\r\n", msg.when());
628 // Received-SPF:
629 if (!spf_received_.empty()) {
630 fmt::format_to(headers, "{}\r\n", spf_received_);
633 return fmt::to_string(headers);
636 namespace {
637 bool lookup_domain(CDB& cdb, Domain const& domain)
639 if (!domain.empty()) {
640 if (cdb.contains(domain.ascii())) {
641 return true;
643 if (domain.is_unicode() && cdb.contains(domain.utf8())) {
644 return true;
647 return false;
649 } // namespace
651 std::tuple<Session::SpamStatus, std::string> Session::spam_status_()
653 if (spf_result_ == SPF::Result::FAIL && !ip_allowed_)
654 return {SpamStatus::spam, "SPF failed"};
656 // These should have already been rejected by verify_client_().
657 if ((reverse_path_.domain() == "localhost.local") ||
658 (reverse_path_.domain() == "localhost"))
659 return {SpamStatus::spam, "bogus reverse_path"};
661 std::vector<std::string> why_ham;
663 // Anything enciphered tastes a lot like ham.
664 if (sock_.tls())
665 why_ham.emplace_back("they used TLS");
667 if (spf_result_ == SPF::Result::PASS) {
668 if (lookup_domain(allow_, spf_sender_domain_)) {
669 why_ham.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
670 spf_sender_domain_.utf8()));
672 else {
673 auto tld_dom{tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
674 if (tld_dom && allow_.contains(tld_dom)) {
675 why_ham.emplace_back(fmt::format(
676 "SPF sender registered domain ({}) is allowed", tld_dom));
681 if (fcrdns_allowed_)
682 why_ham.emplace_back(
683 fmt::format("FCrDNS (or it's registered domain) is allowed"));
685 if (!why_ham.empty())
686 return {SpamStatus::ham,
687 fmt::format("{}", fmt::join(std::begin(why_ham), std::end(why_ham),
688 ", and "))};
690 return {SpamStatus::spam, "it's not ham"};
693 static std::string folder(Session::SpamStatus status,
694 std::vector<Mailbox> const& forward_path,
695 Mailbox const& reverse_path)
697 if (status == Session::SpamStatus::spam)
698 return ".Junk";
700 return "";
703 bool Session::msg_new()
705 CHECK((state_ == xact_step::data) || (state_ == xact_step::bdat));
707 auto const& [status, reason]{spam_status_()};
709 LOG(INFO) << ((status == SpamStatus::ham) ? "ham since " : "spam since ")
710 << reason;
712 // All sources of ham get a fresh 5 minute timeout per message.
713 if (status == SpamStatus::ham) {
714 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr))
715 alarm(5 * 60);
718 msg_ = std::make_unique<MessageStore>();
720 if (!FLAGS_max_write)
721 FLAGS_max_write = max_msg_size();
723 try {
724 msg_->open(server_id_(), FLAGS_max_write,
725 folder(status, forward_path_, reverse_path_));
726 auto const hdrs{added_headers_(*(msg_.get()))};
727 msg_->write(hdrs);
729 // fmt::memory_buffer spam_status;
730 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
731 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
732 // msg_->write(spam_status.data(), spam_status.size());
734 LOG(INFO) << "Spam-Status: "
735 << ((status == SpamStatus::spam) ? "Yes" : "No") << ", "
736 << reason;
738 return true;
740 catch (std::system_error const& e) {
741 switch (errno) {
742 case ENOSPC:
743 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
744 LOG(ERROR) << "no space";
745 msg_->trash();
746 msg_.reset();
747 return false;
749 default:
750 out_() << "550 5.0.0 mail system error\r\n" << std::flush;
751 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
752 LOG(ERROR) << e.what();
753 msg_->trash();
754 msg_.reset();
755 return false;
758 catch (std::exception const& e) {
759 out_() << "550 5.0.0 mail error\r\n" << std::flush;
760 LOG(ERROR) << e.what();
761 msg_->trash();
762 msg_.reset();
763 return false;
766 // out_() << "550 5.0.0 mail error\r\n" << std::flush;
767 // LOG(ERROR) << "msg_new failed with no exception thrown";
768 // msg_->trash();
769 // msg_.reset();
770 // return false;
773 bool Session::msg_write(char const* s, std::streamsize count)
775 if ((state_ != xact_step::data) && (state_ != xact_step::bdat))
776 return false;
778 if (!msg_)
779 return false;
781 try {
782 if (msg_->write(s, count))
783 return true;
785 catch (std::system_error const& e) {
786 switch (errno) {
787 case ENOSPC:
788 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
789 LOG(ERROR) << "no space";
790 msg_->trash();
791 msg_.reset();
792 return false;
794 default:
795 out_() << "550 5.0.0 mail system error\r\n" << std::flush;
796 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
797 LOG(ERROR) << e.what();
798 msg_->trash();
799 msg_.reset();
800 return false;
803 catch (std::exception const& e) {
804 out_() << "550 5.0.0 mail error\r\n" << std::flush;
805 LOG(ERROR) << e.what();
806 msg_->trash();
807 msg_.reset();
808 return false;
811 out_() << "550 5.0.0 mail error\r\n" << std::flush;
812 LOG(ERROR) << "write failed with no exception thrown";
813 msg_->trash();
814 msg_.reset();
815 return false;
818 bool Session::data_start()
820 last_in_group_("DATA");
822 switch (state_) {
823 case xact_step::helo:
824 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
825 LOG(WARNING) << "'DATA' before HELO/EHLO"
826 << (sock_.has_peername() ? " from " : "") << client_;
827 return false;
828 case xact_step::mail:
829 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
830 LOG(WARNING) << "'DATA' before 'MAIL FROM'"
831 << (sock_.has_peername() ? " from " : "") << client_;
832 return false;
833 case xact_step::rcpt:
835 /******************************************************************
836 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
838 The DATA command can fail at only two points in the protocol exchange:
840 If there was no MAIL, or no RCPT, command, or all such commands were
841 rejected, the server MAY return a "command out of sequence" (503) or
842 "no valid recipients" (554) reply in response to the DATA command.
844 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
846 The additional restriction is that when there have been no successful
847 RCPT commands in the mail transaction, the DATA command MUST fail
848 with a 503 reply code.
850 Therefore I will send the reply code that is valid for both, and
851 do the same for the BDAT case.
852 *******************************************************************/
854 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
855 LOG(WARNING) << "no valid recipients"
856 << (sock_.has_peername() ? " from " : "") << client_;
857 return false;
858 case xact_step::data: break;
859 case xact_step::bdat:
860 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
861 LOG(WARNING) << "'DATA' during BDAT transfer"
862 << (sock_.has_peername() ? " from " : "") << client_;
863 return false;
864 case xact_step::rset:
865 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
866 LOG(WARNING) << "error state must be cleared with a RSET"
867 << (sock_.has_peername() ? " from " : "") << client_;
868 return false;
871 if (binarymime_) {
872 out_() << "503 5.5.1 DATA does not support BINARYMIME\r\n" << std::flush;
873 LOG(WARNING) << "DATA does not support BINARYMIME";
874 state_ = xact_step::rset; // RFC 3030 section 3 page 5
875 return false;
878 if (!msg_new()) {
879 LOG(ERROR) << "msg_new() failed";
880 return false;
883 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush;
884 LOG(INFO) << "DATA";
885 return true;
888 bool Session::do_forward_(message::parsed& msg)
890 auto msg_fwd = msg;
892 // Generate a reply address
893 SRS0::from_to reply;
894 reply.mail_from = msg_fwd.dmarc_from;
895 reply.rcpt_to_local_part = fwd_from_.local_part();
897 auto const reply_addr =
898 fmt::format("{}@{}", srs_.enc_reply(reply), server_id_());
900 auto const munging = false;
902 auto const sender = server_identity_.ascii().c_str();
903 auto const selector = FLAGS_selector.c_str();
904 auto const key_file =
905 (config_path_ / FLAGS_selector).replace_extension("private");
906 CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
908 if (munging) {
909 auto const from_hdr =
910 fmt::format("From: \"{} via\" <@>", msg_fwd.dmarc_from, reply_addr);
911 message::rewrite(msg_fwd, from_hdr, "", sender, selector, key_file);
913 else {
914 auto const reply_to_hdr = fmt::format("Reply-To: {}", reply_addr);
915 message::rewrite(msg_fwd, "", reply_to_hdr, sender, selector, key_file);
918 // Forward it on
919 if (!send_.send(msg_fwd.as_string())) {
920 out_() << "432 4.3.0 Recipient's incoming mail queue has been "
921 "stopped\r\n"
922 << std::flush;
924 LOG(ERROR) << "failed to send for " << fwd_path_;
925 return false;
928 LOG(INFO) << "successfully sent for " << fwd_path_;
929 return true;
932 bool Session::do_reply_(message::parsed& msg)
934 Mailbox to_mbx(rep_info_.mail_from);
935 Mailbox from_mbx(rep_info_.rcpt_to_local_part, server_identity_);
937 auto reply = std::make_unique<MessageStore>();
938 reply->open(server_id_(), FLAGS_max_write, ".Drafts");
940 auto const date{Now{}};
941 auto const pill{Pill{}};
942 auto const mid_str =
943 fmt::format("<{}.{}@{}>", date.sec(), pill, server_identity_);
945 fmt::memory_buffer bfr;
947 fmt::format_to(bfr, "From: <{}>\r\n", from_mbx);
948 fmt::format_to(bfr, "To: <{}>\r\n", to_mbx);
950 fmt::format_to(bfr, "Date: {}\r\n", date.c_str());
952 fmt::format_to(bfr, "Message-ID: {}\r\n", mid_str.c_str());
954 if (!msg.get_header(message::Subject).empty()) {
955 fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
956 msg.get_header(message::Subject));
958 else {
959 fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
960 "Reply to your message");
963 if (!msg.get_header(message::In_Reply_To).empty()) {
964 fmt::format_to(bfr, "{}: {}\r\n", message::In_Reply_To,
965 msg.get_header(message::In_Reply_To));
968 if (!msg.get_header(message::MIME_Version).empty() &&
969 msg.get_header(message::Content_Type).empty()) {
970 fmt::format_to(bfr, "{}: {}\r\n", message::MIME_Version,
971 msg.get_header(message::MIME_Version));
972 fmt::format_to(bfr, "{}: {}\r\n", message::Content_Type,
973 msg.get_header(message::Content_Type));
976 reply->write(fmt::to_string(bfr));
978 if (!msg.body.empty()) {
979 reply->write("\r\n");
980 reply->write(msg.body);
983 auto const msg_data = reply->freeze();
984 message::parsed msg_reply;
985 CHECK(msg_reply.parse(msg_data));
987 auto const sender = server_identity_.ascii().c_str();
988 auto const selector = FLAGS_selector.c_str();
989 auto const key_file =
990 (config_path_ / FLAGS_selector).replace_extension("private");
991 CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
993 message::dkim_sign(msg_reply, sender, selector, key_file);
995 if (!send_.send(msg_reply.as_string())) {
996 out_() << "432 4.3.0 Recipient's incoming mail queue has been "
997 "stopped\r\n"
998 << std::flush;
1000 LOG(ERROR) << "send failed for reply to " << to_mbx << " from " << from_mbx;
1001 return false;
1004 LOG(INFO) << "successful reply to " << to_mbx << " from " << from_mbx;
1005 return true;
1008 bool Session::do_deliver_()
1010 CHECK(msg_);
1012 auto const sender = server_identity_.ascii().c_str();
1013 auto const selector = FLAGS_selector.c_str();
1014 auto const key_file =
1015 (config_path_ / FLAGS_selector).replace_extension("private");
1016 CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1018 try {
1019 auto const msg_data = msg_->freeze();
1021 message::parsed msg;
1023 // Only deal in RFC-5322 Mail Objects.
1024 bool const message_parsed = msg.parse(msg_data);
1025 if (message_parsed) {
1027 // remove any Return-Path
1028 message::remove_delivery_headers(msg);
1030 auto const authentic =
1031 message_parsed &&
1032 message::authentication(msg, sender, selector, key_file);
1034 // write a new Return-Path
1035 msg_->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_));
1037 for (auto const h : msg.headers) {
1038 msg_->write(h.as_string());
1039 msg_->write("\r\n");
1041 if (!msg.body.empty()) {
1042 msg_->write("\r\n");
1043 msg_->write(msg.body);
1046 msg_->deliver();
1048 if (authentic && !fwd_path_.empty()) {
1049 if (!do_forward_(msg))
1050 return false;
1052 if (authentic && !rep_info_.empty()) {
1053 if (!do_reply_(msg))
1054 return false;
1058 msg_->close();
1060 catch (std::system_error const& e) {
1061 switch (errno) {
1062 case ENOSPC:
1063 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
1064 LOG(ERROR) << "no space";
1065 msg_->trash();
1066 reset_();
1067 return false;
1069 default:
1070 out_() << "550 5.0.0 mail system error\r\n" << std::flush;
1071 if (errno)
1072 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
1073 LOG(ERROR) << e.what();
1074 msg_->trash();
1075 reset_();
1076 return false;
1080 return true;
1083 void Session::data_done()
1085 CHECK((state_ == xact_step::data));
1087 if (msg_ && msg_->size_error()) {
1088 data_size_error();
1089 return;
1092 do_deliver_();
1094 // if (prdr_) {
1095 // out_() << "353\r\n";
1096 // for (auto fp : forward_path_) {
1097 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1098 // }
1099 // }
1102 using namespace boost::xpressive;
1104 mark_tag secs_(1);
1105 sregex const rex = icase("wait-data-") >> (secs_ = +_d);
1106 smatch what;
1108 for (auto fp : forward_path_) {
1109 if (regex_match(fp.local_part(), what, rex)) {
1110 auto const str = what[secs_].str();
1111 LOG(INFO) << "waiting at DATA " << str << " seconds";
1112 long value = 0;
1113 std::from_chars(str.data(), str.data() + str.size(), value);
1114 sleep(value);
1115 LOG(INFO) << "done waiting";
1120 out_() << "250 2.0.0 DATA OK\r\n" << std::flush;
1121 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1122 << msg_->id();
1124 reset_();
1127 void Session::data_size_error()
1129 out_().clear(); // clear possible eof from input side
1130 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1131 if (msg_) {
1132 msg_->trash();
1134 LOG(WARNING) << "DATA size error";
1135 reset_();
1138 void Session::data_error()
1140 out_().clear(); // clear possible eof from input side
1141 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush;
1142 if (msg_) {
1143 msg_->trash();
1145 LOG(WARNING) << "DATA error";
1146 reset_();
1149 bool Session::bdat_start(size_t n)
1151 switch (state_) {
1152 case xact_step::helo:
1153 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
1154 LOG(WARNING) << "'BDAT' before HELO/EHLO"
1155 << (sock_.has_peername() ? " from " : "") << client_;
1156 return false;
1157 case xact_step::mail:
1158 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
1159 LOG(WARNING) << "'BDAT' before 'MAIL FROM'"
1160 << (sock_.has_peername() ? " from " : "") << client_;
1161 return false;
1162 case xact_step::rcpt:
1163 // See comment in data_start()
1164 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1165 LOG(WARNING) << "no valid recipients"
1166 << (sock_.has_peername() ? " from " : "") << client_;
1167 return false;
1168 case xact_step::data: // first bdat
1169 break;
1170 case xact_step::bdat: return true;
1171 case xact_step::rset:
1172 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1173 LOG(WARNING) << "error state must be cleared with a RSET"
1174 << (sock_.has_peername() ? " from " : "") << client_;
1175 return false;
1178 state_ = xact_step::bdat;
1180 return msg_new();
1183 void Session::bdat_done(size_t n, bool last)
1185 if (state_ != xact_step::bdat) {
1186 bdat_seq_error();
1187 return;
1190 if (!msg_) {
1191 return;
1194 if (msg_->size_error()) {
1195 bdat_size_error();
1196 return;
1199 if (!last) {
1200 out_() << "250 2.0.0 BDAT " << n << " OK\r\n" << std::flush;
1201 LOG(INFO) << "BDAT " << n;
1202 return;
1205 do_deliver_();
1207 out_() << "250 2.0.0 BDAT " << n << " LAST OK\r\n" << std::flush;
1209 LOG(INFO) << "BDAT " << n << " LAST";
1210 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1211 << msg_->id();
1212 reset_();
1215 void Session::bdat_size_error()
1217 out_().clear(); // clear possible eof from input side
1218 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1219 if (msg_) {
1220 msg_->trash();
1222 LOG(WARNING) << "BDAT size error";
1223 reset_();
1226 void Session::bdat_seq_error()
1228 out_().clear(); // clear possible eof from input side
1229 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush;
1230 if (msg_) {
1231 msg_->trash();
1233 LOG(WARNING) << "BDAT sequence error";
1234 reset_();
1237 void Session::bdat_io_error()
1239 out_().clear(); // clear possible eof from input side
1240 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush;
1241 if (msg_) {
1242 msg_->trash();
1244 LOG(WARNING) << "BDAT I/O error";
1245 reset_();
1248 void Session::rset()
1250 out_() << "250 2.1.5 RSET OK\r\n";
1251 // No flush RFC-2920 section 3.1, this could be part of a command group.
1252 LOG(INFO) << "RSET";
1253 reset_();
1256 void Session::noop(std::string_view str)
1258 last_in_group_("NOOP");
1259 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush;
1260 LOG(INFO) << "NOOP" << (str.length() ? " " : "") << str;
1263 void Session::vrfy(std::string_view str)
1265 last_in_group_("VRFY");
1266 out_() << "252 2.1.5 try it\r\n" << std::flush;
1267 LOG(INFO) << "VRFY" << (str.length() ? " " : "") << str;
1270 void Session::help(std::string_view str)
1272 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n" << std::flush;
1273 LOG(INFO) << "HELP" << (str.length() ? " " : "") << str;
1276 void Session::quit()
1278 send_.quit();
1279 // last_in_group_("QUIT");
1280 out_() << "221 2.0.0 closing connection\r\n" << std::flush;
1281 LOG(INFO) << "QUIT";
1282 exit_();
1285 void Session::auth()
1287 out_() << "454 4.7.0 authentication failure\r\n" << std::flush;
1288 LOG(INFO) << "AUTH";
1289 bad_host_("auth");
1292 void Session::error(std::string_view log_msg)
1294 out_() << "421 4.3.5 system error: " << log_msg << "\r\n" << std::flush;
1295 LOG(WARNING) << log_msg;
1298 void Session::cmd_unrecognized(std::string_view cmd)
1300 auto const escaped{esc(cmd)};
1301 LOG(WARNING) << "command unrecognized: \"" << escaped << "\"";
1303 if (++n_unrecognized_cmds_ >= Config::max_unrecognized_cmds) {
1304 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1305 << "\" exceeds limit\r\n"
1306 << std::flush;
1307 LOG(WARNING) << n_unrecognized_cmds_
1308 << " unrecognized commands is too many";
1309 exit_();
1312 out_() << "500 5.5.1 command unrecognized: \"" << escaped << "\"\r\n"
1313 << std::flush;
1316 void Session::bare_lf()
1318 // Error code used by Office 365.
1319 out_() << "554 5.6.11 bare LF\r\n" << std::flush;
1320 LOG(WARNING) << "bare LF";
1321 exit_();
1324 void Session::max_out()
1326 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1327 LOG(WARNING) << "message size maxed out";
1328 exit_();
1331 void Session::time_out()
1333 out_() << "421 4.4.2 time-out\r\n" << std::flush;
1334 LOG(WARNING) << "time-out" << (sock_.has_peername() ? " from " : "")
1335 << client_;
1336 exit_();
1339 void Session::starttls()
1341 last_in_group_("STARTTLS");
1342 if (sock_.tls()) {
1343 out_() << "554 5.5.1 TLS already active\r\n" << std::flush;
1344 LOG(WARNING) << "STARTTLS issued with TLS already active";
1346 else {
1347 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush;
1348 if (sock_.starttls_server(config_path_)) {
1349 reset_();
1350 max_msg_size(Config::max_msg_size_bro);
1351 LOG(INFO) << "STARTTLS " << sock_.tls_info();
1356 void Session::exit_()
1358 // sock_.log_totals();
1360 timespec time_used{};
1361 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time_used);
1363 LOG(INFO) << "CPU time " << time_used.tv_sec << "." << std::setw(9)
1364 << std::setfill('0') << time_used.tv_nsec << " seconds";
1366 std::exit(EXIT_SUCCESS);
1369 namespace {
1370 bool ip4_allowed(char const* addr)
1372 struct nw {
1373 char const* net;
1374 char const* mask;
1375 char const* comment;
1378 // clang-format off
1380 // 255 0b11111111 8
1381 // 254 0b11111110 7
1382 // 252 0b11111100 6
1383 // 248 0b11111000 5
1384 // 240 0b11110000 4
1385 // 224 0b11100000 3
1386 // 192 0b11000000 2
1387 // 128 0b10000000 1
1389 nw const networks[]{
1390 // the one very special case
1391 {"108.83.36.112", "255.255.255.248", "108.83.36.112/29"},
1393 // accept from major providers:
1394 {"3.0.0.0", "255.0.0.0", "3.0.0.0/9 and 3.128.0.0/9 Amazon"},
1395 {"5.45.198.0", "255.255.254.0", "5.45.198.0/23 YANDEX-5-45-198"},
1396 {"12.153.224.0", "255.255.255.0", "12.153.224.0/24 E-TRADE10-224"},
1397 {"17.0.0.0", "255.0.0.0", "17.0.0.0/8 APPLE-WWNET"},
1398 {"40.74.0.0", "255.254.0.0", "40.74.0.0/15 MSFT NET-40-74-0-0-1"},
1399 {"40.76.0.0", "255.252.0.0", "40.76.0.0/14 MSFT NET-40-74-0-0-1"},
1400 {"40.80.0.0", "255.240.0.0", "40.80.0.0/12 MSFT NET-40-74-0-0-1"},
1401 {"40.96.0.0", "255.240.0.0", "40.96.0.0/12 MSFT NET-40-74-0-0-1"},
1402 {"40.112.0.0", "255.248.0.0", "40.112.0.0/13 MSFT NET-40-74-0-0-1"},
1403 {"40.120.0.0", "255.252.0.0", "40.120.0.0/14 MSFT NET-40-74-0-0-1"},
1404 {"40.124.0.0", "255.255.0.0", "40.124.0.0/16 MSFT NET-40-74-0-0-1"},
1405 {"40.125.0.0", "255.255.128.0", "40.125.0.0/17 MSFT NET-40-74-0-0-1"},
1406 {"56.0.0.0", "255.0.0.0", "56.0.0.0/8 USPS1"},
1407 {"65.52.0.0", "255.252.0.0", "65.52.0.0/14 MICROSOFT-1BLK"},
1408 {"66.163.160.0", "255.255.224.0", "66.163.160.0/19 A-YAHOO-US2"},
1409 {"66.211.176.0", "255.255.240.0", "66.211.176.0/20 EBAY-2"},
1410 {"66.211.172.0", "255.255.252.0", "66.211.172.0/22 EBAY-2"},
1411 {"66.220.144.0", "255.255.240.0", "66.220.144.0/20 TFBNET3"},
1412 {"68.232.192.0", "255.255.240.0", "68.232.192.0/20 EXACT-IP-NET-2"},
1413 {"70.47.67.0", "255.255.255.0", "70.47.67.0/24 NET-462F4300-24"},
1414 {"74.6.0.0", "255.255.0.0", "INKTOMI-BLK-6 Oath"},
1415 {"74.125.0.0", "255.255.0.0", "74.125.0.0/16 GOOGLE"},
1416 {"75.101.100.43", "255.255.255.255", "new.toad.com"},
1417 {"76.178.68.57", "255.255.255.255", "cpe-76-178-68-57.natsow.res.rr.com"},
1418 {"98.136.0.0", "255.252.0.0", "98.136.0.0/14 A-YAHOO-US9"},
1419 {"104.40.0.0", "255.248.0.0", "104.40.0.0/13 MSFT"},
1420 {"108.174.0.0", "255.255.240.0", "108.174.0.0/20 LINKEDIN"},
1421 {"159.45.0.0", "255.255.0.0", "159.45.0.0/16 AGE-COM"},
1422 {"159.53.0.0", "255.255.0.0", "159.53.0.0/16 JMC"},
1423 {"159.135.224.0", "255.255.240.0", "159.135.224.0/20 MNO87-159-135-224-0-0"},
1424 {"162.247.72.0", "255.255.252.0", "162.247.72.0/22 CALYX-INSTITUTE-V4-1"},
1425 {"165.107.0.0", "255.255.0.0", "NET-LDC-CA-GOV"},
1426 {"192.175.128.0", "255.255.128.0", "192.175.128.0/17 NETBLK-VANGUARD"},
1427 {"198.2.128.0", "255.255.192.0", "198.2.128.0/18 RSG-DELIVERY"},
1428 {"198.252.206.0", "255.255.255.0", "198.252.206.0/24 SE-NET01"},
1429 {"199.122.120.0", "255.255.248.0", "199.122.120.0/21 EXACT-IP-NET-3"},
1430 {"204.13.164.0", "255.255.255.0", "204.13.164.0/24 RISEUP-NETWORKS-SWIFT-BLOCK2"},
1431 {"204.29.186.0", "255.255.254.0", "204.29.186.0/23 ATDN-NSCAPE"},
1432 {"205.139.104.0", "255.255.252.0", "205.139.104.0/22 SAVV-S259964-8"},
1433 {"205.201.128.0", "255.255.240.0", "205.201.128.0/20 RSG-DELIVERY"},
1434 {"208.118.235.0", "255.255.255.0", "208.118.235.0/24 TWDX-208-118-235-0-1"},
1435 {"208.192.0.0", "255.192.0.0", "208.192.0.0/10 UUNET1996B"},
1436 {"209.51.188.0", "255.255.255.0", "I:NET-209.51.188.0/24 FSF"},
1437 {"209.85.128.0", "255.255.128.0", "209.85.128.0/17 GOOGLE"},
1438 {"209.132.176.0", "255.255.240.0", "209.132.176.0/20 RED-HAT-BLK"},
1439 {"209.237.224.0", "255.255.224.0", "UNITEDLAYER-1"},
1440 // {"209.237.225.253", "255.255.255.255", "Old new.toad.com"},
1442 // clang-format on
1444 uint32_t addr32;
1445 CHECK_EQ(inet_pton(AF_INET, addr, &addr32), 1)
1446 << "can't interpret as IPv4 address";
1448 for (auto const& network : networks) {
1449 uint32_t net32;
1450 CHECK_EQ(inet_pton(AF_INET, network.net, &net32), 1)
1451 << "can't grok " << network.net;
1452 uint32_t mask32;
1453 CHECK_EQ(inet_pton(AF_INET, network.mask, &mask32), 1)
1454 << "can't grok " << network.mask;
1456 // sanity check: all unmasked bits must be zero
1457 CHECK_EQ(net32 & (~mask32), 0)
1458 << "bogus config net=" << network.net << ", mask=" << network.mask;
1460 if (net32 == (addr32 & mask32)) {
1461 LOG(INFO) << addr << " allowed " << network.comment;
1462 return true;
1466 return false;
1468 } // namespace
1470 /////////////////////////////////////////////////////////////////////////////
1472 // All of the verify_* functions send their own error messages back to
1473 // the client on failure, and return false.
1475 bool Session::verify_ip_address_(std::string& error_msg)
1477 auto ip_block_db_name = config_path_ / "ip-block";
1478 CDB ip_block{ip_block_db_name};
1479 if (ip_block.contains(sock_.them_c_str())) {
1480 error_msg =
1481 fmt::format("IP address {} on static blocklist", sock_.them_c_str());
1482 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1483 return false;
1486 client_fcrdns_.clear();
1488 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1489 (sock_.them_address_literal() == IP6::loopback_literal)) {
1490 LOG(INFO) << "loopback address allowed";
1491 ip_allowed_ = true;
1492 client_fcrdns_.emplace_back("localhost");
1493 client_ = fmt::format("localhost {}", sock_.them_address_literal());
1494 return true;
1497 if (IP::is_private(sock_.them_address_literal())) {
1498 LOG(INFO) << "local address allowed";
1499 ip_allowed_ = true;
1500 client_ = sock_.them_address_literal();
1501 return true;
1504 auto const fcrdns = DNS::fcrdns(res_, sock_.them_c_str());
1505 for (auto const& fcr : fcrdns) {
1506 client_fcrdns_.emplace_back(fcr);
1509 if (!client_fcrdns_.empty()) {
1510 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1511 sock_.them_address_literal());
1512 // check allow list
1513 for (auto const& client_fcrdns : client_fcrdns_) {
1514 if (allow_.contains(client_fcrdns.ascii())) {
1515 // LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1516 fcrdns_allowed_ = true;
1517 return true;
1519 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1520 if (tld) {
1521 if (allow_.contains(tld)) {
1522 // LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1523 fcrdns_allowed_ = true;
1524 return true;
1528 // check blocklist
1529 for (auto const& client_fcrdns : client_fcrdns_) {
1530 if (block_.contains(client_fcrdns.ascii())) {
1531 error_msg =
1532 fmt::format("FCrDNS {} on static blocklist", client_fcrdns.ascii());
1533 out_() << "554 5.7.1 blocklisted\r\n" << std::flush;
1534 return false;
1537 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1538 if (tld) {
1539 if (block_.contains(tld)) {
1540 error_msg = fmt::format(
1541 "FCrDNS registered domain {} on static blocklist", tld);
1542 out_() << "554 5.7.1 blocklisted\r\n" << std::flush;
1543 return false;
1548 else {
1549 client_ = fmt::format("unknown {}", sock_.them_address_literal());
1552 if (IP4::is_address(sock_.them_c_str())) {
1554 if (ip4_allowed(sock_.them_c_str())) {
1555 LOG(INFO) << "on internal allow list";
1556 ip_allowed_ = true;
1557 return true;
1560 auto const reversed{IP4::reverse(sock_.them_c_str())};
1562 // Check with allow list.
1563 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1564 random_device_);
1566 for (auto wl : Config::wls) {
1567 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1568 if (q.has_record()) {
1569 using namespace boost::xpressive;
1571 auto const as = q.get_strings()[0];
1572 LOG(INFO) << "on allow list " << wl << " as " << as;
1574 mark_tag x_(1);
1575 mark_tag y_(2);
1576 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1577 smatch what;
1579 if (regex_match(as, what, rex)) {
1580 auto const x = what[x_].str();
1581 auto const y = what[y_].str();
1583 int value = 0;
1584 std::from_chars(y.data(), y.data() + y.size(), value);
1585 if (value > 0) {
1586 ip_allowed_ = true;
1587 LOG(INFO) << "allowed";
1591 // Any A record skips check on block list
1592 return true;
1596 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1597 std::shuffle(std::begin(Config::bls), std::end(Config::bls),
1598 random_device_);
1600 for (auto bl : Config::bls) {
1602 DNS::Query q(res_, DNS::RR_type::A, reversed + bl);
1603 if (q.has_record()) {
1604 auto const as = q.get_strings()[0];
1605 if (as == "127.0.1.1") {
1606 LOG(INFO) << "Query blocked by " << bl;
1608 else {
1609 error_msg = fmt::format("blocked on advice from {}", bl);
1610 LOG(INFO) << sock_.them_c_str() << " " << error_msg;
1611 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1612 return false;
1616 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1619 return true;
1622 // check the identity from HELO/EHLO
1623 bool Session::verify_client_(Domain const& client_identity,
1624 std::string& error_msg)
1626 if (!client_fcrdns_.empty()) {
1627 if (auto id = std::find(begin(client_fcrdns_), end(client_fcrdns_),
1628 client_identity);
1629 id != end(client_fcrdns_)) {
1630 if (id != begin(client_fcrdns_)) {
1631 std::rotate(begin(client_fcrdns_), id, id + 1);
1633 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1634 sock_.them_address_literal());
1635 return true;
1637 LOG(INFO) << "claimed identity " << client_identity
1638 << " does NOT match any FCrDNS: ";
1639 for (auto const& client_fcrdns : client_fcrdns_) {
1640 LOG(INFO) << " " << client_fcrdns;
1644 // Bogus clients claim to be us or some local host.
1645 if (sock_.has_peername() && ((client_identity == server_identity_) ||
1646 (client_identity == "localhost") ||
1647 (client_identity == "localhost.localdomain"))) {
1649 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1650 (sock_.them_address_literal() == IP6::loopback_literal)) {
1651 return true;
1654 // Give 'em a pass.
1655 if (ip_allowed_) {
1656 LOG(INFO) << "allow-listed IP address can claim to be "
1657 << client_identity;
1658 return true;
1661 // Ease up in test mode.
1662 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1663 return true;
1666 error_msg = fmt::format("liar, claimed to be {}", client_identity.ascii());
1667 out_() << "550 5.7.1 liar\r\n" << std::flush;
1668 return false;
1671 std::vector<std::string> labels;
1672 boost::algorithm::split(labels, client_identity.ascii(),
1673 boost::algorithm::is_any_of("."));
1674 if (labels.size() < 2) {
1675 error_msg =
1676 fmt::format("claimed bogus identity {}", client_identity.ascii());
1677 out_() << "550 4.7.1 bogus identity\r\n" << std::flush;
1678 return false;
1679 // // Sometimes we may want to look at mail from non conforming
1680 // // sending systems.
1681 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1682 // << client_ << " claiming " << client_identity;
1683 // return true;
1686 if (lookup_domain(block_, client_identity)) {
1687 error_msg =
1688 fmt::format("claimed blocked identity {}", client_identity.ascii());
1689 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1690 return false;
1693 auto const tld{tld_db_.get_registered_domain(client_identity.ascii())};
1694 if (!tld) {
1695 // Sometimes we may want to look at mail from misconfigured
1696 // sending systems.
1697 // LOG(WARNING) << "claimed identity has no registered domain";
1698 // return true;
1700 else if (block_.contains(tld)) {
1701 error_msg =
1702 fmt::format("claimed identity has blocked registered domain {}", tld);
1703 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush;
1704 return false;
1707 // not otherwise objectionable
1708 return true;
1711 // check sender from RFC5321 MAIL FROM:
1712 bool Session::verify_sender_(Mailbox const& sender, std::string& error_msg)
1714 std::string const sender_str{sender};
1716 auto bad_senders_db_name = config_path_ / "bad_senders";
1717 CDB bad_senders{bad_senders_db_name}; // Addresses we don't accept mail from.
1718 if (bad_senders.contains(sender_str)) {
1719 error_msg = fmt::format("{} bad sender", sender_str);
1720 out_() << "501 5.1.8 " << error_msg << "\r\n" << std::flush;
1721 return false;
1724 // We don't accept mail /from/ a domain we are expecting to accept
1725 // mail for on an external network connection.
1727 if (sock_.them_address_literal() != sock_.us_address_literal()) {
1728 if ((accept_domains_.is_open() &&
1729 (accept_domains_.contains(sender.domain().ascii()) ||
1730 accept_domains_.contains(sender.domain().utf8()))) ||
1731 (sender.domain() == server_identity_)) {
1733 // Ease up in test mode.
1734 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1735 return true;
1737 out_() << "550 5.7.1 liar\r\n" << std::flush;
1738 error_msg = fmt::format("liar, claimed to be {}", sender.domain());
1739 return false;
1743 if (sender.domain().is_address_literal()) {
1744 if (sender.domain() != sock_.them_address_literal()) {
1745 LOG(WARNING) << "sender domain " << sender.domain() << " does not match "
1746 << sock_.them_address_literal();
1748 return true;
1751 if (!verify_sender_domain_(sender.domain(), error_msg)) {
1752 return false;
1755 if (!verify_sender_spf_(sender)) {
1756 error_msg = "failed SPF check";
1757 return false;
1760 return true;
1763 // this sender is the RFC5321 MAIL FROM: domain part
1764 bool Session::verify_sender_domain_(Domain const& sender,
1765 std::string& error_msg)
1767 if (sender.empty()) {
1768 // MAIL FROM:<>
1769 // is used to send bounce messages.
1770 return true;
1773 if (allow_.contains(sender.ascii())) {
1774 LOG(INFO) << "sender " << sender.ascii() << " allowed";
1775 return true;
1778 // Break sender domain into labels:
1780 std::vector<std::string> labels;
1781 boost::algorithm::split(labels, sender.ascii(),
1782 boost::algorithm::is_any_of("."));
1784 if (labels.size() < 2) { // This is not a valid domain.
1785 error_msg = fmt::format("{} invalid syntax", sender.ascii());
1786 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1787 return false;
1790 auto const reg_dom{tld_db_.get_registered_domain(sender.ascii())};
1791 if (!reg_dom) {
1792 error_msg = fmt::format("{} has no registered domain", sender.ascii());
1793 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1794 return false;
1796 if (allow_.contains(reg_dom)) {
1797 LOG(INFO) << "sender registered domain \"" << reg_dom << "\" allowed";
1798 return true;
1801 // LOG(INFO) << "looking up " << reg_dom;
1802 return verify_sender_domain_uribl_(reg_dom, error_msg);
1805 // check sender domain on dynamic URI block lists
1806 bool Session::verify_sender_domain_uribl_(std::string_view sender,
1807 std::string& error_msg)
1809 if (!sock_.has_peername()) // short circuit
1810 return true;
1812 std::shuffle(std::begin(Config::uribls), std::end(Config::uribls),
1813 random_device_);
1814 for (auto uribl : Config::uribls) {
1815 auto const lookup = fmt::format("{}.{}", sender, uribl);
1816 auto as = DNS::get_strings(res_, DNS::RR_type::A, lookup);
1817 if (!as.empty()) {
1818 if (as.front() == "127.0.0.1")
1819 continue;
1820 error_msg = fmt::format("{} blocked on advice of {}", sender, uribl);
1821 out_() << "550 5.7.1 sender " << error_msg << "\r\n" << std::flush;
1822 return false;
1826 // LOG(INFO) << sender << " cleared by URIBLs";
1827 return true;
1830 bool Session::verify_sender_spf_(Mailbox const& sender)
1832 if (!sock_.has_peername()) {
1833 auto const ip_addr = "127.0.0.1"; // use localhost for local socket
1834 spf_received_ =
1835 fmt::format("Received-SPF: pass ({}: allow-listed) client-ip={}; "
1836 "envelope-from={}; helo={};",
1837 server_id_(), ip_addr, sender, client_identity_);
1838 spf_sender_domain_ = "localhost";
1839 return true;
1842 auto const spf_srv = SPF::Server{server_id_().c_str()};
1843 auto spf_request = SPF::Request{spf_srv};
1845 if (IP4::is_address(sock_.them_c_str())) {
1846 spf_request.set_ipv4_str(sock_.them_c_str());
1848 else if (IP6::is_address(sock_.them_c_str())) {
1849 spf_request.set_ipv6_str(sock_.them_c_str());
1851 else {
1852 LOG(FATAL) << "bogus address " << sock_.them_address_literal() << ", "
1853 << sock_.them_c_str();
1856 spf_request.set_helo_dom(client_identity_.ascii().c_str());
1858 auto const from{static_cast<std::string>(sender)};
1860 spf_request.set_env_from(from.c_str());
1862 auto const spf_res{SPF::Response{spf_request}};
1863 spf_result_ = spf_res.result();
1864 spf_received_ = spf_res.received_spf();
1865 spf_sender_domain_ = spf_request.get_sender_dom();
1867 if (spf_result_ == SPF::Result::PASS) {
1868 if (lookup_domain(block_, spf_sender_domain_)) {
1869 LOG(INFO) << "SPF sender domain (" << spf_sender_domain_
1870 << ") is blocked";
1871 return false;
1875 if (spf_result_ == SPF::Result::FAIL) {
1876 LOG(WARNING) << spf_res.header_comment();
1878 If we want to refuse mail that fails SPF.
1879 Error code from RFC 7372, section 3.2. Also:
1880 <https://www.iana.org/assignments/smtp-enhanced-status-codes/smtp-enhanced-status-codes.xhtml>
1882 out_() << "550 5.7.23 " << spf_res.smtp_comment() << "\r\n" << std::flush;
1883 return false;
1886 else {
1887 LOG(INFO) << spf_res.header_comment();
1890 return true;
1893 bool Session::verify_from_params_(parameters_t const& parameters)
1895 // Take a look at the optional parameters:
1896 for (auto const& [name, value] : parameters) {
1897 if (iequal(name, "BODY")) {
1898 if (iequal(value, "8BITMIME")) {
1899 // everything is cool, this is our default...
1901 else if (iequal(value, "7BIT")) {
1902 // nothing to see here, move along...
1904 else if (iequal(value, "BINARYMIME")) {
1905 binarymime_ = true;
1907 else {
1908 LOG(WARNING) << "unrecognized BODY type \"" << value << "\" requested";
1911 else if (iequal(name, "SMTPUTF8")) {
1912 if (!value.empty()) {
1913 LOG(WARNING) << "SMTPUTF8 parameter has a value: " << value;
1915 smtputf8_ = true;
1918 // else if (iequal(name, "PRDR")) {
1919 // LOG(INFO) << "using PRDR";
1920 // prdr_ = true;
1921 // }
1923 else if (iequal(name, "SIZE")) {
1924 if (value.empty()) {
1925 LOG(WARNING) << "SIZE parameter has no value.";
1927 else {
1928 try {
1929 auto const sz = stoull(value);
1930 if (sz > max_msg_size()) {
1931 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1932 LOG(WARNING) << "SIZE parameter too large: " << sz;
1933 return false;
1936 catch (std::invalid_argument const& e) {
1937 LOG(WARNING) << "SIZE parameter has invalid value: " << value;
1939 catch (std::out_of_range const& e) {
1940 LOG(WARNING) << "SIZE parameter has out-of-range value: " << value;
1942 // I guess we just ignore bad size parameters.
1945 else if (iequal(name, "REQUIRETLS")) {
1946 if (!sock_.tls()) {
1947 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush;
1948 LOG(WARNING) << "REQUIRETLS needed";
1949 return false;
1952 else {
1953 LOG(WARNING) << "unrecognized 'MAIL FROM' parameter " << name << "="
1954 << value;
1958 return true;
1961 bool Session::verify_rcpt_params_(parameters_t const& parameters)
1963 // Take a look at the optional parameters:
1964 for (auto const& [name, value] : parameters) {
1965 if (iequal(name, "RRVS")) {
1966 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
1967 LOG(INFO) << name << "=" << value;
1969 else {
1970 LOG(WARNING) << "unrecognized 'RCPT TO' parameter " << name << "="
1971 << value;
1975 return true;
1978 // check recipient from RFC5321 RCPT TO:
1979 bool Session::verify_recipient_(Mailbox const& recipient)
1981 if ((recipient.local_part() == "Postmaster") && (recipient.domain() == "")) {
1982 LOG(INFO) << "magic Postmaster address";
1983 return true;
1986 auto const accepted_domain{[this, &recipient] {
1987 if (recipient.domain().is_address_literal()) {
1988 if (recipient.domain() != sock_.us_address_literal()) {
1989 LOG(WARNING) << "recipient.domain address " << recipient.domain()
1990 << " does not match ours " << sock_.us_address_literal();
1991 return false;
1993 return true;
1996 // Domains we accept mail for.
1997 if (accept_domains_.is_open()) {
1998 if (accept_domains_.contains(recipient.domain().ascii()) ||
1999 accept_domains_.contains(recipient.domain().utf8())) {
2000 return true;
2003 else {
2004 // If we have no list of domains to accept, at least take our own.
2005 if (recipient.domain() == server_id_()) {
2006 return true;
2010 return false;
2011 }()};
2013 if (!accepted_domain) {
2014 out_() << "554 5.7.1 relay access denied\r\n" << std::flush;
2015 LOG(WARNING) << "relay access denied for domain " << recipient.domain();
2016 return false;
2019 // Check for local addresses we reject.
2021 auto bad_recipients_db_name = config_path_ / "bad_recipients";
2022 CDB bad_recipients_db{bad_recipients_db_name};
2023 if (bad_recipients_db.contains(recipient.local_part())) {
2024 out_() << "550 5.1.1 bad recipient " << recipient << "\r\n" << std::flush;
2025 LOG(WARNING) << "bad recipient " << recipient;
2026 return false;
2031 auto temp_fail_db_name = config_path_ / "temp_fail";
2032 CDB temp_fail{temp_fail_db_name}; // Addresses we make wait...
2033 if (temp_fail.contains(recipient.local_part())) {
2034 out_() << "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n"
2035 << std::flush;
2036 LOG(WARNING) << "temp fail for recipient " << recipient;
2037 return false;
2041 // Check for and act on magic "wait" address.
2043 using namespace boost::xpressive;
2045 mark_tag secs_(1);
2046 sregex const rex = icase("wait-rcpt-") >> (secs_ = +_d);
2047 smatch what;
2049 if (regex_match(recipient.local_part(), what, rex)) {
2050 auto const str = what[secs_].str();
2051 LOG(INFO) << "waiting at RCPT TO " << str << " seconds";
2052 long value = 0;
2053 std::from_chars(str.data(), str.data() + str.size(), value);
2054 sleep(value);
2055 LOG(INFO) << "done waiting";
2059 return true;