log A record for block list
[ghsmtp.git] / Session.cpp
blobb2f1a95bfee5d0d14567f1d0249f77fba76d70e0
1 #include <algorithm>
2 #include <charconv>
3 #include <iomanip>
4 #include <iostream>
5 #include <string>
6 #include <vector>
8 #include "DNS.hpp"
9 #include "Domain.hpp"
10 #include "IP.hpp"
11 #include "IP4.hpp"
12 #include "IP6.hpp"
13 #include "MessageStore.hpp"
14 #include "Session.hpp"
15 #include "esc.hpp"
16 #include "iequal.hpp"
17 #include "is_ascii.hpp"
18 #include "osutil.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>
28 #include <syslog.h>
30 #include <gflags/gflags.h>
32 using namespace std::string_literals;
34 namespace Config {
36 char const* wls[]{
37 "list.dnswl.org",
42 <https://www.dnswl.org/?page_id=15#query>
44 Return codes
46 The return codes are structured as 127.0.x.y, with “x” indicating the category
47 of an entry and “y” indicating how trustworthy an entry has been judged.
49 Categories (127.0.X.y):
51 2 – Financial services
52 3 – Email Service Providers
53 4 – Organisations (both for-profit [ie companies] and non-profit)
54 5 – Service/network providers
55 6 – Personal/private servers
56 7 – Travel/leisure industry
57 8 – Public sector/governments
58 9 – Media and Tech companies
59 10 – some special cases
60 11 – Education, academic
61 12 – Healthcare
62 13 – Manufacturing/Industrial
63 14 – Retail/Wholesale/Services
64 15 – Email Marketing Providers
65 20 – Added through Self Service without specific category
67 Trustworthiness / Score (127.0.x.Y):
69 0 = none – only avoid outright blocking (eg large ESP mailservers, -0.1)
70 1 = low – reduce chance of false positives (-1.0)
71 2 = medium – make sure to avoid false positives but allow override for clear
72 cases (-10.0) 3 = high – avoid override (-100.0).
74 The scores in parantheses are typical SpamAssassin scores.
76 Special return code 127.0.0.255
78 In cases where your nameserver issues more than 100’000 queries / 24 hours, you
79 may be blocked from further queries. The return code “127.0.0.255” indicates
80 this situation.
84 char const* bls[]{
85 "b.barracudacentral.org",
86 "sbl-xbl.spamhaus.org",
89 /*** Last octet from A record returned by blocklists ***
91 <https://www.spamhaus.org/faq/section/DNSBL%20Usage#200>
93 Spamhaus uses this general convention for return codes:
95 Return Code Description
96 127.0.0.0/24 Spamhaus IP Blocklists
97 127.0.1.0/24 Spamhaus Domain Blocklists
98 127.0.2.0/24 Spamhaus Zero Reputation Domains list
99 127.255.255.0/24 ERRORS (not implying a "listed" response)
101 Currently used return codes for Spamhaus public IP zones:
103 Return Code Zone Description
104 127.0.0.2 SBL Spamhaus SBL Data
105 127.0.0.3 SBL Spamhaus SBL CSS Data
106 127.0.0.4 XBL CBL Data
107 127.0.0.9 SBL Spamhaus DROP/EDROP Data
108 (in addition to 127.0.0.2, since 01-Jun-2016)
109 127.0.0.10 PBL ISP Maintained
110 127.0.0.11 PBL Spamhaus Maintained
112 127.0.0.5-7 are allocated to XBL for possible future use;
113 127.0.0.8 is allocated to SBL for possible future use.
115 From <https://www.spamhaus.org/faq/section/Spamhaus%20DBL#291>
117 Return Codes Data Source
118 127.0.1.2 spam domain
119 127.0.1.4 phish domain
120 127.0.1.5 malware domain
121 127.0.1.6 botnet C&C domain
122 127.0.1.102 abused legit spam
123 127.0.1.103 abused spammed redirector domain
124 127.0.1.104 abused legit phish
125 127.0.1.105 abused legit malware
126 127.0.1.106 abused legit botnet C&C
127 127.0.1.255 IP queries prohibited!
129 The following special codes indicate an error condition and should not
130 be taken to imply that the queried domain is "listed":
132 Return Code Description
133 127.255.255.252 Typing error in DNSBL name
134 127.255.255.254 Anonymous query through public resolver
135 127.255.255.255 Excessive number of queries
138 From <http://www.surbl.org/lists#multi>
140 last octet indicates which lists it belongs to. The bit positions in
141 that last octet for membership in the different lists are:
143 8 = listed on PH
144 16 = listed on MW
145 64 = listed on ABUSE
146 128 = listed on CR
151 char const* uribls[]{
152 "dbl.spamhaus.org",
153 "multi.uribl.com",
157 constexpr auto greeting_wait = std::chrono::seconds{6};
158 constexpr int max_recipients_per_message = 100;
159 constexpr int max_unrecognized_cmds = 20;
161 // Read timeout value gleaned from RFC-1123 section 5.3.2 and RFC-5321
162 // section 4.5.3.2.7.
163 constexpr auto read_timeout = std::chrono::minutes{5};
164 constexpr auto write_timeout = std::chrono::seconds{30};
165 } // namespace Config
167 DEFINE_bool(immortal, false, "don't set process timout");
169 DEFINE_uint64(max_read, 0, "max data to read");
170 DEFINE_uint64(max_write, 0, "max data to write");
172 DEFINE_string(selector, "ghsmtp", "DKIM selector");
174 DEFINE_bool(test_mode, false, "ease up on some checks");
176 DEFINE_bool(use_binarymime, true, "support BINARYMIME extension, RFC 3030");
177 DEFINE_bool(use_chunking, true, "support CHUNKING extension, RFC 3030");
178 DEFINE_bool(use_pipelining, true, "support PIPELINING extension, RFC 2920");
179 DEFINE_bool(use_rrvs, false, "support RRVS extension, RFC 7293");
180 DEFINE_bool(use_smtputf8, true, "support SMTPUTF8 extension, RFC 6531");
182 boost::xpressive::mark_tag secs_(1);
183 boost::xpressive::sregex const all_rex = boost::xpressive::icase("wait-all-") >>
184 (secs_ = +boost::xpressive::_d);
186 Session::Session(fs::path config_path,
187 std::function<void(void)> read_hook,
188 int fd_in,
189 int fd_out)
190 : config_path_(config_path)
191 , res_(config_path)
192 , sock_(fd_in, fd_out, read_hook, Config::read_timeout, Config::write_timeout)
193 //, send_(config_path, "smtp")
194 //, srs_(config_path)
196 auto accept_db_name = config_path_ / "accept_domains";
197 auto allow_db_name = config_path_ / "allow";
198 auto block_db_name = config_path_ / "block";
199 auto forward_db_name = config_path_ / "forward";
201 accept_domains_.open(accept_db_name);
202 allow_.open(allow_db_name);
203 block_.open(block_db_name);
204 forward_.open(forward_db_name);
206 if (sock_.has_peername() && !IP::is_private(sock_.us_c_str())) {
207 auto fcrdns = DNS::fcrdns(res_, sock_.us_c_str());
208 for (auto const& fcr : fcrdns) {
209 server_fcrdns_.emplace_back(fcr);
213 server_identity_ = [this] {
214 auto const id_from_env{getenv("GHSMTP_SERVER_ID")};
215 if (id_from_env)
216 return std::string{id_from_env};
218 auto const hostname{osutil::get_hostname()};
219 if (hostname.find('.') != std::string::npos)
220 return hostname;
222 if (!server_fcrdns_.empty()) {
223 // first result should be shortest
224 return server_fcrdns_.front().ascii();
227 auto const us_c_str = sock_.us_c_str();
228 if (us_c_str && !IP::is_private(us_c_str)) {
229 return IP::to_address_literal(us_c_str);
232 LOG(FATAL) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
233 return ""s;
234 }();
236 // send_.set_sender(server_identity_);
238 max_msg_size(Config::max_msg_size_initial);
241 void Session::max_msg_size(size_t max)
243 max_msg_size_ = max; // number to advertise via RFC 1870
245 if (FLAGS_max_read) {
246 sock_.set_max_read(FLAGS_max_read);
248 else {
249 auto const overhead = std::max(max / 10, size_t(2048));
250 sock_.set_max_read(max + overhead);
254 void Session::bad_host_(char const* msg) const
256 if (sock_.has_peername()) {
257 // On my systems, this pattern triggers a fail2ban rule that
258 // blocks connections from this IP address on port 25 for a few
259 // days. See <https://www.fail2ban.org/> for more info.
260 syslog(LOG_MAIL | LOG_WARNING, "bad host [%s] %s", sock_.them_c_str(), msg);
262 std::exit(EXIT_SUCCESS);
265 void Session::reset_()
267 // RSET does not force another EHLO/HELO, the one piece of per
268 // transaction data saved is client_identity_:
270 // client_identity_.clear(); <-- not cleared!
272 reverse_path_.clear();
273 forward_path_.clear();
274 spf_received_.clear();
275 // fwd_path_.clear();
276 // fwd_from_.clear();
277 // rep_info_.clear();
279 binarymime_ = false;
280 smtputf8_ = false;
281 // prdr_ = false;
283 if (msg_) {
284 msg_.reset();
287 max_msg_size(max_msg_size());
289 state_ = xact_step::mail;
290 // send_.rset();
293 // Return codes from connection establishment are 220 or 554, according
294 // to RFC 5321. That's it.
296 void Session::greeting()
298 CHECK(state_ == xact_step::helo);
300 if (sock_.has_peername()) {
301 close(2); // if we're a networked program, never send to stderr
303 std::string error_msg;
304 if (!verify_ip_address_(error_msg)) {
305 LOG(INFO) << error_msg;
306 bad_host_(error_msg.c_str());
309 /******************************************************************
310 <https://tools.ietf.org/html/rfc5321#section-4.3.1> says:
312 4.3. Sequencing of Commands and Replies
314 4.3.1. Sequencing Overview
316 The communication between the sender and receiver is an alternating
317 dialogue, controlled by the sender. As such, the sender issues a
318 command and the receiver responds with a reply. Unless other
319 arrangements are negotiated through service extensions, the sender
320 MUST wait for this response before sending further commands. One
321 important reply is the connection greeting. Normally, a receiver
322 will send a 220 "Service ready" reply when the connection is
323 completed. The sender SHOULD wait for this greeting message before
324 sending any commands.
326 So which is it?
328 “…the receiver responds with a reply.”
329 “…the sender MUST wait for this response…”
330 “One important reply is the connection greeting.”
331 “The sender SHOULD wait for this greeting…”
333 So is it MUST or SHOULD? I enforce MUST.
334 *******************************************************************/
336 // Wait a bit of time for pre-greeting traffic.
337 if (!(ip_allowed_ || fcrdns_allowed_)) {
338 if (sock_.input_ready(Config::greeting_wait)) {
339 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush;
340 LOG(INFO) << "input before any greeting from " << client_;
341 bad_host_("input before any greeting");
343 // Give a half greeting and wait again.
344 out_() << "220-" << server_id_() << " ESMTP slowstart - ghsmtp\r\n"
345 << std::flush;
346 if (sock_.input_ready(Config::greeting_wait)) {
347 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush;
348 LOG(INFO) << "input before full greeting from " << client_;
349 bad_host_("input before full greeting");
352 <https://www.rfc-editor.org/rfc/rfc5321#section-4.2>
354 An SMTP client MUST determine its actions only by the reply code, not
355 by the text (except for the "change of address" 251 and 551 and, if
356 necessary, 220, 221, and 421 replies); in the general case, any text,
357 including no text at all (although senders SHOULD NOT send bare
358 codes), MUST be acceptable. The space (blank) following the reply
359 code is considered part of the text. Whenever possible, a receiver-
360 SMTP SHOULD test the first digit (severity indication) of the reply
361 code.
363 Except the following chokes a lot of senders:
365 out_() << "220\r\n" << std::flush;
368 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush;
370 else {
371 out_() << "220 " << server_id_() << " ESMTP faststart - ghsmtp\r\n"
372 << std::flush;
375 else {
376 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush;
379 LOG(INFO) << "connect from " << client_;
381 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
382 alarm(2 * 60); // initial alarm
386 void Session::flush() { out_() << std::flush; }
388 void Session::last_in_group_(std::string_view verb)
390 if (sock_.input_ready(std::chrono::seconds(0))) {
391 LOG(WARNING) << "pipelining error; input ready processing " << verb;
395 void Session::check_for_pipeline_error_(std::string_view verb)
397 if (!(FLAGS_use_pipelining && extensions_)) {
398 if (sock_.input_ready(std::chrono::seconds(0))) {
399 LOG(WARNING) << "pipelining error; input ready processing " << verb;
404 void Session::lo_(char const* verb, std::string_view client_identity)
406 last_in_group_(verb);
407 reset_();
409 if (client_identity_ != client_identity) {
410 client_identity_ = client_identity;
412 std::string error_msg;
413 if (!verify_client_(client_identity_, error_msg)) {
414 LOG(INFO) << "client identity blocked: " << error_msg;
415 bad_host_(error_msg.c_str());
419 if (*verb == 'H') {
420 extensions_ = false;
421 out_() << "250 " << server_id_() << "\r\n";
424 if (*verb == 'E') {
425 extensions_ = true;
427 if (sock_.has_peername()) {
428 out_() << "250-" << server_id_() << " at your service, " << client_
429 << "\r\n";
431 else {
432 out_() << "250-" << server_id_() << "\r\n";
435 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
436 out_() << "250-8BITMIME\r\n"; // RFC 6152
438 if (FLAGS_use_rrvs) {
439 out_() << "250-RRVS\r\n"; // RFC 7293
442 // out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
444 if (sock_.tls()) {
445 // Check sasl sources for auth types.
446 // out_() << "250-AUTH PLAIN\r\n";
447 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
449 else {
450 // If we're not already TLS, offer TLS
451 out_() << "250-STARTTLS\r\n"; // RFC 3207
454 out_() << "250-ENHANCEDSTATUSCODES\r\n"; // RFC 2034
456 if (FLAGS_use_pipelining) {
457 out_() << "250-PIPELINING\r\n"; // RFC 2920
460 if (FLAGS_use_binarymime) {
461 out_() << "250-BINARYMIME\r\n"; // RFC 3030
464 if (FLAGS_use_chunking) {
465 out_() << "250-CHUNKING\r\n"; // RFC 3030
468 if (FLAGS_use_smtputf8) {
469 out_() << "250-SMTPUTF8\r\n"; // RFC 6531
472 out_() << "250 HELP\r\n";
475 out_() << std::flush;
477 if (sock_.has_peername()) {
478 if (std::find(begin(client_fcrdns_), end(client_fcrdns_),
479 client_identity_) != end(client_fcrdns_)) {
480 LOG(INFO) << verb << " " << client_identity << " from "
481 << sock_.them_address_literal();
483 else {
484 LOG(INFO) << verb << " " << client_identity << " from " << client_;
487 else {
488 LOG(INFO) << verb << " " << client_identity;
492 void Session::mail_from(Mailbox&& reverse_path, parameters_t const& parameters)
494 check_for_pipeline_error_("MAIL FROM");
496 switch (state_) {
497 case xact_step::helo:
498 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
499 LOG(WARNING) << "'MAIL FROM' before HELO/EHLO"
500 << (sock_.has_peername() ? " from " : "") << client_;
501 return;
502 case xact_step::mail: break;
503 case xact_step::rcpt:
504 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
505 LOG(WARNING) << "nested MAIL command"
506 << (sock_.has_peername() ? " from " : "") << client_;
507 return;
508 case xact_step::data:
509 case xact_step::bdat:
510 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush;
511 LOG(WARNING) << "nested MAIL command"
512 << (sock_.has_peername() ? " from " : "") << client_;
513 return;
514 case xact_step::rset:
515 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
516 LOG(WARNING) << "error state must be cleared with a RSET"
517 << (sock_.has_peername() ? " from " : "") << client_;
518 return;
521 if (!verify_from_params_(parameters)) {
522 return;
525 if (!smtputf8_ && !is_ascii(reverse_path.local_part())) {
526 LOG(WARNING) << "non ascii reverse_path \"" << reverse_path
527 << "\" without SMTPUTF8 paramater";
530 std::string error_msg;
531 if (!verify_sender_(reverse_path, error_msg)) {
532 LOG(INFO) << "verify sender failed: " << error_msg;
533 bad_host_(error_msg.c_str());
536 reverse_path_ = std::move(reverse_path);
537 // fwd_path_.clear();
538 // fwd_from_.clear();
539 forward_path_.clear();
540 out_() << "250 2.1.0 MAIL FROM OK\r\n";
541 // No flush RFC-2920 section 3.1, this could be part of a command group.
543 fmt::memory_buffer params;
544 for (auto const& [name, value] : parameters) {
545 fmt::format_to(std::back_inserter(params), " {}", name);
546 if (!value.empty()) {
547 fmt::format_to(std::back_inserter(params), "={}", value);
550 LOG(INFO) << "MAIL FROM:<" << reverse_path_ << ">" << fmt::to_string(params);
552 state_ = xact_step::rcpt;
555 // bool Session::forward_to_(std::string const& forward, Mailbox const& rcpt_to)
556 // {
557 // // If we're already forwarding or replying, reject
558 // if (!fwd_path_.empty() || !rep_info_.empty()) {
559 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
560 // stopped\r\n"
561 // << std::flush;
562 // LOG(WARNING) << "failed to forward to <" << forward
563 // << "> already forwarding or replying for: " << rcpt_to;
564 // return false;
565 // }
567 // fwd_path_ = Mailbox(forward);
568 // fwd_from_ = rcpt_to;
570 // // New bounce address
571 // Reply::from_to bounce;
572 // bounce.mail_from = reverse_path_.as_string();
574 // auto const new_bounce = srs_.enc_bounce(bounce, server_id_().c_str());
576 // auto const mail_from = Mailbox(new_bounce);
578 // std::string error_msg;
579 // if (!send_.mail_from_rcpt_to(res_, mail_from, fwd_path_, error_msg)) {
580 // out_() << error_msg << std::flush;
581 // LOG(WARNING) << "failed to forward <" << fwd_path_ << "> " << error_msg;
582 // return false;
583 // }
585 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> forwarding to == <" << fwd_path_
586 // << ">";
587 // return true;
588 // }
590 // bool Session::reply_to_(Reply::from_to const& reply_info, Mailbox const&
591 // rcpt_to)
592 // {
593 // // If we're already forwarding or replying, reject
594 // if (!fwd_path_.empty() || !rep_info_.empty()) {
595 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
596 // stopped\r\n"
597 // << std::flush;
598 // LOG(WARNING) << "failed to reply to <" << reply_info.mail_from
599 // << "> already forwarding or replying for: " << rcpt_to;
600 // return false;
601 // }
603 // rep_info_ = reply_info;
605 // Mailbox const from(rep_info_.rcpt_to_local_part, server_identity_);
606 // Mailbox const to(rep_info_.mail_from);
608 // std::string error_msg;
609 // if (!send_.mail_from_rcpt_to(res_, from, to, error_msg)) {
610 // out_() << error_msg << std::flush;
611 // LOG(WARNING) << "failed to reply from <" << from << "> to <" << to << ">
612 // "
613 // << error_msg;
614 // return false;
615 // }
617 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> is a reply to "
618 // << rep_info_.mail_from << " from " <<
619 // rep_info_.rcpt_to_local_part;
620 // return true;
621 // }
623 void Session::rcpt_to(Mailbox&& forward_path, parameters_t const& parameters)
625 check_for_pipeline_error_("RCPT TO");
627 switch (state_) {
628 case xact_step::helo:
629 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
630 LOG(WARNING) << "'RCPT TO' before HELO/EHLO"
631 << (sock_.has_peername() ? " from " : "") << client_;
632 return;
633 case xact_step::mail:
634 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
635 LOG(WARNING) << "'RCPT TO' before 'MAIL FROM'"
636 << (sock_.has_peername() ? " from " : "") << client_;
637 return;
638 case xact_step::rcpt:
639 case xact_step::data: break;
640 case xact_step::bdat:
641 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
642 LOG(WARNING) << "'RCPT TO' during BDAT transfer"
643 << (sock_.has_peername() ? " from " : "") << client_;
644 return;
645 case xact_step::rset:
646 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
647 LOG(WARNING) << "error state must be cleared with a RSET"
648 << (sock_.has_peername() ? " from " : "") << client_;
649 return;
652 if (!verify_rcpt_params_(parameters))
653 return;
655 if (!verify_recipient_(forward_path))
656 return;
658 if (!smtputf8_ && !is_ascii(forward_path.local_part())) {
659 LOG(WARNING) << "non ascii forward_path \"" << forward_path
660 << "\" without SMTPUTF8 paramater";
663 if (forward_path_.size() >= Config::max_recipients_per_message) {
664 out_() << "452 4.5.3 too many recipients\r\n" << std::flush;
665 LOG(WARNING) << "too many recipients <" << forward_path << ">";
666 return;
668 // no check for dups, postfix doesn't
669 forward_path_.emplace_back(std::move(forward_path));
671 Mailbox const& rcpt_to_mbx = forward_path_.back();
673 LOG(INFO) << "RCPT TO:<" << rcpt_to_mbx << ">";
675 // auto const rcpt_to_str = rcpt_to_mbx.as_string();
677 // if (auto reply = srs_.dec_reply(rcpt_to_mbx.local_part()); reply) {
678 // if (!reply_to_(*reply, rcpt_to_mbx))
679 // return;
680 // }
681 // else if (auto const forward = forward_.find(rcpt_to_str.c_str()); forward)
682 // {
683 // if (!forward_to_(*forward, rcpt_to_mbx))
684 // return;
685 // }
686 // else {
687 // LOG(INFO) << "RCPT TO:<" << rcpt_to_str << ">";
688 // }
690 // No flush RFC-2920 section 3.1, this could be part of a command group.
691 out_() << "250 2.1.5 RCPT TO OK\r\n";
693 state_ = xact_step::data;
696 // The headers Return-Path:, Received-SPF:, and Received: are returned
697 // as a string.
699 std::string Session::added_headers_(MessageStore const& msg)
701 auto const protocol{[this]() {
702 if (sock_.tls() && !extensions_) {
703 LOG(WARNING) << "TLS active without extensions";
705 // <https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#mail-parameters-5>
706 if (smtputf8_)
707 return sock_.tls() ? "UTF8SMTPS" : "UTF8SMTP";
708 else if (sock_.tls())
709 return "ESMTPS";
710 else if (extensions_)
711 return "ESMTP";
712 else
713 return "SMTP";
714 }()};
716 fmt::memory_buffer headers;
718 // Return-Path:
719 fmt::format_to(std::back_inserter(headers), "Return-Path: <{}>\r\n",
720 reverse_path_.as_string());
722 // Received-SPF:
723 if (!spf_received_.empty()) {
724 fmt::format_to(std::back_inserter(headers), "{}\r\n", spf_received_);
727 // Received:
728 // <https://tools.ietf.org/html/rfc5321#section-4.4>
729 fmt::format_to(std::back_inserter(headers), "Received: from {}",
730 client_identity_.utf8());
731 if (sock_.has_peername()) {
732 fmt::format_to(std::back_inserter(headers), " ({})", client_);
734 fmt::format_to(std::back_inserter(headers), "\r\n\tby {} with {} id {}",
735 server_identity_.utf8(), protocol, msg.id().as_string_view());
736 if (forward_path_.size()) {
737 fmt::format_to(std::back_inserter(headers), "\r\n\tfor <{}>",
738 forward_path_[0].as_string());
739 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
740 // “If the FOR clause appears, it MUST contain exactly one <path>
741 // entry, even when multiple RCPT commands have been given. Multiple
742 // <path>s raise some security issues and have been deprecated, see
743 // Section 7.2.”
744 // for (auto i = 1u; i < forward_path_.size(); ++i)
745 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
747 std::string const tls_info{sock_.tls_info()};
748 if (tls_info.length()) {
749 fmt::format_to(std::back_inserter(headers), "\r\n\t({})", tls_info);
751 fmt::format_to(std::back_inserter(headers), ";\r\n\t{}\r\n",
752 msg.when().as_string_view());
754 return fmt::to_string(headers);
757 namespace {
758 bool lookup_domain(CDB& cdb, Domain const& domain)
760 if (!domain.empty()) {
761 if (cdb.contains(domain.ascii())) {
762 return true;
764 if (domain.is_unicode() && cdb.contains(domain.utf8())) {
765 return true;
768 return false;
770 } // namespace
772 std::tuple<Session::SpamStatus, std::string> Session::spam_status_()
774 if (spf_result_ == SPF::Result::FAIL && !ip_allowed_)
775 return {SpamStatus::spam, "SPF failed"};
777 // These should have already been rejected by verify_client_().
778 if ((reverse_path_.domain() == "localhost.local") ||
779 (reverse_path_.domain() == "localhost"))
780 return {SpamStatus::spam, "bogus reverse_path"};
782 std::vector<std::string> why_ham;
784 // Anything enciphered tastes a lot like ham.
785 if (sock_.tls())
786 why_ham.emplace_back("they used TLS");
788 if (spf_result_ == SPF::Result::PASS) {
789 if (lookup_domain(allow_, spf_sender_domain_)) {
790 why_ham.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
791 spf_sender_domain_.utf8()));
793 else {
794 auto tld_dom{tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
795 if (tld_dom && allow_.contains(tld_dom)) {
796 why_ham.emplace_back(fmt::format(
797 "SPF sender registered domain ({}) is allowed", tld_dom));
802 if (fcrdns_allowed_)
803 why_ham.emplace_back(
804 fmt::format("FCrDNS (or it's registered domain) is allowed"));
806 if (!why_ham.empty())
807 return {SpamStatus::ham,
808 fmt::format("{}", fmt::join(std::begin(why_ham), std::end(why_ham),
809 ", and "))};
811 return {SpamStatus::spam, "it's not ham"};
814 static std::string folder(Session::SpamStatus status,
815 std::vector<Mailbox> const& forward_path,
816 Mailbox const& reverse_path)
818 if (reverse_path ==
819 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
820 return ".Gmail";
822 if (reverse_path == Mailbox("ietf-smtp-bounces@ietf.org"))
823 return ".smtp";
825 struct assignment {
826 std::string_view local_part;
827 std::string_view folder;
830 assignment assignments[] = {
831 {"Emailcore", ".emailcore"},
832 {"bootstrappable", ".bootstrappable"},
833 {"coreboot.org", ".coreboot"},
834 {"dmarc", ".dmarc"},
835 {"dns-privacy", ".dns-privacy"},
836 {"fucking-facebook", ".FB"},
837 {"gene-ebay", ".EBay"},
838 {"i-hate-linked-in", ".linkedin"},
839 {"mailop", ".INBOX.mailop"},
840 {"modelfkeyboards.com", ""},
841 {"nest", ".INBOX.Nest"},
842 {"opendmarc-dev", ".dmarc"},
843 {"opendmarc-users", ".dmarc"},
844 {"postmaster-rua", ".INBOX.rua"},
845 {"shadowserver-reports@digilicious.com", ".INBOX.shadowserver"},
846 {"theatlantic.com", ""},
847 {"time-nutz", ".time-nutz"},
848 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
851 for (auto ass : assignments) {
852 if (forward_path[0].local_part() == ass.local_part)
853 return std::string(ass.folder);
856 if (iends_with(forward_path[0].local_part(), "-at-duck"))
857 return ".JunkDuck";
859 if (status == Session::SpamStatus::spam)
860 return ".Junk";
862 return "";
865 bool Session::msg_new()
867 CHECK((state_ == xact_step::data) || (state_ == xact_step::bdat));
869 auto const& [status, reason]{spam_status_()};
871 LOG(INFO) << ((status == SpamStatus::ham) ? "ham since " : "spam since ")
872 << reason;
874 // All sources of ham get a fresh 5 minute timeout per message.
875 if (status == SpamStatus::ham) {
876 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr))
877 alarm(5 * 60);
880 msg_ = std::make_unique<MessageStore>();
882 if (!FLAGS_max_write)
883 FLAGS_max_write = max_msg_size();
885 try {
886 msg_->open(server_id_(), FLAGS_max_write,
887 folder(status, forward_path_, reverse_path_));
888 auto const hdrs{added_headers_(*(msg_.get()))};
889 msg_->write(hdrs);
891 // fmt::memory_buffer spam_status;
892 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
893 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
894 // msg_->write(spam_status.data(), spam_status.size());
896 LOG(INFO) << "Spam-Status: "
897 << ((status == SpamStatus::spam) ? "Yes" : "No") << ", "
898 << reason;
900 return true;
902 catch (std::system_error const& e) {
903 switch (errno) {
904 case ENOSPC:
905 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
906 LOG(ERROR) << "no space";
907 msg_->trash();
908 msg_.reset();
909 return false;
911 default:
912 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
913 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
914 LOG(ERROR) << e.what();
915 msg_->trash();
916 msg_.reset();
917 return false;
920 catch (std::exception const& e) {
921 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
922 LOG(ERROR) << e.what();
923 msg_->trash();
924 msg_.reset();
925 return false;
928 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
929 LOG(ERROR) << "msg_new failed with no exception caught";
930 msg_->trash();
931 msg_.reset();
932 return false;
935 bool Session::msg_write(char const* s, std::streamsize count)
937 if ((state_ != xact_step::data) && (state_ != xact_step::bdat))
938 return false;
940 if (!msg_)
941 return false;
943 try {
944 if (msg_->write(s, count))
945 return true;
947 catch (std::system_error const& e) {
948 switch (errno) {
949 case ENOSPC:
950 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
951 LOG(ERROR) << "no space";
952 msg_->trash();
953 msg_.reset();
954 return false;
956 default:
957 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
958 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
959 LOG(ERROR) << e.what();
960 msg_->trash();
961 msg_.reset();
962 return false;
965 catch (std::exception const& e) {
966 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
967 LOG(ERROR) << e.what();
968 msg_->trash();
969 msg_.reset();
970 return false;
973 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
974 LOG(ERROR) << "msg_write failed with no exception caught";
975 msg_->trash();
976 msg_.reset();
977 return false;
980 bool Session::data_start()
982 last_in_group_("DATA");
984 switch (state_) {
985 case xact_step::helo:
986 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
987 LOG(WARNING) << "'DATA' before HELO/EHLO"
988 << (sock_.has_peername() ? " from " : "") << client_;
989 return false;
990 case xact_step::mail:
991 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
992 LOG(WARNING) << "'DATA' before 'MAIL FROM'"
993 << (sock_.has_peername() ? " from " : "") << client_;
994 return false;
995 case xact_step::rcpt:
997 /******************************************************************
998 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
1000 The DATA command can fail at only two points in the protocol exchange:
1002 If there was no MAIL, or no RCPT, command, or all such commands were
1003 rejected, the server MAY return a "command out of sequence" (503) or
1004 "no valid recipients" (554) reply in response to the DATA command.
1006 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
1008 The additional restriction is that when there have been no successful
1009 RCPT commands in the mail transaction, the DATA command MUST fail
1010 with a 503 reply code.
1012 Therefore I will send the reply code that is valid for both, and
1013 do the same for the BDAT case.
1014 *******************************************************************/
1016 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1017 LOG(WARNING) << "no valid recipients"
1018 << (sock_.has_peername() ? " from " : "") << client_;
1019 return false;
1020 case xact_step::data: break;
1021 case xact_step::bdat:
1022 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
1023 LOG(WARNING) << "'DATA' during BDAT transfer"
1024 << (sock_.has_peername() ? " from " : "") << client_;
1025 return false;
1026 case xact_step::rset:
1027 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1028 LOG(WARNING) << "error state must be cleared with a RSET"
1029 << (sock_.has_peername() ? " from " : "") << client_;
1030 return false;
1033 if (binarymime_) {
1034 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
1035 << std::flush;
1036 LOG(WARNING) << "DATA does not support BINARYMIME";
1037 state_ = xact_step::rset; // RFC 3030 section 3 page 5
1038 return false;
1041 if (!msg_new()) {
1042 LOG(ERROR) << "msg_new() failed";
1043 return false;
1046 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush;
1047 LOG(INFO) << "DATA";
1048 return true;
1051 // bool Session::do_forward_(message::parsed& msg)
1052 // {
1053 // auto msg_fwd = msg;
1055 // // Generate a reply address
1056 // Reply::from_to reply;
1057 // reply.mail_from = msg_fwd.dmarc_from;
1058 // reply.rcpt_to_local_part = fwd_from_.local_part();
1060 // auto const reply_addr =
1061 // fmt::format("{}@{}", srs_.enc_reply(reply), server_id_());
1063 // auto const munging = false;
1065 // auto const sender = server_identity_.ascii().c_str();
1066 // auto const selector = FLAGS_selector.c_str();
1067 // auto const key_file =
1068 // (config_path_ / FLAGS_selector).replace_extension("private");
1069 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1071 // if (munging) {
1072 // auto const from_hdr =
1073 // fmt::format("From: \"{} via\" <@>", msg_fwd.dmarc_from, reply_addr);
1074 // message::rewrite_from_to(msg_fwd, from_hdr, "", sender, selector,
1075 // key_file);
1076 // }
1077 // else {
1078 // auto const reply_to_hdr = fmt::format("Reply-To: {}", reply_addr);
1079 // message::rewrite_from_to(msg_fwd, "", reply_to_hdr, sender, selector,
1080 // key_file);
1081 // }
1083 // // Forward it on
1084 // if (!send_.send(msg_fwd.as_string())) {
1085 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1086 // "stopped\r\n"
1087 // << std::flush;
1089 // LOG(ERROR) << "failed to send for " << fwd_path_;
1090 // return false;
1091 // }
1093 // LOG(INFO) << "successfully sent for " << fwd_path_;
1094 // return true;
1095 // }
1097 // bool Session::do_reply_(message::parsed& msg)
1098 // {
1099 // Mailbox to_mbx(rep_info_.mail_from);
1100 // Mailbox from_mbx(rep_info_.rcpt_to_local_part, server_identity_);
1102 // auto reply = std::make_unique<MessageStore>();
1103 // reply->open(server_id_(), FLAGS_max_write, ".Drafts");
1105 // auto const date{Now{}};
1106 // auto const pill{Pill{}};
1107 // auto const mid_str =
1108 // fmt::format("<{}.{}@{}>", date.sec(), pill, server_identity_);
1110 // fmt::memory_buffer bfr;
1112 // fmt::format_to(bfr, "From: <{}>\r\n", from_mbx);
1113 // fmt::format_to(bfr, "To: <{}>\r\n", to_mbx);
1115 // fmt::format_to(bfr, "Date: {}\r\n", date.c_str());
1117 // fmt::format_to(bfr, "Message-ID: {}\r\n", mid_str.c_str());
1119 // if (!msg.get_header(message::Subject).empty()) {
1120 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1121 // msg.get_header(message::Subject));
1122 // }
1123 // else {
1124 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1125 // "Reply to your message");
1126 // }
1128 // if (!msg.get_header(message::In_Reply_To).empty()) {
1129 // fmt::format_to(bfr, "{}: {}\r\n", message::In_Reply_To,
1130 // msg.get_header(message::In_Reply_To));
1131 // }
1133 // if (!msg.get_header(message::MIME_Version).empty() &&
1134 // msg.get_header(message::Content_Type).empty()) {
1135 // fmt::format_to(bfr, "{}: {}\r\n", message::MIME_Version,
1136 // msg.get_header(message::MIME_Version));
1137 // fmt::format_to(bfr, "{}: {}\r\n", message::Content_Type,
1138 // msg.get_header(message::Content_Type));
1139 // }
1141 // reply->write(fmt::to_string(bfr));
1143 // if (!msg.body.empty()) {
1144 // reply->write("\r\n");
1145 // reply->write(msg.body);
1146 // }
1148 // auto const msg_data = reply->freeze();
1149 // message::parsed msg_reply;
1150 // CHECK(msg_reply.parse(msg_data));
1152 // auto const sender = server_identity_.ascii().c_str();
1153 // auto const selector = FLAGS_selector.c_str();
1154 // auto const key_file =
1155 // (config_path_ / FLAGS_selector).replace_extension("private");
1156 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1158 // message::dkim_sign(msg_reply, sender, selector, key_file);
1160 // if (!send_.send(msg_reply.as_string())) {
1161 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1162 // "stopped\r\n"
1163 // << std::flush;
1165 // LOG(ERROR) << "send failed for reply to " << to_mbx << " from " <<
1166 // from_mbx; return false;
1167 // }
1169 // LOG(INFO) << "successful reply to " << to_mbx << " from " << from_mbx;
1170 // return true;
1171 // }
1173 bool Session::do_deliver_()
1175 CHECK(msg_);
1177 // auto const sender = server_identity_.ascii().c_str();
1178 // auto const selector = FLAGS_selector.c_str();
1179 // auto const key_file =
1180 // (config_path_ / FLAGS_selector).replace_extension("private");
1181 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1183 try {
1184 // auto const msg_data = msg_->freeze();
1186 // message::parsed msg;
1188 // // Only deal in RFC-5322 Mail Objects.
1189 // bool const message_parsed = msg.parse(msg_data);
1190 // if (message_parsed) {
1192 // // remove any Return-Path
1193 // message::remove_delivery_headers(msg);
1195 // auto const authentic =
1196 // message_parsed &&
1197 // message::authentication(msg, sender, selector, key_file);
1199 // // write a new Return-Path
1200 // msg_->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_));
1202 // for (auto const h : msg.headers) {
1203 // msg_->write(h.as_string());
1204 // msg_->write("\r\n");
1205 // }
1206 // if (!msg.body.empty()) {
1207 // msg_->write("\r\n");
1208 // msg_->write(msg.body);
1209 // }
1211 msg_->deliver();
1213 // if (authentic && !fwd_path_.empty()) {
1214 // if (!do_forward_(msg))
1215 // return false;
1216 // }
1217 // if (authentic && !rep_info_.empty()) {
1218 // if (!do_reply_(msg))
1219 // return false;
1220 // }
1221 // }
1223 msg_->close();
1225 catch (std::system_error const& e) {
1226 switch (errno) {
1227 case ENOSPC:
1228 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
1229 LOG(ERROR) << "no space";
1230 msg_->trash();
1231 reset_();
1232 return false;
1234 default:
1235 out_() << "550 5.0.0 mail system error\r\n" << std::flush;
1236 if (errno)
1237 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
1238 LOG(ERROR) << e.what();
1239 msg_->trash();
1240 reset_();
1241 return false;
1245 return true;
1248 void Session::data_done()
1250 CHECK((state_ == xact_step::data));
1252 if (msg_ && msg_->size_error()) {
1253 data_size_error();
1254 return;
1257 // if (prdr_) {
1258 // out_() << "353\r\n";
1259 // for (auto fp : forward_path_) {
1260 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1261 // }
1262 // }
1264 // Check for and act on magic "wait" address.
1266 using namespace boost::xpressive;
1268 sregex const rex = icase("wait-data-") >> (secs_ = +_d);
1269 smatch what;
1271 for (auto fp : forward_path_) {
1272 if (regex_match(fp.local_part(), what, rex) ||
1273 regex_match(fp.local_part(), what, all_rex)) {
1274 auto const str = what[secs_].str();
1275 LOG(INFO) << "waiting at DATA " << str << " seconds";
1276 long value = 0;
1277 std::from_chars(str.data(), str.data() + str.size(), value);
1278 google::FlushLogFiles(google::INFO);
1279 out_() << std::flush;
1280 sleep(value);
1281 LOG(INFO) << "done waiting";
1286 if (do_deliver_()) {
1287 auto temp_fail_db_name = config_path_ / "temp_fail_data";
1288 CDB temp_fail;
1290 for (auto fp : forward_path_) {
1291 if (temp_fail.open(temp_fail_db_name) &&
1292 temp_fail.contains(fp.local_part())) {
1293 out_() << "450 4.2.2 Mailbox full.\r\n" << std::flush;
1294 LOG(WARNING) << "temp fail at DATA for recipient " << fp;
1295 reset_();
1296 return;
1301 // Check for addresses we reject after data.
1303 auto bad_recipients_db_name = config_path_ / "bad_recipients_data";
1304 CDB bad_recipients_db;
1305 if (bad_recipients_db.open(bad_recipients_db_name)) {
1306 for (auto fp : forward_path_) {
1307 if (bad_recipients_db.contains(fp.local_part())) {
1308 out_() << "550 5.1.1 bad recipient " << fp << "\r\n" << std::flush;
1309 LOG(WARNING) << "bad recipient " << fp;
1310 reset_();
1311 return;
1313 else {
1314 LOG(INFO) << "unbad recipient " << fp.local_part();
1318 else {
1319 LOG(WARNING) << "can't open bad_recipients_data";
1323 out_() << "250 2.0.0 DATA OK\r\n" << std::flush;
1324 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1325 << msg_->id();
1326 reset_();
1329 void Session::data_size_error()
1331 out_().clear(); // clear possible eof from input side
1332 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1333 if (msg_) {
1334 msg_->trash();
1336 LOG(WARNING) << "DATA size error";
1337 reset_();
1340 void Session::data_error()
1342 out_().clear(); // clear possible eof from input side
1343 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush;
1344 if (msg_) {
1345 msg_->trash();
1347 LOG(WARNING) << "DATA error";
1348 reset_();
1351 bool Session::bdat_start(size_t n)
1353 // In practice, this one gets pipelined.
1354 // last_in_group_("BDAT");
1356 switch (state_) {
1357 case xact_step::helo:
1358 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
1359 LOG(WARNING) << "'BDAT' before HELO/EHLO"
1360 << (sock_.has_peername() ? " from " : "") << client_;
1361 return false;
1362 case xact_step::mail:
1363 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
1364 LOG(WARNING) << "'BDAT' before 'MAIL FROM'"
1365 << (sock_.has_peername() ? " from " : "") << client_;
1366 return false;
1367 case xact_step::rcpt:
1368 // See comment in data_start()
1369 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1370 LOG(WARNING) << "no valid recipients"
1371 << (sock_.has_peername() ? " from " : "") << client_;
1372 return false;
1373 case xact_step::data: // first bdat
1374 break;
1375 case xact_step::bdat: return true;
1376 case xact_step::rset:
1377 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1378 LOG(WARNING) << "error state must be cleared with a RSET"
1379 << (sock_.has_peername() ? " from " : "") << client_;
1380 return false;
1383 state_ = xact_step::bdat;
1385 return msg_new();
1388 void Session::bdat_done(size_t n, bool last)
1390 if (state_ != xact_step::bdat) {
1391 bdat_seq_error();
1392 return;
1395 if (!msg_) {
1396 return;
1399 if (msg_->size_error()) {
1400 bdat_size_error();
1401 return;
1404 if (!last) {
1405 out_() << "250 2.0.0 BDAT " << n << " OK\r\n" << std::flush;
1406 LOG(INFO) << "BDAT " << n;
1407 return;
1410 // Check for and act on magic "wait" address.
1412 using namespace boost::xpressive;
1414 sregex const rex = icase("wait-bdat-") >> (secs_ = +_d);
1415 smatch what;
1417 for (auto fp : forward_path_) {
1418 if (regex_match(fp.local_part(), what, rex) ||
1419 regex_match(fp.local_part(), what, all_rex)) {
1420 auto const str = what[secs_].str();
1421 LOG(INFO) << "waiting at BDAT " << str << " seconds";
1422 long value = 0;
1423 std::from_chars(str.data(), str.data() + str.size(), value);
1424 google::FlushLogFiles(google::INFO);
1425 out_() << std::flush;
1426 sleep(value);
1427 LOG(INFO) << "done waiting";
1432 do_deliver_();
1434 out_() << "250 2.0.0 BDAT " << n << " LAST OK\r\n" << std::flush;
1435 LOG(INFO) << "BDAT " << n << " LAST";
1436 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1437 << msg_->id();
1438 reset_();
1441 void Session::bdat_size_error()
1443 out_().clear(); // clear possible eof from input side
1444 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1445 if (msg_) {
1446 msg_->trash();
1448 LOG(WARNING) << "BDAT size error";
1449 reset_();
1452 void Session::bdat_seq_error()
1454 out_().clear(); // clear possible eof from input side
1455 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush;
1456 if (msg_) {
1457 msg_->trash();
1459 LOG(WARNING) << "BDAT sequence error";
1460 reset_();
1463 void Session::bdat_io_error()
1465 out_().clear(); // clear possible eof from input side
1466 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush;
1467 if (msg_) {
1468 msg_->trash();
1470 LOG(WARNING) << "BDAT I/O error";
1471 reset_();
1474 void Session::rset()
1476 out_() << "250 2.1.5 RSET OK\r\n";
1477 // No flush RFC-2920 section 3.1, this could be part of a command group.
1478 LOG(INFO) << "RSET";
1479 reset_();
1482 void Session::noop(std::string_view str)
1484 last_in_group_("NOOP");
1485 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush;
1486 LOG(INFO) << "NOOP" << (str.length() ? " " : "") << str;
1489 void Session::vrfy(std::string_view str)
1491 last_in_group_("VRFY");
1492 out_() << "252 2.1.5 try it\r\n" << std::flush;
1493 LOG(INFO) << "VRFY" << (str.length() ? " " : "") << str;
1496 void Session::help(std::string_view str)
1498 if (iequal(str, "help\r\n")) {
1499 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush;
1501 else {
1502 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1503 << std::flush;
1505 LOG(INFO) << "HELP" << (str.length() ? " " : "") << str;
1508 void Session::quit()
1510 // send_.quit();
1511 // last_in_group_("QUIT");
1512 out_() << "221 2.0.0 closing connection\r\n" << std::flush;
1513 LOG(INFO) << "QUIT";
1514 exit_();
1517 void Session::auth()
1519 out_() << "454 4.7.0 authentication failure\r\n" << std::flush;
1520 LOG(INFO) << "AUTH";
1521 bad_host_("auth");
1524 void Session::error(std::string_view log_msg)
1526 out_() << "421 4.3.5 system error: " << log_msg << "\r\n" << std::flush;
1527 LOG(WARNING) << log_msg;
1530 void Session::cmd_unrecognized(std::string_view cmd)
1532 auto const escaped{esc(cmd)};
1533 LOG(WARNING) << "command unrecognized: \"" << escaped << "\"";
1535 if (++n_unrecognized_cmds_ >= Config::max_unrecognized_cmds) {
1536 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1537 << "\" exceeds limit\r\n"
1538 << std::flush;
1539 LOG(WARNING) << n_unrecognized_cmds_
1540 << " unrecognized commands is too many";
1541 exit_();
1544 out_() << "500 5.5.1 command unrecognized: \"" << escaped << "\"\r\n"
1545 << std::flush;
1548 void Session::bare_lf()
1550 // Error code used by Office 365.
1551 out_() << "554 5.6.11 bare LF\r\n" << std::flush;
1552 LOG(WARNING) << "bare LF";
1553 exit_();
1556 void Session::max_out()
1558 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1559 LOG(WARNING) << "message size maxed out";
1560 exit_();
1563 void Session::time_out()
1565 out_() << "421 4.4.2 time-out\r\n" << std::flush;
1566 LOG(WARNING) << "time-out" << (sock_.has_peername() ? " from " : "")
1567 << client_;
1568 exit_();
1571 void Session::starttls()
1573 last_in_group_("STARTTLS");
1574 if (sock_.tls()) {
1575 out_() << "554 5.5.1 TLS already active\r\n" << std::flush;
1576 LOG(WARNING) << "STARTTLS issued with TLS already active";
1578 else if (!extensions_) {
1579 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1580 << std::flush;
1581 LOG(WARNING) << "STARTTLS issued without using EHLO";
1583 else {
1584 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush;
1585 if (sock_.starttls_server(config_path_)) {
1586 reset_();
1587 max_msg_size(Config::max_msg_size_bro);
1588 LOG(INFO) << "STARTTLS " << sock_.tls_info();
1590 else {
1591 LOG(INFO) << "failed STARTTLS";
1596 void Session::exit_()
1598 // sock_.log_totals();
1600 timespec time_used{};
1601 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time_used);
1603 LOG(INFO) << "CPU time " << time_used.tv_sec << "." << std::setw(9)
1604 << std::setfill('0') << time_used.tv_nsec << " seconds";
1606 std::exit(EXIT_SUCCESS);
1609 /////////////////////////////////////////////////////////////////////////////
1611 // All of the verify_* functions send their own error messages back to
1612 // the client on failure, and return false.
1614 bool Session::verify_ip_address_(std::string& error_msg)
1616 auto ip_block_db_name = config_path_ / "ip-block";
1617 CDB ip_block;
1618 if (ip_block.open(ip_block_db_name) &&
1619 ip_block.contains(sock_.them_c_str())) {
1620 error_msg =
1621 fmt::format("IP address {} on static blocklist", sock_.them_c_str());
1622 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1623 return false;
1626 client_fcrdns_.clear();
1628 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1629 (sock_.them_address_literal() == IP6::loopback_literal)) {
1630 LOG(INFO) << "loopback address allowed";
1631 ip_allowed_ = true;
1632 client_fcrdns_.emplace_back("localhost");
1633 client_ = fmt::format("localhost {}", sock_.them_address_literal());
1634 return true;
1637 auto const fcrdns = DNS::fcrdns(res_, sock_.them_c_str());
1638 for (auto const& fcr : fcrdns) {
1639 client_fcrdns_.emplace_back(fcr);
1642 if (IP::is_private(sock_.them_address_literal())) {
1643 LOG(INFO) << "private address allowed";
1644 ip_allowed_ = true;
1645 client_ = sock_.them_address_literal();
1646 return true;
1649 if (!client_fcrdns_.empty()) {
1650 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1651 sock_.them_address_literal());
1652 // check allow list
1653 for (auto const& client_fcrdns : client_fcrdns_) {
1654 if (allow_.contains(client_fcrdns.ascii())) {
1655 LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1656 fcrdns_allowed_ = true;
1657 return true;
1659 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1660 if (tld) {
1661 if (allow_.contains(tld)) {
1662 LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1663 fcrdns_allowed_ = true;
1664 return true;
1668 // check blocklist
1669 for (auto const& client_fcrdns : client_fcrdns_) {
1670 if (block_.contains(client_fcrdns.ascii())) {
1671 error_msg =
1672 fmt::format("FCrDNS {} on static blocklist", client_fcrdns.ascii());
1673 out_() << "554 5.7.1 blocklisted\r\n" << std::flush;
1674 return false;
1677 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1678 if (tld) {
1679 if (block_.contains(tld)) {
1680 error_msg = fmt::format(
1681 "FCrDNS registered domain {} on static blocklist", tld);
1682 out_() << "554 5.7.1 blocklisted\r\n" << std::flush;
1683 return false;
1688 else {
1689 client_ = fmt::format("{}", sock_.them_address_literal());
1692 if (IP4::is_address(sock_.them_c_str())) {
1694 auto const reversed{IP4::reverse(sock_.them_c_str())};
1697 // Check with allow list.
1698 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1699 random_device_);
1701 for (auto wl : Config::wls) {
1702 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1703 if (q.has_record()) {
1704 using namespace boost::xpressive;
1706 auto const as = q.get_strings()[0];
1707 LOG(INFO) << "on allow list " << wl << " as " << as;
1709 mark_tag x_(1);
1710 mark_tag y_(2);
1711 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1712 smatch what;
1714 if (regex_match(as, what, rex)) {
1715 auto const x = what[x_].str();
1716 auto const y = what[y_].str();
1718 int value = 0;
1719 std::from_chars(y.data(), y.data() + y.size(), value);
1720 if (value > 0) {
1721 ip_allowed_ = true;
1722 LOG(INFO) << "allowed";
1726 LOG(INFO) << "Any A record skips check on block list";
1727 return true;
1732 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1733 std::shuffle(std::begin(Config::bls), std::end(Config::bls),
1734 random_device_);
1736 for (auto bl : Config::bls) {
1738 DNS::Query q(res_, DNS::RR_type::A, reversed + bl);
1739 if (q.has_record()) {
1740 auto const as = q.get_strings()[0];
1741 if (as == "127.0.0.1") {
1742 LOG(INFO) << "Should never get 127.0.0.1, from " << bl;
1744 else if (as == "127.0.0.10" || as == "127.0.0.11") {
1745 LOG(INFO) << "PBL listed, ignoring " << bl;
1747 else if (as == "127.255.255.252") {
1748 LOG(INFO) << "Typing error in DNSBL name " << bl;
1750 else if (as == "127.255.255.254") {
1751 LOG(INFO) << "Anonymous query through public resolver " << bl;
1753 else if (as == "127.255.255.255") {
1754 LOG(INFO) << "Excessive number of queries " << bl;
1756 else {
1757 error_msg = fmt::format("IP address {} blocked: {} returned {}",
1758 sock_.them_c_str(), bl, as);
1759 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1760 return false;
1764 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1767 LOG(INFO) << "IP address okay";
1768 return true;
1771 bool domain_blocked(DNS::Resolver& res, Domain const& identity)
1773 Domain lookup{fmt::format("{}.dbl.spamhaus.org", identity.ascii())};
1774 DNS::Query q(res, DNS::RR_type::A, lookup.ascii());
1775 if (q.has_record()) {
1776 auto const as = q.get_strings()[0];
1777 if (istarts_with(as, "127.0.1.")) {
1778 LOG(INFO) << "Domain " << identity << " blocked by spamhaus, " << as;
1779 return true;
1782 return false;
1785 // check the identity from HELO/EHLO
1786 bool Session::verify_client_(Domain const& client_identity,
1787 std::string& error_msg)
1789 if (!client_fcrdns_.empty()) {
1790 if (auto id = std::find(begin(client_fcrdns_), end(client_fcrdns_),
1791 client_identity);
1792 id != end(client_fcrdns_)) {
1793 // If the HELO ident is one of the FCrDNS names...
1794 if (id != begin(client_fcrdns_)) {
1795 // ...then rotate that one to the front of the list
1796 std::rotate(begin(client_fcrdns_), id, id + 1);
1798 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1799 sock_.them_address_literal());
1800 return true;
1802 LOG(INFO) << "claimed identity " << client_identity
1803 << " does NOT match any FCrDNS: ";
1804 for (auto const& client_fcrdns : client_fcrdns_) {
1805 LOG(INFO) << " " << client_fcrdns;
1809 // Bogus clients claim to be us or some local host.
1810 if (sock_.has_peername() && ((client_identity == server_identity_) ||
1811 (client_identity == "localhost") ||
1812 (client_identity == "localhost.localdomain"))) {
1814 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1815 (sock_.them_address_literal() == IP6::loopback_literal)) {
1816 return true;
1819 // Give 'em a pass.
1820 if (ip_allowed_) {
1821 LOG(INFO) << "allow-listed IP address can claim to be "
1822 << client_identity;
1823 return true;
1826 // Ease up in test mode.
1827 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1828 return true;
1831 error_msg = fmt::format("liar, claimed to be {}", client_identity.ascii());
1832 out_() << "550 5.7.1 liar\r\n" << std::flush;
1833 return false;
1836 std::vector<std::string> labels;
1837 boost::algorithm::split(labels, client_identity.ascii(),
1838 boost::algorithm::is_any_of("."));
1839 if (labels.size() < 2) {
1840 error_msg =
1841 fmt::format("claimed bogus identity {}", client_identity.ascii());
1842 out_() << "550 4.7.1 bogus identity\r\n" << std::flush;
1843 return false;
1844 // // Sometimes we may want to look at mail from non conforming
1845 // // sending systems.
1846 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1847 // << client_ << " claiming " << client_identity;
1848 // return true;
1851 if (lookup_domain(block_, client_identity)) {
1852 error_msg =
1853 fmt::format("claimed blocked identity {}", client_identity.ascii());
1854 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1855 return false;
1858 auto const tld{tld_db_.get_registered_domain(client_identity.ascii())};
1859 if (!tld) {
1860 // Sometimes we may want to look at mail from misconfigured
1861 // sending systems.
1862 // LOG(WARNING) << "claimed identity has no registered domain";
1863 // return true;
1865 else if (block_.contains(tld)) {
1866 error_msg =
1867 fmt::format("claimed identity has blocked registered domain {}", tld);
1868 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush;
1869 return false;
1872 if (domain_blocked(res_, client_identity) ||
1873 domain_blocked(res_, Domain(tld))) {
1874 error_msg = fmt::format("claimed identity {} blocked by spamhaus",
1875 client_identity.ascii());
1876 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1877 return false;
1880 DNS::Query q(res_, DNS::RR_type::A, client_identity.ascii());
1881 if (!q.has_record()) {
1882 LOG(WARNING) << "claimed identity " << client_identity.ascii()
1883 << " not DNS resolvable";
1886 // not otherwise objectionable
1887 return true;
1890 // check sender from RFC5321 MAIL FROM:
1891 bool Session::verify_sender_(Mailbox const& sender, std::string& error_msg)
1893 do_spf_check_(sender);
1895 std::string const sender_str{sender};
1897 if (sender.empty()) {
1898 // MAIL FROM:<>
1899 // is used to send bounce messages.
1900 return true;
1903 if (domain_blocked(res_, sender.domain())) {
1904 error_msg = fmt::format("{} sender domain blocked by spamhaus", sender_str);
1905 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1906 return false;
1909 auto bad_senders_db_name = config_path_ / "bad_senders";
1910 CDB bad_senders;
1911 if (bad_senders.open(bad_senders_db_name) &&
1912 bad_senders.contains(sender_str)) {
1913 error_msg = fmt::format("{} bad sender", sender_str);
1914 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1915 return false;
1918 // We don't accept mail /from/ a domain we are expecting to accept
1919 // mail for on an external network connection.
1921 if (sock_.them_address_literal() != sock_.us_address_literal()) {
1922 if ((accept_domains_.is_open() &&
1923 (accept_domains_.contains(sender.domain().ascii()) ||
1924 accept_domains_.contains(sender.domain().utf8()))) ||
1925 (sender.domain() == server_identity_)) {
1927 // Ease up in test mode.
1928 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1929 return true;
1931 out_() << "550 5.7.1 liar\r\n" << std::flush;
1932 error_msg = fmt::format("liar, claimed to be {}", sender.domain().utf8());
1933 return false;
1937 if (sender.domain().is_address_literal()) {
1938 if (sender.domain() != sock_.them_address_literal()) {
1939 LOG(WARNING) << "sender domain " << sender.domain() << " does not match "
1940 << sock_.them_address_literal();
1942 return true;
1945 if (!verify_sender_domain_(sender.domain(), error_msg)) {
1946 return false;
1949 return true;
1952 // this sender is the RFC5321 MAIL FROM: domain part
1953 bool Session::verify_sender_domain_(Domain const& sender,
1954 std::string& error_msg)
1956 if (sender.empty()) {
1957 // MAIL FROM:<>
1958 // is used to send bounce messages.
1959 return true;
1962 // Break sender domain into labels:
1964 std::vector<std::string> labels;
1965 boost::algorithm::split(labels, sender.ascii(),
1966 boost::algorithm::is_any_of("."));
1968 if (labels.size() < 2) { // This is not a valid domain.
1969 error_msg = fmt::format("{} invalid syntax", sender.ascii());
1970 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1971 return false;
1974 if (lookup_domain(block_, sender)) {
1975 error_msg = fmt::format("SPF sender domain ({}) is blocked",
1976 spf_sender_domain_.ascii());
1977 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1978 return false;
1981 if (spf_result_ == SPF::Result::PASS) {
1982 if (allow_.contains(spf_sender_domain_.ascii())) {
1983 LOG(INFO) << "sender " << spf_sender_domain_.ascii() << " allowed";
1984 return true;
1987 auto const reg_dom{
1988 tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
1989 if (reg_dom) {
1990 if (allow_.contains(reg_dom)) {
1991 LOG(INFO) << "sender registered domain \"" << reg_dom << "\" allowed";
1992 return true;
1997 LOG(INFO) << "sender \"" << sender << "\" not disallowed";
1998 return true;
2001 void Session::do_spf_check_(Mailbox const& sender)
2003 if (!sock_.has_peername()) {
2004 auto const ip_addr = "127.0.0.1"; // use localhost for local socket
2005 spf_received_ = fmt::format(
2006 "Received-SPF: pass ({}: allow-listed) client-ip={}; "
2007 "envelope-from={}; helo={};",
2008 server_id_(), ip_addr, sender.as_string(), client_identity_.ascii());
2009 spf_sender_domain_ = "localhost";
2010 return;
2013 auto const spf_srv = SPF::Server{server_id_().c_str()};
2014 auto spf_request = SPF::Request{spf_srv};
2016 if (IP4::is_address(sock_.them_c_str())) {
2017 spf_request.set_ipv4_str(sock_.them_c_str());
2019 else if (IP6::is_address(sock_.them_c_str())) {
2020 spf_request.set_ipv6_str(sock_.them_c_str());
2022 else {
2023 LOG(FATAL) << "bogus address " << sock_.them_address_literal() << ", "
2024 << sock_.them_c_str();
2027 auto const from{static_cast<std::string>(sender)};
2029 spf_request.set_env_from(from.c_str());
2030 spf_request.set_helo_dom(client_identity_.ascii().c_str());
2032 auto const spf_res{SPF::Response{spf_request}};
2033 spf_result_ = spf_res.result();
2034 spf_received_ = spf_res.received_spf();
2035 spf_sender_domain_ = spf_request.get_sender_dom();
2037 LOG(INFO) << "spf_received_ == " << spf_received_;
2039 if (spf_result_ == SPF::Result::FAIL) {
2040 LOG(INFO) << "FAIL " << spf_res.header_comment();
2042 else if (spf_result_ == SPF::Result::NEUTRAL) {
2043 LOG(INFO) << "NEUTRAL " << spf_res.header_comment();
2045 else if (spf_result_ == SPF::Result::PASS) {
2046 LOG(INFO) << "PASS " << spf_res.header_comment();
2048 else {
2049 LOG(INFO) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
2053 bool Session::verify_from_params_(parameters_t const& parameters)
2055 // Take a look at the optional parameters:
2056 for (auto const& [name, value] : parameters) {
2057 if (iequal(name, "BODY")) {
2058 if (iequal(value, "8BITMIME")) {
2059 // everything is cool, this is our default...
2061 else if (iequal(value, "7BIT")) {
2062 // nothing to see here, move along...
2064 else if (iequal(value, "BINARYMIME")) {
2065 binarymime_ = true;
2067 else {
2068 LOG(WARNING) << "unrecognized BODY type \"" << value << "\" requested";
2071 else if (iequal(name, "SMTPUTF8")) {
2072 if (!value.empty()) {
2073 LOG(WARNING) << "SMTPUTF8 parameter has a value: " << value;
2075 smtputf8_ = true;
2078 // else if (iequal(name, "PRDR")) {
2079 // LOG(INFO) << "using PRDR";
2080 // prdr_ = true;
2081 // }
2083 else if (iequal(name, "SIZE")) {
2084 if (value.empty()) {
2085 LOG(WARNING) << "SIZE parameter has no value.";
2087 else {
2088 try {
2089 auto const sz = stoull(value);
2090 if (sz > max_msg_size()) {
2091 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
2092 LOG(WARNING) << "SIZE parameter too large: " << sz;
2093 return false;
2096 catch (std::invalid_argument const& e) {
2097 LOG(WARNING) << "SIZE parameter has invalid value: " << value;
2099 catch (std::out_of_range const& e) {
2100 LOG(WARNING) << "SIZE parameter has out-of-range value: " << value;
2102 // I guess we just ignore bad size parameters.
2105 else if (iequal(name, "REQUIRETLS")) {
2106 if (!sock_.tls()) {
2107 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush;
2108 LOG(WARNING) << "REQUIRETLS needed";
2109 return false;
2112 else {
2113 LOG(WARNING) << "unrecognized 'MAIL FROM' parameter " << name << "="
2114 << value;
2118 return true;
2121 bool Session::verify_rcpt_params_(parameters_t const& parameters)
2123 // Take a look at the optional parameters:
2124 for (auto const& [name, value] : parameters) {
2125 if (iequal(name, "RRVS")) {
2126 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
2127 LOG(INFO) << name << "=" << value;
2129 else {
2130 LOG(WARNING) << "unrecognized 'RCPT TO' parameter " << name << "="
2131 << value;
2135 return true;
2138 // check recipient from RFC5321 RCPT TO:
2139 bool Session::verify_recipient_(Mailbox const& recipient)
2141 if ((recipient.local_part() == "Postmaster") && (recipient.domain() == "")) {
2142 LOG(INFO) << "magic Postmaster address";
2143 return true;
2146 auto const accepted_domain{[this, &recipient] {
2147 if (recipient.domain().is_address_literal()) {
2148 if (recipient.domain() != sock_.us_address_literal()) {
2149 LOG(WARNING) << "recipient.domain address " << recipient.domain()
2150 << " does not match ours " << sock_.us_address_literal();
2152 return false;
2155 return true;
2158 // Domains we accept mail for.
2159 if (accept_domains_.is_open()) {
2160 if (accept_domains_.contains(recipient.domain().ascii()) ||
2161 accept_domains_.contains(recipient.domain().utf8())) {
2162 return true;
2165 else {
2166 // If we have no list of domains to accept, at least take our own.
2167 if (recipient.domain() == server_id_()) {
2168 return true;
2172 return false;
2173 }()};
2175 if (!accepted_domain) {
2176 out_() << "550 5.7.1 relay access denied\r\n" << std::flush;
2177 LOG(WARNING) << "relay access denied for domain " << recipient.domain();
2178 return false;
2181 // Check for local addresses we reject.
2183 auto bad_recipients_db_name = config_path_ / "bad_recipients";
2184 CDB bad_recipients_db;
2185 if (bad_recipients_db.open(bad_recipients_db_name) &&
2186 bad_recipients_db.contains(recipient.local_part())) {
2187 out_() << "550 5.1.1 bad recipient " << recipient << "\r\n" << std::flush;
2188 LOG(WARNING) << "bad recipient " << recipient;
2189 return false;
2194 auto fail_db_name = config_path_ / "fail_554";
2195 if (fs::exists(fail_db_name)) {
2196 CDB fail_db;
2197 if (fail_db.open(fail_db_name) &&
2198 fail_db.contains(recipient.local_part())) {
2199 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2200 << "\r\n"
2201 << std::flush;
2202 LOG(WARNING) << "fail_554 recipient " << recipient;
2203 return false;
2209 auto temp_fail_db_name = config_path_ / "temp_fail";
2210 CDB temp_fail;
2211 if (temp_fail.open(temp_fail_db_name) &&
2212 temp_fail.contains(recipient.local_part())) {
2213 out_() << "432 4.3.0 recipient's incoming mail queue has been stopped\r\n"
2214 << std::flush;
2215 LOG(WARNING) << "temp fail for recipient " << recipient;
2216 return false;
2220 // Check for and act on magic "wait" address.
2222 using namespace boost::xpressive;
2224 sregex const rex = icase("wait-rcpt-") >> (secs_ = +_d);
2225 smatch what;
2227 if (regex_match(recipient.local_part(), what, rex) ||
2228 regex_match(recipient.local_part(), what, all_rex)) {
2229 auto const str = what[secs_].str();
2230 LOG(INFO) << "waiting at RCPT TO " << str << " seconds";
2231 long value = 0;
2232 std::from_chars(str.data(), str.data() + str.size(), value);
2233 google::FlushLogFiles(google::INFO);
2234 out_() << std::flush;
2235 sleep(value);
2236 LOG(INFO) << "done waiting";
2240 // This is a trap for a probe done by some senders to see if we
2241 // accept just any old local-part.
2242 if (!extensions_) {
2243 if (recipient.local_part().length() > 8) {
2244 out_() << "550 5.1.1 unknown recipient " << recipient << "\r\n"
2245 << std::flush;
2246 LOG(WARNING) << "unknown recipient for HELO " << recipient;
2247 return false;
2251 return true;