still must match obs first
[ghsmtp.git] / Session.cpp
blob80187d9e084fbffb58e99710e6b0bacc7a69fc90
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 {"fucking-facebook", ".FB"},
761 {"gene-ebay", ".EBay"},
762 {"i-hate-facebook", ".FB"},
763 {"i-hate-linked-in", ".linkedin"},
764 {"mailop", ".INBOX.mailop"},
765 {"modelfkeyboards.com", ""},
766 {"nest", ".INBOX.Nest"},
767 {"opendmarc-dev", ".dmarc"},
768 {"opendmarc-users", ".dmarc"},
769 {"postmaster-rua", ".INBOX.rua"},
770 {"quic=ietf.org", ".INBOX.quic"},
771 {"shadowserver-reports@digilicious.com", ".INBOX.shadowserver"},
772 {"theatlantic.com", ""},
773 {"time-nutz", ".time-nutz"},
774 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
777 for (auto ass : assignments) {
778 if (forward_path[0].local_part() == ass.local_part)
779 return std::string(ass.folder);
782 if (iends_with(forward_path[0].local_part(), "-at-duck"))
783 return ".JunkDuck";
785 if (status == Session::SpamStatus::spam)
786 return ".Junk";
788 return "";
791 bool Session::msg_new()
793 CHECK((state_ == xact_step::data) || (state_ == xact_step::bdat));
795 auto const& [status, reason]{spam_status_()};
797 LOG(INFO) << ((status == SpamStatus::ham) ? "ham since " : "spam since ")
798 << reason;
800 // All sources of ham get a fresh 5 minute timeout per message.
801 if (status == SpamStatus::ham) {
802 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr))
803 alarm(5 * 60);
806 msg_ = std::make_unique<MessageStore>();
808 if (!FLAGS_max_write)
809 FLAGS_max_write = max_msg_size();
811 try {
812 msg_->open(server_id_(), FLAGS_max_write,
813 folder(status, forward_path_, reverse_path_));
814 auto const hdrs{added_headers_(*(msg_.get()))};
815 msg_->write(hdrs);
817 // fmt::memory_buffer spam_status;
818 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
819 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
820 // msg_->write(spam_status.data(), spam_status.size());
822 LOG(INFO) << "Spam-Status: "
823 << ((status == SpamStatus::spam) ? "Yes" : "No") << ", "
824 << reason;
826 return true;
828 catch (std::system_error const& e) {
829 switch (errno) {
830 case ENOSPC:
831 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
832 LOG(ERROR) << "no space";
833 msg_->trash();
834 msg_.reset();
835 return false;
837 default:
838 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
839 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
840 LOG(ERROR) << e.what();
841 msg_->trash();
842 msg_.reset();
843 return false;
846 catch (std::exception const& e) {
847 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
848 LOG(ERROR) << e.what();
849 msg_->trash();
850 msg_.reset();
851 return false;
854 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
855 LOG(ERROR) << "msg_new failed with no exception caught";
856 msg_->trash();
857 msg_.reset();
858 return false;
861 bool Session::msg_write(char const* s, std::streamsize count)
863 if ((state_ != xact_step::data) && (state_ != xact_step::bdat))
864 return false;
866 if (!msg_)
867 return false;
869 try {
870 if (msg_->write(s, count))
871 return true;
873 catch (std::system_error const& e) {
874 switch (errno) {
875 case ENOSPC:
876 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
877 LOG(ERROR) << "no space";
878 msg_->trash();
879 msg_.reset();
880 return false;
882 default:
883 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
884 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
885 LOG(ERROR) << e.what();
886 msg_->trash();
887 msg_.reset();
888 return false;
891 catch (std::exception const& e) {
892 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
893 LOG(ERROR) << e.what();
894 msg_->trash();
895 msg_.reset();
896 return false;
899 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
900 LOG(ERROR) << "msg_write failed with no exception caught";
901 msg_->trash();
902 msg_.reset();
903 return false;
906 bool Session::data_start()
908 last_in_group_("DATA");
910 switch (state_) {
911 case xact_step::helo:
912 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
913 LOG(WARNING) << "'DATA' before HELO/EHLO"
914 << (sock_.has_peername() ? " from " : "") << client_;
915 return false;
916 case xact_step::mail:
917 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
918 LOG(WARNING) << "'DATA' before 'MAIL FROM'"
919 << (sock_.has_peername() ? " from " : "") << client_;
920 return false;
921 case xact_step::rcpt:
923 /******************************************************************
924 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
926 The DATA command can fail for only two reasons:
928 If there was no MAIL, or no RCPT, command, or all such commands were
929 rejected, the server MAY return a "command out of sequence" (503) or
930 "no valid recipients" (554) reply in response to the DATA command.
932 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
934 The additional restriction is that when there have been no successful
935 RCPT commands in the mail transaction, the DATA command MUST fail
936 with a 503 reply code.
938 Therefore I will send the reply code that is valid for both, and
939 do the same for the BDAT case.
940 *******************************************************************/
942 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
943 LOG(WARNING) << "no valid recipients"
944 << (sock_.has_peername() ? " from " : "") << client_;
945 return false;
946 case xact_step::data: break;
947 case xact_step::bdat:
948 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
949 LOG(WARNING) << "'DATA' during BDAT transfer"
950 << (sock_.has_peername() ? " from " : "") << client_;
951 return false;
952 case xact_step::rset:
953 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
954 LOG(WARNING) << "error state must be cleared with a RSET"
955 << (sock_.has_peername() ? " from " : "") << client_;
956 return false;
959 if (binarymime_) {
960 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
961 << std::flush;
962 LOG(WARNING) << "DATA does not support BINARYMIME";
963 state_ = xact_step::rset; // RFC 3030 section 3 page 5
964 return false;
967 if (!msg_new()) {
968 LOG(ERROR) << "msg_new() failed";
969 return false;
972 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush;
973 LOG(INFO) << "DATA";
974 return true;
977 bool Session::do_deliver_()
979 CHECK(msg_);
981 try {
982 msg_->deliver();
983 msg_->close();
985 catch (std::system_error const& e) {
986 switch (errno) {
987 case ENOSPC:
988 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
989 LOG(ERROR) << "no space";
990 msg_->trash();
991 reset_();
992 return false;
994 default:
995 out_() << "451 4.3.0 mail system error\r\n" << std::flush;
996 if (errno)
997 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
998 LOG(ERROR) << e.what();
999 msg_->trash();
1000 reset_();
1001 return false;
1005 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1006 << msg_->id();
1007 return true;
1010 void Session::xfer_response_(std::string_view success_msg)
1012 auto bad_recipients_db_name = config_path_ / "bad_recipients_data";
1013 CDB bad_recipients_db;
1014 if (!bad_recipients_db.open(bad_recipients_db_name)) {
1015 LOG(WARNING) << "can't open bad_recipients_data";
1018 auto temp_fail_db_name = config_path_ / "temp_fail_data";
1019 CDB temp_fail_db;
1020 if (!temp_fail_db.open(temp_fail_db_name)) {
1021 LOG(WARNING) << "can't open temp_fail_data";
1024 std::vector<std::string> bad_recipients;
1025 if (bad_recipients_db.is_open()) {
1026 for (auto fp : forward_path_) {
1027 std::string loc = fp.local_part();
1028 std::transform(loc.begin(), loc.end(), loc.begin(),
1029 [](unsigned char c) { return std::tolower(c); });
1030 if (bad_recipients_db.contains(loc)) {
1031 bad_recipients.push_back(fp);
1032 LOG(WARNING) << "bad recipient " << fp;
1036 std::vector<std::string> temp_failed;
1037 if (temp_fail_db.is_open()) {
1038 for (auto fp : forward_path_) {
1039 std::string loc = fp.local_part();
1040 std::transform(loc.begin(), loc.end(), loc.begin(),
1041 [](unsigned char c) { return std::tolower(c); });
1042 if (temp_fail_db.contains(loc)) {
1043 temp_failed.push_back(fp);
1044 LOG(WARNING) << "temp failed recipient " << fp;
1049 if (prdr_ && forward_path_.size() > 1 &&
1050 (bad_recipients.size() || temp_failed.size())) {
1052 if (forward_path_.size() == bad_recipients.size()) {
1053 out_() << "550 5.1.1 all recipients bad\r\n";
1055 else if (forward_path_.size() == temp_failed.size()) {
1056 out_() << "450 4.1.1 temporary failure for all recipients\r\n";
1058 else {
1059 // this is the mixed situation
1060 out_() << "353 per recipient responses follow:\r\n";
1061 for (auto fp : forward_path_) {
1062 std::string loc = fp.local_part();
1063 std::transform(loc.begin(), loc.end(), loc.begin(),
1064 [](unsigned char c) { return std::tolower(c); });
1065 if (bad_recipients_db.is_open() && bad_recipients_db.contains(loc)) {
1066 out_() << "550 5.1.1 bad recipient " << fp << "\r\n";
1067 LOG(INFO) << "bad recipient " << fp;
1069 else if (temp_fail_db.is_open() && temp_fail_db.contains(loc)) {
1070 out_() << "450 4.1.1 temporary failure for " << fp << "\r\n";
1071 LOG(INFO) << "temp fail for " << fp;
1073 else {
1074 out_() << "250 2.0.0 success for " << fp << "\r\n";
1075 LOG(INFO) << "success for " << fp;
1079 // after the per recipient status, a final and I think useless message.
1080 if (forward_path_.size() > (bad_recipients.size() + temp_failed.size())) {
1081 out_() << "250 2.0.0 success for some recipients\r\n";
1083 else if (temp_failed.size()) {
1084 out_() << "450 4.1.1 temporary failure for some recipients\r\n";
1086 else {
1087 out_() << "550 5.1.1 some bad recipients\r\n";
1091 else {
1092 if (bad_recipients.size()) {
1093 out_() << "550 5.1.1 bad recipient(s) ";
1094 std::copy(begin(bad_recipients), end(bad_recipients),
1095 std::experimental::make_ostream_joiner(out_(), ", "));
1096 out_() << "\r\n";
1098 else if (temp_failed.size()) {
1099 out_() << "450 4.1.1 temporary failure for ";
1100 std::copy(begin(temp_failed), end(temp_failed),
1101 std::experimental::make_ostream_joiner(out_(), ", "));
1102 out_() << "\r\n";
1104 else {
1105 out_() << "250 2.0.0 " << success_msg << " OK\r\n";
1110 void Session::data_done()
1112 CHECK((state_ == xact_step::data));
1114 if (msg_ && msg_->size_error()) {
1115 data_size_error();
1116 return;
1119 // Check for and act on magic "wait" address.
1121 using namespace boost::xpressive;
1123 sregex const rex = icase("wait-data-") >> (secs_ = +_d);
1124 smatch what;
1126 for (auto fp : forward_path_) {
1127 if (regex_match(fp.local_part(), what, rex) ||
1128 regex_match(fp.local_part(), what, all_rex)) {
1129 auto const str = what[secs_].str();
1130 LOG(INFO) << "waiting at DATA " << str << " seconds";
1131 long value = 0;
1132 std::from_chars(str.data(), str.data() + str.size(), value);
1133 google::FlushLogFiles(google::INFO);
1134 out_() << std::flush;
1135 sleep(value);
1136 LOG(INFO) << "done waiting";
1141 if (!do_deliver_()) {
1142 return;
1145 xfer_response_("DATA");
1147 out_() << std::flush;
1148 reset_();
1151 void Session::data_size_error()
1153 out_().clear(); // clear possible eof from input side
1154 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1155 if (msg_) {
1156 msg_->trash();
1158 LOG(WARNING) << "DATA size error";
1159 reset_();
1162 void Session::data_error()
1164 out_().clear(); // clear possible eof from input side
1165 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush;
1166 if (msg_) {
1167 msg_->trash();
1169 LOG(WARNING) << "DATA error";
1170 reset_();
1173 bool Session::bdat_start(size_t n)
1175 // In practice, this one gets pipelined.
1176 // last_in_group_("BDAT");
1178 switch (state_) {
1179 case xact_step::helo:
1180 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
1181 LOG(WARNING) << "'BDAT' before HELO/EHLO"
1182 << (sock_.has_peername() ? " from " : "") << client_;
1183 return false;
1184 case xact_step::mail:
1185 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
1186 LOG(WARNING) << "'BDAT' before 'MAIL FROM'"
1187 << (sock_.has_peername() ? " from " : "") << client_;
1188 return false;
1189 case xact_step::rcpt:
1190 // See comment in data_start()
1191 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1192 LOG(WARNING) << "no valid recipients"
1193 << (sock_.has_peername() ? " from " : "") << client_;
1194 return false;
1195 case xact_step::data: // first bdat
1196 break;
1197 case xact_step::bdat: return true;
1198 case xact_step::rset:
1199 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1200 LOG(WARNING) << "error state must be cleared with a RSET"
1201 << (sock_.has_peername() ? " from " : "") << client_;
1202 return false;
1205 state_ = xact_step::bdat;
1207 return msg_new();
1210 void Session::bdat_done(size_t n, bool last)
1212 if (state_ != xact_step::bdat) {
1213 bdat_seq_error();
1214 return;
1217 if (!msg_) {
1218 return;
1221 if (msg_->size_error()) {
1222 bdat_size_error();
1223 return;
1226 if (!last) {
1227 out_() << "250 2.0.0 BDAT " << n << " OK\r\n" << std::flush;
1228 LOG(INFO) << "BDAT " << n;
1229 return;
1232 LOG(INFO) << "BDAT " << n << " LAST";
1234 // Check for and act on magic "wait" address.
1236 using namespace boost::xpressive;
1238 sregex const rex = icase("wait-bdat-") >> (secs_ = +_d);
1239 smatch what;
1241 for (auto fp : forward_path_) {
1242 if (regex_match(fp.local_part(), what, rex) ||
1243 regex_match(fp.local_part(), what, all_rex)) {
1244 auto const str = what[secs_].str();
1245 LOG(INFO) << "waiting at BDAT " << str << " seconds";
1246 long value = 0;
1247 std::from_chars(str.data(), str.data() + str.size(), value);
1248 google::FlushLogFiles(google::INFO);
1249 out_() << std::flush;
1250 sleep(value);
1251 LOG(INFO) << "done waiting";
1256 if (!do_deliver_()) {
1257 return;
1260 xfer_response_(fmt::format("BDAT {} LAST", n));
1262 out_() << std::flush;
1263 reset_();
1266 void Session::bdat_size_error()
1268 out_().clear(); // clear possible eof from input side
1269 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1270 if (msg_) {
1271 msg_->trash();
1273 LOG(WARNING) << "BDAT size error";
1274 reset_();
1277 void Session::bdat_seq_error()
1279 out_().clear(); // clear possible eof from input side
1280 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush;
1281 if (msg_) {
1282 msg_->trash();
1284 LOG(WARNING) << "BDAT sequence error";
1285 reset_();
1288 void Session::bdat_io_error()
1290 out_().clear(); // clear possible eof from input side
1291 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush;
1292 if (msg_) {
1293 msg_->trash();
1295 LOG(WARNING) << "BDAT I/O error";
1296 reset_();
1299 void Session::rset()
1301 out_() << "250 2.1.5 RSET OK\r\n";
1302 // No flush RFC-2920 section 3.1, this could be part of a command group.
1303 LOG(INFO) << "RSET";
1304 reset_();
1307 void Session::noop(std::string_view str)
1309 last_in_group_("NOOP");
1310 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush;
1311 LOG(INFO) << "NOOP" << (str.length() ? " " : "") << str;
1314 void Session::vrfy(std::string_view str)
1316 last_in_group_("VRFY");
1317 out_() << "252 2.1.5 try it\r\n" << std::flush;
1318 LOG(INFO) << "VRFY" << (str.length() ? " " : "") << str;
1321 void Session::help(std::string_view str)
1323 if (iequal(str, "help\r\n")) {
1324 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush;
1326 else {
1327 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1328 << std::flush;
1330 LOG(INFO) << "HELP" << (str.length() ? " " : "") << str;
1333 void Session::quit()
1335 // send_.quit();
1336 // last_in_group_("QUIT");
1337 out_() << "221 2.0.0 closing connection\r\n" << std::flush;
1338 LOG(INFO) << "QUIT";
1339 exit_();
1342 void Session::auth()
1344 out_() << "454 4.7.0 authentication failure\r\n" << std::flush;
1345 LOG(INFO) << "AUTH";
1346 bad_host_("auth");
1349 void Session::error(std::string_view log_msg)
1351 out_() << "421 4.3.5 system error: " << log_msg << "\r\n" << std::flush;
1352 LOG(WARNING) << log_msg;
1355 void Session::cmd_unrecognized(std::string_view cmd)
1357 auto const escaped{esc(cmd)};
1358 LOG(WARNING) << "command unrecognized: \"" << escaped << "\"";
1360 if (++n_unrecognized_cmds_ >= Config::max_unrecognized_cmds) {
1361 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1362 << "\" exceeds limit\r\n"
1363 << std::flush;
1364 LOG(WARNING) << n_unrecognized_cmds_
1365 << " unrecognized commands is too many";
1366 exit_();
1369 out_() << "500 5.5.1 command unrecognized: \"" << escaped << "\"\r\n"
1370 << std::flush;
1373 void Session::bare_lf()
1375 // Error code used by Office 365.
1376 out_() << "554 5.6.11 bare LF\r\n" << std::flush;
1377 LOG(WARNING) << "bare LF";
1378 exit_();
1381 void Session::max_out()
1383 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1384 LOG(WARNING) << "message size maxed out";
1385 exit_();
1388 void Session::time_out()
1390 out_() << "421 4.4.2 time-out\r\n" << std::flush;
1391 LOG(WARNING) << "time-out" << (sock_.has_peername() ? " from " : "")
1392 << client_;
1393 exit_();
1396 void Session::starttls()
1398 last_in_group_("STARTTLS");
1399 if (sock_.tls()) {
1400 out_() << "554 5.5.1 TLS already active\r\n" << std::flush;
1401 LOG(WARNING) << "STARTTLS issued with TLS already active";
1403 else if (!extensions_) {
1404 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1405 << std::flush;
1406 LOG(WARNING) << "STARTTLS issued without using EHLO";
1408 else {
1409 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush;
1410 if (sock_.starttls_server(config_path_)) {
1411 reset_();
1412 max_msg_size(Config::max_msg_size_bro);
1413 LOG(INFO) << "STARTTLS " << sock_.tls_info();
1415 else {
1416 LOG(INFO) << "failed STARTTLS";
1421 void Session::exit_()
1423 // sock_.log_totals();
1425 timespec time_used{};
1426 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time_used);
1428 LOG(INFO) << "CPU time " << time_used.tv_sec << "." << std::setw(9)
1429 << std::setfill('0') << time_used.tv_nsec << " seconds";
1431 std::exit(EXIT_SUCCESS);
1434 /////////////////////////////////////////////////////////////////////////////
1436 // All of the verify_* functions send their own error messages back to
1437 // the client on failure, and return false.
1439 bool Session::verify_ip_address_(std::string& error_msg)
1441 auto ip_block_db_name = config_path_ / "ip-block";
1442 CDB ip_block;
1443 if (ip_block.open(ip_block_db_name) &&
1444 ip_block.contains(sock_.them_c_str())) {
1445 error_msg =
1446 fmt::format("IP address {} on static blocklist", sock_.them_c_str());
1447 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1448 return false;
1451 client_fcrdns_.clear();
1453 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1454 (sock_.them_address_literal() == IP6::loopback_literal)) {
1455 LOG(INFO) << "loopback address allowed";
1456 ip_allowed_ = true;
1457 client_fcrdns_.emplace_back("localhost");
1458 client_ = fmt::format("localhost {}", sock_.them_address_literal());
1459 return true;
1462 auto const fcrdns = DNS::fcrdns(res_, sock_.them_c_str());
1463 for (auto const& fcr : fcrdns) {
1464 client_fcrdns_.emplace_back(fcr);
1467 if (IP::is_private(sock_.them_address_literal())) {
1468 LOG(INFO) << "private address allowed";
1469 ip_allowed_ = true;
1470 client_ = sock_.them_address_literal();
1471 return true;
1474 if (!client_fcrdns_.empty()) {
1475 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1476 sock_.them_address_literal());
1477 // check allow list
1478 for (auto const& client_fcrdns : client_fcrdns_) {
1479 if (allow_.contains(client_fcrdns.ascii())) {
1480 LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1481 fcrdns_allowed_ = true;
1482 return true;
1484 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1485 if (tld) {
1486 if (allow_.contains(tld)) {
1487 LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1488 fcrdns_allowed_ = true;
1489 return true;
1493 // check blocklist
1494 for (auto const& client_fcrdns : client_fcrdns_) {
1495 if (block_.contains(client_fcrdns.ascii())) {
1496 error_msg =
1497 fmt::format("FCrDNS {} on static blocklist", client_fcrdns.ascii());
1498 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1499 return false;
1502 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1503 if (tld) {
1504 if (block_.contains(tld)) {
1505 error_msg = fmt::format(
1506 "FCrDNS registered domain {} on static blocklist", tld);
1507 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1508 return false;
1513 else {
1514 client_ = fmt::format("{}", sock_.them_address_literal());
1517 if (IP4::is_address(sock_.them_c_str())) {
1519 auto const reversed{IP4::reverse(sock_.them_c_str())};
1522 // Check with allow list.
1523 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1524 random_device_);
1526 for (auto wl : Config::wls) {
1527 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1528 if (q.has_record()) {
1529 using namespace boost::xpressive;
1531 auto const as = q.get_strings()[0];
1532 LOG(INFO) << "on allow list " << wl << " as " << as;
1534 mark_tag x_(1);
1535 mark_tag y_(2);
1536 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1537 smatch what;
1539 if (regex_match(as, what, rex)) {
1540 auto const x = what[x_].str();
1541 auto const y = what[y_].str();
1543 int value = 0;
1544 std::from_chars(y.data(), y.data() + y.size(), value);
1545 if (value > 0) {
1546 ip_allowed_ = true;
1547 LOG(INFO) << "allowed";
1551 LOG(INFO) << "Any A record skips check on block list";
1552 return true;
1557 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1558 std::shuffle(std::begin(Config::bls), std::end(Config::bls),
1559 random_device_);
1561 for (auto bl : Config::bls) {
1562 DNS::Query q(res_, DNS::RR_type::A, reversed + bl);
1563 if (q.has_record()) {
1564 const auto a_strings = q.get_strings();
1565 for (auto const& as : a_strings) {
1566 LOG(INFO) << bl << " returned " << as;
1568 for (auto const& as : a_strings) {
1569 if (as == "127.0.0.1") {
1570 LOG(INFO) << "Should never get 127.0.0.1, from " << bl;
1572 else if (as == "127.0.0.10" || as == "127.0.0.11") {
1573 LOG(INFO) << "PBL listed, ignoring " << bl;
1575 else if (as == "127.255.255.252") {
1576 LOG(INFO) << "Typing error in DNSBL name " << bl;
1578 else if (as == "127.255.255.254") {
1579 LOG(INFO) << "Anonymous query through public resolver " << bl;
1581 else if (as == "127.255.255.255") {
1582 LOG(INFO) << "Excessive number of queries " << bl;
1584 else {
1585 error_msg = fmt::format("IP address {} blocked: {} returned {}",
1586 sock_.them_c_str(), bl, as);
1587 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1588 return false;
1593 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1596 LOG(INFO) << "IP address okay";
1597 return true;
1600 bool domain_blocked(DNS::Resolver& res, Domain const& identity)
1602 Domain lookup{fmt::format("{}.dbl.spamhaus.org", identity.ascii())};
1603 DNS::Query q(res, DNS::RR_type::A, lookup.ascii());
1604 if (q.has_record()) {
1605 const auto a_strings = q.get_strings();
1606 for (auto const& as : a_strings) {
1607 if (istarts_with(as, "127.0.1.")) {
1608 LOG(INFO) << "Domain " << identity << " blocked by spamhaus, " << as;
1609 return true;
1613 return false;
1616 // check the identity from HELO/EHLO
1617 bool Session::verify_client_(Domain const& client_identity,
1618 std::string& error_msg)
1620 if (!client_fcrdns_.empty()) {
1621 if (auto id = std::find(begin(client_fcrdns_), end(client_fcrdns_),
1622 client_identity);
1623 id != end(client_fcrdns_)) {
1624 // If the HELO ident is one of the FCrDNS names...
1625 if (id != begin(client_fcrdns_)) {
1626 // ...then rotate that one to the front of the list
1627 std::rotate(begin(client_fcrdns_), id, id + 1);
1629 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1630 sock_.them_address_literal());
1631 return true;
1633 LOG(INFO) << "claimed identity " << client_identity
1634 << " does NOT match any FCrDNS: ";
1635 for (auto const& client_fcrdns : client_fcrdns_) {
1636 LOG(INFO) << " " << client_fcrdns;
1640 // Bogus clients claim to be us or some local host.
1641 if (sock_.has_peername() && ((client_identity == server_identity_) ||
1642 (client_identity == "localhost") ||
1643 (client_identity == "localhost.localdomain"))) {
1645 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1646 (sock_.them_address_literal() == IP6::loopback_literal)) {
1647 return true;
1650 // Give 'em a pass.
1651 if (ip_allowed_) {
1652 LOG(INFO) << "allow-listed IP address can claim to be "
1653 << client_identity;
1654 return true;
1657 // Ease up in test mode.
1658 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1659 return true;
1662 error_msg = fmt::format("liar, claimed to be {}", client_identity.ascii());
1663 out_() << "550 5.7.1 liar\r\n" << std::flush;
1664 return false;
1667 std::vector<std::string> labels;
1668 boost::algorithm::split(labels, client_identity.ascii(),
1669 boost::algorithm::is_any_of("."));
1670 if (labels.size() < 2) {
1671 error_msg =
1672 fmt::format("claimed bogus identity {}", client_identity.ascii());
1673 out_() << "550 4.7.1 bogus identity\r\n" << std::flush;
1674 return false;
1675 // // Sometimes we may want to look at mail from non conforming
1676 // // sending systems.
1677 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1678 // << client_ << " claiming " << client_identity;
1679 // return true;
1682 if (lookup_domain(block_, client_identity)) {
1683 error_msg =
1684 fmt::format("claimed blocked identity {}", client_identity.ascii());
1685 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1686 return false;
1689 auto const tld{tld_db_.get_registered_domain(client_identity.ascii())};
1690 if (!tld) {
1691 // Sometimes we may want to look at mail from misconfigured
1692 // sending systems.
1693 // LOG(WARNING) << "claimed identity has no registered domain";
1694 // return true;
1696 else if (block_.contains(tld)) {
1697 error_msg =
1698 fmt::format("claimed identity has blocked registered domain {}", tld);
1699 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush;
1700 return false;
1703 if (domain_blocked(res_, client_identity) ||
1704 domain_blocked(res_, Domain(tld))) {
1705 error_msg =
1706 fmt::format("claimed identity {} blocked", client_identity.ascii());
1707 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1708 return false;
1711 DNS::Query q(res_, DNS::RR_type::A, client_identity.ascii());
1712 if (!q.has_record()) {
1713 LOG(WARNING) << "claimed identity " << client_identity.ascii()
1714 << " not DNS resolvable";
1717 // not otherwise objectionable
1718 return true;
1721 // check sender from RFC5321 MAIL FROM:
1722 bool Session::verify_sender_(Mailbox const& sender, std::string& error_msg)
1724 do_spf_check_(sender);
1726 std::string const sender_str{sender};
1728 if (sender.empty()) {
1729 // MAIL FROM:<>
1730 // is used to send bounce messages.
1731 return true;
1734 if (domain_blocked(res_, sender.domain())) {
1735 error_msg = fmt::format("{} sender domain blocked", sender_str);
1736 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1737 return false;
1740 auto bad_senders_db_name = config_path_ / "bad_senders";
1741 CDB bad_senders;
1742 if (bad_senders.open(bad_senders_db_name) &&
1743 bad_senders.contains(sender_str)) {
1744 error_msg = fmt::format("{} bad sender", sender_str);
1745 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1746 return false;
1749 // We don't accept mail /from/ a domain we are expecting to accept
1750 // mail for on an external network connection.
1752 // if (sock_.them_address_literal() != sock_.us_address_literal()) {
1753 // if ((accept_domains_.is_open() &&
1754 // (accept_domains_.contains(sender.domain().ascii()) ||
1755 // accept_domains_.contains(sender.domain().utf8()))) ||
1756 // (sender.domain() == server_identity_)) {
1758 // // Ease up in test mode.
1759 // if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1760 // return true;
1761 // }
1762 // out_() << "550 5.7.1 liar\r\n" << std::flush;
1763 // error_msg = fmt::format("liar, claimed to be {}",
1764 // sender.domain().utf8()); return false;
1765 // }
1766 // }
1768 if (sender.domain().is_address_literal()) {
1769 if (sender.domain() != sock_.them_address_literal()) {
1770 LOG(WARNING) << "sender domain " << sender.domain() << " does not match "
1771 << sock_.them_address_literal();
1773 return true;
1776 if (!verify_sender_domain_(sender.domain(), error_msg)) {
1777 return false;
1780 return true;
1783 // this sender is the RFC5321 MAIL FROM: domain part
1784 bool Session::verify_sender_domain_(Domain const& sender,
1785 std::string& error_msg)
1787 if (sender.empty()) {
1788 // MAIL FROM:<>
1789 // is used to send bounce messages.
1790 return true;
1793 // Break sender domain into labels:
1795 std::vector<std::string> labels;
1796 boost::algorithm::split(labels, sender.ascii(),
1797 boost::algorithm::is_any_of("."));
1799 if (labels.size() < 2) { // This is not a valid domain.
1800 error_msg = fmt::format("{} invalid syntax", sender.ascii());
1801 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1802 return false;
1805 if (lookup_domain(block_, sender)) {
1806 error_msg = fmt::format("SPF sender domain ({}) is blocked",
1807 spf_sender_domain_.ascii());
1808 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1809 return false;
1812 if (spf_result_ == SPF::Result::PASS) {
1813 if (allow_.contains(spf_sender_domain_.ascii())) {
1814 LOG(INFO) << "sender " << spf_sender_domain_.ascii() << " allowed";
1815 return true;
1818 auto const reg_dom{
1819 tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
1820 if (reg_dom) {
1821 if (allow_.contains(reg_dom)) {
1822 LOG(INFO) << "sender registered domain \"" << reg_dom << "\" allowed";
1823 return true;
1828 LOG(INFO) << "sender \"" << sender << "\" not disallowed";
1829 return true;
1832 void Session::do_spf_check_(Mailbox const& sender)
1834 if (!sock_.has_peername()) {
1835 auto const ip_addr = "127.0.0.1"; // use localhost for local socket
1836 spf_received_ = fmt::format(
1837 "Received-SPF: pass ({}: allow-listed) client-ip={}; "
1838 "envelope-from={}; helo={};",
1839 server_id_(), ip_addr, sender.as_string(), client_identity_.ascii());
1840 spf_sender_domain_ = "localhost";
1841 return;
1844 auto const spf_srv = SPF::Server{server_id_().c_str()};
1845 auto spf_request = SPF::Request{spf_srv};
1847 if (IP4::is_address(sock_.them_c_str())) {
1848 spf_request.set_ipv4_str(sock_.them_c_str());
1850 else if (IP6::is_address(sock_.them_c_str())) {
1851 spf_request.set_ipv6_str(sock_.them_c_str());
1853 else {
1854 LOG(FATAL) << "bogus address " << sock_.them_address_literal() << ", "
1855 << sock_.them_c_str();
1858 auto const from{static_cast<std::string>(sender)};
1860 spf_request.set_env_from(from.c_str());
1861 spf_request.set_helo_dom(client_identity_.ascii().c_str());
1863 auto const spf_res{SPF::Response{spf_request}};
1864 spf_result_ = spf_res.result();
1865 spf_received_ = spf_res.received_spf();
1866 spf_sender_domain_ = spf_request.get_sender_dom();
1868 LOG(INFO) << "spf_received_ == " << spf_received_;
1870 if (spf_result_ == SPF::Result::FAIL) {
1871 LOG(INFO) << "FAIL " << spf_res.header_comment();
1873 else if (spf_result_ == SPF::Result::NEUTRAL) {
1874 LOG(INFO) << "NEUTRAL " << spf_res.header_comment();
1876 else if (spf_result_ == SPF::Result::PASS) {
1877 LOG(INFO) << "PASS " << spf_res.header_comment();
1879 else {
1880 LOG(INFO) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
1884 bool Session::verify_from_params_(parameters_t const& parameters)
1886 // Take a look at the optional parameters:
1887 for (auto const& [name, value] : parameters) {
1888 if (iequal(name, "BODY")) {
1889 if (iequal(value, "8BITMIME")) {
1890 // everything is cool, this is our default...
1892 else if (iequal(value, "7BIT")) {
1893 // nothing to see here, move along...
1895 else if (iequal(value, "BINARYMIME")) {
1896 LOG(INFO) << "using BINARYMIME";
1897 binarymime_ = true;
1899 else {
1900 LOG(WARNING) << "unrecognized BODY type \"" << value << "\" requested";
1903 else if (iequal(name, "SMTPUTF8")) {
1904 if (!value.empty()) {
1905 LOG(WARNING) << "SMTPUTF8 parameter has a value: " << value;
1907 smtputf8_ = true;
1909 else if (iequal(name, "PRDR")) {
1910 LOG(INFO) << "using PRDR";
1911 prdr_ = true;
1914 else if (iequal(name, "SIZE")) {
1915 if (value.empty()) {
1916 LOG(WARNING) << "SIZE parameter has no value.";
1918 else {
1919 try {
1920 auto const sz = stoull(value);
1921 if (sz > max_msg_size()) {
1922 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1923 LOG(WARNING) << "SIZE parameter too large: " << sz;
1924 return false;
1927 catch (std::invalid_argument const& e) {
1928 LOG(WARNING) << "SIZE parameter has invalid value: " << value;
1930 catch (std::out_of_range const& e) {
1931 LOG(WARNING) << "SIZE parameter has out-of-range value: " << value;
1933 // I guess we just ignore bad size parameters.
1936 else if (iequal(name, "REQUIRETLS")) {
1937 if (!sock_.tls()) {
1938 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush;
1939 LOG(WARNING) << "REQUIRETLS needed";
1940 return false;
1943 else {
1944 LOG(WARNING) << "unrecognized 'MAIL FROM' parameter " << name << "="
1945 << value;
1949 return true;
1952 bool Session::verify_rcpt_params_(parameters_t const& parameters)
1954 // Take a look at the optional parameters:
1955 for (auto const& [name, value] : parameters) {
1956 if (iequal(name, "RRVS")) {
1957 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
1958 LOG(INFO) << name << "=" << value;
1960 else {
1961 LOG(WARNING) << "unrecognized 'RCPT TO' parameter " << name << "="
1962 << value;
1966 return true;
1969 // check recipient from RFC5321 RCPT TO:
1970 bool Session::verify_recipient_(Mailbox const& recipient)
1972 if ((recipient.local_part() == "Postmaster") && (recipient.domain() == "")) {
1973 LOG(INFO) << "magic Postmaster address";
1974 return true;
1977 auto const accepted_domain{[this, &recipient] {
1978 if (recipient.domain().is_address_literal()) {
1979 if (recipient.domain() != sock_.us_address_literal()) {
1980 LOG(WARNING) << "recipient.domain address " << recipient.domain()
1981 << " does not match ours " << sock_.us_address_literal();
1983 return false;
1986 return true;
1989 // Domains we accept mail for.
1990 if (accept_domains_.is_open()) {
1991 if (accept_domains_.contains(recipient.domain().ascii()) ||
1992 accept_domains_.contains(recipient.domain().utf8())) {
1993 return true;
1996 else {
1997 // If we have no list of domains to accept, at least take our own.
1998 if (recipient.domain() == server_id_()) {
1999 return true;
2003 return false;
2004 }()};
2006 if (!accepted_domain) {
2007 out_() << "550 5.7.1 relay access denied\r\n" << std::flush;
2008 LOG(WARNING) << "relay access denied for domain " << recipient.domain();
2009 return false;
2012 if (recipient.local_part() == "gene" && client_fcrdns_.size() &&
2013 client_fcrdns_[0].ascii().ends_with("outlook.com")) {
2014 // Getting Spam'ed by MS
2015 if (reverse_path_.empty() || (reverse_path_.length() > 40) ||
2016 reverse_path_.domain().ascii().ends_with(".onmicrosoft.com")) {
2017 std::string error_msg = fmt::format("rejecting spammy message from {}",
2018 client_fcrdns_[0].ascii());
2019 LOG(WARNING) << error_msg;
2020 out_() << "550 5.7.0 " << error_msg << "\r\n" << std::flush;
2021 return false;
2025 // Check for local addresses we reject.
2027 auto bad_recipients_db_name = config_path_ / "bad_recipients";
2028 CDB bad_recipients_db;
2030 std::string loc = recipient.local_part();
2031 std::transform(loc.begin(), loc.end(), loc.begin(),
2032 [](unsigned char c) { return std::tolower(c); });
2034 if (bad_recipients_db.open(bad_recipients_db_name) &&
2035 bad_recipients_db.contains(loc)) {
2036 out_() << "550 5.1.1 bad recipient " << recipient << "\r\n" << std::flush;
2037 LOG(WARNING) << "bad recipient " << recipient;
2038 return false;
2043 auto fail_db_name = config_path_ / "fail_554";
2044 if (fs::exists(fail_db_name)) {
2045 CDB fail_db;
2046 if (fail_db.open(fail_db_name) &&
2047 fail_db.contains(recipient.local_part())) {
2048 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2049 << "\r\n"
2050 << std::flush;
2051 LOG(WARNING) << "fail_554 recipient " << recipient;
2052 return false;
2057 return true;