include local-part in message
[ghsmtp.git] / Session.cpp
blob338d93ed1446cacb4b9873bb136b195fe59b451c
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_ = Domain{[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_str)
407 Domain client_identity{client_identity_str};
409 last_in_group_(verb);
410 reset_();
412 if (client_identity_ != client_identity) {
413 client_identity_ = client_identity;
415 std::string error_msg;
416 if (!verify_client_(client_identity_, error_msg)) {
417 LOG(INFO) << "client identity blocked: " << error_msg;
418 bad_host_(error_msg.c_str());
422 if (*verb == 'H') {
423 extensions_ = false;
424 out_() << "250 " << server_id_() << "\r\n";
427 if (*verb == 'E') {
428 extensions_ = true;
430 if (sock_.has_peername()) {
431 out_() << "250-" << server_id_() << " at your service, " << client_
432 << "\r\n";
434 else {
435 out_() << "250-" << server_id_() << "\r\n";
438 // LIMITS SMTP Service Extension,
439 // <https://www.rfc-editor.org/rfc/rfc9422.html>
440 out_() << "250-LIMITS RCPTMAX=" << Config::max_recipients_per_message
441 << "\r\n";
442 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
443 out_() << "250-8BITMIME\r\n"; // RFC 6152
445 if (FLAGS_use_rrvs) {
446 out_() << "250-RRVS\r\n"; // RFC 7293
449 if (FLAGS_use_prdr) {
450 out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
453 if (sock_.tls()) {
454 // Check sasl sources for auth types.
455 // out_() << "250-AUTH PLAIN\r\n";
456 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
458 else {
459 // If we're not already TLS, offer TLS
460 out_() << "250-STARTTLS\r\n"; // RFC 3207
463 out_() << "250-ENHANCEDSTATUSCODES\r\n"; // RFC 2034
465 if (FLAGS_use_pipelining) {
466 out_() << "250-PIPELINING\r\n"; // RFC 2920
469 if (FLAGS_use_binarymime) {
470 out_() << "250-BINARYMIME\r\n"; // RFC 3030
473 if (FLAGS_use_chunking) {
474 out_() << "250-CHUNKING\r\n"; // RFC 3030
477 if (FLAGS_use_smtputf8) {
478 out_() << "250-SMTPUTF8\r\n"; // RFC 6531
481 out_() << "250 HELP\r\n";
484 out_() << std::flush;
486 if (sock_.has_peername()) {
487 if (std::find(begin(client_fcrdns_), end(client_fcrdns_),
488 client_identity_) != end(client_fcrdns_)) {
489 LOG(INFO) << verb << " " << client_identity_ << " from "
490 << sock_.them_address_literal();
492 else {
493 LOG(INFO) << verb << " " << client_identity_ << " from " << client_;
496 else {
497 LOG(INFO) << verb << " " << client_identity_;
501 void Session::mail_from(Mailbox&& reverse_path, parameters_t const& parameters)
503 check_for_pipeline_error_("MAIL FROM");
505 switch (state_) {
506 case xact_step::helo:
507 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
508 LOG(WARNING) << "'MAIL FROM' before HELO/EHLO"
509 << (sock_.has_peername() ? " from " : "") << client_;
510 return;
511 case xact_step::mail: break;
512 case xact_step::rcpt:
513 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
514 LOG(WARNING) << "nested MAIL command"
515 << (sock_.has_peername() ? " from " : "") << client_;
516 return;
517 case xact_step::data:
518 case xact_step::bdat:
519 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush;
520 LOG(WARNING) << "nested MAIL command"
521 << (sock_.has_peername() ? " from " : "") << client_;
522 return;
523 case xact_step::rset:
524 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
525 LOG(WARNING) << "error state must be cleared with a RSET"
526 << (sock_.has_peername() ? " from " : "") << client_;
527 return;
530 if (!verify_from_params_(parameters)) {
531 return;
534 if (!smtputf8_ && !is_ascii(reverse_path.local_part())) {
535 LOG(WARNING) << "non ascii reverse_path \"" << reverse_path
536 << "\" without SMTPUTF8 paramater";
539 std::string error_msg;
540 if (!verify_sender_(reverse_path, error_msg)) {
541 LOG(INFO) << "verify sender failed: " << error_msg;
542 bad_host_(error_msg.c_str());
545 reverse_path_ = std::move(reverse_path);
546 // fwd_path_.clear();
547 // fwd_from_.clear();
548 forward_path_.clear();
549 out_() << "250 2.1.0 MAIL FROM OK\r\n";
550 // No flush RFC-2920 section 3.1, this could be part of a command group.
552 fmt::memory_buffer params;
553 for (auto const& [name, value] : parameters) {
554 fmt::format_to(std::back_inserter(params), " {}", name);
555 if (!value.empty()) {
556 fmt::format_to(std::back_inserter(params), "={}", value);
559 LOG(INFO) << "MAIL FROM:<" << reverse_path_ << ">" << fmt::to_string(params);
561 state_ = xact_step::rcpt;
564 void Session::rcpt_to(Mailbox&& forward_path, parameters_t const& parameters)
566 check_for_pipeline_error_("RCPT TO");
568 switch (state_) {
569 case xact_step::helo:
570 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
571 LOG(WARNING) << "'RCPT TO' before HELO/EHLO"
572 << (sock_.has_peername() ? " from " : "") << client_;
573 return;
574 case xact_step::mail:
575 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
576 LOG(WARNING) << "'RCPT TO' before 'MAIL FROM'"
577 << (sock_.has_peername() ? " from " : "") << client_;
578 return;
579 case xact_step::rcpt:
580 case xact_step::data: break;
581 case xact_step::bdat:
582 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
583 LOG(WARNING) << "'RCPT TO' during BDAT transfer"
584 << (sock_.has_peername() ? " from " : "") << client_;
585 return;
586 case xact_step::rset:
587 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
588 LOG(WARNING) << "error state must be cleared with a RSET"
589 << (sock_.has_peername() ? " from " : "") << client_;
590 return;
593 if (!verify_rcpt_params_(parameters))
594 return;
596 if (!verify_recipient_(forward_path))
597 return;
599 if (!smtputf8_ && !is_ascii(forward_path.local_part())) {
600 LOG(WARNING) << "non ascii forward_path \"" << forward_path
601 << "\" without SMTPUTF8 paramater";
604 if (forward_path_.size() >= Config::max_recipients_per_message) {
605 out_() << "452 4.5.3 too many recipients\r\n" << std::flush;
606 LOG(WARNING) << "too many recipients <" << forward_path << ">";
607 return;
609 // no check for dups, postfix doesn't
610 forward_path_.emplace_back(std::move(forward_path));
612 Mailbox const& rcpt_to_mbx = forward_path_.back();
614 LOG(INFO) << "RCPT TO:<" << rcpt_to_mbx << ">";
616 // No flush RFC-2920 section 3.1, this could be part of a command group.
617 out_() << "250 2.1.5 RCPT TO OK\r\n";
619 state_ = xact_step::data;
622 // The headers Return-Path:, Received-SPF:, and Received: are returned
623 // as a string.
625 std::string Session::added_headers_(MessageStore const& msg)
627 auto const protocol{[this]() {
628 if (sock_.tls() && !extensions_) {
629 LOG(WARNING) << "TLS active without extensions";
631 // <https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#mail-parameters-5>
632 if (smtputf8_)
633 return sock_.tls() ? "UTF8SMTPS" : "UTF8SMTP";
634 else if (sock_.tls())
635 return "ESMTPS";
636 else if (extensions_)
637 return "ESMTP";
638 else
639 return "SMTP";
640 }()};
642 fmt::memory_buffer headers;
644 // Return-Path:
645 fmt::format_to(std::back_inserter(headers), "Return-Path: <{}>\r\n",
646 reverse_path_.as_string());
648 // Received-SPF:
649 if (!spf_received_.empty()) {
650 fmt::format_to(std::back_inserter(headers), "{}\r\n", spf_received_);
653 // Received:
654 // <https://tools.ietf.org/html/rfc5321#section-4.4>
655 fmt::format_to(std::back_inserter(headers), "Received: from {}",
656 client_identity_.utf8());
657 if (sock_.has_peername()) {
658 fmt::format_to(std::back_inserter(headers), " ({})", client_);
660 fmt::format_to(std::back_inserter(headers), "\r\n\tby {} with {} id {}",
661 server_identity_.utf8(), protocol, msg.id().as_string_view());
662 if (forward_path_.size()) {
663 fmt::format_to(std::back_inserter(headers), "\r\n\tfor <{}>",
664 forward_path_[0].as_string());
665 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
666 // “If the FOR clause appears, it MUST contain exactly one <path>
667 // entry, even when multiple RCPT commands have been given. Multiple
668 // <path>s raise some security issues and have been deprecated, see
669 // Section 7.2.”
670 // for (auto i = 1u; i < forward_path_.size(); ++i)
671 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
673 std::string const tls_info{sock_.tls_info()};
674 if (tls_info.length()) {
675 fmt::format_to(std::back_inserter(headers), "\r\n\t({})", tls_info);
677 fmt::format_to(std::back_inserter(headers), ";\r\n\t{}\r\n",
678 msg.when().as_string_view());
680 return fmt::to_string(headers);
683 namespace {
684 bool lookup_domain(CDB& cdb, Domain const& domain)
686 if (!domain.empty()) {
687 if (cdb.contains(domain.ascii())) {
688 return true;
690 if (domain.is_unicode() && cdb.contains(domain.utf8())) {
691 return true;
694 return false;
696 } // namespace
698 std::tuple<Session::SpamStatus, std::string> Session::spam_status_()
700 if (spf_result_ == SPF::Result::FAIL && !ip_allowed_)
701 return {SpamStatus::spam, "SPF failed"};
703 // These should have already been rejected by verify_client_().
704 if ((reverse_path_.domain().ascii() == "localhost.local") ||
705 (reverse_path_.domain().ascii() == "localhost"))
706 return {SpamStatus::spam, "bogus reverse_path"};
708 std::vector<std::string> why_ham;
710 // Anything enciphered tastes a lot like ham.
711 if (sock_.tls())
712 why_ham.emplace_back("they used TLS");
714 if (spf_result_ == SPF::Result::PASS) {
715 if (lookup_domain(allow_, spf_sender_domain_)) {
716 why_ham.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
717 spf_sender_domain_.utf8()));
719 else {
720 auto tld_dom{tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
721 if (tld_dom && allow_.contains(tld_dom)) {
722 why_ham.emplace_back(fmt::format(
723 "SPF sender registered domain ({}) is allowed", tld_dom));
728 if (fcrdns_allowed_)
729 why_ham.emplace_back(
730 fmt::format("FCrDNS (or it's registered domain) is allowed"));
732 if (!why_ham.empty())
733 return {SpamStatus::ham,
734 fmt::format("{}", fmt::join(std::begin(why_ham), std::end(why_ham),
735 ", and "))};
737 return {SpamStatus::spam, "it's not ham"};
740 static std::string folder(Session::SpamStatus status,
741 std::vector<Mailbox> const& forward_path,
742 Mailbox const& reverse_path)
744 if (reverse_path ==
745 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
746 return ".Gmail";
748 if (reverse_path == Mailbox("ietf-smtp-bounces@ietf.org"))
749 return ".smtp";
751 struct assignment {
752 std::string_view local_part;
753 std::string_view folder;
756 assignment assignments[] = {
757 {"bootstrappable", ".bootstrappable"},
758 {"coreboot.org", ".coreboot"},
759 {"dmarc", ".dmarc"},
760 {"dns-privacy", ".dns-privacy"},
761 {"dnsmasq", ".INBOX.DNSmasq"},
762 {"emailcore", ".emailcore"},
763 {"fucking-facebook", ".FB"},
764 {"gene-ebay", ".EBay"},
765 {"i-hate-facebook", ".FB"},
766 {"i-hate-linked-in", ".linkedin"},
767 {"mailop", ".INBOX.mailop"},
768 {"modelfkeyboards.com", ""},
769 {"nest", ".INBOX.Nest"},
770 {"opendmarc-dev", ".dmarc"},
771 {"opendmarc-users", ".dmarc"},
772 {"papasys.com", ".INBOX.PAPA"},
773 {"postmaster-rua", ".INBOX.rua"},
774 {"quic=ietf.org", ".INBOX.quic"},
775 {"shadowserver-reports@digilicious.com", ".INBOX.shadowserver"},
776 {"theatlantic.com", ""},
777 {"time-nutz", ".time-nutz"},
778 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
781 for (auto ass : assignments) {
782 if (iequal(forward_path[0].local_part(), ass.local_part))
783 return std::string(ass.folder);
786 if (iends_with(forward_path[0].local_part(), "-at-duck"))
787 return ".JunkDuck";
789 if (status == Session::SpamStatus::spam)
790 return ".Junk";
792 return "";
795 bool Session::msg_new()
797 CHECK((state_ == xact_step::data) || (state_ == xact_step::bdat));
799 auto const& [status, reason]{spam_status_()};
801 LOG(INFO) << ((status == SpamStatus::ham) ? "ham since " : "spam since ")
802 << reason;
804 // All sources of ham get a fresh 5 minute timeout per message.
805 if (status == SpamStatus::ham) {
806 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr))
807 alarm(5 * 60);
810 msg_ = std::make_unique<MessageStore>();
812 if (!FLAGS_max_write)
813 FLAGS_max_write = max_msg_size();
815 try {
816 msg_->open(server_id_(), FLAGS_max_write,
817 folder(status, forward_path_, reverse_path_));
818 auto const hdrs{added_headers_(*(msg_.get()))};
819 msg_->write(hdrs);
821 // fmt::memory_buffer spam_status;
822 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
823 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
824 // msg_->write(spam_status.data(), spam_status.size());
826 LOG(INFO) << "Spam-Status: "
827 << ((status == SpamStatus::spam) ? "Yes" : "No") << ", "
828 << reason;
830 return true;
832 catch (std::system_error const& e) {
833 switch (errno) {
834 case ENOSPC:
835 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
836 LOG(ERROR) << "no space";
837 msg_->trash();
838 msg_.reset();
839 return false;
841 default:
842 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
843 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
844 LOG(ERROR) << e.what();
845 msg_->trash();
846 msg_.reset();
847 return false;
850 catch (std::exception const& e) {
851 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
852 LOG(ERROR) << e.what();
853 msg_->trash();
854 msg_.reset();
855 return false;
858 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
859 LOG(ERROR) << "msg_new failed with no exception caught";
860 msg_->trash();
861 msg_.reset();
862 return false;
865 bool Session::msg_write(char const* s, std::streamsize count)
867 if ((state_ != xact_step::data) && (state_ != xact_step::bdat))
868 return false;
870 if (!msg_)
871 return false;
873 try {
874 if (msg_->write(s, count))
875 return true;
877 catch (std::system_error const& e) {
878 switch (errno) {
879 case ENOSPC:
880 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
881 LOG(ERROR) << "no space";
882 msg_->trash();
883 msg_.reset();
884 return false;
886 default:
887 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
888 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
889 LOG(ERROR) << e.what();
890 msg_->trash();
891 msg_.reset();
892 return false;
895 catch (std::exception const& e) {
896 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
897 LOG(ERROR) << e.what();
898 msg_->trash();
899 msg_.reset();
900 return false;
903 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
904 LOG(ERROR) << "msg_write failed with no exception caught";
905 msg_->trash();
906 msg_.reset();
907 return false;
910 bool Session::data_start()
912 last_in_group_("DATA");
914 switch (state_) {
915 case xact_step::helo:
916 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
917 LOG(WARNING) << "'DATA' before HELO/EHLO"
918 << (sock_.has_peername() ? " from " : "") << client_;
919 return false;
920 case xact_step::mail:
921 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
922 LOG(WARNING) << "'DATA' before 'MAIL FROM'"
923 << (sock_.has_peername() ? " from " : "") << client_;
924 return false;
925 case xact_step::rcpt:
927 /******************************************************************
928 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
930 The DATA command can fail at only two points in the protocol
931 exchange:
933 If there was no MAIL, or no RCPT, command, or all such commands
934 were rejected, the server MAY return a "command out of sequence"
935 (503) or "no valid recipients" (554) reply in response to the DATA
936 command. If one of those replies (or any other 5yz reply) is
937 received, the client MUST NOT send the message data; more
938 generally, message data MUST NOT be sent unless a 354 reply is
939 received.
941 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
943 The additional restriction is that when there have been no
944 successful RCPT commands in the mail transaction, the DATA command
945 MUST fail with a 503 reply code.
947 Therefore I will send the reply code (503) that is valid for both,
948 and do the same for the BDAT case.
949 *******************************************************************/
951 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
952 LOG(WARNING) << "no valid recipients"
953 << (sock_.has_peername() ? " from " : "") << client_;
954 return false;
955 case xact_step::data: break;
956 case xact_step::bdat:
957 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
958 LOG(WARNING) << "'DATA' during BDAT transfer"
959 << (sock_.has_peername() ? " from " : "") << client_;
960 return false;
961 case xact_step::rset:
962 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
963 LOG(WARNING) << "error state must be cleared with a RSET"
964 << (sock_.has_peername() ? " from " : "") << client_;
965 return false;
968 if (binarymime_) {
969 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
970 << std::flush;
971 LOG(WARNING) << "DATA does not support BINARYMIME";
972 state_ = xact_step::rset; // RFC 3030 section 3 page 5
973 return false;
976 if (!msg_new()) {
977 LOG(ERROR) << "msg_new() failed";
978 return false;
981 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush;
982 LOG(INFO) << "DATA";
983 return true;
986 bool Session::do_deliver_()
988 CHECK(msg_);
990 try {
991 msg_->deliver();
992 msg_->close();
994 catch (std::system_error const& e) {
995 switch (errno) {
996 case ENOSPC:
997 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
998 LOG(ERROR) << "no space";
999 msg_->trash();
1000 reset_();
1001 return false;
1003 default:
1004 out_() << "451 4.3.0 mail system error\r\n" << std::flush;
1005 if (errno)
1006 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
1007 LOG(ERROR) << e.what();
1008 msg_->trash();
1009 reset_();
1010 return false;
1014 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1015 << msg_->id();
1016 return true;
1019 void Session::xfer_response_(std::string_view success_msg)
1021 auto bad_recipients_db_name = config_path_ / "bad_recipients_data";
1022 CDB bad_recipients_db;
1023 if (!bad_recipients_db.open(bad_recipients_db_name)) {
1024 LOG(WARNING) << "can't open bad_recipients_data";
1027 auto temp_fail_db_name = config_path_ / "temp_fail_data";
1028 CDB temp_fail_db;
1029 if (!temp_fail_db.open(temp_fail_db_name)) {
1030 LOG(WARNING) << "can't open temp_fail_data";
1033 std::vector<std::string> bad_recipients;
1034 if (bad_recipients_db.is_open()) {
1035 for (auto fp : forward_path_) {
1036 if (bad_recipients_db.contains_lc(fp.local_part())) {
1037 bad_recipients.push_back(fp);
1038 LOG(WARNING) << "bad recipient " << fp;
1042 std::vector<std::string> temp_failed;
1043 if (temp_fail_db.is_open()) {
1044 for (auto fp : forward_path_) {
1045 if (temp_fail_db.contains_lc(fp.local_part())) {
1046 temp_failed.push_back(fp);
1047 LOG(WARNING) << "temp failed recipient " << fp;
1052 if (prdr_ && forward_path_.size() > 1 &&
1053 (bad_recipients.size() || temp_failed.size())) {
1055 if (forward_path_.size() == bad_recipients.size()) {
1056 out_() << "550 5.1.1 all recipients bad\r\n";
1058 else if (forward_path_.size() == temp_failed.size()) {
1059 out_() << "450 4.1.1 temporary failure for all recipients\r\n";
1061 else {
1062 // this is the mixed situation
1063 out_() << "353 per recipient responses follow:\r\n";
1064 for (auto fp : forward_path_) {
1065 if (bad_recipients_db.is_open() && bad_recipients_db.contains_lc(fp.local_part())) {
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_lc(fp.local_part())) {
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(Domain{"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_lc(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_lc(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 if (identity.is_address_literal()) {
1603 // don't "domain block" address literals
1604 return false;
1606 if (!identity.ascii().empty()) {
1607 Domain lookup{fmt::format("{}.dbl.spamhaus.org", identity.ascii())};
1608 DNS::Query q(res, DNS::RR_type::A, lookup.ascii());
1609 if (q.has_record()) {
1610 const auto a_strings = q.get_strings();
1611 for (auto const& as : a_strings) {
1612 if (istarts_with(as, "127.0.1.")) {
1613 LOG(INFO) << "Domain " << identity << " blocked by spamhaus, " << as;
1614 return true;
1619 return false;
1622 // check the identity from HELO/EHLO
1623 bool Session::verify_client_(Domain const& client_identity,
1624 std::string& error_msg)
1626 if (!client_fcrdns_.empty()) {
1627 if (auto id = std::find(begin(client_fcrdns_), end(client_fcrdns_),
1628 client_identity);
1629 id != end(client_fcrdns_)) {
1630 // If the HELO ident is one of the FCrDNS names...
1631 if (id != begin(client_fcrdns_)) {
1632 // ...then rotate that one to the front of the list
1633 std::rotate(begin(client_fcrdns_), id, id + 1);
1635 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1636 sock_.them_address_literal());
1637 return true;
1639 LOG(INFO) << "claimed identity " << client_identity
1640 << " does NOT match any FCrDNS: ";
1641 for (auto const& client_fcrdns : client_fcrdns_) {
1642 LOG(INFO) << " " << client_fcrdns;
1646 // Bogus clients claim to be us or some local host.
1647 if (sock_.has_peername() &&
1648 ((client_identity == server_identity_) ||
1649 (client_identity.ascii() == "localhost") ||
1650 (client_identity.ascii() == "localhost.localdomain"))) {
1652 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1653 (sock_.them_address_literal() == IP6::loopback_literal)) {
1654 return true;
1657 // Give 'em a pass.
1658 if (ip_allowed_) {
1659 LOG(INFO) << "allow-listed IP address can claim to be "
1660 << client_identity;
1661 return true;
1664 // Ease up in test mode.
1665 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1666 return true;
1669 error_msg = fmt::format("liar, claimed to be {}", client_identity.ascii());
1670 out_() << "550 5.7.1 liar\r\n" << std::flush;
1671 return false;
1674 std::vector<std::string> labels;
1675 boost::algorithm::split(labels, client_identity.ascii(),
1676 boost::algorithm::is_any_of("."));
1677 if (labels.size() < 2) {
1678 error_msg =
1679 fmt::format("claimed bogus identity {}", client_identity.ascii());
1680 out_() << "550 4.7.1 bogus identity\r\n" << std::flush;
1681 return false;
1682 // // Sometimes we may want to look at mail from non conforming
1683 // // sending systems.
1684 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1685 // << client_ << " claiming " << client_identity;
1686 // return true;
1689 if (lookup_domain(block_, client_identity)) {
1690 error_msg =
1691 fmt::format("claimed blocked identity {}", client_identity.ascii());
1692 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1693 return false;
1696 auto const tld{tld_db_.get_registered_domain(client_identity.ascii())};
1697 if (!tld) {
1698 // Sometimes we may want to look at mail from misconfigured
1699 // sending systems.
1700 // LOG(WARNING) << "claimed identity has no registered domain";
1701 // return true;
1703 else if (block_.contains(tld)) {
1704 error_msg =
1705 fmt::format("claimed identity has blocked registered domain {}", tld);
1706 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush;
1707 return false;
1710 if (domain_blocked(res_, client_identity) ||
1711 (tld && domain_blocked(res_, Domain(tld)))) {
1712 error_msg =
1713 fmt::format("claimed identity {} blocked", client_identity.ascii());
1714 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1715 return false;
1718 DNS::Query q(res_, DNS::RR_type::A, client_identity.ascii());
1719 if (!q.has_record()) {
1720 LOG(WARNING) << "claimed identity " << client_identity.ascii()
1721 << " not DNS resolvable";
1724 // not otherwise objectionable
1725 return true;
1728 // check sender from RFC5321 MAIL FROM:
1729 bool Session::verify_sender_(Mailbox const& sender, std::string& error_msg)
1731 do_spf_check_(sender);
1733 std::string const sender_str{sender};
1735 if (sender.empty()) {
1736 // MAIL FROM:<>
1737 // is used to send bounce messages.
1738 return true;
1741 if (domain_blocked(res_, sender.domain())) {
1742 error_msg = fmt::format("{} sender domain blocked", sender_str);
1743 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1744 return false;
1747 auto bad_senders_db_name = config_path_ / "bad_senders";
1748 CDB bad_senders;
1749 if (bad_senders.open(bad_senders_db_name) &&
1750 bad_senders.contains(sender_str)) {
1751 error_msg = fmt::format("{} bad sender", sender_str);
1752 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1753 return false;
1756 // We don't accept mail /from/ a domain we are expecting to accept
1757 // mail for on an external network connection.
1759 // if (sock_.them_address_literal() != sock_.us_address_literal()) {
1760 // if ((accept_domains_.is_open() &&
1761 // (accept_domains_.contains(sender.domain().ascii()) ||
1762 // accept_domains_.contains(sender.domain().utf8()))) ||
1763 // (sender.domain() == server_identity_)) {
1765 // // Ease up in test mode.
1766 // if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1767 // return true;
1768 // }
1769 // out_() << "550 5.7.1 liar\r\n" << std::flush;
1770 // error_msg = fmt::format("liar, claimed to be {}",
1771 // sender.domain().utf8()); return false;
1772 // }
1773 // }
1775 if (sender.domain().is_address_literal()) {
1776 if (sender.domain().ascii() != sock_.them_address_literal()) {
1777 LOG(WARNING) << "sender domain " << sender.domain() << " does not match "
1778 << sock_.them_address_literal();
1780 return true;
1783 if (!verify_sender_domain_(sender.domain(), error_msg)) {
1784 LOG(INFO) << "verify sender domain failed: " << error_msg;
1785 return false;
1788 return true;
1791 // this sender is the RFC5321 MAIL FROM: domain part
1792 bool Session::verify_sender_domain_(Domain const& sender,
1793 std::string& error_msg)
1795 if (sender.empty()) {
1796 // MAIL FROM:<>
1797 // is used to send bounce messages.
1798 return true;
1801 // Break sender domain into labels:
1803 std::vector<std::string> labels;
1804 boost::algorithm::split(labels, sender.ascii(),
1805 boost::algorithm::is_any_of("."));
1807 if (labels.size() < 2) { // This is not a valid domain.
1808 error_msg = fmt::format("{} invalid syntax", sender.ascii());
1809 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1810 return false;
1813 if (lookup_domain(block_, sender)) {
1814 error_msg = fmt::format("SPF sender domain ({}) is blocked",
1815 spf_sender_domain_.ascii());
1816 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1817 return false;
1820 if (spf_result_ == SPF::Result::PASS) {
1821 if (allow_.contains(spf_sender_domain_.ascii())) {
1822 LOG(INFO) << "sender " << spf_sender_domain_.ascii() << " allowed";
1823 return true;
1826 auto const reg_dom{
1827 tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
1828 if (reg_dom) {
1829 if (allow_.contains(reg_dom)) {
1830 LOG(INFO) << "sender registered domain \"" << reg_dom << "\" allowed";
1831 return true;
1836 LOG(INFO) << "sender \"" << sender << "\" not disallowed";
1837 return true;
1840 void Session::do_spf_check_(Mailbox const& sender)
1842 if (!sock_.has_peername()) {
1843 spf_received_ = fmt::format("Received-SPF: pass ({}: allow-listed) "
1844 "client-ip={}; envelope-from={}; helo={};",
1845 server_id_(), "127.0.0.1", sender.as_string(),
1846 client_identity_.ascii());
1847 spf_sender_domain_ = Domain{"localhost"};
1848 return;
1851 auto const spf_srv = SPF::Server{server_id_().c_str()};
1852 auto spf_request = SPF::Request{spf_srv};
1854 if (IP4::is_address(sock_.them_c_str())) {
1855 spf_request.set_ipv4_str(sock_.them_c_str());
1857 else if (IP6::is_address(sock_.them_c_str())) {
1858 spf_request.set_ipv6_str(sock_.them_c_str());
1860 else {
1861 LOG(FATAL) << "bogus address " << sock_.them_address_literal() << ", "
1862 << sock_.them_c_str();
1865 auto const from{static_cast<std::string>(sender)};
1867 spf_request.set_env_from(from.c_str());
1868 spf_request.set_helo_dom(client_identity_.ascii().c_str());
1870 auto const spf_res{SPF::Response{spf_request}};
1871 spf_result_ = spf_res.result();
1872 spf_received_ = spf_res.received_spf();
1873 spf_sender_domain_ = Domain{spf_request.get_sender_dom()};
1875 LOG(INFO) << "spf_received_ == " << spf_received_;
1877 if (spf_result_ == SPF::Result::FAIL) {
1878 LOG(INFO) << "FAIL " << spf_res.header_comment();
1880 else if (spf_result_ == SPF::Result::NEUTRAL) {
1881 LOG(INFO) << "NEUTRAL " << spf_res.header_comment();
1883 else if (spf_result_ == SPF::Result::PASS) {
1884 LOG(INFO) << "PASS " << spf_res.header_comment();
1886 else {
1887 LOG(INFO) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
1891 bool Session::verify_from_params_(parameters_t const& parameters)
1893 // Take a look at the optional parameters:
1894 for (auto const& [name, value] : parameters) {
1895 if (iequal(name, "BODY")) {
1896 if (iequal(value, "8BITMIME")) {
1897 // everything is cool, this is our default...
1899 else if (iequal(value, "7BIT")) {
1900 // nothing to see here, move along...
1902 else if (iequal(value, "BINARYMIME")) {
1903 LOG(INFO) << "using BINARYMIME";
1904 binarymime_ = true;
1906 else {
1907 LOG(WARNING) << "unrecognized BODY type \"" << value << "\" requested";
1910 else if (iequal(name, "SMTPUTF8")) {
1911 if (!value.empty()) {
1912 LOG(WARNING) << "SMTPUTF8 parameter has a value: " << value;
1914 smtputf8_ = true;
1916 else if (iequal(name, "PRDR")) {
1917 LOG(INFO) << "using PRDR";
1918 prdr_ = true;
1921 else if (iequal(name, "SIZE")) {
1922 if (value.empty()) {
1923 LOG(WARNING) << "SIZE parameter has no value.";
1925 else {
1926 try {
1927 auto const sz = stoull(value);
1928 if (sz > max_msg_size()) {
1929 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1930 LOG(WARNING) << "SIZE parameter too large: " << sz;
1931 return false;
1934 catch (std::invalid_argument const& e) {
1935 LOG(WARNING) << "SIZE parameter has invalid value: " << value;
1937 catch (std::out_of_range const& e) {
1938 LOG(WARNING) << "SIZE parameter has out-of-range value: " << value;
1940 // I guess we just ignore bad size parameters.
1943 else if (iequal(name, "REQUIRETLS")) {
1944 if (!sock_.tls()) {
1945 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush;
1946 LOG(WARNING) << "REQUIRETLS needed";
1947 return false;
1950 else {
1951 LOG(WARNING) << "unrecognized 'MAIL FROM' parameter " << name << "="
1952 << value;
1956 return true;
1959 bool Session::verify_rcpt_params_(parameters_t const& parameters)
1961 // Take a look at the optional parameters:
1962 for (auto const& [name, value] : parameters) {
1963 if (iequal(name, "RRVS")) {
1964 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
1965 LOG(INFO) << name << "=" << value;
1967 else {
1968 LOG(WARNING) << "unrecognized 'RCPT TO' parameter " << name << "="
1969 << value;
1973 return true;
1976 // check recipient from RFC5321 RCPT TO:
1977 bool Session::verify_recipient_(Mailbox const& recipient)
1979 if (recipient == Mailbox{"Postmaster"}) {
1980 LOG(INFO) << "magic Postmaster address";
1981 return true;
1984 auto const accepted_domain{[this, &recipient] {
1985 if (recipient.domain().is_address_literal()) {
1986 if (recipient.domain().ascii() != sock_.us_address_literal()) {
1987 LOG(WARNING) << "recipient.domain address " << recipient.domain()
1988 << " does not match ours " << sock_.us_address_literal();
1990 return false;
1993 return true;
1996 // Domains we accept mail for.
1997 if (accept_domains_.is_open()) {
1998 if (accept_domains_.contains(recipient.domain().ascii()) ||
1999 accept_domains_.contains(recipient.domain().utf8())) {
2000 return true;
2003 else {
2004 // If we have no list of domains to accept, at least take our own.
2005 if (recipient.domain().ascii() == server_id_()) {
2006 return true;
2010 return false;
2011 }()};
2013 if (!accepted_domain) {
2014 out_() << "550 5.7.1 relay access denied\r\n" << std::flush;
2015 LOG(WARNING) << "relay access denied for domain " << recipient.domain();
2016 return false;
2019 // if (recipient.local_part() == "gene" && client_fcrdns_.size() &&
2020 // client_fcrdns_[0].ascii().ends_with("outlook.com")) {
2021 // // Getting Spam'ed by MS
2022 // if (reverse_path_.empty() || (reverse_path_.length() > 40) ||
2023 // reverse_path_.domain().ascii().ends_with(".onmicrosoft.com")) {
2024 // std::string error_msg = fmt::format("rejecting spammy message from {}",
2025 // client_fcrdns_[0].ascii());
2026 // LOG(WARNING) << error_msg;
2027 // out_() << "550 5.7.0 " << error_msg << "\r\n" << std::flush;
2028 // return false;
2029 // }
2030 // }
2032 // Check for local addresses we reject.
2034 auto bad_recipients_db_name = config_path_ / "bad_recipients";
2035 CDB bad_recipients_db;
2036 if (bad_recipients_db.open(bad_recipients_db_name) &&
2037 bad_recipients_db.contains_lc(recipient.local_part())) {
2038 out_() << "550 5.1.1 bad recipient " << recipient << "\r\n" << std::flush;
2039 LOG(WARNING) << "bad recipient " << recipient;
2040 return false;
2045 auto fail_db_name = config_path_ / "fail_554";
2046 if (fs::exists(fail_db_name)) {
2047 CDB fail_db;
2048 if (fail_db.open(fail_db_name) &&
2049 fail_db.contains(recipient.local_part())) {
2050 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2051 << "\r\n"
2052 << std::flush;
2053 LOG(WARNING) << "fail_554 recipient " << recipient;
2054 return false;
2059 return true;