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_bool(test_mode
, false, "ease up on some checks");
174 DEFINE_bool(use_binarymime
, true, "support BINARYMIME extension, RFC 3030");
175 DEFINE_bool(use_chunking
, true, "support CHUNKING extension, RFC 3030");
176 DEFINE_bool(use_pipelining
, true, "support PIPELINING extension, RFC 2920");
177 DEFINE_bool(use_rrvs
, false, "support RRVS extension, RFC 7293");
178 DEFINE_bool(use_smtputf8
, true, "support SMTPUTF8 extension, RFC 6531");
180 boost::xpressive::mark_tag
secs_(1);
181 boost::xpressive::sregex
const all_rex
= boost::xpressive::icase("wait-all-") >>
182 (secs_
= +boost::xpressive::_d
);
184 Session::Session(fs::path config_path
,
185 std::function
<void(void)> read_hook
,
188 : config_path_(config_path
)
190 , sock_(fd_in
, fd_out
, read_hook
, Config::read_timeout
, Config::write_timeout
)
191 //, send_(config_path, "smtp")
192 //, srs_(config_path)
194 auto accept_db_name
= config_path_
/ "accept_domains";
195 auto allow_db_name
= config_path_
/ "allow";
196 auto block_db_name
= config_path_
/ "block";
197 auto forward_db_name
= config_path_
/ "forward";
199 accept_domains_
.open(accept_db_name
);
200 allow_
.open(allow_db_name
);
201 block_
.open(block_db_name
);
202 forward_
.open(forward_db_name
);
204 if (sock_
.has_peername() && !IP::is_private(sock_
.us_c_str())) {
205 auto fcrdns
= DNS::fcrdns(res_
, sock_
.us_c_str());
206 for (auto const& fcr
: fcrdns
) {
207 server_fcrdns_
.emplace_back(fcr
);
211 server_identity_
= [this] {
212 auto const id_from_env
{getenv("GHSMTP_SERVER_ID")};
214 return std::string
{id_from_env
};
216 auto const hostname
{osutil::get_hostname()};
217 if (hostname
.find('.') != std::string::npos
)
220 if (!server_fcrdns_
.empty()) {
221 // first result should be shortest
222 return server_fcrdns_
.front().ascii();
225 auto const us_c_str
= sock_
.us_c_str();
226 if (us_c_str
&& !IP::is_private(us_c_str
)) {
227 return IP::to_address_literal(us_c_str
);
230 LOG(FATAL
) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
234 // send_.set_sender(server_identity_);
236 max_msg_size(Config::max_msg_size_initial
);
239 void Session::max_msg_size(size_t max
)
241 max_msg_size_
= max
; // number to advertise via RFC 1870
243 if (FLAGS_max_read
) {
244 sock_
.set_max_read(FLAGS_max_read
);
247 auto const overhead
= std::max(max
/ 10, size_t(2048));
248 sock_
.set_max_read(max
+ overhead
);
252 void Session::bad_host_(char const* msg
) const
254 if (sock_
.has_peername()) {
255 // On my systems, this pattern triggers a fail2ban rule that
256 // blocks connections from this IP address on port 25 for a few
257 // days. See <https://www.fail2ban.org/> for more info.
258 syslog(LOG_MAIL
| LOG_WARNING
, "bad host [%s] %s", sock_
.them_c_str(), msg
);
260 std::exit(EXIT_SUCCESS
);
263 void Session::reset_()
265 // RSET does not force another EHLO/HELO, the one piece of per
266 // transaction data saved is client_identity_:
268 // client_identity_.clear(); <-- not cleared!
270 reverse_path_
.clear();
271 forward_path_
.clear();
272 spf_received_
.clear();
273 // fwd_path_.clear();
274 // fwd_from_.clear();
275 // rep_info_.clear();
285 max_msg_size(max_msg_size());
287 state_
= xact_step::mail
;
291 // Return codes from connection establishment are 220 or 554, according
292 // to RFC 5321. That's it.
294 void Session::greeting()
296 CHECK(state_
== xact_step::helo
);
298 if (sock_
.has_peername()) {
299 close(2); // if we're a networked program, never send to stderr
301 std::string error_msg
;
302 if (!verify_ip_address_(error_msg
)) {
303 LOG(INFO
) << error_msg
;
304 bad_host_(error_msg
.c_str());
307 /******************************************************************
308 <https://tools.ietf.org/html/rfc5321#section-4.3.1> says:
310 4.3. Sequencing of Commands and Replies
312 4.3.1. Sequencing Overview
314 The communication between the sender and receiver is an alternating
315 dialogue, controlled by the sender. As such, the sender issues a
316 command and the receiver responds with a reply. Unless other
317 arrangements are negotiated through service extensions, the sender
318 MUST wait for this response before sending further commands. One
319 important reply is the connection greeting. Normally, a receiver
320 will send a 220 "Service ready" reply when the connection is
321 completed. The sender SHOULD wait for this greeting message before
322 sending any commands.
326 “…the receiver responds with a reply.”
327 “…the sender MUST wait for this response…”
328 “One important reply is the connection greeting.”
329 “The sender SHOULD wait for this greeting…”
331 So is it MUST or SHOULD? I enforce MUST.
332 *******************************************************************/
334 // Wait a bit of time for pre-greeting traffic.
335 if (!(ip_allowed_
|| fcrdns_allowed_
)) {
336 if (sock_
.input_ready(Config::greeting_wait
)) {
337 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
338 LOG(INFO
) << "input before any greeting from " << client_
;
339 bad_host_("input before any greeting");
341 // Give a half greeting and wait again.
342 out_() << "220-" << server_id_() << " ESMTP slowstart - ghsmtp\r\n"
344 if (sock_
.input_ready(Config::greeting_wait
)) {
345 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
346 LOG(INFO
) << "input before full greeting from " << client_
;
347 bad_host_("input before full greeting");
350 <https://www.rfc-editor.org/rfc/rfc5321#section-4.2>
352 An SMTP client MUST determine its actions only by the reply code, not
353 by the text (except for the "change of address" 251 and 551 and, if
354 necessary, 220, 221, and 421 replies); in the general case, any text,
355 including no text at all (although senders SHOULD NOT send bare
356 codes), MUST be acceptable. The space (blank) following the reply
357 code is considered part of the text. Whenever possible, a receiver-
358 SMTP SHOULD test the first digit (severity indication) of the reply
361 Except the following chokes a lot of senders:
363 out_() << "220\r\n" << std::flush;
366 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
369 out_() << "220 " << server_id_() << " ESMTP faststart - ghsmtp\r\n"
374 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
377 LOG(INFO
) << "connect from " << client_
;
379 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
380 alarm(2 * 60); // initial alarm
384 void Session::flush() { out_() << std::flush
; }
386 void Session::last_in_group_(std::string_view verb
)
388 if (sock_
.input_ready(std::chrono::seconds(0))) {
389 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
393 void Session::check_for_pipeline_error_(std::string_view verb
)
395 if (!(FLAGS_use_pipelining
&& extensions_
)) {
396 if (sock_
.input_ready(std::chrono::seconds(0))) {
397 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
402 void Session::lo_(char const* verb
, std::string_view client_identity
)
404 last_in_group_(verb
);
407 if (client_identity_
!= client_identity
) {
408 client_identity_
= client_identity
;
410 std::string error_msg
;
411 if (!verify_client_(client_identity_
, error_msg
)) {
412 LOG(INFO
) << "client identity blocked: " << error_msg
;
413 bad_host_(error_msg
.c_str());
419 out_() << "250 " << server_id_() << "\r\n";
425 if (sock_
.has_peername()) {
426 out_() << "250-" << server_id_() << " at your service, " << client_
430 out_() << "250-" << server_id_() << "\r\n";
433 // https://datatracker.ietf.org/doc/draft-freed-smtp-limits/
434 out_() << "250-LIMITS RCPTMAX=" << Config::max_recipients_per_message
436 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
437 out_() << "250-8BITMIME\r\n"; // RFC 6152
439 if (FLAGS_use_rrvs
) {
440 out_() << "250-RRVS\r\n"; // RFC 7293
443 // out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
446 // Check sasl sources for auth types.
447 // out_() << "250-AUTH PLAIN\r\n";
448 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
451 // If we're not already TLS, offer TLS
452 out_() << "250-STARTTLS\r\n"; // RFC 3207
455 out_() << "250-ENHANCEDSTATUSCODES\r\n"; // RFC 2034
457 if (FLAGS_use_pipelining
) {
458 out_() << "250-PIPELINING\r\n"; // RFC 2920
461 if (FLAGS_use_binarymime
) {
462 out_() << "250-BINARYMIME\r\n"; // RFC 3030
465 if (FLAGS_use_chunking
) {
466 out_() << "250-CHUNKING\r\n"; // RFC 3030
469 if (FLAGS_use_smtputf8
) {
470 out_() << "250-SMTPUTF8\r\n"; // RFC 6531
473 out_() << "250 HELP\r\n";
476 out_() << std::flush
;
478 if (sock_
.has_peername()) {
479 if (std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
480 client_identity_
) != end(client_fcrdns_
)) {
481 LOG(INFO
) << verb
<< " " << client_identity
<< " from "
482 << sock_
.them_address_literal();
485 LOG(INFO
) << verb
<< " " << client_identity
<< " from " << client_
;
489 LOG(INFO
) << verb
<< " " << client_identity
;
493 void Session::mail_from(Mailbox
&& reverse_path
, parameters_t
const& parameters
)
495 check_for_pipeline_error_("MAIL FROM");
498 case xact_step::helo
:
499 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
500 LOG(WARNING
) << "'MAIL FROM' before HELO/EHLO"
501 << (sock_
.has_peername() ? " from " : "") << client_
;
503 case xact_step::mail
: break;
504 case xact_step::rcpt
:
505 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
506 LOG(WARNING
) << "nested MAIL command"
507 << (sock_
.has_peername() ? " from " : "") << client_
;
509 case xact_step::data
:
510 case xact_step::bdat
:
511 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush
;
512 LOG(WARNING
) << "nested MAIL command"
513 << (sock_
.has_peername() ? " from " : "") << client_
;
515 case xact_step::rset
:
516 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
517 LOG(WARNING
) << "error state must be cleared with a RSET"
518 << (sock_
.has_peername() ? " from " : "") << client_
;
522 if (!verify_from_params_(parameters
)) {
526 if (!smtputf8_
&& !is_ascii(reverse_path
.local_part())) {
527 LOG(WARNING
) << "non ascii reverse_path \"" << reverse_path
528 << "\" without SMTPUTF8 paramater";
531 std::string error_msg
;
532 if (!verify_sender_(reverse_path
, error_msg
)) {
533 LOG(INFO
) << "verify sender failed: " << error_msg
;
534 bad_host_(error_msg
.c_str());
537 reverse_path_
= std::move(reverse_path
);
538 // fwd_path_.clear();
539 // fwd_from_.clear();
540 forward_path_
.clear();
541 out_() << "250 2.1.0 MAIL FROM OK\r\n";
542 // No flush RFC-2920 section 3.1, this could be part of a command group.
544 fmt::memory_buffer params
;
545 for (auto const& [name
, value
] : parameters
) {
546 fmt::format_to(std::back_inserter(params
), " {}", name
);
547 if (!value
.empty()) {
548 fmt::format_to(std::back_inserter(params
), "={}", value
);
551 LOG(INFO
) << "MAIL FROM:<" << reverse_path_
<< ">" << fmt::to_string(params
);
553 state_
= xact_step::rcpt
;
556 // bool Session::forward_to_(std::string const& forward, Mailbox const& rcpt_to)
558 // // If we're already forwarding or replying, reject
559 // if (!fwd_path_.empty() || !rep_info_.empty()) {
560 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
563 // LOG(WARNING) << "failed to forward to <" << forward
564 // << "> already forwarding or replying for: " << rcpt_to;
568 // fwd_path_ = Mailbox(forward);
569 // fwd_from_ = rcpt_to;
571 // // New bounce address
572 // Reply::from_to bounce;
573 // bounce.mail_from = reverse_path_.as_string();
575 // auto const new_bounce = srs_.enc_bounce(bounce, server_id_().c_str());
577 // auto const mail_from = Mailbox(new_bounce);
579 // std::string error_msg;
580 // if (!send_.mail_from_rcpt_to(res_, mail_from, fwd_path_, error_msg)) {
581 // out_() << error_msg << std::flush;
582 // LOG(WARNING) << "failed to forward <" << fwd_path_ << "> " << error_msg;
586 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> forwarding to == <" << fwd_path_
591 // bool Session::reply_to_(Reply::from_to const& reply_info, Mailbox const&
594 // // If we're already forwarding or replying, reject
595 // if (!fwd_path_.empty() || !rep_info_.empty()) {
596 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
599 // LOG(WARNING) << "failed to reply to <" << reply_info.mail_from
600 // << "> already forwarding or replying for: " << rcpt_to;
604 // rep_info_ = reply_info;
606 // Mailbox const from(rep_info_.rcpt_to_local_part, server_identity_);
607 // Mailbox const to(rep_info_.mail_from);
609 // std::string error_msg;
610 // if (!send_.mail_from_rcpt_to(res_, from, to, error_msg)) {
611 // out_() << error_msg << std::flush;
612 // LOG(WARNING) << "failed to reply from <" << from << "> to <" << to << ">
618 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> is a reply to "
619 // << rep_info_.mail_from << " from " <<
620 // rep_info_.rcpt_to_local_part;
624 void Session::rcpt_to(Mailbox
&& forward_path
, parameters_t
const& parameters
)
626 check_for_pipeline_error_("RCPT TO");
629 case xact_step::helo
:
630 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
631 LOG(WARNING
) << "'RCPT TO' before HELO/EHLO"
632 << (sock_
.has_peername() ? " from " : "") << client_
;
634 case xact_step::mail
:
635 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
636 LOG(WARNING
) << "'RCPT TO' before 'MAIL FROM'"
637 << (sock_
.has_peername() ? " from " : "") << client_
;
639 case xact_step::rcpt
:
640 case xact_step::data
: break;
641 case xact_step::bdat
:
642 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
643 LOG(WARNING
) << "'RCPT TO' during BDAT transfer"
644 << (sock_
.has_peername() ? " from " : "") << client_
;
646 case xact_step::rset
:
647 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
648 LOG(WARNING
) << "error state must be cleared with a RSET"
649 << (sock_
.has_peername() ? " from " : "") << client_
;
653 if (!verify_rcpt_params_(parameters
))
656 if (!verify_recipient_(forward_path
))
659 if (!smtputf8_
&& !is_ascii(forward_path
.local_part())) {
660 LOG(WARNING
) << "non ascii forward_path \"" << forward_path
661 << "\" without SMTPUTF8 paramater";
664 if (forward_path_
.size() >= Config::max_recipients_per_message
) {
665 out_() << "452 4.5.3 too many recipients\r\n" << std::flush
;
666 LOG(WARNING
) << "too many recipients <" << forward_path
<< ">";
669 // no check for dups, postfix doesn't
670 forward_path_
.emplace_back(std::move(forward_path
));
672 Mailbox
const& rcpt_to_mbx
= forward_path_
.back();
674 LOG(INFO
) << "RCPT TO:<" << rcpt_to_mbx
<< ">";
676 // auto const rcpt_to_str = rcpt_to_mbx.as_string();
678 // if (auto reply = srs_.dec_reply(rcpt_to_mbx.local_part()); reply) {
679 // if (!reply_to_(*reply, rcpt_to_mbx))
682 // else if (auto const forward = forward_.find(rcpt_to_str.c_str()); forward)
684 // if (!forward_to_(*forward, rcpt_to_mbx))
688 // LOG(INFO) << "RCPT TO:<" << rcpt_to_str << ">";
691 // No flush RFC-2920 section 3.1, this could be part of a command group.
692 out_() << "250 2.1.5 RCPT TO OK\r\n";
694 state_
= xact_step::data
;
697 // The headers Return-Path:, Received-SPF:, and Received: are returned
700 std::string
Session::added_headers_(MessageStore
const& msg
)
702 auto const protocol
{[this]() {
703 if (sock_
.tls() && !extensions_
) {
704 LOG(WARNING
) << "TLS active without extensions";
706 // <https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#mail-parameters-5>
708 return sock_
.tls() ? "UTF8SMTPS" : "UTF8SMTP";
709 else if (sock_
.tls())
711 else if (extensions_
)
717 fmt::memory_buffer headers
;
720 fmt::format_to(std::back_inserter(headers
), "Return-Path: <{}>\r\n",
721 reverse_path_
.as_string());
724 if (!spf_received_
.empty()) {
725 fmt::format_to(std::back_inserter(headers
), "{}\r\n", spf_received_
);
729 // <https://tools.ietf.org/html/rfc5321#section-4.4>
730 fmt::format_to(std::back_inserter(headers
), "Received: from {}",
731 client_identity_
.utf8());
732 if (sock_
.has_peername()) {
733 fmt::format_to(std::back_inserter(headers
), " ({})", client_
);
735 fmt::format_to(std::back_inserter(headers
), "\r\n\tby {} with {} id {}",
736 server_identity_
.utf8(), protocol
, msg
.id().as_string_view());
737 if (forward_path_
.size()) {
738 fmt::format_to(std::back_inserter(headers
), "\r\n\tfor <{}>",
739 forward_path_
[0].as_string());
740 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
741 // “If the FOR clause appears, it MUST contain exactly one <path>
742 // entry, even when multiple RCPT commands have been given. Multiple
743 // <path>s raise some security issues and have been deprecated, see
745 // for (auto i = 1u; i < forward_path_.size(); ++i)
746 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
748 std::string
const tls_info
{sock_
.tls_info()};
749 if (tls_info
.length()) {
750 fmt::format_to(std::back_inserter(headers
), "\r\n\t({})", tls_info
);
752 fmt::format_to(std::back_inserter(headers
), ";\r\n\t{}\r\n",
753 msg
.when().as_string_view());
755 return fmt::to_string(headers
);
759 bool lookup_domain(CDB
& cdb
, Domain
const& domain
)
761 if (!domain
.empty()) {
762 if (cdb
.contains(domain
.ascii())) {
765 if (domain
.is_unicode() && cdb
.contains(domain
.utf8())) {
773 std::tuple
<Session::SpamStatus
, std::string
> Session::spam_status_()
775 if (spf_result_
== SPF::Result::FAIL
&& !ip_allowed_
)
776 return {SpamStatus::spam
, "SPF failed"};
778 // These should have already been rejected by verify_client_().
779 if ((reverse_path_
.domain() == "localhost.local") ||
780 (reverse_path_
.domain() == "localhost"))
781 return {SpamStatus::spam
, "bogus reverse_path"};
783 std::vector
<std::string
> why_ham
;
785 // Anything enciphered tastes a lot like ham.
787 why_ham
.emplace_back("they used TLS");
789 if (spf_result_
== SPF::Result::PASS
) {
790 if (lookup_domain(allow_
, spf_sender_domain_
)) {
791 why_ham
.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
792 spf_sender_domain_
.utf8()));
795 auto tld_dom
{tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
796 if (tld_dom
&& allow_
.contains(tld_dom
)) {
797 why_ham
.emplace_back(fmt::format(
798 "SPF sender registered domain ({}) is allowed", tld_dom
));
804 why_ham
.emplace_back(
805 fmt::format("FCrDNS (or it's registered domain) is allowed"));
807 if (!why_ham
.empty())
808 return {SpamStatus::ham
,
809 fmt::format("{}", fmt::join(std::begin(why_ham
), std::end(why_ham
),
812 return {SpamStatus::spam
, "it's not ham"};
815 static std::string
folder(Session::SpamStatus status
,
816 std::vector
<Mailbox
> const& forward_path
,
817 Mailbox
const& reverse_path
)
820 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
823 if (reverse_path
== Mailbox("ietf-smtp-bounces@ietf.org"))
827 std::string_view local_part
;
828 std::string_view folder
;
831 assignment assignments
[] = {
832 {"Emailcore", ".emailcore"},
833 {"bootstrappable", ".bootstrappable"},
834 {"coreboot.org", ".coreboot"},
836 {"dns-privacy", ".dns-privacy"},
837 {"fucking-facebook", ".FB"},
838 {"gene-ebay", ".EBay"},
839 {"i-hate-facebook", ".FB"},
840 {"i-hate-linked-in", ".linkedin"},
841 {"mailop", ".INBOX.mailop"},
842 {"modelfkeyboards.com", ""},
843 {"nest", ".INBOX.Nest"},
844 {"opendmarc-dev", ".dmarc"},
845 {"opendmarc-users", ".dmarc"},
846 {"postmaster-rua", ".INBOX.rua"},
847 {"quic=ietf.org", ".INBOX.quic"},
848 {"shadowserver-reports@digilicious.com", ".INBOX.shadowserver"},
849 {"theatlantic.com", ""},
850 {"time-nutz", ".time-nutz"},
851 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
854 for (auto ass
: assignments
) {
855 if (forward_path
[0].local_part() == ass
.local_part
)
856 return std::string(ass
.folder
);
859 if (iends_with(forward_path
[0].local_part(), "-at-duck"))
862 if (status
== Session::SpamStatus::spam
)
868 bool Session::msg_new()
870 CHECK((state_
== xact_step::data
) || (state_
== xact_step::bdat
));
872 auto const& [status
, reason
]{spam_status_()};
874 LOG(INFO
) << ((status
== SpamStatus::ham
) ? "ham since " : "spam since ")
877 // All sources of ham get a fresh 5 minute timeout per message.
878 if (status
== SpamStatus::ham
) {
879 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr))
883 msg_
= std::make_unique
<MessageStore
>();
885 if (!FLAGS_max_write
)
886 FLAGS_max_write
= max_msg_size();
889 msg_
->open(server_id_(), FLAGS_max_write
,
890 folder(status
, forward_path_
, reverse_path_
));
891 auto const hdrs
{added_headers_(*(msg_
.get()))};
894 // fmt::memory_buffer spam_status;
895 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
896 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
897 // msg_->write(spam_status.data(), spam_status.size());
899 LOG(INFO
) << "Spam-Status: "
900 << ((status
== SpamStatus::spam
) ? "Yes" : "No") << ", "
905 catch (std::system_error
const& e
) {
908 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
909 LOG(ERROR
) << "no space";
915 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
916 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
917 LOG(ERROR
) << e
.what();
923 catch (std::exception
const& e
) {
924 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
925 LOG(ERROR
) << e
.what();
931 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
932 LOG(ERROR
) << "msg_new failed with no exception caught";
938 bool Session::msg_write(char const* s
, std::streamsize count
)
940 if ((state_
!= xact_step::data
) && (state_
!= xact_step::bdat
))
947 if (msg_
->write(s
, count
))
950 catch (std::system_error
const& e
) {
953 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
954 LOG(ERROR
) << "no space";
960 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
961 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
962 LOG(ERROR
) << e
.what();
968 catch (std::exception
const& e
) {
969 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
970 LOG(ERROR
) << e
.what();
976 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
977 LOG(ERROR
) << "msg_write failed with no exception caught";
983 bool Session::data_start()
985 last_in_group_("DATA");
988 case xact_step::helo
:
989 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
990 LOG(WARNING
) << "'DATA' before HELO/EHLO"
991 << (sock_
.has_peername() ? " from " : "") << client_
;
993 case xact_step::mail
:
994 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
995 LOG(WARNING
) << "'DATA' before 'MAIL FROM'"
996 << (sock_
.has_peername() ? " from " : "") << client_
;
998 case xact_step::rcpt
:
1000 /******************************************************************
1001 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
1003 The DATA command can fail at only two points in the protocol exchange:
1005 If there was no MAIL, or no RCPT, command, or all such commands were
1006 rejected, the server MAY return a "command out of sequence" (503) or
1007 "no valid recipients" (554) reply in response to the DATA command.
1009 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
1011 The additional restriction is that when there have been no successful
1012 RCPT commands in the mail transaction, the DATA command MUST fail
1013 with a 503 reply code.
1015 Therefore I will send the reply code that is valid for both, and
1016 do the same for the BDAT case.
1017 *******************************************************************/
1019 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1020 LOG(WARNING
) << "no valid recipients"
1021 << (sock_
.has_peername() ? " from " : "") << client_
;
1023 case xact_step::data
: break;
1024 case xact_step::bdat
:
1025 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
1026 LOG(WARNING
) << "'DATA' during BDAT transfer"
1027 << (sock_
.has_peername() ? " from " : "") << client_
;
1029 case xact_step::rset
:
1030 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1031 LOG(WARNING
) << "error state must be cleared with a RSET"
1032 << (sock_
.has_peername() ? " from " : "") << client_
;
1037 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
1039 LOG(WARNING
) << "DATA does not support BINARYMIME";
1040 state_
= xact_step::rset
; // RFC 3030 section 3 page 5
1045 LOG(ERROR
) << "msg_new() failed";
1049 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush
;
1050 LOG(INFO
) << "DATA";
1054 // bool Session::do_forward_(message::parsed& msg)
1056 // auto msg_fwd = msg;
1058 // // Generate a reply address
1059 // Reply::from_to reply;
1060 // reply.mail_from = msg_fwd.dmarc_from;
1061 // reply.rcpt_to_local_part = fwd_from_.local_part();
1063 // auto const reply_addr =
1064 // fmt::format("{}@{}", srs_.enc_reply(reply), server_id_());
1066 // auto const munging = false;
1068 // auto const sender = server_identity_.ascii().c_str();
1069 // auto const selector = FLAGS_selector.c_str();
1070 // auto const key_file =
1071 // (config_path_ / FLAGS_selector).replace_extension("private");
1072 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1075 // auto const from_hdr =
1076 // fmt::format("From: \"{} via\" <@>", msg_fwd.dmarc_from, reply_addr);
1077 // message::rewrite_from_to(msg_fwd, from_hdr, "", sender, selector,
1081 // auto const reply_to_hdr = fmt::format("Reply-To: {}", reply_addr);
1082 // message::rewrite_from_to(msg_fwd, "", reply_to_hdr, sender, selector,
1087 // if (!send_.send(msg_fwd.as_string())) {
1088 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1092 // LOG(ERROR) << "failed to send for " << fwd_path_;
1096 // LOG(INFO) << "successfully sent for " << fwd_path_;
1100 // bool Session::do_reply_(message::parsed& msg)
1102 // Mailbox to_mbx(rep_info_.mail_from);
1103 // Mailbox from_mbx(rep_info_.rcpt_to_local_part, server_identity_);
1105 // auto reply = std::make_unique<MessageStore>();
1106 // reply->open(server_id_(), FLAGS_max_write, ".Drafts");
1108 // auto const date{Now{}};
1109 // auto const pill{Pill{}};
1110 // auto const mid_str =
1111 // fmt::format("<{}.{}@{}>", date.sec(), pill, server_identity_);
1113 // fmt::memory_buffer bfr;
1115 // fmt::format_to(bfr, "From: <{}>\r\n", from_mbx);
1116 // fmt::format_to(bfr, "To: <{}>\r\n", to_mbx);
1118 // fmt::format_to(bfr, "Date: {}\r\n", date.c_str());
1120 // fmt::format_to(bfr, "Message-ID: {}\r\n", mid_str.c_str());
1122 // if (!msg.get_header(message::Subject).empty()) {
1123 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1124 // msg.get_header(message::Subject));
1127 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1128 // "Reply to your message");
1131 // if (!msg.get_header(message::In_Reply_To).empty()) {
1132 // fmt::format_to(bfr, "{}: {}\r\n", message::In_Reply_To,
1133 // msg.get_header(message::In_Reply_To));
1136 // if (!msg.get_header(message::MIME_Version).empty() &&
1137 // msg.get_header(message::Content_Type).empty()) {
1138 // fmt::format_to(bfr, "{}: {}\r\n", message::MIME_Version,
1139 // msg.get_header(message::MIME_Version));
1140 // fmt::format_to(bfr, "{}: {}\r\n", message::Content_Type,
1141 // msg.get_header(message::Content_Type));
1144 // reply->write(fmt::to_string(bfr));
1146 // if (!msg.body.empty()) {
1147 // reply->write("\r\n");
1148 // reply->write(msg.body);
1151 // auto const msg_data = reply->freeze();
1152 // message::parsed msg_reply;
1153 // CHECK(msg_reply.parse(msg_data));
1155 // auto const sender = server_identity_.ascii().c_str();
1156 // auto const selector = FLAGS_selector.c_str();
1157 // auto const key_file =
1158 // (config_path_ / FLAGS_selector).replace_extension("private");
1159 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1161 // message::dkim_sign(msg_reply, sender, selector, key_file);
1163 // if (!send_.send(msg_reply.as_string())) {
1164 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1168 // LOG(ERROR) << "send failed for reply to " << to_mbx << " from " <<
1169 // from_mbx; return false;
1172 // LOG(INFO) << "successful reply to " << to_mbx << " from " << from_mbx;
1176 bool Session::do_deliver_()
1180 // auto const sender = server_identity_.ascii().c_str();
1181 // auto const selector = FLAGS_selector.c_str();
1182 // auto const key_file =
1183 // (config_path_ / FLAGS_selector).replace_extension("private");
1184 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1187 // auto const msg_data = msg_->freeze();
1189 // message::parsed msg;
1191 // // Only deal in RFC-5322 Mail Objects.
1192 // bool const message_parsed = msg.parse(msg_data);
1193 // if (message_parsed) {
1195 // // remove any Return-Path
1196 // message::remove_delivery_headers(msg);
1198 // auto const authentic =
1199 // message_parsed &&
1200 // message::authentication(msg, sender, selector, key_file);
1202 // // write a new Return-Path
1203 // msg_->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_));
1205 // for (auto const h : msg.headers) {
1206 // msg_->write(h.as_string());
1207 // msg_->write("\r\n");
1209 // if (!msg.body.empty()) {
1210 // msg_->write("\r\n");
1211 // msg_->write(msg.body);
1216 // if (authentic && !fwd_path_.empty()) {
1217 // if (!do_forward_(msg))
1220 // if (authentic && !rep_info_.empty()) {
1221 // if (!do_reply_(msg))
1228 catch (std::system_error
const& e
) {
1231 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
1232 LOG(ERROR
) << "no space";
1238 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
1240 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
1241 LOG(ERROR
) << e
.what();
1251 void Session::data_done()
1253 CHECK((state_
== xact_step::data
));
1255 if (msg_
&& msg_
->size_error()) {
1261 // out_() << "353\r\n";
1262 // for (auto fp : forward_path_) {
1263 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1267 // Check for and act on magic "wait" address.
1269 using namespace boost::xpressive
;
1271 sregex
const rex
= icase("wait-data-") >> (secs_
= +_d
);
1274 for (auto fp
: forward_path_
) {
1275 if (regex_match(fp
.local_part(), what
, rex
) ||
1276 regex_match(fp
.local_part(), what
, all_rex
)) {
1277 auto const str
= what
[secs_
].str();
1278 LOG(INFO
) << "waiting at DATA " << str
<< " seconds";
1280 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1281 google::FlushLogFiles(google::INFO
);
1282 out_() << std::flush
;
1284 LOG(INFO
) << "done waiting";
1289 if (do_deliver_()) {
1290 auto temp_fail_db_name
= config_path_
/ "temp_fail_data";
1293 for (auto fp
: forward_path_
) {
1294 if (temp_fail
.open(temp_fail_db_name
) &&
1295 temp_fail
.contains(fp
.local_part())) {
1296 out_() << "450 4.2.2 Mailbox full.\r\n" << std::flush
;
1297 LOG(WARNING
) << "temp fail at DATA for recipient " << fp
;
1304 // Check for addresses we reject after data.
1306 auto bad_recipients_db_name
= config_path_
/ "bad_recipients_data";
1307 CDB bad_recipients_db
;
1308 if (bad_recipients_db
.open(bad_recipients_db_name
)) {
1309 for (auto fp
: forward_path_
) {
1310 std::string loc
= fp
.local_part();
1311 std::transform(loc
.begin(), loc
.end(), loc
.begin(),
1312 [](unsigned char c
) { return std::tolower(c
); });
1313 if (bad_recipients_db
.contains(loc
)) {
1314 out_() << "550 5.1.1 bad recipient " << fp
<< "\r\n" << std::flush
;
1315 LOG(WARNING
) << "bad recipient " << fp
;
1320 LOG(INFO
) << "unbad recipient " << fp
.local_part();
1325 LOG(WARNING
) << "can't open bad_recipients_data";
1329 out_() << "250 2.0.0 DATA OK\r\n" << std::flush
;
1330 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1335 void Session::data_size_error()
1337 out_().clear(); // clear possible eof from input side
1338 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1342 LOG(WARNING
) << "DATA size error";
1346 void Session::data_error()
1348 out_().clear(); // clear possible eof from input side
1349 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush
;
1353 LOG(WARNING
) << "DATA error";
1357 bool Session::bdat_start(size_t n
)
1359 // In practice, this one gets pipelined.
1360 // last_in_group_("BDAT");
1363 case xact_step::helo
:
1364 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
1365 LOG(WARNING
) << "'BDAT' before HELO/EHLO"
1366 << (sock_
.has_peername() ? " from " : "") << client_
;
1368 case xact_step::mail
:
1369 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
1370 LOG(WARNING
) << "'BDAT' before 'MAIL FROM'"
1371 << (sock_
.has_peername() ? " from " : "") << client_
;
1373 case xact_step::rcpt
:
1374 // See comment in data_start()
1375 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1376 LOG(WARNING
) << "no valid recipients"
1377 << (sock_
.has_peername() ? " from " : "") << client_
;
1379 case xact_step::data
: // first bdat
1381 case xact_step::bdat
: return true;
1382 case xact_step::rset
:
1383 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1384 LOG(WARNING
) << "error state must be cleared with a RSET"
1385 << (sock_
.has_peername() ? " from " : "") << client_
;
1389 state_
= xact_step::bdat
;
1394 void Session::bdat_done(size_t n
, bool last
)
1396 if (state_
!= xact_step::bdat
) {
1405 if (msg_
->size_error()) {
1411 out_() << "250 2.0.0 BDAT " << n
<< " OK\r\n" << std::flush
;
1412 LOG(INFO
) << "BDAT " << n
;
1416 // Check for and act on magic "wait" address.
1418 using namespace boost::xpressive
;
1420 sregex
const rex
= icase("wait-bdat-") >> (secs_
= +_d
);
1423 for (auto fp
: forward_path_
) {
1424 if (regex_match(fp
.local_part(), what
, rex
) ||
1425 regex_match(fp
.local_part(), what
, all_rex
)) {
1426 auto const str
= what
[secs_
].str();
1427 LOG(INFO
) << "waiting at BDAT " << str
<< " seconds";
1429 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1430 google::FlushLogFiles(google::INFO
);
1431 out_() << std::flush
;
1433 LOG(INFO
) << "done waiting";
1440 out_() << "250 2.0.0 BDAT " << n
<< " LAST OK\r\n" << std::flush
;
1441 LOG(INFO
) << "BDAT " << n
<< " LAST";
1442 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1447 void Session::bdat_size_error()
1449 out_().clear(); // clear possible eof from input side
1450 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1454 LOG(WARNING
) << "BDAT size error";
1458 void Session::bdat_seq_error()
1460 out_().clear(); // clear possible eof from input side
1461 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush
;
1465 LOG(WARNING
) << "BDAT sequence error";
1469 void Session::bdat_io_error()
1471 out_().clear(); // clear possible eof from input side
1472 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush
;
1476 LOG(WARNING
) << "BDAT I/O error";
1480 void Session::rset()
1482 out_() << "250 2.1.5 RSET OK\r\n";
1483 // No flush RFC-2920 section 3.1, this could be part of a command group.
1484 LOG(INFO
) << "RSET";
1488 void Session::noop(std::string_view str
)
1490 last_in_group_("NOOP");
1491 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush
;
1492 LOG(INFO
) << "NOOP" << (str
.length() ? " " : "") << str
;
1495 void Session::vrfy(std::string_view str
)
1497 last_in_group_("VRFY");
1498 out_() << "252 2.1.5 try it\r\n" << std::flush
;
1499 LOG(INFO
) << "VRFY" << (str
.length() ? " " : "") << str
;
1502 void Session::help(std::string_view str
)
1504 if (iequal(str
, "help\r\n")) {
1505 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush
;
1508 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1511 LOG(INFO
) << "HELP" << (str
.length() ? " " : "") << str
;
1514 void Session::quit()
1517 // last_in_group_("QUIT");
1518 out_() << "221 2.0.0 closing connection\r\n" << std::flush
;
1519 LOG(INFO
) << "QUIT";
1523 void Session::auth()
1525 out_() << "454 4.7.0 authentication failure\r\n" << std::flush
;
1526 LOG(INFO
) << "AUTH";
1530 void Session::error(std::string_view log_msg
)
1532 out_() << "421 4.3.5 system error: " << log_msg
<< "\r\n" << std::flush
;
1533 LOG(WARNING
) << log_msg
;
1536 void Session::cmd_unrecognized(std::string_view cmd
)
1538 auto const escaped
{esc(cmd
)};
1539 LOG(WARNING
) << "command unrecognized: \"" << escaped
<< "\"";
1541 if (++n_unrecognized_cmds_
>= Config::max_unrecognized_cmds
) {
1542 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1543 << "\" exceeds limit\r\n"
1545 LOG(WARNING
) << n_unrecognized_cmds_
1546 << " unrecognized commands is too many";
1550 out_() << "500 5.5.1 command unrecognized: \"" << escaped
<< "\"\r\n"
1554 void Session::bare_lf()
1556 // Error code used by Office 365.
1557 out_() << "554 5.6.11 bare LF\r\n" << std::flush
;
1558 LOG(WARNING
) << "bare LF";
1562 void Session::max_out()
1564 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1565 LOG(WARNING
) << "message size maxed out";
1569 void Session::time_out()
1571 out_() << "421 4.4.2 time-out\r\n" << std::flush
;
1572 LOG(WARNING
) << "time-out" << (sock_
.has_peername() ? " from " : "")
1577 void Session::starttls()
1579 last_in_group_("STARTTLS");
1581 out_() << "554 5.5.1 TLS already active\r\n" << std::flush
;
1582 LOG(WARNING
) << "STARTTLS issued with TLS already active";
1584 else if (!extensions_
) {
1585 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1587 LOG(WARNING
) << "STARTTLS issued without using EHLO";
1590 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush
;
1591 if (sock_
.starttls_server(config_path_
)) {
1593 max_msg_size(Config::max_msg_size_bro
);
1594 LOG(INFO
) << "STARTTLS " << sock_
.tls_info();
1597 LOG(INFO
) << "failed STARTTLS";
1602 void Session::exit_()
1604 // sock_.log_totals();
1606 timespec time_used
{};
1607 clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &time_used
);
1609 LOG(INFO
) << "CPU time " << time_used
.tv_sec
<< "." << std::setw(9)
1610 << std::setfill('0') << time_used
.tv_nsec
<< " seconds";
1612 std::exit(EXIT_SUCCESS
);
1615 /////////////////////////////////////////////////////////////////////////////
1617 // All of the verify_* functions send their own error messages back to
1618 // the client on failure, and return false.
1620 bool Session::verify_ip_address_(std::string
& error_msg
)
1622 auto ip_block_db_name
= config_path_
/ "ip-block";
1624 if (ip_block
.open(ip_block_db_name
) &&
1625 ip_block
.contains(sock_
.them_c_str())) {
1627 fmt::format("IP address {} on static blocklist", sock_
.them_c_str());
1628 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1632 client_fcrdns_
.clear();
1634 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1635 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1636 LOG(INFO
) << "loopback address allowed";
1638 client_fcrdns_
.emplace_back("localhost");
1639 client_
= fmt::format("localhost {}", sock_
.them_address_literal());
1643 auto const fcrdns
= DNS::fcrdns(res_
, sock_
.them_c_str());
1644 for (auto const& fcr
: fcrdns
) {
1645 client_fcrdns_
.emplace_back(fcr
);
1648 if (IP::is_private(sock_
.them_address_literal())) {
1649 LOG(INFO
) << "private address allowed";
1651 client_
= sock_
.them_address_literal();
1655 if (!client_fcrdns_
.empty()) {
1656 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1657 sock_
.them_address_literal());
1659 for (auto const& client_fcrdns
: client_fcrdns_
) {
1660 if (allow_
.contains(client_fcrdns
.ascii())) {
1661 LOG(INFO
) << "FCrDNS " << client_fcrdns
<< " allowed";
1662 fcrdns_allowed_
= true;
1665 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1667 if (allow_
.contains(tld
)) {
1668 LOG(INFO
) << "FCrDNS registered domain " << tld
<< " allowed";
1669 fcrdns_allowed_
= true;
1675 for (auto const& client_fcrdns
: client_fcrdns_
) {
1676 if (block_
.contains(client_fcrdns
.ascii())) {
1678 fmt::format("FCrDNS {} on static blocklist", client_fcrdns
.ascii());
1679 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1683 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1685 if (block_
.contains(tld
)) {
1686 error_msg
= fmt::format(
1687 "FCrDNS registered domain {} on static blocklist", tld
);
1688 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1695 client_
= fmt::format("{}", sock_
.them_address_literal());
1698 if (IP4::is_address(sock_
.them_c_str())) {
1700 auto const reversed
{IP4::reverse(sock_
.them_c_str())};
1703 // Check with allow list.
1704 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1707 for (auto wl : Config::wls) {
1708 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1709 if (q.has_record()) {
1710 using namespace boost::xpressive;
1712 auto const as = q.get_strings()[0];
1713 LOG(INFO) << "on allow list " << wl << " as " << as;
1717 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1720 if (regex_match(as, what, rex)) {
1721 auto const x = what[x_].str();
1722 auto const y = what[y_].str();
1725 std::from_chars(y.data(), y.data() + y.size(), value);
1728 LOG(INFO) << "allowed";
1732 LOG(INFO) << "Any A record skips check on block list";
1738 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1739 std::shuffle(std::begin(Config::bls
), std::end(Config::bls
),
1742 for (auto bl
: Config::bls
) {
1743 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ bl
);
1744 if (q
.has_record()) {
1745 const auto a_strings
= q
.get_strings();
1746 for (auto const& as
: a_strings
) {
1747 LOG(INFO
) << bl
<< " returned " << as
;
1749 for (auto const& as
: a_strings
) {
1750 if (as
== "127.0.0.1") {
1751 LOG(INFO
) << "Should never get 127.0.0.1, from " << bl
;
1753 else if (as
== "127.0.0.10" || as
== "127.0.0.11") {
1754 LOG(INFO
) << "PBL listed, ignoring " << bl
;
1756 else if (as
== "127.255.255.252") {
1757 LOG(INFO
) << "Typing error in DNSBL name " << bl
;
1759 else if (as
== "127.255.255.254") {
1760 LOG(INFO
) << "Anonymous query through public resolver " << bl
;
1762 else if (as
== "127.255.255.255") {
1763 LOG(INFO
) << "Excessive number of queries " << bl
;
1766 error_msg
= fmt::format("IP address {} blocked: {} returned {}",
1767 sock_
.them_c_str(), bl
, as
);
1768 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1774 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1777 LOG(INFO
) << "IP address okay";
1781 bool domain_blocked(DNS::Resolver
& res
, Domain
const& identity
)
1783 Domain lookup
{fmt::format("{}.dbl.spamhaus.org", identity
.ascii())};
1784 DNS::Query
q(res
, DNS::RR_type::A
, lookup
.ascii());
1785 if (q
.has_record()) {
1786 const auto a_strings
= q
.get_strings();
1787 for (auto const& as
: a_strings
) {
1788 if (istarts_with(as
, "127.0.1.")) {
1789 LOG(INFO
) << "Domain " << identity
<< " blocked by spamhaus, " << as
;
1797 // check the identity from HELO/EHLO
1798 bool Session::verify_client_(Domain
const& client_identity
,
1799 std::string
& error_msg
)
1801 if (!client_fcrdns_
.empty()) {
1802 if (auto id
= std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
1804 id
!= end(client_fcrdns_
)) {
1805 // If the HELO ident is one of the FCrDNS names...
1806 if (id
!= begin(client_fcrdns_
)) {
1807 // ...then rotate that one to the front of the list
1808 std::rotate(begin(client_fcrdns_
), id
, id
+ 1);
1810 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1811 sock_
.them_address_literal());
1814 LOG(INFO
) << "claimed identity " << client_identity
1815 << " does NOT match any FCrDNS: ";
1816 for (auto const& client_fcrdns
: client_fcrdns_
) {
1817 LOG(INFO
) << " " << client_fcrdns
;
1821 // Bogus clients claim to be us or some local host.
1822 if (sock_
.has_peername() && ((client_identity
== server_identity_
) ||
1823 (client_identity
== "localhost") ||
1824 (client_identity
== "localhost.localdomain"))) {
1826 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1827 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1833 LOG(INFO
) << "allow-listed IP address can claim to be "
1838 // Ease up in test mode.
1839 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1843 error_msg
= fmt::format("liar, claimed to be {}", client_identity
.ascii());
1844 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1848 std::vector
<std::string
> labels
;
1849 boost::algorithm::split(labels
, client_identity
.ascii(),
1850 boost::algorithm::is_any_of("."));
1851 if (labels
.size() < 2) {
1853 fmt::format("claimed bogus identity {}", client_identity
.ascii());
1854 out_() << "550 4.7.1 bogus identity\r\n" << std::flush
;
1856 // // Sometimes we may want to look at mail from non conforming
1857 // // sending systems.
1858 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1859 // << client_ << " claiming " << client_identity;
1863 if (lookup_domain(block_
, client_identity
)) {
1865 fmt::format("claimed blocked identity {}", client_identity
.ascii());
1866 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1870 auto const tld
{tld_db_
.get_registered_domain(client_identity
.ascii())};
1872 // Sometimes we may want to look at mail from misconfigured
1874 // LOG(WARNING) << "claimed identity has no registered domain";
1877 else if (block_
.contains(tld
)) {
1879 fmt::format("claimed identity has blocked registered domain {}", tld
);
1880 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush
;
1884 if (domain_blocked(res_
, client_identity
) ||
1885 domain_blocked(res_
, Domain(tld
))) {
1886 error_msg
= fmt::format("claimed identity {} blocked by spamhaus",
1887 client_identity
.ascii());
1888 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1892 DNS::Query
q(res_
, DNS::RR_type::A
, client_identity
.ascii());
1893 if (!q
.has_record()) {
1894 LOG(WARNING
) << "claimed identity " << client_identity
.ascii()
1895 << " not DNS resolvable";
1898 // not otherwise objectionable
1902 // check sender from RFC5321 MAIL FROM:
1903 bool Session::verify_sender_(Mailbox
const& sender
, std::string
& error_msg
)
1905 do_spf_check_(sender
);
1907 std::string
const sender_str
{sender
};
1909 if (sender
.empty()) {
1911 // is used to send bounce messages.
1915 if (domain_blocked(res_
, sender
.domain())) {
1916 error_msg
= fmt::format("{} sender domain blocked by spamhaus", sender_str
);
1917 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1921 auto bad_senders_db_name
= config_path_
/ "bad_senders";
1923 if (bad_senders
.open(bad_senders_db_name
) &&
1924 bad_senders
.contains(sender_str
)) {
1925 error_msg
= fmt::format("{} bad sender", sender_str
);
1926 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1930 // We don't accept mail /from/ a domain we are expecting to accept
1931 // mail for on an external network connection.
1933 if (sock_
.them_address_literal() != sock_
.us_address_literal()) {
1934 if ((accept_domains_
.is_open() &&
1935 (accept_domains_
.contains(sender
.domain().ascii()) ||
1936 accept_domains_
.contains(sender
.domain().utf8()))) ||
1937 (sender
.domain() == server_identity_
)) {
1939 // Ease up in test mode.
1940 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1943 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1944 error_msg
= fmt::format("liar, claimed to be {}", sender
.domain().utf8());
1949 if (sender
.domain().is_address_literal()) {
1950 if (sender
.domain() != sock_
.them_address_literal()) {
1951 LOG(WARNING
) << "sender domain " << sender
.domain() << " does not match "
1952 << sock_
.them_address_literal();
1957 if (!verify_sender_domain_(sender
.domain(), error_msg
)) {
1964 // this sender is the RFC5321 MAIL FROM: domain part
1965 bool Session::verify_sender_domain_(Domain
const& sender
,
1966 std::string
& error_msg
)
1968 if (sender
.empty()) {
1970 // is used to send bounce messages.
1974 // Break sender domain into labels:
1976 std::vector
<std::string
> labels
;
1977 boost::algorithm::split(labels
, sender
.ascii(),
1978 boost::algorithm::is_any_of("."));
1980 if (labels
.size() < 2) { // This is not a valid domain.
1981 error_msg
= fmt::format("{} invalid syntax", sender
.ascii());
1982 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1986 if (lookup_domain(block_
, sender
)) {
1987 error_msg
= fmt::format("SPF sender domain ({}) is blocked",
1988 spf_sender_domain_
.ascii());
1989 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1993 if (spf_result_
== SPF::Result::PASS
) {
1994 if (allow_
.contains(spf_sender_domain_
.ascii())) {
1995 LOG(INFO
) << "sender " << spf_sender_domain_
.ascii() << " allowed";
2000 tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
2002 if (allow_
.contains(reg_dom
)) {
2003 LOG(INFO
) << "sender registered domain \"" << reg_dom
<< "\" allowed";
2009 LOG(INFO
) << "sender \"" << sender
<< "\" not disallowed";
2013 void Session::do_spf_check_(Mailbox
const& sender
)
2015 if (!sock_
.has_peername()) {
2016 auto const ip_addr
= "127.0.0.1"; // use localhost for local socket
2017 spf_received_
= fmt::format(
2018 "Received-SPF: pass ({}: allow-listed) client-ip={}; "
2019 "envelope-from={}; helo={};",
2020 server_id_(), ip_addr
, sender
.as_string(), client_identity_
.ascii());
2021 spf_sender_domain_
= "localhost";
2025 auto const spf_srv
= SPF::Server
{server_id_().c_str()};
2026 auto spf_request
= SPF::Request
{spf_srv
};
2028 if (IP4::is_address(sock_
.them_c_str())) {
2029 spf_request
.set_ipv4_str(sock_
.them_c_str());
2031 else if (IP6::is_address(sock_
.them_c_str())) {
2032 spf_request
.set_ipv6_str(sock_
.them_c_str());
2035 LOG(FATAL
) << "bogus address " << sock_
.them_address_literal() << ", "
2036 << sock_
.them_c_str();
2039 auto const from
{static_cast<std::string
>(sender
)};
2041 spf_request
.set_env_from(from
.c_str());
2042 spf_request
.set_helo_dom(client_identity_
.ascii().c_str());
2044 auto const spf_res
{SPF::Response
{spf_request
}};
2045 spf_result_
= spf_res
.result();
2046 spf_received_
= spf_res
.received_spf();
2047 spf_sender_domain_
= spf_request
.get_sender_dom();
2049 LOG(INFO
) << "spf_received_ == " << spf_received_
;
2051 if (spf_result_
== SPF::Result::FAIL
) {
2052 LOG(INFO
) << "FAIL " << spf_res
.header_comment();
2054 else if (spf_result_
== SPF::Result::NEUTRAL
) {
2055 LOG(INFO
) << "NEUTRAL " << spf_res
.header_comment();
2057 else if (spf_result_
== SPF::Result::PASS
) {
2058 LOG(INFO
) << "PASS " << spf_res
.header_comment();
2061 LOG(INFO
) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
2065 bool Session::verify_from_params_(parameters_t
const& parameters
)
2067 // Take a look at the optional parameters:
2068 for (auto const& [name
, value
] : parameters
) {
2069 if (iequal(name
, "BODY")) {
2070 if (iequal(value
, "8BITMIME")) {
2071 // everything is cool, this is our default...
2073 else if (iequal(value
, "7BIT")) {
2074 // nothing to see here, move along...
2076 else if (iequal(value
, "BINARYMIME")) {
2080 LOG(WARNING
) << "unrecognized BODY type \"" << value
<< "\" requested";
2083 else if (iequal(name
, "SMTPUTF8")) {
2084 if (!value
.empty()) {
2085 LOG(WARNING
) << "SMTPUTF8 parameter has a value: " << value
;
2090 // else if (iequal(name, "PRDR")) {
2091 // LOG(INFO) << "using PRDR";
2095 else if (iequal(name
, "SIZE")) {
2096 if (value
.empty()) {
2097 LOG(WARNING
) << "SIZE parameter has no value.";
2101 auto const sz
= stoull(value
);
2102 if (sz
> max_msg_size()) {
2103 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
2104 LOG(WARNING
) << "SIZE parameter too large: " << sz
;
2108 catch (std::invalid_argument
const& e
) {
2109 LOG(WARNING
) << "SIZE parameter has invalid value: " << value
;
2111 catch (std::out_of_range
const& e
) {
2112 LOG(WARNING
) << "SIZE parameter has out-of-range value: " << value
;
2114 // I guess we just ignore bad size parameters.
2117 else if (iequal(name
, "REQUIRETLS")) {
2119 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush
;
2120 LOG(WARNING
) << "REQUIRETLS needed";
2125 LOG(WARNING
) << "unrecognized 'MAIL FROM' parameter " << name
<< "="
2133 bool Session::verify_rcpt_params_(parameters_t
const& parameters
)
2135 // Take a look at the optional parameters:
2136 for (auto const& [name
, value
] : parameters
) {
2137 if (iequal(name
, "RRVS")) {
2138 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
2139 LOG(INFO
) << name
<< "=" << value
;
2142 LOG(WARNING
) << "unrecognized 'RCPT TO' parameter " << name
<< "="
2150 // check recipient from RFC5321 RCPT TO:
2151 bool Session::verify_recipient_(Mailbox
const& recipient
)
2153 if ((recipient
.local_part() == "Postmaster") && (recipient
.domain() == "")) {
2154 LOG(INFO
) << "magic Postmaster address";
2158 auto const accepted_domain
{[this, &recipient
] {
2159 if (recipient
.domain().is_address_literal()) {
2160 if (recipient
.domain() != sock_
.us_address_literal()) {
2161 LOG(WARNING
) << "recipient.domain address " << recipient
.domain()
2162 << " does not match ours " << sock_
.us_address_literal();
2170 // Domains we accept mail for.
2171 if (accept_domains_
.is_open()) {
2172 if (accept_domains_
.contains(recipient
.domain().ascii()) ||
2173 accept_domains_
.contains(recipient
.domain().utf8())) {
2178 // If we have no list of domains to accept, at least take our own.
2179 if (recipient
.domain() == server_id_()) {
2187 if (!accepted_domain
) {
2188 out_() << "550 5.7.1 relay access denied\r\n" << std::flush
;
2189 LOG(WARNING
) << "relay access denied for domain " << recipient
.domain();
2193 if (recipient
.local_part() == "gene" && client_fcrdns_
.size() &&
2194 client_fcrdns_
[0].ascii().ends_with("outlook.com")) {
2195 // Getting Spam'ed by MS
2196 if (reverse_path_
.empty() || (reverse_path_
.length() > 40) ||
2197 reverse_path_
.domain().ascii().ends_with(".onmicrosoft.com")) {
2198 std::string error_msg
= fmt::format("rejecting spammy message from {}",
2199 client_fcrdns_
[0].ascii());
2200 LOG(WARNING
) << error_msg
;
2201 out_() << "550 5.7.0 " << error_msg
<< "\r\n" << std::flush
;
2206 // Check for local addresses we reject.
2208 auto bad_recipients_db_name
= config_path_
/ "bad_recipients";
2209 CDB bad_recipients_db
;
2211 std::string loc
= recipient
.local_part();
2212 std::transform(loc
.begin(), loc
.end(), loc
.begin(),
2213 [](unsigned char c
) { return std::tolower(c
); });
2215 if (bad_recipients_db
.open(bad_recipients_db_name
) &&
2216 bad_recipients_db
.contains(loc
)) {
2217 out_() << "550 5.1.1 bad recipient " << recipient
<< "\r\n" << std::flush
;
2218 LOG(WARNING
) << "bad recipient " << recipient
;
2224 auto fail_db_name
= config_path_
/ "fail_554";
2225 if (fs::exists(fail_db_name
)) {
2227 if (fail_db
.open(fail_db_name
) &&
2228 fail_db
.contains(recipient
.local_part())) {
2229 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2232 LOG(WARNING
) << "fail_554 recipient " << recipient
;
2239 auto temp_fail_db_name
= config_path_
/ "temp_fail";
2241 if (temp_fail
.open(temp_fail_db_name
) &&
2242 temp_fail
.contains(recipient
.local_part())) {
2243 out_() << "432 4.3.0 recipient's incoming mail queue has been stopped\r\n"
2245 LOG(WARNING
) << "temp fail for recipient " << recipient
;
2250 // Check for and act on magic "wait" address.
2252 using namespace boost::xpressive
;
2254 sregex
const rex
= icase("wait-rcpt-") >> (secs_
= +_d
);
2257 if (regex_match(recipient
.local_part(), what
, rex
) ||
2258 regex_match(recipient
.local_part(), what
, all_rex
)) {
2259 auto const str
= what
[secs_
].str();
2260 LOG(INFO
) << "waiting at RCPT TO " << str
<< " seconds";
2262 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
2263 google::FlushLogFiles(google::INFO
);
2264 out_() << std::flush
;
2266 LOG(INFO
) << "done waiting";
2270 // This is a trap for a probe done by some senders to see if we
2271 // accept just any old local-part.
2272 // if (!extensions_) {
2273 // if (recipient.local_part().length() > 8) {
2274 // out_() << "550 5.1.1 unknown recipient " << recipient << "\r\n"
2276 // LOG(WARNING) << "unknown recipient for HELO " << recipient;