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#200>
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",
720 reverse_path_
.as_string());
723 if (!spf_received_
.empty()) {
724 fmt::format_to(std::back_inserter(headers
), "{}\r\n", spf_received_
);
728 // <https://tools.ietf.org/html/rfc5321#section-4.4>
729 fmt::format_to(std::back_inserter(headers
), "Received: from {}",
730 client_identity_
.utf8());
731 if (sock_
.has_peername()) {
732 fmt::format_to(std::back_inserter(headers
), " ({})", client_
);
734 fmt::format_to(std::back_inserter(headers
), "\r\n\tby {} with {} id {}",
735 server_identity_
.utf8(), protocol
, msg
.id().as_string_view());
736 if (forward_path_
.size()) {
737 fmt::format_to(std::back_inserter(headers
), "\r\n\tfor <{}>",
738 forward_path_
[0].as_string());
739 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
740 // “If the FOR clause appears, it MUST contain exactly one <path>
741 // entry, even when multiple RCPT commands have been given. Multiple
742 // <path>s raise some security issues and have been deprecated, see
744 // for (auto i = 1u; i < forward_path_.size(); ++i)
745 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
747 std::string
const tls_info
{sock_
.tls_info()};
748 if (tls_info
.length()) {
749 fmt::format_to(std::back_inserter(headers
), "\r\n\t({})", tls_info
);
751 fmt::format_to(std::back_inserter(headers
), ";\r\n\t{}\r\n",
752 msg
.when().as_string_view());
754 return fmt::to_string(headers
);
758 bool lookup_domain(CDB
& cdb
, Domain
const& domain
)
760 if (!domain
.empty()) {
761 if (cdb
.contains(domain
.ascii())) {
764 if (domain
.is_unicode() && cdb
.contains(domain
.utf8())) {
772 std::tuple
<Session::SpamStatus
, std::string
> Session::spam_status_()
774 if (spf_result_
== SPF::Result::FAIL
&& !ip_allowed_
)
775 return {SpamStatus::spam
, "SPF failed"};
777 // These should have already been rejected by verify_client_().
778 if ((reverse_path_
.domain() == "localhost.local") ||
779 (reverse_path_
.domain() == "localhost"))
780 return {SpamStatus::spam
, "bogus reverse_path"};
782 std::vector
<std::string
> why_ham
;
784 // Anything enciphered tastes a lot like ham.
786 why_ham
.emplace_back("they used TLS");
788 if (spf_result_
== SPF::Result::PASS
) {
789 if (lookup_domain(allow_
, spf_sender_domain_
)) {
790 why_ham
.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
791 spf_sender_domain_
.utf8()));
794 auto tld_dom
{tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
795 if (tld_dom
&& allow_
.contains(tld_dom
)) {
796 why_ham
.emplace_back(fmt::format(
797 "SPF sender registered domain ({}) is allowed", tld_dom
));
803 why_ham
.emplace_back(
804 fmt::format("FCrDNS (or it's registered domain) is allowed"));
806 if (!why_ham
.empty())
807 return {SpamStatus::ham
,
808 fmt::format("{}", fmt::join(std::begin(why_ham
), std::end(why_ham
),
811 return {SpamStatus::spam
, "it's not ham"};
814 static std::string
folder(Session::SpamStatus status
,
815 std::vector
<Mailbox
> const& forward_path
,
816 Mailbox
const& reverse_path
)
819 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
822 if (reverse_path
== Mailbox("ietf-smtp-bounces@ietf.org"))
826 std::string_view local_part
;
827 std::string_view folder
;
830 assignment assignments
[] = {
831 {"Emailcore", ".emailcore"},
832 {"bootstrappable", ".bootstrappable"},
833 {"coreboot.org", ".coreboot"},
835 {"dns-privacy", ".dns-privacy"},
836 {"fucking-facebook", ".FB"},
837 {"gene-ebay", ".EBay"},
838 {"i-hate-linked-in", ".linkedin"},
839 {"mailop", ".INBOX.mailop"},
840 {"modelfkeyboards.com", ""},
841 {"nest", ".INBOX.Nest"},
842 {"opendmarc-dev", ".dmarc"},
843 {"opendmarc-users", ".dmarc"},
844 {"postmaster-rua", ".INBOX.rua"},
845 {"shadowserver-reports@digilicious.com", ".INBOX.shadowserver"},
846 {"theatlantic.com", ""},
847 {"time-nutz", ".time-nutz"},
848 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
851 for (auto ass
: assignments
) {
852 if (forward_path
[0].local_part() == ass
.local_part
)
853 return std::string(ass
.folder
);
856 if (iends_with(forward_path
[0].local_part(), "-at-duck"))
859 if (status
== Session::SpamStatus::spam
)
865 bool Session::msg_new()
867 CHECK((state_
== xact_step::data
) || (state_
== xact_step::bdat
));
869 auto const& [status
, reason
]{spam_status_()};
871 LOG(INFO
) << ((status
== SpamStatus::ham
) ? "ham since " : "spam since ")
874 // All sources of ham get a fresh 5 minute timeout per message.
875 if (status
== SpamStatus::ham
) {
876 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr))
880 msg_
= std::make_unique
<MessageStore
>();
882 if (!FLAGS_max_write
)
883 FLAGS_max_write
= max_msg_size();
886 msg_
->open(server_id_(), FLAGS_max_write
,
887 folder(status
, forward_path_
, reverse_path_
));
888 auto const hdrs
{added_headers_(*(msg_
.get()))};
891 // fmt::memory_buffer spam_status;
892 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
893 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
894 // msg_->write(spam_status.data(), spam_status.size());
896 LOG(INFO
) << "Spam-Status: "
897 << ((status
== SpamStatus::spam
) ? "Yes" : "No") << ", "
902 catch (std::system_error
const& e
) {
905 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
906 LOG(ERROR
) << "no space";
912 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
913 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
914 LOG(ERROR
) << e
.what();
920 catch (std::exception
const& e
) {
921 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
922 LOG(ERROR
) << e
.what();
928 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
929 LOG(ERROR
) << "msg_new failed with no exception caught";
935 bool Session::msg_write(char const* s
, std::streamsize count
)
937 if ((state_
!= xact_step::data
) && (state_
!= xact_step::bdat
))
944 if (msg_
->write(s
, count
))
947 catch (std::system_error
const& e
) {
950 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
951 LOG(ERROR
) << "no space";
957 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
958 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
959 LOG(ERROR
) << e
.what();
965 catch (std::exception
const& e
) {
966 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
967 LOG(ERROR
) << e
.what();
973 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
974 LOG(ERROR
) << "msg_write failed with no exception caught";
980 bool Session::data_start()
982 last_in_group_("DATA");
985 case xact_step::helo
:
986 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
987 LOG(WARNING
) << "'DATA' before HELO/EHLO"
988 << (sock_
.has_peername() ? " from " : "") << client_
;
990 case xact_step::mail
:
991 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
992 LOG(WARNING
) << "'DATA' before 'MAIL FROM'"
993 << (sock_
.has_peername() ? " from " : "") << client_
;
995 case xact_step::rcpt
:
997 /******************************************************************
998 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
1000 The DATA command can fail at only two points in the protocol exchange:
1002 If there was no MAIL, or no RCPT, command, or all such commands were
1003 rejected, the server MAY return a "command out of sequence" (503) or
1004 "no valid recipients" (554) reply in response to the DATA command.
1006 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
1008 The additional restriction is that when there have been no successful
1009 RCPT commands in the mail transaction, the DATA command MUST fail
1010 with a 503 reply code.
1012 Therefore I will send the reply code that is valid for both, and
1013 do the same for the BDAT case.
1014 *******************************************************************/
1016 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1017 LOG(WARNING
) << "no valid recipients"
1018 << (sock_
.has_peername() ? " from " : "") << client_
;
1020 case xact_step::data
: break;
1021 case xact_step::bdat
:
1022 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
1023 LOG(WARNING
) << "'DATA' during BDAT transfer"
1024 << (sock_
.has_peername() ? " from " : "") << client_
;
1026 case xact_step::rset
:
1027 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1028 LOG(WARNING
) << "error state must be cleared with a RSET"
1029 << (sock_
.has_peername() ? " from " : "") << client_
;
1034 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
1036 LOG(WARNING
) << "DATA does not support BINARYMIME";
1037 state_
= xact_step::rset
; // RFC 3030 section 3 page 5
1042 LOG(ERROR
) << "msg_new() failed";
1046 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush
;
1047 LOG(INFO
) << "DATA";
1051 // bool Session::do_forward_(message::parsed& msg)
1053 // auto msg_fwd = msg;
1055 // // Generate a reply address
1056 // Reply::from_to reply;
1057 // reply.mail_from = msg_fwd.dmarc_from;
1058 // reply.rcpt_to_local_part = fwd_from_.local_part();
1060 // auto const reply_addr =
1061 // fmt::format("{}@{}", srs_.enc_reply(reply), server_id_());
1063 // auto const munging = false;
1065 // auto const sender = server_identity_.ascii().c_str();
1066 // auto const selector = FLAGS_selector.c_str();
1067 // auto const key_file =
1068 // (config_path_ / FLAGS_selector).replace_extension("private");
1069 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1072 // auto const from_hdr =
1073 // fmt::format("From: \"{} via\" <@>", msg_fwd.dmarc_from, reply_addr);
1074 // message::rewrite_from_to(msg_fwd, from_hdr, "", sender, selector,
1078 // auto const reply_to_hdr = fmt::format("Reply-To: {}", reply_addr);
1079 // message::rewrite_from_to(msg_fwd, "", reply_to_hdr, sender, selector,
1084 // if (!send_.send(msg_fwd.as_string())) {
1085 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1089 // LOG(ERROR) << "failed to send for " << fwd_path_;
1093 // LOG(INFO) << "successfully sent for " << fwd_path_;
1097 // bool Session::do_reply_(message::parsed& msg)
1099 // Mailbox to_mbx(rep_info_.mail_from);
1100 // Mailbox from_mbx(rep_info_.rcpt_to_local_part, server_identity_);
1102 // auto reply = std::make_unique<MessageStore>();
1103 // reply->open(server_id_(), FLAGS_max_write, ".Drafts");
1105 // auto const date{Now{}};
1106 // auto const pill{Pill{}};
1107 // auto const mid_str =
1108 // fmt::format("<{}.{}@{}>", date.sec(), pill, server_identity_);
1110 // fmt::memory_buffer bfr;
1112 // fmt::format_to(bfr, "From: <{}>\r\n", from_mbx);
1113 // fmt::format_to(bfr, "To: <{}>\r\n", to_mbx);
1115 // fmt::format_to(bfr, "Date: {}\r\n", date.c_str());
1117 // fmt::format_to(bfr, "Message-ID: {}\r\n", mid_str.c_str());
1119 // if (!msg.get_header(message::Subject).empty()) {
1120 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1121 // msg.get_header(message::Subject));
1124 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1125 // "Reply to your message");
1128 // if (!msg.get_header(message::In_Reply_To).empty()) {
1129 // fmt::format_to(bfr, "{}: {}\r\n", message::In_Reply_To,
1130 // msg.get_header(message::In_Reply_To));
1133 // if (!msg.get_header(message::MIME_Version).empty() &&
1134 // msg.get_header(message::Content_Type).empty()) {
1135 // fmt::format_to(bfr, "{}: {}\r\n", message::MIME_Version,
1136 // msg.get_header(message::MIME_Version));
1137 // fmt::format_to(bfr, "{}: {}\r\n", message::Content_Type,
1138 // msg.get_header(message::Content_Type));
1141 // reply->write(fmt::to_string(bfr));
1143 // if (!msg.body.empty()) {
1144 // reply->write("\r\n");
1145 // reply->write(msg.body);
1148 // auto const msg_data = reply->freeze();
1149 // message::parsed msg_reply;
1150 // CHECK(msg_reply.parse(msg_data));
1152 // auto const sender = server_identity_.ascii().c_str();
1153 // auto const selector = FLAGS_selector.c_str();
1154 // auto const key_file =
1155 // (config_path_ / FLAGS_selector).replace_extension("private");
1156 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1158 // message::dkim_sign(msg_reply, sender, selector, key_file);
1160 // if (!send_.send(msg_reply.as_string())) {
1161 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1165 // LOG(ERROR) << "send failed for reply to " << to_mbx << " from " <<
1166 // from_mbx; return false;
1169 // LOG(INFO) << "successful reply to " << to_mbx << " from " << from_mbx;
1173 bool Session::do_deliver_()
1177 // auto const sender = server_identity_.ascii().c_str();
1178 // auto const selector = FLAGS_selector.c_str();
1179 // auto const key_file =
1180 // (config_path_ / FLAGS_selector).replace_extension("private");
1181 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1184 // auto const msg_data = msg_->freeze();
1186 // message::parsed msg;
1188 // // Only deal in RFC-5322 Mail Objects.
1189 // bool const message_parsed = msg.parse(msg_data);
1190 // if (message_parsed) {
1192 // // remove any Return-Path
1193 // message::remove_delivery_headers(msg);
1195 // auto const authentic =
1196 // message_parsed &&
1197 // message::authentication(msg, sender, selector, key_file);
1199 // // write a new Return-Path
1200 // msg_->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_));
1202 // for (auto const h : msg.headers) {
1203 // msg_->write(h.as_string());
1204 // msg_->write("\r\n");
1206 // if (!msg.body.empty()) {
1207 // msg_->write("\r\n");
1208 // msg_->write(msg.body);
1213 // if (authentic && !fwd_path_.empty()) {
1214 // if (!do_forward_(msg))
1217 // if (authentic && !rep_info_.empty()) {
1218 // if (!do_reply_(msg))
1225 catch (std::system_error
const& e
) {
1228 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
1229 LOG(ERROR
) << "no space";
1235 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
1237 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
1238 LOG(ERROR
) << e
.what();
1248 void Session::data_done()
1250 CHECK((state_
== xact_step::data
));
1252 if (msg_
&& msg_
->size_error()) {
1258 // out_() << "353\r\n";
1259 // for (auto fp : forward_path_) {
1260 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1264 // Check for and act on magic "wait" address.
1266 using namespace boost::xpressive
;
1268 sregex
const rex
= icase("wait-data-") >> (secs_
= +_d
);
1271 for (auto fp
: forward_path_
) {
1272 if (regex_match(fp
.local_part(), what
, rex
) ||
1273 regex_match(fp
.local_part(), what
, all_rex
)) {
1274 auto const str
= what
[secs_
].str();
1275 LOG(INFO
) << "waiting at DATA " << str
<< " seconds";
1277 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1278 google::FlushLogFiles(google::INFO
);
1279 out_() << std::flush
;
1281 LOG(INFO
) << "done waiting";
1286 if (do_deliver_()) {
1287 auto temp_fail_db_name
= config_path_
/ "temp_fail_data";
1290 for (auto fp
: forward_path_
) {
1291 if (temp_fail
.open(temp_fail_db_name
) &&
1292 temp_fail
.contains(fp
.local_part())) {
1293 out_() << "450 4.2.2 Mailbox full.\r\n" << std::flush
;
1294 LOG(WARNING
) << "temp fail at DATA for recipient " << fp
;
1301 // Check for addresses we reject after data.
1303 auto bad_recipients_db_name
= config_path_
/ "bad_recipients_data";
1304 CDB bad_recipients_db
;
1305 if (bad_recipients_db
.open(bad_recipients_db_name
)) {
1306 for (auto fp
: forward_path_
) {
1307 if (bad_recipients_db
.contains(fp
.local_part())) {
1308 out_() << "550 5.1.1 bad recipient " << fp
<< "\r\n" << std::flush
;
1309 LOG(WARNING
) << "bad recipient " << fp
;
1314 LOG(INFO
) << "unbad recipient " << fp
.local_part();
1319 LOG(WARNING
) << "can't open bad_recipients_data";
1323 out_() << "250 2.0.0 DATA OK\r\n" << std::flush
;
1324 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1329 void Session::data_size_error()
1331 out_().clear(); // clear possible eof from input side
1332 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1336 LOG(WARNING
) << "DATA size error";
1340 void Session::data_error()
1342 out_().clear(); // clear possible eof from input side
1343 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush
;
1347 LOG(WARNING
) << "DATA error";
1351 bool Session::bdat_start(size_t n
)
1353 // In practice, this one gets pipelined.
1354 // last_in_group_("BDAT");
1357 case xact_step::helo
:
1358 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
1359 LOG(WARNING
) << "'BDAT' before HELO/EHLO"
1360 << (sock_
.has_peername() ? " from " : "") << client_
;
1362 case xact_step::mail
:
1363 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
1364 LOG(WARNING
) << "'BDAT' before 'MAIL FROM'"
1365 << (sock_
.has_peername() ? " from " : "") << client_
;
1367 case xact_step::rcpt
:
1368 // See comment in data_start()
1369 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1370 LOG(WARNING
) << "no valid recipients"
1371 << (sock_
.has_peername() ? " from " : "") << client_
;
1373 case xact_step::data
: // first bdat
1375 case xact_step::bdat
: return true;
1376 case xact_step::rset
:
1377 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1378 LOG(WARNING
) << "error state must be cleared with a RSET"
1379 << (sock_
.has_peername() ? " from " : "") << client_
;
1383 state_
= xact_step::bdat
;
1388 void Session::bdat_done(size_t n
, bool last
)
1390 if (state_
!= xact_step::bdat
) {
1399 if (msg_
->size_error()) {
1405 out_() << "250 2.0.0 BDAT " << n
<< " OK\r\n" << std::flush
;
1406 LOG(INFO
) << "BDAT " << n
;
1410 // Check for and act on magic "wait" address.
1412 using namespace boost::xpressive
;
1414 sregex
const rex
= icase("wait-bdat-") >> (secs_
= +_d
);
1417 for (auto fp
: forward_path_
) {
1418 if (regex_match(fp
.local_part(), what
, rex
) ||
1419 regex_match(fp
.local_part(), what
, all_rex
)) {
1420 auto const str
= what
[secs_
].str();
1421 LOG(INFO
) << "waiting at BDAT " << str
<< " seconds";
1423 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1424 google::FlushLogFiles(google::INFO
);
1425 out_() << std::flush
;
1427 LOG(INFO
) << "done waiting";
1434 out_() << "250 2.0.0 BDAT " << n
<< " LAST OK\r\n" << std::flush
;
1435 LOG(INFO
) << "BDAT " << n
<< " LAST";
1436 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1441 void Session::bdat_size_error()
1443 out_().clear(); // clear possible eof from input side
1444 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1448 LOG(WARNING
) << "BDAT size error";
1452 void Session::bdat_seq_error()
1454 out_().clear(); // clear possible eof from input side
1455 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush
;
1459 LOG(WARNING
) << "BDAT sequence error";
1463 void Session::bdat_io_error()
1465 out_().clear(); // clear possible eof from input side
1466 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush
;
1470 LOG(WARNING
) << "BDAT I/O error";
1474 void Session::rset()
1476 out_() << "250 2.1.5 RSET OK\r\n";
1477 // No flush RFC-2920 section 3.1, this could be part of a command group.
1478 LOG(INFO
) << "RSET";
1482 void Session::noop(std::string_view str
)
1484 last_in_group_("NOOP");
1485 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush
;
1486 LOG(INFO
) << "NOOP" << (str
.length() ? " " : "") << str
;
1489 void Session::vrfy(std::string_view str
)
1491 last_in_group_("VRFY");
1492 out_() << "252 2.1.5 try it\r\n" << std::flush
;
1493 LOG(INFO
) << "VRFY" << (str
.length() ? " " : "") << str
;
1496 void Session::help(std::string_view str
)
1498 if (iequal(str
, "help\r\n")) {
1499 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush
;
1502 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1505 LOG(INFO
) << "HELP" << (str
.length() ? " " : "") << str
;
1508 void Session::quit()
1511 // last_in_group_("QUIT");
1512 out_() << "221 2.0.0 closing connection\r\n" << std::flush
;
1513 LOG(INFO
) << "QUIT";
1517 void Session::auth()
1519 out_() << "454 4.7.0 authentication failure\r\n" << std::flush
;
1520 LOG(INFO
) << "AUTH";
1524 void Session::error(std::string_view log_msg
)
1526 out_() << "421 4.3.5 system error: " << log_msg
<< "\r\n" << std::flush
;
1527 LOG(WARNING
) << log_msg
;
1530 void Session::cmd_unrecognized(std::string_view cmd
)
1532 auto const escaped
{esc(cmd
)};
1533 LOG(WARNING
) << "command unrecognized: \"" << escaped
<< "\"";
1535 if (++n_unrecognized_cmds_
>= Config::max_unrecognized_cmds
) {
1536 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1537 << "\" exceeds limit\r\n"
1539 LOG(WARNING
) << n_unrecognized_cmds_
1540 << " unrecognized commands is too many";
1544 out_() << "500 5.5.1 command unrecognized: \"" << escaped
<< "\"\r\n"
1548 void Session::bare_lf()
1550 // Error code used by Office 365.
1551 out_() << "554 5.6.11 bare LF\r\n" << std::flush
;
1552 LOG(WARNING
) << "bare LF";
1556 void Session::max_out()
1558 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1559 LOG(WARNING
) << "message size maxed out";
1563 void Session::time_out()
1565 out_() << "421 4.4.2 time-out\r\n" << std::flush
;
1566 LOG(WARNING
) << "time-out" << (sock_
.has_peername() ? " from " : "")
1571 void Session::starttls()
1573 last_in_group_("STARTTLS");
1575 out_() << "554 5.5.1 TLS already active\r\n" << std::flush
;
1576 LOG(WARNING
) << "STARTTLS issued with TLS already active";
1578 else if (!extensions_
) {
1579 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1581 LOG(WARNING
) << "STARTTLS issued without using EHLO";
1584 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush
;
1585 if (sock_
.starttls_server(config_path_
)) {
1587 max_msg_size(Config::max_msg_size_bro
);
1588 LOG(INFO
) << "STARTTLS " << sock_
.tls_info();
1591 LOG(INFO
) << "failed STARTTLS";
1596 void Session::exit_()
1598 // sock_.log_totals();
1600 timespec time_used
{};
1601 clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &time_used
);
1603 LOG(INFO
) << "CPU time " << time_used
.tv_sec
<< "." << std::setw(9)
1604 << std::setfill('0') << time_used
.tv_nsec
<< " seconds";
1606 std::exit(EXIT_SUCCESS
);
1609 /////////////////////////////////////////////////////////////////////////////
1611 // All of the verify_* functions send their own error messages back to
1612 // the client on failure, and return false.
1614 bool Session::verify_ip_address_(std::string
& error_msg
)
1616 auto ip_block_db_name
= config_path_
/ "ip-block";
1618 if (ip_block
.open(ip_block_db_name
) &&
1619 ip_block
.contains(sock_
.them_c_str())) {
1621 fmt::format("IP address {} on static blocklist", sock_
.them_c_str());
1622 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1626 client_fcrdns_
.clear();
1628 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1629 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1630 LOG(INFO
) << "loopback address allowed";
1632 client_fcrdns_
.emplace_back("localhost");
1633 client_
= fmt::format("localhost {}", sock_
.them_address_literal());
1637 auto const fcrdns
= DNS::fcrdns(res_
, sock_
.them_c_str());
1638 for (auto const& fcr
: fcrdns
) {
1639 client_fcrdns_
.emplace_back(fcr
);
1642 if (IP::is_private(sock_
.them_address_literal())) {
1643 LOG(INFO
) << "private address allowed";
1645 client_
= sock_
.them_address_literal();
1649 if (!client_fcrdns_
.empty()) {
1650 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1651 sock_
.them_address_literal());
1653 for (auto const& client_fcrdns
: client_fcrdns_
) {
1654 if (allow_
.contains(client_fcrdns
.ascii())) {
1655 LOG(INFO
) << "FCrDNS " << client_fcrdns
<< " allowed";
1656 fcrdns_allowed_
= true;
1659 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1661 if (allow_
.contains(tld
)) {
1662 LOG(INFO
) << "FCrDNS registered domain " << tld
<< " allowed";
1663 fcrdns_allowed_
= true;
1669 for (auto const& client_fcrdns
: client_fcrdns_
) {
1670 if (block_
.contains(client_fcrdns
.ascii())) {
1672 fmt::format("FCrDNS {} on static blocklist", client_fcrdns
.ascii());
1673 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1677 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1679 if (block_
.contains(tld
)) {
1680 error_msg
= fmt::format(
1681 "FCrDNS registered domain {} on static blocklist", tld
);
1682 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1689 client_
= fmt::format("{}", sock_
.them_address_literal());
1692 if (IP4::is_address(sock_
.them_c_str())) {
1694 auto const reversed
{IP4::reverse(sock_
.them_c_str())};
1697 // Check with allow list.
1698 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1701 for (auto wl : Config::wls) {
1702 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1703 if (q.has_record()) {
1704 using namespace boost::xpressive;
1706 auto const as = q.get_strings()[0];
1707 LOG(INFO) << "on allow list " << wl << " as " << as;
1711 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1714 if (regex_match(as, what, rex)) {
1715 auto const x = what[x_].str();
1716 auto const y = what[y_].str();
1719 std::from_chars(y.data(), y.data() + y.size(), value);
1722 LOG(INFO) << "allowed";
1726 LOG(INFO) << "Any A record skips check on block list";
1732 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1733 std::shuffle(std::begin(Config::bls
), std::end(Config::bls
),
1736 for (auto bl
: Config::bls
) {
1738 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ bl
);
1739 if (q
.has_record()) {
1740 auto const as
= q
.get_strings()[0];
1741 if (as
== "127.0.0.1") {
1742 LOG(INFO
) << "Should never get 127.0.0.1, from " << bl
;
1744 else if (as
== "127.0.0.10" || as
== "127.0.0.11") {
1745 LOG(INFO
) << "PBL listed, ignoring " << bl
;
1747 else if (as
== "127.255.255.252") {
1748 LOG(INFO
) << "Typing error in DNSBL name " << bl
;
1750 else if (as
== "127.255.255.254") {
1751 LOG(INFO
) << "Anonymous query through public resolver " << bl
;
1753 else if (as
== "127.255.255.255") {
1754 LOG(INFO
) << "Excessive number of queries " << bl
;
1757 error_msg
= fmt::format("IP address {} blocked: {} returned {}",
1758 sock_
.them_c_str(), bl
, as
);
1759 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1764 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1767 LOG(INFO
) << "IP address okay";
1771 bool domain_blocked(DNS::Resolver
& res
, Domain
const& identity
)
1773 Domain lookup
{fmt::format("{}.dbl.spamhaus.org", identity
.ascii())};
1774 DNS::Query
q(res
, DNS::RR_type::A
, lookup
.ascii());
1775 if (q
.has_record()) {
1776 auto const as
= q
.get_strings()[0];
1777 if (istarts_with(as
, "127.0.1.")) {
1778 LOG(INFO
) << "Domain " << identity
<< " blocked by spamhaus, " << as
;
1785 // check the identity from HELO/EHLO
1786 bool Session::verify_client_(Domain
const& client_identity
,
1787 std::string
& error_msg
)
1789 if (!client_fcrdns_
.empty()) {
1790 if (auto id
= std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
1792 id
!= end(client_fcrdns_
)) {
1793 // If the HELO ident is one of the FCrDNS names...
1794 if (id
!= begin(client_fcrdns_
)) {
1795 // ...then rotate that one to the front of the list
1796 std::rotate(begin(client_fcrdns_
), id
, id
+ 1);
1798 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1799 sock_
.them_address_literal());
1802 LOG(INFO
) << "claimed identity " << client_identity
1803 << " does NOT match any FCrDNS: ";
1804 for (auto const& client_fcrdns
: client_fcrdns_
) {
1805 LOG(INFO
) << " " << client_fcrdns
;
1809 // Bogus clients claim to be us or some local host.
1810 if (sock_
.has_peername() && ((client_identity
== server_identity_
) ||
1811 (client_identity
== "localhost") ||
1812 (client_identity
== "localhost.localdomain"))) {
1814 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1815 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1821 LOG(INFO
) << "allow-listed IP address can claim to be "
1826 // Ease up in test mode.
1827 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1831 error_msg
= fmt::format("liar, claimed to be {}", client_identity
.ascii());
1832 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1836 std::vector
<std::string
> labels
;
1837 boost::algorithm::split(labels
, client_identity
.ascii(),
1838 boost::algorithm::is_any_of("."));
1839 if (labels
.size() < 2) {
1841 fmt::format("claimed bogus identity {}", client_identity
.ascii());
1842 out_() << "550 4.7.1 bogus identity\r\n" << std::flush
;
1844 // // Sometimes we may want to look at mail from non conforming
1845 // // sending systems.
1846 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1847 // << client_ << " claiming " << client_identity;
1851 if (lookup_domain(block_
, client_identity
)) {
1853 fmt::format("claimed blocked identity {}", client_identity
.ascii());
1854 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1858 auto const tld
{tld_db_
.get_registered_domain(client_identity
.ascii())};
1860 // Sometimes we may want to look at mail from misconfigured
1862 // LOG(WARNING) << "claimed identity has no registered domain";
1865 else if (block_
.contains(tld
)) {
1867 fmt::format("claimed identity has blocked registered domain {}", tld
);
1868 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush
;
1872 if (domain_blocked(res_
, client_identity
) ||
1873 domain_blocked(res_
, Domain(tld
))) {
1874 error_msg
= fmt::format("claimed identity {} blocked by spamhaus",
1875 client_identity
.ascii());
1876 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1880 DNS::Query
q(res_
, DNS::RR_type::A
, client_identity
.ascii());
1881 if (!q
.has_record()) {
1882 LOG(WARNING
) << "claimed identity " << client_identity
.ascii()
1883 << " not DNS resolvable";
1886 // not otherwise objectionable
1890 // check sender from RFC5321 MAIL FROM:
1891 bool Session::verify_sender_(Mailbox
const& sender
, std::string
& error_msg
)
1893 do_spf_check_(sender
);
1895 std::string
const sender_str
{sender
};
1897 if (sender
.empty()) {
1899 // is used to send bounce messages.
1903 if (domain_blocked(res_
, sender
.domain())) {
1904 error_msg
= fmt::format("{} sender domain blocked by spamhaus", sender_str
);
1905 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1909 auto bad_senders_db_name
= config_path_
/ "bad_senders";
1911 if (bad_senders
.open(bad_senders_db_name
) &&
1912 bad_senders
.contains(sender_str
)) {
1913 error_msg
= fmt::format("{} bad sender", sender_str
);
1914 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1918 // We don't accept mail /from/ a domain we are expecting to accept
1919 // mail for on an external network connection.
1921 if (sock_
.them_address_literal() != sock_
.us_address_literal()) {
1922 if ((accept_domains_
.is_open() &&
1923 (accept_domains_
.contains(sender
.domain().ascii()) ||
1924 accept_domains_
.contains(sender
.domain().utf8()))) ||
1925 (sender
.domain() == server_identity_
)) {
1927 // Ease up in test mode.
1928 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1931 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1932 error_msg
= fmt::format("liar, claimed to be {}", sender
.domain().utf8());
1937 if (sender
.domain().is_address_literal()) {
1938 if (sender
.domain() != sock_
.them_address_literal()) {
1939 LOG(WARNING
) << "sender domain " << sender
.domain() << " does not match "
1940 << sock_
.them_address_literal();
1945 if (!verify_sender_domain_(sender
.domain(), error_msg
)) {
1952 // this sender is the RFC5321 MAIL FROM: domain part
1953 bool Session::verify_sender_domain_(Domain
const& sender
,
1954 std::string
& error_msg
)
1956 if (sender
.empty()) {
1958 // is used to send bounce messages.
1962 // Break sender domain into labels:
1964 std::vector
<std::string
> labels
;
1965 boost::algorithm::split(labels
, sender
.ascii(),
1966 boost::algorithm::is_any_of("."));
1968 if (labels
.size() < 2) { // This is not a valid domain.
1969 error_msg
= fmt::format("{} invalid syntax", sender
.ascii());
1970 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1974 if (lookup_domain(block_
, sender
)) {
1975 error_msg
= fmt::format("SPF sender domain ({}) is blocked",
1976 spf_sender_domain_
.ascii());
1977 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1981 if (spf_result_
== SPF::Result::PASS
) {
1982 if (allow_
.contains(spf_sender_domain_
.ascii())) {
1983 LOG(INFO
) << "sender " << spf_sender_domain_
.ascii() << " allowed";
1988 tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
1990 if (allow_
.contains(reg_dom
)) {
1991 LOG(INFO
) << "sender registered domain \"" << reg_dom
<< "\" allowed";
1997 LOG(INFO
) << "sender \"" << sender
<< "\" not disallowed";
2001 void Session::do_spf_check_(Mailbox
const& sender
)
2003 if (!sock_
.has_peername()) {
2004 auto const ip_addr
= "127.0.0.1"; // use localhost for local socket
2005 spf_received_
= fmt::format(
2006 "Received-SPF: pass ({}: allow-listed) client-ip={}; "
2007 "envelope-from={}; helo={};",
2008 server_id_(), ip_addr
, sender
.as_string(), client_identity_
.ascii());
2009 spf_sender_domain_
= "localhost";
2013 auto const spf_srv
= SPF::Server
{server_id_().c_str()};
2014 auto spf_request
= SPF::Request
{spf_srv
};
2016 if (IP4::is_address(sock_
.them_c_str())) {
2017 spf_request
.set_ipv4_str(sock_
.them_c_str());
2019 else if (IP6::is_address(sock_
.them_c_str())) {
2020 spf_request
.set_ipv6_str(sock_
.them_c_str());
2023 LOG(FATAL
) << "bogus address " << sock_
.them_address_literal() << ", "
2024 << sock_
.them_c_str();
2027 auto const from
{static_cast<std::string
>(sender
)};
2029 spf_request
.set_env_from(from
.c_str());
2030 spf_request
.set_helo_dom(client_identity_
.ascii().c_str());
2032 auto const spf_res
{SPF::Response
{spf_request
}};
2033 spf_result_
= spf_res
.result();
2034 spf_received_
= spf_res
.received_spf();
2035 spf_sender_domain_
= spf_request
.get_sender_dom();
2037 LOG(INFO
) << "spf_received_ == " << spf_received_
;
2039 if (spf_result_
== SPF::Result::FAIL
) {
2040 LOG(INFO
) << "FAIL " << spf_res
.header_comment();
2042 else if (spf_result_
== SPF::Result::NEUTRAL
) {
2043 LOG(INFO
) << "NEUTRAL " << spf_res
.header_comment();
2045 else if (spf_result_
== SPF::Result::PASS
) {
2046 LOG(INFO
) << "PASS " << spf_res
.header_comment();
2049 LOG(INFO
) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
2053 bool Session::verify_from_params_(parameters_t
const& parameters
)
2055 // Take a look at the optional parameters:
2056 for (auto const& [name
, value
] : parameters
) {
2057 if (iequal(name
, "BODY")) {
2058 if (iequal(value
, "8BITMIME")) {
2059 // everything is cool, this is our default...
2061 else if (iequal(value
, "7BIT")) {
2062 // nothing to see here, move along...
2064 else if (iequal(value
, "BINARYMIME")) {
2068 LOG(WARNING
) << "unrecognized BODY type \"" << value
<< "\" requested";
2071 else if (iequal(name
, "SMTPUTF8")) {
2072 if (!value
.empty()) {
2073 LOG(WARNING
) << "SMTPUTF8 parameter has a value: " << value
;
2078 // else if (iequal(name, "PRDR")) {
2079 // LOG(INFO) << "using PRDR";
2083 else if (iequal(name
, "SIZE")) {
2084 if (value
.empty()) {
2085 LOG(WARNING
) << "SIZE parameter has no value.";
2089 auto const sz
= stoull(value
);
2090 if (sz
> max_msg_size()) {
2091 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
2092 LOG(WARNING
) << "SIZE parameter too large: " << sz
;
2096 catch (std::invalid_argument
const& e
) {
2097 LOG(WARNING
) << "SIZE parameter has invalid value: " << value
;
2099 catch (std::out_of_range
const& e
) {
2100 LOG(WARNING
) << "SIZE parameter has out-of-range value: " << value
;
2102 // I guess we just ignore bad size parameters.
2105 else if (iequal(name
, "REQUIRETLS")) {
2107 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush
;
2108 LOG(WARNING
) << "REQUIRETLS needed";
2113 LOG(WARNING
) << "unrecognized 'MAIL FROM' parameter " << name
<< "="
2121 bool Session::verify_rcpt_params_(parameters_t
const& parameters
)
2123 // Take a look at the optional parameters:
2124 for (auto const& [name
, value
] : parameters
) {
2125 if (iequal(name
, "RRVS")) {
2126 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
2127 LOG(INFO
) << name
<< "=" << value
;
2130 LOG(WARNING
) << "unrecognized 'RCPT TO' parameter " << name
<< "="
2138 // check recipient from RFC5321 RCPT TO:
2139 bool Session::verify_recipient_(Mailbox
const& recipient
)
2141 if ((recipient
.local_part() == "Postmaster") && (recipient
.domain() == "")) {
2142 LOG(INFO
) << "magic Postmaster address";
2146 auto const accepted_domain
{[this, &recipient
] {
2147 if (recipient
.domain().is_address_literal()) {
2148 if (recipient
.domain() != sock_
.us_address_literal()) {
2149 LOG(WARNING
) << "recipient.domain address " << recipient
.domain()
2150 << " does not match ours " << sock_
.us_address_literal();
2158 // Domains we accept mail for.
2159 if (accept_domains_
.is_open()) {
2160 if (accept_domains_
.contains(recipient
.domain().ascii()) ||
2161 accept_domains_
.contains(recipient
.domain().utf8())) {
2166 // If we have no list of domains to accept, at least take our own.
2167 if (recipient
.domain() == server_id_()) {
2175 if (!accepted_domain
) {
2176 out_() << "550 5.7.1 relay access denied\r\n" << std::flush
;
2177 LOG(WARNING
) << "relay access denied for domain " << recipient
.domain();
2181 // Check for local addresses we reject.
2183 auto bad_recipients_db_name
= config_path_
/ "bad_recipients";
2184 CDB bad_recipients_db
;
2185 if (bad_recipients_db
.open(bad_recipients_db_name
) &&
2186 bad_recipients_db
.contains(recipient
.local_part())) {
2187 out_() << "550 5.1.1 bad recipient " << recipient
<< "\r\n" << std::flush
;
2188 LOG(WARNING
) << "bad recipient " << recipient
;
2194 auto fail_db_name
= config_path_
/ "fail_554";
2195 if (fs::exists(fail_db_name
)) {
2197 if (fail_db
.open(fail_db_name
) &&
2198 fail_db
.contains(recipient
.local_part())) {
2199 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2202 LOG(WARNING
) << "fail_554 recipient " << recipient
;
2209 auto temp_fail_db_name
= config_path_
/ "temp_fail";
2211 if (temp_fail
.open(temp_fail_db_name
) &&
2212 temp_fail
.contains(recipient
.local_part())) {
2213 out_() << "432 4.3.0 recipient's incoming mail queue has been stopped\r\n"
2215 LOG(WARNING
) << "temp fail for recipient " << recipient
;
2220 // Check for and act on magic "wait" address.
2222 using namespace boost::xpressive
;
2224 sregex
const rex
= icase("wait-rcpt-") >> (secs_
= +_d
);
2227 if (regex_match(recipient
.local_part(), what
, rex
) ||
2228 regex_match(recipient
.local_part(), what
, all_rex
)) {
2229 auto const str
= what
[secs_
].str();
2230 LOG(INFO
) << "waiting at RCPT TO " << str
<< " seconds";
2232 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
2233 google::FlushLogFiles(google::INFO
);
2234 out_() << std::flush
;
2236 LOG(INFO
) << "done waiting";
2240 // This is a trap for a probe done by some senders to see if we
2241 // accept just any old local-part.
2243 if (recipient
.local_part().length() > 8) {
2244 out_() << "550 5.1.1 unknown recipient " << recipient
<< "\r\n"
2246 LOG(WARNING
) << "unknown recipient for HELO " << recipient
;