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>
24 #include <fmt/ranges.h>
26 #include <boost/algorithm/string/classification.hpp>
27 #include <boost/algorithm/string/split.hpp>
29 #include <boost/xpressive/xpressive.hpp>
33 #include <gflags/gflags.h>
35 using namespace std::string_literals
;
45 <https://www.dnswl.org/?page_id=15#query>
49 The return codes are structured as 127.0.x.y, with “x” indicating the category
50 of an entry and “y” indicating how trustworthy an entry has been judged.
52 Categories (127.0.X.y):
54 2 – Financial services
55 3 – Email Service Providers
56 4 – Organisations (both for-profit [ie companies] and non-profit)
57 5 – Service/network providers
58 6 – Personal/private servers
59 7 – Travel/leisure industry
60 8 – Public sector/governments
61 9 – Media and Tech companies
62 10 – some special cases
63 11 – Education, academic
65 13 – Manufacturing/Industrial
66 14 – Retail/Wholesale/Services
67 15 – Email Marketing Providers
68 20 – Added through Self Service without specific category
70 Trustworthiness / Score (127.0.x.Y):
72 0 = none – only avoid outright blocking (eg large ESP mailservers, -0.1)
73 1 = low – reduce chance of false positives (-1.0)
74 2 = medium – make sure to avoid false positives but allow override for clear
75 cases (-10.0) 3 = high – avoid override (-100.0).
77 The scores in parantheses are typical SpamAssassin scores.
79 Special return code 127.0.0.255
81 In cases where your nameserver issues more than 100’000 queries / 24 hours, you
82 may be blocked from further queries. The return code “127.0.0.255” indicates
88 "b.barracudacentral.org",
89 "sbl-xbl.spamhaus.org",
92 /*** Last octet from A record returned by blocklists ***
94 <https://www.spamhaus.org/faq/section/DNSBL%20Usage#200>
96 Spamhaus uses this general convention for return codes:
98 Return Code Description
99 127.0.0.0/24 Spamhaus IP Blocklists
100 127.0.1.0/24 Spamhaus Domain Blocklists
101 127.0.2.0/24 Spamhaus Zero Reputation Domains list
102 127.255.255.0/24 ERRORS (not implying a "listed" response)
104 Currently used return codes for Spamhaus public IP zones:
106 Return Code Zone Description
107 127.0.0.2 SBL Spamhaus SBL Data
108 127.0.0.3 SBL Spamhaus SBL CSS Data
109 127.0.0.4 XBL CBL Data
110 127.0.0.9 SBL Spamhaus DROP/EDROP Data
111 (in addition to 127.0.0.2, since 01-Jun-2016)
112 127.0.0.10 PBL ISP Maintained
113 127.0.0.11 PBL Spamhaus Maintained
115 127.0.0.5-7 are allocated to XBL for possible future use;
116 127.0.0.8 is allocated to SBL for possible future use.
118 From <https://www.spamhaus.org/faq/section/Spamhaus%20DBL#291>
120 Return Codes Data Source
121 127.0.1.2 spam domain
122 127.0.1.4 phish domain
123 127.0.1.5 malware domain
124 127.0.1.6 botnet C&C domain
125 127.0.1.102 abused legit spam
126 127.0.1.103 abused spammed redirector domain
127 127.0.1.104 abused legit phish
128 127.0.1.105 abused legit malware
129 127.0.1.106 abused legit botnet C&C
130 127.0.1.255 IP queries prohibited!
132 The following special codes indicate an error condition and should not
133 be taken to imply that the queried domain is "listed":
135 Return Code Description
136 127.255.255.252 Typing error in DNSBL name
137 127.255.255.254 Anonymous query through public resolver
138 127.255.255.255 Excessive number of queries
141 From <http://www.surbl.org/lists#multi>
143 last octet indicates which lists it belongs to. The bit positions in
144 that last octet for membership in the different lists are:
154 char const* uribls[]{
160 constexpr auto greeting_wait
= std::chrono::seconds
{6};
161 constexpr int max_recipients_per_message
= 100;
162 constexpr int max_unrecognized_cmds
= 20;
164 // Read timeout value gleaned from RFC-1123 section 5.3.2 and RFC-5321
165 // section 4.5.3.2.7.
166 constexpr auto read_timeout
= std::chrono::minutes
{5};
167 constexpr auto write_timeout
= std::chrono::seconds
{30};
168 } // namespace Config
170 DEFINE_bool(immortal
, false, "don't set process timout");
172 DEFINE_uint64(max_read
, 0, "max data to read");
173 DEFINE_uint64(max_write
, 0, "max data to write");
175 DEFINE_bool(test_mode
, false, "ease up on some checks");
177 DEFINE_bool(use_binarymime
, true, "support BINARYMIME extension, RFC 3030");
178 DEFINE_bool(use_chunking
, true, "support CHUNKING extension, RFC 3030");
179 DEFINE_bool(use_pipelining
, true, "support PIPELINING extension, RFC 2920");
180 DEFINE_bool(use_rrvs
, true, "support RRVS extension, RFC 7293");
181 DEFINE_bool(use_prdr
, true, "support PRDR extension");
182 DEFINE_bool(use_smtputf8
, true, "support SMTPUTF8 extension, RFC 6531");
184 boost::xpressive::mark_tag
secs_(1);
185 boost::xpressive::sregex
const all_rex
= boost::xpressive::icase("wait-all-") >>
186 (secs_
= +boost::xpressive::_d
);
188 Session::Session(fs::path config_path
,
189 std::function
<void(void)> read_hook
,
192 : config_path_(config_path
)
194 , sock_(fd_in
, fd_out
, read_hook
, Config::read_timeout
, Config::write_timeout
)
195 //, send_(config_path, "smtp")
196 //, srs_(config_path)
198 auto accept_db_name
= config_path_
/ "accept_domains";
199 auto allow_db_name
= config_path_
/ "allow";
200 auto block_db_name
= config_path_
/ "block";
201 // auto forward_db_name = config_path_ / "forward";
203 accept_domains_
.open(accept_db_name
);
204 allow_
.open(allow_db_name
);
205 block_
.open(block_db_name
);
206 // forward_.open(forward_db_name);
208 if (sock_
.has_peername() && !IP::is_private(sock_
.us_c_str())) {
209 auto fcrdns
= DNS::fcrdns(res_
, sock_
.us_c_str());
210 for (auto const& fcr
: fcrdns
) {
211 server_fcrdns_
.emplace_back(fcr
);
215 server_identity_
= Domain
{[this] {
216 auto const id_from_env
{getenv("GHSMTP_SERVER_ID")};
218 return std::string
{id_from_env
};
220 auto const hostname
{osutil::get_hostname()};
221 if (hostname
.find('.') != std::string::npos
)
224 if (!server_fcrdns_
.empty()) {
225 // first result should be shortest
226 return server_fcrdns_
.front().ascii();
229 auto const us_c_str
= sock_
.us_c_str();
230 if (us_c_str
&& !IP::is_private(us_c_str
)) {
231 return IP::to_address_literal(us_c_str
);
234 LOG(FATAL
) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
238 // send_.set_sender(server_identity_);
240 max_msg_size(Config::max_msg_size_initial
);
243 void Session::max_msg_size(size_t max
)
245 max_msg_size_
= max
; // number to advertise via RFC 1870
247 if (FLAGS_max_read
) {
248 sock_
.set_max_read(FLAGS_max_read
);
251 auto const overhead
= std::max(max
/ 10, size_t(2048));
252 sock_
.set_max_read(max
+ overhead
);
256 void Session::bad_host_(char const* msg
) const
258 if (sock_
.has_peername()) {
259 // On my systems, this pattern triggers a fail2ban rule that
260 // blocks connections from this IP address on port 25 for a few
261 // days. See <https://www.fail2ban.org/> for more info.
262 syslog(LOG_MAIL
| LOG_WARNING
, "bad host [%s] %s", sock_
.them_c_str(), msg
);
264 std::exit(EXIT_SUCCESS
);
267 void Session::reset_()
269 // RSET does not force another EHLO/HELO, the one piece of per
270 // transaction data saved is client_identity_:
272 // client_identity_.clear(); <-- not cleared!
274 reverse_path_
.clear();
275 forward_path_
.clear();
276 spf_received_
.clear();
277 // fwd_path_.clear();
278 // fwd_from_.clear();
279 // rep_info_.clear();
289 max_msg_size(max_msg_size());
291 state_
= xact_step::mail
;
295 // Return codes from connection establishment are 220 or 554, according
296 // to RFC 5321. That's it.
298 void Session::greeting()
300 CHECK(state_
== xact_step::helo
);
302 if (sock_
.has_peername()) {
303 close(2); // if we're a networked program, never send to stderr
305 std::string error_msg
;
306 if (!verify_ip_address_(error_msg
)) {
307 LOG(INFO
) << error_msg
;
308 bad_host_(error_msg
.c_str());
311 /******************************************************************
312 <https://tools.ietf.org/html/rfc5321#section-4.3.1> says:
314 4.3. Sequencing of Commands and Replies
316 4.3.1. Sequencing Overview
318 The communication between the sender and receiver is an alternating
319 dialogue, controlled by the sender. As such, the sender issues a
320 command and the receiver responds with a reply. Unless other
321 arrangements are negotiated through service extensions, the sender
322 MUST wait for this response before sending further commands. One
323 important reply is the connection greeting. Normally, a receiver
324 will send a 220 "Service ready" reply when the connection is
325 completed. The sender SHOULD wait for this greeting message before
326 sending any commands.
330 “…the receiver responds with a reply.”
331 “…the sender MUST wait for this response…”
332 “One important reply is the connection greeting.”
333 “The sender SHOULD wait for this greeting…”
335 So is it MUST or SHOULD? I enforce MUST.
336 *******************************************************************/
338 // Wait a bit of time for pre-greeting traffic.
339 if (!(ip_allowed_
|| fcrdns_allowed_
)) {
340 if (sock_
.input_ready(Config::greeting_wait
)) {
341 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
342 LOG(INFO
) << "input before any greeting from " << client_
;
343 bad_host_("input before any greeting");
345 // Give a half greeting and wait again.
346 out_() << "220-" << server_id_() << " ESMTP slowstart - ghsmtp\r\n"
348 if (sock_
.input_ready(Config::greeting_wait
)) {
349 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
350 LOG(INFO
) << "input before full greeting from " << client_
;
351 bad_host_("input before full greeting");
354 <https://www.rfc-editor.org/rfc/rfc5321#section-4.2>
356 An SMTP client MUST determine its actions only by the reply code, not
357 by the text (except for the "change of address" 251 and 551 and, if
358 necessary, 220, 221, and 421 replies); in the general case, any text,
359 including no text at all (although senders SHOULD NOT send bare
360 codes), MUST be acceptable. The space (blank) following the reply
361 code is considered part of the text. Whenever possible, a receiver-
362 SMTP SHOULD test the first digit (severity indication) of the reply
365 Except the following chokes a lot of senders:
367 out_() << "220\r\n" << std::flush;
370 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
373 out_() << "220 " << server_id_() << " ESMTP faststart - ghsmtp\r\n"
378 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
381 LOG(INFO
) << "connect from " << client_
;
383 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
384 alarm(2 * 60); // initial alarm
388 void Session::flush() { out_() << std::flush
; }
390 void Session::last_in_group_(std::string_view verb
)
392 if (sock_
.input_ready(std::chrono::seconds(0))) {
393 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
397 void Session::check_for_pipeline_error_(std::string_view verb
)
399 if (!(FLAGS_use_pipelining
&& extensions_
)) {
400 if (sock_
.input_ready(std::chrono::seconds(0))) {
401 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
406 void Session::lo_(char const* verb
, std::string_view client_identity_str
)
408 Domain client_identity
{client_identity_str
};
410 last_in_group_(verb
);
413 if (client_identity_
!= client_identity
) {
414 client_identity_
= client_identity
;
416 std::string error_msg
;
417 if (!verify_client_(client_identity_
, error_msg
)) {
418 LOG(INFO
) << "client identity blocked: " << error_msg
;
419 bad_host_(error_msg
.c_str());
425 out_() << "250 " << server_id_() << "\r\n";
431 if (sock_
.has_peername()) {
432 out_() << "250-" << server_id_() << " at your service, " << client_
436 out_() << "250-" << server_id_() << "\r\n";
439 // LIMITS SMTP Service Extension,
440 // <https://www.rfc-editor.org/rfc/rfc9422.html>
441 out_() << "250-LIMITS RCPTMAX=" << Config::max_recipients_per_message
443 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
444 out_() << "250-8BITMIME\r\n"; // RFC 6152
446 if (FLAGS_use_rrvs
) {
447 out_() << "250-RRVS\r\n"; // RFC 7293
450 if (FLAGS_use_prdr
) {
451 out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
455 // Check sasl sources for auth types.
456 // out_() << "250-AUTH PLAIN\r\n";
457 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
460 // If we're not already TLS, offer TLS
461 out_() << "250-STARTTLS\r\n"; // RFC 3207
464 out_() << "250-ENHANCEDSTATUSCODES\r\n"; // RFC 2034
466 if (FLAGS_use_pipelining
) {
467 out_() << "250-PIPELINING\r\n"; // RFC 2920
470 if (FLAGS_use_binarymime
) {
471 out_() << "250-BINARYMIME\r\n"; // RFC 3030
474 if (FLAGS_use_chunking
) {
475 out_() << "250-CHUNKING\r\n"; // RFC 3030
478 if (FLAGS_use_smtputf8
) {
479 out_() << "250-SMTPUTF8\r\n"; // RFC 6531
482 out_() << "250 HELP\r\n";
485 out_() << std::flush
;
487 if (sock_
.has_peername()) {
488 if (std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
489 client_identity_
) != end(client_fcrdns_
)) {
490 LOG(INFO
) << verb
<< " " << client_identity_
<< " from "
491 << sock_
.them_address_literal();
494 LOG(INFO
) << verb
<< " " << client_identity_
<< " from " << client_
;
498 LOG(INFO
) << verb
<< " " << client_identity_
;
502 void Session::mail_from(Mailbox
&& reverse_path
, parameters_t
const& parameters
)
504 check_for_pipeline_error_("MAIL FROM");
507 case xact_step::helo
:
508 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
509 LOG(WARNING
) << "'MAIL FROM' before HELO/EHLO"
510 << (sock_
.has_peername() ? " from " : "") << client_
;
512 case xact_step::mail
: break;
513 case xact_step::rcpt
:
514 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
515 LOG(WARNING
) << "nested MAIL command"
516 << (sock_
.has_peername() ? " from " : "") << client_
;
518 case xact_step::data
:
519 case xact_step::bdat
:
520 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush
;
521 LOG(WARNING
) << "nested MAIL command"
522 << (sock_
.has_peername() ? " from " : "") << client_
;
524 case xact_step::rset
:
525 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
526 LOG(WARNING
) << "error state must be cleared with a RSET"
527 << (sock_
.has_peername() ? " from " : "") << client_
;
531 if (!verify_from_params_(parameters
)) {
535 if (!smtputf8_
&& !is_ascii(reverse_path
.local_part())) {
536 LOG(WARNING
) << "non ascii reverse_path \"" << reverse_path
537 << "\" without SMTPUTF8 paramater";
540 std::string error_msg
;
541 if (!verify_sender_(reverse_path
, error_msg
)) {
542 LOG(INFO
) << "verify sender failed: " << error_msg
;
543 bad_host_(error_msg
.c_str());
546 reverse_path_
= std::move(reverse_path
);
547 // fwd_path_.clear();
548 // fwd_from_.clear();
549 forward_path_
.clear();
550 out_() << "250 2.1.0 MAIL FROM OK\r\n";
551 // No flush RFC-2920 section 3.1, this could be part of a command group.
553 fmt::memory_buffer params
;
554 for (auto const& [name
, value
] : parameters
) {
555 fmt::format_to(std::back_inserter(params
), " {}", name
);
556 if (!value
.empty()) {
557 fmt::format_to(std::back_inserter(params
), "={}", value
);
560 LOG(INFO
) << "MAIL FROM:<" << reverse_path_
<< ">" << fmt::to_string(params
);
562 state_
= xact_step::rcpt
;
565 void Session::rcpt_to(Mailbox
&& forward_path
, parameters_t
const& parameters
)
567 check_for_pipeline_error_("RCPT TO");
570 case xact_step::helo
:
571 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
572 LOG(WARNING
) << "'RCPT TO' before HELO/EHLO"
573 << (sock_
.has_peername() ? " from " : "") << client_
;
575 case xact_step::mail
:
576 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
577 LOG(WARNING
) << "'RCPT TO' before 'MAIL FROM'"
578 << (sock_
.has_peername() ? " from " : "") << client_
;
580 case xact_step::rcpt
:
581 case xact_step::data
: break;
582 case xact_step::bdat
:
583 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
584 LOG(WARNING
) << "'RCPT TO' during BDAT transfer"
585 << (sock_
.has_peername() ? " from " : "") << client_
;
587 case xact_step::rset
:
588 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
589 LOG(WARNING
) << "error state must be cleared with a RSET"
590 << (sock_
.has_peername() ? " from " : "") << client_
;
594 if (!verify_rcpt_params_(parameters
))
597 if (!verify_recipient_(forward_path
))
600 if (!smtputf8_
&& !is_ascii(forward_path
.local_part())) {
601 LOG(WARNING
) << "non ascii forward_path \"" << forward_path
602 << "\" without SMTPUTF8 paramater";
605 if (forward_path_
.size() >= Config::max_recipients_per_message
) {
606 out_() << "452 4.5.3 too many recipients\r\n" << std::flush
;
607 LOG(WARNING
) << "too many recipients <" << forward_path
<< ">";
610 // no check for dups, postfix doesn't
611 forward_path_
.emplace_back(std::move(forward_path
));
613 Mailbox
const& rcpt_to_mbx
= forward_path_
.back();
615 LOG(INFO
) << "RCPT TO:<" << rcpt_to_mbx
<< ">";
617 // No flush RFC-2920 section 3.1, this could be part of a command group.
618 out_() << "250 2.1.5 RCPT TO OK\r\n";
620 state_
= xact_step::data
;
623 // The headers Return-Path:, Received-SPF:, and Received: are returned
626 std::string
Session::added_headers_(MessageStore
const& msg
)
628 auto const protocol
{[this]() {
629 if (sock_
.tls() && !extensions_
) {
630 LOG(WARNING
) << "TLS active without extensions";
632 // <https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#mail-parameters-5>
634 return sock_
.tls() ? "UTF8SMTPS" : "UTF8SMTP";
635 else if (sock_
.tls())
637 else if (extensions_
)
643 fmt::memory_buffer headers
;
646 fmt::format_to(std::back_inserter(headers
), "Return-Path: <{}>\r\n",
647 reverse_path_
.as_string());
650 if (!spf_received_
.empty()) {
651 fmt::format_to(std::back_inserter(headers
), "{}\r\n", spf_received_
);
655 // <https://tools.ietf.org/html/rfc5321#section-4.4>
656 fmt::format_to(std::back_inserter(headers
), "Received: from {}",
657 client_identity_
.utf8());
658 if (sock_
.has_peername()) {
659 fmt::format_to(std::back_inserter(headers
), " ({})", client_
);
661 fmt::format_to(std::back_inserter(headers
), "\r\n\tby {} with {} id {}",
662 server_identity_
.utf8(), protocol
, msg
.id().as_string_view());
663 if (forward_path_
.size()) {
664 fmt::format_to(std::back_inserter(headers
), "\r\n\tfor <{}>",
665 forward_path_
[0].as_string());
666 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
667 // “If the FOR clause appears, it MUST contain exactly one <path>
668 // entry, even when multiple RCPT commands have been given. Multiple
669 // <path>s raise some security issues and have been deprecated, see
671 // for (auto i = 1u; i < forward_path_.size(); ++i)
672 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
674 std::string
const tls_info
{sock_
.tls_info()};
675 if (tls_info
.length()) {
676 fmt::format_to(std::back_inserter(headers
), "\r\n\t({})", tls_info
);
678 fmt::format_to(std::back_inserter(headers
), ";\r\n\t{}\r\n",
679 msg
.when().as_string_view());
681 return fmt::to_string(headers
);
685 bool lookup_domain(CDB
& cdb
, Domain
const& domain
)
687 if (!domain
.empty()) {
688 if (cdb
.contains(domain
.ascii())) {
691 if (domain
.is_unicode() && cdb
.contains(domain
.utf8())) {
699 std::tuple
<Session::SpamStatus
, std::string
> Session::spam_status_()
701 if (spf_result_
== SPF::Result::FAIL
&& !ip_allowed_
) {
702 LOG(INFO
) << "spam since SPF failed";
703 return {SpamStatus::spam
, "SPF failed"};
706 // These should have already been rejected by verify_client_().
707 if ((reverse_path_
.domain().ascii() == "localhost.local") ||
708 (reverse_path_
.domain().ascii() == "localhost")) {
709 LOG(INFO
) << "spam since reverse path is localhost";
710 return {SpamStatus::spam
, "bogus reverse_path"};
713 std::vector
<std::string
> why_ham
;
715 // Anything enciphered tastes a lot like ham.
717 why_ham
.emplace_back("they used TLS");
719 if (spf_result_
== SPF::Result::PASS
) {
720 if (lookup_domain(allow_
, spf_sender_domain_
)) {
721 why_ham
.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
722 spf_sender_domain_
.utf8()));
725 auto tld_dom
{tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
726 if (tld_dom
&& allow_
.contains(tld_dom
)) {
727 why_ham
.emplace_back(fmt::format(
728 "SPF sender registered domain ({}) is allowed", tld_dom
));
733 LOG(INFO
) << "not ham since SPF not PASS";
736 if (fcrdns_allowed_
) {
737 why_ham
.emplace_back(
738 fmt::format("FCrDNS (or it's registered domain) is allowed"));
741 LOG(INFO
) << "not ham since fcrdns not allowed";
744 if (!why_ham
.empty())
745 return {SpamStatus::ham
,
746 fmt::format("{}", fmt::join(std::begin(why_ham
), std::end(why_ham
),
749 return {SpamStatus::spam
, "it's not ham"};
752 static std::string
folder(Session::SpamStatus status
,
753 std::vector
<Mailbox
> const& forward_path
,
754 Mailbox
const& reverse_path
)
757 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
760 if (reverse_path
== Mailbox("ietf-smtp-bounces@ietf.org"))
764 std::string_view local_part
;
765 std::string_view folder
;
768 assignment assignments
[] = {
769 {"bootstrappable", ".bootstrappable"},
770 {"coreboot.org", ".coreboot"},
772 {"dns-privacy", ".dns-privacy"},
773 {"dnsmasq", ".INBOX.DNSmasq"},
774 {"emailcore", ".emailcore"},
775 {"fucking-facebook", ".FB"},
776 {"gene-ebay", ".EBay"},
777 {"i-hate-facebook", ".FB"},
778 {"i-hate-linked-in", ".linkedin"},
779 {"mailop", ".INBOX.mailop"},
780 {"modelfkeyboards.com", ""},
781 {"nest", ".INBOX.Nest"},
782 {"opendmarc-dev", ".dmarc"},
783 {"opendmarc-users", ".dmarc"},
784 {"papasys.com", ".INBOX.PAPA"},
785 {"postmaster-rua", ".INBOX.rua"},
786 {"quic=ietf.org", ".INBOX.quic"},
787 {"shadowserver-reports@digilicious.com", ".INBOX.shadowserver"},
788 {"theatlantic.com", ""},
789 {"time-nutz", ".time-nutz"},
790 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
793 for (auto ass
: assignments
) {
794 if (iequal(forward_path
[0].local_part(), ass
.local_part
))
795 return std::string(ass
.folder
);
798 if (iends_with(forward_path
[0].local_part(), "-at-duck"))
801 if (status
== Session::SpamStatus::spam
)
807 bool Session::msg_new()
809 CHECK((state_
== xact_step::data
) || (state_
== xact_step::bdat
));
811 auto const& [status
, reason
]{spam_status_()};
813 LOG(INFO
) << ((status
== SpamStatus::ham
) ? "ham since " : "spam since ")
816 // All sources of ham get a fresh 5 minute timeout per message.
817 if (status
== SpamStatus::ham
) {
818 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr))
822 msg_
= std::make_unique
<MessageStore
>();
824 if (!FLAGS_max_write
)
825 FLAGS_max_write
= max_msg_size();
828 msg_
->open(server_id_(), FLAGS_max_write
,
829 folder(status
, forward_path_
, reverse_path_
));
830 auto const hdrs
{added_headers_(*(msg_
.get()))};
833 // fmt::memory_buffer spam_status;
834 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
835 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
836 // msg_->write(spam_status.data(), spam_status.size());
838 LOG(INFO
) << "Spam-Status: "
839 << ((status
== SpamStatus::spam
) ? "Yes" : "No") << ", "
844 catch (std::system_error
const& e
) {
847 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
848 LOG(ERROR
) << "no space";
854 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
855 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
856 LOG(ERROR
) << e
.what();
862 catch (std::exception
const& e
) {
863 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
864 LOG(ERROR
) << e
.what();
870 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
871 LOG(ERROR
) << "msg_new failed with no exception caught";
877 bool Session::msg_write(char const* s
, std::streamsize count
)
879 if ((state_
!= xact_step::data
) && (state_
!= xact_step::bdat
))
886 if (msg_
->write(s
, count
))
889 catch (std::system_error
const& e
) {
892 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
893 LOG(ERROR
) << "no space";
899 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
900 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
901 LOG(ERROR
) << e
.what();
907 catch (std::exception
const& e
) {
908 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
909 LOG(ERROR
) << e
.what();
915 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
916 LOG(ERROR
) << "msg_write failed with no exception caught";
922 bool Session::data_start()
924 last_in_group_("DATA");
927 case xact_step::helo
:
928 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
929 LOG(WARNING
) << "'DATA' before HELO/EHLO"
930 << (sock_
.has_peername() ? " from " : "") << client_
;
932 case xact_step::mail
:
933 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
934 LOG(WARNING
) << "'DATA' before 'MAIL FROM'"
935 << (sock_
.has_peername() ? " from " : "") << client_
;
937 case xact_step::rcpt
:
939 /******************************************************************
940 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
942 The DATA command can fail at only two points in the protocol
945 If there was no MAIL, or no RCPT, command, or all such commands
946 were rejected, the server MAY return a "command out of sequence"
947 (503) or "no valid recipients" (554) reply in response to the DATA
948 command. If one of those replies (or any other 5yz reply) is
949 received, the client MUST NOT send the message data; more
950 generally, message data MUST NOT be sent unless a 354 reply is
953 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
955 The additional restriction is that when there have been no
956 successful RCPT commands in the mail transaction, the DATA command
957 MUST fail with a 503 reply code.
959 Therefore I will send the reply code (503) that is valid for both,
960 and do the same for the BDAT case.
961 *******************************************************************/
963 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
964 LOG(WARNING
) << "no valid recipients"
965 << (sock_
.has_peername() ? " from " : "") << client_
;
967 case xact_step::data
: break;
968 case xact_step::bdat
:
969 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
970 LOG(WARNING
) << "'DATA' during BDAT transfer"
971 << (sock_
.has_peername() ? " from " : "") << client_
;
973 case xact_step::rset
:
974 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
975 LOG(WARNING
) << "error state must be cleared with a RSET"
976 << (sock_
.has_peername() ? " from " : "") << client_
;
981 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
983 LOG(WARNING
) << "DATA does not support BINARYMIME";
984 state_
= xact_step::rset
; // RFC 3030 section 3 page 5
989 LOG(ERROR
) << "msg_new() failed";
993 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush
;
998 bool Session::do_deliver_()
1006 catch (std::system_error
const& e
) {
1009 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
1010 LOG(ERROR
) << "no space";
1016 out_() << "451 4.3.0 mail system error\r\n" << std::flush
;
1018 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
1019 LOG(ERROR
) << e
.what();
1026 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1031 void Session::xfer_response_(std::string_view success_msg
)
1033 auto bad_recipients_db_name
= config_path_
/ "bad_recipients_data";
1034 CDB bad_recipients_db
;
1035 if (!bad_recipients_db
.open(bad_recipients_db_name
)) {
1036 LOG(WARNING
) << "can't open bad_recipients_data";
1039 auto temp_fail_db_name
= config_path_
/ "temp_fail_data";
1041 if (!temp_fail_db
.open(temp_fail_db_name
)) {
1042 LOG(WARNING
) << "can't open temp_fail_data";
1045 std::vector
<std::string
> bad_recipients
;
1046 if (bad_recipients_db
.is_open()) {
1047 for (auto fp
: forward_path_
) {
1048 if (bad_recipients_db
.contains_lc(fp
.local_part())) {
1049 bad_recipients
.push_back(fp
);
1050 LOG(WARNING
) << "bad recipient " << fp
;
1054 std::vector
<std::string
> temp_failed
;
1055 if (temp_fail_db
.is_open()) {
1056 for (auto fp
: forward_path_
) {
1057 if (temp_fail_db
.contains_lc(fp
.local_part())) {
1058 temp_failed
.push_back(fp
);
1059 LOG(WARNING
) << "temp failed recipient " << fp
;
1064 if (prdr_
&& forward_path_
.size() > 1 &&
1065 (bad_recipients
.size() || temp_failed
.size())) {
1067 if (forward_path_
.size() == bad_recipients
.size()) {
1068 out_() << "550 5.1.1 all recipients bad\r\n";
1070 else if (forward_path_
.size() == temp_failed
.size()) {
1071 out_() << "450 4.1.1 temporary failure for all recipients\r\n";
1074 // this is the mixed situation
1075 out_() << "353 per recipient responses follow:\r\n";
1076 for (auto fp
: forward_path_
) {
1077 if (bad_recipients_db
.is_open() && bad_recipients_db
.contains_lc(fp
.local_part())) {
1078 out_() << "550 5.1.1 bad recipient " << fp
<< "\r\n";
1079 LOG(INFO
) << "bad recipient " << fp
;
1081 else if (temp_fail_db
.is_open() && temp_fail_db
.contains_lc(fp
.local_part())) {
1082 out_() << "450 4.1.1 temporary failure for " << fp
<< "\r\n";
1083 LOG(INFO
) << "temp fail for " << fp
;
1086 out_() << "250 2.0.0 success for " << fp
<< "\r\n";
1087 LOG(INFO
) << "success for " << fp
;
1091 // after the per recipient status, a final and I think useless message.
1092 if (forward_path_
.size() > (bad_recipients
.size() + temp_failed
.size())) {
1093 out_() << "250 2.0.0 success for some recipients\r\n";
1095 else if (temp_failed
.size()) {
1096 out_() << "450 4.1.1 temporary failure for some recipients\r\n";
1099 out_() << "550 5.1.1 some bad recipients\r\n";
1104 if (bad_recipients
.size()) {
1105 out_() << "550 5.1.1 bad recipient(s) ";
1106 std::copy(begin(bad_recipients
), end(bad_recipients
),
1107 std::experimental::make_ostream_joiner(out_(), ", "));
1110 else if (temp_failed
.size()) {
1111 out_() << "450 4.1.1 temporary failure for ";
1112 std::copy(begin(temp_failed
), end(temp_failed
),
1113 std::experimental::make_ostream_joiner(out_(), ", "));
1117 out_() << "250 2.0.0 " << success_msg
<< " OK\r\n";
1122 void Session::data_done()
1124 CHECK((state_
== xact_step::data
));
1126 if (msg_
&& msg_
->size_error()) {
1131 // Check for and act on magic "wait" address.
1133 using namespace boost::xpressive
;
1135 sregex
const rex
= icase("wait-data-") >> (secs_
= +_d
);
1138 for (auto fp
: forward_path_
) {
1139 if (regex_match(fp
.local_part(), what
, rex
) ||
1140 regex_match(fp
.local_part(), what
, all_rex
)) {
1141 auto const str
= what
[secs_
].str();
1142 LOG(INFO
) << "waiting at DATA " << str
<< " seconds";
1144 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1145 google::FlushLogFiles(google::INFO
);
1146 out_() << std::flush
;
1148 LOG(INFO
) << "done waiting";
1153 if (!do_deliver_()) {
1157 xfer_response_("DATA");
1159 out_() << std::flush
;
1163 void Session::data_size_error()
1165 out_().clear(); // clear possible eof from input side
1166 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1170 LOG(WARNING
) << "DATA size error";
1174 void Session::data_error()
1176 out_().clear(); // clear possible eof from input side
1177 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush
;
1181 LOG(WARNING
) << "DATA error";
1185 bool Session::bdat_start(size_t n
)
1187 // In practice, this one gets pipelined.
1188 // last_in_group_("BDAT");
1191 case xact_step::helo
:
1192 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
1193 LOG(WARNING
) << "'BDAT' before HELO/EHLO"
1194 << (sock_
.has_peername() ? " from " : "") << client_
;
1196 case xact_step::mail
:
1197 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
1198 LOG(WARNING
) << "'BDAT' before 'MAIL FROM'"
1199 << (sock_
.has_peername() ? " from " : "") << client_
;
1201 case xact_step::rcpt
:
1202 // See comment in data_start()
1203 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1204 LOG(WARNING
) << "no valid recipients"
1205 << (sock_
.has_peername() ? " from " : "") << client_
;
1207 case xact_step::data
: // first bdat
1209 case xact_step::bdat
: return true;
1210 case xact_step::rset
:
1211 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1212 LOG(WARNING
) << "error state must be cleared with a RSET"
1213 << (sock_
.has_peername() ? " from " : "") << client_
;
1217 state_
= xact_step::bdat
;
1222 void Session::bdat_done(size_t n
, bool last
)
1224 if (state_
!= xact_step::bdat
) {
1233 if (msg_
->size_error()) {
1239 out_() << "250 2.0.0 BDAT " << n
<< " OK\r\n" << std::flush
;
1240 LOG(INFO
) << "BDAT " << n
;
1244 LOG(INFO
) << "BDAT " << n
<< " LAST";
1246 // Check for and act on magic "wait" address.
1248 using namespace boost::xpressive
;
1250 sregex
const rex
= icase("wait-bdat-") >> (secs_
= +_d
);
1253 for (auto fp
: forward_path_
) {
1254 if (regex_match(fp
.local_part(), what
, rex
) ||
1255 regex_match(fp
.local_part(), what
, all_rex
)) {
1256 auto const str
= what
[secs_
].str();
1257 LOG(INFO
) << "waiting at BDAT " << str
<< " seconds";
1259 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1260 google::FlushLogFiles(google::INFO
);
1261 out_() << std::flush
;
1263 LOG(INFO
) << "done waiting";
1268 if (!do_deliver_()) {
1272 xfer_response_(fmt::format("BDAT {} LAST", n
));
1274 out_() << std::flush
;
1278 void Session::bdat_size_error()
1280 out_().clear(); // clear possible eof from input side
1281 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1285 LOG(WARNING
) << "BDAT size error";
1289 void Session::bdat_seq_error()
1291 out_().clear(); // clear possible eof from input side
1292 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush
;
1296 LOG(WARNING
) << "BDAT sequence error";
1300 void Session::bdat_io_error()
1302 out_().clear(); // clear possible eof from input side
1303 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush
;
1307 LOG(WARNING
) << "BDAT I/O error";
1311 void Session::rset()
1313 out_() << "250 2.1.5 RSET OK\r\n";
1314 // No flush RFC-2920 section 3.1, this could be part of a command group.
1315 LOG(INFO
) << "RSET";
1319 void Session::noop(std::string_view str
)
1321 last_in_group_("NOOP");
1322 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush
;
1323 LOG(INFO
) << "NOOP" << (str
.length() ? " " : "") << str
;
1326 void Session::vrfy(std::string_view str
)
1328 last_in_group_("VRFY");
1329 out_() << "252 2.1.5 try it\r\n" << std::flush
;
1330 LOG(INFO
) << "VRFY" << (str
.length() ? " " : "") << str
;
1333 void Session::help(std::string_view str
)
1335 if (iequal(str
, "help\r\n")) {
1336 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush
;
1339 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1342 LOG(INFO
) << "HELP" << (str
.length() ? " " : "") << str
;
1345 void Session::quit()
1348 // last_in_group_("QUIT");
1349 out_() << "221 2.0.0 closing connection\r\n" << std::flush
;
1350 LOG(INFO
) << "QUIT";
1354 void Session::auth()
1356 out_() << "454 4.7.0 authentication failure\r\n" << std::flush
;
1357 LOG(INFO
) << "AUTH";
1361 void Session::error(std::string_view log_msg
)
1363 out_() << "421 4.3.5 system error: " << log_msg
<< "\r\n" << std::flush
;
1364 LOG(WARNING
) << log_msg
;
1367 void Session::cmd_unrecognized(std::string_view cmd
)
1369 auto const escaped
{esc(cmd
)};
1370 LOG(WARNING
) << "command unrecognized: \"" << escaped
<< "\"";
1372 if (++n_unrecognized_cmds_
>= Config::max_unrecognized_cmds
) {
1373 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1374 << "\" exceeds limit\r\n"
1376 LOG(WARNING
) << n_unrecognized_cmds_
1377 << " unrecognized commands is too many";
1381 out_() << "500 5.5.1 command unrecognized: \"" << escaped
<< "\"\r\n"
1385 void Session::bare_lf()
1387 // Error code used by Office 365.
1388 out_() << "554 5.6.11 bare LF\r\n" << std::flush
;
1389 LOG(WARNING
) << "bare LF";
1393 void Session::max_out()
1395 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1396 LOG(WARNING
) << "message size maxed out";
1400 void Session::time_out()
1402 out_() << "421 4.4.2 time-out\r\n" << std::flush
;
1403 LOG(WARNING
) << "time-out" << (sock_
.has_peername() ? " from " : "")
1408 void Session::starttls()
1410 last_in_group_("STARTTLS");
1412 out_() << "554 5.5.1 TLS already active\r\n" << std::flush
;
1413 LOG(WARNING
) << "STARTTLS issued with TLS already active";
1415 else if (!extensions_
) {
1416 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1418 LOG(WARNING
) << "STARTTLS issued without using EHLO";
1421 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush
;
1422 if (sock_
.starttls_server(config_path_
)) {
1424 max_msg_size(Config::max_msg_size_bro
);
1425 LOG(INFO
) << "STARTTLS " << sock_
.tls_info();
1428 LOG(INFO
) << "failed STARTTLS";
1433 void Session::exit_()
1435 // sock_.log_totals();
1437 timespec time_used
{};
1438 clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &time_used
);
1440 LOG(INFO
) << "CPU time " << time_used
.tv_sec
<< "." << std::setw(9)
1441 << std::setfill('0') << time_used
.tv_nsec
<< " seconds";
1443 std::exit(EXIT_SUCCESS
);
1446 /////////////////////////////////////////////////////////////////////////////
1448 // All of the verify_* functions send their own error messages back to
1449 // the client on failure, and return false.
1451 bool Session::verify_ip_address_(std::string
& error_msg
)
1453 auto ip_block_db_name
= config_path_
/ "ip-block";
1455 if (ip_block
.open(ip_block_db_name
) &&
1456 ip_block
.contains(sock_
.them_c_str())) {
1458 fmt::format("IP address {} on static blocklist", sock_
.them_c_str());
1459 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1463 client_fcrdns_
.clear();
1465 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1466 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1467 LOG(INFO
) << "loopback address allowed";
1469 client_fcrdns_
.emplace_back(Domain
{"localhost"});
1470 client_
= fmt::format("localhost {}", sock_
.them_address_literal());
1474 auto const fcrdns
= DNS::fcrdns(res_
, sock_
.them_c_str());
1475 for (auto const& fcr
: fcrdns
) {
1476 client_fcrdns_
.emplace_back(fcr
);
1479 if (IP::is_private(sock_
.them_address_literal())) {
1480 LOG(INFO
) << "private address allowed";
1482 client_
= sock_
.them_address_literal();
1486 if (!client_fcrdns_
.empty()) {
1487 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1488 sock_
.them_address_literal());
1490 for (auto const& client_fcrdns
: client_fcrdns_
) {
1491 if (allow_
.contains_lc(client_fcrdns
.ascii())) {
1492 LOG(INFO
) << "FCrDNS " << client_fcrdns
<< " allowed";
1493 fcrdns_allowed_
= true;
1496 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1498 if (allow_
.contains(tld
)) {
1499 LOG(INFO
) << "FCrDNS registered domain " << tld
<< " allowed";
1500 fcrdns_allowed_
= true;
1506 for (auto const& client_fcrdns
: client_fcrdns_
) {
1507 if (block_
.contains_lc(client_fcrdns
.ascii())) {
1509 fmt::format("FCrDNS {} on static blocklist", client_fcrdns
.ascii());
1510 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1514 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1516 if (block_
.contains(tld
)) {
1517 error_msg
= fmt::format(
1518 "FCrDNS registered domain {} on static blocklist", tld
);
1519 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1526 client_
= fmt::format("{}", sock_
.them_address_literal());
1529 if (IP4::is_address(sock_
.them_c_str())) {
1531 auto const reversed
{IP4::reverse(sock_
.them_c_str())};
1534 // Check with allow list.
1535 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1538 for (auto wl : Config::wls) {
1539 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1540 if (q.has_record()) {
1541 using namespace boost::xpressive;
1543 auto const as = q.get_strings()[0];
1544 LOG(INFO) << "on allow list " << wl << " as " << as;
1548 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1551 if (regex_match(as, what, rex)) {
1552 auto const x = what[x_].str();
1553 auto const y = what[y_].str();
1556 std::from_chars(y.data(), y.data() + y.size(), value);
1559 LOG(INFO) << "allowed";
1563 LOG(INFO) << "Any A record skips check on block list";
1569 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1570 std::shuffle(std::begin(Config::bls
), std::end(Config::bls
),
1573 for (auto bl
: Config::bls
) {
1574 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ bl
);
1575 if (q
.has_record()) {
1576 const auto a_strings
= q
.get_strings();
1577 for (auto const& as
: a_strings
) {
1578 LOG(INFO
) << bl
<< " returned " << as
;
1580 for (auto const& as
: a_strings
) {
1581 if (as
== "127.0.0.1") {
1582 LOG(INFO
) << "Should never get 127.0.0.1, from " << bl
;
1584 else if (as
== "127.0.0.10" || as
== "127.0.0.11") {
1585 LOG(INFO
) << "PBL listed, ignoring " << bl
;
1587 else if (as
== "127.255.255.252") {
1588 LOG(INFO
) << "Typing error in DNSBL name " << bl
;
1590 else if (as
== "127.255.255.254") {
1591 LOG(INFO
) << "Anonymous query through public resolver " << bl
;
1593 else if (as
== "127.255.255.255") {
1594 LOG(INFO
) << "Excessive number of queries " << bl
;
1597 error_msg
= fmt::format("IP address {} blocked: {} returned {}",
1598 sock_
.them_c_str(), bl
, as
);
1599 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1605 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1608 LOG(INFO
) << "IP address okay";
1612 bool domain_blocked(DNS::Resolver
& res
, Domain
const& identity
)
1614 if (identity
.is_address_literal()) {
1615 // don't "domain block" address literals
1618 if (!identity
.ascii().empty()) {
1619 Domain lookup
{fmt::format("{}.dbl.spamhaus.org", identity
.ascii())};
1620 DNS::Query
q(res
, DNS::RR_type::A
, lookup
.ascii());
1621 if (q
.has_record()) {
1622 const auto a_strings
= q
.get_strings();
1623 for (auto const& as
: a_strings
) {
1624 if (istarts_with(as
, "127.0.1.")) {
1625 LOG(INFO
) << "Domain " << identity
<< " blocked by spamhaus, " << as
;
1634 // check the identity from HELO/EHLO
1635 bool Session::verify_client_(Domain
const& client_identity
,
1636 std::string
& error_msg
)
1638 if (!client_fcrdns_
.empty()) {
1639 if (auto id
= std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
1641 id
!= end(client_fcrdns_
)) {
1642 // If the HELO ident is one of the FCrDNS names...
1643 if (id
!= begin(client_fcrdns_
)) {
1644 // ...then rotate that one to the front of the list
1645 std::rotate(begin(client_fcrdns_
), id
, id
+ 1);
1647 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1648 sock_
.them_address_literal());
1651 LOG(INFO
) << "claimed identity " << client_identity
1652 << " does NOT match any FCrDNS: ";
1653 for (auto const& client_fcrdns
: client_fcrdns_
) {
1654 LOG(INFO
) << " " << client_fcrdns
;
1658 // Bogus clients claim to be us or some local host.
1659 if (sock_
.has_peername() &&
1660 ((client_identity
== server_identity_
) ||
1661 (client_identity
.ascii() == "localhost") ||
1662 (client_identity
.ascii() == "localhost.localdomain"))) {
1664 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1665 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1671 LOG(INFO
) << "allow-listed IP address can claim to be "
1676 // Ease up in test mode.
1677 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1681 error_msg
= fmt::format("liar, claimed to be {}", client_identity
.ascii());
1682 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1686 std::vector
<std::string
> labels
;
1687 boost::algorithm::split(labels
, client_identity
.ascii(),
1688 boost::algorithm::is_any_of("."));
1689 if (labels
.size() < 2) {
1691 fmt::format("claimed bogus identity {}", client_identity
.ascii());
1692 out_() << "550 4.7.1 bogus identity\r\n" << std::flush
;
1694 // // Sometimes we may want to look at mail from non conforming
1695 // // sending systems.
1696 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1697 // << client_ << " claiming " << client_identity;
1701 if (lookup_domain(block_
, client_identity
)) {
1703 fmt::format("claimed blocked identity {}", client_identity
.ascii());
1704 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1708 auto const tld
{tld_db_
.get_registered_domain(client_identity
.ascii())};
1710 // Sometimes we may want to look at mail from misconfigured
1712 // LOG(WARNING) << "claimed identity has no registered domain";
1715 else if (block_
.contains(tld
)) {
1717 fmt::format("claimed identity has blocked registered domain {}", tld
);
1718 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush
;
1722 if (domain_blocked(res_
, client_identity
) ||
1723 (tld
&& domain_blocked(res_
, Domain(tld
)))) {
1725 fmt::format("claimed identity {} blocked", client_identity
.ascii());
1726 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1730 DNS::Query
q(res_
, DNS::RR_type::A
, client_identity
.ascii());
1731 if (!q
.has_record()) {
1732 LOG(WARNING
) << "claimed identity " << client_identity
.ascii()
1733 << " not DNS resolvable";
1736 // not otherwise objectionable
1740 // check sender from RFC5321 MAIL FROM:
1741 bool Session::verify_sender_(Mailbox
const& sender
, std::string
& error_msg
)
1743 do_spf_check_(sender
);
1745 std::string
const sender_str
{sender
};
1747 if (sender
.empty()) {
1749 // is used to send bounce messages.
1753 if (domain_blocked(res_
, sender
.domain())) {
1754 error_msg
= fmt::format("{} sender domain blocked", sender_str
);
1755 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1759 auto bad_senders_db_name
= config_path_
/ "bad_senders";
1761 if (bad_senders
.open(bad_senders_db_name
) &&
1762 bad_senders
.contains(sender_str
)) {
1763 error_msg
= fmt::format("{} bad sender", sender_str
);
1764 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1768 // We don't accept mail /from/ a domain we are expecting to accept
1769 // mail for on an external network connection.
1771 // if (sock_.them_address_literal() != sock_.us_address_literal()) {
1772 // if ((accept_domains_.is_open() &&
1773 // (accept_domains_.contains(sender.domain().ascii()) ||
1774 // accept_domains_.contains(sender.domain().utf8()))) ||
1775 // (sender.domain() == server_identity_)) {
1777 // // Ease up in test mode.
1778 // if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1781 // out_() << "550 5.7.1 liar\r\n" << std::flush;
1782 // error_msg = fmt::format("liar, claimed to be {}",
1783 // sender.domain().utf8()); return false;
1787 if (sender
.domain().is_address_literal()) {
1788 if (sender
.domain().ascii() != sock_
.them_address_literal()) {
1789 LOG(WARNING
) << "sender domain " << sender
.domain() << " does not match "
1790 << sock_
.them_address_literal();
1795 if (!verify_sender_domain_(sender
.domain(), error_msg
)) {
1796 LOG(INFO
) << "verify sender domain failed: " << error_msg
;
1803 // this sender is the RFC5321 MAIL FROM: domain part
1804 bool Session::verify_sender_domain_(Domain
const& sender
,
1805 std::string
& error_msg
)
1807 if (sender
.empty()) {
1809 // is used to send bounce messages.
1813 // Break sender domain into labels:
1815 std::vector
<std::string
> labels
;
1816 boost::algorithm::split(labels
, sender
.ascii(),
1817 boost::algorithm::is_any_of("."));
1819 if (labels
.size() < 2) { // This is not a valid domain.
1820 error_msg
= fmt::format("{} invalid syntax", sender
.ascii());
1821 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1825 if (lookup_domain(block_
, sender
)) {
1826 error_msg
= fmt::format("SPF sender domain ({}) is blocked",
1827 spf_sender_domain_
.ascii());
1828 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1832 if (spf_result_
== SPF::Result::PASS
) {
1833 if (allow_
.contains(spf_sender_domain_
.ascii())) {
1834 LOG(INFO
) << "sender " << spf_sender_domain_
.ascii() << " allowed";
1839 tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
1841 if (allow_
.contains(reg_dom
)) {
1842 LOG(INFO
) << "sender registered domain \"" << reg_dom
<< "\" allowed";
1848 LOG(INFO
) << "sender \"" << sender
<< "\" not disallowed";
1852 void Session::do_spf_check_(Mailbox
const& sender
)
1854 if (!sock_
.has_peername()) {
1855 spf_received_
= fmt::format("Received-SPF: pass ({}: allow-listed) "
1856 "client-ip={}; envelope-from={}; helo={};",
1857 server_id_(), "127.0.0.1", sender
.as_string(),
1858 client_identity_
.ascii());
1859 spf_sender_domain_
= Domain
{"localhost"};
1863 auto const spf_srv
= SPF::Server
{server_id_().c_str()};
1864 auto spf_request
= SPF::Request
{spf_srv
};
1866 if (IP4::is_address(sock_
.them_c_str())) {
1867 spf_request
.set_ipv4_str(sock_
.them_c_str());
1869 else if (IP6::is_address(sock_
.them_c_str())) {
1870 spf_request
.set_ipv6_str(sock_
.them_c_str());
1873 LOG(FATAL
) << "bogus address " << sock_
.them_address_literal() << ", "
1874 << sock_
.them_c_str();
1877 auto const from
{static_cast<std::string
>(sender
)};
1879 spf_request
.set_env_from(from
.c_str());
1880 spf_request
.set_helo_dom(client_identity_
.ascii().c_str());
1882 auto const spf_res
{SPF::Response
{spf_request
}};
1883 spf_result_
= spf_res
.result();
1884 spf_received_
= spf_res
.received_spf();
1885 spf_sender_domain_
= Domain
{spf_request
.get_sender_dom()};
1887 LOG(INFO
) << "spf_received_ == " << spf_received_
;
1889 if (spf_result_
== SPF::Result::FAIL
) {
1890 LOG(INFO
) << "FAIL " << spf_res
.header_comment();
1892 else if (spf_result_
== SPF::Result::NEUTRAL
) {
1893 LOG(INFO
) << "NEUTRAL " << spf_res
.header_comment();
1895 else if (spf_result_
== SPF::Result::PASS
) {
1896 LOG(INFO
) << "PASS " << spf_res
.header_comment();
1899 LOG(INFO
) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
1903 bool Session::verify_from_params_(parameters_t
const& parameters
)
1905 // Take a look at the optional parameters:
1906 for (auto const& [name
, value
] : parameters
) {
1907 if (iequal(name
, "BODY")) {
1908 if (iequal(value
, "8BITMIME")) {
1909 // everything is cool, this is our default...
1911 else if (iequal(value
, "7BIT")) {
1912 // nothing to see here, move along...
1914 else if (iequal(value
, "BINARYMIME")) {
1915 LOG(INFO
) << "using BINARYMIME";
1919 LOG(WARNING
) << "unrecognized BODY type \"" << value
<< "\" requested";
1922 else if (iequal(name
, "SMTPUTF8")) {
1923 if (!value
.empty()) {
1924 LOG(WARNING
) << "SMTPUTF8 parameter has a value: " << value
;
1928 else if (iequal(name
, "PRDR")) {
1929 LOG(INFO
) << "using PRDR";
1933 else if (iequal(name
, "SIZE")) {
1934 if (value
.empty()) {
1935 LOG(WARNING
) << "SIZE parameter has no value.";
1939 auto const sz
= stoull(value
);
1940 if (sz
> max_msg_size()) {
1941 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1942 LOG(WARNING
) << "SIZE parameter too large: " << sz
;
1946 catch (std::invalid_argument
const& e
) {
1947 LOG(WARNING
) << "SIZE parameter has invalid value: " << value
;
1949 catch (std::out_of_range
const& e
) {
1950 LOG(WARNING
) << "SIZE parameter has out-of-range value: " << value
;
1952 // I guess we just ignore bad size parameters.
1955 else if (iequal(name
, "REQUIRETLS")) {
1957 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush
;
1958 LOG(WARNING
) << "REQUIRETLS needed";
1963 LOG(WARNING
) << "unrecognized 'MAIL FROM' parameter " << name
<< "="
1971 bool Session::verify_rcpt_params_(parameters_t
const& parameters
)
1973 // Take a look at the optional parameters:
1974 for (auto const& [name
, value
] : parameters
) {
1975 if (iequal(name
, "RRVS")) {
1976 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
1977 LOG(INFO
) << name
<< "=" << value
;
1980 LOG(WARNING
) << "unrecognized 'RCPT TO' parameter " << name
<< "="
1988 // check recipient from RFC5321 RCPT TO:
1989 bool Session::verify_recipient_(Mailbox
const& recipient
)
1991 if (recipient
== Mailbox
{"Postmaster"}) {
1992 LOG(INFO
) << "magic Postmaster address";
1996 auto const accepted_domain
{[this, &recipient
] {
1997 if (recipient
.domain().is_address_literal()) {
1998 if (recipient
.domain().ascii() != sock_
.us_address_literal()) {
1999 LOG(WARNING
) << "recipient.domain address " << recipient
.domain()
2000 << " does not match ours " << sock_
.us_address_literal();
2008 // Domains we accept mail for.
2009 if (accept_domains_
.is_open()) {
2010 if (accept_domains_
.contains(recipient
.domain().ascii()) ||
2011 accept_domains_
.contains(recipient
.domain().utf8())) {
2016 // If we have no list of domains to accept, at least take our own.
2017 if (recipient
.domain().ascii() == server_id_()) {
2025 if (!accepted_domain
) {
2026 out_() << "550 5.7.1 relay access denied\r\n" << std::flush
;
2027 LOG(WARNING
) << "relay access denied for domain " << recipient
.domain();
2031 // if (recipient.local_part() == "gene" && client_fcrdns_.size() &&
2032 // client_fcrdns_[0].ascii().ends_with("outlook.com")) {
2033 // // Getting Spam'ed by MS
2034 // if (reverse_path_.empty() || (reverse_path_.length() > 40) ||
2035 // reverse_path_.domain().ascii().ends_with(".onmicrosoft.com")) {
2036 // std::string error_msg = fmt::format("rejecting spammy message from {}",
2037 // client_fcrdns_[0].ascii());
2038 // LOG(WARNING) << error_msg;
2039 // out_() << "550 5.7.0 " << error_msg << "\r\n" << std::flush;
2044 // Check for local addresses we reject.
2046 auto bad_recipients_db_name
= config_path_
/ "bad_recipients";
2047 CDB bad_recipients_db
;
2048 if (bad_recipients_db
.open(bad_recipients_db_name
) &&
2049 bad_recipients_db
.contains_lc(recipient
.local_part())) {
2050 out_() << "550 5.1.1 bad recipient " << recipient
<< "\r\n" << std::flush
;
2051 LOG(WARNING
) << "bad recipient " << recipient
;
2057 auto fail_db_name
= config_path_
/ "fail_554";
2058 if (fs::exists(fail_db_name
)) {
2060 if (fail_db
.open(fail_db_name
) &&
2061 fail_db
.contains(recipient
.local_part())) {
2062 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2065 LOG(WARNING
) << "fail_554 recipient " << recipient
;