13 #include "MessageStore.hpp"
14 #include "Session.hpp"
17 #include "is_ascii.hpp"
20 #include <fmt/format.h>
21 #include <fmt/ostream.h>
23 #include <boost/algorithm/string/classification.hpp>
24 #include <boost/algorithm/string/split.hpp>
26 #include <boost/xpressive/xpressive.hpp>
30 #include <gflags/gflags.h>
32 using namespace std::string_literals
;
42 <https://www.dnswl.org/?page_id=15#query>
46 The return codes are structured as 127.0.x.y, with “x” indicating the category
47 of an entry and “y” indicating how trustworthy an entry has been judged.
49 Categories (127.0.X.y):
51 2 – Financial services
52 3 – Email Service Providers
53 4 – Organisations (both for-profit [ie companies] and non-profit)
54 5 – Service/network providers
55 6 – Personal/private servers
56 7 – Travel/leisure industry
57 8 – Public sector/governments
58 9 – Media and Tech companies
59 10 – some special cases
60 11 – Education, academic
62 13 – Manufacturing/Industrial
63 14 – Retail/Wholesale/Services
64 15 – Email Marketing Providers
65 20 – Added through Self Service without specific category
67 Trustworthiness / Score (127.0.x.Y):
69 0 = none – only avoid outright blocking (eg large ESP mailservers, -0.1)
70 1 = low – reduce chance of false positives (-1.0)
71 2 = medium – make sure to avoid false positives but allow override for clear
72 cases (-10.0) 3 = high – avoid override (-100.0).
74 The scores in parantheses are typical SpamAssassin scores.
76 Special return code 127.0.0.255
78 In cases where your nameserver issues more than 100’000 queries / 24 hours, you
79 may be blocked from further queries. The return code “127.0.0.255” indicates
85 "b.barracudacentral.org",
86 "sbl-xbl.spamhaus.org",
89 /*** Last octet from A record returned by blocklists ***
91 <https://www.spamhaus.org/faq/section/DNSBL%20Usage>
93 Spamhaus uses this general convention for return codes:
95 Return Code Description
96 127.0.0.0/24 Spamhaus IP Blocklists
97 127.0.1.0/24 Spamhaus Domain Blocklists
98 127.0.2.0/24 Spamhaus Zero Reputation Domains list
99 127.255.255.0/24 ERRORS (not implying a "listed" response)
101 Currently used return codes for Spamhaus public IP zones:
103 Return Code Zone Description
104 127.0.0.2 SBL Spamhaus SBL Data
105 127.0.0.3 SBL Spamhaus SBL CSS Data
106 127.0.0.4 XBL CBL Data
107 127.0.0.9 SBL Spamhaus DROP/EDROP Data
108 (in addition to 127.0.0.2, since 01-Jun-2016)
109 127.0.0.10 PBL ISP Maintained
110 127.0.0.11 PBL Spamhaus Maintained
112 127.0.0.5-7 are allocated to XBL for possible future use;
113 127.0.0.8 is allocated to SBL for possible future use.
115 From <https://www.spamhaus.org/faq/section/Spamhaus%20DBL#291>
117 Return Codes Data Source
118 127.0.1.2 spam domain
119 127.0.1.4 phish domain
120 127.0.1.5 malware domain
121 127.0.1.6 botnet C&C domain
122 127.0.1.102 abused legit spam
123 127.0.1.103 abused spammed redirector domain
124 127.0.1.104 abused legit phish
125 127.0.1.105 abused legit malware
126 127.0.1.106 abused legit botnet C&C
127 127.0.1.255 IP queries prohibited!
129 The following special codes indicate an error condition and should not
130 be taken to imply that the queried domain is "listed":
132 Return Code Description
133 127.255.255.252 Typing error in DNSBL name
134 127.255.255.254 Anonymous query through public resolver
135 127.255.255.255 Excessive number of queries
138 From <http://www.surbl.org/lists#multi>
140 last octet indicates which lists it belongs to. The bit positions in
141 that last octet for membership in the different lists are:
151 char const* uribls[]{
157 constexpr auto greeting_wait
= std::chrono::seconds
{6};
158 constexpr int max_recipients_per_message
= 100;
159 constexpr int max_unrecognized_cmds
= 20;
161 // Read timeout value gleaned from RFC-1123 section 5.3.2 and RFC-5321
162 // section 4.5.3.2.7.
163 constexpr auto read_timeout
= std::chrono::minutes
{5};
164 constexpr auto write_timeout
= std::chrono::seconds
{30};
165 } // namespace Config
167 DEFINE_bool(immortal
, false, "don't set process timout");
169 DEFINE_uint64(max_read
, 0, "max data to read");
170 DEFINE_uint64(max_write
, 0, "max data to write");
172 DEFINE_string(selector
, "ghsmtp", "DKIM selector");
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
, false, "support RRVS extension, RFC 7293");
180 DEFINE_bool(use_smtputf8
, true, "support SMTPUTF8 extension, RFC 6531");
182 boost::xpressive::mark_tag
secs_(1);
183 boost::xpressive::sregex
const all_rex
= boost::xpressive::icase("wait-all-") >>
184 (secs_
= +boost::xpressive::_d
);
186 Session::Session(fs::path config_path
,
187 std::function
<void(void)> read_hook
,
190 : config_path_(config_path
)
192 , sock_(fd_in
, fd_out
, read_hook
, Config::read_timeout
, Config::write_timeout
)
193 //, send_(config_path, "smtp")
194 //, srs_(config_path)
196 auto accept_db_name
= config_path_
/ "accept_domains";
197 auto allow_db_name
= config_path_
/ "allow";
198 auto block_db_name
= config_path_
/ "block";
199 auto forward_db_name
= config_path_
/ "forward";
201 accept_domains_
.open(accept_db_name
);
202 allow_
.open(allow_db_name
);
203 block_
.open(block_db_name
);
204 forward_
.open(forward_db_name
);
206 if (sock_
.has_peername() && !IP::is_private(sock_
.us_c_str())) {
207 auto fcrdns
= DNS::fcrdns(res_
, sock_
.us_c_str());
208 for (auto const& fcr
: fcrdns
) {
209 server_fcrdns_
.emplace_back(fcr
);
213 server_identity_
= [this] {
214 auto const id_from_env
{getenv("GHSMTP_SERVER_ID")};
216 return std::string
{id_from_env
};
218 auto const hostname
{osutil::get_hostname()};
219 if (hostname
.find('.') != std::string::npos
)
222 if (!server_fcrdns_
.empty()) {
223 // first result should be shortest
224 return server_fcrdns_
.front().ascii();
227 auto const us_c_str
= sock_
.us_c_str();
228 if (us_c_str
&& !IP::is_private(us_c_str
)) {
229 return IP::to_address_literal(us_c_str
);
232 LOG(FATAL
) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
236 // send_.set_sender(server_identity_);
238 max_msg_size(Config::max_msg_size_initial
);
241 void Session::max_msg_size(size_t max
)
243 max_msg_size_
= max
; // number to advertise via RFC 1870
245 if (FLAGS_max_read
) {
246 sock_
.set_max_read(FLAGS_max_read
);
249 auto const overhead
= std::max(max
/ 10, size_t(2048));
250 sock_
.set_max_read(max
+ overhead
);
254 void Session::bad_host_(char const* msg
) const
256 if (sock_
.has_peername()) {
257 // On my systems, this pattern triggers a fail2ban rule that
258 // blocks connections from this IP address on port 25 for a few
259 // days. See <https://www.fail2ban.org/> for more info.
260 syslog(LOG_MAIL
| LOG_WARNING
, "bad host [%s] %s", sock_
.them_c_str(), msg
);
262 std::exit(EXIT_SUCCESS
);
265 void Session::reset_()
267 // RSET does not force another EHLO/HELO, the one piece of per
268 // transaction data saved is client_identity_:
270 // client_identity_.clear(); <-- not cleared!
272 reverse_path_
.clear();
273 forward_path_
.clear();
274 spf_received_
.clear();
275 // fwd_path_.clear();
276 // fwd_from_.clear();
277 // rep_info_.clear();
287 max_msg_size(max_msg_size());
289 state_
= xact_step::mail
;
293 // Return codes from connection establishment are 220 or 554, according
294 // to RFC 5321. That's it.
296 void Session::greeting()
298 CHECK(state_
== xact_step::helo
);
300 if (sock_
.has_peername()) {
301 close(2); // if we're a networked program, never send to stderr
303 std::string error_msg
;
304 if (!verify_ip_address_(error_msg
)) {
305 LOG(INFO
) << error_msg
;
306 bad_host_(error_msg
.c_str());
309 /******************************************************************
310 <https://tools.ietf.org/html/rfc5321#section-4.3.1> says:
312 4.3. Sequencing of Commands and Replies
314 4.3.1. Sequencing Overview
316 The communication between the sender and receiver is an alternating
317 dialogue, controlled by the sender. As such, the sender issues a
318 command and the receiver responds with a reply. Unless other
319 arrangements are negotiated through service extensions, the sender
320 MUST wait for this response before sending further commands. One
321 important reply is the connection greeting. Normally, a receiver
322 will send a 220 "Service ready" reply when the connection is
323 completed. The sender SHOULD wait for this greeting message before
324 sending any commands.
328 “…the receiver responds with a reply.”
329 “…the sender MUST wait for this response…”
330 “One important reply is the connection greeting.”
331 “The sender SHOULD wait for this greeting…”
333 So is it MUST or SHOULD? I enforce MUST.
334 *******************************************************************/
336 // Wait a bit of time for pre-greeting traffic.
337 if (!(ip_allowed_
|| fcrdns_allowed_
)) {
338 if (sock_
.input_ready(Config::greeting_wait
)) {
339 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
340 LOG(INFO
) << "input before any greeting from " << client_
;
341 bad_host_("input before any greeting");
343 // Give a half greeting and wait again.
344 out_() << "220-" << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
345 if (sock_
.input_ready(Config::greeting_wait
)) {
346 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
347 LOG(INFO
) << "input before full greeting from " << client_
;
348 bad_host_("input before full greeting");
351 LOG(INFO
) << "connect from " << client_
;
354 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
356 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
357 alarm(2 * 60); // initial alarm
361 void Session::flush() { out_() << std::flush
; }
363 void Session::last_in_group_(std::string_view verb
)
365 if (sock_
.input_ready(std::chrono::seconds(0))) {
366 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
370 void Session::check_for_pipeline_error_(std::string_view verb
)
372 if (!(FLAGS_use_pipelining
&& extensions_
)) {
373 if (sock_
.input_ready(std::chrono::seconds(0))) {
374 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
379 void Session::lo_(char const* verb
, std::string_view client_identity
)
381 last_in_group_(verb
);
384 if (client_identity_
!= client_identity
) {
385 client_identity_
= client_identity
;
387 std::string error_msg
;
388 if (!verify_client_(client_identity_
, error_msg
)) {
389 LOG(INFO
) << "client identity blocked: " << error_msg
;
390 bad_host_(error_msg
.c_str());
396 out_() << "250 " << server_id_() << "\r\n";
402 if (sock_
.has_peername()) {
403 out_() << "250-" << server_id_() << " at your service, " << client_
407 out_() << "250-" << server_id_() << "\r\n";
410 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
411 out_() << "250-8BITMIME\r\n"; // RFC 6152
413 if (FLAGS_use_rrvs
) {
414 out_() << "250-RRVS\r\n"; // RFC 7293
417 // out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
420 // Check sasl sources for auth types.
421 // out_() << "250-AUTH PLAIN\r\n";
422 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
425 // If we're not already TLS, offer TLS
426 out_() << "250-STARTTLS\r\n"; // RFC 3207
429 out_() << "250-ENHANCEDSTATUSCODES\r\n"; // RFC 2034
431 if (FLAGS_use_pipelining
) {
432 out_() << "250-PIPELINING\r\n"; // RFC 2920
435 if (FLAGS_use_binarymime
) {
436 out_() << "250-BINARYMIME\r\n"; // RFC 3030
439 if (FLAGS_use_chunking
) {
440 out_() << "250-CHUNKING\r\n"; // RFC 3030
443 if (FLAGS_use_smtputf8
) {
444 out_() << "250-SMTPUTF8\r\n"; // RFC 6531
447 out_() << "250 HELP\r\n";
450 out_() << std::flush
;
452 if (sock_
.has_peername()) {
453 if (std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
454 client_identity_
) != end(client_fcrdns_
)) {
455 LOG(INFO
) << verb
<< " " << client_identity
<< " from "
456 << sock_
.them_address_literal();
459 LOG(INFO
) << verb
<< " " << client_identity
<< " from " << client_
;
463 LOG(INFO
) << verb
<< " " << client_identity
;
467 void Session::mail_from(Mailbox
&& reverse_path
, parameters_t
const& parameters
)
469 check_for_pipeline_error_("MAIL FROM");
472 case xact_step::helo
:
473 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
474 LOG(WARNING
) << "'MAIL FROM' before HELO/EHLO"
475 << (sock_
.has_peername() ? " from " : "") << client_
;
477 case xact_step::mail
: break;
478 case xact_step::rcpt
:
479 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
480 LOG(WARNING
) << "nested MAIL command"
481 << (sock_
.has_peername() ? " from " : "") << client_
;
483 case xact_step::data
:
484 case xact_step::bdat
:
485 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush
;
486 LOG(WARNING
) << "nested MAIL command"
487 << (sock_
.has_peername() ? " from " : "") << client_
;
489 case xact_step::rset
:
490 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
491 LOG(WARNING
) << "error state must be cleared with a RSET"
492 << (sock_
.has_peername() ? " from " : "") << client_
;
496 if (!verify_from_params_(parameters
)) {
500 if (!smtputf8_
&& !is_ascii(reverse_path
.local_part())) {
501 LOG(WARNING
) << "non ascii reverse_path \"" << reverse_path
502 << "\" without SMTPUTF8 paramater";
505 std::string error_msg
;
506 if (!verify_sender_(reverse_path
, error_msg
)) {
507 LOG(INFO
) << "verify sender failed: " << error_msg
;
508 bad_host_(error_msg
.c_str());
511 reverse_path_
= std::move(reverse_path
);
512 // fwd_path_.clear();
513 // fwd_from_.clear();
514 forward_path_
.clear();
515 out_() << "250 2.1.0 MAIL FROM OK\r\n";
516 // No flush RFC-2920 section 3.1, this could be part of a command group.
518 fmt::memory_buffer params
;
519 for (auto const& [name
, value
] : parameters
) {
520 fmt::format_to(std::back_inserter(params
), " {}", name
);
521 if (!value
.empty()) {
522 fmt::format_to(std::back_inserter(params
), "={}", value
);
525 LOG(INFO
) << "MAIL FROM:<" << reverse_path_
<< ">" << fmt::to_string(params
);
527 state_
= xact_step::rcpt
;
530 // bool Session::forward_to_(std::string const& forward, Mailbox const& rcpt_to)
532 // // If we're already forwarding or replying, reject
533 // if (!fwd_path_.empty() || !rep_info_.empty()) {
534 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
537 // LOG(WARNING) << "failed to forward to <" << forward
538 // << "> already forwarding or replying for: " << rcpt_to;
542 // fwd_path_ = Mailbox(forward);
543 // fwd_from_ = rcpt_to;
545 // // New bounce address
546 // Reply::from_to bounce;
547 // bounce.mail_from = reverse_path_.as_string();
549 // auto const new_bounce = srs_.enc_bounce(bounce, server_id_().c_str());
551 // auto const mail_from = Mailbox(new_bounce);
553 // std::string error_msg;
554 // if (!send_.mail_from_rcpt_to(res_, mail_from, fwd_path_, error_msg)) {
555 // out_() << error_msg << std::flush;
556 // LOG(WARNING) << "failed to forward <" << fwd_path_ << "> " << error_msg;
560 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> forwarding to == <" << fwd_path_
565 // bool Session::reply_to_(Reply::from_to const& reply_info, Mailbox const&
568 // // If we're already forwarding or replying, reject
569 // if (!fwd_path_.empty() || !rep_info_.empty()) {
570 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
573 // LOG(WARNING) << "failed to reply to <" << reply_info.mail_from
574 // << "> already forwarding or replying for: " << rcpt_to;
578 // rep_info_ = reply_info;
580 // Mailbox const from(rep_info_.rcpt_to_local_part, server_identity_);
581 // Mailbox const to(rep_info_.mail_from);
583 // std::string error_msg;
584 // if (!send_.mail_from_rcpt_to(res_, from, to, error_msg)) {
585 // out_() << error_msg << std::flush;
586 // LOG(WARNING) << "failed to reply from <" << from << "> to <" << to << ">
592 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> is a reply to "
593 // << rep_info_.mail_from << " from " <<
594 // rep_info_.rcpt_to_local_part;
598 void Session::rcpt_to(Mailbox
&& forward_path
, parameters_t
const& parameters
)
600 check_for_pipeline_error_("RCPT TO");
603 case xact_step::helo
:
604 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
605 LOG(WARNING
) << "'RCPT TO' before HELO/EHLO"
606 << (sock_
.has_peername() ? " from " : "") << client_
;
608 case xact_step::mail
:
609 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
610 LOG(WARNING
) << "'RCPT TO' before 'MAIL FROM'"
611 << (sock_
.has_peername() ? " from " : "") << client_
;
613 case xact_step::rcpt
:
614 case xact_step::data
: break;
615 case xact_step::bdat
:
616 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
617 LOG(WARNING
) << "'RCPT TO' during BDAT transfer"
618 << (sock_
.has_peername() ? " from " : "") << client_
;
620 case xact_step::rset
:
621 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
622 LOG(WARNING
) << "error state must be cleared with a RSET"
623 << (sock_
.has_peername() ? " from " : "") << client_
;
627 if (!verify_rcpt_params_(parameters
))
630 if (!verify_recipient_(forward_path
))
633 if (!smtputf8_
&& !is_ascii(forward_path
.local_part())) {
634 LOG(WARNING
) << "non ascii forward_path \"" << forward_path
635 << "\" without SMTPUTF8 paramater";
638 if (forward_path_
.size() >= Config::max_recipients_per_message
) {
639 out_() << "452 4.5.3 too many recipients\r\n" << std::flush
;
640 LOG(WARNING
) << "too many recipients <" << forward_path
<< ">";
643 // no check for dups, postfix doesn't
644 forward_path_
.emplace_back(std::move(forward_path
));
646 Mailbox
const& rcpt_to_mbx
= forward_path_
.back();
648 LOG(INFO
) << "RCPT TO:<" << rcpt_to_mbx
<< ">";
650 // auto const rcpt_to_str = rcpt_to_mbx.as_string();
652 // if (auto reply = srs_.dec_reply(rcpt_to_mbx.local_part()); reply) {
653 // if (!reply_to_(*reply, rcpt_to_mbx))
656 // else if (auto const forward = forward_.find(rcpt_to_str.c_str()); forward)
658 // if (!forward_to_(*forward, rcpt_to_mbx))
662 // LOG(INFO) << "RCPT TO:<" << rcpt_to_str << ">";
665 // No flush RFC-2920 section 3.1, this could be part of a command group.
666 out_() << "250 2.1.5 RCPT TO OK\r\n";
668 state_
= xact_step::data
;
671 // The headers Return-Path:, Received-SPF:, and Received: are returned
674 std::string
Session::added_headers_(MessageStore
const& msg
)
676 auto const protocol
{[this]() {
677 if (sock_
.tls() && !extensions_
) {
678 LOG(WARNING
) << "TLS active without extensions";
680 // <https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#mail-parameters-5>
682 return sock_
.tls() ? "UTF8SMTPS" : "UTF8SMTP";
683 else if (sock_
.tls())
685 else if (extensions_
)
691 fmt::memory_buffer headers
;
694 fmt::format_to(std::back_inserter(headers
), "Return-Path: <{}>\r\n", reverse_path_
);
697 if (!spf_received_
.empty()) {
698 fmt::format_to(std::back_inserter(headers
), "{}\r\n", spf_received_
);
702 // <https://tools.ietf.org/html/rfc5321#section-4.4>
703 fmt::format_to(std::back_inserter(headers
), "Received: from {}", client_identity_
.utf8());
704 if (sock_
.has_peername()) {
705 fmt::format_to(std::back_inserter(headers
), " ({})", client_
);
707 fmt::format_to(std::back_inserter(headers
), "\r\n\tby {} with {} id {}", server_identity_
.utf8(),
709 if (forward_path_
.size()) {
710 fmt::format_to(std::back_inserter(headers
), "\r\n\tfor <{}>", forward_path_
[0]);
711 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
712 // “If the FOR clause appears, it MUST contain exactly one <path>
713 // entry, even when multiple RCPT commands have been given. Multiple
714 // <path>s raise some security issues and have been deprecated, see
716 // for (auto i = 1u; i < forward_path_.size(); ++i)
717 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
719 std::string
const tls_info
{sock_
.tls_info()};
720 if (tls_info
.length()) {
721 fmt::format_to(std::back_inserter(headers
), "\r\n\t({})", tls_info
);
723 fmt::format_to(std::back_inserter(headers
), ";\r\n\t{}\r\n", msg
.when());
725 return fmt::to_string(headers
);
729 bool lookup_domain(CDB
& cdb
, Domain
const& domain
)
731 if (!domain
.empty()) {
732 if (cdb
.contains(domain
.ascii())) {
735 if (domain
.is_unicode() && cdb
.contains(domain
.utf8())) {
743 std::tuple
<Session::SpamStatus
, std::string
> Session::spam_status_()
745 if (spf_result_
== SPF::Result::FAIL
&& !ip_allowed_
)
746 return {SpamStatus::spam
, "SPF failed"};
748 // These should have already been rejected by verify_client_().
749 if ((reverse_path_
.domain() == "localhost.local") ||
750 (reverse_path_
.domain() == "localhost"))
751 return {SpamStatus::spam
, "bogus reverse_path"};
753 std::vector
<std::string
> why_ham
;
755 // Anything enciphered tastes a lot like ham.
757 why_ham
.emplace_back("they used TLS");
759 if (spf_result_
== SPF::Result::PASS
) {
760 if (lookup_domain(allow_
, spf_sender_domain_
)) {
761 why_ham
.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
762 spf_sender_domain_
.utf8()));
765 auto tld_dom
{tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
766 if (tld_dom
&& allow_
.contains(tld_dom
)) {
767 why_ham
.emplace_back(fmt::format(
768 "SPF sender registered domain ({}) is allowed", tld_dom
));
774 why_ham
.emplace_back(
775 fmt::format("FCrDNS (or it's registered domain) is allowed"));
777 if (!why_ham
.empty())
778 return {SpamStatus::ham
,
779 fmt::format("{}", fmt::join(std::begin(why_ham
), std::end(why_ham
),
782 return {SpamStatus::spam
, "it's not ham"};
785 static std::string
folder(Session::SpamStatus status
,
786 std::vector
<Mailbox
> const& forward_path
,
787 Mailbox
const& reverse_path
)
790 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
793 if (reverse_path
== Mailbox("ietf-smtp-bounces@ietf.org"))
797 std::string_view local_part
;
798 std::string_view folder
;
801 assignment assignments
[] = {
802 {"Emailcore", ".emailcore"},
803 {"bootstrappable", ".bootstrappable"},
804 {"coreboot.org", ".coreboot"},
806 {"dns-privacy", ".dns-privacy"},
807 {"fucking-facebook", ".FB"},
808 {"gene-ebay", ".EBay"},
809 {"i-hate-linked-in", ".linkedin"},
810 {"mailop", ".INBOX.mailop"},
811 {"modelfkeyboards.com", ""},
812 {"nest", ".INBOX.Nest"},
813 {"opendmarc-dev", ".dmarc"},
814 {"opendmarc-users", ".dmarc"},
815 {"theatlantic.com", ""},
816 {"time-nutz", ".time-nutz"},
817 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
820 for (auto ass
: assignments
) {
821 if (forward_path
[0].local_part() == ass
.local_part
)
822 return std::string(ass
.folder
);
825 if (iends_with(forward_path
[0].local_part(), "-at-duck"))
828 if (status
== Session::SpamStatus::spam
)
834 bool Session::msg_new()
836 CHECK((state_
== xact_step::data
) || (state_
== xact_step::bdat
));
838 auto const& [status
, reason
]{spam_status_()};
840 LOG(INFO
) << ((status
== SpamStatus::ham
) ? "ham since " : "spam since ")
843 // All sources of ham get a fresh 5 minute timeout per message.
844 if (status
== SpamStatus::ham
) {
845 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr))
849 msg_
= std::make_unique
<MessageStore
>();
851 if (!FLAGS_max_write
)
852 FLAGS_max_write
= max_msg_size();
855 msg_
->open(server_id_(), FLAGS_max_write
,
856 folder(status
, forward_path_
, reverse_path_
));
857 auto const hdrs
{added_headers_(*(msg_
.get()))};
860 // fmt::memory_buffer spam_status;
861 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
862 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
863 // msg_->write(spam_status.data(), spam_status.size());
865 LOG(INFO
) << "Spam-Status: "
866 << ((status
== SpamStatus::spam
) ? "Yes" : "No") << ", "
871 catch (std::system_error
const& e
) {
874 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
875 LOG(ERROR
) << "no space";
881 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
882 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
883 LOG(ERROR
) << e
.what();
889 catch (std::exception
const& e
) {
890 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
891 LOG(ERROR
) << e
.what();
897 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
898 LOG(ERROR
) << "msg_new failed with no exception caught";
904 bool Session::msg_write(char const* s
, std::streamsize count
)
906 if ((state_
!= xact_step::data
) && (state_
!= xact_step::bdat
))
913 if (msg_
->write(s
, count
))
916 catch (std::system_error
const& e
) {
919 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
920 LOG(ERROR
) << "no space";
926 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
927 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
928 LOG(ERROR
) << e
.what();
934 catch (std::exception
const& e
) {
935 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
936 LOG(ERROR
) << e
.what();
942 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
943 LOG(ERROR
) << "msg_write failed with no exception caught";
949 bool Session::data_start()
951 last_in_group_("DATA");
954 case xact_step::helo
:
955 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
956 LOG(WARNING
) << "'DATA' before HELO/EHLO"
957 << (sock_
.has_peername() ? " from " : "") << client_
;
959 case xact_step::mail
:
960 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
961 LOG(WARNING
) << "'DATA' before 'MAIL FROM'"
962 << (sock_
.has_peername() ? " from " : "") << client_
;
964 case xact_step::rcpt
:
966 /******************************************************************
967 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
969 The DATA command can fail at only two points in the protocol exchange:
971 If there was no MAIL, or no RCPT, command, or all such commands were
972 rejected, the server MAY return a "command out of sequence" (503) or
973 "no valid recipients" (554) reply in response to the DATA command.
975 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
977 The additional restriction is that when there have been no successful
978 RCPT commands in the mail transaction, the DATA command MUST fail
979 with a 503 reply code.
981 Therefore I will send the reply code that is valid for both, and
982 do the same for the BDAT case.
983 *******************************************************************/
985 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
986 LOG(WARNING
) << "no valid recipients"
987 << (sock_
.has_peername() ? " from " : "") << client_
;
989 case xact_step::data
: break;
990 case xact_step::bdat
:
991 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
992 LOG(WARNING
) << "'DATA' during BDAT transfer"
993 << (sock_
.has_peername() ? " from " : "") << client_
;
995 case xact_step::rset
:
996 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
997 LOG(WARNING
) << "error state must be cleared with a RSET"
998 << (sock_
.has_peername() ? " from " : "") << client_
;
1003 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
1005 LOG(WARNING
) << "DATA does not support BINARYMIME";
1006 state_
= xact_step::rset
; // RFC 3030 section 3 page 5
1011 LOG(ERROR
) << "msg_new() failed";
1015 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush
;
1016 LOG(INFO
) << "DATA";
1020 // bool Session::do_forward_(message::parsed& msg)
1022 // auto msg_fwd = msg;
1024 // // Generate a reply address
1025 // Reply::from_to reply;
1026 // reply.mail_from = msg_fwd.dmarc_from;
1027 // reply.rcpt_to_local_part = fwd_from_.local_part();
1029 // auto const reply_addr =
1030 // fmt::format("{}@{}", srs_.enc_reply(reply), server_id_());
1032 // auto const munging = false;
1034 // auto const sender = server_identity_.ascii().c_str();
1035 // auto const selector = FLAGS_selector.c_str();
1036 // auto const key_file =
1037 // (config_path_ / FLAGS_selector).replace_extension("private");
1038 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1041 // auto const from_hdr =
1042 // fmt::format("From: \"{} via\" <@>", msg_fwd.dmarc_from, reply_addr);
1043 // message::rewrite_from_to(msg_fwd, from_hdr, "", sender, selector,
1047 // auto const reply_to_hdr = fmt::format("Reply-To: {}", reply_addr);
1048 // message::rewrite_from_to(msg_fwd, "", reply_to_hdr, sender, selector,
1053 // if (!send_.send(msg_fwd.as_string())) {
1054 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1058 // LOG(ERROR) << "failed to send for " << fwd_path_;
1062 // LOG(INFO) << "successfully sent for " << fwd_path_;
1066 // bool Session::do_reply_(message::parsed& msg)
1068 // Mailbox to_mbx(rep_info_.mail_from);
1069 // Mailbox from_mbx(rep_info_.rcpt_to_local_part, server_identity_);
1071 // auto reply = std::make_unique<MessageStore>();
1072 // reply->open(server_id_(), FLAGS_max_write, ".Drafts");
1074 // auto const date{Now{}};
1075 // auto const pill{Pill{}};
1076 // auto const mid_str =
1077 // fmt::format("<{}.{}@{}>", date.sec(), pill, server_identity_);
1079 // fmt::memory_buffer bfr;
1081 // fmt::format_to(bfr, "From: <{}>\r\n", from_mbx);
1082 // fmt::format_to(bfr, "To: <{}>\r\n", to_mbx);
1084 // fmt::format_to(bfr, "Date: {}\r\n", date.c_str());
1086 // fmt::format_to(bfr, "Message-ID: {}\r\n", mid_str.c_str());
1088 // if (!msg.get_header(message::Subject).empty()) {
1089 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1090 // msg.get_header(message::Subject));
1093 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1094 // "Reply to your message");
1097 // if (!msg.get_header(message::In_Reply_To).empty()) {
1098 // fmt::format_to(bfr, "{}: {}\r\n", message::In_Reply_To,
1099 // msg.get_header(message::In_Reply_To));
1102 // if (!msg.get_header(message::MIME_Version).empty() &&
1103 // msg.get_header(message::Content_Type).empty()) {
1104 // fmt::format_to(bfr, "{}: {}\r\n", message::MIME_Version,
1105 // msg.get_header(message::MIME_Version));
1106 // fmt::format_to(bfr, "{}: {}\r\n", message::Content_Type,
1107 // msg.get_header(message::Content_Type));
1110 // reply->write(fmt::to_string(bfr));
1112 // if (!msg.body.empty()) {
1113 // reply->write("\r\n");
1114 // reply->write(msg.body);
1117 // auto const msg_data = reply->freeze();
1118 // message::parsed msg_reply;
1119 // CHECK(msg_reply.parse(msg_data));
1121 // auto const sender = server_identity_.ascii().c_str();
1122 // auto const selector = FLAGS_selector.c_str();
1123 // auto const key_file =
1124 // (config_path_ / FLAGS_selector).replace_extension("private");
1125 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1127 // message::dkim_sign(msg_reply, sender, selector, key_file);
1129 // if (!send_.send(msg_reply.as_string())) {
1130 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1134 // LOG(ERROR) << "send failed for reply to " << to_mbx << " from " <<
1135 // from_mbx; return false;
1138 // LOG(INFO) << "successful reply to " << to_mbx << " from " << from_mbx;
1142 bool Session::do_deliver_()
1146 // auto const sender = server_identity_.ascii().c_str();
1147 // auto const selector = FLAGS_selector.c_str();
1148 // auto const key_file =
1149 // (config_path_ / FLAGS_selector).replace_extension("private");
1150 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1153 // auto const msg_data = msg_->freeze();
1155 // message::parsed msg;
1157 // // Only deal in RFC-5322 Mail Objects.
1158 // bool const message_parsed = msg.parse(msg_data);
1159 // if (message_parsed) {
1161 // // remove any Return-Path
1162 // message::remove_delivery_headers(msg);
1164 // auto const authentic =
1165 // message_parsed &&
1166 // message::authentication(msg, sender, selector, key_file);
1168 // // write a new Return-Path
1169 // msg_->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_));
1171 // for (auto const h : msg.headers) {
1172 // msg_->write(h.as_string());
1173 // msg_->write("\r\n");
1175 // if (!msg.body.empty()) {
1176 // msg_->write("\r\n");
1177 // msg_->write(msg.body);
1182 // if (authentic && !fwd_path_.empty()) {
1183 // if (!do_forward_(msg))
1186 // if (authentic && !rep_info_.empty()) {
1187 // if (!do_reply_(msg))
1194 catch (std::system_error
const& e
) {
1197 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
1198 LOG(ERROR
) << "no space";
1204 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
1206 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
1207 LOG(ERROR
) << e
.what();
1217 void Session::data_done()
1219 CHECK((state_
== xact_step::data
));
1221 if (msg_
&& msg_
->size_error()) {
1227 // out_() << "353\r\n";
1228 // for (auto fp : forward_path_) {
1229 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1233 // Check for and act on magic "wait" address.
1235 using namespace boost::xpressive
;
1237 sregex
const rex
= icase("wait-data-") >> (secs_
= +_d
);
1240 for (auto fp
: forward_path_
) {
1241 if (regex_match(fp
.local_part(), what
, rex
) ||
1242 regex_match(fp
.local_part(), what
, all_rex
)) {
1243 auto const str
= what
[secs_
].str();
1244 LOG(INFO
) << "waiting at DATA " << str
<< " seconds";
1246 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1247 google::FlushLogFiles(google::INFO
);
1248 out_() << std::flush
;
1250 LOG(INFO
) << "done waiting";
1255 if (do_deliver_()) {
1256 auto temp_fail_db_name
= config_path_
/ "temp_fail_data";
1259 for (auto fp
: forward_path_
) {
1260 if (temp_fail
.open(temp_fail_db_name
) &&
1261 temp_fail
.contains(fp
.local_part())) {
1262 out_() << "450 4.2.2 Mailbox full.\r\n" << std::flush
;
1263 LOG(WARNING
) << "temp fail at DATA for recipient " << fp
;
1270 // Check for addresses we reject after data.
1272 auto bad_recipients_db_name
= config_path_
/ "bad_recipients_data";
1273 CDB bad_recipients_db
;
1274 if (bad_recipients_db
.open(bad_recipients_db_name
)) {
1275 for (auto fp
: forward_path_
) {
1276 if (bad_recipients_db
.contains(fp
.local_part())) {
1277 out_() << "550 5.1.1 bad recipient " << fp
<< "\r\n" << std::flush
;
1278 LOG(WARNING
) << "bad recipient " << fp
;
1283 LOG(INFO
) << "unbad recipient " << fp
.local_part();
1288 LOG(WARNING
) << "can't open bad_recipients_data";
1292 out_() << "250 2.0.0 DATA OK\r\n" << std::flush
;
1293 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1298 void Session::data_size_error()
1300 out_().clear(); // clear possible eof from input side
1301 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1305 LOG(WARNING
) << "DATA size error";
1309 void Session::data_error()
1311 out_().clear(); // clear possible eof from input side
1312 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush
;
1316 LOG(WARNING
) << "DATA error";
1320 bool Session::bdat_start(size_t n
)
1322 // In practice, this one gets pipelined.
1323 // last_in_group_("BDAT");
1326 case xact_step::helo
:
1327 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
1328 LOG(WARNING
) << "'BDAT' before HELO/EHLO"
1329 << (sock_
.has_peername() ? " from " : "") << client_
;
1331 case xact_step::mail
:
1332 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
1333 LOG(WARNING
) << "'BDAT' before 'MAIL FROM'"
1334 << (sock_
.has_peername() ? " from " : "") << client_
;
1336 case xact_step::rcpt
:
1337 // See comment in data_start()
1338 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1339 LOG(WARNING
) << "no valid recipients"
1340 << (sock_
.has_peername() ? " from " : "") << client_
;
1342 case xact_step::data
: // first bdat
1344 case xact_step::bdat
: return true;
1345 case xact_step::rset
:
1346 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1347 LOG(WARNING
) << "error state must be cleared with a RSET"
1348 << (sock_
.has_peername() ? " from " : "") << client_
;
1352 state_
= xact_step::bdat
;
1357 void Session::bdat_done(size_t n
, bool last
)
1359 if (state_
!= xact_step::bdat
) {
1368 if (msg_
->size_error()) {
1374 out_() << "250 2.0.0 BDAT " << n
<< " OK\r\n" << std::flush
;
1375 LOG(INFO
) << "BDAT " << n
;
1379 // Check for and act on magic "wait" address.
1381 using namespace boost::xpressive
;
1383 sregex
const rex
= icase("wait-bdat-") >> (secs_
= +_d
);
1386 for (auto fp
: forward_path_
) {
1387 if (regex_match(fp
.local_part(), what
, rex
) ||
1388 regex_match(fp
.local_part(), what
, all_rex
)) {
1389 auto const str
= what
[secs_
].str();
1390 LOG(INFO
) << "waiting at BDAT " << str
<< " seconds";
1392 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1393 google::FlushLogFiles(google::INFO
);
1394 out_() << std::flush
;
1396 LOG(INFO
) << "done waiting";
1403 out_() << "250 2.0.0 BDAT " << n
<< " LAST OK\r\n" << std::flush
;
1404 LOG(INFO
) << "BDAT " << n
<< " LAST";
1405 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1410 void Session::bdat_size_error()
1412 out_().clear(); // clear possible eof from input side
1413 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1417 LOG(WARNING
) << "BDAT size error";
1421 void Session::bdat_seq_error()
1423 out_().clear(); // clear possible eof from input side
1424 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush
;
1428 LOG(WARNING
) << "BDAT sequence error";
1432 void Session::bdat_io_error()
1434 out_().clear(); // clear possible eof from input side
1435 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush
;
1439 LOG(WARNING
) << "BDAT I/O error";
1443 void Session::rset()
1445 out_() << "250 2.1.5 RSET OK\r\n";
1446 // No flush RFC-2920 section 3.1, this could be part of a command group.
1447 LOG(INFO
) << "RSET";
1451 void Session::noop(std::string_view str
)
1453 last_in_group_("NOOP");
1454 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush
;
1455 LOG(INFO
) << "NOOP" << (str
.length() ? " " : "") << str
;
1458 void Session::vrfy(std::string_view str
)
1460 last_in_group_("VRFY");
1461 out_() << "252 2.1.5 try it\r\n" << std::flush
;
1462 LOG(INFO
) << "VRFY" << (str
.length() ? " " : "") << str
;
1465 void Session::help(std::string_view str
)
1467 if (iequal(str
, "help\r\n")) {
1468 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush
;
1471 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1474 LOG(INFO
) << "HELP" << (str
.length() ? " " : "") << str
;
1477 void Session::quit()
1480 // last_in_group_("QUIT");
1481 out_() << "221 2.0.0 closing connection\r\n" << std::flush
;
1482 LOG(INFO
) << "QUIT";
1486 void Session::auth()
1488 out_() << "454 4.7.0 authentication failure\r\n" << std::flush
;
1489 LOG(INFO
) << "AUTH";
1493 void Session::error(std::string_view log_msg
)
1495 out_() << "421 4.3.5 system error: " << log_msg
<< "\r\n" << std::flush
;
1496 LOG(WARNING
) << log_msg
;
1499 void Session::cmd_unrecognized(std::string_view cmd
)
1501 auto const escaped
{esc(cmd
)};
1502 LOG(WARNING
) << "command unrecognized: \"" << escaped
<< "\"";
1504 if (++n_unrecognized_cmds_
>= Config::max_unrecognized_cmds
) {
1505 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1506 << "\" exceeds limit\r\n"
1508 LOG(WARNING
) << n_unrecognized_cmds_
1509 << " unrecognized commands is too many";
1513 out_() << "500 5.5.1 command unrecognized: \"" << escaped
<< "\"\r\n"
1517 void Session::bare_lf()
1519 // Error code used by Office 365.
1520 out_() << "554 5.6.11 bare LF\r\n" << std::flush
;
1521 LOG(WARNING
) << "bare LF";
1525 void Session::max_out()
1527 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1528 LOG(WARNING
) << "message size maxed out";
1532 void Session::time_out()
1534 out_() << "421 4.4.2 time-out\r\n" << std::flush
;
1535 LOG(WARNING
) << "time-out" << (sock_
.has_peername() ? " from " : "")
1540 void Session::starttls()
1542 last_in_group_("STARTTLS");
1544 out_() << "554 5.5.1 TLS already active\r\n" << std::flush
;
1545 LOG(WARNING
) << "STARTTLS issued with TLS already active";
1547 else if (!extensions_
) {
1548 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1550 LOG(WARNING
) << "STARTTLS issued without using EHLO";
1553 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush
;
1554 if (sock_
.starttls_server(config_path_
)) {
1556 max_msg_size(Config::max_msg_size_bro
);
1557 LOG(INFO
) << "STARTTLS " << sock_
.tls_info();
1560 LOG(INFO
) << "failed STARTTLS";
1565 void Session::exit_()
1567 // sock_.log_totals();
1569 timespec time_used
{};
1570 clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &time_used
);
1572 LOG(INFO
) << "CPU time " << time_used
.tv_sec
<< "." << std::setw(9)
1573 << std::setfill('0') << time_used
.tv_nsec
<< " seconds";
1575 std::exit(EXIT_SUCCESS
);
1578 /////////////////////////////////////////////////////////////////////////////
1580 // All of the verify_* functions send their own error messages back to
1581 // the client on failure, and return false.
1583 bool Session::verify_ip_address_(std::string
& error_msg
)
1585 auto ip_block_db_name
= config_path_
/ "ip-block";
1587 if (ip_block
.open(ip_block_db_name
) &&
1588 ip_block
.contains(sock_
.them_c_str())) {
1590 fmt::format("IP address {} on static blocklist", sock_
.them_c_str());
1591 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1595 client_fcrdns_
.clear();
1597 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1598 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1599 LOG(INFO
) << "loopback address allowed";
1601 client_fcrdns_
.emplace_back("localhost");
1602 client_
= fmt::format("localhost {}", sock_
.them_address_literal());
1606 auto const fcrdns
= DNS::fcrdns(res_
, sock_
.them_c_str());
1607 for (auto const& fcr
: fcrdns
) {
1608 client_fcrdns_
.emplace_back(fcr
);
1611 if (IP::is_private(sock_
.them_address_literal())) {
1612 LOG(INFO
) << "private address allowed";
1614 client_
= sock_
.them_address_literal();
1618 if (!client_fcrdns_
.empty()) {
1619 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1620 sock_
.them_address_literal());
1622 for (auto const& client_fcrdns
: client_fcrdns_
) {
1623 if (allow_
.contains(client_fcrdns
.ascii())) {
1624 LOG(INFO
) << "FCrDNS " << client_fcrdns
<< " allowed";
1625 fcrdns_allowed_
= true;
1628 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1630 if (allow_
.contains(tld
)) {
1631 LOG(INFO
) << "FCrDNS registered domain " << tld
<< " allowed";
1632 fcrdns_allowed_
= true;
1638 for (auto const& client_fcrdns
: client_fcrdns_
) {
1639 if (block_
.contains(client_fcrdns
.ascii())) {
1641 fmt::format("FCrDNS {} on static blocklist", client_fcrdns
.ascii());
1642 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1646 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1648 if (block_
.contains(tld
)) {
1649 error_msg
= fmt::format(
1650 "FCrDNS registered domain {} on static blocklist", tld
);
1651 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1658 client_
= fmt::format("{}", sock_
.them_address_literal());
1661 if (IP4::is_address(sock_
.them_c_str())) {
1663 auto const reversed
{IP4::reverse(sock_
.them_c_str())};
1666 // Check with allow list.
1667 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1670 for (auto wl : Config::wls) {
1671 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1672 if (q.has_record()) {
1673 using namespace boost::xpressive;
1675 auto const as = q.get_strings()[0];
1676 LOG(INFO) << "on allow list " << wl << " as " << as;
1680 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1683 if (regex_match(as, what, rex)) {
1684 auto const x = what[x_].str();
1685 auto const y = what[y_].str();
1688 std::from_chars(y.data(), y.data() + y.size(), value);
1691 LOG(INFO) << "allowed";
1695 LOG(INFO) << "Any A record skips check on block list";
1701 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1702 std::shuffle(std::begin(Config::bls
), std::end(Config::bls
),
1705 for (auto bl
: Config::bls
) {
1707 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ bl
);
1708 if (q
.has_record()) {
1709 auto const as
= q
.get_strings()[0];
1710 if (as
== "127.0.1.1") {
1711 LOG(INFO
) << "Query blocked by " << bl
;
1713 else if (as
== "127.0.0.10" || as
== "127.0.0.11") {
1714 LOG(INFO
) << "PBL listed, ignoring " << bl
;
1716 else if (as
== "127.255.255.252") {
1717 LOG(INFO
) << "Typing error in DNSBL name " << bl
;
1719 else if (as
== "127.255.255.254") {
1720 LOG(INFO
) << "Anonymous query through public resolver " << bl
;
1722 else if (as
== "127.255.255.255") {
1723 LOG(INFO
) << "Excessive number of queries " << bl
;
1726 error_msg
= fmt::format("IP address {} blocked: {} returned {}",
1727 sock_
.them_c_str(), bl
, as
);
1728 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1733 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1736 LOG(INFO
) << "IP address okay";
1740 bool domain_blocked(DNS::Resolver
& res
, Domain
const& identity
)
1742 Domain lookup
{fmt::format("{}.dbl.spamhaus.org", identity
.ascii())};
1743 DNS::Query
q(res
, DNS::RR_type::A
, lookup
.ascii());
1744 if (q
.has_record()) {
1745 auto const as
= q
.get_strings()[0];
1746 if (istarts_with(as
, "127.0.1.")) {
1747 LOG(INFO
) << "Domain " << identity
<< " blocked by spamhaus";
1754 // check the identity from HELO/EHLO
1755 bool Session::verify_client_(Domain
const& client_identity
,
1756 std::string
& error_msg
)
1758 if (!client_fcrdns_
.empty()) {
1759 if (auto id
= std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
1761 id
!= end(client_fcrdns_
)) {
1762 // If the HELO ident is one of the FCrDNS names...
1763 if (id
!= begin(client_fcrdns_
)) {
1764 // ...then rotate that one to the front of the list
1765 std::rotate(begin(client_fcrdns_
), id
, id
+ 1);
1767 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1768 sock_
.them_address_literal());
1771 LOG(INFO
) << "claimed identity " << client_identity
1772 << " does NOT match any FCrDNS: ";
1773 for (auto const& client_fcrdns
: client_fcrdns_
) {
1774 LOG(INFO
) << " " << client_fcrdns
;
1778 // Bogus clients claim to be us or some local host.
1779 if (sock_
.has_peername() && ((client_identity
== server_identity_
) ||
1780 (client_identity
== "localhost") ||
1781 (client_identity
== "localhost.localdomain"))) {
1783 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1784 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1790 LOG(INFO
) << "allow-listed IP address can claim to be "
1795 // Ease up in test mode.
1796 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1800 error_msg
= fmt::format("liar, claimed to be {}", client_identity
.ascii());
1801 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1805 std::vector
<std::string
> labels
;
1806 boost::algorithm::split(labels
, client_identity
.ascii(),
1807 boost::algorithm::is_any_of("."));
1808 if (labels
.size() < 2) {
1810 fmt::format("claimed bogus identity {}", client_identity
.ascii());
1811 out_() << "550 4.7.1 bogus identity\r\n" << std::flush
;
1813 // // Sometimes we may want to look at mail from non conforming
1814 // // sending systems.
1815 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1816 // << client_ << " claiming " << client_identity;
1820 if (lookup_domain(block_
, client_identity
)) {
1822 fmt::format("claimed blocked identity {}", client_identity
.ascii());
1823 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1827 auto const tld
{tld_db_
.get_registered_domain(client_identity
.ascii())};
1829 // Sometimes we may want to look at mail from misconfigured
1831 // LOG(WARNING) << "claimed identity has no registered domain";
1834 else if (block_
.contains(tld
)) {
1836 fmt::format("claimed identity has blocked registered domain {}", tld
);
1837 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush
;
1841 if (domain_blocked(res_
, client_identity
) ||
1842 domain_blocked(res_
, Domain(tld
))) {
1843 error_msg
= fmt::format("claimed identity {} blocked by spamhaus",
1844 client_identity
.ascii());
1845 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1849 DNS::Query
q(res_
, DNS::RR_type::A
, client_identity
.ascii());
1850 if (!q
.has_record()) {
1851 LOG(WARNING
) << "claimed identity " << client_identity
.ascii()
1852 << " not DNS resolvable";
1855 // not otherwise objectionable
1859 // check sender from RFC5321 MAIL FROM:
1860 bool Session::verify_sender_(Mailbox
const& sender
, std::string
& error_msg
)
1862 do_spf_check_(sender
);
1864 std::string
const sender_str
{sender
};
1866 if (sender
.empty()) {
1868 // is used to send bounce messages.
1872 if (domain_blocked(res_
, sender
.domain())) {
1873 error_msg
= fmt::format("{} sender domain blocked by spamhaus", sender_str
);
1874 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1878 auto bad_senders_db_name
= config_path_
/ "bad_senders";
1880 if (bad_senders
.open(bad_senders_db_name
) &&
1881 bad_senders
.contains(sender_str
)) {
1882 error_msg
= fmt::format("{} bad sender", sender_str
);
1883 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1887 // We don't accept mail /from/ a domain we are expecting to accept
1888 // mail for on an external network connection.
1890 if (sock_
.them_address_literal() != sock_
.us_address_literal()) {
1891 if ((accept_domains_
.is_open() &&
1892 (accept_domains_
.contains(sender
.domain().ascii()) ||
1893 accept_domains_
.contains(sender
.domain().utf8()))) ||
1894 (sender
.domain() == server_identity_
)) {
1896 // Ease up in test mode.
1897 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1900 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1901 error_msg
= fmt::format("liar, claimed to be {}", sender
.domain());
1906 if (sender
.domain().is_address_literal()) {
1907 if (sender
.domain() != sock_
.them_address_literal()) {
1908 LOG(WARNING
) << "sender domain " << sender
.domain() << " does not match "
1909 << sock_
.them_address_literal();
1914 if (!verify_sender_domain_(sender
.domain(), error_msg
)) {
1921 // this sender is the RFC5321 MAIL FROM: domain part
1922 bool Session::verify_sender_domain_(Domain
const& sender
,
1923 std::string
& error_msg
)
1925 if (sender
.empty()) {
1927 // is used to send bounce messages.
1931 // Break sender domain into labels:
1933 std::vector
<std::string
> labels
;
1934 boost::algorithm::split(labels
, sender
.ascii(),
1935 boost::algorithm::is_any_of("."));
1937 if (labels
.size() < 2) { // This is not a valid domain.
1938 error_msg
= fmt::format("{} invalid syntax", sender
.ascii());
1939 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1943 if (lookup_domain(block_
, sender
)) {
1945 fmt::format("SPF sender domain ({}) is blocked", spf_sender_domain_
);
1946 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1950 if (spf_result_
== SPF::Result::PASS
) {
1951 if (allow_
.contains(spf_sender_domain_
.ascii())) {
1952 LOG(INFO
) << "sender " << spf_sender_domain_
.ascii() << " allowed";
1957 tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
1959 if (allow_
.contains(reg_dom
)) {
1960 LOG(INFO
) << "sender registered domain \"" << reg_dom
<< "\" allowed";
1966 LOG(INFO
) << "sender \"" << sender
<< "\" not disallowed";
1970 void Session::do_spf_check_(Mailbox
const& sender
)
1972 if (!sock_
.has_peername()) {
1973 auto const ip_addr
= "127.0.0.1"; // use localhost for local socket
1975 fmt::format("Received-SPF: pass ({}: allow-listed) client-ip={}; "
1976 "envelope-from={}; helo={};",
1977 server_id_(), ip_addr
, sender
, client_identity_
);
1978 spf_sender_domain_
= "localhost";
1982 auto const spf_srv
= SPF::Server
{server_id_().c_str()};
1983 auto spf_request
= SPF::Request
{spf_srv
};
1985 if (IP4::is_address(sock_
.them_c_str())) {
1986 spf_request
.set_ipv4_str(sock_
.them_c_str());
1988 else if (IP6::is_address(sock_
.them_c_str())) {
1989 spf_request
.set_ipv6_str(sock_
.them_c_str());
1992 LOG(FATAL
) << "bogus address " << sock_
.them_address_literal() << ", "
1993 << sock_
.them_c_str();
1996 auto const from
{static_cast<std::string
>(sender
)};
1998 spf_request
.set_env_from(from
.c_str());
1999 spf_request
.set_helo_dom(client_identity_
.ascii().c_str());
2001 auto const spf_res
{SPF::Response
{spf_request
}};
2002 spf_result_
= spf_res
.result();
2003 spf_received_
= spf_res
.received_spf();
2004 spf_sender_domain_
= spf_request
.get_sender_dom();
2006 LOG(INFO
) << "spf_received_ == " << spf_received_
;
2008 if (spf_result_
== SPF::Result::FAIL
) {
2009 LOG(INFO
) << "FAIL " << spf_res
.header_comment();
2011 else if (spf_result_
== SPF::Result::NEUTRAL
) {
2012 LOG(INFO
) << "NEUTRAL " << spf_res
.header_comment();
2014 else if (spf_result_
== SPF::Result::PASS
) {
2015 LOG(INFO
) << "PASS " << spf_res
.header_comment();
2018 LOG(INFO
) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
2022 bool Session::verify_from_params_(parameters_t
const& parameters
)
2024 // Take a look at the optional parameters:
2025 for (auto const& [name
, value
] : parameters
) {
2026 if (iequal(name
, "BODY")) {
2027 if (iequal(value
, "8BITMIME")) {
2028 // everything is cool, this is our default...
2030 else if (iequal(value
, "7BIT")) {
2031 // nothing to see here, move along...
2033 else if (iequal(value
, "BINARYMIME")) {
2037 LOG(WARNING
) << "unrecognized BODY type \"" << value
<< "\" requested";
2040 else if (iequal(name
, "SMTPUTF8")) {
2041 if (!value
.empty()) {
2042 LOG(WARNING
) << "SMTPUTF8 parameter has a value: " << value
;
2047 // else if (iequal(name, "PRDR")) {
2048 // LOG(INFO) << "using PRDR";
2052 else if (iequal(name
, "SIZE")) {
2053 if (value
.empty()) {
2054 LOG(WARNING
) << "SIZE parameter has no value.";
2058 auto const sz
= stoull(value
);
2059 if (sz
> max_msg_size()) {
2060 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
2061 LOG(WARNING
) << "SIZE parameter too large: " << sz
;
2065 catch (std::invalid_argument
const& e
) {
2066 LOG(WARNING
) << "SIZE parameter has invalid value: " << value
;
2068 catch (std::out_of_range
const& e
) {
2069 LOG(WARNING
) << "SIZE parameter has out-of-range value: " << value
;
2071 // I guess we just ignore bad size parameters.
2074 else if (iequal(name
, "REQUIRETLS")) {
2076 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush
;
2077 LOG(WARNING
) << "REQUIRETLS needed";
2082 LOG(WARNING
) << "unrecognized 'MAIL FROM' parameter " << name
<< "="
2090 bool Session::verify_rcpt_params_(parameters_t
const& parameters
)
2092 // Take a look at the optional parameters:
2093 for (auto const& [name
, value
] : parameters
) {
2094 if (iequal(name
, "RRVS")) {
2095 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
2096 LOG(INFO
) << name
<< "=" << value
;
2099 LOG(WARNING
) << "unrecognized 'RCPT TO' parameter " << name
<< "="
2107 // check recipient from RFC5321 RCPT TO:
2108 bool Session::verify_recipient_(Mailbox
const& recipient
)
2110 if ((recipient
.local_part() == "Postmaster") && (recipient
.domain() == "")) {
2111 LOG(INFO
) << "magic Postmaster address";
2115 auto const accepted_domain
{[this, &recipient
] {
2116 if (recipient
.domain().is_address_literal()) {
2117 if (recipient
.domain() != sock_
.us_address_literal()) {
2118 LOG(WARNING
) << "recipient.domain address " << recipient
.domain()
2119 << " does not match ours " << sock_
.us_address_literal();
2127 // Domains we accept mail for.
2128 if (accept_domains_
.is_open()) {
2129 if (accept_domains_
.contains(recipient
.domain().ascii()) ||
2130 accept_domains_
.contains(recipient
.domain().utf8())) {
2135 // If we have no list of domains to accept, at least take our own.
2136 if (recipient
.domain() == server_id_()) {
2144 if (!accepted_domain
) {
2145 out_() << "550 5.7.1 relay access denied\r\n" << std::flush
;
2146 LOG(WARNING
) << "relay access denied for domain " << recipient
.domain();
2150 // Check for local addresses we reject.
2152 auto bad_recipients_db_name
= config_path_
/ "bad_recipients";
2153 CDB bad_recipients_db
;
2154 if (bad_recipients_db
.open(bad_recipients_db_name
) &&
2155 bad_recipients_db
.contains(recipient
.local_part())) {
2156 out_() << "550 5.1.1 bad recipient " << recipient
<< "\r\n" << std::flush
;
2157 LOG(WARNING
) << "bad recipient " << recipient
;
2163 auto fail_db_name
= config_path_
/ "fail_554";
2164 if (fs::exists(fail_db_name
)) {
2166 if (fail_db
.open(fail_db_name
) &&
2167 fail_db
.contains(recipient
.local_part())) {
2168 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2171 LOG(WARNING
) << "fail_554 recipient " << recipient
;
2178 auto temp_fail_db_name
= config_path_
/ "temp_fail";
2180 if (temp_fail
.open(temp_fail_db_name
) &&
2181 temp_fail
.contains(recipient
.local_part())) {
2182 out_() << "432 4.3.0 recipient's incoming mail queue has been stopped\r\n"
2184 LOG(WARNING
) << "temp fail for recipient " << recipient
;
2189 // Check for and act on magic "wait" address.
2191 using namespace boost::xpressive
;
2193 sregex
const rex
= icase("wait-rcpt-") >> (secs_
= +_d
);
2196 if (regex_match(recipient
.local_part(), what
, rex
) ||
2197 regex_match(recipient
.local_part(), what
, all_rex
)) {
2198 auto const str
= what
[secs_
].str();
2199 LOG(INFO
) << "waiting at RCPT TO " << str
<< " seconds";
2201 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
2202 google::FlushLogFiles(google::INFO
);
2203 out_() << std::flush
;
2205 LOG(INFO
) << "done waiting";
2209 // This is a trap for a probe done by some senders to see if we
2210 // accept just any old local-part.
2212 if (recipient
.local_part().length() > 8) {
2213 out_() << "550 5.1.1 unknown recipient " << recipient
<< "\r\n"
2215 LOG(WARNING
) << "unknown recipient for HELO " << recipient
;