share
[ghsmtp.git] / Session.cpp
blob82a40286de11d3547f81b4c2d800f36c5d606b55
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 "is_ascii.hpp"
18 #include "osutil.hpp"
20 #include <experimental/iterator>
22 #include <fmt/format.h>
23 #include <fmt/ostream.h>
25 #include <boost/algorithm/string/classification.hpp>
26 #include <boost/algorithm/string/split.hpp>
28 #include <boost/xpressive/xpressive.hpp>
30 #include <syslog.h>
32 #include <gflags/gflags.h>
34 using namespace std::string_literals;
36 namespace Config {
38 char const* wls[]{
39 "list.dnswl.org",
44 <https://www.dnswl.org/?page_id=15#query>
46 Return codes
48 The return codes are structured as 127.0.x.y, with “x” indicating the category
49 of an entry and “y” indicating how trustworthy an entry has been judged.
51 Categories (127.0.X.y):
53 2 – Financial services
54 3 – Email Service Providers
55 4 – Organisations (both for-profit [ie companies] and non-profit)
56 5 – Service/network providers
57 6 – Personal/private servers
58 7 – Travel/leisure industry
59 8 – Public sector/governments
60 9 – Media and Tech companies
61 10 – some special cases
62 11 – Education, academic
63 12 – Healthcare
64 13 – Manufacturing/Industrial
65 14 – Retail/Wholesale/Services
66 15 – Email Marketing Providers
67 20 – Added through Self Service without specific category
69 Trustworthiness / Score (127.0.x.Y):
71 0 = none – only avoid outright blocking (eg large ESP mailservers, -0.1)
72 1 = low – reduce chance of false positives (-1.0)
73 2 = medium – make sure to avoid false positives but allow override for clear
74 cases (-10.0) 3 = high – avoid override (-100.0).
76 The scores in parantheses are typical SpamAssassin scores.
78 Special return code 127.0.0.255
80 In cases where your nameserver issues more than 100’000 queries / 24 hours, you
81 may be blocked from further queries. The return code “127.0.0.255” indicates
82 this situation.
86 char const* bls[]{
87 "b.barracudacentral.org",
88 "sbl-xbl.spamhaus.org",
91 /*** Last octet from A record returned by blocklists ***
93 <https://www.spamhaus.org/faq/section/DNSBL%20Usage#200>
95 Spamhaus uses this general convention for return codes:
97 Return Code Description
98 127.0.0.0/24 Spamhaus IP Blocklists
99 127.0.1.0/24 Spamhaus Domain Blocklists
100 127.0.2.0/24 Spamhaus Zero Reputation Domains list
101 127.255.255.0/24 ERRORS (not implying a "listed" response)
103 Currently used return codes for Spamhaus public IP zones:
105 Return Code Zone Description
106 127.0.0.2 SBL Spamhaus SBL Data
107 127.0.0.3 SBL Spamhaus SBL CSS Data
108 127.0.0.4 XBL CBL Data
109 127.0.0.9 SBL Spamhaus DROP/EDROP Data
110 (in addition to 127.0.0.2, since 01-Jun-2016)
111 127.0.0.10 PBL ISP Maintained
112 127.0.0.11 PBL Spamhaus Maintained
114 127.0.0.5-7 are allocated to XBL for possible future use;
115 127.0.0.8 is allocated to SBL for possible future use.
117 From <https://www.spamhaus.org/faq/section/Spamhaus%20DBL#291>
119 Return Codes Data Source
120 127.0.1.2 spam domain
121 127.0.1.4 phish domain
122 127.0.1.5 malware domain
123 127.0.1.6 botnet C&C domain
124 127.0.1.102 abused legit spam
125 127.0.1.103 abused spammed redirector domain
126 127.0.1.104 abused legit phish
127 127.0.1.105 abused legit malware
128 127.0.1.106 abused legit botnet C&C
129 127.0.1.255 IP queries prohibited!
131 The following special codes indicate an error condition and should not
132 be taken to imply that the queried domain is "listed":
134 Return Code Description
135 127.255.255.252 Typing error in DNSBL name
136 127.255.255.254 Anonymous query through public resolver
137 127.255.255.255 Excessive number of queries
140 From <http://www.surbl.org/lists#multi>
142 last octet indicates which lists it belongs to. The bit positions in
143 that last octet for membership in the different lists are:
145 8 = listed on PH
146 16 = listed on MW
147 64 = listed on ABUSE
148 128 = listed on CR
153 char const* uribls[]{
154 "dbl.spamhaus.org",
155 "multi.uribl.com",
159 constexpr auto greeting_wait = std::chrono::seconds{6};
160 constexpr int max_recipients_per_message = 100;
161 constexpr int max_unrecognized_cmds = 20;
163 // Read timeout value gleaned from RFC-1123 section 5.3.2 and RFC-5321
164 // section 4.5.3.2.7.
165 constexpr auto read_timeout = std::chrono::minutes{5};
166 constexpr auto write_timeout = std::chrono::seconds{30};
167 } // namespace Config
169 DEFINE_bool(immortal, false, "don't set process timout");
171 DEFINE_uint64(max_read, 0, "max data to read");
172 DEFINE_uint64(max_write, 0, "max data to write");
174 DEFINE_bool(test_mode, false, "ease up on some checks");
176 DEFINE_bool(use_binarymime, true, "support BINARYMIME extension, RFC 3030");
177 DEFINE_bool(use_chunking, true, "support CHUNKING extension, RFC 3030");
178 DEFINE_bool(use_pipelining, true, "support PIPELINING extension, RFC 2920");
179 DEFINE_bool(use_rrvs, true, "support RRVS extension, RFC 7293");
180 DEFINE_bool(use_prdr, true, "support PRDR extension");
181 DEFINE_bool(use_smtputf8, true, "support SMTPUTF8 extension, RFC 6531");
183 boost::xpressive::mark_tag secs_(1);
184 boost::xpressive::sregex const all_rex = boost::xpressive::icase("wait-all-") >>
185 (secs_ = +boost::xpressive::_d);
187 Session::Session(fs::path config_path,
188 std::function<void(void)> read_hook,
189 int fd_in,
190 int fd_out)
191 : config_path_(config_path)
192 , res_(config_path)
193 , sock_(fd_in, fd_out, read_hook, Config::read_timeout, Config::write_timeout)
194 //, send_(config_path, "smtp")
195 //, srs_(config_path)
197 auto accept_db_name = config_path_ / "accept_domains";
198 auto allow_db_name = config_path_ / "allow";
199 auto block_db_name = config_path_ / "block";
200 // auto forward_db_name = config_path_ / "forward";
202 accept_domains_.open(accept_db_name);
203 allow_.open(allow_db_name);
204 block_.open(block_db_name);
205 // forward_.open(forward_db_name);
207 if (sock_.has_peername() && !IP::is_private(sock_.us_c_str())) {
208 auto fcrdns = DNS::fcrdns(res_, sock_.us_c_str());
209 for (auto const& fcr : fcrdns) {
210 server_fcrdns_.emplace_back(fcr);
214 server_identity_ = [this] {
215 auto const id_from_env{getenv("GHSMTP_SERVER_ID")};
216 if (id_from_env)
217 return std::string{id_from_env};
219 auto const hostname{osutil::get_hostname()};
220 if (hostname.find('.') != std::string::npos)
221 return hostname;
223 if (!server_fcrdns_.empty()) {
224 // first result should be shortest
225 return server_fcrdns_.front().ascii();
228 auto const us_c_str = sock_.us_c_str();
229 if (us_c_str && !IP::is_private(us_c_str)) {
230 return IP::to_address_literal(us_c_str);
233 LOG(FATAL) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
234 return ""s;
235 }();
237 // send_.set_sender(server_identity_);
239 max_msg_size(Config::max_msg_size_initial);
242 void Session::max_msg_size(size_t max)
244 max_msg_size_ = max; // number to advertise via RFC 1870
246 if (FLAGS_max_read) {
247 sock_.set_max_read(FLAGS_max_read);
249 else {
250 auto const overhead = std::max(max / 10, size_t(2048));
251 sock_.set_max_read(max + overhead);
255 void Session::bad_host_(char const* msg) const
257 if (sock_.has_peername()) {
258 // On my systems, this pattern triggers a fail2ban rule that
259 // blocks connections from this IP address on port 25 for a few
260 // days. See <https://www.fail2ban.org/> for more info.
261 syslog(LOG_MAIL | LOG_WARNING, "bad host [%s] %s", sock_.them_c_str(), msg);
263 std::exit(EXIT_SUCCESS);
266 void Session::reset_()
268 // RSET does not force another EHLO/HELO, the one piece of per
269 // transaction data saved is client_identity_:
271 // client_identity_.clear(); <-- not cleared!
273 reverse_path_.clear();
274 forward_path_.clear();
275 spf_received_.clear();
276 // fwd_path_.clear();
277 // fwd_from_.clear();
278 // rep_info_.clear();
280 binarymime_ = false;
281 smtputf8_ = false;
282 prdr_ = false;
284 if (msg_) {
285 msg_.reset();
288 max_msg_size(max_msg_size());
290 state_ = xact_step::mail;
291 // send_.rset();
294 // Return codes from connection establishment are 220 or 554, according
295 // to RFC 5321. That's it.
297 void Session::greeting()
299 CHECK(state_ == xact_step::helo);
301 if (sock_.has_peername()) {
302 close(2); // if we're a networked program, never send to stderr
304 std::string error_msg;
305 if (!verify_ip_address_(error_msg)) {
306 LOG(INFO) << error_msg;
307 bad_host_(error_msg.c_str());
310 /******************************************************************
311 <https://tools.ietf.org/html/rfc5321#section-4.3.1> says:
313 4.3. Sequencing of Commands and Replies
315 4.3.1. Sequencing Overview
317 The communication between the sender and receiver is an alternating
318 dialogue, controlled by the sender. As such, the sender issues a
319 command and the receiver responds with a reply. Unless other
320 arrangements are negotiated through service extensions, the sender
321 MUST wait for this response before sending further commands. One
322 important reply is the connection greeting. Normally, a receiver
323 will send a 220 "Service ready" reply when the connection is
324 completed. The sender SHOULD wait for this greeting message before
325 sending any commands.
327 So which is it?
329 “…the receiver responds with a reply.”
330 “…the sender MUST wait for this response…”
331 “One important reply is the connection greeting.”
332 “The sender SHOULD wait for this greeting…”
334 So is it MUST or SHOULD? I enforce MUST.
335 *******************************************************************/
337 // Wait a bit of time for pre-greeting traffic.
338 if (!(ip_allowed_ || fcrdns_allowed_)) {
339 if (sock_.input_ready(Config::greeting_wait)) {
340 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush;
341 LOG(INFO) << "input before any greeting from " << client_;
342 bad_host_("input before any greeting");
344 // Give a half greeting and wait again.
345 out_() << "220-" << server_id_() << " ESMTP slowstart - ghsmtp\r\n"
346 << std::flush;
347 if (sock_.input_ready(Config::greeting_wait)) {
348 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush;
349 LOG(INFO) << "input before full greeting from " << client_;
350 bad_host_("input before full greeting");
353 <https://www.rfc-editor.org/rfc/rfc5321#section-4.2>
355 An SMTP client MUST determine its actions only by the reply code, not
356 by the text (except for the "change of address" 251 and 551 and, if
357 necessary, 220, 221, and 421 replies); in the general case, any text,
358 including no text at all (although senders SHOULD NOT send bare
359 codes), MUST be acceptable. The space (blank) following the reply
360 code is considered part of the text. Whenever possible, a receiver-
361 SMTP SHOULD test the first digit (severity indication) of the reply
362 code.
364 Except the following chokes a lot of senders:
366 out_() << "220\r\n" << std::flush;
369 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush;
371 else {
372 out_() << "220 " << server_id_() << " ESMTP faststart - ghsmtp\r\n"
373 << std::flush;
376 else {
377 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush;
380 LOG(INFO) << "connect from " << client_;
382 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
383 alarm(2 * 60); // initial alarm
387 void Session::flush() { out_() << std::flush; }
389 void Session::last_in_group_(std::string_view verb)
391 if (sock_.input_ready(std::chrono::seconds(0))) {
392 LOG(WARNING) << "pipelining error; input ready processing " << verb;
396 void Session::check_for_pipeline_error_(std::string_view verb)
398 if (!(FLAGS_use_pipelining && extensions_)) {
399 if (sock_.input_ready(std::chrono::seconds(0))) {
400 LOG(WARNING) << "pipelining error; input ready processing " << verb;
405 void Session::lo_(char const* verb, std::string_view client_identity)
407 last_in_group_(verb);
408 reset_();
410 if (client_identity_ != client_identity) {
411 client_identity_ = client_identity;
413 std::string error_msg;
414 if (!verify_client_(client_identity_, error_msg)) {
415 LOG(INFO) << "client identity blocked: " << error_msg;
416 bad_host_(error_msg.c_str());
420 if (*verb == 'H') {
421 extensions_ = false;
422 out_() << "250 " << server_id_() << "\r\n";
425 if (*verb == 'E') {
426 extensions_ = true;
428 if (sock_.has_peername()) {
429 out_() << "250-" << server_id_() << " at your service, " << client_
430 << "\r\n";
432 else {
433 out_() << "250-" << server_id_() << "\r\n";
436 // LIMITS SMTP Service Extension,
437 // <https://www.rfc-editor.org/rfc/rfc9422.html>
438 out_() << "250-LIMITS RCPTMAX=" << Config::max_recipients_per_message
439 << "\r\n";
440 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
441 out_() << "250-8BITMIME\r\n"; // RFC 6152
443 if (FLAGS_use_rrvs) {
444 out_() << "250-RRVS\r\n"; // RFC 7293
447 if (FLAGS_use_prdr) {
448 out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
451 if (sock_.tls()) {
452 // Check sasl sources for auth types.
453 // out_() << "250-AUTH PLAIN\r\n";
454 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
456 else {
457 // If we're not already TLS, offer TLS
458 out_() << "250-STARTTLS\r\n"; // RFC 3207
461 out_() << "250-ENHANCEDSTATUSCODES\r\n"; // RFC 2034
463 if (FLAGS_use_pipelining) {
464 out_() << "250-PIPELINING\r\n"; // RFC 2920
467 if (FLAGS_use_binarymime) {
468 out_() << "250-BINARYMIME\r\n"; // RFC 3030
471 if (FLAGS_use_chunking) {
472 out_() << "250-CHUNKING\r\n"; // RFC 3030
475 if (FLAGS_use_smtputf8) {
476 out_() << "250-SMTPUTF8\r\n"; // RFC 6531
479 out_() << "250 HELP\r\n";
482 out_() << std::flush;
484 if (sock_.has_peername()) {
485 if (std::find(begin(client_fcrdns_), end(client_fcrdns_),
486 client_identity_) != end(client_fcrdns_)) {
487 LOG(INFO) << verb << " " << client_identity << " from "
488 << sock_.them_address_literal();
490 else {
491 LOG(INFO) << verb << " " << client_identity << " from " << client_;
494 else {
495 LOG(INFO) << verb << " " << client_identity;
499 void Session::mail_from(Mailbox&& reverse_path, parameters_t const& parameters)
501 check_for_pipeline_error_("MAIL FROM");
503 switch (state_) {
504 case xact_step::helo:
505 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
506 LOG(WARNING) << "'MAIL FROM' before HELO/EHLO"
507 << (sock_.has_peername() ? " from " : "") << client_;
508 return;
509 case xact_step::mail: break;
510 case xact_step::rcpt:
511 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
512 LOG(WARNING) << "nested MAIL command"
513 << (sock_.has_peername() ? " from " : "") << client_;
514 return;
515 case xact_step::data:
516 case xact_step::bdat:
517 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush;
518 LOG(WARNING) << "nested MAIL command"
519 << (sock_.has_peername() ? " from " : "") << client_;
520 return;
521 case xact_step::rset:
522 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
523 LOG(WARNING) << "error state must be cleared with a RSET"
524 << (sock_.has_peername() ? " from " : "") << client_;
525 return;
528 if (!verify_from_params_(parameters)) {
529 return;
532 if (!smtputf8_ && !is_ascii(reverse_path.local_part())) {
533 LOG(WARNING) << "non ascii reverse_path \"" << reverse_path
534 << "\" without SMTPUTF8 paramater";
537 std::string error_msg;
538 if (!verify_sender_(reverse_path, error_msg)) {
539 LOG(INFO) << "verify sender failed: " << error_msg;
540 bad_host_(error_msg.c_str());
543 reverse_path_ = std::move(reverse_path);
544 // fwd_path_.clear();
545 // fwd_from_.clear();
546 forward_path_.clear();
547 out_() << "250 2.1.0 MAIL FROM OK\r\n";
548 // No flush RFC-2920 section 3.1, this could be part of a command group.
550 fmt::memory_buffer params;
551 for (auto const& [name, value] : parameters) {
552 fmt::format_to(std::back_inserter(params), " {}", name);
553 if (!value.empty()) {
554 fmt::format_to(std::back_inserter(params), "={}", value);
557 LOG(INFO) << "MAIL FROM:<" << reverse_path_ << ">" << fmt::to_string(params);
559 state_ = xact_step::rcpt;
562 void Session::rcpt_to(Mailbox&& forward_path, parameters_t const& parameters)
564 check_for_pipeline_error_("RCPT TO");
566 switch (state_) {
567 case xact_step::helo:
568 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
569 LOG(WARNING) << "'RCPT TO' before HELO/EHLO"
570 << (sock_.has_peername() ? " from " : "") << client_;
571 return;
572 case xact_step::mail:
573 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
574 LOG(WARNING) << "'RCPT TO' before 'MAIL FROM'"
575 << (sock_.has_peername() ? " from " : "") << client_;
576 return;
577 case xact_step::rcpt:
578 case xact_step::data: break;
579 case xact_step::bdat:
580 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
581 LOG(WARNING) << "'RCPT TO' during BDAT transfer"
582 << (sock_.has_peername() ? " from " : "") << client_;
583 return;
584 case xact_step::rset:
585 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
586 LOG(WARNING) << "error state must be cleared with a RSET"
587 << (sock_.has_peername() ? " from " : "") << client_;
588 return;
591 if (!verify_rcpt_params_(parameters))
592 return;
594 if (!verify_recipient_(forward_path))
595 return;
597 if (!smtputf8_ && !is_ascii(forward_path.local_part())) {
598 LOG(WARNING) << "non ascii forward_path \"" << forward_path
599 << "\" without SMTPUTF8 paramater";
602 if (forward_path_.size() >= Config::max_recipients_per_message) {
603 out_() << "452 4.5.3 too many recipients\r\n" << std::flush;
604 LOG(WARNING) << "too many recipients <" << forward_path << ">";
605 return;
607 // no check for dups, postfix doesn't
608 forward_path_.emplace_back(std::move(forward_path));
610 Mailbox const& rcpt_to_mbx = forward_path_.back();
612 LOG(INFO) << "RCPT TO:<" << rcpt_to_mbx << ">";
614 // No flush RFC-2920 section 3.1, this could be part of a command group.
615 out_() << "250 2.1.5 RCPT TO OK\r\n";
617 state_ = xact_step::data;
620 // The headers Return-Path:, Received-SPF:, and Received: are returned
621 // as a string.
623 std::string Session::added_headers_(MessageStore const& msg)
625 auto const protocol{[this]() {
626 if (sock_.tls() && !extensions_) {
627 LOG(WARNING) << "TLS active without extensions";
629 // <https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#mail-parameters-5>
630 if (smtputf8_)
631 return sock_.tls() ? "UTF8SMTPS" : "UTF8SMTP";
632 else if (sock_.tls())
633 return "ESMTPS";
634 else if (extensions_)
635 return "ESMTP";
636 else
637 return "SMTP";
638 }()};
640 fmt::memory_buffer headers;
642 // Return-Path:
643 fmt::format_to(std::back_inserter(headers), "Return-Path: <{}>\r\n",
644 reverse_path_.as_string());
646 // Received-SPF:
647 if (!spf_received_.empty()) {
648 fmt::format_to(std::back_inserter(headers), "{}\r\n", spf_received_);
651 // Received:
652 // <https://tools.ietf.org/html/rfc5321#section-4.4>
653 fmt::format_to(std::back_inserter(headers), "Received: from {}",
654 client_identity_.utf8());
655 if (sock_.has_peername()) {
656 fmt::format_to(std::back_inserter(headers), " ({})", client_);
658 fmt::format_to(std::back_inserter(headers), "\r\n\tby {} with {} id {}",
659 server_identity_.utf8(), protocol, msg.id().as_string_view());
660 if (forward_path_.size()) {
661 fmt::format_to(std::back_inserter(headers), "\r\n\tfor <{}>",
662 forward_path_[0].as_string());
663 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
664 // “If the FOR clause appears, it MUST contain exactly one <path>
665 // entry, even when multiple RCPT commands have been given. Multiple
666 // <path>s raise some security issues and have been deprecated, see
667 // Section 7.2.”
668 // for (auto i = 1u; i < forward_path_.size(); ++i)
669 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
671 std::string const tls_info{sock_.tls_info()};
672 if (tls_info.length()) {
673 fmt::format_to(std::back_inserter(headers), "\r\n\t({})", tls_info);
675 fmt::format_to(std::back_inserter(headers), ";\r\n\t{}\r\n",
676 msg.when().as_string_view());
678 return fmt::to_string(headers);
681 namespace {
682 bool lookup_domain(CDB& cdb, Domain const& domain)
684 if (!domain.empty()) {
685 if (cdb.contains(domain.ascii())) {
686 return true;
688 if (domain.is_unicode() && cdb.contains(domain.utf8())) {
689 return true;
692 return false;
694 } // namespace
696 std::tuple<Session::SpamStatus, std::string> Session::spam_status_()
698 if (spf_result_ == SPF::Result::FAIL && !ip_allowed_)
699 return {SpamStatus::spam, "SPF failed"};
701 // These should have already been rejected by verify_client_().
702 if ((reverse_path_.domain() == "localhost.local") ||
703 (reverse_path_.domain() == "localhost"))
704 return {SpamStatus::spam, "bogus reverse_path"};
706 std::vector<std::string> why_ham;
708 // Anything enciphered tastes a lot like ham.
709 if (sock_.tls())
710 why_ham.emplace_back("they used TLS");
712 if (spf_result_ == SPF::Result::PASS) {
713 if (lookup_domain(allow_, spf_sender_domain_)) {
714 why_ham.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
715 spf_sender_domain_.utf8()));
717 else {
718 auto tld_dom{tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
719 if (tld_dom && allow_.contains(tld_dom)) {
720 why_ham.emplace_back(fmt::format(
721 "SPF sender registered domain ({}) is allowed", tld_dom));
726 if (fcrdns_allowed_)
727 why_ham.emplace_back(
728 fmt::format("FCrDNS (or it's registered domain) is allowed"));
730 if (!why_ham.empty())
731 return {SpamStatus::ham,
732 fmt::format("{}", fmt::join(std::begin(why_ham), std::end(why_ham),
733 ", and "))};
735 return {SpamStatus::spam, "it's not ham"};
738 static std::string folder(Session::SpamStatus status,
739 std::vector<Mailbox> const& forward_path,
740 Mailbox const& reverse_path)
742 if (reverse_path ==
743 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
744 return ".Gmail";
746 if (reverse_path == Mailbox("ietf-smtp-bounces@ietf.org"))
747 return ".smtp";
749 struct assignment {
750 std::string_view local_part;
751 std::string_view folder;
754 assignment assignments[] = {
755 {"bootstrappable", ".bootstrappable"},
756 {"coreboot.org", ".coreboot"},
757 {"dmarc", ".dmarc"},
758 {"dns-privacy", ".dns-privacy"},
759 {"dnsmasq", ".INBOX.DNSmasq"},
760 {"emailcore", ".emailcore"},
761 {"fucking-facebook", ".FB"},
762 {"gene-ebay", ".EBay"},
763 {"i-hate-facebook", ".FB"},
764 {"i-hate-linked-in", ".linkedin"},
765 {"mailop", ".INBOX.mailop"},
766 {"modelfkeyboards.com", ""},
767 {"nest", ".INBOX.Nest"},
768 {"opendmarc-dev", ".dmarc"},
769 {"opendmarc-users", ".dmarc"},
770 {"papasys.com", ".INBOX.PAPA"},
771 {"postmaster-rua", ".INBOX.rua"},
772 {"quic=ietf.org", ".INBOX.quic"},
773 {"shadowserver-reports@digilicious.com", ".INBOX.shadowserver"},
774 {"theatlantic.com", ""},
775 {"time-nutz", ".time-nutz"},
776 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
779 for (auto ass : assignments) {
780 if (iequal(forward_path[0].local_part(), ass.local_part))
781 return std::string(ass.folder);
784 if (iends_with(forward_path[0].local_part(), "-at-duck"))
785 return ".JunkDuck";
787 if (status == Session::SpamStatus::spam)
788 return ".Junk";
790 return "";
793 bool Session::msg_new()
795 CHECK((state_ == xact_step::data) || (state_ == xact_step::bdat));
797 auto const& [status, reason]{spam_status_()};
799 LOG(INFO) << ((status == SpamStatus::ham) ? "ham since " : "spam since ")
800 << reason;
802 // All sources of ham get a fresh 5 minute timeout per message.
803 if (status == SpamStatus::ham) {
804 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr))
805 alarm(5 * 60);
808 msg_ = std::make_unique<MessageStore>();
810 if (!FLAGS_max_write)
811 FLAGS_max_write = max_msg_size();
813 try {
814 msg_->open(server_id_(), FLAGS_max_write,
815 folder(status, forward_path_, reverse_path_));
816 auto const hdrs{added_headers_(*(msg_.get()))};
817 msg_->write(hdrs);
819 // fmt::memory_buffer spam_status;
820 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
821 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
822 // msg_->write(spam_status.data(), spam_status.size());
824 LOG(INFO) << "Spam-Status: "
825 << ((status == SpamStatus::spam) ? "Yes" : "No") << ", "
826 << reason;
828 return true;
830 catch (std::system_error const& e) {
831 switch (errno) {
832 case ENOSPC:
833 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
834 LOG(ERROR) << "no space";
835 msg_->trash();
836 msg_.reset();
837 return false;
839 default:
840 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
841 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
842 LOG(ERROR) << e.what();
843 msg_->trash();
844 msg_.reset();
845 return false;
848 catch (std::exception const& e) {
849 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
850 LOG(ERROR) << e.what();
851 msg_->trash();
852 msg_.reset();
853 return false;
856 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
857 LOG(ERROR) << "msg_new failed with no exception caught";
858 msg_->trash();
859 msg_.reset();
860 return false;
863 bool Session::msg_write(char const* s, std::streamsize count)
865 if ((state_ != xact_step::data) && (state_ != xact_step::bdat))
866 return false;
868 if (!msg_)
869 return false;
871 try {
872 if (msg_->write(s, count))
873 return true;
875 catch (std::system_error const& e) {
876 switch (errno) {
877 case ENOSPC:
878 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
879 LOG(ERROR) << "no space";
880 msg_->trash();
881 msg_.reset();
882 return false;
884 default:
885 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
886 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
887 LOG(ERROR) << e.what();
888 msg_->trash();
889 msg_.reset();
890 return false;
893 catch (std::exception const& e) {
894 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
895 LOG(ERROR) << e.what();
896 msg_->trash();
897 msg_.reset();
898 return false;
901 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
902 LOG(ERROR) << "msg_write failed with no exception caught";
903 msg_->trash();
904 msg_.reset();
905 return false;
908 bool Session::data_start()
910 last_in_group_("DATA");
912 switch (state_) {
913 case xact_step::helo:
914 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
915 LOG(WARNING) << "'DATA' before HELO/EHLO"
916 << (sock_.has_peername() ? " from " : "") << client_;
917 return false;
918 case xact_step::mail:
919 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
920 LOG(WARNING) << "'DATA' before 'MAIL FROM'"
921 << (sock_.has_peername() ? " from " : "") << client_;
922 return false;
923 case xact_step::rcpt:
925 /******************************************************************
926 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
928 The DATA command can fail for only two reasons:
930 If there was no MAIL, or no RCPT, command, or all such commands were
931 rejected, the server MAY return a "command out of sequence" (503) or
932 "no valid recipients" (554) reply in response to the DATA command.
934 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
936 The additional restriction is that when there have been no successful
937 RCPT commands in the mail transaction, the DATA command MUST fail
938 with a 503 reply code.
940 Therefore I will send the reply code that is valid for both, and
941 do the same for the BDAT case.
942 *******************************************************************/
944 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
945 LOG(WARNING) << "no valid recipients"
946 << (sock_.has_peername() ? " from " : "") << client_;
947 return false;
948 case xact_step::data: break;
949 case xact_step::bdat:
950 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
951 LOG(WARNING) << "'DATA' during BDAT transfer"
952 << (sock_.has_peername() ? " from " : "") << client_;
953 return false;
954 case xact_step::rset:
955 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
956 LOG(WARNING) << "error state must be cleared with a RSET"
957 << (sock_.has_peername() ? " from " : "") << client_;
958 return false;
961 if (binarymime_) {
962 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
963 << std::flush;
964 LOG(WARNING) << "DATA does not support BINARYMIME";
965 state_ = xact_step::rset; // RFC 3030 section 3 page 5
966 return false;
969 if (!msg_new()) {
970 LOG(ERROR) << "msg_new() failed";
971 return false;
974 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush;
975 LOG(INFO) << "DATA";
976 return true;
979 bool Session::do_deliver_()
981 CHECK(msg_);
983 try {
984 msg_->deliver();
985 msg_->close();
987 catch (std::system_error const& e) {
988 switch (errno) {
989 case ENOSPC:
990 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
991 LOG(ERROR) << "no space";
992 msg_->trash();
993 reset_();
994 return false;
996 default:
997 out_() << "451 4.3.0 mail system error\r\n" << std::flush;
998 if (errno)
999 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
1000 LOG(ERROR) << e.what();
1001 msg_->trash();
1002 reset_();
1003 return false;
1007 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1008 << msg_->id();
1009 return true;
1012 void Session::xfer_response_(std::string_view success_msg)
1014 auto bad_recipients_db_name = config_path_ / "bad_recipients_data";
1015 CDB bad_recipients_db;
1016 if (!bad_recipients_db.open(bad_recipients_db_name)) {
1017 LOG(WARNING) << "can't open bad_recipients_data";
1020 auto temp_fail_db_name = config_path_ / "temp_fail_data";
1021 CDB temp_fail_db;
1022 if (!temp_fail_db.open(temp_fail_db_name)) {
1023 LOG(WARNING) << "can't open temp_fail_data";
1026 std::vector<std::string> bad_recipients;
1027 if (bad_recipients_db.is_open()) {
1028 for (auto fp : forward_path_) {
1029 if (bad_recipients_db.contains_lc(fp.local_part())) {
1030 bad_recipients.push_back(fp);
1031 LOG(WARNING) << "bad recipient " << fp;
1035 std::vector<std::string> temp_failed;
1036 if (temp_fail_db.is_open()) {
1037 for (auto fp : forward_path_) {
1038 if (temp_fail_db.contains_lc(fp.local_part())) {
1039 temp_failed.push_back(fp);
1040 LOG(WARNING) << "temp failed recipient " << fp;
1045 if (prdr_ && forward_path_.size() > 1 &&
1046 (bad_recipients.size() || temp_failed.size())) {
1048 if (forward_path_.size() == bad_recipients.size()) {
1049 out_() << "550 5.1.1 all recipients bad\r\n";
1051 else if (forward_path_.size() == temp_failed.size()) {
1052 out_() << "450 4.1.1 temporary failure for all recipients\r\n";
1054 else {
1055 // this is the mixed situation
1056 out_() << "353 per recipient responses follow:\r\n";
1057 for (auto fp : forward_path_) {
1058 if (bad_recipients_db.is_open() && bad_recipients_db.contains_lc(fp.local_part())) {
1059 out_() << "550 5.1.1 bad recipient " << fp << "\r\n";
1060 LOG(INFO) << "bad recipient " << fp;
1062 else if (temp_fail_db.is_open() && temp_fail_db.contains_lc(fp.local_part())) {
1063 out_() << "450 4.1.1 temporary failure for " << fp << "\r\n";
1064 LOG(INFO) << "temp fail for " << fp;
1066 else {
1067 out_() << "250 2.0.0 success for " << fp << "\r\n";
1068 LOG(INFO) << "success for " << fp;
1072 // after the per recipient status, a final and I think useless message.
1073 if (forward_path_.size() > (bad_recipients.size() + temp_failed.size())) {
1074 out_() << "250 2.0.0 success for some recipients\r\n";
1076 else if (temp_failed.size()) {
1077 out_() << "450 4.1.1 temporary failure for some recipients\r\n";
1079 else {
1080 out_() << "550 5.1.1 some bad recipients\r\n";
1084 else {
1085 if (bad_recipients.size()) {
1086 out_() << "550 5.1.1 bad recipient(s) ";
1087 std::copy(begin(bad_recipients), end(bad_recipients),
1088 std::experimental::make_ostream_joiner(out_(), ", "));
1089 out_() << "\r\n";
1091 else if (temp_failed.size()) {
1092 out_() << "450 4.1.1 temporary failure for ";
1093 std::copy(begin(temp_failed), end(temp_failed),
1094 std::experimental::make_ostream_joiner(out_(), ", "));
1095 out_() << "\r\n";
1097 else {
1098 out_() << "250 2.0.0 " << success_msg << " OK\r\n";
1103 void Session::data_done()
1105 CHECK((state_ == xact_step::data));
1107 if (msg_ && msg_->size_error()) {
1108 data_size_error();
1109 return;
1112 // Check for and act on magic "wait" address.
1114 using namespace boost::xpressive;
1116 sregex const rex = icase("wait-data-") >> (secs_ = +_d);
1117 smatch what;
1119 for (auto fp : forward_path_) {
1120 if (regex_match(fp.local_part(), what, rex) ||
1121 regex_match(fp.local_part(), what, all_rex)) {
1122 auto const str = what[secs_].str();
1123 LOG(INFO) << "waiting at DATA " << str << " seconds";
1124 long value = 0;
1125 std::from_chars(str.data(), str.data() + str.size(), value);
1126 google::FlushLogFiles(google::INFO);
1127 out_() << std::flush;
1128 sleep(value);
1129 LOG(INFO) << "done waiting";
1134 if (!do_deliver_()) {
1135 return;
1138 xfer_response_("DATA");
1140 out_() << std::flush;
1141 reset_();
1144 void Session::data_size_error()
1146 out_().clear(); // clear possible eof from input side
1147 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1148 if (msg_) {
1149 msg_->trash();
1151 LOG(WARNING) << "DATA size error";
1152 reset_();
1155 void Session::data_error()
1157 out_().clear(); // clear possible eof from input side
1158 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush;
1159 if (msg_) {
1160 msg_->trash();
1162 LOG(WARNING) << "DATA error";
1163 reset_();
1166 bool Session::bdat_start(size_t n)
1168 // In practice, this one gets pipelined.
1169 // last_in_group_("BDAT");
1171 switch (state_) {
1172 case xact_step::helo:
1173 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
1174 LOG(WARNING) << "'BDAT' before HELO/EHLO"
1175 << (sock_.has_peername() ? " from " : "") << client_;
1176 return false;
1177 case xact_step::mail:
1178 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
1179 LOG(WARNING) << "'BDAT' before 'MAIL FROM'"
1180 << (sock_.has_peername() ? " from " : "") << client_;
1181 return false;
1182 case xact_step::rcpt:
1183 // See comment in data_start()
1184 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1185 LOG(WARNING) << "no valid recipients"
1186 << (sock_.has_peername() ? " from " : "") << client_;
1187 return false;
1188 case xact_step::data: // first bdat
1189 break;
1190 case xact_step::bdat: return true;
1191 case xact_step::rset:
1192 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1193 LOG(WARNING) << "error state must be cleared with a RSET"
1194 << (sock_.has_peername() ? " from " : "") << client_;
1195 return false;
1198 state_ = xact_step::bdat;
1200 return msg_new();
1203 void Session::bdat_done(size_t n, bool last)
1205 if (state_ != xact_step::bdat) {
1206 bdat_seq_error();
1207 return;
1210 if (!msg_) {
1211 return;
1214 if (msg_->size_error()) {
1215 bdat_size_error();
1216 return;
1219 if (!last) {
1220 out_() << "250 2.0.0 BDAT " << n << " OK\r\n" << std::flush;
1221 LOG(INFO) << "BDAT " << n;
1222 return;
1225 LOG(INFO) << "BDAT " << n << " LAST";
1227 // Check for and act on magic "wait" address.
1229 using namespace boost::xpressive;
1231 sregex const rex = icase("wait-bdat-") >> (secs_ = +_d);
1232 smatch what;
1234 for (auto fp : forward_path_) {
1235 if (regex_match(fp.local_part(), what, rex) ||
1236 regex_match(fp.local_part(), what, all_rex)) {
1237 auto const str = what[secs_].str();
1238 LOG(INFO) << "waiting at BDAT " << str << " seconds";
1239 long value = 0;
1240 std::from_chars(str.data(), str.data() + str.size(), value);
1241 google::FlushLogFiles(google::INFO);
1242 out_() << std::flush;
1243 sleep(value);
1244 LOG(INFO) << "done waiting";
1249 if (!do_deliver_()) {
1250 return;
1253 xfer_response_(fmt::format("BDAT {} LAST", n));
1255 out_() << std::flush;
1256 reset_();
1259 void Session::bdat_size_error()
1261 out_().clear(); // clear possible eof from input side
1262 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1263 if (msg_) {
1264 msg_->trash();
1266 LOG(WARNING) << "BDAT size error";
1267 reset_();
1270 void Session::bdat_seq_error()
1272 out_().clear(); // clear possible eof from input side
1273 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush;
1274 if (msg_) {
1275 msg_->trash();
1277 LOG(WARNING) << "BDAT sequence error";
1278 reset_();
1281 void Session::bdat_io_error()
1283 out_().clear(); // clear possible eof from input side
1284 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush;
1285 if (msg_) {
1286 msg_->trash();
1288 LOG(WARNING) << "BDAT I/O error";
1289 reset_();
1292 void Session::rset()
1294 out_() << "250 2.1.5 RSET OK\r\n";
1295 // No flush RFC-2920 section 3.1, this could be part of a command group.
1296 LOG(INFO) << "RSET";
1297 reset_();
1300 void Session::noop(std::string_view str)
1302 last_in_group_("NOOP");
1303 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush;
1304 LOG(INFO) << "NOOP" << (str.length() ? " " : "") << str;
1307 void Session::vrfy(std::string_view str)
1309 last_in_group_("VRFY");
1310 out_() << "252 2.1.5 try it\r\n" << std::flush;
1311 LOG(INFO) << "VRFY" << (str.length() ? " " : "") << str;
1314 void Session::help(std::string_view str)
1316 if (iequal(str, "help\r\n")) {
1317 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush;
1319 else {
1320 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1321 << std::flush;
1323 LOG(INFO) << "HELP" << (str.length() ? " " : "") << str;
1326 void Session::quit()
1328 // send_.quit();
1329 // last_in_group_("QUIT");
1330 out_() << "221 2.0.0 closing connection\r\n" << std::flush;
1331 LOG(INFO) << "QUIT";
1332 exit_();
1335 void Session::auth()
1337 out_() << "454 4.7.0 authentication failure\r\n" << std::flush;
1338 LOG(INFO) << "AUTH";
1339 bad_host_("auth");
1342 void Session::error(std::string_view log_msg)
1344 out_() << "421 4.3.5 system error: " << log_msg << "\r\n" << std::flush;
1345 LOG(WARNING) << log_msg;
1348 void Session::cmd_unrecognized(std::string_view cmd)
1350 auto const escaped{esc(cmd)};
1351 LOG(WARNING) << "command unrecognized: \"" << escaped << "\"";
1353 if (++n_unrecognized_cmds_ >= Config::max_unrecognized_cmds) {
1354 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1355 << "\" exceeds limit\r\n"
1356 << std::flush;
1357 LOG(WARNING) << n_unrecognized_cmds_
1358 << " unrecognized commands is too many";
1359 exit_();
1362 out_() << "500 5.5.1 command unrecognized: \"" << escaped << "\"\r\n"
1363 << std::flush;
1366 void Session::bare_lf()
1368 // Error code used by Office 365.
1369 out_() << "554 5.6.11 bare LF\r\n" << std::flush;
1370 LOG(WARNING) << "bare LF";
1371 exit_();
1374 void Session::max_out()
1376 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1377 LOG(WARNING) << "message size maxed out";
1378 exit_();
1381 void Session::time_out()
1383 out_() << "421 4.4.2 time-out\r\n" << std::flush;
1384 LOG(WARNING) << "time-out" << (sock_.has_peername() ? " from " : "")
1385 << client_;
1386 exit_();
1389 void Session::starttls()
1391 last_in_group_("STARTTLS");
1392 if (sock_.tls()) {
1393 out_() << "554 5.5.1 TLS already active\r\n" << std::flush;
1394 LOG(WARNING) << "STARTTLS issued with TLS already active";
1396 else if (!extensions_) {
1397 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1398 << std::flush;
1399 LOG(WARNING) << "STARTTLS issued without using EHLO";
1401 else {
1402 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush;
1403 if (sock_.starttls_server(config_path_)) {
1404 reset_();
1405 max_msg_size(Config::max_msg_size_bro);
1406 LOG(INFO) << "STARTTLS " << sock_.tls_info();
1408 else {
1409 LOG(INFO) << "failed STARTTLS";
1414 void Session::exit_()
1416 // sock_.log_totals();
1418 timespec time_used{};
1419 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time_used);
1421 LOG(INFO) << "CPU time " << time_used.tv_sec << "." << std::setw(9)
1422 << std::setfill('0') << time_used.tv_nsec << " seconds";
1424 std::exit(EXIT_SUCCESS);
1427 /////////////////////////////////////////////////////////////////////////////
1429 // All of the verify_* functions send their own error messages back to
1430 // the client on failure, and return false.
1432 bool Session::verify_ip_address_(std::string& error_msg)
1434 auto ip_block_db_name = config_path_ / "ip-block";
1435 CDB ip_block;
1436 if (ip_block.open(ip_block_db_name) &&
1437 ip_block.contains(sock_.them_c_str())) {
1438 error_msg =
1439 fmt::format("IP address {} on static blocklist", sock_.them_c_str());
1440 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1441 return false;
1444 client_fcrdns_.clear();
1446 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1447 (sock_.them_address_literal() == IP6::loopback_literal)) {
1448 LOG(INFO) << "loopback address allowed";
1449 ip_allowed_ = true;
1450 client_fcrdns_.emplace_back("localhost");
1451 client_ = fmt::format("localhost {}", sock_.them_address_literal());
1452 return true;
1455 auto const fcrdns = DNS::fcrdns(res_, sock_.them_c_str());
1456 for (auto const& fcr : fcrdns) {
1457 client_fcrdns_.emplace_back(fcr);
1460 if (IP::is_private(sock_.them_address_literal())) {
1461 LOG(INFO) << "private address allowed";
1462 ip_allowed_ = true;
1463 client_ = sock_.them_address_literal();
1464 return true;
1467 if (!client_fcrdns_.empty()) {
1468 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1469 sock_.them_address_literal());
1470 // check allow list
1471 for (auto const& client_fcrdns : client_fcrdns_) {
1472 if (allow_.contains_lc(client_fcrdns.ascii())) {
1473 LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1474 fcrdns_allowed_ = true;
1475 return true;
1477 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1478 if (tld) {
1479 if (allow_.contains(tld)) {
1480 LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1481 fcrdns_allowed_ = true;
1482 return true;
1486 // check blocklist
1487 for (auto const& client_fcrdns : client_fcrdns_) {
1488 if (block_.contains_lc(client_fcrdns.ascii())) {
1489 error_msg =
1490 fmt::format("FCrDNS {} on static blocklist", client_fcrdns.ascii());
1491 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1492 return false;
1495 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1496 if (tld) {
1497 if (block_.contains(tld)) {
1498 error_msg = fmt::format(
1499 "FCrDNS registered domain {} on static blocklist", tld);
1500 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1501 return false;
1506 else {
1507 client_ = fmt::format("{}", sock_.them_address_literal());
1510 if (IP4::is_address(sock_.them_c_str())) {
1512 auto const reversed{IP4::reverse(sock_.them_c_str())};
1515 // Check with allow list.
1516 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1517 random_device_);
1519 for (auto wl : Config::wls) {
1520 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1521 if (q.has_record()) {
1522 using namespace boost::xpressive;
1524 auto const as = q.get_strings()[0];
1525 LOG(INFO) << "on allow list " << wl << " as " << as;
1527 mark_tag x_(1);
1528 mark_tag y_(2);
1529 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1530 smatch what;
1532 if (regex_match(as, what, rex)) {
1533 auto const x = what[x_].str();
1534 auto const y = what[y_].str();
1536 int value = 0;
1537 std::from_chars(y.data(), y.data() + y.size(), value);
1538 if (value > 0) {
1539 ip_allowed_ = true;
1540 LOG(INFO) << "allowed";
1544 LOG(INFO) << "Any A record skips check on block list";
1545 return true;
1550 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1551 std::shuffle(std::begin(Config::bls), std::end(Config::bls),
1552 random_device_);
1554 for (auto bl : Config::bls) {
1555 DNS::Query q(res_, DNS::RR_type::A, reversed + bl);
1556 if (q.has_record()) {
1557 const auto a_strings = q.get_strings();
1558 for (auto const& as : a_strings) {
1559 LOG(INFO) << bl << " returned " << as;
1561 for (auto const& as : a_strings) {
1562 if (as == "127.0.0.1") {
1563 LOG(INFO) << "Should never get 127.0.0.1, from " << bl;
1565 else if (as == "127.0.0.10" || as == "127.0.0.11") {
1566 LOG(INFO) << "PBL listed, ignoring " << bl;
1568 else if (as == "127.255.255.252") {
1569 LOG(INFO) << "Typing error in DNSBL name " << bl;
1571 else if (as == "127.255.255.254") {
1572 LOG(INFO) << "Anonymous query through public resolver " << bl;
1574 else if (as == "127.255.255.255") {
1575 LOG(INFO) << "Excessive number of queries " << bl;
1577 else {
1578 error_msg = fmt::format("IP address {} blocked: {} returned {}",
1579 sock_.them_c_str(), bl, as);
1580 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1581 return false;
1586 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1589 LOG(INFO) << "IP address okay";
1590 return true;
1593 bool domain_blocked(DNS::Resolver& res, Domain const& identity)
1595 if (identity.is_address_literal()) {
1596 // don't "domain block" address literals
1597 return false;
1599 if (!identity.ascii().empty()) {
1600 Domain lookup{fmt::format("{}.dbl.spamhaus.org", identity.ascii())};
1601 DNS::Query q(res, DNS::RR_type::A, lookup.ascii());
1602 if (q.has_record()) {
1603 const auto a_strings = q.get_strings();
1604 for (auto const& as : a_strings) {
1605 if (istarts_with(as, "127.0.1.")) {
1606 LOG(INFO) << "Domain " << identity << " blocked by spamhaus, " << as;
1607 return true;
1612 return false;
1615 // check the identity from HELO/EHLO
1616 bool Session::verify_client_(Domain const& client_identity,
1617 std::string& error_msg)
1619 if (!client_fcrdns_.empty()) {
1620 if (auto id = std::find(begin(client_fcrdns_), end(client_fcrdns_),
1621 client_identity);
1622 id != end(client_fcrdns_)) {
1623 // If the HELO ident is one of the FCrDNS names...
1624 if (id != begin(client_fcrdns_)) {
1625 // ...then rotate that one to the front of the list
1626 std::rotate(begin(client_fcrdns_), id, id + 1);
1628 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1629 sock_.them_address_literal());
1630 return true;
1632 LOG(INFO) << "claimed identity " << client_identity
1633 << " does NOT match any FCrDNS: ";
1634 for (auto const& client_fcrdns : client_fcrdns_) {
1635 LOG(INFO) << " " << client_fcrdns;
1639 // Bogus clients claim to be us or some local host.
1640 if (sock_.has_peername() && ((client_identity == server_identity_) ||
1641 (client_identity == "localhost") ||
1642 (client_identity == "localhost.localdomain"))) {
1644 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1645 (sock_.them_address_literal() == IP6::loopback_literal)) {
1646 return true;
1649 // Give 'em a pass.
1650 if (ip_allowed_) {
1651 LOG(INFO) << "allow-listed IP address can claim to be "
1652 << client_identity;
1653 return true;
1656 // Ease up in test mode.
1657 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1658 return true;
1661 error_msg = fmt::format("liar, claimed to be {}", client_identity.ascii());
1662 out_() << "550 5.7.1 liar\r\n" << std::flush;
1663 return false;
1666 std::vector<std::string> labels;
1667 boost::algorithm::split(labels, client_identity.ascii(),
1668 boost::algorithm::is_any_of("."));
1669 if (labels.size() < 2) {
1670 error_msg =
1671 fmt::format("claimed bogus identity {}", client_identity.ascii());
1672 out_() << "550 4.7.1 bogus identity\r\n" << std::flush;
1673 return false;
1674 // // Sometimes we may want to look at mail from non conforming
1675 // // sending systems.
1676 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1677 // << client_ << " claiming " << client_identity;
1678 // return true;
1681 if (lookup_domain(block_, client_identity)) {
1682 error_msg =
1683 fmt::format("claimed blocked identity {}", client_identity.ascii());
1684 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1685 return false;
1688 auto const tld{tld_db_.get_registered_domain(client_identity.ascii())};
1689 if (!tld) {
1690 // Sometimes we may want to look at mail from misconfigured
1691 // sending systems.
1692 // LOG(WARNING) << "claimed identity has no registered domain";
1693 // return true;
1695 else if (block_.contains(tld)) {
1696 error_msg =
1697 fmt::format("claimed identity has blocked registered domain {}", tld);
1698 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush;
1699 return false;
1702 if (domain_blocked(res_, client_identity) ||
1703 domain_blocked(res_, Domain(tld))) {
1704 error_msg =
1705 fmt::format("claimed identity {} blocked", client_identity.ascii());
1706 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1707 return false;
1710 DNS::Query q(res_, DNS::RR_type::A, client_identity.ascii());
1711 if (!q.has_record()) {
1712 LOG(WARNING) << "claimed identity " << client_identity.ascii()
1713 << " not DNS resolvable";
1716 // not otherwise objectionable
1717 return true;
1720 // check sender from RFC5321 MAIL FROM:
1721 bool Session::verify_sender_(Mailbox const& sender, std::string& error_msg)
1723 do_spf_check_(sender);
1725 std::string const sender_str{sender};
1727 if (sender.empty()) {
1728 // MAIL FROM:<>
1729 // is used to send bounce messages.
1730 return true;
1733 if (domain_blocked(res_, sender.domain())) {
1734 error_msg = fmt::format("{} sender domain blocked", sender_str);
1735 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1736 return false;
1739 auto bad_senders_db_name = config_path_ / "bad_senders";
1740 CDB bad_senders;
1741 if (bad_senders.open(bad_senders_db_name) &&
1742 bad_senders.contains(sender_str)) {
1743 error_msg = fmt::format("{} bad sender", sender_str);
1744 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1745 return false;
1748 // We don't accept mail /from/ a domain we are expecting to accept
1749 // mail for on an external network connection.
1751 // if (sock_.them_address_literal() != sock_.us_address_literal()) {
1752 // if ((accept_domains_.is_open() &&
1753 // (accept_domains_.contains(sender.domain().ascii()) ||
1754 // accept_domains_.contains(sender.domain().utf8()))) ||
1755 // (sender.domain() == server_identity_)) {
1757 // // Ease up in test mode.
1758 // if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1759 // return true;
1760 // }
1761 // out_() << "550 5.7.1 liar\r\n" << std::flush;
1762 // error_msg = fmt::format("liar, claimed to be {}",
1763 // sender.domain().utf8()); return false;
1764 // }
1765 // }
1767 if (sender.domain().is_address_literal()) {
1768 if (sender.domain() != sock_.them_address_literal()) {
1769 LOG(WARNING) << "sender domain " << sender.domain() << " does not match "
1770 << sock_.them_address_literal();
1772 return true;
1775 if (!verify_sender_domain_(sender.domain(), error_msg)) {
1776 return false;
1779 return true;
1782 // this sender is the RFC5321 MAIL FROM: domain part
1783 bool Session::verify_sender_domain_(Domain const& sender,
1784 std::string& error_msg)
1786 if (sender.empty()) {
1787 // MAIL FROM:<>
1788 // is used to send bounce messages.
1789 return true;
1792 // Break sender domain into labels:
1794 std::vector<std::string> labels;
1795 boost::algorithm::split(labels, sender.ascii(),
1796 boost::algorithm::is_any_of("."));
1798 if (labels.size() < 2) { // This is not a valid domain.
1799 error_msg = fmt::format("{} invalid syntax", sender.ascii());
1800 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1801 return false;
1804 if (lookup_domain(block_, sender)) {
1805 error_msg = fmt::format("SPF sender domain ({}) is blocked",
1806 spf_sender_domain_.ascii());
1807 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1808 return false;
1811 if (spf_result_ == SPF::Result::PASS) {
1812 if (allow_.contains(spf_sender_domain_.ascii())) {
1813 LOG(INFO) << "sender " << spf_sender_domain_.ascii() << " allowed";
1814 return true;
1817 auto const reg_dom{
1818 tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
1819 if (reg_dom) {
1820 if (allow_.contains(reg_dom)) {
1821 LOG(INFO) << "sender registered domain \"" << reg_dom << "\" allowed";
1822 return true;
1827 LOG(INFO) << "sender \"" << sender << "\" not disallowed";
1828 return true;
1831 void Session::do_spf_check_(Mailbox const& sender)
1833 if (!sock_.has_peername()) {
1834 auto const ip_addr = "127.0.0.1"; // use localhost for local socket
1835 spf_received_ = fmt::format(
1836 "Received-SPF: pass ({}: allow-listed) client-ip={}; "
1837 "envelope-from={}; helo={};",
1838 server_id_(), ip_addr, sender.as_string(), client_identity_.ascii());
1839 spf_sender_domain_ = "localhost";
1840 return;
1843 auto const spf_srv = SPF::Server{server_id_().c_str()};
1844 auto spf_request = SPF::Request{spf_srv};
1846 if (IP4::is_address(sock_.them_c_str())) {
1847 spf_request.set_ipv4_str(sock_.them_c_str());
1849 else if (IP6::is_address(sock_.them_c_str())) {
1850 spf_request.set_ipv6_str(sock_.them_c_str());
1852 else {
1853 LOG(FATAL) << "bogus address " << sock_.them_address_literal() << ", "
1854 << sock_.them_c_str();
1857 auto const from{static_cast<std::string>(sender)};
1859 spf_request.set_env_from(from.c_str());
1860 spf_request.set_helo_dom(client_identity_.ascii().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 LOG(INFO) << "spf_received_ == " << spf_received_;
1869 if (spf_result_ == SPF::Result::FAIL) {
1870 LOG(INFO) << "FAIL " << spf_res.header_comment();
1872 else if (spf_result_ == SPF::Result::NEUTRAL) {
1873 LOG(INFO) << "NEUTRAL " << spf_res.header_comment();
1875 else if (spf_result_ == SPF::Result::PASS) {
1876 LOG(INFO) << "PASS " << spf_res.header_comment();
1878 else {
1879 LOG(INFO) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
1883 bool Session::verify_from_params_(parameters_t const& parameters)
1885 // Take a look at the optional parameters:
1886 for (auto const& [name, value] : parameters) {
1887 if (iequal(name, "BODY")) {
1888 if (iequal(value, "8BITMIME")) {
1889 // everything is cool, this is our default...
1891 else if (iequal(value, "7BIT")) {
1892 // nothing to see here, move along...
1894 else if (iequal(value, "BINARYMIME")) {
1895 LOG(INFO) << "using BINARYMIME";
1896 binarymime_ = true;
1898 else {
1899 LOG(WARNING) << "unrecognized BODY type \"" << value << "\" requested";
1902 else if (iequal(name, "SMTPUTF8")) {
1903 if (!value.empty()) {
1904 LOG(WARNING) << "SMTPUTF8 parameter has a value: " << value;
1906 smtputf8_ = true;
1908 else if (iequal(name, "PRDR")) {
1909 LOG(INFO) << "using PRDR";
1910 prdr_ = true;
1913 else if (iequal(name, "SIZE")) {
1914 if (value.empty()) {
1915 LOG(WARNING) << "SIZE parameter has no value.";
1917 else {
1918 try {
1919 auto const sz = stoull(value);
1920 if (sz > max_msg_size()) {
1921 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1922 LOG(WARNING) << "SIZE parameter too large: " << sz;
1923 return false;
1926 catch (std::invalid_argument const& e) {
1927 LOG(WARNING) << "SIZE parameter has invalid value: " << value;
1929 catch (std::out_of_range const& e) {
1930 LOG(WARNING) << "SIZE parameter has out-of-range value: " << value;
1932 // I guess we just ignore bad size parameters.
1935 else if (iequal(name, "REQUIRETLS")) {
1936 if (!sock_.tls()) {
1937 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush;
1938 LOG(WARNING) << "REQUIRETLS needed";
1939 return false;
1942 else {
1943 LOG(WARNING) << "unrecognized 'MAIL FROM' parameter " << name << "="
1944 << value;
1948 return true;
1951 bool Session::verify_rcpt_params_(parameters_t const& parameters)
1953 // Take a look at the optional parameters:
1954 for (auto const& [name, value] : parameters) {
1955 if (iequal(name, "RRVS")) {
1956 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
1957 LOG(INFO) << name << "=" << value;
1959 else {
1960 LOG(WARNING) << "unrecognized 'RCPT TO' parameter " << name << "="
1961 << value;
1965 return true;
1968 // check recipient from RFC5321 RCPT TO:
1969 bool Session::verify_recipient_(Mailbox const& recipient)
1971 if ((recipient.local_part() == "Postmaster") && (recipient.domain() == "")) {
1972 LOG(INFO) << "magic Postmaster address";
1973 return true;
1976 auto const accepted_domain{[this, &recipient] {
1977 if (recipient.domain().is_address_literal()) {
1978 if (recipient.domain() != sock_.us_address_literal()) {
1979 LOG(WARNING) << "recipient.domain address " << recipient.domain()
1980 << " does not match ours " << sock_.us_address_literal();
1982 return false;
1985 return true;
1988 // Domains we accept mail for.
1989 if (accept_domains_.is_open()) {
1990 if (accept_domains_.contains(recipient.domain().ascii()) ||
1991 accept_domains_.contains(recipient.domain().utf8())) {
1992 return true;
1995 else {
1996 // If we have no list of domains to accept, at least take our own.
1997 if (recipient.domain() == server_id_()) {
1998 return true;
2002 return false;
2003 }()};
2005 if (!accepted_domain) {
2006 out_() << "550 5.7.1 relay access denied\r\n" << std::flush;
2007 LOG(WARNING) << "relay access denied for domain " << recipient.domain();
2008 return false;
2011 if (recipient.local_part() == "gene" && client_fcrdns_.size() &&
2012 client_fcrdns_[0].ascii().ends_with("outlook.com")) {
2013 // Getting Spam'ed by MS
2014 if (reverse_path_.empty() || (reverse_path_.length() > 40) ||
2015 reverse_path_.domain().ascii().ends_with(".onmicrosoft.com")) {
2016 std::string error_msg = fmt::format("rejecting spammy message from {}",
2017 client_fcrdns_[0].ascii());
2018 LOG(WARNING) << error_msg;
2019 out_() << "550 5.7.0 " << error_msg << "\r\n" << std::flush;
2020 return false;
2024 // Check for local addresses we reject.
2026 auto bad_recipients_db_name = config_path_ / "bad_recipients";
2027 CDB bad_recipients_db;
2028 if (bad_recipients_db.open(bad_recipients_db_name) &&
2029 bad_recipients_db.contains_lc(recipient.local_part())) {
2030 out_() << "550 5.1.1 bad recipient " << recipient << "\r\n" << std::flush;
2031 LOG(WARNING) << "bad recipient " << recipient;
2032 return false;
2037 auto fail_db_name = config_path_ / "fail_554";
2038 if (fs::exists(fail_db_name)) {
2039 CDB fail_db;
2040 if (fail_db.open(fail_db_name) &&
2041 fail_db.contains(recipient.local_part())) {
2042 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2043 << "\r\n"
2044 << std::flush;
2045 LOG(WARNING) << "fail_554 recipient " << recipient;
2046 return false;
2051 return true;