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 slowstart - ghsmtp\r\n"
346 if (sock_
.input_ready(Config::greeting_wait
)) {
347 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
348 LOG(INFO
) << "input before full greeting from " << client_
;
349 bad_host_("input before full greeting");
352 <https://www.rfc-editor.org/rfc/rfc5321#section-4.2>
354 An SMTP client MUST determine its actions only by the reply code, not
355 by the text (except for the "change of address" 251 and 551 and, if
356 necessary, 220, 221, and 421 replies); in the general case, any text,
357 including no text at all (although senders SHOULD NOT send bare
358 codes), MUST be acceptable. The space (blank) following the reply
359 code is considered part of the text. Whenever possible, a receiver-
360 SMTP SHOULD test the first digit (severity indication) of the reply
363 Except the following chokes a lot of senders:
365 out_() << "220\r\n" << std::flush;
368 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
371 out_() << "220 " << server_id_() << " ESMTP faststart - ghsmtp\r\n"
376 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
379 LOG(INFO
) << "connect from " << client_
;
381 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
382 alarm(2 * 60); // initial alarm
386 void Session::flush() { out_() << std::flush
; }
388 void Session::last_in_group_(std::string_view verb
)
390 if (sock_
.input_ready(std::chrono::seconds(0))) {
391 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
395 void Session::check_for_pipeline_error_(std::string_view verb
)
397 if (!(FLAGS_use_pipelining
&& extensions_
)) {
398 if (sock_
.input_ready(std::chrono::seconds(0))) {
399 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
404 void Session::lo_(char const* verb
, std::string_view client_identity
)
406 last_in_group_(verb
);
409 if (client_identity_
!= client_identity
) {
410 client_identity_
= client_identity
;
412 std::string error_msg
;
413 if (!verify_client_(client_identity_
, error_msg
)) {
414 LOG(INFO
) << "client identity blocked: " << error_msg
;
415 bad_host_(error_msg
.c_str());
421 out_() << "250 " << server_id_() << "\r\n";
427 if (sock_
.has_peername()) {
428 out_() << "250-" << server_id_() << " at your service, " << client_
432 out_() << "250-" << server_id_() << "\r\n";
435 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
436 out_() << "250-8BITMIME\r\n"; // RFC 6152
438 if (FLAGS_use_rrvs
) {
439 out_() << "250-RRVS\r\n"; // RFC 7293
442 // out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
445 // Check sasl sources for auth types.
446 // out_() << "250-AUTH PLAIN\r\n";
447 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
450 // If we're not already TLS, offer TLS
451 out_() << "250-STARTTLS\r\n"; // RFC 3207
454 out_() << "250-ENHANCEDSTATUSCODES\r\n"; // RFC 2034
456 if (FLAGS_use_pipelining
) {
457 out_() << "250-PIPELINING\r\n"; // RFC 2920
460 if (FLAGS_use_binarymime
) {
461 out_() << "250-BINARYMIME\r\n"; // RFC 3030
464 if (FLAGS_use_chunking
) {
465 out_() << "250-CHUNKING\r\n"; // RFC 3030
468 if (FLAGS_use_smtputf8
) {
469 out_() << "250-SMTPUTF8\r\n"; // RFC 6531
472 out_() << "250 HELP\r\n";
475 out_() << std::flush
;
477 if (sock_
.has_peername()) {
478 if (std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
479 client_identity_
) != end(client_fcrdns_
)) {
480 LOG(INFO
) << verb
<< " " << client_identity
<< " from "
481 << sock_
.them_address_literal();
484 LOG(INFO
) << verb
<< " " << client_identity
<< " from " << client_
;
488 LOG(INFO
) << verb
<< " " << client_identity
;
492 void Session::mail_from(Mailbox
&& reverse_path
, parameters_t
const& parameters
)
494 check_for_pipeline_error_("MAIL FROM");
497 case xact_step::helo
:
498 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
499 LOG(WARNING
) << "'MAIL FROM' before HELO/EHLO"
500 << (sock_
.has_peername() ? " from " : "") << client_
;
502 case xact_step::mail
: break;
503 case xact_step::rcpt
:
504 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
505 LOG(WARNING
) << "nested MAIL command"
506 << (sock_
.has_peername() ? " from " : "") << client_
;
508 case xact_step::data
:
509 case xact_step::bdat
:
510 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush
;
511 LOG(WARNING
) << "nested MAIL command"
512 << (sock_
.has_peername() ? " from " : "") << client_
;
514 case xact_step::rset
:
515 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
516 LOG(WARNING
) << "error state must be cleared with a RSET"
517 << (sock_
.has_peername() ? " from " : "") << client_
;
521 if (!verify_from_params_(parameters
)) {
525 if (!smtputf8_
&& !is_ascii(reverse_path
.local_part())) {
526 LOG(WARNING
) << "non ascii reverse_path \"" << reverse_path
527 << "\" without SMTPUTF8 paramater";
530 std::string error_msg
;
531 if (!verify_sender_(reverse_path
, error_msg
)) {
532 LOG(INFO
) << "verify sender failed: " << error_msg
;
533 bad_host_(error_msg
.c_str());
536 reverse_path_
= std::move(reverse_path
);
537 // fwd_path_.clear();
538 // fwd_from_.clear();
539 forward_path_
.clear();
540 out_() << "250 2.1.0 MAIL FROM OK\r\n";
541 // No flush RFC-2920 section 3.1, this could be part of a command group.
543 fmt::memory_buffer params
;
544 for (auto const& [name
, value
] : parameters
) {
545 fmt::format_to(std::back_inserter(params
), " {}", name
);
546 if (!value
.empty()) {
547 fmt::format_to(std::back_inserter(params
), "={}", value
);
550 LOG(INFO
) << "MAIL FROM:<" << reverse_path_
<< ">" << fmt::to_string(params
);
552 state_
= xact_step::rcpt
;
555 // bool Session::forward_to_(std::string const& forward, Mailbox const& rcpt_to)
557 // // If we're already forwarding or replying, reject
558 // if (!fwd_path_.empty() || !rep_info_.empty()) {
559 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
562 // LOG(WARNING) << "failed to forward to <" << forward
563 // << "> already forwarding or replying for: " << rcpt_to;
567 // fwd_path_ = Mailbox(forward);
568 // fwd_from_ = rcpt_to;
570 // // New bounce address
571 // Reply::from_to bounce;
572 // bounce.mail_from = reverse_path_.as_string();
574 // auto const new_bounce = srs_.enc_bounce(bounce, server_id_().c_str());
576 // auto const mail_from = Mailbox(new_bounce);
578 // std::string error_msg;
579 // if (!send_.mail_from_rcpt_to(res_, mail_from, fwd_path_, error_msg)) {
580 // out_() << error_msg << std::flush;
581 // LOG(WARNING) << "failed to forward <" << fwd_path_ << "> " << error_msg;
585 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> forwarding to == <" << fwd_path_
590 // bool Session::reply_to_(Reply::from_to const& reply_info, Mailbox const&
593 // // If we're already forwarding or replying, reject
594 // if (!fwd_path_.empty() || !rep_info_.empty()) {
595 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
598 // LOG(WARNING) << "failed to reply to <" << reply_info.mail_from
599 // << "> already forwarding or replying for: " << rcpt_to;
603 // rep_info_ = reply_info;
605 // Mailbox const from(rep_info_.rcpt_to_local_part, server_identity_);
606 // Mailbox const to(rep_info_.mail_from);
608 // std::string error_msg;
609 // if (!send_.mail_from_rcpt_to(res_, from, to, error_msg)) {
610 // out_() << error_msg << std::flush;
611 // LOG(WARNING) << "failed to reply from <" << from << "> to <" << to << ">
617 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> is a reply to "
618 // << rep_info_.mail_from << " from " <<
619 // rep_info_.rcpt_to_local_part;
623 void Session::rcpt_to(Mailbox
&& forward_path
, parameters_t
const& parameters
)
625 check_for_pipeline_error_("RCPT TO");
628 case xact_step::helo
:
629 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
630 LOG(WARNING
) << "'RCPT TO' before HELO/EHLO"
631 << (sock_
.has_peername() ? " from " : "") << client_
;
633 case xact_step::mail
:
634 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
635 LOG(WARNING
) << "'RCPT TO' before 'MAIL FROM'"
636 << (sock_
.has_peername() ? " from " : "") << client_
;
638 case xact_step::rcpt
:
639 case xact_step::data
: break;
640 case xact_step::bdat
:
641 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
642 LOG(WARNING
) << "'RCPT TO' during BDAT transfer"
643 << (sock_
.has_peername() ? " from " : "") << client_
;
645 case xact_step::rset
:
646 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
647 LOG(WARNING
) << "error state must be cleared with a RSET"
648 << (sock_
.has_peername() ? " from " : "") << client_
;
652 if (!verify_rcpt_params_(parameters
))
655 if (!verify_recipient_(forward_path
))
658 if (!smtputf8_
&& !is_ascii(forward_path
.local_part())) {
659 LOG(WARNING
) << "non ascii forward_path \"" << forward_path
660 << "\" without SMTPUTF8 paramater";
663 if (forward_path_
.size() >= Config::max_recipients_per_message
) {
664 out_() << "452 4.5.3 too many recipients\r\n" << std::flush
;
665 LOG(WARNING
) << "too many recipients <" << forward_path
<< ">";
668 // no check for dups, postfix doesn't
669 forward_path_
.emplace_back(std::move(forward_path
));
671 Mailbox
const& rcpt_to_mbx
= forward_path_
.back();
673 LOG(INFO
) << "RCPT TO:<" << rcpt_to_mbx
<< ">";
675 // auto const rcpt_to_str = rcpt_to_mbx.as_string();
677 // if (auto reply = srs_.dec_reply(rcpt_to_mbx.local_part()); reply) {
678 // if (!reply_to_(*reply, rcpt_to_mbx))
681 // else if (auto const forward = forward_.find(rcpt_to_str.c_str()); forward)
683 // if (!forward_to_(*forward, rcpt_to_mbx))
687 // LOG(INFO) << "RCPT TO:<" << rcpt_to_str << ">";
690 // No flush RFC-2920 section 3.1, this could be part of a command group.
691 out_() << "250 2.1.5 RCPT TO OK\r\n";
693 state_
= xact_step::data
;
696 // The headers Return-Path:, Received-SPF:, and Received: are returned
699 std::string
Session::added_headers_(MessageStore
const& msg
)
701 auto const protocol
{[this]() {
702 if (sock_
.tls() && !extensions_
) {
703 LOG(WARNING
) << "TLS active without extensions";
705 // <https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#mail-parameters-5>
707 return sock_
.tls() ? "UTF8SMTPS" : "UTF8SMTP";
708 else if (sock_
.tls())
710 else if (extensions_
)
716 fmt::memory_buffer headers
;
719 fmt::format_to(std::back_inserter(headers
), "Return-Path: <{}>\r\n", reverse_path_
);
722 if (!spf_received_
.empty()) {
723 fmt::format_to(std::back_inserter(headers
), "{}\r\n", spf_received_
);
727 // <https://tools.ietf.org/html/rfc5321#section-4.4>
728 fmt::format_to(std::back_inserter(headers
), "Received: from {}", client_identity_
.utf8());
729 if (sock_
.has_peername()) {
730 fmt::format_to(std::back_inserter(headers
), " ({})", client_
);
732 fmt::format_to(std::back_inserter(headers
), "\r\n\tby {} with {} id {}", server_identity_
.utf8(),
734 if (forward_path_
.size()) {
735 fmt::format_to(std::back_inserter(headers
), "\r\n\tfor <{}>", forward_path_
[0]);
736 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
737 // “If the FOR clause appears, it MUST contain exactly one <path>
738 // entry, even when multiple RCPT commands have been given. Multiple
739 // <path>s raise some security issues and have been deprecated, see
741 // for (auto i = 1u; i < forward_path_.size(); ++i)
742 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
744 std::string
const tls_info
{sock_
.tls_info()};
745 if (tls_info
.length()) {
746 fmt::format_to(std::back_inserter(headers
), "\r\n\t({})", tls_info
);
748 fmt::format_to(std::back_inserter(headers
), ";\r\n\t{}\r\n", msg
.when());
750 return fmt::to_string(headers
);
754 bool lookup_domain(CDB
& cdb
, Domain
const& domain
)
756 if (!domain
.empty()) {
757 if (cdb
.contains(domain
.ascii())) {
760 if (domain
.is_unicode() && cdb
.contains(domain
.utf8())) {
768 std::tuple
<Session::SpamStatus
, std::string
> Session::spam_status_()
770 if (spf_result_
== SPF::Result::FAIL
&& !ip_allowed_
)
771 return {SpamStatus::spam
, "SPF failed"};
773 // These should have already been rejected by verify_client_().
774 if ((reverse_path_
.domain() == "localhost.local") ||
775 (reverse_path_
.domain() == "localhost"))
776 return {SpamStatus::spam
, "bogus reverse_path"};
778 std::vector
<std::string
> why_ham
;
780 // Anything enciphered tastes a lot like ham.
782 why_ham
.emplace_back("they used TLS");
784 if (spf_result_
== SPF::Result::PASS
) {
785 if (lookup_domain(allow_
, spf_sender_domain_
)) {
786 why_ham
.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
787 spf_sender_domain_
.utf8()));
790 auto tld_dom
{tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
791 if (tld_dom
&& allow_
.contains(tld_dom
)) {
792 why_ham
.emplace_back(fmt::format(
793 "SPF sender registered domain ({}) is allowed", tld_dom
));
799 why_ham
.emplace_back(
800 fmt::format("FCrDNS (or it's registered domain) is allowed"));
802 if (!why_ham
.empty())
803 return {SpamStatus::ham
,
804 fmt::format("{}", fmt::join(std::begin(why_ham
), std::end(why_ham
),
807 return {SpamStatus::spam
, "it's not ham"};
810 static std::string
folder(Session::SpamStatus status
,
811 std::vector
<Mailbox
> const& forward_path
,
812 Mailbox
const& reverse_path
)
815 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
818 if (reverse_path
== Mailbox("ietf-smtp-bounces@ietf.org"))
822 std::string_view local_part
;
823 std::string_view folder
;
826 assignment assignments
[] = {
827 {"Emailcore", ".emailcore"},
828 {"bootstrappable", ".bootstrappable"},
829 {"coreboot.org", ".coreboot"},
831 {"dns-privacy", ".dns-privacy"},
832 {"fucking-facebook", ".FB"},
833 {"gene-ebay", ".EBay"},
834 {"i-hate-linked-in", ".linkedin"},
835 {"mailop", ".INBOX.mailop"},
836 {"modelfkeyboards.com", ""},
837 {"nest", ".INBOX.Nest"},
838 {"opendmarc-dev", ".dmarc"},
839 {"opendmarc-users", ".dmarc"},
840 {"theatlantic.com", ""},
841 {"time-nutz", ".time-nutz"},
842 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
845 for (auto ass
: assignments
) {
846 if (forward_path
[0].local_part() == ass
.local_part
)
847 return std::string(ass
.folder
);
850 if (iends_with(forward_path
[0].local_part(), "-at-duck"))
853 if (status
== Session::SpamStatus::spam
)
859 bool Session::msg_new()
861 CHECK((state_
== xact_step::data
) || (state_
== xact_step::bdat
));
863 auto const& [status
, reason
]{spam_status_()};
865 LOG(INFO
) << ((status
== SpamStatus::ham
) ? "ham since " : "spam since ")
868 // All sources of ham get a fresh 5 minute timeout per message.
869 if (status
== SpamStatus::ham
) {
870 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr))
874 msg_
= std::make_unique
<MessageStore
>();
876 if (!FLAGS_max_write
)
877 FLAGS_max_write
= max_msg_size();
880 msg_
->open(server_id_(), FLAGS_max_write
,
881 folder(status
, forward_path_
, reverse_path_
));
882 auto const hdrs
{added_headers_(*(msg_
.get()))};
885 // fmt::memory_buffer spam_status;
886 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
887 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
888 // msg_->write(spam_status.data(), spam_status.size());
890 LOG(INFO
) << "Spam-Status: "
891 << ((status
== SpamStatus::spam
) ? "Yes" : "No") << ", "
896 catch (std::system_error
const& e
) {
899 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
900 LOG(ERROR
) << "no space";
906 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
907 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
908 LOG(ERROR
) << e
.what();
914 catch (std::exception
const& e
) {
915 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
916 LOG(ERROR
) << e
.what();
922 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
923 LOG(ERROR
) << "msg_new failed with no exception caught";
929 bool Session::msg_write(char const* s
, std::streamsize count
)
931 if ((state_
!= xact_step::data
) && (state_
!= xact_step::bdat
))
938 if (msg_
->write(s
, count
))
941 catch (std::system_error
const& e
) {
944 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
945 LOG(ERROR
) << "no space";
951 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
952 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
953 LOG(ERROR
) << e
.what();
959 catch (std::exception
const& e
) {
960 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
961 LOG(ERROR
) << e
.what();
967 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
968 LOG(ERROR
) << "msg_write failed with no exception caught";
974 bool Session::data_start()
976 last_in_group_("DATA");
979 case xact_step::helo
:
980 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
981 LOG(WARNING
) << "'DATA' before HELO/EHLO"
982 << (sock_
.has_peername() ? " from " : "") << client_
;
984 case xact_step::mail
:
985 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
986 LOG(WARNING
) << "'DATA' before 'MAIL FROM'"
987 << (sock_
.has_peername() ? " from " : "") << client_
;
989 case xact_step::rcpt
:
991 /******************************************************************
992 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
994 The DATA command can fail at only two points in the protocol exchange:
996 If there was no MAIL, or no RCPT, command, or all such commands were
997 rejected, the server MAY return a "command out of sequence" (503) or
998 "no valid recipients" (554) reply in response to the DATA command.
1000 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
1002 The additional restriction is that when there have been no successful
1003 RCPT commands in the mail transaction, the DATA command MUST fail
1004 with a 503 reply code.
1006 Therefore I will send the reply code that is valid for both, and
1007 do the same for the BDAT case.
1008 *******************************************************************/
1010 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1011 LOG(WARNING
) << "no valid recipients"
1012 << (sock_
.has_peername() ? " from " : "") << client_
;
1014 case xact_step::data
: break;
1015 case xact_step::bdat
:
1016 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
1017 LOG(WARNING
) << "'DATA' during BDAT transfer"
1018 << (sock_
.has_peername() ? " from " : "") << client_
;
1020 case xact_step::rset
:
1021 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1022 LOG(WARNING
) << "error state must be cleared with a RSET"
1023 << (sock_
.has_peername() ? " from " : "") << client_
;
1028 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
1030 LOG(WARNING
) << "DATA does not support BINARYMIME";
1031 state_
= xact_step::rset
; // RFC 3030 section 3 page 5
1036 LOG(ERROR
) << "msg_new() failed";
1040 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush
;
1041 LOG(INFO
) << "DATA";
1045 // bool Session::do_forward_(message::parsed& msg)
1047 // auto msg_fwd = msg;
1049 // // Generate a reply address
1050 // Reply::from_to reply;
1051 // reply.mail_from = msg_fwd.dmarc_from;
1052 // reply.rcpt_to_local_part = fwd_from_.local_part();
1054 // auto const reply_addr =
1055 // fmt::format("{}@{}", srs_.enc_reply(reply), server_id_());
1057 // auto const munging = false;
1059 // auto const sender = server_identity_.ascii().c_str();
1060 // auto const selector = FLAGS_selector.c_str();
1061 // auto const key_file =
1062 // (config_path_ / FLAGS_selector).replace_extension("private");
1063 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1066 // auto const from_hdr =
1067 // fmt::format("From: \"{} via\" <@>", msg_fwd.dmarc_from, reply_addr);
1068 // message::rewrite_from_to(msg_fwd, from_hdr, "", sender, selector,
1072 // auto const reply_to_hdr = fmt::format("Reply-To: {}", reply_addr);
1073 // message::rewrite_from_to(msg_fwd, "", reply_to_hdr, sender, selector,
1078 // if (!send_.send(msg_fwd.as_string())) {
1079 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1083 // LOG(ERROR) << "failed to send for " << fwd_path_;
1087 // LOG(INFO) << "successfully sent for " << fwd_path_;
1091 // bool Session::do_reply_(message::parsed& msg)
1093 // Mailbox to_mbx(rep_info_.mail_from);
1094 // Mailbox from_mbx(rep_info_.rcpt_to_local_part, server_identity_);
1096 // auto reply = std::make_unique<MessageStore>();
1097 // reply->open(server_id_(), FLAGS_max_write, ".Drafts");
1099 // auto const date{Now{}};
1100 // auto const pill{Pill{}};
1101 // auto const mid_str =
1102 // fmt::format("<{}.{}@{}>", date.sec(), pill, server_identity_);
1104 // fmt::memory_buffer bfr;
1106 // fmt::format_to(bfr, "From: <{}>\r\n", from_mbx);
1107 // fmt::format_to(bfr, "To: <{}>\r\n", to_mbx);
1109 // fmt::format_to(bfr, "Date: {}\r\n", date.c_str());
1111 // fmt::format_to(bfr, "Message-ID: {}\r\n", mid_str.c_str());
1113 // if (!msg.get_header(message::Subject).empty()) {
1114 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1115 // msg.get_header(message::Subject));
1118 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1119 // "Reply to your message");
1122 // if (!msg.get_header(message::In_Reply_To).empty()) {
1123 // fmt::format_to(bfr, "{}: {}\r\n", message::In_Reply_To,
1124 // msg.get_header(message::In_Reply_To));
1127 // if (!msg.get_header(message::MIME_Version).empty() &&
1128 // msg.get_header(message::Content_Type).empty()) {
1129 // fmt::format_to(bfr, "{}: {}\r\n", message::MIME_Version,
1130 // msg.get_header(message::MIME_Version));
1131 // fmt::format_to(bfr, "{}: {}\r\n", message::Content_Type,
1132 // msg.get_header(message::Content_Type));
1135 // reply->write(fmt::to_string(bfr));
1137 // if (!msg.body.empty()) {
1138 // reply->write("\r\n");
1139 // reply->write(msg.body);
1142 // auto const msg_data = reply->freeze();
1143 // message::parsed msg_reply;
1144 // CHECK(msg_reply.parse(msg_data));
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;
1152 // message::dkim_sign(msg_reply, sender, selector, key_file);
1154 // if (!send_.send(msg_reply.as_string())) {
1155 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1159 // LOG(ERROR) << "send failed for reply to " << to_mbx << " from " <<
1160 // from_mbx; return false;
1163 // LOG(INFO) << "successful reply to " << to_mbx << " from " << from_mbx;
1167 bool Session::do_deliver_()
1171 // auto const sender = server_identity_.ascii().c_str();
1172 // auto const selector = FLAGS_selector.c_str();
1173 // auto const key_file =
1174 // (config_path_ / FLAGS_selector).replace_extension("private");
1175 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1178 // auto const msg_data = msg_->freeze();
1180 // message::parsed msg;
1182 // // Only deal in RFC-5322 Mail Objects.
1183 // bool const message_parsed = msg.parse(msg_data);
1184 // if (message_parsed) {
1186 // // remove any Return-Path
1187 // message::remove_delivery_headers(msg);
1189 // auto const authentic =
1190 // message_parsed &&
1191 // message::authentication(msg, sender, selector, key_file);
1193 // // write a new Return-Path
1194 // msg_->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_));
1196 // for (auto const h : msg.headers) {
1197 // msg_->write(h.as_string());
1198 // msg_->write("\r\n");
1200 // if (!msg.body.empty()) {
1201 // msg_->write("\r\n");
1202 // msg_->write(msg.body);
1207 // if (authentic && !fwd_path_.empty()) {
1208 // if (!do_forward_(msg))
1211 // if (authentic && !rep_info_.empty()) {
1212 // if (!do_reply_(msg))
1219 catch (std::system_error
const& e
) {
1222 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
1223 LOG(ERROR
) << "no space";
1229 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
1231 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
1232 LOG(ERROR
) << e
.what();
1242 void Session::data_done()
1244 CHECK((state_
== xact_step::data
));
1246 if (msg_
&& msg_
->size_error()) {
1252 // out_() << "353\r\n";
1253 // for (auto fp : forward_path_) {
1254 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1258 // Check for and act on magic "wait" address.
1260 using namespace boost::xpressive
;
1262 sregex
const rex
= icase("wait-data-") >> (secs_
= +_d
);
1265 for (auto fp
: forward_path_
) {
1266 if (regex_match(fp
.local_part(), what
, rex
) ||
1267 regex_match(fp
.local_part(), what
, all_rex
)) {
1268 auto const str
= what
[secs_
].str();
1269 LOG(INFO
) << "waiting at DATA " << str
<< " seconds";
1271 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1272 google::FlushLogFiles(google::INFO
);
1273 out_() << std::flush
;
1275 LOG(INFO
) << "done waiting";
1280 if (do_deliver_()) {
1281 auto temp_fail_db_name
= config_path_
/ "temp_fail_data";
1284 for (auto fp
: forward_path_
) {
1285 if (temp_fail
.open(temp_fail_db_name
) &&
1286 temp_fail
.contains(fp
.local_part())) {
1287 out_() << "450 4.2.2 Mailbox full.\r\n" << std::flush
;
1288 LOG(WARNING
) << "temp fail at DATA for recipient " << fp
;
1295 // Check for addresses we reject after data.
1297 auto bad_recipients_db_name
= config_path_
/ "bad_recipients_data";
1298 CDB bad_recipients_db
;
1299 if (bad_recipients_db
.open(bad_recipients_db_name
)) {
1300 for (auto fp
: forward_path_
) {
1301 if (bad_recipients_db
.contains(fp
.local_part())) {
1302 out_() << "550 5.1.1 bad recipient " << fp
<< "\r\n" << std::flush
;
1303 LOG(WARNING
) << "bad recipient " << fp
;
1308 LOG(INFO
) << "unbad recipient " << fp
.local_part();
1313 LOG(WARNING
) << "can't open bad_recipients_data";
1317 out_() << "250 2.0.0 DATA OK\r\n" << std::flush
;
1318 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1323 void Session::data_size_error()
1325 out_().clear(); // clear possible eof from input side
1326 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1330 LOG(WARNING
) << "DATA size error";
1334 void Session::data_error()
1336 out_().clear(); // clear possible eof from input side
1337 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush
;
1341 LOG(WARNING
) << "DATA error";
1345 bool Session::bdat_start(size_t n
)
1347 // In practice, this one gets pipelined.
1348 // last_in_group_("BDAT");
1351 case xact_step::helo
:
1352 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
1353 LOG(WARNING
) << "'BDAT' before HELO/EHLO"
1354 << (sock_
.has_peername() ? " from " : "") << client_
;
1356 case xact_step::mail
:
1357 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
1358 LOG(WARNING
) << "'BDAT' before 'MAIL FROM'"
1359 << (sock_
.has_peername() ? " from " : "") << client_
;
1361 case xact_step::rcpt
:
1362 // See comment in data_start()
1363 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1364 LOG(WARNING
) << "no valid recipients"
1365 << (sock_
.has_peername() ? " from " : "") << client_
;
1367 case xact_step::data
: // first bdat
1369 case xact_step::bdat
: return true;
1370 case xact_step::rset
:
1371 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1372 LOG(WARNING
) << "error state must be cleared with a RSET"
1373 << (sock_
.has_peername() ? " from " : "") << client_
;
1377 state_
= xact_step::bdat
;
1382 void Session::bdat_done(size_t n
, bool last
)
1384 if (state_
!= xact_step::bdat
) {
1393 if (msg_
->size_error()) {
1399 out_() << "250 2.0.0 BDAT " << n
<< " OK\r\n" << std::flush
;
1400 LOG(INFO
) << "BDAT " << n
;
1404 // Check for and act on magic "wait" address.
1406 using namespace boost::xpressive
;
1408 sregex
const rex
= icase("wait-bdat-") >> (secs_
= +_d
);
1411 for (auto fp
: forward_path_
) {
1412 if (regex_match(fp
.local_part(), what
, rex
) ||
1413 regex_match(fp
.local_part(), what
, all_rex
)) {
1414 auto const str
= what
[secs_
].str();
1415 LOG(INFO
) << "waiting at BDAT " << str
<< " seconds";
1417 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1418 google::FlushLogFiles(google::INFO
);
1419 out_() << std::flush
;
1421 LOG(INFO
) << "done waiting";
1428 out_() << "250 2.0.0 BDAT " << n
<< " LAST OK\r\n" << std::flush
;
1429 LOG(INFO
) << "BDAT " << n
<< " LAST";
1430 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1435 void Session::bdat_size_error()
1437 out_().clear(); // clear possible eof from input side
1438 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1442 LOG(WARNING
) << "BDAT size error";
1446 void Session::bdat_seq_error()
1448 out_().clear(); // clear possible eof from input side
1449 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush
;
1453 LOG(WARNING
) << "BDAT sequence error";
1457 void Session::bdat_io_error()
1459 out_().clear(); // clear possible eof from input side
1460 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush
;
1464 LOG(WARNING
) << "BDAT I/O error";
1468 void Session::rset()
1470 out_() << "250 2.1.5 RSET OK\r\n";
1471 // No flush RFC-2920 section 3.1, this could be part of a command group.
1472 LOG(INFO
) << "RSET";
1476 void Session::noop(std::string_view str
)
1478 last_in_group_("NOOP");
1479 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush
;
1480 LOG(INFO
) << "NOOP" << (str
.length() ? " " : "") << str
;
1483 void Session::vrfy(std::string_view str
)
1485 last_in_group_("VRFY");
1486 out_() << "252 2.1.5 try it\r\n" << std::flush
;
1487 LOG(INFO
) << "VRFY" << (str
.length() ? " " : "") << str
;
1490 void Session::help(std::string_view str
)
1492 if (iequal(str
, "help\r\n")) {
1493 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush
;
1496 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1499 LOG(INFO
) << "HELP" << (str
.length() ? " " : "") << str
;
1502 void Session::quit()
1505 // last_in_group_("QUIT");
1506 out_() << "221 2.0.0 closing connection\r\n" << std::flush
;
1507 LOG(INFO
) << "QUIT";
1511 void Session::auth()
1513 out_() << "454 4.7.0 authentication failure\r\n" << std::flush
;
1514 LOG(INFO
) << "AUTH";
1518 void Session::error(std::string_view log_msg
)
1520 out_() << "421 4.3.5 system error: " << log_msg
<< "\r\n" << std::flush
;
1521 LOG(WARNING
) << log_msg
;
1524 void Session::cmd_unrecognized(std::string_view cmd
)
1526 auto const escaped
{esc(cmd
)};
1527 LOG(WARNING
) << "command unrecognized: \"" << escaped
<< "\"";
1529 if (++n_unrecognized_cmds_
>= Config::max_unrecognized_cmds
) {
1530 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1531 << "\" exceeds limit\r\n"
1533 LOG(WARNING
) << n_unrecognized_cmds_
1534 << " unrecognized commands is too many";
1538 out_() << "500 5.5.1 command unrecognized: \"" << escaped
<< "\"\r\n"
1542 void Session::bare_lf()
1544 // Error code used by Office 365.
1545 out_() << "554 5.6.11 bare LF\r\n" << std::flush
;
1546 LOG(WARNING
) << "bare LF";
1550 void Session::max_out()
1552 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1553 LOG(WARNING
) << "message size maxed out";
1557 void Session::time_out()
1559 out_() << "421 4.4.2 time-out\r\n" << std::flush
;
1560 LOG(WARNING
) << "time-out" << (sock_
.has_peername() ? " from " : "")
1565 void Session::starttls()
1567 last_in_group_("STARTTLS");
1569 out_() << "554 5.5.1 TLS already active\r\n" << std::flush
;
1570 LOG(WARNING
) << "STARTTLS issued with TLS already active";
1572 else if (!extensions_
) {
1573 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1575 LOG(WARNING
) << "STARTTLS issued without using EHLO";
1578 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush
;
1579 if (sock_
.starttls_server(config_path_
)) {
1581 max_msg_size(Config::max_msg_size_bro
);
1582 LOG(INFO
) << "STARTTLS " << sock_
.tls_info();
1585 LOG(INFO
) << "failed STARTTLS";
1590 void Session::exit_()
1592 // sock_.log_totals();
1594 timespec time_used
{};
1595 clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &time_used
);
1597 LOG(INFO
) << "CPU time " << time_used
.tv_sec
<< "." << std::setw(9)
1598 << std::setfill('0') << time_used
.tv_nsec
<< " seconds";
1600 std::exit(EXIT_SUCCESS
);
1603 /////////////////////////////////////////////////////////////////////////////
1605 // All of the verify_* functions send their own error messages back to
1606 // the client on failure, and return false.
1608 bool Session::verify_ip_address_(std::string
& error_msg
)
1610 auto ip_block_db_name
= config_path_
/ "ip-block";
1612 if (ip_block
.open(ip_block_db_name
) &&
1613 ip_block
.contains(sock_
.them_c_str())) {
1615 fmt::format("IP address {} on static blocklist", sock_
.them_c_str());
1616 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1620 client_fcrdns_
.clear();
1622 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1623 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1624 LOG(INFO
) << "loopback address allowed";
1626 client_fcrdns_
.emplace_back("localhost");
1627 client_
= fmt::format("localhost {}", sock_
.them_address_literal());
1631 auto const fcrdns
= DNS::fcrdns(res_
, sock_
.them_c_str());
1632 for (auto const& fcr
: fcrdns
) {
1633 client_fcrdns_
.emplace_back(fcr
);
1636 if (IP::is_private(sock_
.them_address_literal())) {
1637 LOG(INFO
) << "private address allowed";
1639 client_
= sock_
.them_address_literal();
1643 if (!client_fcrdns_
.empty()) {
1644 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1645 sock_
.them_address_literal());
1647 for (auto const& client_fcrdns
: client_fcrdns_
) {
1648 if (allow_
.contains(client_fcrdns
.ascii())) {
1649 LOG(INFO
) << "FCrDNS " << client_fcrdns
<< " allowed";
1650 fcrdns_allowed_
= true;
1653 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1655 if (allow_
.contains(tld
)) {
1656 LOG(INFO
) << "FCrDNS registered domain " << tld
<< " allowed";
1657 fcrdns_allowed_
= true;
1663 for (auto const& client_fcrdns
: client_fcrdns_
) {
1664 if (block_
.contains(client_fcrdns
.ascii())) {
1666 fmt::format("FCrDNS {} on static blocklist", client_fcrdns
.ascii());
1667 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1671 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1673 if (block_
.contains(tld
)) {
1674 error_msg
= fmt::format(
1675 "FCrDNS registered domain {} on static blocklist", tld
);
1676 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1683 client_
= fmt::format("{}", sock_
.them_address_literal());
1686 if (IP4::is_address(sock_
.them_c_str())) {
1688 auto const reversed
{IP4::reverse(sock_
.them_c_str())};
1691 // Check with allow list.
1692 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1695 for (auto wl : Config::wls) {
1696 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1697 if (q.has_record()) {
1698 using namespace boost::xpressive;
1700 auto const as = q.get_strings()[0];
1701 LOG(INFO) << "on allow list " << wl << " as " << as;
1705 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1708 if (regex_match(as, what, rex)) {
1709 auto const x = what[x_].str();
1710 auto const y = what[y_].str();
1713 std::from_chars(y.data(), y.data() + y.size(), value);
1716 LOG(INFO) << "allowed";
1720 LOG(INFO) << "Any A record skips check on block list";
1726 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1727 std::shuffle(std::begin(Config::bls
), std::end(Config::bls
),
1730 for (auto bl
: Config::bls
) {
1732 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ bl
);
1733 if (q
.has_record()) {
1734 auto const as
= q
.get_strings()[0];
1735 if (as
== "127.0.1.1") {
1736 LOG(INFO
) << "Query blocked by " << bl
;
1738 else if (as
== "127.0.0.10" || as
== "127.0.0.11") {
1739 LOG(INFO
) << "PBL listed, ignoring " << bl
;
1741 else if (as
== "127.255.255.252") {
1742 LOG(INFO
) << "Typing error in DNSBL name " << bl
;
1744 else if (as
== "127.255.255.254") {
1745 LOG(INFO
) << "Anonymous query through public resolver " << bl
;
1747 else if (as
== "127.255.255.255") {
1748 LOG(INFO
) << "Excessive number of queries " << bl
;
1751 error_msg
= fmt::format("IP address {} blocked: {} returned {}",
1752 sock_
.them_c_str(), bl
, as
);
1753 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1758 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1761 LOG(INFO
) << "IP address okay";
1765 bool domain_blocked(DNS::Resolver
& res
, Domain
const& identity
)
1767 Domain lookup
{fmt::format("{}.dbl.spamhaus.org", identity
.ascii())};
1768 DNS::Query
q(res
, DNS::RR_type::A
, lookup
.ascii());
1769 if (q
.has_record()) {
1770 auto const as
= q
.get_strings()[0];
1771 if (istarts_with(as
, "127.0.1.")) {
1772 LOG(INFO
) << "Domain " << identity
<< " blocked by spamhaus";
1779 // check the identity from HELO/EHLO
1780 bool Session::verify_client_(Domain
const& client_identity
,
1781 std::string
& error_msg
)
1783 if (!client_fcrdns_
.empty()) {
1784 if (auto id
= std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
1786 id
!= end(client_fcrdns_
)) {
1787 // If the HELO ident is one of the FCrDNS names...
1788 if (id
!= begin(client_fcrdns_
)) {
1789 // ...then rotate that one to the front of the list
1790 std::rotate(begin(client_fcrdns_
), id
, id
+ 1);
1792 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1793 sock_
.them_address_literal());
1796 LOG(INFO
) << "claimed identity " << client_identity
1797 << " does NOT match any FCrDNS: ";
1798 for (auto const& client_fcrdns
: client_fcrdns_
) {
1799 LOG(INFO
) << " " << client_fcrdns
;
1803 // Bogus clients claim to be us or some local host.
1804 if (sock_
.has_peername() && ((client_identity
== server_identity_
) ||
1805 (client_identity
== "localhost") ||
1806 (client_identity
== "localhost.localdomain"))) {
1808 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1809 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1815 LOG(INFO
) << "allow-listed IP address can claim to be "
1820 // Ease up in test mode.
1821 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1825 error_msg
= fmt::format("liar, claimed to be {}", client_identity
.ascii());
1826 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1830 std::vector
<std::string
> labels
;
1831 boost::algorithm::split(labels
, client_identity
.ascii(),
1832 boost::algorithm::is_any_of("."));
1833 if (labels
.size() < 2) {
1835 fmt::format("claimed bogus identity {}", client_identity
.ascii());
1836 out_() << "550 4.7.1 bogus identity\r\n" << std::flush
;
1838 // // Sometimes we may want to look at mail from non conforming
1839 // // sending systems.
1840 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1841 // << client_ << " claiming " << client_identity;
1845 if (lookup_domain(block_
, client_identity
)) {
1847 fmt::format("claimed blocked identity {}", client_identity
.ascii());
1848 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1852 auto const tld
{tld_db_
.get_registered_domain(client_identity
.ascii())};
1854 // Sometimes we may want to look at mail from misconfigured
1856 // LOG(WARNING) << "claimed identity has no registered domain";
1859 else if (block_
.contains(tld
)) {
1861 fmt::format("claimed identity has blocked registered domain {}", tld
);
1862 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush
;
1866 if (domain_blocked(res_
, client_identity
) ||
1867 domain_blocked(res_
, Domain(tld
))) {
1868 error_msg
= fmt::format("claimed identity {} blocked by spamhaus",
1869 client_identity
.ascii());
1870 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1874 DNS::Query
q(res_
, DNS::RR_type::A
, client_identity
.ascii());
1875 if (!q
.has_record()) {
1876 LOG(WARNING
) << "claimed identity " << client_identity
.ascii()
1877 << " not DNS resolvable";
1880 // not otherwise objectionable
1884 // check sender from RFC5321 MAIL FROM:
1885 bool Session::verify_sender_(Mailbox
const& sender
, std::string
& error_msg
)
1887 do_spf_check_(sender
);
1889 std::string
const sender_str
{sender
};
1891 if (sender
.empty()) {
1893 // is used to send bounce messages.
1897 if (domain_blocked(res_
, sender
.domain())) {
1898 error_msg
= fmt::format("{} sender domain blocked by spamhaus", sender_str
);
1899 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1903 auto bad_senders_db_name
= config_path_
/ "bad_senders";
1905 if (bad_senders
.open(bad_senders_db_name
) &&
1906 bad_senders
.contains(sender_str
)) {
1907 error_msg
= fmt::format("{} bad sender", sender_str
);
1908 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1912 // We don't accept mail /from/ a domain we are expecting to accept
1913 // mail for on an external network connection.
1915 if (sock_
.them_address_literal() != sock_
.us_address_literal()) {
1916 if ((accept_domains_
.is_open() &&
1917 (accept_domains_
.contains(sender
.domain().ascii()) ||
1918 accept_domains_
.contains(sender
.domain().utf8()))) ||
1919 (sender
.domain() == server_identity_
)) {
1921 // Ease up in test mode.
1922 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1925 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1926 error_msg
= fmt::format("liar, claimed to be {}", sender
.domain());
1931 if (sender
.domain().is_address_literal()) {
1932 if (sender
.domain() != sock_
.them_address_literal()) {
1933 LOG(WARNING
) << "sender domain " << sender
.domain() << " does not match "
1934 << sock_
.them_address_literal();
1939 if (!verify_sender_domain_(sender
.domain(), error_msg
)) {
1946 // this sender is the RFC5321 MAIL FROM: domain part
1947 bool Session::verify_sender_domain_(Domain
const& sender
,
1948 std::string
& error_msg
)
1950 if (sender
.empty()) {
1952 // is used to send bounce messages.
1956 // Break sender domain into labels:
1958 std::vector
<std::string
> labels
;
1959 boost::algorithm::split(labels
, sender
.ascii(),
1960 boost::algorithm::is_any_of("."));
1962 if (labels
.size() < 2) { // This is not a valid domain.
1963 error_msg
= fmt::format("{} invalid syntax", sender
.ascii());
1964 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1968 if (lookup_domain(block_
, sender
)) {
1970 fmt::format("SPF sender domain ({}) is blocked", spf_sender_domain_
);
1971 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1975 if (spf_result_
== SPF::Result::PASS
) {
1976 if (allow_
.contains(spf_sender_domain_
.ascii())) {
1977 LOG(INFO
) << "sender " << spf_sender_domain_
.ascii() << " allowed";
1982 tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
1984 if (allow_
.contains(reg_dom
)) {
1985 LOG(INFO
) << "sender registered domain \"" << reg_dom
<< "\" allowed";
1991 LOG(INFO
) << "sender \"" << sender
<< "\" not disallowed";
1995 void Session::do_spf_check_(Mailbox
const& sender
)
1997 if (!sock_
.has_peername()) {
1998 auto const ip_addr
= "127.0.0.1"; // use localhost for local socket
2000 fmt::format("Received-SPF: pass ({}: allow-listed) client-ip={}; "
2001 "envelope-from={}; helo={};",
2002 server_id_(), ip_addr
, sender
, client_identity_
);
2003 spf_sender_domain_
= "localhost";
2007 auto const spf_srv
= SPF::Server
{server_id_().c_str()};
2008 auto spf_request
= SPF::Request
{spf_srv
};
2010 if (IP4::is_address(sock_
.them_c_str())) {
2011 spf_request
.set_ipv4_str(sock_
.them_c_str());
2013 else if (IP6::is_address(sock_
.them_c_str())) {
2014 spf_request
.set_ipv6_str(sock_
.them_c_str());
2017 LOG(FATAL
) << "bogus address " << sock_
.them_address_literal() << ", "
2018 << sock_
.them_c_str();
2021 auto const from
{static_cast<std::string
>(sender
)};
2023 spf_request
.set_env_from(from
.c_str());
2024 spf_request
.set_helo_dom(client_identity_
.ascii().c_str());
2026 auto const spf_res
{SPF::Response
{spf_request
}};
2027 spf_result_
= spf_res
.result();
2028 spf_received_
= spf_res
.received_spf();
2029 spf_sender_domain_
= spf_request
.get_sender_dom();
2031 LOG(INFO
) << "spf_received_ == " << spf_received_
;
2033 if (spf_result_
== SPF::Result::FAIL
) {
2034 LOG(INFO
) << "FAIL " << spf_res
.header_comment();
2036 else if (spf_result_
== SPF::Result::NEUTRAL
) {
2037 LOG(INFO
) << "NEUTRAL " << spf_res
.header_comment();
2039 else if (spf_result_
== SPF::Result::PASS
) {
2040 LOG(INFO
) << "PASS " << spf_res
.header_comment();
2043 LOG(INFO
) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
2047 bool Session::verify_from_params_(parameters_t
const& parameters
)
2049 // Take a look at the optional parameters:
2050 for (auto const& [name
, value
] : parameters
) {
2051 if (iequal(name
, "BODY")) {
2052 if (iequal(value
, "8BITMIME")) {
2053 // everything is cool, this is our default...
2055 else if (iequal(value
, "7BIT")) {
2056 // nothing to see here, move along...
2058 else if (iequal(value
, "BINARYMIME")) {
2062 LOG(WARNING
) << "unrecognized BODY type \"" << value
<< "\" requested";
2065 else if (iequal(name
, "SMTPUTF8")) {
2066 if (!value
.empty()) {
2067 LOG(WARNING
) << "SMTPUTF8 parameter has a value: " << value
;
2072 // else if (iequal(name, "PRDR")) {
2073 // LOG(INFO) << "using PRDR";
2077 else if (iequal(name
, "SIZE")) {
2078 if (value
.empty()) {
2079 LOG(WARNING
) << "SIZE parameter has no value.";
2083 auto const sz
= stoull(value
);
2084 if (sz
> max_msg_size()) {
2085 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
2086 LOG(WARNING
) << "SIZE parameter too large: " << sz
;
2090 catch (std::invalid_argument
const& e
) {
2091 LOG(WARNING
) << "SIZE parameter has invalid value: " << value
;
2093 catch (std::out_of_range
const& e
) {
2094 LOG(WARNING
) << "SIZE parameter has out-of-range value: " << value
;
2096 // I guess we just ignore bad size parameters.
2099 else if (iequal(name
, "REQUIRETLS")) {
2101 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush
;
2102 LOG(WARNING
) << "REQUIRETLS needed";
2107 LOG(WARNING
) << "unrecognized 'MAIL FROM' parameter " << name
<< "="
2115 bool Session::verify_rcpt_params_(parameters_t
const& parameters
)
2117 // Take a look at the optional parameters:
2118 for (auto const& [name
, value
] : parameters
) {
2119 if (iequal(name
, "RRVS")) {
2120 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
2121 LOG(INFO
) << name
<< "=" << value
;
2124 LOG(WARNING
) << "unrecognized 'RCPT TO' parameter " << name
<< "="
2132 // check recipient from RFC5321 RCPT TO:
2133 bool Session::verify_recipient_(Mailbox
const& recipient
)
2135 if ((recipient
.local_part() == "Postmaster") && (recipient
.domain() == "")) {
2136 LOG(INFO
) << "magic Postmaster address";
2140 auto const accepted_domain
{[this, &recipient
] {
2141 if (recipient
.domain().is_address_literal()) {
2142 if (recipient
.domain() != sock_
.us_address_literal()) {
2143 LOG(WARNING
) << "recipient.domain address " << recipient
.domain()
2144 << " does not match ours " << sock_
.us_address_literal();
2152 // Domains we accept mail for.
2153 if (accept_domains_
.is_open()) {
2154 if (accept_domains_
.contains(recipient
.domain().ascii()) ||
2155 accept_domains_
.contains(recipient
.domain().utf8())) {
2160 // If we have no list of domains to accept, at least take our own.
2161 if (recipient
.domain() == server_id_()) {
2169 if (!accepted_domain
) {
2170 out_() << "550 5.7.1 relay access denied\r\n" << std::flush
;
2171 LOG(WARNING
) << "relay access denied for domain " << recipient
.domain();
2175 // Check for local addresses we reject.
2177 auto bad_recipients_db_name
= config_path_
/ "bad_recipients";
2178 CDB bad_recipients_db
;
2179 if (bad_recipients_db
.open(bad_recipients_db_name
) &&
2180 bad_recipients_db
.contains(recipient
.local_part())) {
2181 out_() << "550 5.1.1 bad recipient " << recipient
<< "\r\n" << std::flush
;
2182 LOG(WARNING
) << "bad recipient " << recipient
;
2188 auto fail_db_name
= config_path_
/ "fail_554";
2189 if (fs::exists(fail_db_name
)) {
2191 if (fail_db
.open(fail_db_name
) &&
2192 fail_db
.contains(recipient
.local_part())) {
2193 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2196 LOG(WARNING
) << "fail_554 recipient " << recipient
;
2203 auto temp_fail_db_name
= config_path_
/ "temp_fail";
2205 if (temp_fail
.open(temp_fail_db_name
) &&
2206 temp_fail
.contains(recipient
.local_part())) {
2207 out_() << "432 4.3.0 recipient's incoming mail queue has been stopped\r\n"
2209 LOG(WARNING
) << "temp fail for recipient " << recipient
;
2214 // Check for and act on magic "wait" address.
2216 using namespace boost::xpressive
;
2218 sregex
const rex
= icase("wait-rcpt-") >> (secs_
= +_d
);
2221 if (regex_match(recipient
.local_part(), what
, rex
) ||
2222 regex_match(recipient
.local_part(), what
, all_rex
)) {
2223 auto const str
= what
[secs_
].str();
2224 LOG(INFO
) << "waiting at RCPT TO " << str
<< " seconds";
2226 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
2227 google::FlushLogFiles(google::INFO
);
2228 out_() << std::flush
;
2230 LOG(INFO
) << "done waiting";
2234 // This is a trap for a probe done by some senders to see if we
2235 // accept just any old local-part.
2237 if (recipient
.local_part().length() > 8) {
2238 out_() << "550 5.1.1 unknown recipient " << recipient
<< "\r\n"
2240 LOG(WARNING
) << "unknown recipient for HELO " << recipient
;