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 DEFINE_string(selector
, "ghsmtp", "DKIM selector");
32 using namespace std::string_literals
;
40 <https://www.dnswl.org/?page_id=15#query>
44 The return codes are structured as 127.0.x.y, with “x” indicating the category
45 of an entry and “y” indicating how trustworthy an entry has been judged.
47 Categories (127.0.X.y):
49 2 – Financial services
50 3 – Email Service Providers
51 4 – Organisations (both for-profit [ie companies] and non-profit)
52 5 – Service/network providers
53 6 – Personal/private servers
54 7 – Travel/leisure industry
55 8 – Public sector/governments
56 9 – Media and Tech companies
57 10 – some special cases
58 11 – Education, academic
60 13 – Manufacturing/Industrial
61 14 – Retail/Wholesale/Services
62 15 – Email Marketing Providers
63 20 – Added through Self Service without specific category
65 Trustworthiness / Score (127.0.x.Y):
67 0 = none – only avoid outright blocking (eg large ESP mailservers, -0.1)
68 1 = low – reduce chance of false positives (-1.0)
69 2 = medium – make sure to avoid false positives but allow override for clear
70 cases (-10.0) 3 = high – avoid override (-100.0).
72 The scores in parantheses are typical SpamAssassin scores.
74 Special return code 127.0.0.255
76 In cases where your nameserver issues more than 100’000 queries / 24 hours, you
77 may be blocked from further queries. The return code “127.0.0.255” indicates
83 "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
[]{
130 constexpr auto greeting_wait
= std::chrono::seconds
{2};
131 constexpr int max_recipients_per_message
= 100;
132 constexpr int max_unrecognized_cmds
= 20;
134 // Read timeout value gleaned from RFC-1123 section 5.3.2 and RFC-5321
135 // section 4.5.3.2.7.
136 constexpr auto read_timeout
= std::chrono::minutes
{5};
137 constexpr auto write_timeout
= std::chrono::seconds
{30};
138 } // namespace Config
140 #include <gflags/gflags.h>
142 DEFINE_bool(test_mode
, false, "ease up on some checks");
144 DEFINE_bool(immortal
, false, "don't set process timout");
146 DEFINE_uint64(max_read
, 0, "max data to read");
147 DEFINE_uint64(max_write
, 0, "max data to write");
149 DEFINE_bool(rrvs
, false, "support RRVS à la RFC 7293");
151 Session::Session(fs::path config_path
,
152 std::function
<void(void)> read_hook
,
155 : config_path_(config_path
)
157 , sock_(fd_in
, fd_out
, read_hook
, Config::read_timeout
, Config::write_timeout
)
158 //, send_(config_path, "smtp")
159 //, srs_(config_path)
161 auto accept_db_name
= config_path_
/ "accept_domains";
162 auto allow_db_name
= config_path_
/ "allow";
163 auto block_db_name
= config_path_
/ "block";
164 auto forward_db_name
= config_path_
/ "forward";
166 accept_domains_
.open(accept_db_name
);
167 allow_
.open(allow_db_name
);
168 block_
.open(block_db_name
);
169 forward_
.open(forward_db_name
);
171 if (sock_
.has_peername() && !IP::is_private(sock_
.us_c_str())) {
172 auto fcrdns
= DNS::fcrdns(res_
, sock_
.us_c_str());
173 for (auto const& fcr
: fcrdns
) {
174 server_fcrdns_
.emplace_back(fcr
);
178 server_identity_
= [this] {
179 auto const id_from_env
{getenv("GHSMTP_SERVER_ID")};
181 return std::string
{id_from_env
};
183 auto const hostname
{osutil::get_hostname()};
184 if (hostname
.find('.') != std::string::npos
)
187 if (!server_fcrdns_
.empty()) {
188 // first result should be shortest
189 return server_fcrdns_
.front().ascii();
192 auto const us_c_str
= sock_
.us_c_str();
193 if (us_c_str
&& !IP::is_private(us_c_str
)) {
194 return IP::to_address_literal(us_c_str
);
197 LOG(FATAL
) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
201 // send_.set_sender(server_identity_);
203 max_msg_size(Config::max_msg_size_initial
);
206 void Session::max_msg_size(size_t max
)
208 max_msg_size_
= max
; // number to advertise via RFC 1870
210 if (FLAGS_max_read
) {
211 sock_
.set_max_read(FLAGS_max_read
);
214 auto const overhead
= std::max(max
/ 10, size_t(2048));
215 sock_
.set_max_read(max
+ overhead
);
219 void Session::bad_host_(char const* msg
) const
221 if (sock_
.has_peername()) {
222 // On my systems, this pattern triggers a fail2ban rule that
223 // blocks connections from this IP address on port 25 for a few
224 // days. See <https://www.fail2ban.org/> for more info.
225 syslog(LOG_MAIL
| LOG_WARNING
, "bad host [%s] %s", sock_
.them_c_str(), msg
);
227 std::exit(EXIT_SUCCESS
);
230 void Session::reset_()
232 // RSET does not force another EHLO/HELO, the one piece of per
233 // transaction data saved is client_identity_:
235 // client_identity_.clear(); <-- not cleared!
237 reverse_path_
.clear();
238 forward_path_
.clear();
239 spf_received_
.clear();
240 // fwd_path_.clear();
241 // fwd_from_.clear();
242 // rep_info_.clear();
252 max_msg_size(max_msg_size());
254 state_
= xact_step::mail
;
258 // Return codes from connection establishment are 220 or 554, according
259 // to RFC 5321. That's it.
261 void Session::greeting()
263 CHECK(state_
== xact_step::helo
);
265 if (sock_
.has_peername()) {
266 close(2); // if we're a networked program, never send to stderr
268 std::string error_msg
;
269 if (!verify_ip_address_(error_msg
)) {
270 // no glog message at this point
271 bad_host_(error_msg
.c_str());
274 /******************************************************************
275 <https://tools.ietf.org/html/rfc5321#section-4.3.1> says:
277 4.3. Sequencing of Commands and Replies
279 4.3.1. Sequencing Overview
281 The communication between the sender and receiver is an alternating
282 dialogue, controlled by the sender. As such, the sender issues a
283 command and the receiver responds with a reply. Unless other
284 arrangements are negotiated through service extensions, the sender
285 MUST wait for this response before sending further commands. One
286 important reply is the connection greeting. Normally, a receiver
287 will send a 220 "Service ready" reply when the connection is
288 completed. The sender SHOULD wait for this greeting message before
289 sending any commands.
293 “…the receiver responds with a reply.”
294 “…the sender MUST wait for this response…”
295 “One important reply is the connection greeting.”
296 “The sender SHOULD wait for this greeting…”
298 So is it MUST or SHOULD? I enforce MUST.
299 *******************************************************************/
301 // Wait a bit of time for pre-greeting traffic.
302 if (!(ip_allowed_
|| fcrdns_allowed_
)) {
303 if (sock_
.input_ready(Config::greeting_wait
)) {
304 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
305 // no glog message at this point
306 bad_host_("input before any greeting");
308 // Give a half greeting and wait again.
309 out_() << "220-" << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
310 if (sock_
.input_ready(Config::greeting_wait
)) {
311 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
312 // LOG(INFO) << "half greeting got " << client_;
313 bad_host_("input before full greeting");
316 LOG(INFO
) << "connect from " << client_
;
319 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
321 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
322 alarm(2 * 60); // initial alarm
326 void Session::flush() { out_() << std::flush
; }
328 void Session::last_in_group_(std::string_view verb
)
330 if (sock_
.input_ready(std::chrono::seconds(0))) {
331 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
335 void Session::check_for_pipeline_error_(std::string_view verb
)
337 if (!extensions_
&& sock_
.input_ready(std::chrono::seconds(0))) {
338 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
342 void Session::lo_(char const* verb
, std::string_view client_identity
)
344 last_in_group_(verb
);
347 if (client_identity_
!= client_identity
) {
348 client_identity_
= client_identity
;
350 std::string error_msg
;
351 if (!verify_client_(client_identity_
, error_msg
)) {
352 bad_host_(error_msg
.c_str());
358 out_() << "250 " << server_id_() << "\r\n";
364 if (sock_
.has_peername()) {
365 out_() << "250-" << server_id_() << " at your service, " << client_
369 out_() << "250-" << server_id_() << "\r\n";
372 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
373 out_() << "250-8BITMIME\r\n"; // RFC 6152
376 out_() << "250-RRVS\r\n"; // RFC 7293
379 // out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
382 // Check sasl sources for auth types.
383 // out_() << "250-AUTH PLAIN\r\n";
384 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
387 // If we're not already TLS, offer TLS
388 out_() << "250-STARTTLS\r\n"; // RFC 3207
390 out_() << "250-ENHANCEDSTATUSCODES\r\n" // RFC 2034
391 "250-PIPELINING\r\n" // RFC 2920
392 "250-BINARYMIME\r\n" // RFC 3030
393 "250-CHUNKING\r\n" // RFC 3030
394 "250 SMTPUTF8\r\n"; // RFC 6531
397 out_() << std::flush
;
399 if (sock_
.has_peername()) {
400 if (std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
401 client_identity_
) != end(client_fcrdns_
)) {
402 LOG(INFO
) << verb
<< " " << client_identity
<< " from "
403 << sock_
.them_address_literal();
406 LOG(INFO
) << verb
<< " " << client_identity
<< " from " << client_
;
410 LOG(INFO
) << verb
<< " " << client_identity
;
414 void Session::mail_from(Mailbox
&& reverse_path
, parameters_t
const& parameters
)
416 check_for_pipeline_error_("MAIL FROM");
419 case xact_step::helo
:
420 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
421 LOG(WARNING
) << "'MAIL FROM' before HELO/EHLO"
422 << (sock_
.has_peername() ? " from " : "") << client_
;
424 case xact_step::mail
: break;
425 case xact_step::rcpt
:
426 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
427 LOG(WARNING
) << "nested MAIL command"
428 << (sock_
.has_peername() ? " from " : "") << client_
;
430 case xact_step::data
:
431 case xact_step::bdat
:
432 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush
;
433 LOG(WARNING
) << "nested MAIL command"
434 << (sock_
.has_peername() ? " from " : "") << client_
;
436 case xact_step::rset
:
437 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
438 LOG(WARNING
) << "error state must be cleared with a RSET"
439 << (sock_
.has_peername() ? " from " : "") << client_
;
443 if (!verify_from_params_(parameters
)) {
447 if (!smtputf8_
&& !is_ascii(reverse_path
.local_part())) {
448 LOG(WARNING
) << "non ascii reverse_path \"" << reverse_path
449 << "\" without SMTPUTF8 paramater";
452 std::string error_msg
;
453 if (!verify_sender_(reverse_path
, error_msg
)) {
454 LOG(WARNING
) << "verify sender failed: " << error_msg
;
455 bad_host_(error_msg
.c_str());
458 reverse_path_
= std::move(reverse_path
);
459 // fwd_path_.clear();
460 // fwd_from_.clear();
461 forward_path_
.clear();
462 out_() << "250 2.1.0 MAIL FROM OK\r\n";
463 // No flush RFC-2920 section 3.1, this could be part of a command group.
465 fmt::memory_buffer params
;
466 for (auto const& [name
, value
] : parameters
) {
467 fmt::format_to(params
, " {}", name
);
468 if (!value
.empty()) {
469 fmt::format_to(params
, "={}", value
);
472 LOG(INFO
) << "MAIL FROM:<" << reverse_path_
<< ">" << fmt::to_string(params
);
474 state_
= xact_step::rcpt
;
477 // bool Session::forward_to_(std::string const& forward, Mailbox const& rcpt_to)
479 // // If we're already forwarding or replying, reject
480 // if (!fwd_path_.empty() || !rep_info_.empty()) {
481 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
484 // LOG(WARNING) << "failed to forward to <" << forward
485 // << "> already forwarding or replying for: " << rcpt_to;
489 // fwd_path_ = Mailbox(forward);
490 // fwd_from_ = rcpt_to;
492 // // New bounce address
493 // Reply::from_to bounce;
494 // bounce.mail_from = reverse_path_.as_string();
496 // auto const new_bounce = srs_.enc_bounce(bounce, server_id_().c_str());
498 // auto const mail_from = Mailbox(new_bounce);
500 // std::string error_msg;
501 // if (!send_.mail_from_rcpt_to(res_, mail_from, fwd_path_, error_msg)) {
502 // out_() << error_msg << std::flush;
503 // LOG(WARNING) << "failed to forward <" << fwd_path_ << "> " << error_msg;
507 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> forwarding to == <" << fwd_path_
512 // bool Session::reply_to_(Reply::from_to const& reply_info, Mailbox const&
515 // // If we're already forwarding or replying, reject
516 // if (!fwd_path_.empty() || !rep_info_.empty()) {
517 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
520 // LOG(WARNING) << "failed to reply to <" << reply_info.mail_from
521 // << "> already forwarding or replying for: " << rcpt_to;
525 // rep_info_ = reply_info;
527 // Mailbox const from(rep_info_.rcpt_to_local_part, server_identity_);
528 // Mailbox const to(rep_info_.mail_from);
530 // std::string error_msg;
531 // if (!send_.mail_from_rcpt_to(res_, from, to, error_msg)) {
532 // out_() << error_msg << std::flush;
533 // LOG(WARNING) << "failed to reply from <" << from << "> to <" << to << ">
539 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> is a reply to "
540 // << rep_info_.mail_from << " from " <<
541 // rep_info_.rcpt_to_local_part;
545 void Session::rcpt_to(Mailbox
&& forward_path
, parameters_t
const& parameters
)
547 check_for_pipeline_error_("RCPT TO");
550 case xact_step::helo
:
551 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
552 LOG(WARNING
) << "'RCPT TO' before HELO/EHLO"
553 << (sock_
.has_peername() ? " from " : "") << client_
;
555 case xact_step::mail
:
556 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
557 LOG(WARNING
) << "'RCPT TO' before 'MAIL FROM'"
558 << (sock_
.has_peername() ? " from " : "") << client_
;
560 case xact_step::rcpt
:
561 case xact_step::data
: break;
562 case xact_step::bdat
:
563 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
564 LOG(WARNING
) << "'RCPT TO' during BDAT transfer"
565 << (sock_
.has_peername() ? " from " : "") << client_
;
567 case xact_step::rset
:
568 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
569 LOG(WARNING
) << "error state must be cleared with a RSET"
570 << (sock_
.has_peername() ? " from " : "") << client_
;
574 if (!verify_rcpt_params_(parameters
))
577 if (!verify_recipient_(forward_path
))
580 if (!smtputf8_
&& !is_ascii(forward_path
.local_part())) {
581 LOG(WARNING
) << "non ascii forward_path \"" << forward_path
582 << "\" without SMTPUTF8 paramater";
585 if (forward_path_
.size() >= Config::max_recipients_per_message
) {
586 out_() << "452 4.5.3 too many recipients\r\n" << std::flush
;
587 LOG(WARNING
) << "too many recipients <" << forward_path
<< ">";
590 // no check for dups, postfix doesn't
591 forward_path_
.emplace_back(std::move(forward_path
));
593 Mailbox
const& rcpt_to_mbx
= forward_path_
.back();
595 LOG(INFO
) << "RCPT TO:<" << rcpt_to_mbx
<< ">";
597 // auto const rcpt_to_str = rcpt_to_mbx.as_string();
599 // if (auto reply = srs_.dec_reply(rcpt_to_mbx.local_part()); reply) {
600 // if (!reply_to_(*reply, rcpt_to_mbx))
603 // else if (auto const forward = forward_.find(rcpt_to_str.c_str()); forward)
605 // if (!forward_to_(*forward, rcpt_to_mbx))
609 // LOG(INFO) << "RCPT TO:<" << rcpt_to_str << ">";
612 // No flush RFC-2920 section 3.1, this could be part of a command group.
613 out_() << "250 2.1.5 RCPT TO OK\r\n";
615 state_
= xact_step::data
;
618 // The headers Received and Received-SPF are returned as a string.
620 std::string
Session::added_headers_(MessageStore
const& msg
)
622 auto const protocol
{[this]() {
624 return sock_
.tls() ? "UTF8SMTPS" : "UTF8SMTP";
625 else if (extensions_
)
626 return sock_
.tls() ? "ESMTPS" : "ESMTP";
628 return sock_
.tls() ? "SMTPS" : "SMTP";
631 fmt::memory_buffer headers
;
634 if (!spf_received_
.empty()) {
635 fmt::format_to(headers
, "{}\r\n", spf_received_
);
639 // <https://tools.ietf.org/html/rfc5321#section-4.4>
640 fmt::format_to(headers
, "Received: from {}", client_identity_
.utf8());
641 if (sock_
.has_peername()) {
642 fmt::format_to(headers
, " ({})", client_
);
644 fmt::format_to(headers
, "\r\n\tby {} with {} id {}", server_identity_
.utf8(),
646 if (forward_path_
.size()) {
647 fmt::format_to(headers
, "\r\n\tfor <{}>", forward_path_
[0]);
648 for (auto i
= 1u; i
< forward_path_
.size(); ++i
)
649 fmt::format_to(headers
, ",\r\n\t <{}>", forward_path_
[i
]);
651 std::string
const tls_info
{sock_
.tls_info()};
652 if (tls_info
.length()) {
653 fmt::format_to(headers
, "\r\n\t({})", tls_info
);
655 fmt::format_to(headers
, ";\r\n\t{}\r\n", msg
.when());
657 return fmt::to_string(headers
);
661 bool lookup_domain(CDB
& cdb
, Domain
const& domain
)
663 if (!domain
.empty()) {
664 if (cdb
.contains(domain
.ascii())) {
667 if (domain
.is_unicode() && cdb
.contains(domain
.utf8())) {
675 std::tuple
<Session::SpamStatus
, std::string
> Session::spam_status_()
677 if (spf_result_
== SPF::Result::FAIL
&& !ip_allowed_
)
678 return {SpamStatus::spam
, "SPF failed"};
680 // These should have already been rejected by verify_client_().
681 if ((reverse_path_
.domain() == "localhost.local") ||
682 (reverse_path_
.domain() == "localhost"))
683 return {SpamStatus::spam
, "bogus reverse_path"};
685 std::vector
<std::string
> why_ham
;
687 // Anything enciphered tastes a lot like ham.
689 why_ham
.emplace_back("they used TLS");
691 if (spf_result_
== SPF::Result::PASS
) {
692 if (lookup_domain(allow_
, spf_sender_domain_
)) {
693 why_ham
.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
694 spf_sender_domain_
.utf8()));
697 auto tld_dom
{tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
698 if (tld_dom
&& allow_
.contains(tld_dom
)) {
699 why_ham
.emplace_back(fmt::format(
700 "SPF sender registered domain ({}) is allowed", tld_dom
));
706 why_ham
.emplace_back(
707 fmt::format("FCrDNS (or it's registered domain) is allowed"));
709 if (!why_ham
.empty())
710 return {SpamStatus::ham
,
711 fmt::format("{}", fmt::join(std::begin(why_ham
), std::end(why_ham
),
714 return {SpamStatus::spam
, "it's not ham"};
717 static std::string
folder(Session::SpamStatus status
,
718 std::vector
<Mailbox
> const& forward_path
,
719 Mailbox
const& reverse_path
)
721 if (status
== Session::SpamStatus::spam
)
727 bool Session::msg_new()
729 CHECK((state_
== xact_step::data
) || (state_
== xact_step::bdat
));
731 auto const& [status
, reason
]{spam_status_()};
733 LOG(INFO
) << ((status
== SpamStatus::ham
) ? "ham since " : "spam since ")
736 // All sources of ham get a fresh 5 minute timeout per message.
737 if (status
== SpamStatus::ham
) {
738 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr))
742 msg_
= std::make_unique
<MessageStore
>();
744 if (!FLAGS_max_write
)
745 FLAGS_max_write
= max_msg_size();
748 msg_
->open(server_id_(), FLAGS_max_write
,
749 folder(status
, forward_path_
, reverse_path_
));
750 auto const hdrs
{added_headers_(*(msg_
.get()))};
753 // fmt::memory_buffer spam_status;
754 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
755 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
756 // msg_->write(spam_status.data(), spam_status.size());
758 LOG(INFO
) << "Spam-Status: "
759 << ((status
== SpamStatus::spam
) ? "Yes" : "No") << ", "
764 catch (std::system_error
const& e
) {
767 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
768 LOG(ERROR
) << "no space";
774 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
775 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
776 LOG(ERROR
) << e
.what();
782 catch (std::exception
const& e
) {
783 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
784 LOG(ERROR
) << e
.what();
790 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
791 LOG(ERROR
) << "msg_new failed with no exception caught";
797 bool Session::msg_write(char const* s
, std::streamsize count
)
799 if ((state_
!= xact_step::data
) && (state_
!= xact_step::bdat
))
806 if (msg_
->write(s
, count
))
809 catch (std::system_error
const& e
) {
812 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
813 LOG(ERROR
) << "no space";
819 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
820 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
821 LOG(ERROR
) << e
.what();
827 catch (std::exception
const& e
) {
828 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
829 LOG(ERROR
) << e
.what();
835 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
836 LOG(ERROR
) << "msg_write failed with no exception caught";
842 bool Session::data_start()
844 last_in_group_("DATA");
847 case xact_step::helo
:
848 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
849 LOG(WARNING
) << "'DATA' before HELO/EHLO"
850 << (sock_
.has_peername() ? " from " : "") << client_
;
852 case xact_step::mail
:
853 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
854 LOG(WARNING
) << "'DATA' before 'MAIL FROM'"
855 << (sock_
.has_peername() ? " from " : "") << client_
;
857 case xact_step::rcpt
:
859 /******************************************************************
860 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
862 The DATA command can fail at only two points in the protocol exchange:
864 If there was no MAIL, or no RCPT, command, or all such commands were
865 rejected, the server MAY return a "command out of sequence" (503) or
866 "no valid recipients" (554) reply in response to the DATA command.
868 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
870 The additional restriction is that when there have been no successful
871 RCPT commands in the mail transaction, the DATA command MUST fail
872 with a 503 reply code.
874 Therefore I will send the reply code that is valid for both, and
875 do the same for the BDAT case.
876 *******************************************************************/
878 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
879 LOG(WARNING
) << "no valid recipients"
880 << (sock_
.has_peername() ? " from " : "") << client_
;
882 case xact_step::data
: break;
883 case xact_step::bdat
:
884 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
885 LOG(WARNING
) << "'DATA' during BDAT transfer"
886 << (sock_
.has_peername() ? " from " : "") << client_
;
888 case xact_step::rset
:
889 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
890 LOG(WARNING
) << "error state must be cleared with a RSET"
891 << (sock_
.has_peername() ? " from " : "") << client_
;
896 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
898 LOG(WARNING
) << "DATA does not support BINARYMIME";
899 state_
= xact_step::rset
; // RFC 3030 section 3 page 5
904 LOG(ERROR
) << "msg_new() failed";
908 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush
;
913 // bool Session::do_forward_(message::parsed& msg)
915 // auto msg_fwd = msg;
917 // // Generate a reply address
918 // Reply::from_to reply;
919 // reply.mail_from = msg_fwd.dmarc_from;
920 // reply.rcpt_to_local_part = fwd_from_.local_part();
922 // auto const reply_addr =
923 // fmt::format("{}@{}", srs_.enc_reply(reply), server_id_());
925 // auto const munging = false;
927 // auto const sender = server_identity_.ascii().c_str();
928 // auto const selector = FLAGS_selector.c_str();
929 // auto const key_file =
930 // (config_path_ / FLAGS_selector).replace_extension("private");
931 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
934 // auto const from_hdr =
935 // fmt::format("From: \"{} via\" <@>", msg_fwd.dmarc_from, reply_addr);
936 // message::rewrite_from_to(msg_fwd, from_hdr, "", sender, selector,
940 // auto const reply_to_hdr = fmt::format("Reply-To: {}", reply_addr);
941 // message::rewrite_from_to(msg_fwd, "", reply_to_hdr, sender, selector,
946 // if (!send_.send(msg_fwd.as_string())) {
947 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
951 // LOG(ERROR) << "failed to send for " << fwd_path_;
955 // LOG(INFO) << "successfully sent for " << fwd_path_;
959 // bool Session::do_reply_(message::parsed& msg)
961 // Mailbox to_mbx(rep_info_.mail_from);
962 // Mailbox from_mbx(rep_info_.rcpt_to_local_part, server_identity_);
964 // auto reply = std::make_unique<MessageStore>();
965 // reply->open(server_id_(), FLAGS_max_write, ".Drafts");
967 // auto const date{Now{}};
968 // auto const pill{Pill{}};
969 // auto const mid_str =
970 // fmt::format("<{}.{}@{}>", date.sec(), pill, server_identity_);
972 // fmt::memory_buffer bfr;
974 // fmt::format_to(bfr, "From: <{}>\r\n", from_mbx);
975 // fmt::format_to(bfr, "To: <{}>\r\n", to_mbx);
977 // fmt::format_to(bfr, "Date: {}\r\n", date.c_str());
979 // fmt::format_to(bfr, "Message-ID: {}\r\n", mid_str.c_str());
981 // if (!msg.get_header(message::Subject).empty()) {
982 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
983 // msg.get_header(message::Subject));
986 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
987 // "Reply to your message");
990 // if (!msg.get_header(message::In_Reply_To).empty()) {
991 // fmt::format_to(bfr, "{}: {}\r\n", message::In_Reply_To,
992 // msg.get_header(message::In_Reply_To));
995 // if (!msg.get_header(message::MIME_Version).empty() &&
996 // msg.get_header(message::Content_Type).empty()) {
997 // fmt::format_to(bfr, "{}: {}\r\n", message::MIME_Version,
998 // msg.get_header(message::MIME_Version));
999 // fmt::format_to(bfr, "{}: {}\r\n", message::Content_Type,
1000 // msg.get_header(message::Content_Type));
1003 // reply->write(fmt::to_string(bfr));
1005 // if (!msg.body.empty()) {
1006 // reply->write("\r\n");
1007 // reply->write(msg.body);
1010 // auto const msg_data = reply->freeze();
1011 // message::parsed msg_reply;
1012 // CHECK(msg_reply.parse(msg_data));
1014 // auto const sender = server_identity_.ascii().c_str();
1015 // auto const selector = FLAGS_selector.c_str();
1016 // auto const key_file =
1017 // (config_path_ / FLAGS_selector).replace_extension("private");
1018 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1020 // message::dkim_sign(msg_reply, sender, selector, key_file);
1022 // if (!send_.send(msg_reply.as_string())) {
1023 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1027 // LOG(ERROR) << "send failed for reply to " << to_mbx << " from " <<
1028 // from_mbx; return false;
1031 // LOG(INFO) << "successful reply to " << to_mbx << " from " << from_mbx;
1035 bool Session::do_deliver_()
1039 // auto const sender = server_identity_.ascii().c_str();
1040 // auto const selector = FLAGS_selector.c_str();
1041 // auto const key_file =
1042 // (config_path_ / FLAGS_selector).replace_extension("private");
1043 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1046 // auto const msg_data = msg_->freeze();
1048 // message::parsed msg;
1050 // // Only deal in RFC-5322 Mail Objects.
1051 // bool const message_parsed = msg.parse(msg_data);
1052 // if (message_parsed) {
1054 // // remove any Return-Path
1055 // message::remove_delivery_headers(msg);
1057 // auto const authentic =
1058 // message_parsed &&
1059 // message::authentication(msg, sender, selector, key_file);
1061 // // write a new Return-Path
1062 // msg_->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_));
1064 // for (auto const h : msg.headers) {
1065 // msg_->write(h.as_string());
1066 // msg_->write("\r\n");
1068 // if (!msg.body.empty()) {
1069 // msg_->write("\r\n");
1070 // msg_->write(msg.body);
1075 // if (authentic && !fwd_path_.empty()) {
1076 // if (!do_forward_(msg))
1079 // if (authentic && !rep_info_.empty()) {
1080 // if (!do_reply_(msg))
1087 catch (std::system_error
const& e
) {
1090 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
1091 LOG(ERROR
) << "no space";
1097 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
1099 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
1100 LOG(ERROR
) << e
.what();
1110 void Session::data_done()
1112 CHECK((state_
== xact_step::data
));
1114 if (msg_
&& msg_
->size_error()) {
1122 // out_() << "353\r\n";
1123 // for (auto fp : forward_path_) {
1124 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1129 using namespace boost::xpressive
;
1132 sregex
const rex
= icase("wait-data-") >> (secs_
= +_d
);
1135 for (auto fp
: forward_path_
) {
1136 if (regex_match(fp
.local_part(), what
, rex
)) {
1137 auto const str
= what
[secs_
].str();
1138 LOG(INFO
) << "waiting at DATA " << str
<< " seconds";
1140 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1142 LOG(INFO
) << "done waiting";
1147 out_() << "250 2.0.0 DATA OK\r\n" << std::flush
;
1148 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1154 void Session::data_size_error()
1156 out_().clear(); // clear possible eof from input side
1157 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1161 LOG(WARNING
) << "DATA size error";
1165 void Session::data_error()
1167 out_().clear(); // clear possible eof from input side
1168 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush
;
1172 LOG(WARNING
) << "DATA error";
1176 bool Session::bdat_start(size_t n
)
1178 // In practice, this one gets pipelined.
1179 // last_in_group_("BDAT");
1182 case xact_step::helo
:
1183 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
1184 LOG(WARNING
) << "'BDAT' before HELO/EHLO"
1185 << (sock_
.has_peername() ? " from " : "") << client_
;
1187 case xact_step::mail
:
1188 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
1189 LOG(WARNING
) << "'BDAT' before 'MAIL FROM'"
1190 << (sock_
.has_peername() ? " from " : "") << client_
;
1192 case xact_step::rcpt
:
1193 // See comment in data_start()
1194 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1195 LOG(WARNING
) << "no valid recipients"
1196 << (sock_
.has_peername() ? " from " : "") << client_
;
1198 case xact_step::data
: // first bdat
1200 case xact_step::bdat
: return true;
1201 case xact_step::rset
:
1202 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1203 LOG(WARNING
) << "error state must be cleared with a RSET"
1204 << (sock_
.has_peername() ? " from " : "") << client_
;
1208 state_
= xact_step::bdat
;
1213 void Session::bdat_done(size_t n
, bool last
)
1215 if (state_
!= xact_step::bdat
) {
1224 if (msg_
->size_error()) {
1230 out_() << "250 2.0.0 BDAT " << n
<< " OK\r\n" << std::flush
;
1231 LOG(INFO
) << "BDAT " << n
;
1237 out_() << "250 2.0.0 BDAT " << n
<< " LAST OK\r\n" << std::flush
;
1239 LOG(INFO
) << "BDAT " << n
<< " LAST";
1240 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1245 void Session::bdat_size_error()
1247 out_().clear(); // clear possible eof from input side
1248 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1252 LOG(WARNING
) << "BDAT size error";
1256 void Session::bdat_seq_error()
1258 out_().clear(); // clear possible eof from input side
1259 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush
;
1263 LOG(WARNING
) << "BDAT sequence error";
1267 void Session::bdat_io_error()
1269 out_().clear(); // clear possible eof from input side
1270 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush
;
1274 LOG(WARNING
) << "BDAT I/O error";
1278 void Session::rset()
1280 out_() << "250 2.1.5 RSET OK\r\n";
1281 // No flush RFC-2920 section 3.1, this could be part of a command group.
1282 LOG(INFO
) << "RSET";
1286 void Session::noop(std::string_view str
)
1288 last_in_group_("NOOP");
1289 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush
;
1290 LOG(INFO
) << "NOOP" << (str
.length() ? " " : "") << str
;
1293 void Session::vrfy(std::string_view str
)
1295 last_in_group_("VRFY");
1296 out_() << "252 2.1.5 try it\r\n" << std::flush
;
1297 LOG(INFO
) << "VRFY" << (str
.length() ? " " : "") << str
;
1300 void Session::help(std::string_view str
)
1302 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n" << std::flush
;
1303 LOG(INFO
) << "HELP" << (str
.length() ? " " : "") << str
;
1306 void Session::quit()
1309 // last_in_group_("QUIT");
1310 out_() << "221 2.0.0 closing connection\r\n" << std::flush
;
1311 LOG(INFO
) << "QUIT";
1315 void Session::auth()
1317 out_() << "454 4.7.0 authentication failure\r\n" << std::flush
;
1318 LOG(INFO
) << "AUTH";
1322 void Session::error(std::string_view log_msg
)
1324 out_() << "421 4.3.5 system error: " << log_msg
<< "\r\n" << std::flush
;
1325 LOG(WARNING
) << log_msg
;
1328 void Session::cmd_unrecognized(std::string_view cmd
)
1330 auto const escaped
{esc(cmd
)};
1331 LOG(WARNING
) << "command unrecognized: \"" << escaped
<< "\"";
1333 if (++n_unrecognized_cmds_
>= Config::max_unrecognized_cmds
) {
1334 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1335 << "\" exceeds limit\r\n"
1337 LOG(WARNING
) << n_unrecognized_cmds_
1338 << " unrecognized commands is too many";
1342 out_() << "500 5.5.1 command unrecognized: \"" << escaped
<< "\"\r\n"
1346 void Session::bare_lf()
1348 // Error code used by Office 365.
1349 out_() << "554 5.6.11 bare LF\r\n" << std::flush
;
1350 LOG(WARNING
) << "bare LF";
1354 void Session::max_out()
1356 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1357 LOG(WARNING
) << "message size maxed out";
1361 void Session::time_out()
1363 out_() << "421 4.4.2 time-out\r\n" << std::flush
;
1364 LOG(WARNING
) << "time-out" << (sock_
.has_peername() ? " from " : "")
1369 void Session::starttls()
1371 last_in_group_("STARTTLS");
1373 out_() << "554 5.5.1 TLS already active\r\n" << std::flush
;
1374 LOG(WARNING
) << "STARTTLS issued with TLS already active";
1377 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush
;
1378 if (sock_
.starttls_server(config_path_
)) {
1380 max_msg_size(Config::max_msg_size_bro
);
1381 LOG(INFO
) << "STARTTLS " << sock_
.tls_info();
1386 void Session::exit_()
1388 // sock_.log_totals();
1390 timespec time_used
{};
1391 clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &time_used
);
1393 LOG(INFO
) << "CPU time " << time_used
.tv_sec
<< "." << std::setw(9)
1394 << std::setfill('0') << time_used
.tv_nsec
<< " seconds";
1396 std::exit(EXIT_SUCCESS
);
1400 bool ip4_allowed(char const* addr
)
1405 char const* comment
;
1419 nw
const networks
[]{
1420 // the one very special case
1421 {"108.83.36.112", "255.255.255.248", "108.83.36.112/29"},
1423 // accept from major providers:
1424 {"3.0.0.0", "255.0.0.0", "3.0.0.0/9 and 3.128.0.0/9 Amazon"},
1425 {"5.45.198.0", "255.255.254.0", "5.45.198.0/23 YANDEX-5-45-198"},
1426 {"12.153.224.0", "255.255.255.0", "12.153.224.0/24 E-TRADE10-224"},
1427 {"17.0.0.0", "255.0.0.0", "17.0.0.0/8 APPLE-WWNET"},
1428 {"40.74.0.0", "255.254.0.0", "40.74.0.0/15 MSFT NET-40-74-0-0-1"},
1429 {"40.76.0.0", "255.252.0.0", "40.76.0.0/14 MSFT NET-40-74-0-0-1"},
1430 {"40.80.0.0", "255.240.0.0", "40.80.0.0/12 MSFT NET-40-74-0-0-1"},
1431 {"40.92.0.0", "255.252.0.0", "40.96.0.0/14 MSFT NET-40-74-0-0-1"},
1432 {"40.96.0.0", "255.240.0.0", "40.96.0.0/12 MSFT NET-40-74-0-0-1"},
1433 {"40.112.0.0", "255.248.0.0", "40.112.0.0/13 MSFT NET-40-74-0-0-1"},
1434 {"40.120.0.0", "255.252.0.0", "40.120.0.0/14 MSFT NET-40-74-0-0-1"},
1435 {"40.124.0.0", "255.255.0.0", "40.124.0.0/16 MSFT NET-40-74-0-0-1"},
1436 {"40.125.0.0", "255.255.128.0", "40.125.0.0/17 MSFT NET-40-74-0-0-1"},
1437 {"56.0.0.0", "255.0.0.0", "56.0.0.0/8 USPS1"},
1438 {"65.52.0.0", "255.252.0.0", "65.52.0.0/14 MICROSOFT-1BLK"},
1439 {"66.163.160.0", "255.255.224.0", "66.163.160.0/19 A-YAHOO-US2"},
1440 {"66.211.176.0", "255.255.240.0", "66.211.176.0/20 EBAY-2"},
1441 {"66.211.172.0", "255.255.252.0", "66.211.172.0/22 EBAY-2"},
1442 {"66.220.144.0", "255.255.240.0", "66.220.144.0/20 TFBNET3"},
1443 {"68.232.192.0", "255.255.240.0", "68.232.192.0/20 EXACT-IP-NET-2"},
1444 {"69.171.224.0", "255.255.224.0", "69.171.224.0/19 TFBNET3"},
1445 {"70.47.67.0", "255.255.255.0", "70.47.67.0/24 NET-462F4300-24"},
1446 {"74.6.0.0", "255.255.0.0", "INKTOMI-BLK-6 Oath"},
1447 {"74.125.0.0", "255.255.0.0", "74.125.0.0/16 GOOGLE"},
1448 {"75.101.100.43", "255.255.255.255", "new.toad.com"},
1449 {"76.178.68.57", "255.255.255.255", "cpe-76-178-68-57.natsow.res.rr.com"},
1450 {"98.136.0.0", "255.252.0.0", "98.136.0.0/14 A-YAHOO-US9"},
1451 {"104.40.0.0", "255.248.0.0", "104.40.0.0/13 MSFT"},
1452 {"108.174.0.0", "255.255.240.0", "108.174.0.0/20 LINKEDIN"},
1453 {"159.45.0.0", "255.255.0.0", "159.45.0.0/16 AGE-COM"},
1454 {"159.53.0.0", "255.255.0.0", "159.53.0.0/16 JMC"},
1455 {"159.135.224.0", "255.255.240.0", "159.135.224.0/20 MNO87-159-135-224-0-0"},
1456 {"162.247.72.0", "255.255.252.0", "162.247.72.0/22 CALYX-INSTITUTE-V4-1"},
1457 {"165.107.0.0", "255.255.0.0", "NET-LDC-CA-GOV"},
1458 {"192.175.128.0", "255.255.128.0", "192.175.128.0/17 NETBLK-VANGUARD"},
1459 {"198.2.128.0", "255.255.192.0", "198.2.128.0/18 RSG-DELIVERY"},
1460 {"198.252.206.0", "255.255.255.0", "198.252.206.0/24 SE-NET01"},
1461 {"199.122.120.0", "255.255.248.0", "199.122.120.0/21 EXACT-IP-NET-3"},
1462 {"204.13.164.0", "255.255.255.0", "204.13.164.0/24 RISEUP-NETWORKS-SWIFT-BLOCK2"},
1463 {"204.29.186.0", "255.255.254.0", "204.29.186.0/23 ATDN-NSCAPE"},
1464 {"205.139.104.0", "255.255.252.0", "205.139.104.0/22 SAVV-S259964-8"},
1465 {"205.201.128.0", "255.255.240.0", "205.201.128.0/20 RSG-DELIVERY"},
1466 {"208.118.235.0", "255.255.255.0", "208.118.235.0/24 TWDX-208-118-235-0-1"},
1467 {"208.192.0.0", "255.192.0.0", "208.192.0.0/10 UUNET1996B"},
1468 {"209.51.188.0", "255.255.255.0", "I:NET-209.51.188.0/24 FSF"},
1469 {"209.85.128.0", "255.255.128.0", "209.85.128.0/17 GOOGLE"},
1470 {"209.132.176.0", "255.255.240.0", "209.132.176.0/20 RED-HAT-BLK"},
1471 {"209.237.224.0", "255.255.224.0", "UNITEDLAYER-1"},
1472 // {"209.237.225.253", "255.255.255.255", "Old new.toad.com"},
1477 CHECK_EQ(inet_pton(AF_INET
, addr
, &addr32
), 1)
1478 << "can't interpret as IPv4 address";
1480 for (auto const& network
: networks
) {
1482 CHECK_EQ(inet_pton(AF_INET
, network
.net
, &net32
), 1)
1483 << "can't grok " << network
.net
;
1485 CHECK_EQ(inet_pton(AF_INET
, network
.mask
, &mask32
), 1)
1486 << "can't grok " << network
.mask
;
1488 // sanity check: all unmasked bits must be zero
1489 CHECK_EQ(net32
& (~mask32
), 0)
1490 << "bogus config net=" << network
.net
<< ", mask=" << network
.mask
;
1492 if (net32
== (addr32
& mask32
)) {
1493 LOG(INFO
) << addr
<< " allowed " << network
.comment
;
1502 /////////////////////////////////////////////////////////////////////////////
1504 // All of the verify_* functions send their own error messages back to
1505 // the client on failure, and return false.
1507 bool Session::verify_ip_address_(std::string
& error_msg
)
1509 auto ip_block_db_name
= config_path_
/ "ip-block";
1510 CDB ip_block
{ip_block_db_name
};
1511 if (ip_block
.contains(sock_
.them_c_str())) {
1513 fmt::format("IP address {} on static blocklist", sock_
.them_c_str());
1514 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1518 client_fcrdns_
.clear();
1520 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1521 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1522 LOG(INFO
) << "loopback address allowed";
1524 client_fcrdns_
.emplace_back("localhost");
1525 client_
= fmt::format("localhost {}", sock_
.them_address_literal());
1529 if (IP::is_private(sock_
.them_address_literal())) {
1530 LOG(INFO
) << "local address allowed";
1532 client_
= sock_
.them_address_literal();
1536 auto const fcrdns
= DNS::fcrdns(res_
, sock_
.them_c_str());
1537 for (auto const& fcr
: fcrdns
) {
1538 client_fcrdns_
.emplace_back(fcr
);
1541 if (!client_fcrdns_
.empty()) {
1542 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1543 sock_
.them_address_literal());
1545 for (auto const& client_fcrdns
: client_fcrdns_
) {
1546 if (allow_
.contains(client_fcrdns
.ascii())) {
1547 // LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1548 fcrdns_allowed_
= true;
1551 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1553 if (allow_
.contains(tld
)) {
1554 // LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1555 fcrdns_allowed_
= true;
1561 for (auto const& client_fcrdns
: client_fcrdns_
) {
1562 if (block_
.contains(client_fcrdns
.ascii())) {
1564 fmt::format("FCrDNS {} on static blocklist", client_fcrdns
.ascii());
1565 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1569 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1571 if (block_
.contains(tld
)) {
1572 error_msg
= fmt::format(
1573 "FCrDNS registered domain {} on static blocklist", tld
);
1574 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1581 client_
= fmt::format("unknown {}", sock_
.them_address_literal());
1584 if (IP4::is_address(sock_
.them_c_str())) {
1586 if (ip4_allowed(sock_
.them_c_str())) {
1587 LOG(INFO
) << "on internal allow list";
1592 auto const reversed
{IP4::reverse(sock_
.them_c_str())};
1594 // Check with allow list.
1595 std::shuffle(std::begin(Config::wls
), std::end(Config::wls
),
1598 for (auto wl
: Config::wls
) {
1599 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ wl
);
1600 if (q
.has_record()) {
1601 using namespace boost::xpressive
;
1603 auto const as
= q
.get_strings()[0];
1604 LOG(INFO
) << "on allow list " << wl
<< " as " << as
;
1608 sregex
const rex
= as_xpr("127.0.") >> (x_
= +_d
) >> '.' >> (y_
= +_d
);
1611 if (regex_match(as
, what
, rex
)) {
1612 auto const x
= what
[x_
].str();
1613 auto const y
= what
[y_
].str();
1616 std::from_chars(y
.data(), y
.data() + y
.size(), value
);
1619 LOG(INFO
) << "allowed";
1623 // Any A record skips check on block list
1628 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1629 std::shuffle(std::begin(Config::bls
), std::end(Config::bls
),
1632 for (auto bl
: Config::bls
) {
1634 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ bl
);
1635 if (q
.has_record()) {
1636 auto const as
= q
.get_strings()[0];
1637 if (as
== "127.0.1.1") {
1638 LOG(INFO
) << "Query blocked by " << bl
;
1641 error_msg
= fmt::format("blocked on advice from {}", bl
);
1642 LOG(INFO
) << sock_
.them_c_str() << " " << error_msg
;
1643 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1648 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1654 // check the identity from HELO/EHLO
1655 bool Session::verify_client_(Domain
const& client_identity
,
1656 std::string
& error_msg
)
1658 if (!client_fcrdns_
.empty()) {
1659 if (auto id
= std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
1661 id
!= end(client_fcrdns_
)) {
1662 // If the HELO ident is one of the FCrDNS names...
1663 if (id
!= begin(client_fcrdns_
)) {
1664 // ...then rotate that one to the front of the list
1665 std::rotate(begin(client_fcrdns_
), id
, id
+ 1);
1667 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1668 sock_
.them_address_literal());
1671 LOG(INFO
) << "claimed identity " << client_identity
1672 << " does NOT match any FCrDNS: ";
1673 for (auto const& client_fcrdns
: client_fcrdns_
) {
1674 LOG(INFO
) << " " << client_fcrdns
;
1678 // Bogus clients claim to be us or some local host.
1679 if (sock_
.has_peername() && ((client_identity
== server_identity_
) ||
1680 (client_identity
== "localhost") ||
1681 (client_identity
== "localhost.localdomain"))) {
1683 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1684 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1690 LOG(INFO
) << "allow-listed IP address can claim to be "
1695 // Ease up in test mode.
1696 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1700 error_msg
= fmt::format("liar, claimed to be {}", client_identity
.ascii());
1701 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1705 std::vector
<std::string
> labels
;
1706 boost::algorithm::split(labels
, client_identity
.ascii(),
1707 boost::algorithm::is_any_of("."));
1708 if (labels
.size() < 2) {
1710 fmt::format("claimed bogus identity {}", client_identity
.ascii());
1711 out_() << "550 4.7.1 bogus identity\r\n" << std::flush
;
1713 // // Sometimes we may want to look at mail from non conforming
1714 // // sending systems.
1715 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1716 // << client_ << " claiming " << client_identity;
1720 if (lookup_domain(block_
, client_identity
)) {
1722 fmt::format("claimed blocked identity {}", client_identity
.ascii());
1723 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1727 auto const tld
{tld_db_
.get_registered_domain(client_identity
.ascii())};
1729 // Sometimes we may want to look at mail from misconfigured
1731 // LOG(WARNING) << "claimed identity has no registered domain";
1734 else if (block_
.contains(tld
)) {
1736 fmt::format("claimed identity has blocked registered domain {}", tld
);
1737 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush
;
1741 // not otherwise objectionable
1745 // check sender from RFC5321 MAIL FROM:
1746 bool Session::verify_sender_(Mailbox
const& sender
, std::string
& error_msg
)
1748 std::string
const sender_str
{sender
};
1750 auto bad_senders_db_name
= config_path_
/ "bad_senders";
1751 CDB bad_senders
{bad_senders_db_name
}; // Addresses we don't accept mail from.
1752 if (bad_senders
.contains(sender_str
)) {
1753 error_msg
= fmt::format("{} bad sender", sender_str
);
1754 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1758 // We don't accept mail /from/ a domain we are expecting to accept
1759 // mail for on an external network connection.
1761 if (sock_
.them_address_literal() != sock_
.us_address_literal()) {
1762 if ((accept_domains_
.is_open() &&
1763 (accept_domains_
.contains(sender
.domain().ascii()) ||
1764 accept_domains_
.contains(sender
.domain().utf8()))) ||
1765 (sender
.domain() == server_identity_
)) {
1767 // Ease up in test mode.
1768 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1771 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1772 error_msg
= fmt::format("liar, claimed to be {}", sender
.domain());
1777 if (sender
.domain().is_address_literal()) {
1778 if (sender
.domain() != sock_
.them_address_literal()) {
1779 LOG(WARNING
) << "sender domain " << sender
.domain() << " does not match "
1780 << sock_
.them_address_literal();
1785 if (!verify_sender_spf_(sender
)) {
1786 error_msg
= "failed SPF check";
1790 if (!verify_sender_domain_(sender
.domain(), error_msg
)) {
1797 // this sender is the RFC5321 MAIL FROM: domain part
1798 bool Session::verify_sender_domain_(Domain
const& sender
,
1799 std::string
& error_msg
)
1801 if (sender
.empty()) {
1803 // is used to send bounce messages.
1807 // Break sender domain into labels:
1809 std::vector
<std::string
> labels
;
1810 boost::algorithm::split(labels
, sender
.ascii(),
1811 boost::algorithm::is_any_of("."));
1813 if (labels
.size() < 2) { // This is not a valid domain.
1814 error_msg
= fmt::format("{} invalid syntax", sender
.ascii());
1815 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1819 if (allow_
.contains(sender
.ascii())) {
1820 LOG(INFO
) << "sender " << sender
.ascii() << " allowed";
1823 auto const reg_dom
{tld_db_
.get_registered_domain(sender
.ascii())};
1825 if (allow_
.contains(reg_dom
)) {
1826 LOG(INFO
) << "sender registered domain \"" << reg_dom
<< "\" allowed";
1830 // LOG(INFO) << "looking up " << reg_dom;
1831 return verify_sender_domain_uribl_(reg_dom
, error_msg
);
1834 LOG(INFO
) << "sender \"" << sender
<< "\" not disallowed";
1838 // check sender domain on dynamic URI block lists
1839 bool Session::verify_sender_domain_uribl_(std::string_view sender
,
1840 std::string
& error_msg
)
1842 if (!sock_
.has_peername()) // short circuit
1845 std::shuffle(std::begin(Config::uribls
), std::end(Config::uribls
),
1847 for (auto uribl
: Config::uribls
) {
1848 auto const lookup
= fmt::format("{}.{}", sender
, uribl
);
1849 auto as
= DNS::get_strings(res_
, DNS::RR_type::A
, lookup
);
1851 if (as
.front() == "127.0.0.1")
1853 error_msg
= fmt::format("{} blocked on advice of {}", sender
, uribl
);
1854 out_() << "550 5.7.1 sender " << error_msg
<< "\r\n" << std::flush
;
1859 LOG(INFO
) << "sender \"" << sender
<< "\" not blocked by URIBLs";
1863 bool Session::verify_sender_spf_(Mailbox
const& sender
)
1865 if (!sock_
.has_peername()) {
1866 auto const ip_addr
= "127.0.0.1"; // use localhost for local socket
1868 fmt::format("Received-SPF: pass ({}: allow-listed) client-ip={}; "
1869 "envelope-from={}; helo={};",
1870 server_id_(), ip_addr
, sender
, client_identity_
);
1871 spf_sender_domain_
= "localhost";
1875 auto const spf_srv
= SPF::Server
{server_id_().c_str()};
1876 auto spf_request
= SPF::Request
{spf_srv
};
1878 if (IP4::is_address(sock_
.them_c_str())) {
1879 spf_request
.set_ipv4_str(sock_
.them_c_str());
1881 else if (IP6::is_address(sock_
.them_c_str())) {
1882 spf_request
.set_ipv6_str(sock_
.them_c_str());
1885 LOG(FATAL
) << "bogus address " << sock_
.them_address_literal() << ", "
1886 << sock_
.them_c_str();
1889 auto const from
{static_cast<std::string
>(sender
)};
1891 spf_request
.set_env_from(from
.c_str());
1892 spf_request
.set_helo_dom(client_identity_
.ascii().c_str());
1894 auto const spf_res
{SPF::Response
{spf_request
}};
1895 spf_result_
= spf_res
.result();
1896 spf_received_
= spf_res
.received_spf();
1897 spf_sender_domain_
= spf_request
.get_sender_dom();
1899 LOG(INFO
) << "spf_received_ == " << spf_received_
;
1901 if (spf_result_
== SPF::Result::FAIL
) {
1902 LOG(WARNING
) << spf_res
.header_comment();
1905 LOG(INFO
) << spf_res
.header_comment();
1908 if (spf_result_
== SPF::Result::PASS
) {
1909 if (lookup_domain(block_
, spf_sender_domain_
)) {
1910 LOG(INFO
) << "SPF sender domain (" << spf_sender_domain_
1919 bool Session::verify_from_params_(parameters_t
const& parameters
)
1921 // Take a look at the optional parameters:
1922 for (auto const& [name
, value
] : parameters
) {
1923 if (iequal(name
, "BODY")) {
1924 if (iequal(value
, "8BITMIME")) {
1925 // everything is cool, this is our default...
1927 else if (iequal(value
, "7BIT")) {
1928 // nothing to see here, move along...
1930 else if (iequal(value
, "BINARYMIME")) {
1934 LOG(WARNING
) << "unrecognized BODY type \"" << value
<< "\" requested";
1937 else if (iequal(name
, "SMTPUTF8")) {
1938 if (!value
.empty()) {
1939 LOG(WARNING
) << "SMTPUTF8 parameter has a value: " << value
;
1944 // else if (iequal(name, "PRDR")) {
1945 // LOG(INFO) << "using PRDR";
1949 else if (iequal(name
, "SIZE")) {
1950 if (value
.empty()) {
1951 LOG(WARNING
) << "SIZE parameter has no value.";
1955 auto const sz
= stoull(value
);
1956 if (sz
> max_msg_size()) {
1957 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1958 LOG(WARNING
) << "SIZE parameter too large: " << sz
;
1962 catch (std::invalid_argument
const& e
) {
1963 LOG(WARNING
) << "SIZE parameter has invalid value: " << value
;
1965 catch (std::out_of_range
const& e
) {
1966 LOG(WARNING
) << "SIZE parameter has out-of-range value: " << value
;
1968 // I guess we just ignore bad size parameters.
1971 else if (iequal(name
, "REQUIRETLS")) {
1973 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush
;
1974 LOG(WARNING
) << "REQUIRETLS needed";
1979 LOG(WARNING
) << "unrecognized 'MAIL FROM' parameter " << name
<< "="
1987 bool Session::verify_rcpt_params_(parameters_t
const& parameters
)
1989 // Take a look at the optional parameters:
1990 for (auto const& [name
, value
] : parameters
) {
1991 if (iequal(name
, "RRVS")) {
1992 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
1993 LOG(INFO
) << name
<< "=" << value
;
1996 LOG(WARNING
) << "unrecognized 'RCPT TO' parameter " << name
<< "="
2004 // check recipient from RFC5321 RCPT TO:
2005 bool Session::verify_recipient_(Mailbox
const& recipient
)
2007 if ((recipient
.local_part() == "Postmaster") && (recipient
.domain() == "")) {
2008 LOG(INFO
) << "magic Postmaster address";
2012 auto const accepted_domain
{[this, &recipient
] {
2013 if (recipient
.domain().is_address_literal()) {
2014 if (recipient
.domain() != sock_
.us_address_literal()) {
2015 LOG(WARNING
) << "recipient.domain address " << recipient
.domain()
2016 << " does not match ours " << sock_
.us_address_literal();
2022 // Domains we accept mail for.
2023 if (accept_domains_
.is_open()) {
2024 if (accept_domains_
.contains(recipient
.domain().ascii()) ||
2025 accept_domains_
.contains(recipient
.domain().utf8())) {
2030 // If we have no list of domains to accept, at least take our own.
2031 if (recipient
.domain() == server_id_()) {
2039 if (!accepted_domain
) {
2040 out_() << "554 5.7.1 relay access denied\r\n" << std::flush
;
2041 LOG(WARNING
) << "relay access denied for domain " << recipient
.domain();
2045 // Check for local addresses we reject.
2047 auto bad_recipients_db_name
= config_path_
/ "bad_recipients";
2048 CDB bad_recipients_db
{bad_recipients_db_name
};
2049 if (bad_recipients_db
.contains(recipient
.local_part())) {
2050 out_() << "550 5.1.1 bad recipient " << recipient
<< "\r\n" << std::flush
;
2051 LOG(WARNING
) << "bad recipient " << recipient
;
2057 auto temp_fail_db_name
= config_path_
/ "temp_fail";
2058 CDB temp_fail
{temp_fail_db_name
}; // Addresses we make wait...
2059 if (temp_fail
.contains(recipient
.local_part())) {
2060 out_() << "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n"
2062 LOG(WARNING
) << "temp fail for recipient " << recipient
;
2067 // Check for and act on magic "wait" address.
2069 using namespace boost::xpressive
;
2072 sregex
const rex
= icase("wait-rcpt-") >> (secs_
= +_d
);
2075 if (regex_match(recipient
.local_part(), what
, rex
)) {
2076 auto const str
= what
[secs_
].str();
2077 LOG(INFO
) << "waiting at RCPT TO " << str
<< " seconds";
2079 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
2081 LOG(INFO
) << "done waiting";
2086 if (recipient
.local_part().length() > 8) {
2087 out_() << "550 5.1.1 unknown recipient " << recipient
<< "\r\n"
2089 LOG(WARNING
) << "unknown recipient for HELO " << recipient
;