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