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
{6};
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]() {
623 if (sock_
.tls() && !extensions_
) {
624 LOG(WARNING
) << "TLS active without extensions";
626 // <https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#mail-parameters-5>
628 return sock_
.tls() ? "UTF8SMTPS" : "UTF8SMTP";
629 else if (sock_
.tls())
631 else if (extensions_
)
637 fmt::memory_buffer headers
;
640 fmt::format_to(headers
, "Return-Path: <{}>\r\n", reverse_path_
);
643 if (!spf_received_
.empty()) {
644 fmt::format_to(headers
, "{}\r\n", spf_received_
);
648 // <https://tools.ietf.org/html/rfc5321#section-4.4>
649 fmt::format_to(headers
, "Received: from {}", client_identity_
.utf8());
650 if (sock_
.has_peername()) {
651 fmt::format_to(headers
, " ({})", client_
);
653 fmt::format_to(headers
, "\r\n\tby {} with {} id {}", server_identity_
.utf8(),
655 if (forward_path_
.size()) {
656 fmt::format_to(headers
, "\r\n\tfor <{}>", forward_path_
[0]);
657 for (auto i
= 1u; i
< forward_path_
.size(); ++i
)
658 fmt::format_to(headers
, ",\r\n\t <{}>", forward_path_
[i
]);
660 std::string
const tls_info
{sock_
.tls_info()};
661 if (tls_info
.length()) {
662 fmt::format_to(headers
, "\r\n\t({})", tls_info
);
664 fmt::format_to(headers
, ";\r\n\t{}\r\n", msg
.when());
666 return fmt::to_string(headers
);
670 bool lookup_domain(CDB
& cdb
, Domain
const& domain
)
672 if (!domain
.empty()) {
673 if (cdb
.contains(domain
.ascii())) {
676 if (domain
.is_unicode() && cdb
.contains(domain
.utf8())) {
684 std::tuple
<Session::SpamStatus
, std::string
> Session::spam_status_()
686 if (spf_result_
== SPF::Result::FAIL
&& !ip_allowed_
)
687 return {SpamStatus::spam
, "SPF failed"};
689 // These should have already been rejected by verify_client_().
690 if ((reverse_path_
.domain() == "localhost.local") ||
691 (reverse_path_
.domain() == "localhost"))
692 return {SpamStatus::spam
, "bogus reverse_path"};
694 std::vector
<std::string
> why_ham
;
696 // Anything enciphered tastes a lot like ham.
698 why_ham
.emplace_back("they used TLS");
700 if (spf_result_
== SPF::Result::PASS
) {
701 if (lookup_domain(allow_
, spf_sender_domain_
)) {
702 why_ham
.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
703 spf_sender_domain_
.utf8()));
706 auto tld_dom
{tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
707 if (tld_dom
&& allow_
.contains(tld_dom
)) {
708 why_ham
.emplace_back(fmt::format(
709 "SPF sender registered domain ({}) is allowed", tld_dom
));
715 why_ham
.emplace_back(
716 fmt::format("FCrDNS (or it's registered domain) is allowed"));
718 if (!why_ham
.empty())
719 return {SpamStatus::ham
,
720 fmt::format("{}", fmt::join(std::begin(why_ham
), std::end(why_ham
),
723 return {SpamStatus::spam
, "it's not ham"};
726 static std::string
folder(Session::SpamStatus status
,
727 std::vector
<Mailbox
> const& forward_path
,
728 Mailbox
const& reverse_path
)
730 if (status
== Session::SpamStatus::spam
)
736 bool Session::msg_new()
738 CHECK((state_
== xact_step::data
) || (state_
== xact_step::bdat
));
740 auto const& [status
, reason
]{spam_status_()};
742 LOG(INFO
) << ((status
== SpamStatus::ham
) ? "ham since " : "spam since ")
745 // All sources of ham get a fresh 5 minute timeout per message.
746 if (status
== SpamStatus::ham
) {
747 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr))
751 msg_
= std::make_unique
<MessageStore
>();
753 if (!FLAGS_max_write
)
754 FLAGS_max_write
= max_msg_size();
757 msg_
->open(server_id_(), FLAGS_max_write
,
758 folder(status
, forward_path_
, reverse_path_
));
759 auto const hdrs
{added_headers_(*(msg_
.get()))};
762 // fmt::memory_buffer spam_status;
763 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
764 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
765 // msg_->write(spam_status.data(), spam_status.size());
767 LOG(INFO
) << "Spam-Status: "
768 << ((status
== SpamStatus::spam
) ? "Yes" : "No") << ", "
773 catch (std::system_error
const& e
) {
776 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
777 LOG(ERROR
) << "no space";
783 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
784 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
785 LOG(ERROR
) << e
.what();
791 catch (std::exception
const& e
) {
792 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
793 LOG(ERROR
) << e
.what();
799 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
800 LOG(ERROR
) << "msg_new failed with no exception caught";
806 bool Session::msg_write(char const* s
, std::streamsize count
)
808 if ((state_
!= xact_step::data
) && (state_
!= xact_step::bdat
))
815 if (msg_
->write(s
, count
))
818 catch (std::system_error
const& e
) {
821 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush
;
822 LOG(ERROR
) << "no space";
828 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
829 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
830 LOG(ERROR
) << e
.what();
836 catch (std::exception
const& e
) {
837 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
838 LOG(ERROR
) << e
.what();
844 out_() << "451 4.0.0 mail system error\r\n" << std::flush
;
845 LOG(ERROR
) << "msg_write failed with no exception caught";
851 bool Session::data_start()
853 last_in_group_("DATA");
856 case xact_step::helo
:
857 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
858 LOG(WARNING
) << "'DATA' before HELO/EHLO"
859 << (sock_
.has_peername() ? " from " : "") << client_
;
861 case xact_step::mail
:
862 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
863 LOG(WARNING
) << "'DATA' before 'MAIL FROM'"
864 << (sock_
.has_peername() ? " from " : "") << client_
;
866 case xact_step::rcpt
:
868 /******************************************************************
869 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
871 The DATA command can fail at only two points in the protocol exchange:
873 If there was no MAIL, or no RCPT, command, or all such commands were
874 rejected, the server MAY return a "command out of sequence" (503) or
875 "no valid recipients" (554) reply in response to the DATA command.
877 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
879 The additional restriction is that when there have been no successful
880 RCPT commands in the mail transaction, the DATA command MUST fail
881 with a 503 reply code.
883 Therefore I will send the reply code that is valid for both, and
884 do the same for the BDAT case.
885 *******************************************************************/
887 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
888 LOG(WARNING
) << "no valid recipients"
889 << (sock_
.has_peername() ? " from " : "") << client_
;
891 case xact_step::data
: break;
892 case xact_step::bdat
:
893 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
894 LOG(WARNING
) << "'DATA' during BDAT transfer"
895 << (sock_
.has_peername() ? " from " : "") << client_
;
897 case xact_step::rset
:
898 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
899 LOG(WARNING
) << "error state must be cleared with a RSET"
900 << (sock_
.has_peername() ? " from " : "") << client_
;
905 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
907 LOG(WARNING
) << "DATA does not support BINARYMIME";
908 state_
= xact_step::rset
; // RFC 3030 section 3 page 5
913 LOG(ERROR
) << "msg_new() failed";
917 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush
;
922 // bool Session::do_forward_(message::parsed& msg)
924 // auto msg_fwd = msg;
926 // // Generate a reply address
927 // Reply::from_to reply;
928 // reply.mail_from = msg_fwd.dmarc_from;
929 // reply.rcpt_to_local_part = fwd_from_.local_part();
931 // auto const reply_addr =
932 // fmt::format("{}@{}", srs_.enc_reply(reply), server_id_());
934 // auto const munging = false;
936 // auto const sender = server_identity_.ascii().c_str();
937 // auto const selector = FLAGS_selector.c_str();
938 // auto const key_file =
939 // (config_path_ / FLAGS_selector).replace_extension("private");
940 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
943 // auto const from_hdr =
944 // fmt::format("From: \"{} via\" <@>", msg_fwd.dmarc_from, reply_addr);
945 // message::rewrite_from_to(msg_fwd, from_hdr, "", sender, selector,
949 // auto const reply_to_hdr = fmt::format("Reply-To: {}", reply_addr);
950 // message::rewrite_from_to(msg_fwd, "", reply_to_hdr, sender, selector,
955 // if (!send_.send(msg_fwd.as_string())) {
956 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
960 // LOG(ERROR) << "failed to send for " << fwd_path_;
964 // LOG(INFO) << "successfully sent for " << fwd_path_;
968 // bool Session::do_reply_(message::parsed& msg)
970 // Mailbox to_mbx(rep_info_.mail_from);
971 // Mailbox from_mbx(rep_info_.rcpt_to_local_part, server_identity_);
973 // auto reply = std::make_unique<MessageStore>();
974 // reply->open(server_id_(), FLAGS_max_write, ".Drafts");
976 // auto const date{Now{}};
977 // auto const pill{Pill{}};
978 // auto const mid_str =
979 // fmt::format("<{}.{}@{}>", date.sec(), pill, server_identity_);
981 // fmt::memory_buffer bfr;
983 // fmt::format_to(bfr, "From: <{}>\r\n", from_mbx);
984 // fmt::format_to(bfr, "To: <{}>\r\n", to_mbx);
986 // fmt::format_to(bfr, "Date: {}\r\n", date.c_str());
988 // fmt::format_to(bfr, "Message-ID: {}\r\n", mid_str.c_str());
990 // if (!msg.get_header(message::Subject).empty()) {
991 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
992 // msg.get_header(message::Subject));
995 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
996 // "Reply to your message");
999 // if (!msg.get_header(message::In_Reply_To).empty()) {
1000 // fmt::format_to(bfr, "{}: {}\r\n", message::In_Reply_To,
1001 // msg.get_header(message::In_Reply_To));
1004 // if (!msg.get_header(message::MIME_Version).empty() &&
1005 // msg.get_header(message::Content_Type).empty()) {
1006 // fmt::format_to(bfr, "{}: {}\r\n", message::MIME_Version,
1007 // msg.get_header(message::MIME_Version));
1008 // fmt::format_to(bfr, "{}: {}\r\n", message::Content_Type,
1009 // msg.get_header(message::Content_Type));
1012 // reply->write(fmt::to_string(bfr));
1014 // if (!msg.body.empty()) {
1015 // reply->write("\r\n");
1016 // reply->write(msg.body);
1019 // auto const msg_data = reply->freeze();
1020 // message::parsed msg_reply;
1021 // CHECK(msg_reply.parse(msg_data));
1023 // auto const sender = server_identity_.ascii().c_str();
1024 // auto const selector = FLAGS_selector.c_str();
1025 // auto const key_file =
1026 // (config_path_ / FLAGS_selector).replace_extension("private");
1027 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1029 // message::dkim_sign(msg_reply, sender, selector, key_file);
1031 // if (!send_.send(msg_reply.as_string())) {
1032 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1036 // LOG(ERROR) << "send failed for reply to " << to_mbx << " from " <<
1037 // from_mbx; return false;
1040 // LOG(INFO) << "successful reply to " << to_mbx << " from " << from_mbx;
1044 bool Session::do_deliver_()
1048 // auto const sender = server_identity_.ascii().c_str();
1049 // auto const selector = FLAGS_selector.c_str();
1050 // auto const key_file =
1051 // (config_path_ / FLAGS_selector).replace_extension("private");
1052 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1055 // auto const msg_data = msg_->freeze();
1057 // message::parsed msg;
1059 // // Only deal in RFC-5322 Mail Objects.
1060 // bool const message_parsed = msg.parse(msg_data);
1061 // if (message_parsed) {
1063 // // remove any Return-Path
1064 // message::remove_delivery_headers(msg);
1066 // auto const authentic =
1067 // message_parsed &&
1068 // message::authentication(msg, sender, selector, key_file);
1070 // // write a new Return-Path
1071 // msg_->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_));
1073 // for (auto const h : msg.headers) {
1074 // msg_->write(h.as_string());
1075 // msg_->write("\r\n");
1077 // if (!msg.body.empty()) {
1078 // msg_->write("\r\n");
1079 // msg_->write(msg.body);
1084 // if (authentic && !fwd_path_.empty()) {
1085 // if (!do_forward_(msg))
1088 // if (authentic && !rep_info_.empty()) {
1089 // if (!do_reply_(msg))
1096 catch (std::system_error
const& e
) {
1099 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
1100 LOG(ERROR
) << "no space";
1106 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
1108 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
1109 LOG(ERROR
) << e
.what();
1119 void Session::data_done()
1121 CHECK((state_
== xact_step::data
));
1123 if (msg_
&& msg_
->size_error()) {
1131 // out_() << "353\r\n";
1132 // for (auto fp : forward_path_) {
1133 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1137 // Check for and act on magic "wait" address.
1139 using namespace boost::xpressive
;
1142 sregex
const rex
= icase("wait-data-") >> (secs_
= +_d
);
1145 for (auto fp
: forward_path_
) {
1146 if (regex_match(fp
.local_part(), what
, rex
)) {
1147 auto const str
= what
[secs_
].str();
1148 LOG(INFO
) << "waiting at DATA " << str
<< " seconds";
1150 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1152 LOG(INFO
) << "done waiting";
1157 out_() << "250 2.0.0 DATA OK\r\n" << std::flush
;
1158 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1164 void Session::data_size_error()
1166 out_().clear(); // clear possible eof from input side
1167 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1171 LOG(WARNING
) << "DATA size error";
1175 void Session::data_error()
1177 out_().clear(); // clear possible eof from input side
1178 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush
;
1182 LOG(WARNING
) << "DATA error";
1186 bool Session::bdat_start(size_t n
)
1188 // In practice, this one gets pipelined.
1189 // last_in_group_("BDAT");
1192 case xact_step::helo
:
1193 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
1194 LOG(WARNING
) << "'BDAT' before HELO/EHLO"
1195 << (sock_
.has_peername() ? " from " : "") << client_
;
1197 case xact_step::mail
:
1198 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
1199 LOG(WARNING
) << "'BDAT' before 'MAIL FROM'"
1200 << (sock_
.has_peername() ? " from " : "") << client_
;
1202 case xact_step::rcpt
:
1203 // See comment in data_start()
1204 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1205 LOG(WARNING
) << "no valid recipients"
1206 << (sock_
.has_peername() ? " from " : "") << client_
;
1208 case xact_step::data
: // first bdat
1210 case xact_step::bdat
: return true;
1211 case xact_step::rset
:
1212 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1213 LOG(WARNING
) << "error state must be cleared with a RSET"
1214 << (sock_
.has_peername() ? " from " : "") << client_
;
1218 state_
= xact_step::bdat
;
1223 void Session::bdat_done(size_t n
, bool last
)
1225 if (state_
!= xact_step::bdat
) {
1234 if (msg_
->size_error()) {
1240 out_() << "250 2.0.0 BDAT " << n
<< " OK\r\n" << std::flush
;
1241 LOG(INFO
) << "BDAT " << n
;
1247 out_() << "250 2.0.0 BDAT " << n
<< " LAST OK\r\n" << std::flush
;
1249 LOG(INFO
) << "BDAT " << n
<< " LAST";
1250 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1255 void Session::bdat_size_error()
1257 out_().clear(); // clear possible eof from input side
1258 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1262 LOG(WARNING
) << "BDAT size error";
1266 void Session::bdat_seq_error()
1268 out_().clear(); // clear possible eof from input side
1269 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush
;
1273 LOG(WARNING
) << "BDAT sequence error";
1277 void Session::bdat_io_error()
1279 out_().clear(); // clear possible eof from input side
1280 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush
;
1284 LOG(WARNING
) << "BDAT I/O error";
1288 void Session::rset()
1290 out_() << "250 2.1.5 RSET OK\r\n";
1291 // No flush RFC-2920 section 3.1, this could be part of a command group.
1292 LOG(INFO
) << "RSET";
1296 void Session::noop(std::string_view str
)
1298 last_in_group_("NOOP");
1299 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush
;
1300 LOG(INFO
) << "NOOP" << (str
.length() ? " " : "") << str
;
1303 void Session::vrfy(std::string_view str
)
1305 last_in_group_("VRFY");
1306 out_() << "252 2.1.5 try it\r\n" << std::flush
;
1307 LOG(INFO
) << "VRFY" << (str
.length() ? " " : "") << str
;
1310 void Session::help(std::string_view str
)
1312 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n" << std::flush
;
1313 LOG(INFO
) << "HELP" << (str
.length() ? " " : "") << str
;
1316 void Session::quit()
1319 // last_in_group_("QUIT");
1320 out_() << "221 2.0.0 closing connection\r\n" << std::flush
;
1321 LOG(INFO
) << "QUIT";
1325 void Session::auth()
1327 out_() << "454 4.7.0 authentication failure\r\n" << std::flush
;
1328 LOG(INFO
) << "AUTH";
1332 void Session::error(std::string_view log_msg
)
1334 out_() << "421 4.3.5 system error: " << log_msg
<< "\r\n" << std::flush
;
1335 LOG(WARNING
) << log_msg
;
1338 void Session::cmd_unrecognized(std::string_view cmd
)
1340 auto const escaped
{esc(cmd
)};
1341 LOG(WARNING
) << "command unrecognized: \"" << escaped
<< "\"";
1343 if (++n_unrecognized_cmds_
>= Config::max_unrecognized_cmds
) {
1344 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1345 << "\" exceeds limit\r\n"
1347 LOG(WARNING
) << n_unrecognized_cmds_
1348 << " unrecognized commands is too many";
1352 out_() << "500 5.5.1 command unrecognized: \"" << escaped
<< "\"\r\n"
1356 void Session::bare_lf()
1358 // Error code used by Office 365.
1359 out_() << "554 5.6.11 bare LF\r\n" << std::flush
;
1360 LOG(WARNING
) << "bare LF";
1364 void Session::max_out()
1366 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1367 LOG(WARNING
) << "message size maxed out";
1371 void Session::time_out()
1373 out_() << "421 4.4.2 time-out\r\n" << std::flush
;
1374 LOG(WARNING
) << "time-out" << (sock_
.has_peername() ? " from " : "")
1379 void Session::starttls()
1381 last_in_group_("STARTTLS");
1383 out_() << "554 5.5.1 TLS already active\r\n" << std::flush
;
1384 LOG(WARNING
) << "STARTTLS issued with TLS already active";
1386 else if (!extensions_
) {
1387 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1389 LOG(WARNING
) << "STARTTLS issued without using EHLO";
1392 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush
;
1393 if (sock_
.starttls_server(config_path_
)) {
1395 max_msg_size(Config::max_msg_size_bro
);
1396 LOG(INFO
) << "STARTTLS " << sock_
.tls_info();
1399 LOG(INFO
) << "failed STARTTLS";
1404 void Session::exit_()
1406 // sock_.log_totals();
1408 timespec time_used
{};
1409 clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &time_used
);
1411 LOG(INFO
) << "CPU time " << time_used
.tv_sec
<< "." << std::setw(9)
1412 << std::setfill('0') << time_used
.tv_nsec
<< " seconds";
1414 std::exit(EXIT_SUCCESS
);
1418 bool ip4_allowed(char const* addr
)
1423 char const* comment
;
1437 nw
const networks
[]{
1438 // the one very special case
1439 {"108.83.36.112", "255.255.255.248", "108.83.36.112/29"},
1441 // accept from major providers:
1442 {"3.0.0.0", "255.0.0.0", "3.0.0.0/9 and 3.128.0.0/9 Amazon"},
1443 {"5.45.198.0", "255.255.254.0", "5.45.198.0/23 YANDEX-5-45-198"},
1444 {"12.153.224.0", "255.255.255.0", "12.153.224.0/24 E-TRADE10-224"},
1445 {"13.108.0.0", "255.252.0.0", "13.108.0.0/14 SALESF-3 NET-13-108-0-0-1"},
1446 {"17.0.0.0", "255.0.0.0", "17.0.0.0/8 APPLE-WWNET"},
1447 {"20.67.221.0", "255.255.255.0", "20.64.0.0/10 NET-20-33-0-0-1 duck.com"},
1448 {"20.67.222.0", "255.255.255.0", "20.64.0.0/10 NET-20-33-0-0-1 duck.com"},
1449 {"20.67.223.0", "255.255.255.0", "20.64.0.0/10 NET-20-33-0-0-1 duck.com"},
1450 {"40.74.0.0", "255.254.0.0", "40.74.0.0/15 MSFT NET-40-74-0-0-1"},
1451 {"40.76.0.0", "255.252.0.0", "40.76.0.0/14 MSFT NET-40-74-0-0-1"},
1452 {"40.80.0.0", "255.240.0.0", "40.80.0.0/12 MSFT NET-40-74-0-0-1"},
1453 {"40.92.0.0", "255.252.0.0", "40.96.0.0/14 MSFT NET-40-74-0-0-1"},
1454 {"40.96.0.0", "255.240.0.0", "40.96.0.0/12 MSFT NET-40-74-0-0-1"},
1455 {"40.112.0.0", "255.248.0.0", "40.112.0.0/13 MSFT NET-40-74-0-0-1"},
1456 {"40.120.0.0", "255.252.0.0", "40.120.0.0/14 MSFT NET-40-74-0-0-1"},
1457 {"40.124.0.0", "255.255.0.0", "40.124.0.0/16 MSFT NET-40-74-0-0-1"},
1458 {"40.125.0.0", "255.255.128.0", "40.125.0.0/17 MSFT NET-40-74-0-0-1"},
1459 {"56.0.0.0", "255.0.0.0", "56.0.0.0/8 USPS1"},
1460 {"65.52.0.0", "255.252.0.0", "65.52.0.0/14 MICROSOFT-1BLK"},
1461 {"65.196.177.0", "255.255.255.0", "UU-65-196-177 E*TRADE FINANCIAL CORPORATION (C05251409)"},
1462 {"66.163.160.0", "255.255.224.0", "66.163.160.0/19 A-YAHOO-US2"},
1463 {"66.211.176.0", "255.255.240.0", "66.211.176.0/20 EBAY-2"},
1464 {"66.211.172.0", "255.255.252.0", "66.211.172.0/22 EBAY-2"},
1465 {"66.220.144.0", "255.255.240.0", "66.220.144.0/20 TFBNET3"},
1466 {"68.232.192.0", "255.255.240.0", "68.232.192.0/20 EXACT-IP-NET-2"},
1467 {"69.171.224.0", "255.255.224.0", "69.171.224.0/19 TFBNET3"},
1468 {"70.47.67.0", "255.255.255.0", "70.47.67.0/24 NET-462F4300-24"},
1469 {"74.6.0.0", "255.255.0.0", "INKTOMI-BLK-6 Oath"},
1470 {"74.125.0.0", "255.255.0.0", "74.125.0.0/16 GOOGLE"},
1471 {"75.101.100.43", "255.255.255.255", "new.toad.com"},
1472 {"76.178.68.57", "255.255.255.255", "cpe-76-178-68-57.natsow.res.rr.com"},
1473 {"98.136.0.0", "255.252.0.0", "98.136.0.0/14 A-YAHOO-US9"},
1474 {"104.40.0.0", "255.248.0.0", "104.40.0.0/13 MSFT"},
1475 {"108.174.0.0", "255.255.240.0", "108.174.0.0/20 LINKEDIN"},
1476 {"159.45.0.0", "255.255.0.0", "159.45.0.0/16 AGE-COM"},
1477 {"159.53.0.0", "255.255.0.0", "159.53.0.0/16 JMC"},
1478 {"159.135.224.0", "255.255.240.0", "159.135.224.0/20 MNO87-159-135-224-0-0"},
1479 {"162.247.72.0", "255.255.252.0", "162.247.72.0/22 CALYX-INSTITUTE-V4-1"},
1480 {"165.107.0.0", "255.255.0.0", "NET-LDC-CA-GOV"},
1481 {"192.30.252.0", "255.255.252.0", "192.30.252.0/22 GITHUB-NET4-1"},
1482 {"192.175.128.0", "255.255.128.0", "192.175.128.0/17 NETBLK-VANGUARD"},
1483 {"198.2.128.0", "255.255.192.0", "198.2.128.0/18 RSG-DELIVERY"},
1484 {"198.252.206.0", "255.255.255.0", "198.252.206.0/24 SE-NET01"},
1485 {"199.122.120.0", "255.255.248.0", "199.122.120.0/21 EXACT-IP-NET-3"},
1486 {"204.13.164.0", "255.255.255.0", "204.13.164.0/24 RISEUP-NETWORKS-SWIFT-BLOCK2"},
1487 {"204.29.186.0", "255.255.254.0", "204.29.186.0/23 ATDN-NSCAPE"},
1488 {"205.139.104.0", "255.255.252.0", "205.139.104.0/22 SAVV-S259964-8"},
1489 {"205.201.128.0", "255.255.240.0", "205.201.128.0/20 RSG-DELIVERY"},
1490 {"208.118.235.0", "255.255.255.0", "208.118.235.0/24 TWDX-208-118-235-0-1"},
1491 {"208.192.0.0", "255.192.0.0", "208.192.0.0/10 UUNET1996B"},
1492 {"209.51.188.0", "255.255.255.0", "I:NET-209.51.188.0/24 FSF"},
1493 {"209.85.128.0", "255.255.128.0", "209.85.128.0/17 GOOGLE"},
1494 {"209.132.176.0", "255.255.240.0", "209.132.176.0/20 RED-HAT-BLK"},
1495 {"209.237.224.0", "255.255.224.0", "UNITEDLAYER-1"},
1496 // {"209.237.225.253", "255.255.255.255", "Old new.toad.com"},
1501 CHECK_EQ(inet_pton(AF_INET
, addr
, &addr32
), 1)
1502 << "can't interpret as IPv4 address";
1504 for (auto const& network
: networks
) {
1506 CHECK_EQ(inet_pton(AF_INET
, network
.net
, &net32
), 1)
1507 << "can't grok " << network
.net
;
1509 CHECK_EQ(inet_pton(AF_INET
, network
.mask
, &mask32
), 1)
1510 << "can't grok " << network
.mask
;
1512 // sanity check: all unmasked bits must be zero
1513 CHECK_EQ(net32
& (~mask32
), 0)
1514 << "bogus config net=" << network
.net
<< ", mask=" << network
.mask
;
1516 if (net32
== (addr32
& mask32
)) {
1517 LOG(INFO
) << addr
<< " allowed " << network
.comment
;
1526 /////////////////////////////////////////////////////////////////////////////
1528 // All of the verify_* functions send their own error messages back to
1529 // the client on failure, and return false.
1531 bool Session::verify_ip_address_(std::string
& error_msg
)
1533 auto ip_block_db_name
= config_path_
/ "ip-block";
1535 if (ip_block
.open(ip_block_db_name
) &&
1536 ip_block
.contains(sock_
.them_c_str())) {
1538 fmt::format("IP address {} on static blocklist", sock_
.them_c_str());
1539 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1543 client_fcrdns_
.clear();
1545 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1546 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1547 LOG(INFO
) << "loopback address allowed";
1549 client_fcrdns_
.emplace_back("localhost");
1550 client_
= fmt::format("localhost {}", sock_
.them_address_literal());
1554 if (IP::is_private(sock_
.them_address_literal())) {
1555 LOG(INFO
) << "local address allowed";
1557 client_
= sock_
.them_address_literal();
1561 auto const fcrdns
= DNS::fcrdns(res_
, sock_
.them_c_str());
1562 for (auto const& fcr
: fcrdns
) {
1563 client_fcrdns_
.emplace_back(fcr
);
1566 if (!client_fcrdns_
.empty()) {
1567 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1568 sock_
.them_address_literal());
1570 for (auto const& client_fcrdns
: client_fcrdns_
) {
1571 if (allow_
.contains(client_fcrdns
.ascii())) {
1572 // LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1573 fcrdns_allowed_
= true;
1576 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1578 if (allow_
.contains(tld
)) {
1579 // LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1580 fcrdns_allowed_
= true;
1586 for (auto const& client_fcrdns
: client_fcrdns_
) {
1587 if (block_
.contains(client_fcrdns
.ascii())) {
1589 fmt::format("FCrDNS {} on static blocklist", client_fcrdns
.ascii());
1590 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1594 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1596 if (block_
.contains(tld
)) {
1597 error_msg
= fmt::format(
1598 "FCrDNS registered domain {} on static blocklist", tld
);
1599 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1606 client_
= fmt::format("{}", sock_
.them_address_literal());
1609 if (IP4::is_address(sock_
.them_c_str())) {
1611 if (ip4_allowed(sock_
.them_c_str())) {
1612 LOG(INFO
) << "on internal allow list";
1617 auto const reversed
{IP4::reverse(sock_
.them_c_str())};
1620 // Check with allow list.
1621 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1624 for (auto wl : Config::wls) {
1625 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1626 if (q.has_record()) {
1627 using namespace boost::xpressive;
1629 auto const as = q.get_strings()[0];
1630 LOG(INFO) << "on allow list " << wl << " as " << as;
1634 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1637 if (regex_match(as, what, rex)) {
1638 auto const x = what[x_].str();
1639 auto const y = what[y_].str();
1642 std::from_chars(y.data(), y.data() + y.size(), value);
1645 LOG(INFO) << "allowed";
1649 // Any A record skips check on block list
1655 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1656 std::shuffle(std::begin(Config::bls
), std::end(Config::bls
),
1659 for (auto bl
: Config::bls
) {
1661 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ bl
);
1662 if (q
.has_record()) {
1663 auto const as
= q
.get_strings()[0];
1664 if (as
== "127.0.1.1") {
1665 LOG(INFO
) << "Query blocked by " << bl
;
1668 error_msg
= fmt::format("blocked on advice from {}", bl
);
1669 LOG(INFO
) << sock_
.them_c_str() << " " << error_msg
;
1670 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1675 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1681 // check the identity from HELO/EHLO
1682 bool Session::verify_client_(Domain
const& client_identity
,
1683 std::string
& error_msg
)
1685 if (!client_fcrdns_
.empty()) {
1686 if (auto id
= std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
1688 id
!= end(client_fcrdns_
)) {
1689 // If the HELO ident is one of the FCrDNS names...
1690 if (id
!= begin(client_fcrdns_
)) {
1691 // ...then rotate that one to the front of the list
1692 std::rotate(begin(client_fcrdns_
), id
, id
+ 1);
1694 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1695 sock_
.them_address_literal());
1698 LOG(INFO
) << "claimed identity " << client_identity
1699 << " does NOT match any FCrDNS: ";
1700 for (auto const& client_fcrdns
: client_fcrdns_
) {
1701 LOG(INFO
) << " " << client_fcrdns
;
1705 // Bogus clients claim to be us or some local host.
1706 if (sock_
.has_peername() && ((client_identity
== server_identity_
) ||
1707 (client_identity
== "localhost") ||
1708 (client_identity
== "localhost.localdomain"))) {
1710 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1711 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1717 LOG(INFO
) << "allow-listed IP address can claim to be "
1722 // Ease up in test mode.
1723 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1727 error_msg
= fmt::format("liar, claimed to be {}", client_identity
.ascii());
1728 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1732 std::vector
<std::string
> labels
;
1733 boost::algorithm::split(labels
, client_identity
.ascii(),
1734 boost::algorithm::is_any_of("."));
1735 if (labels
.size() < 2) {
1737 fmt::format("claimed bogus identity {}", client_identity
.ascii());
1738 out_() << "550 4.7.1 bogus identity\r\n" << std::flush
;
1740 // // Sometimes we may want to look at mail from non conforming
1741 // // sending systems.
1742 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1743 // << client_ << " claiming " << client_identity;
1747 if (lookup_domain(block_
, client_identity
)) {
1749 fmt::format("claimed blocked identity {}", client_identity
.ascii());
1750 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1754 auto const tld
{tld_db_
.get_registered_domain(client_identity
.ascii())};
1756 // Sometimes we may want to look at mail from misconfigured
1758 // LOG(WARNING) << "claimed identity has no registered domain";
1761 else if (block_
.contains(tld
)) {
1763 fmt::format("claimed identity has blocked registered domain {}", tld
);
1764 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush
;
1768 // not otherwise objectionable
1772 // check sender from RFC5321 MAIL FROM:
1773 bool Session::verify_sender_(Mailbox
const& sender
, std::string
& error_msg
)
1775 std::string
const sender_str
{sender
};
1777 auto bad_senders_db_name
= config_path_
/ "bad_senders";
1779 if (bad_senders
.open(bad_senders_db_name
) &&
1780 bad_senders
.contains(sender_str
)) {
1781 error_msg
= fmt::format("{} bad sender", sender_str
);
1782 out_() << "550 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1786 // We don't accept mail /from/ a domain we are expecting to accept
1787 // mail for on an external network connection.
1789 if (sock_
.them_address_literal() != sock_
.us_address_literal()) {
1790 if ((accept_domains_
.is_open() &&
1791 (accept_domains_
.contains(sender
.domain().ascii()) ||
1792 accept_domains_
.contains(sender
.domain().utf8()))) ||
1793 (sender
.domain() == server_identity_
)) {
1795 // Ease up in test mode.
1796 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1799 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1800 error_msg
= fmt::format("liar, claimed to be {}", sender
.domain());
1805 if (sender
.domain().is_address_literal()) {
1806 if (sender
.domain() != sock_
.them_address_literal()) {
1807 LOG(WARNING
) << "sender domain " << sender
.domain() << " does not match "
1808 << sock_
.them_address_literal();
1813 do_spf_check_(sender
);
1815 if (!verify_sender_domain_(sender
.domain(), error_msg
)) {
1822 // this sender is the RFC5321 MAIL FROM: domain part
1823 bool Session::verify_sender_domain_(Domain
const& sender
,
1824 std::string
& error_msg
)
1826 if (sender
.empty()) {
1828 // is used to send bounce messages.
1832 // Break sender domain into labels:
1834 std::vector
<std::string
> labels
;
1835 boost::algorithm::split(labels
, sender
.ascii(),
1836 boost::algorithm::is_any_of("."));
1838 if (labels
.size() < 2) { // This is not a valid domain.
1839 error_msg
= fmt::format("{} invalid syntax", sender
.ascii());
1840 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1844 if (lookup_domain(block_
, sender
)) {
1846 fmt::format("SPF sender domain ({}) is blocked", spf_sender_domain_
);
1847 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1851 if (spf_result_
== SPF::Result::PASS
) {
1852 if (allow_
.contains(spf_sender_domain_
.ascii())) {
1853 LOG(INFO
) << "sender " << spf_sender_domain_
.ascii() << " allowed";
1858 tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
1860 if (allow_
.contains(reg_dom
)) {
1861 LOG(INFO
) << "sender registered domain \"" << reg_dom
<< "\" allowed";
1867 LOG(INFO
) << "sender \"" << sender
<< "\" not disallowed";
1871 void Session::do_spf_check_(Mailbox
const& sender
)
1873 if (!sock_
.has_peername()) {
1874 auto const ip_addr
= "127.0.0.1"; // use localhost for local socket
1876 fmt::format("Received-SPF: pass ({}: allow-listed) client-ip={}; "
1877 "envelope-from={}; helo={};",
1878 server_id_(), ip_addr
, sender
, client_identity_
);
1879 spf_sender_domain_
= "localhost";
1883 auto const spf_srv
= SPF::Server
{server_id_().c_str()};
1884 auto spf_request
= SPF::Request
{spf_srv
};
1886 if (IP4::is_address(sock_
.them_c_str())) {
1887 spf_request
.set_ipv4_str(sock_
.them_c_str());
1889 else if (IP6::is_address(sock_
.them_c_str())) {
1890 spf_request
.set_ipv6_str(sock_
.them_c_str());
1893 LOG(FATAL
) << "bogus address " << sock_
.them_address_literal() << ", "
1894 << sock_
.them_c_str();
1897 auto const from
{static_cast<std::string
>(sender
)};
1899 spf_request
.set_env_from(from
.c_str());
1900 spf_request
.set_helo_dom(client_identity_
.ascii().c_str());
1902 auto const spf_res
{SPF::Response
{spf_request
}};
1903 spf_result_
= spf_res
.result();
1904 spf_received_
= spf_res
.received_spf();
1905 spf_sender_domain_
= spf_request
.get_sender_dom();
1907 LOG(INFO
) << "spf_received_ == " << spf_received_
;
1909 if (spf_result_
== SPF::Result::FAIL
) {
1910 LOG(WARNING
) << spf_res
.header_comment();
1913 LOG(INFO
) << spf_res
.header_comment();
1917 bool Session::verify_from_params_(parameters_t
const& parameters
)
1919 // Take a look at the optional parameters:
1920 for (auto const& [name
, value
] : parameters
) {
1921 if (iequal(name
, "BODY")) {
1922 if (iequal(value
, "8BITMIME")) {
1923 // everything is cool, this is our default...
1925 else if (iequal(value
, "7BIT")) {
1926 // nothing to see here, move along...
1928 else if (iequal(value
, "BINARYMIME")) {
1932 LOG(WARNING
) << "unrecognized BODY type \"" << value
<< "\" requested";
1935 else if (iequal(name
, "SMTPUTF8")) {
1936 if (!value
.empty()) {
1937 LOG(WARNING
) << "SMTPUTF8 parameter has a value: " << value
;
1942 // else if (iequal(name, "PRDR")) {
1943 // LOG(INFO) << "using PRDR";
1947 else if (iequal(name
, "SIZE")) {
1948 if (value
.empty()) {
1949 LOG(WARNING
) << "SIZE parameter has no value.";
1953 auto const sz
= stoull(value
);
1954 if (sz
> max_msg_size()) {
1955 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1956 LOG(WARNING
) << "SIZE parameter too large: " << sz
;
1960 catch (std::invalid_argument
const& e
) {
1961 LOG(WARNING
) << "SIZE parameter has invalid value: " << value
;
1963 catch (std::out_of_range
const& e
) {
1964 LOG(WARNING
) << "SIZE parameter has out-of-range value: " << value
;
1966 // I guess we just ignore bad size parameters.
1969 else if (iequal(name
, "REQUIRETLS")) {
1971 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush
;
1972 LOG(WARNING
) << "REQUIRETLS needed";
1977 LOG(WARNING
) << "unrecognized 'MAIL FROM' parameter " << name
<< "="
1985 bool Session::verify_rcpt_params_(parameters_t
const& parameters
)
1987 // Take a look at the optional parameters:
1988 for (auto const& [name
, value
] : parameters
) {
1989 if (iequal(name
, "RRVS")) {
1990 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
1991 LOG(INFO
) << name
<< "=" << value
;
1994 LOG(WARNING
) << "unrecognized 'RCPT TO' parameter " << name
<< "="
2002 // check recipient from RFC5321 RCPT TO:
2003 bool Session::verify_recipient_(Mailbox
const& recipient
)
2005 if ((recipient
.local_part() == "Postmaster") && (recipient
.domain() == "")) {
2006 LOG(INFO
) << "magic Postmaster address";
2010 auto const accepted_domain
{[this, &recipient
] {
2011 if (recipient
.domain().is_address_literal()) {
2012 if (recipient
.domain() != sock_
.us_address_literal()) {
2013 LOG(WARNING
) << "recipient.domain address " << recipient
.domain()
2014 << " does not match ours " << sock_
.us_address_literal();
2020 // Domains we accept mail for.
2021 if (accept_domains_
.is_open()) {
2022 if (accept_domains_
.contains(recipient
.domain().ascii()) ||
2023 accept_domains_
.contains(recipient
.domain().utf8())) {
2028 // If we have no list of domains to accept, at least take our own.
2029 if (recipient
.domain() == server_id_()) {
2037 if (!accepted_domain
) {
2038 out_() << "554 5.7.1 relay access denied\r\n" << std::flush
;
2039 LOG(WARNING
) << "relay access denied for domain " << recipient
.domain();
2043 // Check for local addresses we reject.
2045 auto bad_recipients_db_name
= config_path_
/ "bad_recipients";
2046 CDB bad_recipients_db
;
2047 if (bad_recipients_db
.open(bad_recipients_db_name
) &&
2048 bad_recipients_db
.contains(recipient
.local_part())) {
2049 out_() << "550 5.1.1 bad recipient " << recipient
<< "\r\n" << std::flush
;
2050 LOG(WARNING
) << "bad recipient " << recipient
;
2056 auto fail_db_name
= config_path_
/ "fail_554";
2058 if (fail_db
.open(fail_db_name
) &&
2059 fail_db
.contains(recipient
.local_part())) {
2060 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
<< "\r\n"
2062 LOG(WARNING
) << "fail_554 recipient " << recipient
;
2068 auto temp_fail_db_name
= config_path_
/ "temp_fail";
2069 CDB temp_fail
; // Addresses we make wait...
2070 if (temp_fail
.open(temp_fail_db_name
) &&
2071 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";
2097 // This is a trap for a probe done by some senders to see if we
2098 // accept just any old local-part.
2100 if (recipient
.local_part().length() > 8) {
2101 out_() << "550 5.1.1 unknown recipient " << recipient
<< "\r\n"
2103 LOG(WARNING
) << "unknown recipient for HELO " << recipient
;