13 #include "MessageStore.hpp"
14 #include "Session.hpp"
19 #include <fmt/format.h>
20 #include <fmt/ostream.h>
22 #include <boost/algorithm/string/classification.hpp>
23 #include <boost/algorithm/string/split.hpp>
25 #include <boost/xpressive/xpressive.hpp>
29 DEFINE_string(selector
, "ghsmtp", "DKIM selector");
31 using namespace std::string_literals
;
39 <https://www.dnswl.org/?page_id=15#query>
43 The return codes are structured as 127.0.x.y, with “x” indicating the category
44 of an entry and “y” indicating how trustworthy an entry has been judged.
46 Categories (127.0.X.y):
48 2 – Financial services
49 3 – Email Service Providers
50 4 – Organisations (both for-profit [ie companies] and non-profit)
51 5 – Service/network providers
52 6 – Personal/private servers
53 7 – Travel/leisure industry
54 8 – Public sector/governments
55 9 – Media and Tech companies
56 10 – some special cases
57 11 – Education, academic
59 13 – Manufacturing/Industrial
60 14 – Retail/Wholesale/Services
61 15 – Email Marketing Providers
62 20 – Added through Self Service without specific category
64 Trustworthiness / Score (127.0.x.Y):
66 0 = none – only avoid outright blocking (eg large ESP mailservers, -0.1)
67 1 = low – reduce chance of false positives (-1.0)
68 2 = medium – make sure to avoid false positives but allow override for clear
69 cases (-10.0) 3 = high – avoid override (-100.0).
71 The scores in parantheses are typical SpamAssassin scores.
73 Special return code 127.0.0.255
75 In cases where your nameserver issues more than 100’000 queries / 24 hours, you
76 may be blocked from further queries. The return code “127.0.0.255” indicates
82 "b.barracudacentral.org",
87 /*** Last octet from A record returned by blocklists ***
89 From <http://uribl.com/about.shtml#implementation>
92 ---------------------------------------------------------
93 1 00000001 Query blocked, possibly due to high volume
97 14 00001110 black,grey,red (for testpoints)
99 From <https://www.spamhaus.org/faq/section/Spamhaus%20DBL#291>
101 Return Codes Data Source
102 127.0.1.2 spam domain
103 127.0.1.4 phish domain
104 127.0.1.5 malware domain
105 127.0.1.6 botnet C&C domain
106 127.0.1.102 abused legit spam
107 127.0.1.103 abused spammed redirector domain
108 127.0.1.104 abused legit phish
109 127.0.1.105 abused legit malware
110 127.0.1.106 abused legit botnet C&C
111 127.0.1.255 IP queries prohibited!
113 From <http://www.surbl.org/lists#multi>
115 last octet indicates which lists it belongs to. The bit positions in
116 that last octet for membership in the different lists are:
125 char const* uribls
[]{
131 constexpr auto greeting_wait
= std::chrono::seconds
{2};
132 constexpr int max_recipients_per_message
= 100;
133 constexpr int max_unrecognized_cmds
= 20;
135 // Read timeout value gleaned from RFC-1123 section 5.3.2 and RFC-5321
136 // section 4.5.3.2.7.
137 constexpr auto read_timeout
= std::chrono::minutes
{5};
138 constexpr auto write_timeout
= std::chrono::seconds
{30};
139 } // namespace Config
141 #include <gflags/gflags.h>
143 DEFINE_bool(test_mode
, false, "ease up on some checks");
145 DEFINE_bool(immortal
, false, "don't set process timout");
147 DEFINE_uint64(max_read
, 0, "max data to read");
148 DEFINE_uint64(max_write
, 0, "max data to write");
150 DEFINE_bool(rrvs
, false, "support RRVS à la RFC 7293");
152 Session::Session(fs::path config_path
,
153 std::function
<void(void)> read_hook
,
156 : config_path_(config_path
)
158 , sock_(fd_in
, fd_out
, read_hook
, Config::read_timeout
, Config::write_timeout
)
159 , send_(config_path
, "smtp")
162 auto accept_db_name
= config_path_
/ "accept_domains";
163 auto allow_db_name
= config_path_
/ "allow";
164 auto block_db_name
= config_path_
/ "block";
165 auto forward_db_name
= config_path_
/ "forward";
167 accept_domains_
.open(accept_db_name
);
168 allow_
.open(allow_db_name
);
169 block_
.open(block_db_name
);
170 forward_
.open(forward_db_name
);
172 if (sock_
.has_peername() && !IP::is_private(sock_
.us_c_str())) {
173 auto fcrdns
= DNS::fcrdns(res_
, sock_
.us_c_str());
174 for (auto const& fcr
: fcrdns
) {
175 server_fcrdns_
.emplace_back(fcr
);
179 server_identity_
= [this] {
180 auto const id_from_env
{getenv("GHSMTP_SERVER_ID")};
182 return std::string
{id_from_env
};
184 auto const hostname
{osutil::get_hostname()};
185 if (hostname
.find('.') != std::string::npos
)
188 if (!server_fcrdns_
.empty()) {
189 // first result should be shortest
190 return server_fcrdns_
.front().ascii();
193 auto const us_c_str
= sock_
.us_c_str();
194 if (us_c_str
&& !IP::is_private(us_c_str
)) {
195 return IP::to_address_literal(us_c_str
);
198 LOG(FATAL
) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
202 send_
.set_sender(server_identity_
);
204 max_msg_size(Config::max_msg_size_initial
);
207 void Session::max_msg_size(size_t max
)
209 max_msg_size_
= max
; // number to advertise via RFC 1870
211 if (FLAGS_max_read
) {
212 sock_
.set_max_read(FLAGS_max_read
);
215 auto const overhead
= std::max(max
/ 10, size_t(2048));
216 sock_
.set_max_read(max
+ overhead
);
220 void Session::bad_host_(char const* msg
) const
222 if (sock_
.has_peername()) {
223 // On my systems, this pattern triggers a fail2ban rule that
224 // blocks connections from this IP address on port 25 for a few
225 // days. See <https://www.fail2ban.org/> for more info.
226 syslog(LOG_MAIL
| LOG_WARNING
, "bad host [%s] %s", sock_
.them_c_str(), msg
);
228 std::exit(EXIT_SUCCESS
);
231 void Session::reset_()
233 // RSET does not force another EHLO/HELO, the one piece of per
234 // transaction data saved is client_identity_:
236 // client_identity_.clear(); <-- not cleared!
238 reverse_path_
.clear();
239 forward_path_
.clear();
240 spf_received_helo_
.clear();
241 spf_received_mailfrom_
.clear();
254 max_msg_size(max_msg_size());
256 state_
= xact_step::mail
;
260 // Return codes from connection establishment are 220 or 554, according
261 // to RFC 5321. That's it.
263 void Session::greeting()
265 CHECK(state_
== xact_step::helo
);
267 if (sock_
.has_peername()) {
268 close(2); // if we're a networked program, never send to stderr
270 std::string error_msg
;
271 if (!verify_ip_address_(error_msg
)) {
272 // no glog message at this point
273 bad_host_(error_msg
.c_str());
276 /******************************************************************
277 <https://tools.ietf.org/html/rfc5321#section-4.3.1> says:
279 4.3. Sequencing of Commands and Replies
281 4.3.1. Sequencing Overview
283 The communication between the sender and receiver is an alternating
284 dialogue, controlled by the sender. As such, the sender issues a
285 command and the receiver responds with a reply. Unless other
286 arrangements are negotiated through service extensions, the sender
287 MUST wait for this response before sending further commands. One
288 important reply is the connection greeting. Normally, a receiver
289 will send a 220 "Service ready" reply when the connection is
290 completed. The sender SHOULD wait for this greeting message before
291 sending any commands.
295 “…the receiver responds with a reply.”
296 “…the sender MUST wait for this response…”
297 “One important reply is the connection greeting.”
298 “The sender SHOULD wait for this greeting…”
300 So is it MUST or SHOULD? I enforce MUST.
301 *******************************************************************/
303 // Wait a bit of time for pre-greeting traffic.
304 if (!(ip_allowed_
|| fcrdns_allowed_
)) {
305 if (sock_
.input_ready(Config::greeting_wait
)) {
306 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
307 // no glog message at this point
308 bad_host_("input before any greeting");
310 // Give a half greeting and wait again.
311 out_() << "220-" << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
312 if (sock_
.input_ready(Config::greeting_wait
)) {
313 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
314 // LOG(INFO) << "half greeting got " << client_;
315 bad_host_("input before full greeting");
318 LOG(INFO
) << "connect from " << client_
;
321 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
323 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
324 alarm(2 * 60); // initial alarm
328 void Session::flush() { out_() << std::flush
; }
330 void Session::last_in_group_(std::string_view verb
)
332 if (sock_
.input_ready(std::chrono::seconds(0))) {
333 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
337 void Session::lo_(char const* verb
, std::string_view client_identity
)
339 last_in_group_(verb
);
342 if (client_identity_
!= client_identity
) {
343 client_identity_
= client_identity
;
345 std::string error_msg
;
346 if (!verify_client_(client_identity_
, error_msg
)) {
347 // no glog message at this point
348 bad_host_(error_msg
.c_str());
353 out_() << "250 " << server_id_() << "\r\n";
359 if (sock_
.has_peername()) {
360 out_() << "250-" << server_id_() << " at your service, " << client_
364 out_() << "250-" << server_id_() << "\r\n";
367 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
368 out_() << "250-8BITMIME\r\n"; // RFC 6152
371 out_() << "250-RRVS\r\n"; // RFC 7293
374 // out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
377 // Check sasl sources for auth types.
378 // out_() << "250-AUTH PLAIN\r\n";
379 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
382 // If we're not already TLS, offer TLS
383 out_() << "250-STARTTLS\r\n"; // RFC 3207
385 out_() << "250-ENHANCEDSTATUSCODES\r\n" // RFC 2034
386 "250-PIPELINING\r\n" // RFC 2920
387 //-----------------------------------------------
388 // Disable this right now, nobody uses it anyhow,
389 // but this might break DKIM signing for relay.
390 // "250-BINARYMIME\r\n" // RFC 3030
391 //-----------------------------------------------
392 "250-CHUNKING\r\n" // RFC 3030
393 "250 SMTPUTF8\r\n"; // RFC 6531
396 out_() << std::flush
;
398 if (sock_
.has_peername()) {
399 if (std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
400 client_identity_
) != end(client_fcrdns_
)) {
401 LOG(INFO
) << verb
<< " " << client_identity
<< " from "
402 << sock_
.them_address_literal();
405 LOG(INFO
) << verb
<< " " << client_identity
<< " from " << client_
;
409 LOG(INFO
) << verb
<< " " << client_identity
;
413 void Session::mail_from(Mailbox
&& reverse_path
, parameters_t
const& parameters
)
416 case xact_step::helo
:
417 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
418 LOG(WARNING
) << "'MAIL FROM' before HELO/EHLO"
419 << (sock_
.has_peername() ? " from " : "") << client_
;
421 case xact_step::mail
: break;
422 case xact_step::rcpt
:
423 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
424 LOG(WARNING
) << "nested MAIL command"
425 << (sock_
.has_peername() ? " from " : "") << client_
;
427 case xact_step::data
:
428 case xact_step::bdat
:
429 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush
;
430 LOG(WARNING
) << "nested MAIL command"
431 << (sock_
.has_peername() ? " from " : "") << client_
;
433 case xact_step::rset
:
434 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
435 LOG(WARNING
) << "error state must be cleared with a RSET"
436 << (sock_
.has_peername() ? " from " : "") << client_
;
440 if (!verify_from_params_(parameters
)) {
444 std::string error_msg
;
445 if (!verify_sender_(reverse_path
, error_msg
)) {
446 LOG(WARNING
) << "verify sender failed: " << error_msg
;
447 bad_host_(error_msg
.c_str());
450 reverse_path_
= std::move(reverse_path
);
453 forward_path_
.clear();
454 out_() << "250 2.1.0 MAIL FROM OK\r\n";
455 // No flush RFC-2920 section 3.1, this could be part of a command group.
457 fmt::memory_buffer params
;
458 for (auto const& [name
, value
] : parameters
) {
459 fmt::format_to(params
, " {}", name
);
460 if (!value
.empty()) {
461 fmt::format_to(params
, "={}", value
);
464 LOG(INFO
) << "MAIL FROM:<" << reverse_path_
<< ">" << fmt::to_string(params
);
466 state_
= xact_step::rcpt
;
469 bool Session::forward_to_(std::string
const& forward
, Mailbox
const& rcpt_to
)
471 // If we're already forwarding or replying, reject
472 if (!fwd_path_
.empty() || !rep_info_
.empty()) {
473 out_() << "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n"
475 LOG(WARNING
) << "failed to forward to <" << forward
476 << "> already forwarding or replying for: " << rcpt_to
;
480 fwd_path_
= Mailbox(forward
);
483 // New bounce address
484 SRS0::from_to bounce
;
485 bounce
.mail_from
= reverse_path_
.as_string(Mailbox::domain_encoding::ascii
);
487 auto const new_bounce
= srs_
.enc_bounce(bounce
, server_id_().c_str());
489 auto const mail_from
= Mailbox(new_bounce
);
491 std::string error_msg
;
492 if (!send_
.mail_from_rcpt_to(res_
, mail_from
, fwd_path_
, error_msg
)) {
493 out_() << error_msg
<< std::flush
;
494 LOG(WARNING
) << "failed to forward <" << fwd_path_
<< "> " << error_msg
;
498 LOG(INFO
) << "RCPT TO:<" << rcpt_to
<< "> forwarding to == <" << fwd_path_
503 bool Session::reply_to_(SRS0::from_to
const& reply_info
, Mailbox
const& rcpt_to
)
505 // If we're already forwarding or replying, reject
506 if (!fwd_path_
.empty() || !rep_info_
.empty()) {
507 out_() << "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n"
509 LOG(WARNING
) << "failed to reply to <" << reply_info
.mail_from
510 << "> already forwarding or replying for: " << rcpt_to
;
514 rep_info_
= reply_info
;
516 Mailbox
const from(rep_info_
.rcpt_to_local_part
, server_identity_
);
517 Mailbox
const to(rep_info_
.mail_from
);
519 std::string error_msg
;
520 if (!send_
.mail_from_rcpt_to(res_
, from
, to
, error_msg
)) {
521 out_() << error_msg
<< std::flush
;
522 LOG(WARNING
) << "failed to reply from <" << from
<< "> to <" << to
<< "> "
527 LOG(INFO
) << "RCPT TO:<" << rcpt_to
<< "> is a reply to "
528 << rep_info_
.mail_from
<< " from " << rep_info_
.rcpt_to_local_part
;
532 void Session::rcpt_to(Mailbox
&& forward_path
, parameters_t
const& parameters
)
535 case xact_step::helo
:
536 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
537 LOG(WARNING
) << "'RCPT TO' before HELO/EHLO"
538 << (sock_
.has_peername() ? " from " : "") << client_
;
540 case xact_step::mail
:
541 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
542 LOG(WARNING
) << "'RCPT TO' before 'MAIL FROM'"
543 << (sock_
.has_peername() ? " from " : "") << client_
;
545 case xact_step::rcpt
:
546 case xact_step::data
: break;
547 case xact_step::bdat
:
548 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
549 LOG(WARNING
) << "'RCPT TO' during BDAT transfer"
550 << (sock_
.has_peername() ? " from " : "") << client_
;
552 case xact_step::rset
:
553 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
554 LOG(WARNING
) << "error state must be cleared with a RSET"
555 << (sock_
.has_peername() ? " from " : "") << client_
;
559 if (!verify_rcpt_params_(parameters
))
562 if (!verify_recipient_(forward_path
))
565 if (forward_path_
.size() >= Config::max_recipients_per_message
) {
566 out_() << "452 4.5.3 too many recipients\r\n" << std::flush
;
567 LOG(WARNING
) << "too many recipients <" << forward_path
<< ">";
570 // no check for dups, postfix doesn't
571 forward_path_
.emplace_back(std::move(forward_path
));
573 Mailbox
const& rcpt_to_mbx
= forward_path_
.back();
575 auto const rcpt_to_str
=
576 rcpt_to_mbx
.as_string(Mailbox::domain_encoding::ascii
);
578 if (auto reply
= srs_
.dec_reply(rcpt_to_mbx
.local_part()); reply
) {
579 if (!reply_to_(*reply
, rcpt_to_mbx
))
582 else if (auto const forward
= forward_
.find(rcpt_to_str
.c_str()); forward
) {
583 if (!forward_to_(*forward
, rcpt_to_mbx
))
587 LOG(INFO
) << "RCPT TO:<" << rcpt_to_str
<< ">";
590 // No flush RFC-2920 section 3.1, this could be part of a command group.
591 out_() << "250 2.1.5 RCPT TO OK\r\n";
593 state_
= xact_step::data
;
596 // The headers Received and Received-SPF are returned as a string.
598 std::string
Session::added_headers_(MessageStore
const& msg
)
600 auto const protocol
{[this]() {
602 return sock_
.tls() ? "UTF8SMTPS" : "UTF8SMTP";
603 else if (extensions_
)
604 return sock_
.tls() ? "ESMTPS" : "ESMTP";
606 return sock_
.tls() ? "SMTPS" : "SMTP";
609 fmt::memory_buffer headers
;
611 // <https://tools.ietf.org/html/rfc5321#section-4.4>
612 fmt::format_to(headers
, "Received: from {}", client_identity_
.utf8());
613 if (sock_
.has_peername()) {
614 fmt::format_to(headers
, " ({})", client_
);
616 fmt::format_to(headers
, "\r\n\tby {} with {} id {}", server_identity_
.utf8(),
618 if (forward_path_
.size()) {
619 fmt::format_to(headers
, "\r\n\tfor <{}>", forward_path_
[0]);
620 for (auto i
= 1u; i
< forward_path_
.size(); ++i
)
621 fmt::format_to(headers
, ",\r\n\t <{}>", forward_path_
[i
]);
623 std::string
const tls_info
{sock_
.tls_info()};
624 if (tls_info
.length()) {
625 fmt::format_to(headers
, "\r\n\t({})", tls_info
);
627 fmt::format_to(headers
, ";\r\n\t{}\r\n", msg
.when());
630 if (!spf_received_helo_
.empty()) {
631 fmt::format_to(headers
, "{}\r\n", spf_received_helo_
);
633 if (!spf_received_mailfrom_
.empty()) {
634 fmt::format_to(headers
, "{}\r\n", spf_received_mailfrom_
);
637 return fmt::to_string(headers
);
641 bool lookup_domain(CDB
& cdb
, Domain
const& domain
)
643 if (!domain
.empty()) {
644 if (cdb
.contains(domain
.ascii())) {
647 if (domain
.is_unicode() && cdb
.contains(domain
.utf8())) {
655 std::tuple
<Session::SpamStatus
, std::string
> Session::spam_status_()
657 if (spf_result_mailfrom_
== SPF::Result::FAIL
&&
658 spf_result_helo_
== SPF::Result::FAIL
&& !ip_allowed_
)
659 return {SpamStatus::spam
, "SPF failed"};
661 // These should have already been rejected by verify_client_().
662 if ((reverse_path_
.domain() == "localhost.local") ||
663 (reverse_path_
.domain() == "localhost"))
664 return {SpamStatus::spam
, "bogus reverse_path"};
666 std::vector
<std::string
> why_ham
;
668 // Anything enciphered tastes a lot like ham.
670 why_ham
.emplace_back("they used TLS");
672 if (spf_result_helo_
== SPF::Result::PASS
) {
673 if (lookup_domain(allow_
, spf_sender_domain_helo_
)) {
674 why_ham
.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
675 spf_sender_domain_helo_
.utf8()));
679 tld_db_
.get_registered_domain(spf_sender_domain_helo_
.ascii())};
680 if (tld_dom
&& allow_
.contains(tld_dom
)) {
681 why_ham
.emplace_back(fmt::format(
682 "SPF sender registered domain ({}) is allowed", tld_dom
));
687 if (spf_result_mailfrom_
== SPF::Result::PASS
) {
688 if (lookup_domain(allow_
, spf_sender_domain_mailfrom_
)) {
689 why_ham
.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
690 spf_sender_domain_mailfrom_
.utf8()));
694 tld_db_
.get_registered_domain(spf_sender_domain_mailfrom_
.ascii())};
695 if (tld_dom
&& allow_
.contains(tld_dom
)) {
696 why_ham
.emplace_back(fmt::format(
697 "SPF sender registered domain ({}) is allowed", tld_dom
));
703 why_ham
.emplace_back(
704 fmt::format("FCrDNS (or it's registered domain) is allowed"));
706 if (!why_ham
.empty())
707 return {SpamStatus::ham
,
708 fmt::format("{}", fmt::join(std::begin(why_ham
), std::end(why_ham
),
711 return {SpamStatus::spam
, "it's not ham"};
714 static std::string
folder(Session::SpamStatus status
,
715 std::vector
<Mailbox
> const& forward_path
,
716 Mailbox
const& reverse_path
)
718 if (status
== Session::SpamStatus::spam
)
724 bool Session::msg_new()
726 CHECK((state_
== xact_step::data
) || (state_
== xact_step::bdat
));
728 auto const& [status
, reason
]{spam_status_()};
730 LOG(INFO
) << ((status
== SpamStatus::ham
) ? "ham since " : "spam since ")
733 // All sources of ham get a fresh 5 minute timeout per message.
734 if (status
== SpamStatus::ham
) {
735 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr))
739 msg_
= std::make_unique
<MessageStore
>();
741 if (!FLAGS_max_write
)
742 FLAGS_max_write
= max_msg_size();
745 msg_
->open(server_id_(), FLAGS_max_write
,
746 folder(status
, forward_path_
, reverse_path_
));
747 auto const hdrs
{added_headers_(*(msg_
.get()))};
750 // fmt::memory_buffer spam_status;
751 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
752 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
753 // msg_->write(spam_status.data(), spam_status.size());
755 LOG(INFO
) << "Spam-Status: "
756 << ((status
== SpamStatus::spam
) ? "Yes" : "No") << ", "
761 catch (std::system_error
const& e
) {
764 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
765 LOG(ERROR
) << "no space";
771 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
772 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
773 LOG(ERROR
) << e
.what();
779 catch (std::exception
const& e
) {
780 out_() << "550 5.0.0 mail error\r\n" << std::flush
;
781 LOG(ERROR
) << e
.what();
787 // out_() << "550 5.0.0 mail error\r\n" << std::flush;
788 // LOG(ERROR) << "msg_new failed with no exception thrown";
794 bool Session::msg_write(char const* s
, std::streamsize count
)
796 if ((state_
!= xact_step::data
) && (state_
!= xact_step::bdat
))
803 if (msg_
->write(s
, count
))
806 catch (std::system_error
const& e
) {
809 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
810 LOG(ERROR
) << "no space";
816 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
817 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
818 LOG(ERROR
) << e
.what();
824 catch (std::exception
const& e
) {
825 out_() << "550 5.0.0 mail error\r\n" << std::flush
;
826 LOG(ERROR
) << e
.what();
832 out_() << "550 5.0.0 mail error\r\n" << std::flush
;
833 LOG(ERROR
) << "write failed with no exception thrown";
839 bool Session::data_start()
841 last_in_group_("DATA");
844 case xact_step::helo
:
845 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
846 LOG(WARNING
) << "'DATA' before HELO/EHLO"
847 << (sock_
.has_peername() ? " from " : "") << client_
;
849 case xact_step::mail
:
850 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
851 LOG(WARNING
) << "'DATA' before 'MAIL FROM'"
852 << (sock_
.has_peername() ? " from " : "") << client_
;
854 case xact_step::rcpt
:
856 /******************************************************************
857 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
859 The DATA command can fail at only two points in the protocol exchange:
861 If there was no MAIL, or no RCPT, command, or all such commands were
862 rejected, the server MAY return a "command out of sequence" (503) or
863 "no valid recipients" (554) reply in response to the DATA command.
865 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
867 The additional restriction is that when there have been no successful
868 RCPT commands in the mail transaction, the DATA command MUST fail
869 with a 503 reply code.
871 Therefore I will send the reply code that is valid for both, and
872 do the same for the BDAT case.
873 *******************************************************************/
875 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
876 LOG(WARNING
) << "no valid recipients"
877 << (sock_
.has_peername() ? " from " : "") << client_
;
879 case xact_step::data
: break;
880 case xact_step::bdat
:
881 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
882 LOG(WARNING
) << "'DATA' during BDAT transfer"
883 << (sock_
.has_peername() ? " from " : "") << client_
;
885 case xact_step::rset
:
886 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
887 LOG(WARNING
) << "error state must be cleared with a RSET"
888 << (sock_
.has_peername() ? " from " : "") << client_
;
893 out_() << "503 5.5.1 DATA does not support BINARYMIME\r\n" << std::flush
;
894 LOG(WARNING
) << "DATA does not support BINARYMIME";
895 state_
= xact_step::rset
; // RFC 3030 section 3 page 5
900 LOG(ERROR
) << "msg_new() failed";
904 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush
;
909 bool Session::do_forward_(message::parsed
& msg
)
913 // Generate a reply address
915 reply
.mail_from
= msg_fwd
.dmarc_from
;
916 reply
.rcpt_to_local_part
= fwd_from_
.local_part();
918 auto const reply_addr
=
919 fmt::format("{}@{}", srs_
.enc_reply(reply
), server_id_());
921 auto const munging
= false;
923 auto const sender
= server_identity_
.ascii().c_str();
924 auto const selector
= FLAGS_selector
.c_str();
925 auto const key_file
=
926 (config_path_
/ FLAGS_selector
).replace_extension("private");
927 CHECK(fs::exists(key_file
)) << "can't find key file " << key_file
;
930 auto const from_hdr
=
931 fmt::format("From: \"{} via\" <@>", msg_fwd
.dmarc_from
, reply_addr
);
932 message::rewrite_from_to(msg_fwd
, from_hdr
, "", sender
, selector
, key_file
);
935 auto const reply_to_hdr
= fmt::format("Reply-To: {}", reply_addr
);
936 message::rewrite_from_to(msg_fwd
, "", reply_to_hdr
, sender
, selector
,
941 if (!send_
.send(msg_fwd
.as_string())) {
942 out_() << "432 4.3.0 Recipient's incoming mail queue has been "
946 LOG(ERROR
) << "failed to send for " << fwd_path_
;
950 LOG(INFO
) << "successfully sent for " << fwd_path_
;
954 bool Session::do_reply_(message::parsed
& msg
)
956 Mailbox
to_mbx(rep_info_
.mail_from
);
957 Mailbox
from_mbx(rep_info_
.rcpt_to_local_part
, server_identity_
);
959 auto reply
= std::make_unique
<MessageStore
>();
960 reply
->open(server_id_(), FLAGS_max_write
, ".Drafts");
962 auto const date
{Now
{}};
963 auto const pill
{Pill
{}};
965 fmt::format("<{}.{}@{}>", date
.sec(), pill
, server_identity_
);
967 fmt::memory_buffer bfr
;
969 fmt::format_to(bfr
, "From: <{}>\r\n", from_mbx
);
970 fmt::format_to(bfr
, "To: <{}>\r\n", to_mbx
);
972 fmt::format_to(bfr
, "Date: {}\r\n", date
.c_str());
974 fmt::format_to(bfr
, "Message-ID: {}\r\n", mid_str
.c_str());
976 if (!msg
.get_header(message::Subject
).empty()) {
977 fmt::format_to(bfr
, "{}: {}\r\n", message::Subject
,
978 msg
.get_header(message::Subject
));
981 fmt::format_to(bfr
, "{}: {}\r\n", message::Subject
,
982 "Reply to your message");
985 if (!msg
.get_header(message::In_Reply_To
).empty()) {
986 fmt::format_to(bfr
, "{}: {}\r\n", message::In_Reply_To
,
987 msg
.get_header(message::In_Reply_To
));
990 if (!msg
.get_header(message::MIME_Version
).empty() &&
991 msg
.get_header(message::Content_Type
).empty()) {
992 fmt::format_to(bfr
, "{}: {}\r\n", message::MIME_Version
,
993 msg
.get_header(message::MIME_Version
));
994 fmt::format_to(bfr
, "{}: {}\r\n", message::Content_Type
,
995 msg
.get_header(message::Content_Type
));
998 reply
->write(fmt::to_string(bfr
));
1000 if (!msg
.body
.empty()) {
1001 reply
->write("\r\n");
1002 reply
->write(msg
.body
);
1005 auto const msg_data
= reply
->freeze();
1006 message::parsed msg_reply
;
1007 CHECK(msg_reply
.parse(msg_data
));
1009 auto const sender
= server_identity_
.ascii().c_str();
1010 auto const selector
= FLAGS_selector
.c_str();
1011 auto const key_file
=
1012 (config_path_
/ FLAGS_selector
).replace_extension("private");
1013 CHECK(fs::exists(key_file
)) << "can't find key file " << key_file
;
1015 message::dkim_sign(msg_reply
, sender
, selector
, key_file
);
1017 if (!send_
.send(msg_reply
.as_string())) {
1018 out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1022 LOG(ERROR
) << "send failed for reply to " << to_mbx
<< " from " << from_mbx
;
1026 LOG(INFO
) << "successful reply to " << to_mbx
<< " from " << from_mbx
;
1030 bool Session::do_deliver_()
1034 auto const sender
= server_identity_
.ascii().c_str();
1035 auto const selector
= FLAGS_selector
.c_str();
1036 auto const key_file
=
1037 (config_path_
/ FLAGS_selector
).replace_extension("private");
1038 CHECK(fs::exists(key_file
)) << "can't find key file " << key_file
;
1041 auto const msg_data
= msg_
->freeze();
1043 message::parsed msg
;
1045 // Only deal in RFC-5322 Mail Objects.
1046 bool const message_parsed
= msg
.parse(msg_data
);
1047 if (message_parsed
) {
1049 // remove any Return-Path
1050 message::remove_delivery_headers(msg
);
1052 auto const authentic
=
1054 message::authentication(msg
, sender
, selector
, key_file
);
1056 // write a new Return-Path
1057 msg_
->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_
));
1059 for (auto const h
: msg
.headers
) {
1060 msg_
->write(h
.as_string());
1061 msg_
->write("\r\n");
1063 if (!msg
.body
.empty()) {
1064 msg_
->write("\r\n");
1065 msg_
->write(msg
.body
);
1070 if (authentic
&& !fwd_path_
.empty()) {
1071 if (!do_forward_(msg
))
1074 if (authentic
&& !rep_info_
.empty()) {
1075 if (!do_reply_(msg
))
1082 catch (std::system_error
const& e
) {
1085 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
1086 LOG(ERROR
) << "no space";
1092 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
1094 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
1095 LOG(ERROR
) << e
.what();
1105 void Session::data_done()
1107 CHECK((state_
== xact_step::data
));
1109 if (msg_
&& msg_
->size_error()) {
1117 // out_() << "353\r\n";
1118 // for (auto fp : forward_path_) {
1119 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1124 using namespace boost::xpressive
;
1127 sregex
const rex
= icase("wait-data-") >> (secs_
= +_d
);
1130 for (auto fp
: forward_path_
) {
1131 if (regex_match(fp
.local_part(), what
, rex
)) {
1132 auto const str
= what
[secs_
].str();
1133 LOG(INFO
) << "waiting at DATA " << str
<< " seconds";
1135 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1137 LOG(INFO
) << "done waiting";
1142 out_() << "250 2.0.0 DATA OK\r\n" << std::flush
;
1143 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1149 void Session::data_size_error()
1151 out_().clear(); // clear possible eof from input side
1152 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1156 LOG(WARNING
) << "DATA size error";
1160 void Session::data_error()
1162 out_().clear(); // clear possible eof from input side
1163 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush
;
1167 LOG(WARNING
) << "DATA error";
1171 bool Session::bdat_start(size_t n
)
1174 case xact_step::helo
:
1175 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
1176 LOG(WARNING
) << "'BDAT' before HELO/EHLO"
1177 << (sock_
.has_peername() ? " from " : "") << client_
;
1179 case xact_step::mail
:
1180 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
1181 LOG(WARNING
) << "'BDAT' before 'MAIL FROM'"
1182 << (sock_
.has_peername() ? " from " : "") << client_
;
1184 case xact_step::rcpt
:
1185 // See comment in data_start()
1186 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1187 LOG(WARNING
) << "no valid recipients"
1188 << (sock_
.has_peername() ? " from " : "") << client_
;
1190 case xact_step::data
: // first bdat
1192 case xact_step::bdat
: return true;
1193 case xact_step::rset
:
1194 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1195 LOG(WARNING
) << "error state must be cleared with a RSET"
1196 << (sock_
.has_peername() ? " from " : "") << client_
;
1200 state_
= xact_step::bdat
;
1205 void Session::bdat_done(size_t n
, bool last
)
1207 if (state_
!= xact_step::bdat
) {
1216 if (msg_
->size_error()) {
1222 out_() << "250 2.0.0 BDAT " << n
<< " OK\r\n" << std::flush
;
1223 LOG(INFO
) << "BDAT " << n
;
1229 out_() << "250 2.0.0 BDAT " << n
<< " LAST OK\r\n" << std::flush
;
1231 LOG(INFO
) << "BDAT " << n
<< " LAST";
1232 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1237 void Session::bdat_size_error()
1239 out_().clear(); // clear possible eof from input side
1240 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1244 LOG(WARNING
) << "BDAT size error";
1248 void Session::bdat_seq_error()
1250 out_().clear(); // clear possible eof from input side
1251 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush
;
1255 LOG(WARNING
) << "BDAT sequence error";
1259 void Session::bdat_io_error()
1261 out_().clear(); // clear possible eof from input side
1262 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush
;
1266 LOG(WARNING
) << "BDAT I/O error";
1270 void Session::rset()
1272 out_() << "250 2.1.5 RSET OK\r\n";
1273 // No flush RFC-2920 section 3.1, this could be part of a command group.
1274 LOG(INFO
) << "RSET";
1278 void Session::noop(std::string_view str
)
1280 last_in_group_("NOOP");
1281 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush
;
1282 LOG(INFO
) << "NOOP" << (str
.length() ? " " : "") << str
;
1285 void Session::vrfy(std::string_view str
)
1287 last_in_group_("VRFY");
1288 out_() << "252 2.1.5 try it\r\n" << std::flush
;
1289 LOG(INFO
) << "VRFY" << (str
.length() ? " " : "") << str
;
1292 void Session::help(std::string_view str
)
1294 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n" << std::flush
;
1295 LOG(INFO
) << "HELP" << (str
.length() ? " " : "") << str
;
1298 void Session::quit()
1301 // last_in_group_("QUIT");
1302 out_() << "221 2.0.0 closing connection\r\n" << std::flush
;
1303 LOG(INFO
) << "QUIT";
1307 void Session::auth()
1309 out_() << "454 4.7.0 authentication failure\r\n" << std::flush
;
1310 LOG(INFO
) << "AUTH";
1314 void Session::error(std::string_view log_msg
)
1316 out_() << "421 4.3.5 system error: " << log_msg
<< "\r\n" << std::flush
;
1317 LOG(WARNING
) << log_msg
;
1320 void Session::cmd_unrecognized(std::string_view cmd
)
1322 auto const escaped
{esc(cmd
)};
1323 LOG(WARNING
) << "command unrecognized: \"" << escaped
<< "\"";
1325 if (++n_unrecognized_cmds_
>= Config::max_unrecognized_cmds
) {
1326 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1327 << "\" exceeds limit\r\n"
1329 LOG(WARNING
) << n_unrecognized_cmds_
1330 << " unrecognized commands is too many";
1334 out_() << "500 5.5.1 command unrecognized: \"" << escaped
<< "\"\r\n"
1338 void Session::bare_lf()
1340 // Error code used by Office 365.
1341 out_() << "554 5.6.11 bare LF\r\n" << std::flush
;
1342 LOG(WARNING
) << "bare LF";
1346 void Session::max_out()
1348 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1349 LOG(WARNING
) << "message size maxed out";
1353 void Session::time_out()
1355 out_() << "421 4.4.2 time-out\r\n" << std::flush
;
1356 LOG(WARNING
) << "time-out" << (sock_
.has_peername() ? " from " : "")
1361 void Session::starttls()
1363 last_in_group_("STARTTLS");
1365 out_() << "554 5.5.1 TLS already active\r\n" << std::flush
;
1366 LOG(WARNING
) << "STARTTLS issued with TLS already active";
1369 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush
;
1370 if (sock_
.starttls_server(config_path_
)) {
1372 max_msg_size(Config::max_msg_size_bro
);
1373 LOG(INFO
) << "STARTTLS " << sock_
.tls_info();
1378 void Session::exit_()
1380 // sock_.log_totals();
1382 timespec time_used
{};
1383 clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &time_used
);
1385 LOG(INFO
) << "CPU time " << time_used
.tv_sec
<< "." << std::setw(9)
1386 << std::setfill('0') << time_used
.tv_nsec
<< " seconds";
1388 std::exit(EXIT_SUCCESS
);
1392 bool ip4_allowed(char const* addr
)
1397 char const* comment
;
1411 nw
const networks
[]{
1412 // the one very special case
1413 {"108.83.36.112", "255.255.255.248", "108.83.36.112/29"},
1415 // accept from major providers:
1416 {"3.0.0.0", "255.0.0.0", "3.0.0.0/9 and 3.128.0.0/9 Amazon"},
1417 {"5.45.198.0", "255.255.254.0", "5.45.198.0/23 YANDEX-5-45-198"},
1418 {"12.153.224.0", "255.255.255.0", "12.153.224.0/24 E-TRADE10-224"},
1419 {"17.0.0.0", "255.0.0.0", "17.0.0.0/8 APPLE-WWNET"},
1420 {"40.74.0.0", "255.254.0.0", "40.74.0.0/15 MSFT NET-40-74-0-0-1"},
1421 {"40.76.0.0", "255.252.0.0", "40.76.0.0/14 MSFT NET-40-74-0-0-1"},
1422 {"40.80.0.0", "255.240.0.0", "40.80.0.0/12 MSFT NET-40-74-0-0-1"},
1423 {"40.96.0.0", "255.240.0.0", "40.96.0.0/12 MSFT NET-40-74-0-0-1"},
1424 {"40.112.0.0", "255.248.0.0", "40.112.0.0/13 MSFT NET-40-74-0-0-1"},
1425 {"40.120.0.0", "255.252.0.0", "40.120.0.0/14 MSFT NET-40-74-0-0-1"},
1426 {"40.124.0.0", "255.255.0.0", "40.124.0.0/16 MSFT NET-40-74-0-0-1"},
1427 {"40.125.0.0", "255.255.128.0", "40.125.0.0/17 MSFT NET-40-74-0-0-1"},
1428 {"56.0.0.0", "255.0.0.0", "56.0.0.0/8 USPS1"},
1429 {"65.52.0.0", "255.252.0.0", "65.52.0.0/14 MICROSOFT-1BLK"},
1430 {"66.163.160.0", "255.255.224.0", "66.163.160.0/19 A-YAHOO-US2"},
1431 {"66.211.176.0", "255.255.240.0", "66.211.176.0/20 EBAY-2"},
1432 {"66.211.172.0", "255.255.252.0", "66.211.172.0/22 EBAY-2"},
1433 {"66.220.144.0", "255.255.240.0", "66.220.144.0/20 TFBNET3"},
1434 {"68.232.192.0", "255.255.240.0", "68.232.192.0/20 EXACT-IP-NET-2"},
1435 {"69.171.224.0", "255.255.224.0", "69.171.224.0/19 TFBNET3"},
1436 {"70.47.67.0", "255.255.255.0", "70.47.67.0/24 NET-462F4300-24"},
1437 {"74.6.0.0", "255.255.0.0", "INKTOMI-BLK-6 Oath"},
1438 {"74.125.0.0", "255.255.0.0", "74.125.0.0/16 GOOGLE"},
1439 {"75.101.100.43", "255.255.255.255", "new.toad.com"},
1440 {"76.178.68.57", "255.255.255.255", "cpe-76-178-68-57.natsow.res.rr.com"},
1441 {"98.136.0.0", "255.252.0.0", "98.136.0.0/14 A-YAHOO-US9"},
1442 {"104.40.0.0", "255.248.0.0", "104.40.0.0/13 MSFT"},
1443 {"108.174.0.0", "255.255.240.0", "108.174.0.0/20 LINKEDIN"},
1444 {"159.45.0.0", "255.255.0.0", "159.45.0.0/16 AGE-COM"},
1445 {"159.53.0.0", "255.255.0.0", "159.53.0.0/16 JMC"},
1446 {"159.135.224.0", "255.255.240.0", "159.135.224.0/20 MNO87-159-135-224-0-0"},
1447 {"162.247.72.0", "255.255.252.0", "162.247.72.0/22 CALYX-INSTITUTE-V4-1"},
1448 {"165.107.0.0", "255.255.0.0", "NET-LDC-CA-GOV"},
1449 {"192.175.128.0", "255.255.128.0", "192.175.128.0/17 NETBLK-VANGUARD"},
1450 {"198.2.128.0", "255.255.192.0", "198.2.128.0/18 RSG-DELIVERY"},
1451 {"198.252.206.0", "255.255.255.0", "198.252.206.0/24 SE-NET01"},
1452 {"199.122.120.0", "255.255.248.0", "199.122.120.0/21 EXACT-IP-NET-3"},
1453 {"204.13.164.0", "255.255.255.0", "204.13.164.0/24 RISEUP-NETWORKS-SWIFT-BLOCK2"},
1454 {"204.29.186.0", "255.255.254.0", "204.29.186.0/23 ATDN-NSCAPE"},
1455 {"205.139.104.0", "255.255.252.0", "205.139.104.0/22 SAVV-S259964-8"},
1456 {"205.201.128.0", "255.255.240.0", "205.201.128.0/20 RSG-DELIVERY"},
1457 {"208.118.235.0", "255.255.255.0", "208.118.235.0/24 TWDX-208-118-235-0-1"},
1458 {"208.192.0.0", "255.192.0.0", "208.192.0.0/10 UUNET1996B"},
1459 {"209.51.188.0", "255.255.255.0", "I:NET-209.51.188.0/24 FSF"},
1460 {"209.85.128.0", "255.255.128.0", "209.85.128.0/17 GOOGLE"},
1461 {"209.132.176.0", "255.255.240.0", "209.132.176.0/20 RED-HAT-BLK"},
1462 {"209.237.224.0", "255.255.224.0", "UNITEDLAYER-1"},
1463 // {"209.237.225.253", "255.255.255.255", "Old new.toad.com"},
1468 CHECK_EQ(inet_pton(AF_INET
, addr
, &addr32
), 1)
1469 << "can't interpret as IPv4 address";
1471 for (auto const& network
: networks
) {
1473 CHECK_EQ(inet_pton(AF_INET
, network
.net
, &net32
), 1)
1474 << "can't grok " << network
.net
;
1476 CHECK_EQ(inet_pton(AF_INET
, network
.mask
, &mask32
), 1)
1477 << "can't grok " << network
.mask
;
1479 // sanity check: all unmasked bits must be zero
1480 CHECK_EQ(net32
& (~mask32
), 0)
1481 << "bogus config net=" << network
.net
<< ", mask=" << network
.mask
;
1483 if (net32
== (addr32
& mask32
)) {
1484 LOG(INFO
) << addr
<< " allowed " << network
.comment
;
1493 /////////////////////////////////////////////////////////////////////////////
1495 // All of the verify_* functions send their own error messages back to
1496 // the client on failure, and return false.
1498 bool Session::verify_ip_address_(std::string
& error_msg
)
1500 auto ip_block_db_name
= config_path_
/ "ip-block";
1501 CDB ip_block
{ip_block_db_name
};
1502 if (ip_block
.contains(sock_
.them_c_str())) {
1504 fmt::format("IP address {} on static blocklist", sock_
.them_c_str());
1505 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1509 client_fcrdns_
.clear();
1511 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1512 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1513 LOG(INFO
) << "loopback address allowed";
1515 client_fcrdns_
.emplace_back("localhost");
1516 client_
= fmt::format("localhost {}", sock_
.them_address_literal());
1520 if (IP::is_private(sock_
.them_address_literal())) {
1521 LOG(INFO
) << "local address allowed";
1523 client_
= sock_
.them_address_literal();
1527 auto const fcrdns
= DNS::fcrdns(res_
, sock_
.them_c_str());
1528 for (auto const& fcr
: fcrdns
) {
1529 client_fcrdns_
.emplace_back(fcr
);
1532 if (!client_fcrdns_
.empty()) {
1533 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1534 sock_
.them_address_literal());
1536 for (auto const& client_fcrdns
: client_fcrdns_
) {
1537 if (allow_
.contains(client_fcrdns
.ascii())) {
1538 // LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1539 fcrdns_allowed_
= true;
1542 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1544 if (allow_
.contains(tld
)) {
1545 // LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1546 fcrdns_allowed_
= true;
1552 for (auto const& client_fcrdns
: client_fcrdns_
) {
1553 if (block_
.contains(client_fcrdns
.ascii())) {
1555 fmt::format("FCrDNS {} on static blocklist", client_fcrdns
.ascii());
1556 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1560 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1562 if (block_
.contains(tld
)) {
1563 error_msg
= fmt::format(
1564 "FCrDNS registered domain {} on static blocklist", tld
);
1565 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1572 client_
= fmt::format("unknown {}", sock_
.them_address_literal());
1575 if (IP4::is_address(sock_
.them_c_str())) {
1577 if (ip4_allowed(sock_
.them_c_str())) {
1578 LOG(INFO
) << "on internal allow list";
1583 auto const reversed
{IP4::reverse(sock_
.them_c_str())};
1585 // Check with allow list.
1586 std::shuffle(std::begin(Config::wls
), std::end(Config::wls
),
1589 for (auto wl
: Config::wls
) {
1590 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ wl
);
1591 if (q
.has_record()) {
1592 using namespace boost::xpressive
;
1594 auto const as
= q
.get_strings()[0];
1595 LOG(INFO
) << "on allow list " << wl
<< " as " << as
;
1599 sregex
const rex
= as_xpr("127.0.") >> (x_
= +_d
) >> '.' >> (y_
= +_d
);
1602 if (regex_match(as
, what
, rex
)) {
1603 auto const x
= what
[x_
].str();
1604 auto const y
= what
[y_
].str();
1607 std::from_chars(y
.data(), y
.data() + y
.size(), value
);
1610 LOG(INFO
) << "allowed";
1614 // Any A record skips check on block list
1619 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1620 std::shuffle(std::begin(Config::bls
), std::end(Config::bls
),
1623 for (auto bl
: Config::bls
) {
1625 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ bl
);
1626 if (q
.has_record()) {
1627 auto const as
= q
.get_strings()[0];
1628 if (as
== "127.0.1.1") {
1629 LOG(INFO
) << "Query blocked by " << bl
;
1632 error_msg
= fmt::format("blocked on advice from {}", bl
);
1633 LOG(INFO
) << sock_
.them_c_str() << " " << error_msg
;
1634 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1639 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1645 // check the identity from HELO/EHLO
1646 bool Session::verify_client_(Domain
const& client_identity
,
1647 std::string
& error_msg
)
1649 if (!client_fcrdns_
.empty()) {
1650 if (auto id
= std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
1652 id
!= end(client_fcrdns_
)) {
1653 if (id
!= begin(client_fcrdns_
)) {
1654 std::rotate(begin(client_fcrdns_
), id
, id
+ 1);
1656 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1657 sock_
.them_address_literal());
1660 LOG(INFO
) << "claimed identity " << client_identity
1661 << " does NOT match any FCrDNS: ";
1662 for (auto const& client_fcrdns
: client_fcrdns_
) {
1663 LOG(INFO
) << " " << client_fcrdns
;
1667 // Bogus clients claim to be us or some local host.
1668 if (sock_
.has_peername() && ((client_identity
== server_identity_
) ||
1669 (client_identity
== "localhost") ||
1670 (client_identity
== "localhost.localdomain"))) {
1672 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1673 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1679 LOG(INFO
) << "allow-listed IP address can claim to be "
1684 // Ease up in test mode.
1685 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1689 error_msg
= fmt::format("liar, claimed to be {}", client_identity
.ascii());
1690 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1694 std::vector
<std::string
> labels
;
1695 boost::algorithm::split(labels
, client_identity
.ascii(),
1696 boost::algorithm::is_any_of("."));
1697 if (labels
.size() < 2) {
1699 fmt::format("claimed bogus identity {}", client_identity
.ascii());
1700 out_() << "550 4.7.1 bogus identity\r\n" << std::flush
;
1702 // // Sometimes we may want to look at mail from non conforming
1703 // // sending systems.
1704 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1705 // << client_ << " claiming " << client_identity;
1709 if (lookup_domain(block_
, client_identity
)) {
1711 fmt::format("claimed blocked identity {}", client_identity
.ascii());
1712 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1716 auto const tld
{tld_db_
.get_registered_domain(client_identity
.ascii())};
1718 // Sometimes we may want to look at mail from misconfigured
1720 // LOG(WARNING) << "claimed identity has no registered domain";
1723 else if (block_
.contains(tld
)) {
1725 fmt::format("claimed identity has blocked registered domain {}", tld
);
1726 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush
;
1730 // not otherwise objectionable
1734 // check sender from RFC5321 MAIL FROM:
1735 bool Session::verify_sender_(Mailbox
const& sender
, std::string
& error_msg
)
1737 std::string
const sender_str
{sender
};
1739 auto bad_senders_db_name
= config_path_
/ "bad_senders";
1740 CDB bad_senders
{bad_senders_db_name
}; // Addresses we don't accept mail from.
1741 if (bad_senders
.contains(sender_str
)) {
1742 error_msg
= fmt::format("{} bad sender", sender_str
);
1743 out_() << "501 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1747 // We don't accept mail /from/ a domain we are expecting to accept
1748 // mail for on an external network connection.
1750 if (sock_
.them_address_literal() != sock_
.us_address_literal()) {
1751 if ((accept_domains_
.is_open() &&
1752 (accept_domains_
.contains(sender
.domain().ascii()) ||
1753 accept_domains_
.contains(sender
.domain().utf8()))) ||
1754 (sender
.domain() == server_identity_
)) {
1756 // Ease up in test mode.
1757 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1760 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1761 error_msg
= fmt::format("liar, claimed to be {}", sender
.domain());
1766 if (sender
.domain().is_address_literal()) {
1767 if (sender
.domain() != sock_
.them_address_literal()) {
1768 LOG(WARNING
) << "sender domain " << sender
.domain() << " does not match "
1769 << sock_
.them_address_literal();
1774 if (!verify_sender_domain_(sender
.domain(), error_msg
)) {
1778 if (!verify_sender_spf_(sender
)) {
1779 error_msg
= "failed SPF check";
1786 // this sender is the RFC5321 MAIL FROM: domain part
1787 bool Session::verify_sender_domain_(Domain
const& sender
,
1788 std::string
& error_msg
)
1790 if (sender
.empty()) {
1792 // is used to send bounce messages.
1796 // Break sender domain into labels:
1798 std::vector
<std::string
> labels
;
1799 boost::algorithm::split(labels
, sender
.ascii(),
1800 boost::algorithm::is_any_of("."));
1802 if (labels
.size() < 2) { // This is not a valid domain.
1803 error_msg
= fmt::format("{} invalid syntax", sender
.ascii());
1804 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1808 if (allow_
.contains(sender
.ascii())) {
1809 LOG(INFO
) << "sender " << sender
.ascii() << " allowed";
1812 auto const reg_dom
{tld_db_
.get_registered_domain(sender
.ascii())};
1814 if (allow_
.contains(reg_dom
)) {
1815 LOG(INFO
) << "sender registered domain \"" << reg_dom
<< "\" allowed";
1819 // LOG(INFO) << "looking up " << reg_dom;
1820 return verify_sender_domain_uribl_(reg_dom
, error_msg
);
1823 LOG(INFO
) << "sender \"" << sender
<< "\" not disallowed";
1827 // check sender domain on dynamic URI block lists
1828 bool Session::verify_sender_domain_uribl_(std::string_view sender
,
1829 std::string
& error_msg
)
1831 if (!sock_
.has_peername()) // short circuit
1834 std::shuffle(std::begin(Config::uribls
), std::end(Config::uribls
),
1836 for (auto uribl
: Config::uribls
) {
1837 auto const lookup
= fmt::format("{}.{}", sender
, uribl
);
1838 auto as
= DNS::get_strings(res_
, DNS::RR_type::A
, lookup
);
1840 if (as
.front() == "127.0.0.1")
1842 error_msg
= fmt::format("{} blocked on advice of {}", sender
, uribl
);
1843 out_() << "550 5.7.1 sender " << error_msg
<< "\r\n" << std::flush
;
1848 LOG(INFO
) << "sender \"" << sender
<< "\" not blocked by URIBLs";
1852 bool Session::verify_sender_spf_(Mailbox
const& sender
)
1854 if (!sock_
.has_peername()) {
1855 auto const ip_addr
= "127.0.0.1"; // use localhost for local socket
1856 spf_received_helo_
=
1857 fmt::format("Received-SPF: pass ({}: allow-listed) client-ip={}; "
1858 "envelope-from={}; helo={};",
1859 server_id_(), ip_addr
, sender
, client_identity_
);
1860 spf_sender_domain_helo_
= "localhost";
1864 auto const spf_srv
= SPF::Server
{server_id_().c_str()};
1865 auto spf_request_helo
= SPF::Request
{spf_srv
};
1866 auto spf_request_mailfrom
= SPF::Request
{spf_srv
};
1868 if (IP4::is_address(sock_
.them_c_str())) {
1869 spf_request_helo
.set_ipv4_str(sock_
.them_c_str());
1870 spf_request_mailfrom
.set_ipv4_str(sock_
.them_c_str());
1872 else if (IP6::is_address(sock_
.them_c_str())) {
1873 spf_request_helo
.set_ipv6_str(sock_
.them_c_str());
1874 spf_request_mailfrom
.set_ipv6_str(sock_
.them_c_str());
1877 LOG(FATAL
) << "bogus address " << sock_
.them_address_literal() << ", "
1878 << sock_
.them_c_str();
1881 auto const from
{static_cast<std::string
>(sender
)};
1884 spf_request_helo
.set_helo_dom(client_identity_
.ascii().c_str());
1886 auto const spf_res_helo
{SPF::Response
{spf_request_helo
}};
1887 spf_result_helo_
= spf_res_helo
.result();
1888 spf_received_helo_
= spf_res_helo
.received_spf();
1889 spf_sender_domain_helo_
= spf_request_helo
.get_sender_dom();
1891 if (spf_result_helo_
== SPF::Result::FAIL
) {
1892 LOG(WARNING
) << spf_res_helo
.header_comment();
1895 LOG(INFO
) << spf_res_helo
.header_comment();
1899 spf_request_mailfrom
.set_env_from(from
.c_str());
1901 auto const spf_res_mailfrom
{SPF::Response
{spf_request_mailfrom
}};
1902 spf_result_mailfrom_
= spf_res_mailfrom
.result();
1903 spf_received_mailfrom_
= spf_res_mailfrom
.received_spf();
1904 spf_sender_domain_mailfrom_
= spf_request_mailfrom
.get_sender_dom();
1906 if (spf_result_mailfrom_
== SPF::Result::FAIL
) {
1907 LOG(WARNING
) << spf_res_mailfrom
.header_comment();
1910 LOG(INFO
) << spf_res_mailfrom
.header_comment();
1913 if (spf_result_helo_
== SPF::Result::PASS
) {
1914 if (lookup_domain(block_
, spf_sender_domain_helo_
)) {
1915 LOG(INFO
) << "SPF sender domain (ehlo/helo " << spf_sender_domain_helo_
1920 if (spf_result_mailfrom_
== SPF::Result::PASS
) {
1921 if (lookup_domain(block_
, spf_sender_domain_mailfrom_
)) {
1922 LOG(INFO
) << "SPF sender domain (MailFrom " << spf_sender_domain_mailfrom_
1931 bool Session::verify_from_params_(parameters_t
const& parameters
)
1933 // Take a look at the optional parameters:
1934 for (auto const& [name
, value
] : parameters
) {
1935 if (iequal(name
, "BODY")) {
1936 if (iequal(value
, "8BITMIME")) {
1937 // everything is cool, this is our default...
1939 else if (iequal(value
, "7BIT")) {
1940 // nothing to see here, move along...
1942 else if (iequal(value
, "BINARYMIME")) {
1946 LOG(WARNING
) << "unrecognized BODY type \"" << value
<< "\" requested";
1949 else if (iequal(name
, "SMTPUTF8")) {
1950 if (!value
.empty()) {
1951 LOG(WARNING
) << "SMTPUTF8 parameter has a value: " << value
;
1956 // else if (iequal(name, "PRDR")) {
1957 // LOG(INFO) << "using PRDR";
1961 else if (iequal(name
, "SIZE")) {
1962 if (value
.empty()) {
1963 LOG(WARNING
) << "SIZE parameter has no value.";
1967 auto const sz
= stoull(value
);
1968 if (sz
> max_msg_size()) {
1969 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1970 LOG(WARNING
) << "SIZE parameter too large: " << sz
;
1974 catch (std::invalid_argument
const& e
) {
1975 LOG(WARNING
) << "SIZE parameter has invalid value: " << value
;
1977 catch (std::out_of_range
const& e
) {
1978 LOG(WARNING
) << "SIZE parameter has out-of-range value: " << value
;
1980 // I guess we just ignore bad size parameters.
1983 else if (iequal(name
, "REQUIRETLS")) {
1985 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush
;
1986 LOG(WARNING
) << "REQUIRETLS needed";
1991 LOG(WARNING
) << "unrecognized 'MAIL FROM' parameter " << name
<< "="
1999 bool Session::verify_rcpt_params_(parameters_t
const& parameters
)
2001 // Take a look at the optional parameters:
2002 for (auto const& [name
, value
] : parameters
) {
2003 if (iequal(name
, "RRVS")) {
2004 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
2005 LOG(INFO
) << name
<< "=" << value
;
2008 LOG(WARNING
) << "unrecognized 'RCPT TO' parameter " << name
<< "="
2016 // check recipient from RFC5321 RCPT TO:
2017 bool Session::verify_recipient_(Mailbox
const& recipient
)
2019 if ((recipient
.local_part() == "Postmaster") && (recipient
.domain() == "")) {
2020 LOG(INFO
) << "magic Postmaster address";
2024 auto const accepted_domain
{[this, &recipient
] {
2025 if (recipient
.domain().is_address_literal()) {
2026 if (recipient
.domain() != sock_
.us_address_literal()) {
2027 LOG(WARNING
) << "recipient.domain address " << recipient
.domain()
2028 << " does not match ours " << sock_
.us_address_literal();
2034 // Domains we accept mail for.
2035 if (accept_domains_
.is_open()) {
2036 if (accept_domains_
.contains(recipient
.domain().ascii()) ||
2037 accept_domains_
.contains(recipient
.domain().utf8())) {
2042 // If we have no list of domains to accept, at least take our own.
2043 if (recipient
.domain() == server_id_()) {
2051 if (!accepted_domain
) {
2052 out_() << "554 5.7.1 relay access denied\r\n" << std::flush
;
2053 LOG(WARNING
) << "relay access denied for domain " << recipient
.domain();
2057 // Check for local addresses we reject.
2059 auto bad_recipients_db_name
= config_path_
/ "bad_recipients";
2060 CDB bad_recipients_db
{bad_recipients_db_name
};
2061 if (bad_recipients_db
.contains(recipient
.local_part())) {
2062 out_() << "550 5.1.1 bad recipient " << recipient
<< "\r\n" << std::flush
;
2063 LOG(WARNING
) << "bad recipient " << recipient
;
2069 auto temp_fail_db_name
= config_path_
/ "temp_fail";
2070 CDB temp_fail
{temp_fail_db_name
}; // Addresses we make wait...
2071 if (temp_fail
.contains(recipient
.local_part())) {
2072 out_() << "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n"
2074 LOG(WARNING
) << "temp fail for recipient " << recipient
;
2079 // Check for and act on magic "wait" address.
2081 using namespace boost::xpressive
;
2084 sregex
const rex
= icase("wait-rcpt-") >> (secs_
= +_d
);
2087 if (regex_match(recipient
.local_part(), what
, rex
)) {
2088 auto const str
= what
[secs_
].str();
2089 LOG(INFO
) << "waiting at RCPT TO " << str
<< " seconds";
2091 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
2093 LOG(INFO
) << "done waiting";