13 #include "MessageStore.hpp"
14 #include "Session.hpp"
19 #include <fmt/format.h>
20 #include <fmt/ostream.h>
22 #include <boost/algorithm/string/classification.hpp>
23 #include <boost/algorithm/string/split.hpp>
25 #include <boost/xpressive/xpressive.hpp>
29 DEFINE_string(selector
, "ghsmtp", "DKIM selector");
31 using namespace std::string_literals
;
39 <https://www.dnswl.org/?page_id=15#query>
43 The return codes are structured as 127.0.x.y, with “x” indicating the category
44 of an entry and “y” indicating how trustworthy an entry has been judged.
46 Categories (127.0.X.y):
48 2 – Financial services
49 3 – Email Service Providers
50 4 – Organisations (both for-profit [ie companies] and non-profit)
51 5 – Service/network providers
52 6 – Personal/private servers
53 7 – Travel/leisure industry
54 8 – Public sector/governments
55 9 – Media and Tech companies
56 10 – some special cases
57 11 – Education, academic
59 13 – Manufacturing/Industrial
60 14 – Retail/Wholesale/Services
61 15 – Email Marketing Providers
62 20 – Added through Self Service without specific category
64 Trustworthiness / Score (127.0.x.Y):
66 0 = none – only avoid outright blocking (eg large ESP mailservers, -0.1)
67 1 = low – reduce chance of false positives (-1.0)
68 2 = medium – make sure to avoid false positives but allow override for clear
69 cases (-10.0) 3 = high – avoid override (-100.0).
71 The scores in parantheses are typical SpamAssassin scores.
73 Special return code 127.0.0.255
75 In cases where your nameserver issues more than 100’000 queries / 24 hours, you
76 may be blocked from further queries. The return code “127.0.0.255” indicates
82 "b.barracudacentral.org",
87 /*** Last octet from A record returned by blocklists ***
89 From <http://uribl.com/about.shtml#implementation>
92 ---------------------------------------------------------
93 1 00000001 Query blocked, possibly due to high volume
97 14 00001110 black,grey,red (for testpoints)
99 From <https://www.spamhaus.org/faq/section/Spamhaus%20DBL#291>
101 Return Codes Data Source
102 127.0.1.2 spam domain
103 127.0.1.4 phish domain
104 127.0.1.5 malware domain
105 127.0.1.6 botnet C&C domain
106 127.0.1.102 abused legit spam
107 127.0.1.103 abused spammed redirector domain
108 127.0.1.104 abused legit phish
109 127.0.1.105 abused legit malware
110 127.0.1.106 abused legit botnet C&C
111 127.0.1.255 IP queries prohibited!
113 From <http://www.surbl.org/lists#multi>
115 last octet indicates which lists it belongs to. The bit positions in
116 that last octet for membership in the different lists are:
125 char const* uribls
[]{
131 constexpr auto greeting_wait
= std::chrono::seconds
{2};
132 constexpr int max_recipients_per_message
= 100;
133 constexpr int max_unrecognized_cmds
= 20;
135 // Read timeout value gleaned from RFC-1123 section 5.3.2 and RFC-5321
136 // section 4.5.3.2.7.
137 constexpr auto read_timeout
= std::chrono::minutes
{5};
138 constexpr auto write_timeout
= std::chrono::seconds
{30};
139 } // namespace Config
141 #include <gflags/gflags.h>
143 DEFINE_bool(test_mode
, false, "ease up on some checks");
145 DEFINE_bool(immortal
, false, "don't set process timout");
147 DEFINE_uint64(max_read
, 0, "max data to read");
148 DEFINE_uint64(max_write
, 0, "max data to write");
150 DEFINE_bool(rrvs
, false, "support RRVS à la RFC 7293");
152 Session::Session(fs::path config_path
,
153 std::function
<void(void)> read_hook
,
156 : config_path_(config_path
)
158 , sock_(fd_in
, fd_out
, read_hook
, Config::read_timeout
, Config::write_timeout
)
159 , send_(config_path
, "smtp")
162 auto accept_db_name
= config_path_
/ "accept_domains";
163 auto allow_db_name
= config_path_
/ "allow";
164 auto block_db_name
= config_path_
/ "block";
165 auto forward_db_name
= config_path_
/ "forward";
167 accept_domains_
.open(accept_db_name
);
168 allow_
.open(allow_db_name
);
169 block_
.open(block_db_name
);
170 forward_
.open(forward_db_name
);
172 if (sock_
.has_peername() && !IP::is_private(sock_
.us_c_str())) {
173 auto fcrdns
= DNS::fcrdns(res_
, sock_
.us_c_str());
174 for (auto const& fcr
: fcrdns
) {
175 server_fcrdns_
.emplace_back(fcr
);
179 server_identity_
= [this] {
180 auto const id_from_env
{getenv("GHSMTP_SERVER_ID")};
182 return std::string
{id_from_env
};
184 auto const hostname
{osutil::get_hostname()};
185 if (hostname
.find('.') != std::string::npos
)
188 if (!server_fcrdns_
.empty()) {
189 // first result should be shortest
190 return server_fcrdns_
.front().ascii();
193 auto const us_c_str
= sock_
.us_c_str();
194 if (us_c_str
&& !IP::is_private(us_c_str
)) {
195 return IP::to_address_literal(us_c_str
);
198 LOG(FATAL
) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
202 send_
.set_sender(server_identity_
);
204 max_msg_size(Config::max_msg_size_initial
);
207 void Session::max_msg_size(size_t max
)
209 max_msg_size_
= max
; // number to advertise via RFC 1870
211 if (FLAGS_max_read
) {
212 sock_
.set_max_read(FLAGS_max_read
);
215 auto const overhead
= std::max(max
/ 10, size_t(2048));
216 sock_
.set_max_read(max
+ overhead
);
220 void Session::bad_host_(char const* msg
) const
222 if (sock_
.has_peername()) {
223 // On my systems, this pattern triggers a fail2ban rule that
224 // blocks connections from this IP address on port 25 for a few
225 // days. See <https://www.fail2ban.org/> for more info.
226 syslog(LOG_MAIL
| LOG_WARNING
, "bad host [%s] %s", sock_
.them_c_str(), msg
);
228 std::exit(EXIT_SUCCESS
);
231 void Session::reset_()
233 // RSET does not force another EHLO/HELO, the one piece of per
234 // transaction data saved is client_identity_:
236 // client_identity_.clear(); <-- not cleared!
238 reverse_path_
.clear();
239 forward_path_
.clear();
240 spf_received_
.clear();
253 max_msg_size(max_msg_size());
255 state_
= xact_step::mail
;
259 // Return codes from connection establishment are 220 or 554, according
260 // to RFC 5321. That's it.
262 void Session::greeting()
264 CHECK(state_
== xact_step::helo
);
266 if (sock_
.has_peername()) {
267 close(2); // if we're a networked program, never send to stderr
269 std::string error_msg
;
270 if (!verify_ip_address_(error_msg
)) {
271 // no glog message at this point
272 bad_host_(error_msg
.c_str());
275 /******************************************************************
276 <https://tools.ietf.org/html/rfc5321#section-4.3.1> says:
278 4.3. Sequencing of Commands and Replies
280 4.3.1. Sequencing Overview
282 The communication between the sender and receiver is an alternating
283 dialogue, controlled by the sender. As such, the sender issues a
284 command and the receiver responds with a reply. Unless other
285 arrangements are negotiated through service extensions, the sender
286 MUST wait for this response before sending further commands. One
287 important reply is the connection greeting. Normally, a receiver
288 will send a 220 "Service ready" reply when the connection is
289 completed. The sender SHOULD wait for this greeting message before
290 sending any commands.
294 “…the receiver responds with a reply.”
295 “…the sender MUST wait for this response…”
296 “One important reply is the connection greeting.”
297 “The sender SHOULD wait for this greeting…”
299 So is it MUST or SHOULD? I enforce MUST.
300 *******************************************************************/
302 // Wait a bit of time for pre-greeting traffic.
303 if (!(ip_allowed_
|| fcrdns_allowed_
)) {
304 if (sock_
.input_ready(Config::greeting_wait
)) {
305 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
306 // no glog message at this point
307 bad_host_("input before any greeting");
309 // Give a half greeting and wait again.
310 out_() << "220-" << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
311 if (sock_
.input_ready(Config::greeting_wait
)) {
312 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush
;
313 // LOG(INFO) << "half greeting got " << client_;
314 bad_host_("input before full greeting");
317 LOG(INFO
) << "connect from " << client_
;
320 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush
;
322 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
323 alarm(2 * 60); // initial alarm
327 void Session::flush() { out_() << std::flush
; }
329 void Session::last_in_group_(std::string_view verb
)
331 if (sock_
.input_ready(std::chrono::seconds(0))) {
332 LOG(WARNING
) << "pipelining error; input ready processing " << verb
;
336 void Session::lo_(char const* verb
, std::string_view client_identity
)
338 last_in_group_(verb
);
341 if (client_identity_
!= client_identity
) {
342 client_identity_
= client_identity
;
344 std::string error_msg
;
345 if (!verify_client_(client_identity_
, error_msg
)) {
346 // no glog message at this point
347 bad_host_(error_msg
.c_str());
352 out_() << "250 " << server_id_() << "\r\n";
358 if (sock_
.has_peername()) {
359 out_() << "250-" << server_id_() << " at your service, " << client_
363 out_() << "250-" << server_id_() << "\r\n";
366 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
367 out_() << "250-8BITMIME\r\n"; // RFC 6152
370 out_() << "250-RRVS\r\n"; // RFC 7293
373 // out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
376 // Check sasl sources for auth types.
377 // out_() << "250-AUTH PLAIN\r\n";
378 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
381 // If we're not already TLS, offer TLS
382 out_() << "250-STARTTLS\r\n"; // RFC 3207
384 out_() << "250-ENHANCEDSTATUSCODES\r\n" // RFC 2034
385 "250-PIPELINING\r\n" // RFC 2920
386 //-----------------------------------------------
387 // Disable this right now, nobody uses it anyhow,
388 // but this might break DKIM signing for relay.
389 // "250-BINARYMIME\r\n" // RFC 3030
390 //-----------------------------------------------
391 "250-CHUNKING\r\n" // RFC 3030
392 "250 SMTPUTF8\r\n"; // RFC 6531
395 out_() << std::flush
;
397 if (sock_
.has_peername()) {
398 if (std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
399 client_identity_
) != end(client_fcrdns_
)) {
400 LOG(INFO
) << verb
<< " " << client_identity
<< " from "
401 << sock_
.them_address_literal();
404 LOG(INFO
) << verb
<< " " << client_identity
<< " from " << client_
;
408 LOG(INFO
) << verb
<< " " << client_identity
;
412 void Session::mail_from(Mailbox
&& reverse_path
, parameters_t
const& parameters
)
415 case xact_step::helo
:
416 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
417 LOG(WARNING
) << "'MAIL FROM' before HELO/EHLO"
418 << (sock_
.has_peername() ? " from " : "") << client_
;
420 case xact_step::mail
: break;
421 case xact_step::rcpt
:
422 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
423 LOG(WARNING
) << "nested MAIL command"
424 << (sock_
.has_peername() ? " from " : "") << client_
;
426 case xact_step::data
:
427 case xact_step::bdat
:
428 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush
;
429 LOG(WARNING
) << "nested MAIL command"
430 << (sock_
.has_peername() ? " from " : "") << client_
;
432 case xact_step::rset
:
433 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
434 LOG(WARNING
) << "error state must be cleared with a RSET"
435 << (sock_
.has_peername() ? " from " : "") << client_
;
439 if (!verify_from_params_(parameters
)) {
443 std::string error_msg
;
444 if (!verify_sender_(reverse_path
, error_msg
)) {
445 LOG(WARNING
) << "verify sender failed: " << error_msg
;
446 bad_host_(error_msg
.c_str());
449 reverse_path_
= std::move(reverse_path
);
452 forward_path_
.clear();
453 out_() << "250 2.1.0 MAIL FROM OK\r\n";
454 // No flush RFC-2920 section 3.1, this could be part of a command group.
456 fmt::memory_buffer params
;
457 for (auto const& [name
, value
] : parameters
) {
458 fmt::format_to(params
, " {}", name
);
459 if (!value
.empty()) {
460 fmt::format_to(params
, "={}", value
);
463 LOG(INFO
) << "MAIL FROM:<" << reverse_path_
<< ">" << fmt::to_string(params
);
465 state_
= xact_step::rcpt
;
468 bool Session::forward_to_(std::string
const& forward
, Mailbox
const& rcpt_to
)
470 // If we're already forwarding or replying, reject
471 if (!fwd_path_
.empty() || !rep_info_
.empty()) {
472 out_() << "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n"
474 LOG(WARNING
) << "failed to forward to <" << forward
475 << "> already forwarding or replying for: " << rcpt_to
;
479 fwd_path_
= Mailbox(forward
);
482 // New bounce address
483 SRS0::from_to bounce
;
484 bounce
.mail_from
= reverse_path_
.as_string(Mailbox::domain_encoding::ascii
);
486 auto const new_bounce
= srs_
.enc_bounce(bounce
, server_id_().c_str());
488 auto const mail_from
= Mailbox(new_bounce
);
490 std::string error_msg
;
491 if (!send_
.mail_from_rcpt_to(res_
, mail_from
, fwd_path_
, error_msg
)) {
492 out_() << error_msg
<< std::flush
;
493 LOG(WARNING
) << "failed to forward <" << fwd_path_
<< "> " << error_msg
;
497 LOG(INFO
) << "RCPT TO:<" << rcpt_to
<< "> forwarding to == <" << fwd_path_
502 bool Session::reply_to_(SRS0::from_to
const& reply_info
, Mailbox
const& rcpt_to
)
504 // If we're already forwarding or replying, reject
505 if (!fwd_path_
.empty() || !rep_info_
.empty()) {
506 out_() << "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n"
508 LOG(WARNING
) << "failed to reply to <" << reply_info
.mail_from
509 << "> already forwarding or replying for: " << rcpt_to
;
513 rep_info_
= reply_info
;
515 Mailbox
const from(rep_info_
.rcpt_to_local_part
, server_identity_
);
516 Mailbox
const to(rep_info_
.mail_from
);
518 std::string error_msg
;
519 if (!send_
.mail_from_rcpt_to(res_
, from
, to
, error_msg
)) {
520 out_() << error_msg
<< std::flush
;
521 LOG(WARNING
) << "failed to reply from <" << from
<< "> to <" << to
<< "> "
526 LOG(INFO
) << "RCPT TO:<" << rcpt_to
<< "> is a reply to "
527 << rep_info_
.mail_from
<< " from " << rep_info_
.rcpt_to_local_part
;
531 void Session::rcpt_to(Mailbox
&& forward_path
, parameters_t
const& parameters
)
534 case xact_step::helo
:
535 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
536 LOG(WARNING
) << "'RCPT TO' before HELO/EHLO"
537 << (sock_
.has_peername() ? " from " : "") << client_
;
539 case xact_step::mail
:
540 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
541 LOG(WARNING
) << "'RCPT TO' before 'MAIL FROM'"
542 << (sock_
.has_peername() ? " from " : "") << client_
;
544 case xact_step::rcpt
:
545 case xact_step::data
: break;
546 case xact_step::bdat
:
547 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
548 LOG(WARNING
) << "'RCPT TO' during BDAT transfer"
549 << (sock_
.has_peername() ? " from " : "") << client_
;
551 case xact_step::rset
:
552 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
553 LOG(WARNING
) << "error state must be cleared with a RSET"
554 << (sock_
.has_peername() ? " from " : "") << client_
;
558 if (!verify_rcpt_params_(parameters
))
561 if (!verify_recipient_(forward_path
))
564 if (forward_path_
.size() >= Config::max_recipients_per_message
) {
565 out_() << "452 4.5.3 too many recipients\r\n" << std::flush
;
566 LOG(WARNING
) << "too many recipients <" << forward_path
<< ">";
569 // no check for dups, postfix doesn't
570 forward_path_
.emplace_back(std::move(forward_path
));
572 Mailbox
const& rcpt_to_mbx
= forward_path_
.back();
574 auto const rcpt_to_str
=
575 rcpt_to_mbx
.as_string(Mailbox::domain_encoding::ascii
);
577 if (auto reply
= srs_
.dec_reply(rcpt_to_mbx
.local_part()); reply
) {
578 if (!reply_to_(*reply
, rcpt_to_mbx
))
581 else if (auto const forward
= forward_
.find(rcpt_to_str
.c_str()); forward
) {
582 if (!forward_to_(*forward
, rcpt_to_mbx
))
586 LOG(INFO
) << "RCPT TO:<" << rcpt_to_str
<< ">";
589 // No flush RFC-2920 section 3.1, this could be part of a command group.
590 out_() << "250 2.1.5 RCPT TO OK\r\n";
592 state_
= xact_step::data
;
595 // The headers Received and Received-SPF are returned as a string.
597 std::string
Session::added_headers_(MessageStore
const& msg
)
599 auto const protocol
{[this]() {
601 return sock_
.tls() ? "UTF8SMTPS" : "UTF8SMTP";
602 else if (extensions_
)
603 return sock_
.tls() ? "ESMTPS" : "ESMTP";
605 return sock_
.tls() ? "SMTPS" : "SMTP";
608 fmt::memory_buffer headers
;
610 // <https://tools.ietf.org/html/rfc5321#section-4.4>
611 fmt::format_to(headers
, "Received: from {}", client_identity_
.utf8());
612 if (sock_
.has_peername()) {
613 fmt::format_to(headers
, " ({})", client_
);
615 fmt::format_to(headers
, "\r\n\tby {} with {} id {}", server_identity_
.utf8(),
617 if (forward_path_
.size()) {
618 fmt::format_to(headers
, "\r\n\tfor <{}>", forward_path_
[0]);
619 for (auto i
= 1u; i
< forward_path_
.size(); ++i
)
620 fmt::format_to(headers
, ",\r\n\t <{}>", forward_path_
[i
]);
622 std::string
const tls_info
{sock_
.tls_info()};
623 if (tls_info
.length()) {
624 fmt::format_to(headers
, "\r\n\t({})", tls_info
);
626 fmt::format_to(headers
, ";\r\n\t{}\r\n", msg
.when());
629 if (!spf_received_
.empty()) {
630 fmt::format_to(headers
, "{}\r\n", spf_received_
);
633 return fmt::to_string(headers
);
637 bool lookup_domain(CDB
& cdb
, Domain
const& domain
)
639 if (!domain
.empty()) {
640 if (cdb
.contains(domain
.ascii())) {
643 if (domain
.is_unicode() && cdb
.contains(domain
.utf8())) {
651 std::tuple
<Session::SpamStatus
, std::string
> Session::spam_status_()
653 if (spf_result_
== SPF::Result::FAIL
&& !ip_allowed_
)
654 return {SpamStatus::spam
, "SPF failed"};
656 // These should have already been rejected by verify_client_().
657 if ((reverse_path_
.domain() == "localhost.local") ||
658 (reverse_path_
.domain() == "localhost"))
659 return {SpamStatus::spam
, "bogus reverse_path"};
661 std::vector
<std::string
> why_ham
;
663 // Anything enciphered tastes a lot like ham.
665 why_ham
.emplace_back("they used TLS");
667 if (spf_result_
== SPF::Result::PASS
) {
668 if (lookup_domain(allow_
, spf_sender_domain_
)) {
669 why_ham
.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
670 spf_sender_domain_
.utf8()));
673 auto tld_dom
{tld_db_
.get_registered_domain(spf_sender_domain_
.ascii())};
674 if (tld_dom
&& allow_
.contains(tld_dom
)) {
675 why_ham
.emplace_back(fmt::format(
676 "SPF sender registered domain ({}) is allowed", tld_dom
));
682 why_ham
.emplace_back(
683 fmt::format("FCrDNS (or it's registered domain) is allowed"));
685 if (!why_ham
.empty())
686 return {SpamStatus::ham
,
687 fmt::format("{}", fmt::join(std::begin(why_ham
), std::end(why_ham
),
690 return {SpamStatus::spam
, "it's not ham"};
693 static std::string
folder(Session::SpamStatus status
,
694 std::vector
<Mailbox
> const& forward_path
,
695 Mailbox
const& reverse_path
)
697 if (status
== Session::SpamStatus::spam
)
703 bool Session::msg_new()
705 CHECK((state_
== xact_step::data
) || (state_
== xact_step::bdat
));
707 auto const& [status
, reason
]{spam_status_()};
709 LOG(INFO
) << ((status
== SpamStatus::ham
) ? "ham since " : "spam since ")
712 // All sources of ham get a fresh 5 minute timeout per message.
713 if (status
== SpamStatus::ham
) {
714 if ((!FLAGS_immortal
) && (getenv("GHSMTP_IMMORTAL") == nullptr))
718 msg_
= std::make_unique
<MessageStore
>();
720 if (!FLAGS_max_write
)
721 FLAGS_max_write
= max_msg_size();
724 msg_
->open(server_id_(), FLAGS_max_write
,
725 folder(status
, forward_path_
, reverse_path_
));
726 auto const hdrs
{added_headers_(*(msg_
.get()))};
729 // fmt::memory_buffer spam_status;
730 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
731 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
732 // msg_->write(spam_status.data(), spam_status.size());
734 LOG(INFO
) << "Spam-Status: "
735 << ((status
== SpamStatus::spam
) ? "Yes" : "No") << ", "
740 catch (std::system_error
const& e
) {
743 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
744 LOG(ERROR
) << "no space";
750 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
751 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
752 LOG(ERROR
) << e
.what();
758 catch (std::exception
const& e
) {
759 out_() << "550 5.0.0 mail error\r\n" << std::flush
;
760 LOG(ERROR
) << e
.what();
766 // out_() << "550 5.0.0 mail error\r\n" << std::flush;
767 // LOG(ERROR) << "msg_new failed with no exception thrown";
773 bool Session::msg_write(char const* s
, std::streamsize count
)
775 if ((state_
!= xact_step::data
) && (state_
!= xact_step::bdat
))
782 if (msg_
->write(s
, count
))
785 catch (std::system_error
const& e
) {
788 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
789 LOG(ERROR
) << "no space";
795 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
796 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
797 LOG(ERROR
) << e
.what();
803 catch (std::exception
const& e
) {
804 out_() << "550 5.0.0 mail error\r\n" << std::flush
;
805 LOG(ERROR
) << e
.what();
811 out_() << "550 5.0.0 mail error\r\n" << std::flush
;
812 LOG(ERROR
) << "write failed with no exception thrown";
818 bool Session::data_start()
820 last_in_group_("DATA");
823 case xact_step::helo
:
824 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
825 LOG(WARNING
) << "'DATA' before HELO/EHLO"
826 << (sock_
.has_peername() ? " from " : "") << client_
;
828 case xact_step::mail
:
829 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
830 LOG(WARNING
) << "'DATA' before 'MAIL FROM'"
831 << (sock_
.has_peername() ? " from " : "") << client_
;
833 case xact_step::rcpt
:
835 /******************************************************************
836 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
838 The DATA command can fail at only two points in the protocol exchange:
840 If there was no MAIL, or no RCPT, command, or all such commands were
841 rejected, the server MAY return a "command out of sequence" (503) or
842 "no valid recipients" (554) reply in response to the DATA command.
844 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
846 The additional restriction is that when there have been no successful
847 RCPT commands in the mail transaction, the DATA command MUST fail
848 with a 503 reply code.
850 Therefore I will send the reply code that is valid for both, and
851 do the same for the BDAT case.
852 *******************************************************************/
854 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
855 LOG(WARNING
) << "no valid recipients"
856 << (sock_
.has_peername() ? " from " : "") << client_
;
858 case xact_step::data
: break;
859 case xact_step::bdat
:
860 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush
;
861 LOG(WARNING
) << "'DATA' during BDAT transfer"
862 << (sock_
.has_peername() ? " from " : "") << client_
;
864 case xact_step::rset
:
865 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
866 LOG(WARNING
) << "error state must be cleared with a RSET"
867 << (sock_
.has_peername() ? " from " : "") << client_
;
872 out_() << "503 5.5.1 DATA does not support BINARYMIME\r\n" << std::flush
;
873 LOG(WARNING
) << "DATA does not support BINARYMIME";
874 state_
= xact_step::rset
; // RFC 3030 section 3 page 5
879 LOG(ERROR
) << "msg_new() failed";
883 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush
;
888 bool Session::do_forward_(message::parsed
& msg
)
892 // Generate a reply address
894 reply
.mail_from
= msg_fwd
.dmarc_from
;
895 reply
.rcpt_to_local_part
= fwd_from_
.local_part();
897 auto const reply_addr
=
898 fmt::format("{}@{}", srs_
.enc_reply(reply
), server_id_());
900 auto const munging
= false;
902 auto const sender
= server_identity_
.ascii().c_str();
903 auto const selector
= FLAGS_selector
.c_str();
904 auto const key_file
=
905 (config_path_
/ FLAGS_selector
).replace_extension("private");
906 CHECK(fs::exists(key_file
)) << "can't find key file " << key_file
;
909 auto const from_hdr
=
910 fmt::format("From: \"{} via\" <@>", msg_fwd
.dmarc_from
, reply_addr
);
911 message::rewrite(msg_fwd
, from_hdr
, "", sender
, selector
, key_file
);
914 auto const reply_to_hdr
= fmt::format("Reply-To: {}", reply_addr
);
915 message::rewrite(msg_fwd
, "", reply_to_hdr
, sender
, selector
, key_file
);
919 if (!send_
.send(msg_fwd
.as_string())) {
920 out_() << "432 4.3.0 Recipient's incoming mail queue has been "
924 LOG(ERROR
) << "failed to send for " << fwd_path_
;
928 LOG(INFO
) << "successfully sent for " << fwd_path_
;
932 bool Session::do_reply_(message::parsed
& msg
)
934 Mailbox
to_mbx(rep_info_
.mail_from
);
935 Mailbox
from_mbx(rep_info_
.rcpt_to_local_part
, server_identity_
);
937 auto reply
= std::make_unique
<MessageStore
>();
938 reply
->open(server_id_(), FLAGS_max_write
, ".Drafts");
940 auto const date
{Now
{}};
941 auto const pill
{Pill
{}};
943 fmt::format("<{}.{}@{}>", date
.sec(), pill
, server_identity_
);
945 fmt::memory_buffer bfr
;
947 fmt::format_to(bfr
, "From: <{}>\r\n", from_mbx
);
948 fmt::format_to(bfr
, "To: <{}>\r\n", to_mbx
);
950 fmt::format_to(bfr
, "Date: {}\r\n", date
.c_str());
952 fmt::format_to(bfr
, "Message-ID: {}\r\n", mid_str
.c_str());
954 if (!msg
.get_header(message::Subject
).empty()) {
955 fmt::format_to(bfr
, "{}: {}\r\n", message::Subject
,
956 msg
.get_header(message::Subject
));
959 fmt::format_to(bfr
, "{}: {}\r\n", message::Subject
,
960 "Reply to your message");
963 if (!msg
.get_header(message::In_Reply_To
).empty()) {
964 fmt::format_to(bfr
, "{}: {}\r\n", message::In_Reply_To
,
965 msg
.get_header(message::In_Reply_To
));
968 if (!msg
.get_header(message::MIME_Version
).empty() &&
969 msg
.get_header(message::Content_Type
).empty()) {
970 fmt::format_to(bfr
, "{}: {}\r\n", message::MIME_Version
,
971 msg
.get_header(message::MIME_Version
));
972 fmt::format_to(bfr
, "{}: {}\r\n", message::Content_Type
,
973 msg
.get_header(message::Content_Type
));
976 reply
->write(fmt::to_string(bfr
));
978 if (!msg
.body
.empty()) {
979 reply
->write("\r\n");
980 reply
->write(msg
.body
);
983 auto const msg_data
= reply
->freeze();
984 message::parsed msg_reply
;
985 CHECK(msg_reply
.parse(msg_data
));
987 auto const sender
= server_identity_
.ascii().c_str();
988 auto const selector
= FLAGS_selector
.c_str();
989 auto const key_file
=
990 (config_path_
/ FLAGS_selector
).replace_extension("private");
991 CHECK(fs::exists(key_file
)) << "can't find key file " << key_file
;
993 message::dkim_sign(msg_reply
, sender
, selector
, key_file
);
995 if (!send_
.send(msg_reply
.as_string())) {
996 out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1000 LOG(ERROR
) << "send failed for reply to " << to_mbx
<< " from " << from_mbx
;
1004 LOG(INFO
) << "successful reply to " << to_mbx
<< " from " << from_mbx
;
1008 bool Session::do_deliver_()
1012 auto const sender
= server_identity_
.ascii().c_str();
1013 auto const selector
= FLAGS_selector
.c_str();
1014 auto const key_file
=
1015 (config_path_
/ FLAGS_selector
).replace_extension("private");
1016 CHECK(fs::exists(key_file
)) << "can't find key file " << key_file
;
1019 auto const msg_data
= msg_
->freeze();
1021 message::parsed msg
;
1023 // Only deal in RFC-5322 Mail Objects.
1024 bool const message_parsed
= msg
.parse(msg_data
);
1025 if (message_parsed
) {
1027 // remove any Return-Path
1028 message::remove_delivery_headers(msg
);
1030 auto const authentic
=
1032 message::authentication(msg
, sender
, selector
, key_file
);
1034 // write a new Return-Path
1035 msg_
->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_
));
1037 for (auto const h
: msg
.headers
) {
1038 msg_
->write(h
.as_string());
1039 msg_
->write("\r\n");
1041 if (!msg
.body
.empty()) {
1042 msg_
->write("\r\n");
1043 msg_
->write(msg
.body
);
1048 if (authentic
&& !fwd_path_
.empty()) {
1049 if (!do_forward_(msg
))
1052 if (authentic
&& !rep_info_
.empty()) {
1053 if (!do_reply_(msg
))
1060 catch (std::system_error
const& e
) {
1063 out_() << "452 4.3.1 mail system full\r\n" << std::flush
;
1064 LOG(ERROR
) << "no space";
1070 out_() << "550 5.0.0 mail system error\r\n" << std::flush
;
1072 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
1073 LOG(ERROR
) << e
.what();
1083 void Session::data_done()
1085 CHECK((state_
== xact_step::data
));
1087 if (msg_
&& msg_
->size_error()) {
1095 // out_() << "353\r\n";
1096 // for (auto fp : forward_path_) {
1097 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1102 using namespace boost::xpressive
;
1105 sregex
const rex
= icase("wait-data-") >> (secs_
= +_d
);
1108 for (auto fp
: forward_path_
) {
1109 if (regex_match(fp
.local_part(), what
, rex
)) {
1110 auto const str
= what
[secs_
].str();
1111 LOG(INFO
) << "waiting at DATA " << str
<< " seconds";
1113 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
1115 LOG(INFO
) << "done waiting";
1120 out_() << "250 2.0.0 DATA OK\r\n" << std::flush
;
1121 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1127 void Session::data_size_error()
1129 out_().clear(); // clear possible eof from input side
1130 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1134 LOG(WARNING
) << "DATA size error";
1138 void Session::data_error()
1140 out_().clear(); // clear possible eof from input side
1141 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush
;
1145 LOG(WARNING
) << "DATA error";
1149 bool Session::bdat_start(size_t n
)
1152 case xact_step::helo
:
1153 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush
;
1154 LOG(WARNING
) << "'BDAT' before HELO/EHLO"
1155 << (sock_
.has_peername() ? " from " : "") << client_
;
1157 case xact_step::mail
:
1158 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush
;
1159 LOG(WARNING
) << "'BDAT' before 'MAIL FROM'"
1160 << (sock_
.has_peername() ? " from " : "") << client_
;
1162 case xact_step::rcpt
:
1163 // See comment in data_start()
1164 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush
;
1165 LOG(WARNING
) << "no valid recipients"
1166 << (sock_
.has_peername() ? " from " : "") << client_
;
1168 case xact_step::data
: // first bdat
1170 case xact_step::bdat
: return true;
1171 case xact_step::rset
:
1172 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush
;
1173 LOG(WARNING
) << "error state must be cleared with a RSET"
1174 << (sock_
.has_peername() ? " from " : "") << client_
;
1178 state_
= xact_step::bdat
;
1183 void Session::bdat_done(size_t n
, bool last
)
1185 if (state_
!= xact_step::bdat
) {
1194 if (msg_
->size_error()) {
1200 out_() << "250 2.0.0 BDAT " << n
<< " OK\r\n" << std::flush
;
1201 LOG(INFO
) << "BDAT " << n
;
1207 out_() << "250 2.0.0 BDAT " << n
<< " LAST OK\r\n" << std::flush
;
1209 LOG(INFO
) << "BDAT " << n
<< " LAST";
1210 LOG(INFO
) << "message delivered, " << msg_
->size() << " octets, with id "
1215 void Session::bdat_size_error()
1217 out_().clear(); // clear possible eof from input side
1218 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1222 LOG(WARNING
) << "BDAT size error";
1226 void Session::bdat_seq_error()
1228 out_().clear(); // clear possible eof from input side
1229 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush
;
1233 LOG(WARNING
) << "BDAT sequence error";
1237 void Session::bdat_io_error()
1239 out_().clear(); // clear possible eof from input side
1240 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush
;
1244 LOG(WARNING
) << "BDAT I/O error";
1248 void Session::rset()
1250 out_() << "250 2.1.5 RSET OK\r\n";
1251 // No flush RFC-2920 section 3.1, this could be part of a command group.
1252 LOG(INFO
) << "RSET";
1256 void Session::noop(std::string_view str
)
1258 last_in_group_("NOOP");
1259 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush
;
1260 LOG(INFO
) << "NOOP" << (str
.length() ? " " : "") << str
;
1263 void Session::vrfy(std::string_view str
)
1265 last_in_group_("VRFY");
1266 out_() << "252 2.1.5 try it\r\n" << std::flush
;
1267 LOG(INFO
) << "VRFY" << (str
.length() ? " " : "") << str
;
1270 void Session::help(std::string_view str
)
1272 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n" << std::flush
;
1273 LOG(INFO
) << "HELP" << (str
.length() ? " " : "") << str
;
1276 void Session::quit()
1279 // last_in_group_("QUIT");
1280 out_() << "221 2.0.0 closing connection\r\n" << std::flush
;
1281 LOG(INFO
) << "QUIT";
1285 void Session::auth()
1287 out_() << "454 4.7.0 authentication failure\r\n" << std::flush
;
1288 LOG(INFO
) << "AUTH";
1292 void Session::error(std::string_view log_msg
)
1294 out_() << "421 4.3.5 system error: " << log_msg
<< "\r\n" << std::flush
;
1295 LOG(WARNING
) << log_msg
;
1298 void Session::cmd_unrecognized(std::string_view cmd
)
1300 auto const escaped
{esc(cmd
)};
1301 LOG(WARNING
) << "command unrecognized: \"" << escaped
<< "\"";
1303 if (++n_unrecognized_cmds_
>= Config::max_unrecognized_cmds
) {
1304 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1305 << "\" exceeds limit\r\n"
1307 LOG(WARNING
) << n_unrecognized_cmds_
1308 << " unrecognized commands is too many";
1312 out_() << "500 5.5.1 command unrecognized: \"" << escaped
<< "\"\r\n"
1316 void Session::bare_lf()
1318 // Error code used by Office 365.
1319 out_() << "554 5.6.11 bare LF\r\n" << std::flush
;
1320 LOG(WARNING
) << "bare LF";
1324 void Session::max_out()
1326 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1327 LOG(WARNING
) << "message size maxed out";
1331 void Session::time_out()
1333 out_() << "421 4.4.2 time-out\r\n" << std::flush
;
1334 LOG(WARNING
) << "time-out" << (sock_
.has_peername() ? " from " : "")
1339 void Session::starttls()
1341 last_in_group_("STARTTLS");
1343 out_() << "554 5.5.1 TLS already active\r\n" << std::flush
;
1344 LOG(WARNING
) << "STARTTLS issued with TLS already active";
1347 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush
;
1348 if (sock_
.starttls_server(config_path_
)) {
1350 max_msg_size(Config::max_msg_size_bro
);
1351 LOG(INFO
) << "STARTTLS " << sock_
.tls_info();
1356 void Session::exit_()
1358 // sock_.log_totals();
1360 timespec time_used
{};
1361 clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &time_used
);
1363 LOG(INFO
) << "CPU time " << time_used
.tv_sec
<< "." << std::setw(9)
1364 << std::setfill('0') << time_used
.tv_nsec
<< " seconds";
1366 std::exit(EXIT_SUCCESS
);
1370 bool ip4_allowed(char const* addr
)
1375 char const* comment
;
1389 nw
const networks
[]{
1390 // the one very special case
1391 {"108.83.36.112", "255.255.255.248", "108.83.36.112/29"},
1393 // accept from major providers:
1394 {"3.0.0.0", "255.0.0.0", "3.0.0.0/9 and 3.128.0.0/9 Amazon"},
1395 {"5.45.198.0", "255.255.254.0", "5.45.198.0/23 YANDEX-5-45-198"},
1396 {"12.153.224.0", "255.255.255.0", "12.153.224.0/24 E-TRADE10-224"},
1397 {"17.0.0.0", "255.0.0.0", "17.0.0.0/8 APPLE-WWNET"},
1398 {"40.74.0.0", "255.254.0.0", "40.74.0.0/15 MSFT NET-40-74-0-0-1"},
1399 {"40.76.0.0", "255.252.0.0", "40.76.0.0/14 MSFT NET-40-74-0-0-1"},
1400 {"40.80.0.0", "255.240.0.0", "40.80.0.0/12 MSFT NET-40-74-0-0-1"},
1401 {"40.96.0.0", "255.240.0.0", "40.96.0.0/12 MSFT NET-40-74-0-0-1"},
1402 {"40.112.0.0", "255.248.0.0", "40.112.0.0/13 MSFT NET-40-74-0-0-1"},
1403 {"40.120.0.0", "255.252.0.0", "40.120.0.0/14 MSFT NET-40-74-0-0-1"},
1404 {"40.124.0.0", "255.255.0.0", "40.124.0.0/16 MSFT NET-40-74-0-0-1"},
1405 {"40.125.0.0", "255.255.128.0", "40.125.0.0/17 MSFT NET-40-74-0-0-1"},
1406 {"56.0.0.0", "255.0.0.0", "56.0.0.0/8 USPS1"},
1407 {"65.52.0.0", "255.252.0.0", "65.52.0.0/14 MICROSOFT-1BLK"},
1408 {"66.163.160.0", "255.255.224.0", "66.163.160.0/19 A-YAHOO-US2"},
1409 {"66.211.176.0", "255.255.240.0", "66.211.176.0/20 EBAY-2"},
1410 {"66.211.172.0", "255.255.252.0", "66.211.172.0/22 EBAY-2"},
1411 {"66.220.144.0", "255.255.240.0", "66.220.144.0/20 TFBNET3"},
1412 {"68.232.192.0", "255.255.240.0", "68.232.192.0/20 EXACT-IP-NET-2"},
1413 {"70.47.67.0", "255.255.255.0", "70.47.67.0/24 NET-462F4300-24"},
1414 {"74.6.0.0", "255.255.0.0", "INKTOMI-BLK-6 Oath"},
1415 {"74.125.0.0", "255.255.0.0", "74.125.0.0/16 GOOGLE"},
1416 {"75.101.100.43", "255.255.255.255", "new.toad.com"},
1417 {"76.178.68.57", "255.255.255.255", "cpe-76-178-68-57.natsow.res.rr.com"},
1418 {"98.136.0.0", "255.252.0.0", "98.136.0.0/14 A-YAHOO-US9"},
1419 {"104.40.0.0", "255.248.0.0", "104.40.0.0/13 MSFT"},
1420 {"108.174.0.0", "255.255.240.0", "108.174.0.0/20 LINKEDIN"},
1421 {"159.45.0.0", "255.255.0.0", "159.45.0.0/16 AGE-COM"},
1422 {"159.53.0.0", "255.255.0.0", "159.53.0.0/16 JMC"},
1423 {"159.135.224.0", "255.255.240.0", "159.135.224.0/20 MNO87-159-135-224-0-0"},
1424 {"162.247.72.0", "255.255.252.0", "162.247.72.0/22 CALYX-INSTITUTE-V4-1"},
1425 {"165.107.0.0", "255.255.0.0", "NET-LDC-CA-GOV"},
1426 {"192.175.128.0", "255.255.128.0", "192.175.128.0/17 NETBLK-VANGUARD"},
1427 {"198.2.128.0", "255.255.192.0", "198.2.128.0/18 RSG-DELIVERY"},
1428 {"198.252.206.0", "255.255.255.0", "198.252.206.0/24 SE-NET01"},
1429 {"199.122.120.0", "255.255.248.0", "199.122.120.0/21 EXACT-IP-NET-3"},
1430 {"204.13.164.0", "255.255.255.0", "204.13.164.0/24 RISEUP-NETWORKS-SWIFT-BLOCK2"},
1431 {"204.29.186.0", "255.255.254.0", "204.29.186.0/23 ATDN-NSCAPE"},
1432 {"205.139.104.0", "255.255.252.0", "205.139.104.0/22 SAVV-S259964-8"},
1433 {"205.201.128.0", "255.255.240.0", "205.201.128.0/20 RSG-DELIVERY"},
1434 {"208.118.235.0", "255.255.255.0", "208.118.235.0/24 TWDX-208-118-235-0-1"},
1435 {"208.192.0.0", "255.192.0.0", "208.192.0.0/10 UUNET1996B"},
1436 {"209.51.188.0", "255.255.255.0", "I:NET-209.51.188.0/24 FSF"},
1437 {"209.85.128.0", "255.255.128.0", "209.85.128.0/17 GOOGLE"},
1438 {"209.132.176.0", "255.255.240.0", "209.132.176.0/20 RED-HAT-BLK"},
1439 {"209.237.224.0", "255.255.224.0", "UNITEDLAYER-1"},
1440 // {"209.237.225.253", "255.255.255.255", "Old new.toad.com"},
1445 CHECK_EQ(inet_pton(AF_INET
, addr
, &addr32
), 1)
1446 << "can't interpret as IPv4 address";
1448 for (auto const& network
: networks
) {
1450 CHECK_EQ(inet_pton(AF_INET
, network
.net
, &net32
), 1)
1451 << "can't grok " << network
.net
;
1453 CHECK_EQ(inet_pton(AF_INET
, network
.mask
, &mask32
), 1)
1454 << "can't grok " << network
.mask
;
1456 // sanity check: all unmasked bits must be zero
1457 CHECK_EQ(net32
& (~mask32
), 0)
1458 << "bogus config net=" << network
.net
<< ", mask=" << network
.mask
;
1460 if (net32
== (addr32
& mask32
)) {
1461 LOG(INFO
) << addr
<< " allowed " << network
.comment
;
1470 /////////////////////////////////////////////////////////////////////////////
1472 // All of the verify_* functions send their own error messages back to
1473 // the client on failure, and return false.
1475 bool Session::verify_ip_address_(std::string
& error_msg
)
1477 auto ip_block_db_name
= config_path_
/ "ip-block";
1478 CDB ip_block
{ip_block_db_name
};
1479 if (ip_block
.contains(sock_
.them_c_str())) {
1481 fmt::format("IP address {} on static blocklist", sock_
.them_c_str());
1482 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1486 client_fcrdns_
.clear();
1488 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1489 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1490 LOG(INFO
) << "loopback address allowed";
1492 client_fcrdns_
.emplace_back("localhost");
1493 client_
= fmt::format("localhost {}", sock_
.them_address_literal());
1497 if (IP::is_private(sock_
.them_address_literal())) {
1498 LOG(INFO
) << "local address allowed";
1500 client_
= sock_
.them_address_literal();
1504 auto const fcrdns
= DNS::fcrdns(res_
, sock_
.them_c_str());
1505 for (auto const& fcr
: fcrdns
) {
1506 client_fcrdns_
.emplace_back(fcr
);
1509 if (!client_fcrdns_
.empty()) {
1510 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1511 sock_
.them_address_literal());
1513 for (auto const& client_fcrdns
: client_fcrdns_
) {
1514 if (allow_
.contains(client_fcrdns
.ascii())) {
1515 // LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1516 fcrdns_allowed_
= true;
1519 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1521 if (allow_
.contains(tld
)) {
1522 // LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1523 fcrdns_allowed_
= true;
1529 for (auto const& client_fcrdns
: client_fcrdns_
) {
1530 if (block_
.contains(client_fcrdns
.ascii())) {
1532 fmt::format("FCrDNS {} on static blocklist", client_fcrdns
.ascii());
1533 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1537 auto const tld
{tld_db_
.get_registered_domain(client_fcrdns
.ascii())};
1539 if (block_
.contains(tld
)) {
1540 error_msg
= fmt::format(
1541 "FCrDNS registered domain {} on static blocklist", tld
);
1542 out_() << "554 5.7.1 blocklisted\r\n" << std::flush
;
1549 client_
= fmt::format("unknown {}", sock_
.them_address_literal());
1552 if (IP4::is_address(sock_
.them_c_str())) {
1554 if (ip4_allowed(sock_
.them_c_str())) {
1555 LOG(INFO
) << "on internal allow list";
1560 auto const reversed
{IP4::reverse(sock_
.them_c_str())};
1562 // Check with allow list.
1563 std::shuffle(std::begin(Config::wls
), std::end(Config::wls
),
1566 for (auto wl
: Config::wls
) {
1567 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ wl
);
1568 if (q
.has_record()) {
1569 using namespace boost::xpressive
;
1571 auto const as
= q
.get_strings()[0];
1572 LOG(INFO
) << "on allow list " << wl
<< " as " << as
;
1576 sregex
const rex
= as_xpr("127.0.") >> (x_
= +_d
) >> '.' >> (y_
= +_d
);
1579 if (regex_match(as
, what
, rex
)) {
1580 auto const x
= what
[x_
].str();
1581 auto const y
= what
[y_
].str();
1584 std::from_chars(y
.data(), y
.data() + y
.size(), value
);
1587 LOG(INFO
) << "allowed";
1591 // Any A record skips check on block list
1596 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1597 std::shuffle(std::begin(Config::bls
), std::end(Config::bls
),
1600 for (auto bl
: Config::bls
) {
1602 DNS::Query
q(res_
, DNS::RR_type::A
, reversed
+ bl
);
1603 if (q
.has_record()) {
1604 auto const as
= q
.get_strings()[0];
1605 if (as
== "127.0.1.1") {
1606 LOG(INFO
) << "Query blocked by " << bl
;
1609 error_msg
= fmt::format("blocked on advice from {}", bl
);
1610 LOG(INFO
) << sock_
.them_c_str() << " " << error_msg
;
1611 out_() << "554 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1616 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1622 // check the identity from HELO/EHLO
1623 bool Session::verify_client_(Domain
const& client_identity
,
1624 std::string
& error_msg
)
1626 if (!client_fcrdns_
.empty()) {
1627 if (auto id
= std::find(begin(client_fcrdns_
), end(client_fcrdns_
),
1629 id
!= end(client_fcrdns_
)) {
1630 if (id
!= begin(client_fcrdns_
)) {
1631 std::rotate(begin(client_fcrdns_
), id
, id
+ 1);
1633 client_
= fmt::format("{} {}", client_fcrdns_
.front().ascii(),
1634 sock_
.them_address_literal());
1637 LOG(INFO
) << "claimed identity " << client_identity
1638 << " does NOT match any FCrDNS: ";
1639 for (auto const& client_fcrdns
: client_fcrdns_
) {
1640 LOG(INFO
) << " " << client_fcrdns
;
1644 // Bogus clients claim to be us or some local host.
1645 if (sock_
.has_peername() && ((client_identity
== server_identity_
) ||
1646 (client_identity
== "localhost") ||
1647 (client_identity
== "localhost.localdomain"))) {
1649 if ((sock_
.them_address_literal() == IP4::loopback_literal
) ||
1650 (sock_
.them_address_literal() == IP6::loopback_literal
)) {
1656 LOG(INFO
) << "allow-listed IP address can claim to be "
1661 // Ease up in test mode.
1662 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1666 error_msg
= fmt::format("liar, claimed to be {}", client_identity
.ascii());
1667 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1671 std::vector
<std::string
> labels
;
1672 boost::algorithm::split(labels
, client_identity
.ascii(),
1673 boost::algorithm::is_any_of("."));
1674 if (labels
.size() < 2) {
1676 fmt::format("claimed bogus identity {}", client_identity
.ascii());
1677 out_() << "550 4.7.1 bogus identity\r\n" << std::flush
;
1679 // // Sometimes we may want to look at mail from non conforming
1680 // // sending systems.
1681 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1682 // << client_ << " claiming " << client_identity;
1686 if (lookup_domain(block_
, client_identity
)) {
1688 fmt::format("claimed blocked identity {}", client_identity
.ascii());
1689 out_() << "550 4.7.1 blocked identity\r\n" << std::flush
;
1693 auto const tld
{tld_db_
.get_registered_domain(client_identity
.ascii())};
1695 // Sometimes we may want to look at mail from misconfigured
1697 // LOG(WARNING) << "claimed identity has no registered domain";
1700 else if (block_
.contains(tld
)) {
1702 fmt::format("claimed identity has blocked registered domain {}", tld
);
1703 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush
;
1707 // not otherwise objectionable
1711 // check sender from RFC5321 MAIL FROM:
1712 bool Session::verify_sender_(Mailbox
const& sender
, std::string
& error_msg
)
1714 std::string
const sender_str
{sender
};
1716 auto bad_senders_db_name
= config_path_
/ "bad_senders";
1717 CDB bad_senders
{bad_senders_db_name
}; // Addresses we don't accept mail from.
1718 if (bad_senders
.contains(sender_str
)) {
1719 error_msg
= fmt::format("{} bad sender", sender_str
);
1720 out_() << "501 5.1.8 " << error_msg
<< "\r\n" << std::flush
;
1724 // We don't accept mail /from/ a domain we are expecting to accept
1725 // mail for on an external network connection.
1727 if (sock_
.them_address_literal() != sock_
.us_address_literal()) {
1728 if ((accept_domains_
.is_open() &&
1729 (accept_domains_
.contains(sender
.domain().ascii()) ||
1730 accept_domains_
.contains(sender
.domain().utf8()))) ||
1731 (sender
.domain() == server_identity_
)) {
1733 // Ease up in test mode.
1734 if (FLAGS_test_mode
|| getenv("GHSMTP_TEST_MODE")) {
1737 out_() << "550 5.7.1 liar\r\n" << std::flush
;
1738 error_msg
= fmt::format("liar, claimed to be {}", sender
.domain());
1743 if (sender
.domain().is_address_literal()) {
1744 if (sender
.domain() != sock_
.them_address_literal()) {
1745 LOG(WARNING
) << "sender domain " << sender
.domain() << " does not match "
1746 << sock_
.them_address_literal();
1751 if (!verify_sender_domain_(sender
.domain(), error_msg
)) {
1755 if (!verify_sender_spf_(sender
)) {
1756 error_msg
= "failed SPF check";
1763 // this sender is the RFC5321 MAIL FROM: domain part
1764 bool Session::verify_sender_domain_(Domain
const& sender
,
1765 std::string
& error_msg
)
1767 if (sender
.empty()) {
1769 // is used to send bounce messages.
1773 if (allow_
.contains(sender
.ascii())) {
1774 LOG(INFO
) << "sender " << sender
.ascii() << " allowed";
1778 // Break sender domain into labels:
1780 std::vector
<std::string
> labels
;
1781 boost::algorithm::split(labels
, sender
.ascii(),
1782 boost::algorithm::is_any_of("."));
1784 if (labels
.size() < 2) { // This is not a valid domain.
1785 error_msg
= fmt::format("{} invalid syntax", sender
.ascii());
1786 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1790 auto const reg_dom
{tld_db_
.get_registered_domain(sender
.ascii())};
1792 error_msg
= fmt::format("{} has no registered domain", sender
.ascii());
1793 out_() << "550 5.7.1 " << error_msg
<< "\r\n" << std::flush
;
1796 if (allow_
.contains(reg_dom
)) {
1797 LOG(INFO
) << "sender registered domain \"" << reg_dom
<< "\" allowed";
1801 // LOG(INFO) << "looking up " << reg_dom;
1802 return verify_sender_domain_uribl_(reg_dom
, error_msg
);
1805 // check sender domain on dynamic URI block lists
1806 bool Session::verify_sender_domain_uribl_(std::string_view sender
,
1807 std::string
& error_msg
)
1809 if (!sock_
.has_peername()) // short circuit
1812 std::shuffle(std::begin(Config::uribls
), std::end(Config::uribls
),
1814 for (auto uribl
: Config::uribls
) {
1815 auto const lookup
= fmt::format("{}.{}", sender
, uribl
);
1816 auto as
= DNS::get_strings(res_
, DNS::RR_type::A
, lookup
);
1818 if (as
.front() == "127.0.0.1")
1820 error_msg
= fmt::format("{} blocked on advice of {}", sender
, uribl
);
1821 out_() << "550 5.7.1 sender " << error_msg
<< "\r\n" << std::flush
;
1826 // LOG(INFO) << sender << " cleared by URIBLs";
1830 bool Session::verify_sender_spf_(Mailbox
const& sender
)
1832 if (!sock_
.has_peername()) {
1833 auto const ip_addr
= "127.0.0.1"; // use localhost for local socket
1835 fmt::format("Received-SPF: pass ({}: allow-listed) client-ip={}; "
1836 "envelope-from={}; helo={};",
1837 server_id_(), ip_addr
, sender
, client_identity_
);
1838 spf_sender_domain_
= "localhost";
1842 auto const spf_srv
= SPF::Server
{server_id_().c_str()};
1843 auto spf_request
= SPF::Request
{spf_srv
};
1845 if (IP4::is_address(sock_
.them_c_str())) {
1846 spf_request
.set_ipv4_str(sock_
.them_c_str());
1848 else if (IP6::is_address(sock_
.them_c_str())) {
1849 spf_request
.set_ipv6_str(sock_
.them_c_str());
1852 LOG(FATAL
) << "bogus address " << sock_
.them_address_literal() << ", "
1853 << sock_
.them_c_str();
1856 spf_request
.set_helo_dom(client_identity_
.ascii().c_str());
1858 auto const from
{static_cast<std::string
>(sender
)};
1860 spf_request
.set_env_from(from
.c_str());
1862 auto const spf_res
{SPF::Response
{spf_request
}};
1863 spf_result_
= spf_res
.result();
1864 spf_received_
= spf_res
.received_spf();
1865 spf_sender_domain_
= spf_request
.get_sender_dom();
1867 if (spf_result_
== SPF::Result::PASS
) {
1868 if (lookup_domain(block_
, spf_sender_domain_
)) {
1869 LOG(INFO
) << "SPF sender domain (" << spf_sender_domain_
1875 if (spf_result_
== SPF::Result::FAIL
) {
1876 LOG(WARNING
) << spf_res
.header_comment();
1878 If we want to refuse mail that fails SPF.
1879 Error code from RFC 7372, section 3.2. Also:
1880 <https://www.iana.org/assignments/smtp-enhanced-status-codes/smtp-enhanced-status-codes.xhtml>
1882 out_() << "550 5.7.23 " << spf_res.smtp_comment() << "\r\n" << std::flush;
1887 LOG(INFO
) << spf_res
.header_comment();
1893 bool Session::verify_from_params_(parameters_t
const& parameters
)
1895 // Take a look at the optional parameters:
1896 for (auto const& [name
, value
] : parameters
) {
1897 if (iequal(name
, "BODY")) {
1898 if (iequal(value
, "8BITMIME")) {
1899 // everything is cool, this is our default...
1901 else if (iequal(value
, "7BIT")) {
1902 // nothing to see here, move along...
1904 else if (iequal(value
, "BINARYMIME")) {
1908 LOG(WARNING
) << "unrecognized BODY type \"" << value
<< "\" requested";
1911 else if (iequal(name
, "SMTPUTF8")) {
1912 if (!value
.empty()) {
1913 LOG(WARNING
) << "SMTPUTF8 parameter has a value: " << value
;
1918 // else if (iequal(name, "PRDR")) {
1919 // LOG(INFO) << "using PRDR";
1923 else if (iequal(name
, "SIZE")) {
1924 if (value
.empty()) {
1925 LOG(WARNING
) << "SIZE parameter has no value.";
1929 auto const sz
= stoull(value
);
1930 if (sz
> max_msg_size()) {
1931 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush
;
1932 LOG(WARNING
) << "SIZE parameter too large: " << sz
;
1936 catch (std::invalid_argument
const& e
) {
1937 LOG(WARNING
) << "SIZE parameter has invalid value: " << value
;
1939 catch (std::out_of_range
const& e
) {
1940 LOG(WARNING
) << "SIZE parameter has out-of-range value: " << value
;
1942 // I guess we just ignore bad size parameters.
1945 else if (iequal(name
, "REQUIRETLS")) {
1947 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush
;
1948 LOG(WARNING
) << "REQUIRETLS needed";
1953 LOG(WARNING
) << "unrecognized 'MAIL FROM' parameter " << name
<< "="
1961 bool Session::verify_rcpt_params_(parameters_t
const& parameters
)
1963 // Take a look at the optional parameters:
1964 for (auto const& [name
, value
] : parameters
) {
1965 if (iequal(name
, "RRVS")) {
1966 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
1967 LOG(INFO
) << name
<< "=" << value
;
1970 LOG(WARNING
) << "unrecognized 'RCPT TO' parameter " << name
<< "="
1978 // check recipient from RFC5321 RCPT TO:
1979 bool Session::verify_recipient_(Mailbox
const& recipient
)
1981 if ((recipient
.local_part() == "Postmaster") && (recipient
.domain() == "")) {
1982 LOG(INFO
) << "magic Postmaster address";
1986 auto const accepted_domain
{[this, &recipient
] {
1987 if (recipient
.domain().is_address_literal()) {
1988 if (recipient
.domain() != sock_
.us_address_literal()) {
1989 LOG(WARNING
) << "recipient.domain address " << recipient
.domain()
1990 << " does not match ours " << sock_
.us_address_literal();
1996 // Domains we accept mail for.
1997 if (accept_domains_
.is_open()) {
1998 if (accept_domains_
.contains(recipient
.domain().ascii()) ||
1999 accept_domains_
.contains(recipient
.domain().utf8())) {
2004 // If we have no list of domains to accept, at least take our own.
2005 if (recipient
.domain() == server_id_()) {
2013 if (!accepted_domain
) {
2014 out_() << "554 5.7.1 relay access denied\r\n" << std::flush
;
2015 LOG(WARNING
) << "relay access denied for domain " << recipient
.domain();
2019 // Check for local addresses we reject.
2021 auto bad_recipients_db_name
= config_path_
/ "bad_recipients";
2022 CDB bad_recipients_db
{bad_recipients_db_name
};
2023 if (bad_recipients_db
.contains(recipient
.local_part())) {
2024 out_() << "550 5.1.1 bad recipient " << recipient
<< "\r\n" << std::flush
;
2025 LOG(WARNING
) << "bad recipient " << recipient
;
2031 auto temp_fail_db_name
= config_path_
/ "temp_fail";
2032 CDB temp_fail
{temp_fail_db_name
}; // Addresses we make wait...
2033 if (temp_fail
.contains(recipient
.local_part())) {
2034 out_() << "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n"
2036 LOG(WARNING
) << "temp fail for recipient " << recipient
;
2041 // Check for and act on magic "wait" address.
2043 using namespace boost::xpressive
;
2046 sregex
const rex
= icase("wait-rcpt-") >> (secs_
= +_d
);
2049 if (regex_match(recipient
.local_part(), what
, rex
)) {
2050 auto const str
= what
[secs_
].str();
2051 LOG(INFO
) << "waiting at RCPT TO " << str
<< " seconds";
2053 std::from_chars(str
.data(), str
.data() + str
.size(), value
);
2055 LOG(INFO
) << "done waiting";