add _lc version
[ghsmtp.git] / Session.cpp
blobd5ef3a6f16b57c4ffa84b64d785a43d941f42974
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 {"Emailcore", ".emailcore"},
756 {"bootstrappable", ".bootstrappable"},
757 {"coreboot.org", ".coreboot"},
758 {"dmarc", ".dmarc"},
759 {"dns-privacy", ".dns-privacy"},
760 {"dnsmasq", ".INBOX.DNSmasq"},
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 {"postmaster-rua", ".INBOX.rua"},
771 {"quic=ietf.org", ".INBOX.quic"},
772 {"shadowserver-reports@digilicious.com", ".INBOX.shadowserver"},
773 {"theatlantic.com", ""},
774 {"time-nutz", ".time-nutz"},
775 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
778 for (auto ass : assignments) {
779 if (forward_path[0].local_part() == ass.local_part)
780 return std::string(ass.folder);
783 if (iends_with(forward_path[0].local_part(), "-at-duck"))
784 return ".JunkDuck";
786 if (status == Session::SpamStatus::spam)
787 return ".Junk";
789 return "";
792 bool Session::msg_new()
794 CHECK((state_ == xact_step::data) || (state_ == xact_step::bdat));
796 auto const& [status, reason]{spam_status_()};
798 LOG(INFO) << ((status == SpamStatus::ham) ? "ham since " : "spam since ")
799 << reason;
801 // All sources of ham get a fresh 5 minute timeout per message.
802 if (status == SpamStatus::ham) {
803 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr))
804 alarm(5 * 60);
807 msg_ = std::make_unique<MessageStore>();
809 if (!FLAGS_max_write)
810 FLAGS_max_write = max_msg_size();
812 try {
813 msg_->open(server_id_(), FLAGS_max_write,
814 folder(status, forward_path_, reverse_path_));
815 auto const hdrs{added_headers_(*(msg_.get()))};
816 msg_->write(hdrs);
818 // fmt::memory_buffer spam_status;
819 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
820 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
821 // msg_->write(spam_status.data(), spam_status.size());
823 LOG(INFO) << "Spam-Status: "
824 << ((status == SpamStatus::spam) ? "Yes" : "No") << ", "
825 << reason;
827 return true;
829 catch (std::system_error const& e) {
830 switch (errno) {
831 case ENOSPC:
832 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
833 LOG(ERROR) << "no space";
834 msg_->trash();
835 msg_.reset();
836 return false;
838 default:
839 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
840 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
841 LOG(ERROR) << e.what();
842 msg_->trash();
843 msg_.reset();
844 return false;
847 catch (std::exception const& e) {
848 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
849 LOG(ERROR) << e.what();
850 msg_->trash();
851 msg_.reset();
852 return false;
855 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
856 LOG(ERROR) << "msg_new failed with no exception caught";
857 msg_->trash();
858 msg_.reset();
859 return false;
862 bool Session::msg_write(char const* s, std::streamsize count)
864 if ((state_ != xact_step::data) && (state_ != xact_step::bdat))
865 return false;
867 if (!msg_)
868 return false;
870 try {
871 if (msg_->write(s, count))
872 return true;
874 catch (std::system_error const& e) {
875 switch (errno) {
876 case ENOSPC:
877 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
878 LOG(ERROR) << "no space";
879 msg_->trash();
880 msg_.reset();
881 return false;
883 default:
884 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
885 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
886 LOG(ERROR) << e.what();
887 msg_->trash();
888 msg_.reset();
889 return false;
892 catch (std::exception const& e) {
893 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
894 LOG(ERROR) << e.what();
895 msg_->trash();
896 msg_.reset();
897 return false;
900 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
901 LOG(ERROR) << "msg_write failed with no exception caught";
902 msg_->trash();
903 msg_.reset();
904 return false;
907 bool Session::data_start()
909 last_in_group_("DATA");
911 switch (state_) {
912 case xact_step::helo:
913 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
914 LOG(WARNING) << "'DATA' before HELO/EHLO"
915 << (sock_.has_peername() ? " from " : "") << client_;
916 return false;
917 case xact_step::mail:
918 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
919 LOG(WARNING) << "'DATA' before 'MAIL FROM'"
920 << (sock_.has_peername() ? " from " : "") << client_;
921 return false;
922 case xact_step::rcpt:
924 /******************************************************************
925 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
927 The DATA command can fail for only two reasons:
929 If there was no MAIL, or no RCPT, command, or all such commands were
930 rejected, the server MAY return a "command out of sequence" (503) or
931 "no valid recipients" (554) reply in response to the DATA command.
933 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
935 The additional restriction is that when there have been no successful
936 RCPT commands in the mail transaction, the DATA command MUST fail
937 with a 503 reply code.
939 Therefore I will send the reply code that is valid for both, and
940 do the same for the BDAT case.
941 *******************************************************************/
943 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
944 LOG(WARNING) << "no valid recipients"
945 << (sock_.has_peername() ? " from " : "") << client_;
946 return false;
947 case xact_step::data: break;
948 case xact_step::bdat:
949 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
950 LOG(WARNING) << "'DATA' during BDAT transfer"
951 << (sock_.has_peername() ? " from " : "") << client_;
952 return false;
953 case xact_step::rset:
954 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
955 LOG(WARNING) << "error state must be cleared with a RSET"
956 << (sock_.has_peername() ? " from " : "") << client_;
957 return false;
960 if (binarymime_) {
961 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
962 << std::flush;
963 LOG(WARNING) << "DATA does not support BINARYMIME";
964 state_ = xact_step::rset; // RFC 3030 section 3 page 5
965 return false;
968 if (!msg_new()) {
969 LOG(ERROR) << "msg_new() failed";
970 return false;
973 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush;
974 LOG(INFO) << "DATA";
975 return true;
978 bool Session::do_deliver_()
980 CHECK(msg_);
982 try {
983 msg_->deliver();
984 msg_->close();
986 catch (std::system_error const& e) {
987 switch (errno) {
988 case ENOSPC:
989 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
990 LOG(ERROR) << "no space";
991 msg_->trash();
992 reset_();
993 return false;
995 default:
996 out_() << "451 4.3.0 mail system error\r\n" << std::flush;
997 if (errno)
998 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
999 LOG(ERROR) << e.what();
1000 msg_->trash();
1001 reset_();
1002 return false;
1006 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1007 << msg_->id();
1008 return true;
1011 void Session::xfer_response_(std::string_view success_msg)
1013 auto bad_recipients_db_name = config_path_ / "bad_recipients_data";
1014 CDB bad_recipients_db;
1015 if (!bad_recipients_db.open(bad_recipients_db_name)) {
1016 LOG(WARNING) << "can't open bad_recipients_data";
1019 auto temp_fail_db_name = config_path_ / "temp_fail_data";
1020 CDB temp_fail_db;
1021 if (!temp_fail_db.open(temp_fail_db_name)) {
1022 LOG(WARNING) << "can't open temp_fail_data";
1025 std::vector<std::string> bad_recipients;
1026 if (bad_recipients_db.is_open()) {
1027 for (auto fp : forward_path_) {
1028 if (bad_recipients_db.contains_lc(fp.local_part())) {
1029 bad_recipients.push_back(fp);
1030 LOG(WARNING) << "bad recipient " << fp;
1034 std::vector<std::string> temp_failed;
1035 if (temp_fail_db.is_open()) {
1036 for (auto fp : forward_path_) {
1037 if (temp_fail_db.contains_lc(fp.local_part())) {
1038 temp_failed.push_back(fp);
1039 LOG(WARNING) << "temp failed recipient " << fp;
1044 if (prdr_ && forward_path_.size() > 1 &&
1045 (bad_recipients.size() || temp_failed.size())) {
1047 if (forward_path_.size() == bad_recipients.size()) {
1048 out_() << "550 5.1.1 all recipients bad\r\n";
1050 else if (forward_path_.size() == temp_failed.size()) {
1051 out_() << "450 4.1.1 temporary failure for all recipients\r\n";
1053 else {
1054 // this is the mixed situation
1055 out_() << "353 per recipient responses follow:\r\n";
1056 for (auto fp : forward_path_) {
1057 if (bad_recipients_db.is_open() && bad_recipients_db.contains_lc(fp.local_part())) {
1058 out_() << "550 5.1.1 bad recipient " << fp << "\r\n";
1059 LOG(INFO) << "bad recipient " << fp;
1061 else if (temp_fail_db.is_open() && temp_fail_db.contains_lc(fp.local_part())) {
1062 out_() << "450 4.1.1 temporary failure for " << fp << "\r\n";
1063 LOG(INFO) << "temp fail for " << fp;
1065 else {
1066 out_() << "250 2.0.0 success for " << fp << "\r\n";
1067 LOG(INFO) << "success for " << fp;
1071 // after the per recipient status, a final and I think useless message.
1072 if (forward_path_.size() > (bad_recipients.size() + temp_failed.size())) {
1073 out_() << "250 2.0.0 success for some recipients\r\n";
1075 else if (temp_failed.size()) {
1076 out_() << "450 4.1.1 temporary failure for some recipients\r\n";
1078 else {
1079 out_() << "550 5.1.1 some bad recipients\r\n";
1083 else {
1084 if (bad_recipients.size()) {
1085 out_() << "550 5.1.1 bad recipient(s) ";
1086 std::copy(begin(bad_recipients), end(bad_recipients),
1087 std::experimental::make_ostream_joiner(out_(), ", "));
1088 out_() << "\r\n";
1090 else if (temp_failed.size()) {
1091 out_() << "450 4.1.1 temporary failure for ";
1092 std::copy(begin(temp_failed), end(temp_failed),
1093 std::experimental::make_ostream_joiner(out_(), ", "));
1094 out_() << "\r\n";
1096 else {
1097 out_() << "250 2.0.0 " << success_msg << " OK\r\n";
1102 void Session::data_done()
1104 CHECK((state_ == xact_step::data));
1106 if (msg_ && msg_->size_error()) {
1107 data_size_error();
1108 return;
1111 // Check for and act on magic "wait" address.
1113 using namespace boost::xpressive;
1115 sregex const rex = icase("wait-data-") >> (secs_ = +_d);
1116 smatch what;
1118 for (auto fp : forward_path_) {
1119 if (regex_match(fp.local_part(), what, rex) ||
1120 regex_match(fp.local_part(), what, all_rex)) {
1121 auto const str = what[secs_].str();
1122 LOG(INFO) << "waiting at DATA " << str << " seconds";
1123 long value = 0;
1124 std::from_chars(str.data(), str.data() + str.size(), value);
1125 google::FlushLogFiles(google::INFO);
1126 out_() << std::flush;
1127 sleep(value);
1128 LOG(INFO) << "done waiting";
1133 if (!do_deliver_()) {
1134 return;
1137 xfer_response_("DATA");
1139 out_() << std::flush;
1140 reset_();
1143 void Session::data_size_error()
1145 out_().clear(); // clear possible eof from input side
1146 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1147 if (msg_) {
1148 msg_->trash();
1150 LOG(WARNING) << "DATA size error";
1151 reset_();
1154 void Session::data_error()
1156 out_().clear(); // clear possible eof from input side
1157 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush;
1158 if (msg_) {
1159 msg_->trash();
1161 LOG(WARNING) << "DATA error";
1162 reset_();
1165 bool Session::bdat_start(size_t n)
1167 // In practice, this one gets pipelined.
1168 // last_in_group_("BDAT");
1170 switch (state_) {
1171 case xact_step::helo:
1172 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
1173 LOG(WARNING) << "'BDAT' before HELO/EHLO"
1174 << (sock_.has_peername() ? " from " : "") << client_;
1175 return false;
1176 case xact_step::mail:
1177 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
1178 LOG(WARNING) << "'BDAT' before 'MAIL FROM'"
1179 << (sock_.has_peername() ? " from " : "") << client_;
1180 return false;
1181 case xact_step::rcpt:
1182 // See comment in data_start()
1183 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1184 LOG(WARNING) << "no valid recipients"
1185 << (sock_.has_peername() ? " from " : "") << client_;
1186 return false;
1187 case xact_step::data: // first bdat
1188 break;
1189 case xact_step::bdat: return true;
1190 case xact_step::rset:
1191 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1192 LOG(WARNING) << "error state must be cleared with a RSET"
1193 << (sock_.has_peername() ? " from " : "") << client_;
1194 return false;
1197 state_ = xact_step::bdat;
1199 return msg_new();
1202 void Session::bdat_done(size_t n, bool last)
1204 if (state_ != xact_step::bdat) {
1205 bdat_seq_error();
1206 return;
1209 if (!msg_) {
1210 return;
1213 if (msg_->size_error()) {
1214 bdat_size_error();
1215 return;
1218 if (!last) {
1219 out_() << "250 2.0.0 BDAT " << n << " OK\r\n" << std::flush;
1220 LOG(INFO) << "BDAT " << n;
1221 return;
1224 LOG(INFO) << "BDAT " << n << " LAST";
1226 // Check for and act on magic "wait" address.
1228 using namespace boost::xpressive;
1230 sregex const rex = icase("wait-bdat-") >> (secs_ = +_d);
1231 smatch what;
1233 for (auto fp : forward_path_) {
1234 if (regex_match(fp.local_part(), what, rex) ||
1235 regex_match(fp.local_part(), what, all_rex)) {
1236 auto const str = what[secs_].str();
1237 LOG(INFO) << "waiting at BDAT " << str << " seconds";
1238 long value = 0;
1239 std::from_chars(str.data(), str.data() + str.size(), value);
1240 google::FlushLogFiles(google::INFO);
1241 out_() << std::flush;
1242 sleep(value);
1243 LOG(INFO) << "done waiting";
1248 if (!do_deliver_()) {
1249 return;
1252 xfer_response_(fmt::format("BDAT {} LAST", n));
1254 out_() << std::flush;
1255 reset_();
1258 void Session::bdat_size_error()
1260 out_().clear(); // clear possible eof from input side
1261 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1262 if (msg_) {
1263 msg_->trash();
1265 LOG(WARNING) << "BDAT size error";
1266 reset_();
1269 void Session::bdat_seq_error()
1271 out_().clear(); // clear possible eof from input side
1272 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush;
1273 if (msg_) {
1274 msg_->trash();
1276 LOG(WARNING) << "BDAT sequence error";
1277 reset_();
1280 void Session::bdat_io_error()
1282 out_().clear(); // clear possible eof from input side
1283 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush;
1284 if (msg_) {
1285 msg_->trash();
1287 LOG(WARNING) << "BDAT I/O error";
1288 reset_();
1291 void Session::rset()
1293 out_() << "250 2.1.5 RSET OK\r\n";
1294 // No flush RFC-2920 section 3.1, this could be part of a command group.
1295 LOG(INFO) << "RSET";
1296 reset_();
1299 void Session::noop(std::string_view str)
1301 last_in_group_("NOOP");
1302 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush;
1303 LOG(INFO) << "NOOP" << (str.length() ? " " : "") << str;
1306 void Session::vrfy(std::string_view str)
1308 last_in_group_("VRFY");
1309 out_() << "252 2.1.5 try it\r\n" << std::flush;
1310 LOG(INFO) << "VRFY" << (str.length() ? " " : "") << str;
1313 void Session::help(std::string_view str)
1315 if (iequal(str, "help\r\n")) {
1316 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush;
1318 else {
1319 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1320 << std::flush;
1322 LOG(INFO) << "HELP" << (str.length() ? " " : "") << str;
1325 void Session::quit()
1327 // send_.quit();
1328 // last_in_group_("QUIT");
1329 out_() << "221 2.0.0 closing connection\r\n" << std::flush;
1330 LOG(INFO) << "QUIT";
1331 exit_();
1334 void Session::auth()
1336 out_() << "454 4.7.0 authentication failure\r\n" << std::flush;
1337 LOG(INFO) << "AUTH";
1338 bad_host_("auth");
1341 void Session::error(std::string_view log_msg)
1343 out_() << "421 4.3.5 system error: " << log_msg << "\r\n" << std::flush;
1344 LOG(WARNING) << log_msg;
1347 void Session::cmd_unrecognized(std::string_view cmd)
1349 auto const escaped{esc(cmd)};
1350 LOG(WARNING) << "command unrecognized: \"" << escaped << "\"";
1352 if (++n_unrecognized_cmds_ >= Config::max_unrecognized_cmds) {
1353 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1354 << "\" exceeds limit\r\n"
1355 << std::flush;
1356 LOG(WARNING) << n_unrecognized_cmds_
1357 << " unrecognized commands is too many";
1358 exit_();
1361 out_() << "500 5.5.1 command unrecognized: \"" << escaped << "\"\r\n"
1362 << std::flush;
1365 void Session::bare_lf()
1367 // Error code used by Office 365.
1368 out_() << "554 5.6.11 bare LF\r\n" << std::flush;
1369 LOG(WARNING) << "bare LF";
1370 exit_();
1373 void Session::max_out()
1375 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1376 LOG(WARNING) << "message size maxed out";
1377 exit_();
1380 void Session::time_out()
1382 out_() << "421 4.4.2 time-out\r\n" << std::flush;
1383 LOG(WARNING) << "time-out" << (sock_.has_peername() ? " from " : "")
1384 << client_;
1385 exit_();
1388 void Session::starttls()
1390 last_in_group_("STARTTLS");
1391 if (sock_.tls()) {
1392 out_() << "554 5.5.1 TLS already active\r\n" << std::flush;
1393 LOG(WARNING) << "STARTTLS issued with TLS already active";
1395 else if (!extensions_) {
1396 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1397 << std::flush;
1398 LOG(WARNING) << "STARTTLS issued without using EHLO";
1400 else {
1401 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush;
1402 if (sock_.starttls_server(config_path_)) {
1403 reset_();
1404 max_msg_size(Config::max_msg_size_bro);
1405 LOG(INFO) << "STARTTLS " << sock_.tls_info();
1407 else {
1408 LOG(INFO) << "failed STARTTLS";
1413 void Session::exit_()
1415 // sock_.log_totals();
1417 timespec time_used{};
1418 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time_used);
1420 LOG(INFO) << "CPU time " << time_used.tv_sec << "." << std::setw(9)
1421 << std::setfill('0') << time_used.tv_nsec << " seconds";
1423 std::exit(EXIT_SUCCESS);
1426 /////////////////////////////////////////////////////////////////////////////
1428 // All of the verify_* functions send their own error messages back to
1429 // the client on failure, and return false.
1431 bool Session::verify_ip_address_(std::string& error_msg)
1433 auto ip_block_db_name = config_path_ / "ip-block";
1434 CDB ip_block;
1435 if (ip_block.open(ip_block_db_name) &&
1436 ip_block.contains(sock_.them_c_str())) {
1437 error_msg =
1438 fmt::format("IP address {} on static blocklist", sock_.them_c_str());
1439 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1440 return false;
1443 client_fcrdns_.clear();
1445 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1446 (sock_.them_address_literal() == IP6::loopback_literal)) {
1447 LOG(INFO) << "loopback address allowed";
1448 ip_allowed_ = true;
1449 client_fcrdns_.emplace_back("localhost");
1450 client_ = fmt::format("localhost {}", sock_.them_address_literal());
1451 return true;
1454 auto const fcrdns = DNS::fcrdns(res_, sock_.them_c_str());
1455 for (auto const& fcr : fcrdns) {
1456 client_fcrdns_.emplace_back(fcr);
1459 if (IP::is_private(sock_.them_address_literal())) {
1460 LOG(INFO) << "private address allowed";
1461 ip_allowed_ = true;
1462 client_ = sock_.them_address_literal();
1463 return true;
1466 if (!client_fcrdns_.empty()) {
1467 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1468 sock_.them_address_literal());
1469 // check allow list
1470 for (auto const& client_fcrdns : client_fcrdns_) {
1471 if (allow_.contains_lc(client_fcrdns.ascii())) {
1472 LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1473 fcrdns_allowed_ = true;
1474 return true;
1476 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1477 if (tld) {
1478 if (allow_.contains(tld)) {
1479 LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1480 fcrdns_allowed_ = true;
1481 return true;
1485 // check blocklist
1486 for (auto const& client_fcrdns : client_fcrdns_) {
1487 if (block_.contains_lc(client_fcrdns.ascii())) {
1488 error_msg =
1489 fmt::format("FCrDNS {} on static blocklist", client_fcrdns.ascii());
1490 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1491 return false;
1494 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1495 if (tld) {
1496 if (block_.contains(tld)) {
1497 error_msg = fmt::format(
1498 "FCrDNS registered domain {} on static blocklist", tld);
1499 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1500 return false;
1505 else {
1506 client_ = fmt::format("{}", sock_.them_address_literal());
1509 if (IP4::is_address(sock_.them_c_str())) {
1511 auto const reversed{IP4::reverse(sock_.them_c_str())};
1514 // Check with allow list.
1515 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1516 random_device_);
1518 for (auto wl : Config::wls) {
1519 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1520 if (q.has_record()) {
1521 using namespace boost::xpressive;
1523 auto const as = q.get_strings()[0];
1524 LOG(INFO) << "on allow list " << wl << " as " << as;
1526 mark_tag x_(1);
1527 mark_tag y_(2);
1528 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1529 smatch what;
1531 if (regex_match(as, what, rex)) {
1532 auto const x = what[x_].str();
1533 auto const y = what[y_].str();
1535 int value = 0;
1536 std::from_chars(y.data(), y.data() + y.size(), value);
1537 if (value > 0) {
1538 ip_allowed_ = true;
1539 LOG(INFO) << "allowed";
1543 LOG(INFO) << "Any A record skips check on block list";
1544 return true;
1549 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1550 std::shuffle(std::begin(Config::bls), std::end(Config::bls),
1551 random_device_);
1553 for (auto bl : Config::bls) {
1554 DNS::Query q(res_, DNS::RR_type::A, reversed + bl);
1555 if (q.has_record()) {
1556 const auto a_strings = q.get_strings();
1557 for (auto const& as : a_strings) {
1558 LOG(INFO) << bl << " returned " << as;
1560 for (auto const& as : a_strings) {
1561 if (as == "127.0.0.1") {
1562 LOG(INFO) << "Should never get 127.0.0.1, from " << bl;
1564 else if (as == "127.0.0.10" || as == "127.0.0.11") {
1565 LOG(INFO) << "PBL listed, ignoring " << bl;
1567 else if (as == "127.255.255.252") {
1568 LOG(INFO) << "Typing error in DNSBL name " << bl;
1570 else if (as == "127.255.255.254") {
1571 LOG(INFO) << "Anonymous query through public resolver " << bl;
1573 else if (as == "127.255.255.255") {
1574 LOG(INFO) << "Excessive number of queries " << bl;
1576 else {
1577 error_msg = fmt::format("IP address {} blocked: {} returned {}",
1578 sock_.them_c_str(), bl, as);
1579 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1580 return false;
1585 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1588 LOG(INFO) << "IP address okay";
1589 return true;
1592 bool domain_blocked(DNS::Resolver& res, Domain const& identity)
1594 Domain lookup{fmt::format("{}.dbl.spamhaus.org", identity.ascii())};
1595 DNS::Query q(res, DNS::RR_type::A, lookup.ascii());
1596 if (q.has_record()) {
1597 const auto a_strings = q.get_strings();
1598 for (auto const& as : a_strings) {
1599 if (istarts_with(as, "127.0.1.")) {
1600 LOG(INFO) << "Domain " << identity << " blocked by spamhaus, " << as;
1601 return true;
1605 return false;
1608 // check the identity from HELO/EHLO
1609 bool Session::verify_client_(Domain const& client_identity,
1610 std::string& error_msg)
1612 if (!client_fcrdns_.empty()) {
1613 if (auto id = std::find(begin(client_fcrdns_), end(client_fcrdns_),
1614 client_identity);
1615 id != end(client_fcrdns_)) {
1616 // If the HELO ident is one of the FCrDNS names...
1617 if (id != begin(client_fcrdns_)) {
1618 // ...then rotate that one to the front of the list
1619 std::rotate(begin(client_fcrdns_), id, id + 1);
1621 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1622 sock_.them_address_literal());
1623 return true;
1625 LOG(INFO) << "claimed identity " << client_identity
1626 << " does NOT match any FCrDNS: ";
1627 for (auto const& client_fcrdns : client_fcrdns_) {
1628 LOG(INFO) << " " << client_fcrdns;
1632 // Bogus clients claim to be us or some local host.
1633 if (sock_.has_peername() && ((client_identity == server_identity_) ||
1634 (client_identity == "localhost") ||
1635 (client_identity == "localhost.localdomain"))) {
1637 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1638 (sock_.them_address_literal() == IP6::loopback_literal)) {
1639 return true;
1642 // Give 'em a pass.
1643 if (ip_allowed_) {
1644 LOG(INFO) << "allow-listed IP address can claim to be "
1645 << client_identity;
1646 return true;
1649 // Ease up in test mode.
1650 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1651 return true;
1654 error_msg = fmt::format("liar, claimed to be {}", client_identity.ascii());
1655 out_() << "550 5.7.1 liar\r\n" << std::flush;
1656 return false;
1659 std::vector<std::string> labels;
1660 boost::algorithm::split(labels, client_identity.ascii(),
1661 boost::algorithm::is_any_of("."));
1662 if (labels.size() < 2) {
1663 error_msg =
1664 fmt::format("claimed bogus identity {}", client_identity.ascii());
1665 out_() << "550 4.7.1 bogus identity\r\n" << std::flush;
1666 return false;
1667 // // Sometimes we may want to look at mail from non conforming
1668 // // sending systems.
1669 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1670 // << client_ << " claiming " << client_identity;
1671 // return true;
1674 if (lookup_domain(block_, client_identity)) {
1675 error_msg =
1676 fmt::format("claimed blocked identity {}", client_identity.ascii());
1677 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1678 return false;
1681 auto const tld{tld_db_.get_registered_domain(client_identity.ascii())};
1682 if (!tld) {
1683 // Sometimes we may want to look at mail from misconfigured
1684 // sending systems.
1685 // LOG(WARNING) << "claimed identity has no registered domain";
1686 // return true;
1688 else if (block_.contains(tld)) {
1689 error_msg =
1690 fmt::format("claimed identity has blocked registered domain {}", tld);
1691 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush;
1692 return false;
1695 if (domain_blocked(res_, client_identity) ||
1696 domain_blocked(res_, Domain(tld))) {
1697 error_msg =
1698 fmt::format("claimed identity {} blocked", client_identity.ascii());
1699 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1700 return false;
1703 DNS::Query q(res_, DNS::RR_type::A, client_identity.ascii());
1704 if (!q.has_record()) {
1705 LOG(WARNING) << "claimed identity " << client_identity.ascii()
1706 << " not DNS resolvable";
1709 // not otherwise objectionable
1710 return true;
1713 // check sender from RFC5321 MAIL FROM:
1714 bool Session::verify_sender_(Mailbox const& sender, std::string& error_msg)
1716 do_spf_check_(sender);
1718 std::string const sender_str{sender};
1720 if (sender.empty()) {
1721 // MAIL FROM:<>
1722 // is used to send bounce messages.
1723 return true;
1726 if (domain_blocked(res_, sender.domain())) {
1727 error_msg = fmt::format("{} sender domain blocked", sender_str);
1728 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1729 return false;
1732 auto bad_senders_db_name = config_path_ / "bad_senders";
1733 CDB bad_senders;
1734 if (bad_senders.open(bad_senders_db_name) &&
1735 bad_senders.contains(sender_str)) {
1736 error_msg = fmt::format("{} bad sender", sender_str);
1737 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1738 return false;
1741 // We don't accept mail /from/ a domain we are expecting to accept
1742 // mail for on an external network connection.
1744 // if (sock_.them_address_literal() != sock_.us_address_literal()) {
1745 // if ((accept_domains_.is_open() &&
1746 // (accept_domains_.contains(sender.domain().ascii()) ||
1747 // accept_domains_.contains(sender.domain().utf8()))) ||
1748 // (sender.domain() == server_identity_)) {
1750 // // Ease up in test mode.
1751 // if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1752 // return true;
1753 // }
1754 // out_() << "550 5.7.1 liar\r\n" << std::flush;
1755 // error_msg = fmt::format("liar, claimed to be {}",
1756 // sender.domain().utf8()); return false;
1757 // }
1758 // }
1760 if (sender.domain().is_address_literal()) {
1761 if (sender.domain() != sock_.them_address_literal()) {
1762 LOG(WARNING) << "sender domain " << sender.domain() << " does not match "
1763 << sock_.them_address_literal();
1765 return true;
1768 if (!verify_sender_domain_(sender.domain(), error_msg)) {
1769 return false;
1772 return true;
1775 // this sender is the RFC5321 MAIL FROM: domain part
1776 bool Session::verify_sender_domain_(Domain const& sender,
1777 std::string& error_msg)
1779 if (sender.empty()) {
1780 // MAIL FROM:<>
1781 // is used to send bounce messages.
1782 return true;
1785 // Break sender domain into labels:
1787 std::vector<std::string> labels;
1788 boost::algorithm::split(labels, sender.ascii(),
1789 boost::algorithm::is_any_of("."));
1791 if (labels.size() < 2) { // This is not a valid domain.
1792 error_msg = fmt::format("{} invalid syntax", sender.ascii());
1793 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1794 return false;
1797 if (lookup_domain(block_, sender)) {
1798 error_msg = fmt::format("SPF sender domain ({}) is blocked",
1799 spf_sender_domain_.ascii());
1800 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1801 return false;
1804 if (spf_result_ == SPF::Result::PASS) {
1805 if (allow_.contains(spf_sender_domain_.ascii())) {
1806 LOG(INFO) << "sender " << spf_sender_domain_.ascii() << " allowed";
1807 return true;
1810 auto const reg_dom{
1811 tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
1812 if (reg_dom) {
1813 if (allow_.contains(reg_dom)) {
1814 LOG(INFO) << "sender registered domain \"" << reg_dom << "\" allowed";
1815 return true;
1820 LOG(INFO) << "sender \"" << sender << "\" not disallowed";
1821 return true;
1824 void Session::do_spf_check_(Mailbox const& sender)
1826 if (!sock_.has_peername()) {
1827 auto const ip_addr = "127.0.0.1"; // use localhost for local socket
1828 spf_received_ = fmt::format(
1829 "Received-SPF: pass ({}: allow-listed) client-ip={}; "
1830 "envelope-from={}; helo={};",
1831 server_id_(), ip_addr, sender.as_string(), client_identity_.ascii());
1832 spf_sender_domain_ = "localhost";
1833 return;
1836 auto const spf_srv = SPF::Server{server_id_().c_str()};
1837 auto spf_request = SPF::Request{spf_srv};
1839 if (IP4::is_address(sock_.them_c_str())) {
1840 spf_request.set_ipv4_str(sock_.them_c_str());
1842 else if (IP6::is_address(sock_.them_c_str())) {
1843 spf_request.set_ipv6_str(sock_.them_c_str());
1845 else {
1846 LOG(FATAL) << "bogus address " << sock_.them_address_literal() << ", "
1847 << sock_.them_c_str();
1850 auto const from{static_cast<std::string>(sender)};
1852 spf_request.set_env_from(from.c_str());
1853 spf_request.set_helo_dom(client_identity_.ascii().c_str());
1855 auto const spf_res{SPF::Response{spf_request}};
1856 spf_result_ = spf_res.result();
1857 spf_received_ = spf_res.received_spf();
1858 spf_sender_domain_ = spf_request.get_sender_dom();
1860 LOG(INFO) << "spf_received_ == " << spf_received_;
1862 if (spf_result_ == SPF::Result::FAIL) {
1863 LOG(INFO) << "FAIL " << spf_res.header_comment();
1865 else if (spf_result_ == SPF::Result::NEUTRAL) {
1866 LOG(INFO) << "NEUTRAL " << spf_res.header_comment();
1868 else if (spf_result_ == SPF::Result::PASS) {
1869 LOG(INFO) << "PASS " << spf_res.header_comment();
1871 else {
1872 LOG(INFO) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
1876 bool Session::verify_from_params_(parameters_t const& parameters)
1878 // Take a look at the optional parameters:
1879 for (auto const& [name, value] : parameters) {
1880 if (iequal(name, "BODY")) {
1881 if (iequal(value, "8BITMIME")) {
1882 // everything is cool, this is our default...
1884 else if (iequal(value, "7BIT")) {
1885 // nothing to see here, move along...
1887 else if (iequal(value, "BINARYMIME")) {
1888 LOG(INFO) << "using BINARYMIME";
1889 binarymime_ = true;
1891 else {
1892 LOG(WARNING) << "unrecognized BODY type \"" << value << "\" requested";
1895 else if (iequal(name, "SMTPUTF8")) {
1896 if (!value.empty()) {
1897 LOG(WARNING) << "SMTPUTF8 parameter has a value: " << value;
1899 smtputf8_ = true;
1901 else if (iequal(name, "PRDR")) {
1902 LOG(INFO) << "using PRDR";
1903 prdr_ = true;
1906 else if (iequal(name, "SIZE")) {
1907 if (value.empty()) {
1908 LOG(WARNING) << "SIZE parameter has no value.";
1910 else {
1911 try {
1912 auto const sz = stoull(value);
1913 if (sz > max_msg_size()) {
1914 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1915 LOG(WARNING) << "SIZE parameter too large: " << sz;
1916 return false;
1919 catch (std::invalid_argument const& e) {
1920 LOG(WARNING) << "SIZE parameter has invalid value: " << value;
1922 catch (std::out_of_range const& e) {
1923 LOG(WARNING) << "SIZE parameter has out-of-range value: " << value;
1925 // I guess we just ignore bad size parameters.
1928 else if (iequal(name, "REQUIRETLS")) {
1929 if (!sock_.tls()) {
1930 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush;
1931 LOG(WARNING) << "REQUIRETLS needed";
1932 return false;
1935 else {
1936 LOG(WARNING) << "unrecognized 'MAIL FROM' parameter " << name << "="
1937 << value;
1941 return true;
1944 bool Session::verify_rcpt_params_(parameters_t const& parameters)
1946 // Take a look at the optional parameters:
1947 for (auto const& [name, value] : parameters) {
1948 if (iequal(name, "RRVS")) {
1949 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
1950 LOG(INFO) << name << "=" << value;
1952 else {
1953 LOG(WARNING) << "unrecognized 'RCPT TO' parameter " << name << "="
1954 << value;
1958 return true;
1961 // check recipient from RFC5321 RCPT TO:
1962 bool Session::verify_recipient_(Mailbox const& recipient)
1964 if ((recipient.local_part() == "Postmaster") && (recipient.domain() == "")) {
1965 LOG(INFO) << "magic Postmaster address";
1966 return true;
1969 auto const accepted_domain{[this, &recipient] {
1970 if (recipient.domain().is_address_literal()) {
1971 if (recipient.domain() != sock_.us_address_literal()) {
1972 LOG(WARNING) << "recipient.domain address " << recipient.domain()
1973 << " does not match ours " << sock_.us_address_literal();
1975 return false;
1978 return true;
1981 // Domains we accept mail for.
1982 if (accept_domains_.is_open()) {
1983 if (accept_domains_.contains(recipient.domain().ascii()) ||
1984 accept_domains_.contains(recipient.domain().utf8())) {
1985 return true;
1988 else {
1989 // If we have no list of domains to accept, at least take our own.
1990 if (recipient.domain() == server_id_()) {
1991 return true;
1995 return false;
1996 }()};
1998 if (!accepted_domain) {
1999 out_() << "550 5.7.1 relay access denied\r\n" << std::flush;
2000 LOG(WARNING) << "relay access denied for domain " << recipient.domain();
2001 return false;
2004 if (recipient.local_part() == "gene" && client_fcrdns_.size() &&
2005 client_fcrdns_[0].ascii().ends_with("outlook.com")) {
2006 // Getting Spam'ed by MS
2007 if (reverse_path_.empty() || (reverse_path_.length() > 40) ||
2008 reverse_path_.domain().ascii().ends_with(".onmicrosoft.com")) {
2009 std::string error_msg = fmt::format("rejecting spammy message from {}",
2010 client_fcrdns_[0].ascii());
2011 LOG(WARNING) << error_msg;
2012 out_() << "550 5.7.0 " << error_msg << "\r\n" << std::flush;
2013 return false;
2017 // Check for local addresses we reject.
2019 auto bad_recipients_db_name = config_path_ / "bad_recipients";
2020 CDB bad_recipients_db;
2021 if (bad_recipients_db.open(bad_recipients_db_name) &&
2022 bad_recipients_db.contains_lc(recipient.local_part())) {
2023 out_() << "550 5.1.1 bad recipient " << recipient << "\r\n" << std::flush;
2024 LOG(WARNING) << "bad recipient " << recipient;
2025 return false;
2030 auto fail_db_name = config_path_ / "fail_554";
2031 if (fs::exists(fail_db_name)) {
2032 CDB fail_db;
2033 if (fail_db.open(fail_db_name) &&
2034 fail_db.contains(recipient.local_part())) {
2035 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2036 << "\r\n"
2037 << std::flush;
2038 LOG(WARNING) << "fail_554 recipient " << recipient;
2039 return false;
2044 return true;