needed for fmt::join
[ghsmtp.git] / Session.cpp
blob4a715d1d61960ba247d6256df290f0cf972697ff
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>
24 #include <fmt/ranges.h>
26 #include <boost/algorithm/string/classification.hpp>
27 #include <boost/algorithm/string/split.hpp>
29 #include <boost/xpressive/xpressive.hpp>
31 #include <syslog.h>
33 #include <gflags/gflags.h>
35 using namespace std::string_literals;
37 namespace Config {
39 char const* wls[]{
40 "list.dnswl.org",
45 <https://www.dnswl.org/?page_id=15#query>
47 Return codes
49 The return codes are structured as 127.0.x.y, with “x” indicating the category
50 of an entry and “y” indicating how trustworthy an entry has been judged.
52 Categories (127.0.X.y):
54 2 – Financial services
55 3 – Email Service Providers
56 4 – Organisations (both for-profit [ie companies] and non-profit)
57 5 – Service/network providers
58 6 – Personal/private servers
59 7 – Travel/leisure industry
60 8 – Public sector/governments
61 9 – Media and Tech companies
62 10 – some special cases
63 11 – Education, academic
64 12 – Healthcare
65 13 – Manufacturing/Industrial
66 14 – Retail/Wholesale/Services
67 15 – Email Marketing Providers
68 20 – Added through Self Service without specific category
70 Trustworthiness / Score (127.0.x.Y):
72 0 = none – only avoid outright blocking (eg large ESP mailservers, -0.1)
73 1 = low – reduce chance of false positives (-1.0)
74 2 = medium – make sure to avoid false positives but allow override for clear
75 cases (-10.0) 3 = high – avoid override (-100.0).
77 The scores in parantheses are typical SpamAssassin scores.
79 Special return code 127.0.0.255
81 In cases where your nameserver issues more than 100’000 queries / 24 hours, you
82 may be blocked from further queries. The return code “127.0.0.255” indicates
83 this situation.
87 char const* bls[]{
88 "b.barracudacentral.org",
89 "sbl-xbl.spamhaus.org",
92 /*** Last octet from A record returned by blocklists ***
94 <https://www.spamhaus.org/faq/section/DNSBL%20Usage#200>
96 Spamhaus uses this general convention for return codes:
98 Return Code Description
99 127.0.0.0/24 Spamhaus IP Blocklists
100 127.0.1.0/24 Spamhaus Domain Blocklists
101 127.0.2.0/24 Spamhaus Zero Reputation Domains list
102 127.255.255.0/24 ERRORS (not implying a "listed" response)
104 Currently used return codes for Spamhaus public IP zones:
106 Return Code Zone Description
107 127.0.0.2 SBL Spamhaus SBL Data
108 127.0.0.3 SBL Spamhaus SBL CSS Data
109 127.0.0.4 XBL CBL Data
110 127.0.0.9 SBL Spamhaus DROP/EDROP Data
111 (in addition to 127.0.0.2, since 01-Jun-2016)
112 127.0.0.10 PBL ISP Maintained
113 127.0.0.11 PBL Spamhaus Maintained
115 127.0.0.5-7 are allocated to XBL for possible future use;
116 127.0.0.8 is allocated to SBL for possible future use.
118 From <https://www.spamhaus.org/faq/section/Spamhaus%20DBL#291>
120 Return Codes Data Source
121 127.0.1.2 spam domain
122 127.0.1.4 phish domain
123 127.0.1.5 malware domain
124 127.0.1.6 botnet C&C domain
125 127.0.1.102 abused legit spam
126 127.0.1.103 abused spammed redirector domain
127 127.0.1.104 abused legit phish
128 127.0.1.105 abused legit malware
129 127.0.1.106 abused legit botnet C&C
130 127.0.1.255 IP queries prohibited!
132 The following special codes indicate an error condition and should not
133 be taken to imply that the queried domain is "listed":
135 Return Code Description
136 127.255.255.252 Typing error in DNSBL name
137 127.255.255.254 Anonymous query through public resolver
138 127.255.255.255 Excessive number of queries
141 From <http://www.surbl.org/lists#multi>
143 last octet indicates which lists it belongs to. The bit positions in
144 that last octet for membership in the different lists are:
146 8 = listed on PH
147 16 = listed on MW
148 64 = listed on ABUSE
149 128 = listed on CR
154 char const* uribls[]{
155 "dbl.spamhaus.org",
156 "multi.uribl.com",
160 constexpr auto greeting_wait = std::chrono::seconds{6};
161 constexpr int max_recipients_per_message = 100;
162 constexpr int max_unrecognized_cmds = 20;
164 // Read timeout value gleaned from RFC-1123 section 5.3.2 and RFC-5321
165 // section 4.5.3.2.7.
166 constexpr auto read_timeout = std::chrono::minutes{5};
167 constexpr auto write_timeout = std::chrono::seconds{30};
168 } // namespace Config
170 DEFINE_bool(immortal, false, "don't set process timout");
172 DEFINE_uint64(max_read, 0, "max data to read");
173 DEFINE_uint64(max_write, 0, "max data to write");
175 DEFINE_bool(test_mode, false, "ease up on some checks");
177 DEFINE_bool(use_binarymime, true, "support BINARYMIME extension, RFC 3030");
178 DEFINE_bool(use_chunking, true, "support CHUNKING extension, RFC 3030");
179 DEFINE_bool(use_pipelining, true, "support PIPELINING extension, RFC 2920");
180 DEFINE_bool(use_rrvs, true, "support RRVS extension, RFC 7293");
181 DEFINE_bool(use_prdr, true, "support PRDR extension");
182 DEFINE_bool(use_smtputf8, true, "support SMTPUTF8 extension, RFC 6531");
184 boost::xpressive::mark_tag secs_(1);
185 boost::xpressive::sregex const all_rex = boost::xpressive::icase("wait-all-") >>
186 (secs_ = +boost::xpressive::_d);
188 Session::Session(fs::path config_path,
189 std::function<void(void)> read_hook,
190 int fd_in,
191 int fd_out)
192 : config_path_(config_path)
193 , res_(config_path)
194 , sock_(fd_in, fd_out, read_hook, Config::read_timeout, Config::write_timeout)
195 //, send_(config_path, "smtp")
196 //, srs_(config_path)
198 auto accept_db_name = config_path_ / "accept_domains";
199 auto allow_db_name = config_path_ / "allow";
200 auto block_db_name = config_path_ / "block";
201 // auto forward_db_name = config_path_ / "forward";
203 accept_domains_.open(accept_db_name);
204 allow_.open(allow_db_name);
205 block_.open(block_db_name);
206 // forward_.open(forward_db_name);
208 if (sock_.has_peername() && !IP::is_private(sock_.us_c_str())) {
209 auto fcrdns = DNS::fcrdns(res_, sock_.us_c_str());
210 for (auto const& fcr : fcrdns) {
211 server_fcrdns_.emplace_back(fcr);
215 server_identity_ = Domain{[this] {
216 auto const id_from_env{getenv("GHSMTP_SERVER_ID")};
217 if (id_from_env)
218 return std::string{id_from_env};
220 auto const hostname{osutil::get_hostname()};
221 if (hostname.find('.') != std::string::npos)
222 return hostname;
224 if (!server_fcrdns_.empty()) {
225 // first result should be shortest
226 return server_fcrdns_.front().ascii();
229 auto const us_c_str = sock_.us_c_str();
230 if (us_c_str && !IP::is_private(us_c_str)) {
231 return IP::to_address_literal(us_c_str);
234 LOG(FATAL) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
235 return ""s;
236 }()};
238 // send_.set_sender(server_identity_);
240 max_msg_size(Config::max_msg_size_initial);
243 void Session::max_msg_size(size_t max)
245 max_msg_size_ = max; // number to advertise via RFC 1870
247 if (FLAGS_max_read) {
248 sock_.set_max_read(FLAGS_max_read);
250 else {
251 auto const overhead = std::max(max / 10, size_t(2048));
252 sock_.set_max_read(max + overhead);
256 void Session::bad_host_(char const* msg) const
258 if (sock_.has_peername()) {
259 // On my systems, this pattern triggers a fail2ban rule that
260 // blocks connections from this IP address on port 25 for a few
261 // days. See <https://www.fail2ban.org/> for more info.
262 syslog(LOG_MAIL | LOG_WARNING, "bad host [%s] %s", sock_.them_c_str(), msg);
264 std::exit(EXIT_SUCCESS);
267 void Session::reset_()
269 // RSET does not force another EHLO/HELO, the one piece of per
270 // transaction data saved is client_identity_:
272 // client_identity_.clear(); <-- not cleared!
274 reverse_path_.clear();
275 forward_path_.clear();
276 spf_received_.clear();
277 // fwd_path_.clear();
278 // fwd_from_.clear();
279 // rep_info_.clear();
281 binarymime_ = false;
282 smtputf8_ = false;
283 prdr_ = false;
285 if (msg_) {
286 msg_.reset();
289 max_msg_size(max_msg_size());
291 state_ = xact_step::mail;
292 // send_.rset();
295 // Return codes from connection establishment are 220 or 554, according
296 // to RFC 5321. That's it.
298 void Session::greeting()
300 CHECK(state_ == xact_step::helo);
302 if (sock_.has_peername()) {
303 close(2); // if we're a networked program, never send to stderr
305 std::string error_msg;
306 if (!verify_ip_address_(error_msg)) {
307 LOG(INFO) << error_msg;
308 bad_host_(error_msg.c_str());
311 /******************************************************************
312 <https://tools.ietf.org/html/rfc5321#section-4.3.1> says:
314 4.3. Sequencing of Commands and Replies
316 4.3.1. Sequencing Overview
318 The communication between the sender and receiver is an alternating
319 dialogue, controlled by the sender. As such, the sender issues a
320 command and the receiver responds with a reply. Unless other
321 arrangements are negotiated through service extensions, the sender
322 MUST wait for this response before sending further commands. One
323 important reply is the connection greeting. Normally, a receiver
324 will send a 220 "Service ready" reply when the connection is
325 completed. The sender SHOULD wait for this greeting message before
326 sending any commands.
328 So which is it?
330 “…the receiver responds with a reply.”
331 “…the sender MUST wait for this response…”
332 “One important reply is the connection greeting.”
333 “The sender SHOULD wait for this greeting…”
335 So is it MUST or SHOULD? I enforce MUST.
336 *******************************************************************/
338 // Wait a bit of time for pre-greeting traffic.
339 if (!(ip_allowed_ || fcrdns_allowed_)) {
340 if (sock_.input_ready(Config::greeting_wait)) {
341 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush;
342 LOG(INFO) << "input before any greeting from " << client_;
343 bad_host_("input before any greeting");
345 // Give a half greeting and wait again.
346 out_() << "220-" << server_id_() << " ESMTP slowstart - ghsmtp\r\n"
347 << std::flush;
348 if (sock_.input_ready(Config::greeting_wait)) {
349 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush;
350 LOG(INFO) << "input before full greeting from " << client_;
351 bad_host_("input before full greeting");
354 <https://www.rfc-editor.org/rfc/rfc5321#section-4.2>
356 An SMTP client MUST determine its actions only by the reply code, not
357 by the text (except for the "change of address" 251 and 551 and, if
358 necessary, 220, 221, and 421 replies); in the general case, any text,
359 including no text at all (although senders SHOULD NOT send bare
360 codes), MUST be acceptable. The space (blank) following the reply
361 code is considered part of the text. Whenever possible, a receiver-
362 SMTP SHOULD test the first digit (severity indication) of the reply
363 code.
365 Except the following chokes a lot of senders:
367 out_() << "220\r\n" << std::flush;
370 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush;
372 else {
373 out_() << "220 " << server_id_() << " ESMTP faststart - ghsmtp\r\n"
374 << std::flush;
377 else {
378 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush;
381 LOG(INFO) << "connect from " << client_;
383 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
384 alarm(2 * 60); // initial alarm
388 void Session::flush() { out_() << std::flush; }
390 void Session::last_in_group_(std::string_view verb)
392 if (sock_.input_ready(std::chrono::seconds(0))) {
393 LOG(WARNING) << "pipelining error; input ready processing " << verb;
397 void Session::check_for_pipeline_error_(std::string_view verb)
399 if (!(FLAGS_use_pipelining && extensions_)) {
400 if (sock_.input_ready(std::chrono::seconds(0))) {
401 LOG(WARNING) << "pipelining error; input ready processing " << verb;
406 void Session::lo_(char const* verb, std::string_view client_identity_str)
408 Domain client_identity{client_identity_str};
410 last_in_group_(verb);
411 reset_();
413 if (client_identity_ != client_identity) {
414 client_identity_ = client_identity;
416 std::string error_msg;
417 if (!verify_client_(client_identity_, error_msg)) {
418 LOG(INFO) << "client identity blocked: " << error_msg;
419 bad_host_(error_msg.c_str());
423 if (*verb == 'H') {
424 extensions_ = false;
425 out_() << "250 " << server_id_() << "\r\n";
428 if (*verb == 'E') {
429 extensions_ = true;
431 if (sock_.has_peername()) {
432 out_() << "250-" << server_id_() << " at your service, " << client_
433 << "\r\n";
435 else {
436 out_() << "250-" << server_id_() << "\r\n";
439 // LIMITS SMTP Service Extension,
440 // <https://www.rfc-editor.org/rfc/rfc9422.html>
441 out_() << "250-LIMITS RCPTMAX=" << Config::max_recipients_per_message
442 << "\r\n";
443 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
444 out_() << "250-8BITMIME\r\n"; // RFC 6152
446 if (FLAGS_use_rrvs) {
447 out_() << "250-RRVS\r\n"; // RFC 7293
450 if (FLAGS_use_prdr) {
451 out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
454 if (sock_.tls()) {
455 // Check sasl sources for auth types.
456 // out_() << "250-AUTH PLAIN\r\n";
457 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
459 else {
460 // If we're not already TLS, offer TLS
461 out_() << "250-STARTTLS\r\n"; // RFC 3207
464 out_() << "250-ENHANCEDSTATUSCODES\r\n"; // RFC 2034
466 if (FLAGS_use_pipelining) {
467 out_() << "250-PIPELINING\r\n"; // RFC 2920
470 if (FLAGS_use_binarymime) {
471 out_() << "250-BINARYMIME\r\n"; // RFC 3030
474 if (FLAGS_use_chunking) {
475 out_() << "250-CHUNKING\r\n"; // RFC 3030
478 if (FLAGS_use_smtputf8) {
479 out_() << "250-SMTPUTF8\r\n"; // RFC 6531
482 out_() << "250 HELP\r\n";
485 out_() << std::flush;
487 if (sock_.has_peername()) {
488 if (std::find(begin(client_fcrdns_), end(client_fcrdns_),
489 client_identity_) != end(client_fcrdns_)) {
490 LOG(INFO) << verb << " " << client_identity_ << " from "
491 << sock_.them_address_literal();
493 else {
494 LOG(INFO) << verb << " " << client_identity_ << " from " << client_;
497 else {
498 LOG(INFO) << verb << " " << client_identity_;
502 void Session::mail_from(Mailbox&& reverse_path, parameters_t const& parameters)
504 check_for_pipeline_error_("MAIL FROM");
506 switch (state_) {
507 case xact_step::helo:
508 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
509 LOG(WARNING) << "'MAIL FROM' before HELO/EHLO"
510 << (sock_.has_peername() ? " from " : "") << client_;
511 return;
512 case xact_step::mail: break;
513 case xact_step::rcpt:
514 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
515 LOG(WARNING) << "nested MAIL command"
516 << (sock_.has_peername() ? " from " : "") << client_;
517 return;
518 case xact_step::data:
519 case xact_step::bdat:
520 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush;
521 LOG(WARNING) << "nested MAIL command"
522 << (sock_.has_peername() ? " from " : "") << client_;
523 return;
524 case xact_step::rset:
525 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
526 LOG(WARNING) << "error state must be cleared with a RSET"
527 << (sock_.has_peername() ? " from " : "") << client_;
528 return;
531 if (!verify_from_params_(parameters)) {
532 return;
535 if (!smtputf8_ && !is_ascii(reverse_path.local_part())) {
536 LOG(WARNING) << "non ascii reverse_path \"" << reverse_path
537 << "\" without SMTPUTF8 paramater";
540 std::string error_msg;
541 if (!verify_sender_(reverse_path, error_msg)) {
542 LOG(INFO) << "verify sender failed: " << error_msg;
543 bad_host_(error_msg.c_str());
546 reverse_path_ = std::move(reverse_path);
547 // fwd_path_.clear();
548 // fwd_from_.clear();
549 forward_path_.clear();
550 out_() << "250 2.1.0 MAIL FROM OK\r\n";
551 // No flush RFC-2920 section 3.1, this could be part of a command group.
553 fmt::memory_buffer params;
554 for (auto const& [name, value] : parameters) {
555 fmt::format_to(std::back_inserter(params), " {}", name);
556 if (!value.empty()) {
557 fmt::format_to(std::back_inserter(params), "={}", value);
560 LOG(INFO) << "MAIL FROM:<" << reverse_path_ << ">" << fmt::to_string(params);
562 state_ = xact_step::rcpt;
565 void Session::rcpt_to(Mailbox&& forward_path, parameters_t const& parameters)
567 check_for_pipeline_error_("RCPT TO");
569 switch (state_) {
570 case xact_step::helo:
571 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
572 LOG(WARNING) << "'RCPT TO' before HELO/EHLO"
573 << (sock_.has_peername() ? " from " : "") << client_;
574 return;
575 case xact_step::mail:
576 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
577 LOG(WARNING) << "'RCPT TO' before 'MAIL FROM'"
578 << (sock_.has_peername() ? " from " : "") << client_;
579 return;
580 case xact_step::rcpt:
581 case xact_step::data: break;
582 case xact_step::bdat:
583 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
584 LOG(WARNING) << "'RCPT TO' during BDAT transfer"
585 << (sock_.has_peername() ? " from " : "") << client_;
586 return;
587 case xact_step::rset:
588 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
589 LOG(WARNING) << "error state must be cleared with a RSET"
590 << (sock_.has_peername() ? " from " : "") << client_;
591 return;
594 if (!verify_rcpt_params_(parameters))
595 return;
597 if (!verify_recipient_(forward_path))
598 return;
600 if (!smtputf8_ && !is_ascii(forward_path.local_part())) {
601 LOG(WARNING) << "non ascii forward_path \"" << forward_path
602 << "\" without SMTPUTF8 paramater";
605 if (forward_path_.size() >= Config::max_recipients_per_message) {
606 out_() << "452 4.5.3 too many recipients\r\n" << std::flush;
607 LOG(WARNING) << "too many recipients <" << forward_path << ">";
608 return;
610 // no check for dups, postfix doesn't
611 forward_path_.emplace_back(std::move(forward_path));
613 Mailbox const& rcpt_to_mbx = forward_path_.back();
615 LOG(INFO) << "RCPT TO:<" << rcpt_to_mbx << ">";
617 // No flush RFC-2920 section 3.1, this could be part of a command group.
618 out_() << "250 2.1.5 RCPT TO OK\r\n";
620 state_ = xact_step::data;
623 // The headers Return-Path:, Received-SPF:, and Received: are returned
624 // as a string.
626 std::string Session::added_headers_(MessageStore const& msg)
628 auto const protocol{[this]() {
629 if (sock_.tls() && !extensions_) {
630 LOG(WARNING) << "TLS active without extensions";
632 // <https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#mail-parameters-5>
633 if (smtputf8_)
634 return sock_.tls() ? "UTF8SMTPS" : "UTF8SMTP";
635 else if (sock_.tls())
636 return "ESMTPS";
637 else if (extensions_)
638 return "ESMTP";
639 else
640 return "SMTP";
641 }()};
643 fmt::memory_buffer headers;
645 // Return-Path:
646 fmt::format_to(std::back_inserter(headers), "Return-Path: <{}>\r\n",
647 reverse_path_.as_string());
649 // Received-SPF:
650 if (!spf_received_.empty()) {
651 fmt::format_to(std::back_inserter(headers), "{}\r\n", spf_received_);
654 // Received:
655 // <https://tools.ietf.org/html/rfc5321#section-4.4>
656 fmt::format_to(std::back_inserter(headers), "Received: from {}",
657 client_identity_.utf8());
658 if (sock_.has_peername()) {
659 fmt::format_to(std::back_inserter(headers), " ({})", client_);
661 fmt::format_to(std::back_inserter(headers), "\r\n\tby {} with {} id {}",
662 server_identity_.utf8(), protocol, msg.id().as_string_view());
663 if (forward_path_.size()) {
664 fmt::format_to(std::back_inserter(headers), "\r\n\tfor <{}>",
665 forward_path_[0].as_string());
666 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
667 // “If the FOR clause appears, it MUST contain exactly one <path>
668 // entry, even when multiple RCPT commands have been given. Multiple
669 // <path>s raise some security issues and have been deprecated, see
670 // Section 7.2.”
671 // for (auto i = 1u; i < forward_path_.size(); ++i)
672 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
674 std::string const tls_info{sock_.tls_info()};
675 if (tls_info.length()) {
676 fmt::format_to(std::back_inserter(headers), "\r\n\t({})", tls_info);
678 fmt::format_to(std::back_inserter(headers), ";\r\n\t{}\r\n",
679 msg.when().as_string_view());
681 return fmt::to_string(headers);
684 namespace {
685 bool lookup_domain(CDB& cdb, Domain const& domain)
687 if (!domain.empty()) {
688 if (cdb.contains(domain.ascii())) {
689 return true;
691 if (domain.is_unicode() && cdb.contains(domain.utf8())) {
692 return true;
695 return false;
697 } // namespace
699 std::tuple<Session::SpamStatus, std::string> Session::spam_status_()
701 if (spf_result_ == SPF::Result::FAIL && !ip_allowed_) {
702 LOG(INFO) << "spam since SPF failed";
703 return {SpamStatus::spam, "SPF failed"};
706 // These should have already been rejected by verify_client_().
707 if ((reverse_path_.domain().ascii() == "localhost.local") ||
708 (reverse_path_.domain().ascii() == "localhost")) {
709 LOG(INFO) << "spam since reverse path is localhost";
710 return {SpamStatus::spam, "bogus reverse_path"};
713 std::vector<std::string> why_ham;
715 // Anything enciphered tastes a lot like ham.
716 if (sock_.tls())
717 why_ham.emplace_back("they used TLS");
719 if (spf_result_ == SPF::Result::PASS) {
720 if (lookup_domain(allow_, spf_sender_domain_)) {
721 why_ham.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
722 spf_sender_domain_.utf8()));
724 else {
725 auto tld_dom{tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
726 if (tld_dom && allow_.contains(tld_dom)) {
727 why_ham.emplace_back(fmt::format(
728 "SPF sender registered domain ({}) is allowed", tld_dom));
732 else {
733 LOG(INFO) << "not ham since SPF not PASS";
736 if (fcrdns_allowed_) {
737 why_ham.emplace_back(
738 fmt::format("FCrDNS (or it's registered domain) is allowed"));
740 else {
741 LOG(INFO) << "not ham since fcrdns not allowed";
744 if (!why_ham.empty())
745 return {SpamStatus::ham,
746 fmt::format("{}", fmt::join(std::begin(why_ham), std::end(why_ham),
747 ", and "))};
749 return {SpamStatus::spam, "it's not ham"};
752 static std::string folder(Session::SpamStatus status,
753 std::vector<Mailbox> const& forward_path,
754 Mailbox const& reverse_path)
756 if (reverse_path ==
757 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
758 return ".Gmail";
760 if (reverse_path == Mailbox("ietf-smtp-bounces@ietf.org"))
761 return ".smtp";
763 struct assignment {
764 std::string_view local_part;
765 std::string_view folder;
768 assignment assignments[] = {
769 {"bootstrappable", ".bootstrappable"},
770 {"coreboot.org", ".coreboot"},
771 {"dmarc", ".dmarc"},
772 {"dns-privacy", ".dns-privacy"},
773 {"dnsmasq", ".INBOX.DNSmasq"},
774 {"emailcore", ".emailcore"},
775 {"fucking-facebook", ".FB"},
776 {"gene-ebay", ".EBay"},
777 {"i-hate-facebook", ".FB"},
778 {"i-hate-linked-in", ".linkedin"},
779 {"mailop", ".INBOX.mailop"},
780 {"modelfkeyboards.com", ""},
781 {"nest", ".INBOX.Nest"},
782 {"opendmarc-dev", ".dmarc"},
783 {"opendmarc-users", ".dmarc"},
784 {"papasys.com", ".INBOX.PAPA"},
785 {"postmaster-rua", ".INBOX.rua"},
786 {"quic=ietf.org", ".INBOX.quic"},
787 {"shadowserver-reports@digilicious.com", ".INBOX.shadowserver"},
788 {"theatlantic.com", ""},
789 {"time-nutz", ".time-nutz"},
790 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
793 for (auto ass : assignments) {
794 if (iequal(forward_path[0].local_part(), ass.local_part))
795 return std::string(ass.folder);
798 if (iends_with(forward_path[0].local_part(), "-at-duck"))
799 return ".JunkDuck";
801 if (status == Session::SpamStatus::spam)
802 return ".Junk";
804 return "";
807 bool Session::msg_new()
809 CHECK((state_ == xact_step::data) || (state_ == xact_step::bdat));
811 auto const& [status, reason]{spam_status_()};
813 LOG(INFO) << ((status == SpamStatus::ham) ? "ham since " : "spam since ")
814 << reason;
816 // All sources of ham get a fresh 5 minute timeout per message.
817 if (status == SpamStatus::ham) {
818 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr))
819 alarm(5 * 60);
822 msg_ = std::make_unique<MessageStore>();
824 if (!FLAGS_max_write)
825 FLAGS_max_write = max_msg_size();
827 try {
828 msg_->open(server_id_(), FLAGS_max_write,
829 folder(status, forward_path_, reverse_path_));
830 auto const hdrs{added_headers_(*(msg_.get()))};
831 msg_->write(hdrs);
833 // fmt::memory_buffer spam_status;
834 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
835 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
836 // msg_->write(spam_status.data(), spam_status.size());
838 LOG(INFO) << "Spam-Status: "
839 << ((status == SpamStatus::spam) ? "Yes" : "No") << ", "
840 << reason;
842 return true;
844 catch (std::system_error const& e) {
845 switch (errno) {
846 case ENOSPC:
847 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
848 LOG(ERROR) << "no space";
849 msg_->trash();
850 msg_.reset();
851 return false;
853 default:
854 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
855 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
856 LOG(ERROR) << e.what();
857 msg_->trash();
858 msg_.reset();
859 return false;
862 catch (std::exception const& e) {
863 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
864 LOG(ERROR) << e.what();
865 msg_->trash();
866 msg_.reset();
867 return false;
870 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
871 LOG(ERROR) << "msg_new failed with no exception caught";
872 msg_->trash();
873 msg_.reset();
874 return false;
877 bool Session::msg_write(char const* s, std::streamsize count)
879 if ((state_ != xact_step::data) && (state_ != xact_step::bdat))
880 return false;
882 if (!msg_)
883 return false;
885 try {
886 if (msg_->write(s, count))
887 return true;
889 catch (std::system_error const& e) {
890 switch (errno) {
891 case ENOSPC:
892 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
893 LOG(ERROR) << "no space";
894 msg_->trash();
895 msg_.reset();
896 return false;
898 default:
899 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
900 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
901 LOG(ERROR) << e.what();
902 msg_->trash();
903 msg_.reset();
904 return false;
907 catch (std::exception const& e) {
908 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
909 LOG(ERROR) << e.what();
910 msg_->trash();
911 msg_.reset();
912 return false;
915 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
916 LOG(ERROR) << "msg_write failed with no exception caught";
917 msg_->trash();
918 msg_.reset();
919 return false;
922 bool Session::data_start()
924 last_in_group_("DATA");
926 switch (state_) {
927 case xact_step::helo:
928 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
929 LOG(WARNING) << "'DATA' before HELO/EHLO"
930 << (sock_.has_peername() ? " from " : "") << client_;
931 return false;
932 case xact_step::mail:
933 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
934 LOG(WARNING) << "'DATA' before 'MAIL FROM'"
935 << (sock_.has_peername() ? " from " : "") << client_;
936 return false;
937 case xact_step::rcpt:
939 /******************************************************************
940 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
942 The DATA command can fail at only two points in the protocol
943 exchange:
945 If there was no MAIL, or no RCPT, command, or all such commands
946 were rejected, the server MAY return a "command out of sequence"
947 (503) or "no valid recipients" (554) reply in response to the DATA
948 command. If one of those replies (or any other 5yz reply) is
949 received, the client MUST NOT send the message data; more
950 generally, message data MUST NOT be sent unless a 354 reply is
951 received.
953 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
955 The additional restriction is that when there have been no
956 successful RCPT commands in the mail transaction, the DATA command
957 MUST fail with a 503 reply code.
959 Therefore I will send the reply code (503) that is valid for both,
960 and do the same for the BDAT case.
961 *******************************************************************/
963 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
964 LOG(WARNING) << "no valid recipients"
965 << (sock_.has_peername() ? " from " : "") << client_;
966 return false;
967 case xact_step::data: break;
968 case xact_step::bdat:
969 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
970 LOG(WARNING) << "'DATA' during BDAT transfer"
971 << (sock_.has_peername() ? " from " : "") << client_;
972 return false;
973 case xact_step::rset:
974 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
975 LOG(WARNING) << "error state must be cleared with a RSET"
976 << (sock_.has_peername() ? " from " : "") << client_;
977 return false;
980 if (binarymime_) {
981 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
982 << std::flush;
983 LOG(WARNING) << "DATA does not support BINARYMIME";
984 state_ = xact_step::rset; // RFC 3030 section 3 page 5
985 return false;
988 if (!msg_new()) {
989 LOG(ERROR) << "msg_new() failed";
990 return false;
993 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush;
994 LOG(INFO) << "DATA";
995 return true;
998 bool Session::do_deliver_()
1000 CHECK(msg_);
1002 try {
1003 msg_->deliver();
1004 msg_->close();
1006 catch (std::system_error const& e) {
1007 switch (errno) {
1008 case ENOSPC:
1009 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
1010 LOG(ERROR) << "no space";
1011 msg_->trash();
1012 reset_();
1013 return false;
1015 default:
1016 out_() << "451 4.3.0 mail system error\r\n" << std::flush;
1017 if (errno)
1018 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
1019 LOG(ERROR) << e.what();
1020 msg_->trash();
1021 reset_();
1022 return false;
1026 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1027 << msg_->id();
1028 return true;
1031 void Session::xfer_response_(std::string_view success_msg)
1033 auto bad_recipients_db_name = config_path_ / "bad_recipients_data";
1034 CDB bad_recipients_db;
1035 if (!bad_recipients_db.open(bad_recipients_db_name)) {
1036 LOG(WARNING) << "can't open bad_recipients_data";
1039 auto temp_fail_db_name = config_path_ / "temp_fail_data";
1040 CDB temp_fail_db;
1041 if (!temp_fail_db.open(temp_fail_db_name)) {
1042 LOG(WARNING) << "can't open temp_fail_data";
1045 std::vector<std::string> bad_recipients;
1046 if (bad_recipients_db.is_open()) {
1047 for (auto fp : forward_path_) {
1048 if (bad_recipients_db.contains_lc(fp.local_part())) {
1049 bad_recipients.push_back(fp);
1050 LOG(WARNING) << "bad recipient " << fp;
1054 std::vector<std::string> temp_failed;
1055 if (temp_fail_db.is_open()) {
1056 for (auto fp : forward_path_) {
1057 if (temp_fail_db.contains_lc(fp.local_part())) {
1058 temp_failed.push_back(fp);
1059 LOG(WARNING) << "temp failed recipient " << fp;
1064 if (prdr_ && forward_path_.size() > 1 &&
1065 (bad_recipients.size() || temp_failed.size())) {
1067 if (forward_path_.size() == bad_recipients.size()) {
1068 out_() << "550 5.1.1 all recipients bad\r\n";
1070 else if (forward_path_.size() == temp_failed.size()) {
1071 out_() << "450 4.1.1 temporary failure for all recipients\r\n";
1073 else {
1074 // this is the mixed situation
1075 out_() << "353 per recipient responses follow:\r\n";
1076 for (auto fp : forward_path_) {
1077 if (bad_recipients_db.is_open() && bad_recipients_db.contains_lc(fp.local_part())) {
1078 out_() << "550 5.1.1 bad recipient " << fp << "\r\n";
1079 LOG(INFO) << "bad recipient " << fp;
1081 else if (temp_fail_db.is_open() && temp_fail_db.contains_lc(fp.local_part())) {
1082 out_() << "450 4.1.1 temporary failure for " << fp << "\r\n";
1083 LOG(INFO) << "temp fail for " << fp;
1085 else {
1086 out_() << "250 2.0.0 success for " << fp << "\r\n";
1087 LOG(INFO) << "success for " << fp;
1091 // after the per recipient status, a final and I think useless message.
1092 if (forward_path_.size() > (bad_recipients.size() + temp_failed.size())) {
1093 out_() << "250 2.0.0 success for some recipients\r\n";
1095 else if (temp_failed.size()) {
1096 out_() << "450 4.1.1 temporary failure for some recipients\r\n";
1098 else {
1099 out_() << "550 5.1.1 some bad recipients\r\n";
1103 else {
1104 if (bad_recipients.size()) {
1105 out_() << "550 5.1.1 bad recipient(s) ";
1106 std::copy(begin(bad_recipients), end(bad_recipients),
1107 std::experimental::make_ostream_joiner(out_(), ", "));
1108 out_() << "\r\n";
1110 else if (temp_failed.size()) {
1111 out_() << "450 4.1.1 temporary failure for ";
1112 std::copy(begin(temp_failed), end(temp_failed),
1113 std::experimental::make_ostream_joiner(out_(), ", "));
1114 out_() << "\r\n";
1116 else {
1117 out_() << "250 2.0.0 " << success_msg << " OK\r\n";
1122 void Session::data_done()
1124 CHECK((state_ == xact_step::data));
1126 if (msg_ && msg_->size_error()) {
1127 data_size_error();
1128 return;
1131 // Check for and act on magic "wait" address.
1133 using namespace boost::xpressive;
1135 sregex const rex = icase("wait-data-") >> (secs_ = +_d);
1136 smatch what;
1138 for (auto fp : forward_path_) {
1139 if (regex_match(fp.local_part(), what, rex) ||
1140 regex_match(fp.local_part(), what, all_rex)) {
1141 auto const str = what[secs_].str();
1142 LOG(INFO) << "waiting at DATA " << str << " seconds";
1143 long value = 0;
1144 std::from_chars(str.data(), str.data() + str.size(), value);
1145 google::FlushLogFiles(google::INFO);
1146 out_() << std::flush;
1147 sleep(value);
1148 LOG(INFO) << "done waiting";
1153 if (!do_deliver_()) {
1154 return;
1157 xfer_response_("DATA");
1159 out_() << std::flush;
1160 reset_();
1163 void Session::data_size_error()
1165 out_().clear(); // clear possible eof from input side
1166 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1167 if (msg_) {
1168 msg_->trash();
1170 LOG(WARNING) << "DATA size error";
1171 reset_();
1174 void Session::data_error()
1176 out_().clear(); // clear possible eof from input side
1177 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush;
1178 if (msg_) {
1179 msg_->trash();
1181 LOG(WARNING) << "DATA error";
1182 reset_();
1185 bool Session::bdat_start(size_t n)
1187 // In practice, this one gets pipelined.
1188 // last_in_group_("BDAT");
1190 switch (state_) {
1191 case xact_step::helo:
1192 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
1193 LOG(WARNING) << "'BDAT' before HELO/EHLO"
1194 << (sock_.has_peername() ? " from " : "") << client_;
1195 return false;
1196 case xact_step::mail:
1197 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
1198 LOG(WARNING) << "'BDAT' before 'MAIL FROM'"
1199 << (sock_.has_peername() ? " from " : "") << client_;
1200 return false;
1201 case xact_step::rcpt:
1202 // See comment in data_start()
1203 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1204 LOG(WARNING) << "no valid recipients"
1205 << (sock_.has_peername() ? " from " : "") << client_;
1206 return false;
1207 case xact_step::data: // first bdat
1208 break;
1209 case xact_step::bdat: return true;
1210 case xact_step::rset:
1211 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1212 LOG(WARNING) << "error state must be cleared with a RSET"
1213 << (sock_.has_peername() ? " from " : "") << client_;
1214 return false;
1217 state_ = xact_step::bdat;
1219 return msg_new();
1222 void Session::bdat_done(size_t n, bool last)
1224 if (state_ != xact_step::bdat) {
1225 bdat_seq_error();
1226 return;
1229 if (!msg_) {
1230 return;
1233 if (msg_->size_error()) {
1234 bdat_size_error();
1235 return;
1238 if (!last) {
1239 out_() << "250 2.0.0 BDAT " << n << " OK\r\n" << std::flush;
1240 LOG(INFO) << "BDAT " << n;
1241 return;
1244 LOG(INFO) << "BDAT " << n << " LAST";
1246 // Check for and act on magic "wait" address.
1248 using namespace boost::xpressive;
1250 sregex const rex = icase("wait-bdat-") >> (secs_ = +_d);
1251 smatch what;
1253 for (auto fp : forward_path_) {
1254 if (regex_match(fp.local_part(), what, rex) ||
1255 regex_match(fp.local_part(), what, all_rex)) {
1256 auto const str = what[secs_].str();
1257 LOG(INFO) << "waiting at BDAT " << str << " seconds";
1258 long value = 0;
1259 std::from_chars(str.data(), str.data() + str.size(), value);
1260 google::FlushLogFiles(google::INFO);
1261 out_() << std::flush;
1262 sleep(value);
1263 LOG(INFO) << "done waiting";
1268 if (!do_deliver_()) {
1269 return;
1272 xfer_response_(fmt::format("BDAT {} LAST", n));
1274 out_() << std::flush;
1275 reset_();
1278 void Session::bdat_size_error()
1280 out_().clear(); // clear possible eof from input side
1281 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1282 if (msg_) {
1283 msg_->trash();
1285 LOG(WARNING) << "BDAT size error";
1286 reset_();
1289 void Session::bdat_seq_error()
1291 out_().clear(); // clear possible eof from input side
1292 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush;
1293 if (msg_) {
1294 msg_->trash();
1296 LOG(WARNING) << "BDAT sequence error";
1297 reset_();
1300 void Session::bdat_io_error()
1302 out_().clear(); // clear possible eof from input side
1303 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush;
1304 if (msg_) {
1305 msg_->trash();
1307 LOG(WARNING) << "BDAT I/O error";
1308 reset_();
1311 void Session::rset()
1313 out_() << "250 2.1.5 RSET OK\r\n";
1314 // No flush RFC-2920 section 3.1, this could be part of a command group.
1315 LOG(INFO) << "RSET";
1316 reset_();
1319 void Session::noop(std::string_view str)
1321 last_in_group_("NOOP");
1322 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush;
1323 LOG(INFO) << "NOOP" << (str.length() ? " " : "") << str;
1326 void Session::vrfy(std::string_view str)
1328 last_in_group_("VRFY");
1329 out_() << "252 2.1.5 try it\r\n" << std::flush;
1330 LOG(INFO) << "VRFY" << (str.length() ? " " : "") << str;
1333 void Session::help(std::string_view str)
1335 if (iequal(str, "help\r\n")) {
1336 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush;
1338 else {
1339 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1340 << std::flush;
1342 LOG(INFO) << "HELP" << (str.length() ? " " : "") << str;
1345 void Session::quit()
1347 // send_.quit();
1348 // last_in_group_("QUIT");
1349 out_() << "221 2.0.0 closing connection\r\n" << std::flush;
1350 LOG(INFO) << "QUIT";
1351 exit_();
1354 void Session::auth()
1356 out_() << "454 4.7.0 authentication failure\r\n" << std::flush;
1357 LOG(INFO) << "AUTH";
1358 bad_host_("auth");
1361 void Session::error(std::string_view log_msg)
1363 out_() << "421 4.3.5 system error: " << log_msg << "\r\n" << std::flush;
1364 LOG(WARNING) << log_msg;
1367 void Session::cmd_unrecognized(std::string_view cmd)
1369 auto const escaped{esc(cmd)};
1370 LOG(WARNING) << "command unrecognized: \"" << escaped << "\"";
1372 if (++n_unrecognized_cmds_ >= Config::max_unrecognized_cmds) {
1373 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1374 << "\" exceeds limit\r\n"
1375 << std::flush;
1376 LOG(WARNING) << n_unrecognized_cmds_
1377 << " unrecognized commands is too many";
1378 exit_();
1381 out_() << "500 5.5.1 command unrecognized: \"" << escaped << "\"\r\n"
1382 << std::flush;
1385 void Session::bare_lf()
1387 // Error code used by Office 365.
1388 out_() << "554 5.6.11 bare LF\r\n" << std::flush;
1389 LOG(WARNING) << "bare LF";
1390 exit_();
1393 void Session::max_out()
1395 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1396 LOG(WARNING) << "message size maxed out";
1397 exit_();
1400 void Session::time_out()
1402 out_() << "421 4.4.2 time-out\r\n" << std::flush;
1403 LOG(WARNING) << "time-out" << (sock_.has_peername() ? " from " : "")
1404 << client_;
1405 exit_();
1408 void Session::starttls()
1410 last_in_group_("STARTTLS");
1411 if (sock_.tls()) {
1412 out_() << "554 5.5.1 TLS already active\r\n" << std::flush;
1413 LOG(WARNING) << "STARTTLS issued with TLS already active";
1415 else if (!extensions_) {
1416 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1417 << std::flush;
1418 LOG(WARNING) << "STARTTLS issued without using EHLO";
1420 else {
1421 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush;
1422 if (sock_.starttls_server(config_path_)) {
1423 reset_();
1424 max_msg_size(Config::max_msg_size_bro);
1425 LOG(INFO) << "STARTTLS " << sock_.tls_info();
1427 else {
1428 LOG(INFO) << "failed STARTTLS";
1433 void Session::exit_()
1435 // sock_.log_totals();
1437 timespec time_used{};
1438 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time_used);
1440 LOG(INFO) << "CPU time " << time_used.tv_sec << "." << std::setw(9)
1441 << std::setfill('0') << time_used.tv_nsec << " seconds";
1443 std::exit(EXIT_SUCCESS);
1446 /////////////////////////////////////////////////////////////////////////////
1448 // All of the verify_* functions send their own error messages back to
1449 // the client on failure, and return false.
1451 bool Session::verify_ip_address_(std::string& error_msg)
1453 auto ip_block_db_name = config_path_ / "ip-block";
1454 CDB ip_block;
1455 if (ip_block.open(ip_block_db_name) &&
1456 ip_block.contains(sock_.them_c_str())) {
1457 error_msg =
1458 fmt::format("IP address {} on static blocklist", sock_.them_c_str());
1459 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1460 return false;
1463 client_fcrdns_.clear();
1465 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1466 (sock_.them_address_literal() == IP6::loopback_literal)) {
1467 LOG(INFO) << "loopback address allowed";
1468 ip_allowed_ = true;
1469 client_fcrdns_.emplace_back(Domain{"localhost"});
1470 client_ = fmt::format("localhost {}", sock_.them_address_literal());
1471 return true;
1474 auto const fcrdns = DNS::fcrdns(res_, sock_.them_c_str());
1475 for (auto const& fcr : fcrdns) {
1476 client_fcrdns_.emplace_back(fcr);
1479 if (IP::is_private(sock_.them_address_literal())) {
1480 LOG(INFO) << "private address allowed";
1481 ip_allowed_ = true;
1482 client_ = sock_.them_address_literal();
1483 return true;
1486 if (!client_fcrdns_.empty()) {
1487 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1488 sock_.them_address_literal());
1489 // check allow list
1490 for (auto const& client_fcrdns : client_fcrdns_) {
1491 if (allow_.contains_lc(client_fcrdns.ascii())) {
1492 LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1493 fcrdns_allowed_ = true;
1494 return true;
1496 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1497 if (tld) {
1498 if (allow_.contains(tld)) {
1499 LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1500 fcrdns_allowed_ = true;
1501 return true;
1505 // check blocklist
1506 for (auto const& client_fcrdns : client_fcrdns_) {
1507 if (block_.contains_lc(client_fcrdns.ascii())) {
1508 error_msg =
1509 fmt::format("FCrDNS {} on static blocklist", client_fcrdns.ascii());
1510 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1511 return false;
1514 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1515 if (tld) {
1516 if (block_.contains(tld)) {
1517 error_msg = fmt::format(
1518 "FCrDNS registered domain {} on static blocklist", tld);
1519 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1520 return false;
1525 else {
1526 client_ = fmt::format("{}", sock_.them_address_literal());
1529 if (IP4::is_address(sock_.them_c_str())) {
1531 auto const reversed{IP4::reverse(sock_.them_c_str())};
1534 // Check with allow list.
1535 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1536 random_device_);
1538 for (auto wl : Config::wls) {
1539 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1540 if (q.has_record()) {
1541 using namespace boost::xpressive;
1543 auto const as = q.get_strings()[0];
1544 LOG(INFO) << "on allow list " << wl << " as " << as;
1546 mark_tag x_(1);
1547 mark_tag y_(2);
1548 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1549 smatch what;
1551 if (regex_match(as, what, rex)) {
1552 auto const x = what[x_].str();
1553 auto const y = what[y_].str();
1555 int value = 0;
1556 std::from_chars(y.data(), y.data() + y.size(), value);
1557 if (value > 0) {
1558 ip_allowed_ = true;
1559 LOG(INFO) << "allowed";
1563 LOG(INFO) << "Any A record skips check on block list";
1564 return true;
1569 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1570 std::shuffle(std::begin(Config::bls), std::end(Config::bls),
1571 random_device_);
1573 for (auto bl : Config::bls) {
1574 DNS::Query q(res_, DNS::RR_type::A, reversed + bl);
1575 if (q.has_record()) {
1576 const auto a_strings = q.get_strings();
1577 for (auto const& as : a_strings) {
1578 LOG(INFO) << bl << " returned " << as;
1580 for (auto const& as : a_strings) {
1581 if (as == "127.0.0.1") {
1582 LOG(INFO) << "Should never get 127.0.0.1, from " << bl;
1584 else if (as == "127.0.0.10" || as == "127.0.0.11") {
1585 LOG(INFO) << "PBL listed, ignoring " << bl;
1587 else if (as == "127.255.255.252") {
1588 LOG(INFO) << "Typing error in DNSBL name " << bl;
1590 else if (as == "127.255.255.254") {
1591 LOG(INFO) << "Anonymous query through public resolver " << bl;
1593 else if (as == "127.255.255.255") {
1594 LOG(INFO) << "Excessive number of queries " << bl;
1596 else {
1597 error_msg = fmt::format("IP address {} blocked: {} returned {}",
1598 sock_.them_c_str(), bl, as);
1599 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1600 return false;
1605 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1608 LOG(INFO) << "IP address okay";
1609 return true;
1612 bool domain_blocked(DNS::Resolver& res, Domain const& identity)
1614 if (identity.is_address_literal()) {
1615 // don't "domain block" address literals
1616 return false;
1618 if (!identity.ascii().empty()) {
1619 Domain lookup{fmt::format("{}.dbl.spamhaus.org", identity.ascii())};
1620 DNS::Query q(res, DNS::RR_type::A, lookup.ascii());
1621 if (q.has_record()) {
1622 const auto a_strings = q.get_strings();
1623 for (auto const& as : a_strings) {
1624 if (istarts_with(as, "127.0.1.")) {
1625 LOG(INFO) << "Domain " << identity << " blocked by spamhaus, " << as;
1626 return true;
1631 return false;
1634 // check the identity from HELO/EHLO
1635 bool Session::verify_client_(Domain const& client_identity,
1636 std::string& error_msg)
1638 if (!client_fcrdns_.empty()) {
1639 if (auto id = std::find(begin(client_fcrdns_), end(client_fcrdns_),
1640 client_identity);
1641 id != end(client_fcrdns_)) {
1642 // If the HELO ident is one of the FCrDNS names...
1643 if (id != begin(client_fcrdns_)) {
1644 // ...then rotate that one to the front of the list
1645 std::rotate(begin(client_fcrdns_), id, id + 1);
1647 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1648 sock_.them_address_literal());
1649 return true;
1651 LOG(INFO) << "claimed identity " << client_identity
1652 << " does NOT match any FCrDNS: ";
1653 for (auto const& client_fcrdns : client_fcrdns_) {
1654 LOG(INFO) << " " << client_fcrdns;
1658 // Bogus clients claim to be us or some local host.
1659 if (sock_.has_peername() &&
1660 ((client_identity == server_identity_) ||
1661 (client_identity.ascii() == "localhost") ||
1662 (client_identity.ascii() == "localhost.localdomain"))) {
1664 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1665 (sock_.them_address_literal() == IP6::loopback_literal)) {
1666 return true;
1669 // Give 'em a pass.
1670 if (ip_allowed_) {
1671 LOG(INFO) << "allow-listed IP address can claim to be "
1672 << client_identity;
1673 return true;
1676 // Ease up in test mode.
1677 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1678 return true;
1681 error_msg = fmt::format("liar, claimed to be {}", client_identity.ascii());
1682 out_() << "550 5.7.1 liar\r\n" << std::flush;
1683 return false;
1686 std::vector<std::string> labels;
1687 boost::algorithm::split(labels, client_identity.ascii(),
1688 boost::algorithm::is_any_of("."));
1689 if (labels.size() < 2) {
1690 error_msg =
1691 fmt::format("claimed bogus identity {}", client_identity.ascii());
1692 out_() << "550 4.7.1 bogus identity\r\n" << std::flush;
1693 return false;
1694 // // Sometimes we may want to look at mail from non conforming
1695 // // sending systems.
1696 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1697 // << client_ << " claiming " << client_identity;
1698 // return true;
1701 if (lookup_domain(block_, client_identity)) {
1702 error_msg =
1703 fmt::format("claimed blocked identity {}", client_identity.ascii());
1704 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1705 return false;
1708 auto const tld{tld_db_.get_registered_domain(client_identity.ascii())};
1709 if (!tld) {
1710 // Sometimes we may want to look at mail from misconfigured
1711 // sending systems.
1712 // LOG(WARNING) << "claimed identity has no registered domain";
1713 // return true;
1715 else if (block_.contains(tld)) {
1716 error_msg =
1717 fmt::format("claimed identity has blocked registered domain {}", tld);
1718 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush;
1719 return false;
1722 if (domain_blocked(res_, client_identity) ||
1723 (tld && domain_blocked(res_, Domain(tld)))) {
1724 error_msg =
1725 fmt::format("claimed identity {} blocked", client_identity.ascii());
1726 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1727 return false;
1730 DNS::Query q(res_, DNS::RR_type::A, client_identity.ascii());
1731 if (!q.has_record()) {
1732 LOG(WARNING) << "claimed identity " << client_identity.ascii()
1733 << " not DNS resolvable";
1736 // not otherwise objectionable
1737 return true;
1740 // check sender from RFC5321 MAIL FROM:
1741 bool Session::verify_sender_(Mailbox const& sender, std::string& error_msg)
1743 do_spf_check_(sender);
1745 std::string const sender_str{sender};
1747 if (sender.empty()) {
1748 // MAIL FROM:<>
1749 // is used to send bounce messages.
1750 return true;
1753 if (domain_blocked(res_, sender.domain())) {
1754 error_msg = fmt::format("{} sender domain blocked", sender_str);
1755 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1756 return false;
1759 auto bad_senders_db_name = config_path_ / "bad_senders";
1760 CDB bad_senders;
1761 if (bad_senders.open(bad_senders_db_name) &&
1762 bad_senders.contains(sender_str)) {
1763 error_msg = fmt::format("{} bad sender", sender_str);
1764 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1765 return false;
1768 // We don't accept mail /from/ a domain we are expecting to accept
1769 // mail for on an external network connection.
1771 // if (sock_.them_address_literal() != sock_.us_address_literal()) {
1772 // if ((accept_domains_.is_open() &&
1773 // (accept_domains_.contains(sender.domain().ascii()) ||
1774 // accept_domains_.contains(sender.domain().utf8()))) ||
1775 // (sender.domain() == server_identity_)) {
1777 // // Ease up in test mode.
1778 // if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1779 // return true;
1780 // }
1781 // out_() << "550 5.7.1 liar\r\n" << std::flush;
1782 // error_msg = fmt::format("liar, claimed to be {}",
1783 // sender.domain().utf8()); return false;
1784 // }
1785 // }
1787 if (sender.domain().is_address_literal()) {
1788 if (sender.domain().ascii() != sock_.them_address_literal()) {
1789 LOG(WARNING) << "sender domain " << sender.domain() << " does not match "
1790 << sock_.them_address_literal();
1792 return true;
1795 if (!verify_sender_domain_(sender.domain(), error_msg)) {
1796 LOG(INFO) << "verify sender domain failed: " << error_msg;
1797 return false;
1800 return true;
1803 // this sender is the RFC5321 MAIL FROM: domain part
1804 bool Session::verify_sender_domain_(Domain const& sender,
1805 std::string& error_msg)
1807 if (sender.empty()) {
1808 // MAIL FROM:<>
1809 // is used to send bounce messages.
1810 return true;
1813 // Break sender domain into labels:
1815 std::vector<std::string> labels;
1816 boost::algorithm::split(labels, sender.ascii(),
1817 boost::algorithm::is_any_of("."));
1819 if (labels.size() < 2) { // This is not a valid domain.
1820 error_msg = fmt::format("{} invalid syntax", sender.ascii());
1821 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1822 return false;
1825 if (lookup_domain(block_, sender)) {
1826 error_msg = fmt::format("SPF sender domain ({}) is blocked",
1827 spf_sender_domain_.ascii());
1828 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1829 return false;
1832 if (spf_result_ == SPF::Result::PASS) {
1833 if (allow_.contains(spf_sender_domain_.ascii())) {
1834 LOG(INFO) << "sender " << spf_sender_domain_.ascii() << " allowed";
1835 return true;
1838 auto const reg_dom{
1839 tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
1840 if (reg_dom) {
1841 if (allow_.contains(reg_dom)) {
1842 LOG(INFO) << "sender registered domain \"" << reg_dom << "\" allowed";
1843 return true;
1848 LOG(INFO) << "sender \"" << sender << "\" not disallowed";
1849 return true;
1852 void Session::do_spf_check_(Mailbox const& sender)
1854 if (!sock_.has_peername()) {
1855 spf_received_ = fmt::format("Received-SPF: pass ({}: allow-listed) "
1856 "client-ip={}; envelope-from={}; helo={};",
1857 server_id_(), "127.0.0.1", sender.as_string(),
1858 client_identity_.ascii());
1859 spf_sender_domain_ = Domain{"localhost"};
1860 return;
1863 auto const spf_srv = SPF::Server{server_id_().c_str()};
1864 auto spf_request = SPF::Request{spf_srv};
1866 if (IP4::is_address(sock_.them_c_str())) {
1867 spf_request.set_ipv4_str(sock_.them_c_str());
1869 else if (IP6::is_address(sock_.them_c_str())) {
1870 spf_request.set_ipv6_str(sock_.them_c_str());
1872 else {
1873 LOG(FATAL) << "bogus address " << sock_.them_address_literal() << ", "
1874 << sock_.them_c_str();
1877 auto const from{static_cast<std::string>(sender)};
1879 spf_request.set_env_from(from.c_str());
1880 spf_request.set_helo_dom(client_identity_.ascii().c_str());
1882 auto const spf_res{SPF::Response{spf_request}};
1883 spf_result_ = spf_res.result();
1884 spf_received_ = spf_res.received_spf();
1885 spf_sender_domain_ = Domain{spf_request.get_sender_dom()};
1887 LOG(INFO) << "spf_received_ == " << spf_received_;
1889 if (spf_result_ == SPF::Result::FAIL) {
1890 LOG(INFO) << "FAIL " << spf_res.header_comment();
1892 else if (spf_result_ == SPF::Result::NEUTRAL) {
1893 LOG(INFO) << "NEUTRAL " << spf_res.header_comment();
1895 else if (spf_result_ == SPF::Result::PASS) {
1896 LOG(INFO) << "PASS " << spf_res.header_comment();
1898 else {
1899 LOG(INFO) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
1903 bool Session::verify_from_params_(parameters_t const& parameters)
1905 // Take a look at the optional parameters:
1906 for (auto const& [name, value] : parameters) {
1907 if (iequal(name, "BODY")) {
1908 if (iequal(value, "8BITMIME")) {
1909 // everything is cool, this is our default...
1911 else if (iequal(value, "7BIT")) {
1912 // nothing to see here, move along...
1914 else if (iequal(value, "BINARYMIME")) {
1915 LOG(INFO) << "using BINARYMIME";
1916 binarymime_ = true;
1918 else {
1919 LOG(WARNING) << "unrecognized BODY type \"" << value << "\" requested";
1922 else if (iequal(name, "SMTPUTF8")) {
1923 if (!value.empty()) {
1924 LOG(WARNING) << "SMTPUTF8 parameter has a value: " << value;
1926 smtputf8_ = true;
1928 else if (iequal(name, "PRDR")) {
1929 LOG(INFO) << "using PRDR";
1930 prdr_ = true;
1933 else if (iequal(name, "SIZE")) {
1934 if (value.empty()) {
1935 LOG(WARNING) << "SIZE parameter has no value.";
1937 else {
1938 try {
1939 auto const sz = stoull(value);
1940 if (sz > max_msg_size()) {
1941 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1942 LOG(WARNING) << "SIZE parameter too large: " << sz;
1943 return false;
1946 catch (std::invalid_argument const& e) {
1947 LOG(WARNING) << "SIZE parameter has invalid value: " << value;
1949 catch (std::out_of_range const& e) {
1950 LOG(WARNING) << "SIZE parameter has out-of-range value: " << value;
1952 // I guess we just ignore bad size parameters.
1955 else if (iequal(name, "REQUIRETLS")) {
1956 if (!sock_.tls()) {
1957 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush;
1958 LOG(WARNING) << "REQUIRETLS needed";
1959 return false;
1962 else {
1963 LOG(WARNING) << "unrecognized 'MAIL FROM' parameter " << name << "="
1964 << value;
1968 return true;
1971 bool Session::verify_rcpt_params_(parameters_t const& parameters)
1973 // Take a look at the optional parameters:
1974 for (auto const& [name, value] : parameters) {
1975 if (iequal(name, "RRVS")) {
1976 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
1977 LOG(INFO) << name << "=" << value;
1979 else {
1980 LOG(WARNING) << "unrecognized 'RCPT TO' parameter " << name << "="
1981 << value;
1985 return true;
1988 // check recipient from RFC5321 RCPT TO:
1989 bool Session::verify_recipient_(Mailbox const& recipient)
1991 if (recipient == Mailbox{"Postmaster"}) {
1992 LOG(INFO) << "magic Postmaster address";
1993 return true;
1996 auto const accepted_domain{[this, &recipient] {
1997 if (recipient.domain().is_address_literal()) {
1998 if (recipient.domain().ascii() != sock_.us_address_literal()) {
1999 LOG(WARNING) << "recipient.domain address " << recipient.domain()
2000 << " does not match ours " << sock_.us_address_literal();
2002 return false;
2005 return true;
2008 // Domains we accept mail for.
2009 if (accept_domains_.is_open()) {
2010 if (accept_domains_.contains(recipient.domain().ascii()) ||
2011 accept_domains_.contains(recipient.domain().utf8())) {
2012 return true;
2015 else {
2016 // If we have no list of domains to accept, at least take our own.
2017 if (recipient.domain().ascii() == server_id_()) {
2018 return true;
2022 return false;
2023 }()};
2025 if (!accepted_domain) {
2026 out_() << "550 5.7.1 relay access denied\r\n" << std::flush;
2027 LOG(WARNING) << "relay access denied for domain " << recipient.domain();
2028 return false;
2031 // if (recipient.local_part() == "gene" && client_fcrdns_.size() &&
2032 // client_fcrdns_[0].ascii().ends_with("outlook.com")) {
2033 // // Getting Spam'ed by MS
2034 // if (reverse_path_.empty() || (reverse_path_.length() > 40) ||
2035 // reverse_path_.domain().ascii().ends_with(".onmicrosoft.com")) {
2036 // std::string error_msg = fmt::format("rejecting spammy message from {}",
2037 // client_fcrdns_[0].ascii());
2038 // LOG(WARNING) << error_msg;
2039 // out_() << "550 5.7.0 " << error_msg << "\r\n" << std::flush;
2040 // return false;
2041 // }
2042 // }
2044 // Check for local addresses we reject.
2046 auto bad_recipients_db_name = config_path_ / "bad_recipients";
2047 CDB bad_recipients_db;
2048 if (bad_recipients_db.open(bad_recipients_db_name) &&
2049 bad_recipients_db.contains_lc(recipient.local_part())) {
2050 out_() << "550 5.1.1 bad recipient " << recipient << "\r\n" << std::flush;
2051 LOG(WARNING) << "bad recipient " << recipient;
2052 return false;
2057 auto fail_db_name = config_path_ / "fail_554";
2058 if (fs::exists(fail_db_name)) {
2059 CDB fail_db;
2060 if (fail_db.open(fail_db_name) &&
2061 fail_db.contains(recipient.local_part())) {
2062 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2063 << "\r\n"
2064 << std::flush;
2065 LOG(WARNING) << "fail_554 recipient " << recipient;
2066 return false;
2071 return true;