13 #include "MessageStore.hpp"
14 #include "Session.hpp"
17 #include "is_ascii.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>
32 #include <gflags/gflags.h>
34 using namespace std::string_literals
;
44 <https://www.dnswl.org/?page_id=15#query>
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
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
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:
153 char const* uribls[]{
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
,
191 : config_path_(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")};
217 return std::string
{id_from_env
};
219 auto const hostname
{osutil::get_hostname()};
220 if (hostname
.find('.') != std::string::npos
)
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";
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
);
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();
288 max_msg_size(max_msg_size());
290 state_
= xact_step::mail
;
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.
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"
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
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
;
372 out_() << "220 " << server_id_() << " ESMTP faststart - ghsmtp\r\n"
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
);
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());
422 out_() << "250 " << server_id_() << "\r\n";
428 if (sock_
.has_peername()) {
429 out_() << "250-" << server_id_() << " at your service, " << client_
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
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
452 // Check sasl sources for auth types.
453 // out_() << "250-AUTH PLAIN\r\n";
454 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
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();
491 LOG(INFO
) << verb
<< " " << client_identity
<< " from " << client_
;
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");
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_
;
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_
;
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_
;
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_
;
528 if (!verify_from_params_(parameters
)) {
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");
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_
;
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_
;
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_
;
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_
;
591 if (!verify_rcpt_params_(parameters
))
594 if (!verify_recipient_(forward_path
))
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
<< ">";
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
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>
631 return sock_
.tls() ? "UTF8SMTPS" : "UTF8SMTP";
632 else if (sock_
.tls())
634 else if (extensions_
)
640 fmt::memory_buffer headers
;
643 fmt::format_to(std::back_inserter(headers
), "Return-Path: <{}>\r\n",
644 reverse_path_
.as_string());
647 if (!spf_received_
.empty()) {
648 fmt::format_to(std::back_inserter(headers
), "{}\r\n", spf_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
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
);
682 bool lookup_domain(CDB
& cdb
, Domain
const& domain
)
684 if (!domain
.empty()) {
685 if (cdb
.contains(domain
.ascii())) {
688 if (domain
.is_unicode() && cdb
.contains(domain
.utf8())) {
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.
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()));
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
));
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
),
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
)
743 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
746 if (reverse_path
== Mailbox("ietf-smtp-bounces@ietf.org"))
750 std::string_view local_part
;
751 std::string_view folder
;
754 assignment assignments
[] = {
755 {"Emailcore", ".emailcore"},
756 {"bootstrappable", ".bootstrappable"},
757 {"coreboot.org", ".coreboot"},
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"))
785 if (status
== Session::SpamStatus::spam
)
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 ")
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))
806 msg_
= std::make_unique
<MessageStore
>();
808 if (!FLAGS_max_write
)
809 FLAGS_max_write
= max_msg_size();
812 msg_
->open(server_id_(), FLAGS_max_write
,
813 folder(status
, forward_path_
, reverse_path_
));
814 auto const hdrs
{added_headers_(*(msg_
.get()))};
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") << ", "
828 catch (std::system_error
const& e
) {
831 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
832 LOG(ERROR
) << "no space";
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();
846 catch (std::exception
const& e
) {
847 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
848 LOG(ERROR
) << e
.what();
854 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
855 LOG(ERROR
) << "msg_new failed with no exception caught";
861 bool Session::msg_write(char const* s
, std::streamsize count
)
863 if ((state_
!= xact_step::data
) && (state_
!= xact_step::bdat
))
870 if (msg_
->write(s
, count
))
873 catch (std::system_error
const& e
) {
876 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
877 LOG(ERROR
) << "no space";
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();
891 catch (std::exception
const& e
) {
892 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
893 LOG(ERROR
) << e
.what();
899 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
900 LOG(ERROR
) << "msg_write failed with no exception caught";
906 bool Session::data_start()
908 last_in_group_("DATA");
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_
;
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_
;
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_
;
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_
;
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_
;
960 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
962 LOG(WARNING
) << "DATA does not support BINARYMIME";
963 state_
= xact_step::rset
; // RFC 3030 section 3 page 5
968 LOG(ERROR
) << "msg_new() failed";
972 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush
;
977 bool Session::do_deliver_()
985 catch (std::system_error
const& e
) {
988 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
989 LOG(ERROR
) << "no space";
995 out_() << "451 4.3.0 mail system error\r\n" << std::flush
;
997 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
998 LOG(ERROR
) << e
.what();
1005 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
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";
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";
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
;
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";
1087 out_() << "550 5.1.1 some bad recipients\r\n";
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_(), ", "));
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_(), ", "));
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()) {
1119 // Check for and act on magic "wait" address.
1121 using namespace boost::xpressive
;
1123 sregex
const rex
= icase("wait-data-") >> (secs_
= +_d
);
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";
1132 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1133 google::FlushLogFiles(google::INFO
);
1134 out_() << std::flush
;
1136 LOG(INFO
) << "done waiting";
1141 if (!do_deliver_()) {
1145 xfer_response_("DATA");
1147 out_() << std::flush
;
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
;
1158 LOG(WARNING
) << "DATA size error";
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
;
1169 LOG(WARNING
) << "DATA error";
1173 bool Session::bdat_start(size_t n
)
1175 // In practice, this one gets pipelined.
1176 // last_in_group_("BDAT");
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_
;
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_
;
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_
;
1195 case xact_step::data
: // first bdat
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_
;
1205 state_
= xact_step::bdat
;
1210 void Session::bdat_done(size_t n
, bool last
)
1212 if (state_
!= xact_step::bdat
) {
1221 if (msg_
->size_error()) {
1227 out_() << "250 2.0.0 BDAT " << n
<< " OK\r\n" << std::flush
;
1228 LOG(INFO
) << "BDAT " << n
;
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
);
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";
1247 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1248 google::FlushLogFiles(google::INFO
);
1249 out_() << std::flush
;
1251 LOG(INFO
) << "done waiting";
1256 if (!do_deliver_()) {
1260 xfer_response_(fmt::format("BDAT {} LAST", n
));
1262 out_() << std::flush
;
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
;
1273 LOG(WARNING
) << "BDAT size error";
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
;
1284 LOG(WARNING
) << "BDAT sequence error";
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
;
1295 LOG(WARNING
) << "BDAT I/O error";
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";
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
;
1327 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1330 LOG(INFO
) << "HELP" << (str
.length() ? " " : "") << str
;
1333 void Session::quit()
1336 // last_in_group_("QUIT");
1337 out_() << "221 2.0.0 closing connection\r\n" << std::flush
;
1338 LOG(INFO
) << "QUIT";
1342 void Session::auth()
1344 out_() << "454 4.7.0 authentication failure\r\n" << std::flush
;
1345 LOG(INFO
) << "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"
1364 LOG(WARNING
) << n_unrecognized_cmds_
1365 << " unrecognized commands is too many";
1369 out_() << "500 5.5.1 command unrecognized: \"" << escaped
<< "\"\r\n"
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";
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";
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 " : "")
1396 void Session::starttls()
1398 last_in_group_("STARTTLS");
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"
1406 LOG(WARNING
) << "STARTTLS issued without using EHLO";
1409 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush
;
1410 if (sock_
.starttls_server(config_path_
)) {
1412 max_msg_size(Config::max_msg_size_bro
);
1413 LOG(INFO
) << "STARTTLS " << sock_
.tls_info();
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";
1443 if (ip_block
.open(ip_block_db_name
) &&
1444 ip_block
.contains(sock_
.them_c_str())) {
1446 fmt::format("IP address {} on static blocklist", sock_
.them_c_str());
1447 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
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";
1457 client_fcrdns_
.emplace_back("localhost");
1458 client_
= fmt::format("localhost {}", sock_
.them_address_literal());
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";
1470 client_
= sock_
.them_address_literal();
1474 if (!client_fcrdns_
.empty()) {
1475 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1476 sock_
.them_address_literal());
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;
1484 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1486 if (allow_
.contains(tld
)) {
1487 LOG(INFO
) << "FCrDNS registered domain " << tld
<< " allowed";
1488 fcrdns_allowed_
= true;
1494 for (auto const& client_fcrdns
: client_fcrdns_
) {
1495 if (block_
.contains(client_fcrdns
.ascii())) {
1497 fmt::format("FCrDNS {} on static blocklist", client_fcrdns
.ascii());
1498 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1502 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
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
;
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),
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;
1536 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1539 if (regex_match(as, what, rex)) {
1540 auto const x = what[x_].str();
1541 auto const y = what[y_].str();
1544 std::from_chars(y.data(), y.data() + y.size(), value);
1547 LOG(INFO) << "allowed";
1551 LOG(INFO) << "Any A record skips check on block list";
1557 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1558 std::shuffle(std::begin(Config::bls
), std::end(Config::bls
),
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
;
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
;
1593 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1596 LOG(INFO
) << "IP address okay";
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
;
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_
),
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());
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
)) {
1652 LOG(INFO
) << "allow-listed IP address can claim to be "
1657 // Ease up in test mode.
1658 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1662 error_msg
= fmt::format("liar, claimed to be {}", client_identity
.ascii());
1663 out_() << "550 5.7.1 liar\r\n" << std::flush
;
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) {
1672 fmt::format("claimed bogus identity {}", client_identity
.ascii());
1673 out_() << "550 4.7.1 bogus identity\r\n" << std::flush
;
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;
1682 if (lookup_domain(block_
, client_identity
)) {
1684 fmt::format("claimed blocked identity {}", client_identity
.ascii());
1685 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1689 auto const tld
{tld_db_
.get_registered_domain(client_identity
.ascii())};
1691 // Sometimes we may want to look at mail from misconfigured
1693 // LOG(WARNING) << "claimed identity has no registered domain";
1696 else if (block_
.contains(tld
)) {
1698 fmt::format("claimed identity has blocked registered domain {}", tld
);
1699 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush
;
1703 if (domain_blocked(res_
, client_identity
) ||
1704 domain_blocked(res_
, Domain(tld
))) {
1706 fmt::format("claimed identity {} blocked", client_identity
.ascii());
1707 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
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
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()) {
1730 // is used to send bounce messages.
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
;
1740 auto bad_senders_db_name
= config_path_
/ "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
;
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")) {
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;
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();
1776 if (!verify_sender_domain_(sender
.domain(), error_msg
)) {
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()) {
1789 // is used to send bounce messages.
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
;
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
;
1812 if (spf_result_
== SPF::Result::PASS
) {
1813 if (allow_
.contains(spf_sender_domain_
.ascii())) {
1814 LOG(INFO
) << "sender " << spf_sender_domain_
.ascii() << " allowed";
1819 tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
1821 if (allow_
.contains(reg_dom
)) {
1822 LOG(INFO
) << "sender registered domain \"" << reg_dom
<< "\" allowed";
1828 LOG(INFO
) << "sender \"" << sender
<< "\" not disallowed";
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";
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());
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();
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";
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
;
1909 else if (iequal(name
, "PRDR")) {
1910 LOG(INFO
) << "using PRDR";
1914 else if (iequal(name
, "SIZE")) {
1915 if (value
.empty()) {
1916 LOG(WARNING
) << "SIZE parameter has no value.";
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
;
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")) {
1938 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush
;
1939 LOG(WARNING
) << "REQUIRETLS needed";
1944 LOG(WARNING
) << "unrecognized 'MAIL FROM' parameter " << name
<< "="
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
;
1961 LOG(WARNING
) << "unrecognized 'RCPT TO' parameter " << name
<< "="
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";
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();
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())) {
1997 // If we have no list of domains to accept, at least take our own.
1998 if (recipient
.domain() == server_id_()) {
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();
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
;
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
;
2043 auto fail_db_name
= config_path_
/ "fail_554";
2044 if (fs::exists(fail_db_name
)) {
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
2051 LOG(WARNING
) << "fail_554 recipient " << recipient
;