ignore "Anonymous query through public resolver" errors
[ghsmtp.git] / Session.cpp
blobb4251b7b3eb421526fe583088ee9183a567d682e
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>
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 - ghsmtp\r\n" << std::flush;
345 if (sock_.input_ready(Config::greeting_wait)) {
346 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush;
347 LOG(INFO) << "input before full greeting from " << client_;
348 bad_host_("input before full greeting");
351 LOG(INFO) << "connect from " << client_;
354 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush;
356 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
357 alarm(2 * 60); // initial alarm
361 void Session::flush() { out_() << std::flush; }
363 void Session::last_in_group_(std::string_view verb)
365 if (sock_.input_ready(std::chrono::seconds(0))) {
366 LOG(WARNING) << "pipelining error; input ready processing " << verb;
370 void Session::check_for_pipeline_error_(std::string_view verb)
372 if (!(FLAGS_use_pipelining && extensions_)) {
373 if (sock_.input_ready(std::chrono::seconds(0))) {
374 LOG(WARNING) << "pipelining error; input ready processing " << verb;
379 void Session::lo_(char const* verb, std::string_view client_identity)
381 last_in_group_(verb);
382 reset_();
384 if (client_identity_ != client_identity) {
385 client_identity_ = client_identity;
387 std::string error_msg;
388 if (!verify_client_(client_identity_, error_msg)) {
389 LOG(INFO) << "client identity blocked: " << error_msg;
390 bad_host_(error_msg.c_str());
394 if (*verb == 'H') {
395 extensions_ = false;
396 out_() << "250 " << server_id_() << "\r\n";
399 if (*verb == 'E') {
400 extensions_ = true;
402 if (sock_.has_peername()) {
403 out_() << "250-" << server_id_() << " at your service, " << client_
404 << "\r\n";
406 else {
407 out_() << "250-" << server_id_() << "\r\n";
410 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
411 out_() << "250-8BITMIME\r\n"; // RFC 6152
413 if (FLAGS_use_rrvs) {
414 out_() << "250-RRVS\r\n"; // RFC 7293
417 // out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
419 if (sock_.tls()) {
420 // Check sasl sources for auth types.
421 // out_() << "250-AUTH PLAIN\r\n";
422 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
424 else {
425 // If we're not already TLS, offer TLS
426 out_() << "250-STARTTLS\r\n"; // RFC 3207
429 out_() << "250-ENHANCEDSTATUSCODES\r\n"; // RFC 2034
431 if (FLAGS_use_pipelining) {
432 out_() << "250-PIPELINING\r\n"; // RFC 2920
435 if (FLAGS_use_binarymime) {
436 out_() << "250-BINARYMIME\r\n"; // RFC 3030
439 if (FLAGS_use_chunking) {
440 out_() << "250-CHUNKING\r\n"; // RFC 3030
443 if (FLAGS_use_smtputf8) {
444 out_() << "250-SMTPUTF8\r\n"; // RFC 6531
447 out_() << "250 HELP\r\n";
450 out_() << std::flush;
452 if (sock_.has_peername()) {
453 if (std::find(begin(client_fcrdns_), end(client_fcrdns_),
454 client_identity_) != end(client_fcrdns_)) {
455 LOG(INFO) << verb << " " << client_identity << " from "
456 << sock_.them_address_literal();
458 else {
459 LOG(INFO) << verb << " " << client_identity << " from " << client_;
462 else {
463 LOG(INFO) << verb << " " << client_identity;
467 void Session::mail_from(Mailbox&& reverse_path, parameters_t const& parameters)
469 check_for_pipeline_error_("MAIL FROM");
471 switch (state_) {
472 case xact_step::helo:
473 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
474 LOG(WARNING) << "'MAIL FROM' before HELO/EHLO"
475 << (sock_.has_peername() ? " from " : "") << client_;
476 return;
477 case xact_step::mail: break;
478 case xact_step::rcpt:
479 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
480 LOG(WARNING) << "nested MAIL command"
481 << (sock_.has_peername() ? " from " : "") << client_;
482 return;
483 case xact_step::data:
484 case xact_step::bdat:
485 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush;
486 LOG(WARNING) << "nested MAIL command"
487 << (sock_.has_peername() ? " from " : "") << client_;
488 return;
489 case xact_step::rset:
490 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
491 LOG(WARNING) << "error state must be cleared with a RSET"
492 << (sock_.has_peername() ? " from " : "") << client_;
493 return;
496 if (!verify_from_params_(parameters)) {
497 return;
500 if (!smtputf8_ && !is_ascii(reverse_path.local_part())) {
501 LOG(WARNING) << "non ascii reverse_path \"" << reverse_path
502 << "\" without SMTPUTF8 paramater";
505 std::string error_msg;
506 if (!verify_sender_(reverse_path, error_msg)) {
507 LOG(INFO) << "verify sender failed: " << error_msg;
508 bad_host_(error_msg.c_str());
511 reverse_path_ = std::move(reverse_path);
512 // fwd_path_.clear();
513 // fwd_from_.clear();
514 forward_path_.clear();
515 out_() << "250 2.1.0 MAIL FROM OK\r\n";
516 // No flush RFC-2920 section 3.1, this could be part of a command group.
518 fmt::memory_buffer params;
519 for (auto const& [name, value] : parameters) {
520 fmt::format_to(std::back_inserter(params), " {}", name);
521 if (!value.empty()) {
522 fmt::format_to(std::back_inserter(params), "={}", value);
525 LOG(INFO) << "MAIL FROM:<" << reverse_path_ << ">" << fmt::to_string(params);
527 state_ = xact_step::rcpt;
530 // bool Session::forward_to_(std::string const& forward, Mailbox const& rcpt_to)
531 // {
532 // // If we're already forwarding or replying, reject
533 // if (!fwd_path_.empty() || !rep_info_.empty()) {
534 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
535 // stopped\r\n"
536 // << std::flush;
537 // LOG(WARNING) << "failed to forward to <" << forward
538 // << "> already forwarding or replying for: " << rcpt_to;
539 // return false;
540 // }
542 // fwd_path_ = Mailbox(forward);
543 // fwd_from_ = rcpt_to;
545 // // New bounce address
546 // Reply::from_to bounce;
547 // bounce.mail_from = reverse_path_.as_string();
549 // auto const new_bounce = srs_.enc_bounce(bounce, server_id_().c_str());
551 // auto const mail_from = Mailbox(new_bounce);
553 // std::string error_msg;
554 // if (!send_.mail_from_rcpt_to(res_, mail_from, fwd_path_, error_msg)) {
555 // out_() << error_msg << std::flush;
556 // LOG(WARNING) << "failed to forward <" << fwd_path_ << "> " << error_msg;
557 // return false;
558 // }
560 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> forwarding to == <" << fwd_path_
561 // << ">";
562 // return true;
563 // }
565 // bool Session::reply_to_(Reply::from_to const& reply_info, Mailbox const&
566 // rcpt_to)
567 // {
568 // // If we're already forwarding or replying, reject
569 // if (!fwd_path_.empty() || !rep_info_.empty()) {
570 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
571 // stopped\r\n"
572 // << std::flush;
573 // LOG(WARNING) << "failed to reply to <" << reply_info.mail_from
574 // << "> already forwarding or replying for: " << rcpt_to;
575 // return false;
576 // }
578 // rep_info_ = reply_info;
580 // Mailbox const from(rep_info_.rcpt_to_local_part, server_identity_);
581 // Mailbox const to(rep_info_.mail_from);
583 // std::string error_msg;
584 // if (!send_.mail_from_rcpt_to(res_, from, to, error_msg)) {
585 // out_() << error_msg << std::flush;
586 // LOG(WARNING) << "failed to reply from <" << from << "> to <" << to << ">
587 // "
588 // << error_msg;
589 // return false;
590 // }
592 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> is a reply to "
593 // << rep_info_.mail_from << " from " <<
594 // rep_info_.rcpt_to_local_part;
595 // return true;
596 // }
598 void Session::rcpt_to(Mailbox&& forward_path, parameters_t const& parameters)
600 check_for_pipeline_error_("RCPT TO");
602 switch (state_) {
603 case xact_step::helo:
604 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
605 LOG(WARNING) << "'RCPT TO' before HELO/EHLO"
606 << (sock_.has_peername() ? " from " : "") << client_;
607 return;
608 case xact_step::mail:
609 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
610 LOG(WARNING) << "'RCPT TO' before 'MAIL FROM'"
611 << (sock_.has_peername() ? " from " : "") << client_;
612 return;
613 case xact_step::rcpt:
614 case xact_step::data: break;
615 case xact_step::bdat:
616 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
617 LOG(WARNING) << "'RCPT TO' during BDAT transfer"
618 << (sock_.has_peername() ? " from " : "") << client_;
619 return;
620 case xact_step::rset:
621 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
622 LOG(WARNING) << "error state must be cleared with a RSET"
623 << (sock_.has_peername() ? " from " : "") << client_;
624 return;
627 if (!verify_rcpt_params_(parameters))
628 return;
630 if (!verify_recipient_(forward_path))
631 return;
633 if (!smtputf8_ && !is_ascii(forward_path.local_part())) {
634 LOG(WARNING) << "non ascii forward_path \"" << forward_path
635 << "\" without SMTPUTF8 paramater";
638 if (forward_path_.size() >= Config::max_recipients_per_message) {
639 out_() << "452 4.5.3 too many recipients\r\n" << std::flush;
640 LOG(WARNING) << "too many recipients <" << forward_path << ">";
641 return;
643 // no check for dups, postfix doesn't
644 forward_path_.emplace_back(std::move(forward_path));
646 Mailbox const& rcpt_to_mbx = forward_path_.back();
648 LOG(INFO) << "RCPT TO:<" << rcpt_to_mbx << ">";
650 // auto const rcpt_to_str = rcpt_to_mbx.as_string();
652 // if (auto reply = srs_.dec_reply(rcpt_to_mbx.local_part()); reply) {
653 // if (!reply_to_(*reply, rcpt_to_mbx))
654 // return;
655 // }
656 // else if (auto const forward = forward_.find(rcpt_to_str.c_str()); forward)
657 // {
658 // if (!forward_to_(*forward, rcpt_to_mbx))
659 // return;
660 // }
661 // else {
662 // LOG(INFO) << "RCPT TO:<" << rcpt_to_str << ">";
663 // }
665 // No flush RFC-2920 section 3.1, this could be part of a command group.
666 out_() << "250 2.1.5 RCPT TO OK\r\n";
668 state_ = xact_step::data;
671 // The headers Return-Path:, Received-SPF:, and Received: are returned
672 // as a string.
674 std::string Session::added_headers_(MessageStore const& msg)
676 auto const protocol{[this]() {
677 if (sock_.tls() && !extensions_) {
678 LOG(WARNING) << "TLS active without extensions";
680 // <https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#mail-parameters-5>
681 if (smtputf8_)
682 return sock_.tls() ? "UTF8SMTPS" : "UTF8SMTP";
683 else if (sock_.tls())
684 return "ESMTPS";
685 else if (extensions_)
686 return "ESMTP";
687 else
688 return "SMTP";
689 }()};
691 fmt::memory_buffer headers;
693 // Return-Path:
694 fmt::format_to(std::back_inserter(headers), "Return-Path: <{}>\r\n", reverse_path_);
696 // Received-SPF:
697 if (!spf_received_.empty()) {
698 fmt::format_to(std::back_inserter(headers), "{}\r\n", spf_received_);
701 // Received:
702 // <https://tools.ietf.org/html/rfc5321#section-4.4>
703 fmt::format_to(std::back_inserter(headers), "Received: from {}", client_identity_.utf8());
704 if (sock_.has_peername()) {
705 fmt::format_to(std::back_inserter(headers), " ({})", client_);
707 fmt::format_to(std::back_inserter(headers), "\r\n\tby {} with {} id {}", server_identity_.utf8(),
708 protocol, msg.id());
709 if (forward_path_.size()) {
710 fmt::format_to(std::back_inserter(headers), "\r\n\tfor <{}>", forward_path_[0]);
711 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
712 // “If the FOR clause appears, it MUST contain exactly one <path>
713 // entry, even when multiple RCPT commands have been given. Multiple
714 // <path>s raise some security issues and have been deprecated, see
715 // Section 7.2.”
716 // for (auto i = 1u; i < forward_path_.size(); ++i)
717 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
719 std::string const tls_info{sock_.tls_info()};
720 if (tls_info.length()) {
721 fmt::format_to(std::back_inserter(headers), "\r\n\t({})", tls_info);
723 fmt::format_to(std::back_inserter(headers), ";\r\n\t{}\r\n", msg.when());
725 return fmt::to_string(headers);
728 namespace {
729 bool lookup_domain(CDB& cdb, Domain const& domain)
731 if (!domain.empty()) {
732 if (cdb.contains(domain.ascii())) {
733 return true;
735 if (domain.is_unicode() && cdb.contains(domain.utf8())) {
736 return true;
739 return false;
741 } // namespace
743 std::tuple<Session::SpamStatus, std::string> Session::spam_status_()
745 if (spf_result_ == SPF::Result::FAIL && !ip_allowed_)
746 return {SpamStatus::spam, "SPF failed"};
748 // These should have already been rejected by verify_client_().
749 if ((reverse_path_.domain() == "localhost.local") ||
750 (reverse_path_.domain() == "localhost"))
751 return {SpamStatus::spam, "bogus reverse_path"};
753 std::vector<std::string> why_ham;
755 // Anything enciphered tastes a lot like ham.
756 if (sock_.tls())
757 why_ham.emplace_back("they used TLS");
759 if (spf_result_ == SPF::Result::PASS) {
760 if (lookup_domain(allow_, spf_sender_domain_)) {
761 why_ham.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
762 spf_sender_domain_.utf8()));
764 else {
765 auto tld_dom{tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
766 if (tld_dom && allow_.contains(tld_dom)) {
767 why_ham.emplace_back(fmt::format(
768 "SPF sender registered domain ({}) is allowed", tld_dom));
773 if (fcrdns_allowed_)
774 why_ham.emplace_back(
775 fmt::format("FCrDNS (or it's registered domain) is allowed"));
777 if (!why_ham.empty())
778 return {SpamStatus::ham,
779 fmt::format("{}", fmt::join(std::begin(why_ham), std::end(why_ham),
780 ", and "))};
782 return {SpamStatus::spam, "it's not ham"};
785 static std::string folder(Session::SpamStatus status,
786 std::vector<Mailbox> const& forward_path,
787 Mailbox const& reverse_path)
789 if (reverse_path ==
790 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
791 return ".Gmail";
793 if (reverse_path == Mailbox("ietf-smtp-bounces@ietf.org"))
794 return ".smtp";
796 struct assignment {
797 std::string_view local_part;
798 std::string_view folder;
801 assignment assignments[] = {
802 {"Emailcore", ".emailcore"},
803 {"bootstrappable", ".bootstrappable"},
804 {"coreboot.org", ".coreboot"},
805 {"dmarc", ".dmarc"},
806 {"dns-privacy", ".dns-privacy"},
807 {"fucking-facebook", ".FB"},
808 {"gene-ebay", ".EBay"},
809 {"i-hate-linked-in", ".linkedin"},
810 {"mailop", ".INBOX.mailop"},
811 {"modelfkeyboards.com", ""},
812 {"nest", ".INBOX.Nest"},
813 {"opendmarc-dev", ".dmarc"},
814 {"opendmarc-users", ".dmarc"},
815 {"theatlantic.com", ""},
816 {"time-nutz", ".time-nutz"},
817 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
820 for (auto ass : assignments) {
821 if (forward_path[0].local_part() == ass.local_part)
822 return std::string(ass.folder);
825 if (iends_with(forward_path[0].local_part(), "-at-duck"))
826 return ".JunkDuck";
828 if (status == Session::SpamStatus::spam)
829 return ".Junk";
831 return "";
834 bool Session::msg_new()
836 CHECK((state_ == xact_step::data) || (state_ == xact_step::bdat));
838 auto const& [status, reason]{spam_status_()};
840 LOG(INFO) << ((status == SpamStatus::ham) ? "ham since " : "spam since ")
841 << reason;
843 // All sources of ham get a fresh 5 minute timeout per message.
844 if (status == SpamStatus::ham) {
845 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr))
846 alarm(5 * 60);
849 msg_ = std::make_unique<MessageStore>();
851 if (!FLAGS_max_write)
852 FLAGS_max_write = max_msg_size();
854 try {
855 msg_->open(server_id_(), FLAGS_max_write,
856 folder(status, forward_path_, reverse_path_));
857 auto const hdrs{added_headers_(*(msg_.get()))};
858 msg_->write(hdrs);
860 // fmt::memory_buffer spam_status;
861 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
862 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
863 // msg_->write(spam_status.data(), spam_status.size());
865 LOG(INFO) << "Spam-Status: "
866 << ((status == SpamStatus::spam) ? "Yes" : "No") << ", "
867 << reason;
869 return true;
871 catch (std::system_error const& e) {
872 switch (errno) {
873 case ENOSPC:
874 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
875 LOG(ERROR) << "no space";
876 msg_->trash();
877 msg_.reset();
878 return false;
880 default:
881 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
882 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
883 LOG(ERROR) << e.what();
884 msg_->trash();
885 msg_.reset();
886 return false;
889 catch (std::exception const& e) {
890 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
891 LOG(ERROR) << e.what();
892 msg_->trash();
893 msg_.reset();
894 return false;
897 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
898 LOG(ERROR) << "msg_new failed with no exception caught";
899 msg_->trash();
900 msg_.reset();
901 return false;
904 bool Session::msg_write(char const* s, std::streamsize count)
906 if ((state_ != xact_step::data) && (state_ != xact_step::bdat))
907 return false;
909 if (!msg_)
910 return false;
912 try {
913 if (msg_->write(s, count))
914 return true;
916 catch (std::system_error const& e) {
917 switch (errno) {
918 case ENOSPC:
919 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
920 LOG(ERROR) << "no space";
921 msg_->trash();
922 msg_.reset();
923 return false;
925 default:
926 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
927 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
928 LOG(ERROR) << e.what();
929 msg_->trash();
930 msg_.reset();
931 return false;
934 catch (std::exception const& e) {
935 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
936 LOG(ERROR) << e.what();
937 msg_->trash();
938 msg_.reset();
939 return false;
942 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
943 LOG(ERROR) << "msg_write failed with no exception caught";
944 msg_->trash();
945 msg_.reset();
946 return false;
949 bool Session::data_start()
951 last_in_group_("DATA");
953 switch (state_) {
954 case xact_step::helo:
955 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
956 LOG(WARNING) << "'DATA' before HELO/EHLO"
957 << (sock_.has_peername() ? " from " : "") << client_;
958 return false;
959 case xact_step::mail:
960 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
961 LOG(WARNING) << "'DATA' before 'MAIL FROM'"
962 << (sock_.has_peername() ? " from " : "") << client_;
963 return false;
964 case xact_step::rcpt:
966 /******************************************************************
967 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
969 The DATA command can fail at only two points in the protocol exchange:
971 If there was no MAIL, or no RCPT, command, or all such commands were
972 rejected, the server MAY return a "command out of sequence" (503) or
973 "no valid recipients" (554) reply in response to the DATA command.
975 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
977 The additional restriction is that when there have been no successful
978 RCPT commands in the mail transaction, the DATA command MUST fail
979 with a 503 reply code.
981 Therefore I will send the reply code that is valid for both, and
982 do the same for the BDAT case.
983 *******************************************************************/
985 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
986 LOG(WARNING) << "no valid recipients"
987 << (sock_.has_peername() ? " from " : "") << client_;
988 return false;
989 case xact_step::data: break;
990 case xact_step::bdat:
991 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
992 LOG(WARNING) << "'DATA' during BDAT transfer"
993 << (sock_.has_peername() ? " from " : "") << client_;
994 return false;
995 case xact_step::rset:
996 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
997 LOG(WARNING) << "error state must be cleared with a RSET"
998 << (sock_.has_peername() ? " from " : "") << client_;
999 return false;
1002 if (binarymime_) {
1003 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
1004 << std::flush;
1005 LOG(WARNING) << "DATA does not support BINARYMIME";
1006 state_ = xact_step::rset; // RFC 3030 section 3 page 5
1007 return false;
1010 if (!msg_new()) {
1011 LOG(ERROR) << "msg_new() failed";
1012 return false;
1015 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush;
1016 LOG(INFO) << "DATA";
1017 return true;
1020 // bool Session::do_forward_(message::parsed& msg)
1021 // {
1022 // auto msg_fwd = msg;
1024 // // Generate a reply address
1025 // Reply::from_to reply;
1026 // reply.mail_from = msg_fwd.dmarc_from;
1027 // reply.rcpt_to_local_part = fwd_from_.local_part();
1029 // auto const reply_addr =
1030 // fmt::format("{}@{}", srs_.enc_reply(reply), server_id_());
1032 // auto const munging = false;
1034 // auto const sender = server_identity_.ascii().c_str();
1035 // auto const selector = FLAGS_selector.c_str();
1036 // auto const key_file =
1037 // (config_path_ / FLAGS_selector).replace_extension("private");
1038 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1040 // if (munging) {
1041 // auto const from_hdr =
1042 // fmt::format("From: \"{} via\" <@>", msg_fwd.dmarc_from, reply_addr);
1043 // message::rewrite_from_to(msg_fwd, from_hdr, "", sender, selector,
1044 // key_file);
1045 // }
1046 // else {
1047 // auto const reply_to_hdr = fmt::format("Reply-To: {}", reply_addr);
1048 // message::rewrite_from_to(msg_fwd, "", reply_to_hdr, sender, selector,
1049 // key_file);
1050 // }
1052 // // Forward it on
1053 // if (!send_.send(msg_fwd.as_string())) {
1054 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1055 // "stopped\r\n"
1056 // << std::flush;
1058 // LOG(ERROR) << "failed to send for " << fwd_path_;
1059 // return false;
1060 // }
1062 // LOG(INFO) << "successfully sent for " << fwd_path_;
1063 // return true;
1064 // }
1066 // bool Session::do_reply_(message::parsed& msg)
1067 // {
1068 // Mailbox to_mbx(rep_info_.mail_from);
1069 // Mailbox from_mbx(rep_info_.rcpt_to_local_part, server_identity_);
1071 // auto reply = std::make_unique<MessageStore>();
1072 // reply->open(server_id_(), FLAGS_max_write, ".Drafts");
1074 // auto const date{Now{}};
1075 // auto const pill{Pill{}};
1076 // auto const mid_str =
1077 // fmt::format("<{}.{}@{}>", date.sec(), pill, server_identity_);
1079 // fmt::memory_buffer bfr;
1081 // fmt::format_to(bfr, "From: <{}>\r\n", from_mbx);
1082 // fmt::format_to(bfr, "To: <{}>\r\n", to_mbx);
1084 // fmt::format_to(bfr, "Date: {}\r\n", date.c_str());
1086 // fmt::format_to(bfr, "Message-ID: {}\r\n", mid_str.c_str());
1088 // if (!msg.get_header(message::Subject).empty()) {
1089 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1090 // msg.get_header(message::Subject));
1091 // }
1092 // else {
1093 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1094 // "Reply to your message");
1095 // }
1097 // if (!msg.get_header(message::In_Reply_To).empty()) {
1098 // fmt::format_to(bfr, "{}: {}\r\n", message::In_Reply_To,
1099 // msg.get_header(message::In_Reply_To));
1100 // }
1102 // if (!msg.get_header(message::MIME_Version).empty() &&
1103 // msg.get_header(message::Content_Type).empty()) {
1104 // fmt::format_to(bfr, "{}: {}\r\n", message::MIME_Version,
1105 // msg.get_header(message::MIME_Version));
1106 // fmt::format_to(bfr, "{}: {}\r\n", message::Content_Type,
1107 // msg.get_header(message::Content_Type));
1108 // }
1110 // reply->write(fmt::to_string(bfr));
1112 // if (!msg.body.empty()) {
1113 // reply->write("\r\n");
1114 // reply->write(msg.body);
1115 // }
1117 // auto const msg_data = reply->freeze();
1118 // message::parsed msg_reply;
1119 // CHECK(msg_reply.parse(msg_data));
1121 // auto const sender = server_identity_.ascii().c_str();
1122 // auto const selector = FLAGS_selector.c_str();
1123 // auto const key_file =
1124 // (config_path_ / FLAGS_selector).replace_extension("private");
1125 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1127 // message::dkim_sign(msg_reply, sender, selector, key_file);
1129 // if (!send_.send(msg_reply.as_string())) {
1130 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1131 // "stopped\r\n"
1132 // << std::flush;
1134 // LOG(ERROR) << "send failed for reply to " << to_mbx << " from " <<
1135 // from_mbx; return false;
1136 // }
1138 // LOG(INFO) << "successful reply to " << to_mbx << " from " << from_mbx;
1139 // return true;
1140 // }
1142 bool Session::do_deliver_()
1144 CHECK(msg_);
1146 // auto const sender = server_identity_.ascii().c_str();
1147 // auto const selector = FLAGS_selector.c_str();
1148 // auto const key_file =
1149 // (config_path_ / FLAGS_selector).replace_extension("private");
1150 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1152 try {
1153 // auto const msg_data = msg_->freeze();
1155 // message::parsed msg;
1157 // // Only deal in RFC-5322 Mail Objects.
1158 // bool const message_parsed = msg.parse(msg_data);
1159 // if (message_parsed) {
1161 // // remove any Return-Path
1162 // message::remove_delivery_headers(msg);
1164 // auto const authentic =
1165 // message_parsed &&
1166 // message::authentication(msg, sender, selector, key_file);
1168 // // write a new Return-Path
1169 // msg_->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_));
1171 // for (auto const h : msg.headers) {
1172 // msg_->write(h.as_string());
1173 // msg_->write("\r\n");
1174 // }
1175 // if (!msg.body.empty()) {
1176 // msg_->write("\r\n");
1177 // msg_->write(msg.body);
1178 // }
1180 msg_->deliver();
1182 // if (authentic && !fwd_path_.empty()) {
1183 // if (!do_forward_(msg))
1184 // return false;
1185 // }
1186 // if (authentic && !rep_info_.empty()) {
1187 // if (!do_reply_(msg))
1188 // return false;
1189 // }
1190 // }
1192 msg_->close();
1194 catch (std::system_error const& e) {
1195 switch (errno) {
1196 case ENOSPC:
1197 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
1198 LOG(ERROR) << "no space";
1199 msg_->trash();
1200 reset_();
1201 return false;
1203 default:
1204 out_() << "550 5.0.0 mail system error\r\n" << std::flush;
1205 if (errno)
1206 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
1207 LOG(ERROR) << e.what();
1208 msg_->trash();
1209 reset_();
1210 return false;
1214 return true;
1217 void Session::data_done()
1219 CHECK((state_ == xact_step::data));
1221 if (msg_ && msg_->size_error()) {
1222 data_size_error();
1223 return;
1226 // if (prdr_) {
1227 // out_() << "353\r\n";
1228 // for (auto fp : forward_path_) {
1229 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1230 // }
1231 // }
1233 // Check for and act on magic "wait" address.
1235 using namespace boost::xpressive;
1237 sregex const rex = icase("wait-data-") >> (secs_ = +_d);
1238 smatch what;
1240 for (auto fp : forward_path_) {
1241 if (regex_match(fp.local_part(), what, rex) ||
1242 regex_match(fp.local_part(), what, all_rex)) {
1243 auto const str = what[secs_].str();
1244 LOG(INFO) << "waiting at DATA " << str << " seconds";
1245 long value = 0;
1246 std::from_chars(str.data(), str.data() + str.size(), value);
1247 google::FlushLogFiles(google::INFO);
1248 out_() << std::flush;
1249 sleep(value);
1250 LOG(INFO) << "done waiting";
1255 if (do_deliver_()) {
1256 auto temp_fail_db_name = config_path_ / "temp_fail_data";
1257 CDB temp_fail;
1259 for (auto fp : forward_path_) {
1260 if (temp_fail.open(temp_fail_db_name) &&
1261 temp_fail.contains(fp.local_part())) {
1262 out_() << "450 4.2.2 Mailbox full.\r\n" << std::flush;
1263 LOG(WARNING) << "temp fail at DATA for recipient " << fp;
1264 reset_();
1265 return;
1270 // Check for addresses we reject after data.
1272 auto bad_recipients_db_name = config_path_ / "bad_recipients_data";
1273 CDB bad_recipients_db;
1274 if (bad_recipients_db.open(bad_recipients_db_name)) {
1275 for (auto fp : forward_path_) {
1276 if (bad_recipients_db.contains(fp.local_part())) {
1277 out_() << "550 5.1.1 bad recipient " << fp << "\r\n" << std::flush;
1278 LOG(WARNING) << "bad recipient " << fp;
1279 reset_();
1280 return;
1282 else {
1283 LOG(INFO) << "unbad recipient " << fp.local_part();
1287 else {
1288 LOG(WARNING) << "can't open bad_recipients_data";
1292 out_() << "250 2.0.0 DATA OK\r\n" << std::flush;
1293 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1294 << msg_->id();
1295 reset_();
1298 void Session::data_size_error()
1300 out_().clear(); // clear possible eof from input side
1301 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1302 if (msg_) {
1303 msg_->trash();
1305 LOG(WARNING) << "DATA size error";
1306 reset_();
1309 void Session::data_error()
1311 out_().clear(); // clear possible eof from input side
1312 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush;
1313 if (msg_) {
1314 msg_->trash();
1316 LOG(WARNING) << "DATA error";
1317 reset_();
1320 bool Session::bdat_start(size_t n)
1322 // In practice, this one gets pipelined.
1323 // last_in_group_("BDAT");
1325 switch (state_) {
1326 case xact_step::helo:
1327 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
1328 LOG(WARNING) << "'BDAT' before HELO/EHLO"
1329 << (sock_.has_peername() ? " from " : "") << client_;
1330 return false;
1331 case xact_step::mail:
1332 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
1333 LOG(WARNING) << "'BDAT' before 'MAIL FROM'"
1334 << (sock_.has_peername() ? " from " : "") << client_;
1335 return false;
1336 case xact_step::rcpt:
1337 // See comment in data_start()
1338 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1339 LOG(WARNING) << "no valid recipients"
1340 << (sock_.has_peername() ? " from " : "") << client_;
1341 return false;
1342 case xact_step::data: // first bdat
1343 break;
1344 case xact_step::bdat: return true;
1345 case xact_step::rset:
1346 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1347 LOG(WARNING) << "error state must be cleared with a RSET"
1348 << (sock_.has_peername() ? " from " : "") << client_;
1349 return false;
1352 state_ = xact_step::bdat;
1354 return msg_new();
1357 void Session::bdat_done(size_t n, bool last)
1359 if (state_ != xact_step::bdat) {
1360 bdat_seq_error();
1361 return;
1364 if (!msg_) {
1365 return;
1368 if (msg_->size_error()) {
1369 bdat_size_error();
1370 return;
1373 if (!last) {
1374 out_() << "250 2.0.0 BDAT " << n << " OK\r\n" << std::flush;
1375 LOG(INFO) << "BDAT " << n;
1376 return;
1379 // Check for and act on magic "wait" address.
1381 using namespace boost::xpressive;
1383 sregex const rex = icase("wait-bdat-") >> (secs_ = +_d);
1384 smatch what;
1386 for (auto fp : forward_path_) {
1387 if (regex_match(fp.local_part(), what, rex) ||
1388 regex_match(fp.local_part(), what, all_rex)) {
1389 auto const str = what[secs_].str();
1390 LOG(INFO) << "waiting at BDAT " << str << " seconds";
1391 long value = 0;
1392 std::from_chars(str.data(), str.data() + str.size(), value);
1393 google::FlushLogFiles(google::INFO);
1394 out_() << std::flush;
1395 sleep(value);
1396 LOG(INFO) << "done waiting";
1401 do_deliver_();
1403 out_() << "250 2.0.0 BDAT " << n << " LAST OK\r\n" << std::flush;
1404 LOG(INFO) << "BDAT " << n << " LAST";
1405 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1406 << msg_->id();
1407 reset_();
1410 void Session::bdat_size_error()
1412 out_().clear(); // clear possible eof from input side
1413 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1414 if (msg_) {
1415 msg_->trash();
1417 LOG(WARNING) << "BDAT size error";
1418 reset_();
1421 void Session::bdat_seq_error()
1423 out_().clear(); // clear possible eof from input side
1424 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush;
1425 if (msg_) {
1426 msg_->trash();
1428 LOG(WARNING) << "BDAT sequence error";
1429 reset_();
1432 void Session::bdat_io_error()
1434 out_().clear(); // clear possible eof from input side
1435 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush;
1436 if (msg_) {
1437 msg_->trash();
1439 LOG(WARNING) << "BDAT I/O error";
1440 reset_();
1443 void Session::rset()
1445 out_() << "250 2.1.5 RSET OK\r\n";
1446 // No flush RFC-2920 section 3.1, this could be part of a command group.
1447 LOG(INFO) << "RSET";
1448 reset_();
1451 void Session::noop(std::string_view str)
1453 last_in_group_("NOOP");
1454 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush;
1455 LOG(INFO) << "NOOP" << (str.length() ? " " : "") << str;
1458 void Session::vrfy(std::string_view str)
1460 last_in_group_("VRFY");
1461 out_() << "252 2.1.5 try it\r\n" << std::flush;
1462 LOG(INFO) << "VRFY" << (str.length() ? " " : "") << str;
1465 void Session::help(std::string_view str)
1467 if (iequal(str, "help\r\n")) {
1468 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush;
1470 else {
1471 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1472 << std::flush;
1474 LOG(INFO) << "HELP" << (str.length() ? " " : "") << str;
1477 void Session::quit()
1479 // send_.quit();
1480 // last_in_group_("QUIT");
1481 out_() << "221 2.0.0 closing connection\r\n" << std::flush;
1482 LOG(INFO) << "QUIT";
1483 exit_();
1486 void Session::auth()
1488 out_() << "454 4.7.0 authentication failure\r\n" << std::flush;
1489 LOG(INFO) << "AUTH";
1490 bad_host_("auth");
1493 void Session::error(std::string_view log_msg)
1495 out_() << "421 4.3.5 system error: " << log_msg << "\r\n" << std::flush;
1496 LOG(WARNING) << log_msg;
1499 void Session::cmd_unrecognized(std::string_view cmd)
1501 auto const escaped{esc(cmd)};
1502 LOG(WARNING) << "command unrecognized: \"" << escaped << "\"";
1504 if (++n_unrecognized_cmds_ >= Config::max_unrecognized_cmds) {
1505 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1506 << "\" exceeds limit\r\n"
1507 << std::flush;
1508 LOG(WARNING) << n_unrecognized_cmds_
1509 << " unrecognized commands is too many";
1510 exit_();
1513 out_() << "500 5.5.1 command unrecognized: \"" << escaped << "\"\r\n"
1514 << std::flush;
1517 void Session::bare_lf()
1519 // Error code used by Office 365.
1520 out_() << "554 5.6.11 bare LF\r\n" << std::flush;
1521 LOG(WARNING) << "bare LF";
1522 exit_();
1525 void Session::max_out()
1527 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1528 LOG(WARNING) << "message size maxed out";
1529 exit_();
1532 void Session::time_out()
1534 out_() << "421 4.4.2 time-out\r\n" << std::flush;
1535 LOG(WARNING) << "time-out" << (sock_.has_peername() ? " from " : "")
1536 << client_;
1537 exit_();
1540 void Session::starttls()
1542 last_in_group_("STARTTLS");
1543 if (sock_.tls()) {
1544 out_() << "554 5.5.1 TLS already active\r\n" << std::flush;
1545 LOG(WARNING) << "STARTTLS issued with TLS already active";
1547 else if (!extensions_) {
1548 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1549 << std::flush;
1550 LOG(WARNING) << "STARTTLS issued without using EHLO";
1552 else {
1553 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush;
1554 if (sock_.starttls_server(config_path_)) {
1555 reset_();
1556 max_msg_size(Config::max_msg_size_bro);
1557 LOG(INFO) << "STARTTLS " << sock_.tls_info();
1559 else {
1560 LOG(INFO) << "failed STARTTLS";
1565 void Session::exit_()
1567 // sock_.log_totals();
1569 timespec time_used{};
1570 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time_used);
1572 LOG(INFO) << "CPU time " << time_used.tv_sec << "." << std::setw(9)
1573 << std::setfill('0') << time_used.tv_nsec << " seconds";
1575 std::exit(EXIT_SUCCESS);
1578 /////////////////////////////////////////////////////////////////////////////
1580 // All of the verify_* functions send their own error messages back to
1581 // the client on failure, and return false.
1583 bool Session::verify_ip_address_(std::string& error_msg)
1585 auto ip_block_db_name = config_path_ / "ip-block";
1586 CDB ip_block;
1587 if (ip_block.open(ip_block_db_name) &&
1588 ip_block.contains(sock_.them_c_str())) {
1589 error_msg =
1590 fmt::format("IP address {} on static blocklist", sock_.them_c_str());
1591 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1592 return false;
1595 client_fcrdns_.clear();
1597 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1598 (sock_.them_address_literal() == IP6::loopback_literal)) {
1599 LOG(INFO) << "loopback address allowed";
1600 ip_allowed_ = true;
1601 client_fcrdns_.emplace_back("localhost");
1602 client_ = fmt::format("localhost {}", sock_.them_address_literal());
1603 return true;
1606 auto const fcrdns = DNS::fcrdns(res_, sock_.them_c_str());
1607 for (auto const& fcr : fcrdns) {
1608 client_fcrdns_.emplace_back(fcr);
1611 if (IP::is_private(sock_.them_address_literal())) {
1612 LOG(INFO) << "private address allowed";
1613 ip_allowed_ = true;
1614 client_ = sock_.them_address_literal();
1615 return true;
1618 if (!client_fcrdns_.empty()) {
1619 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1620 sock_.them_address_literal());
1621 // check allow list
1622 for (auto const& client_fcrdns : client_fcrdns_) {
1623 if (allow_.contains(client_fcrdns.ascii())) {
1624 LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1625 fcrdns_allowed_ = true;
1626 return true;
1628 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1629 if (tld) {
1630 if (allow_.contains(tld)) {
1631 LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1632 fcrdns_allowed_ = true;
1633 return true;
1637 // check blocklist
1638 for (auto const& client_fcrdns : client_fcrdns_) {
1639 if (block_.contains(client_fcrdns.ascii())) {
1640 error_msg =
1641 fmt::format("FCrDNS {} on static blocklist", client_fcrdns.ascii());
1642 out_() << "554 5.7.1 blocklisted\r\n" << std::flush;
1643 return false;
1646 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1647 if (tld) {
1648 if (block_.contains(tld)) {
1649 error_msg = fmt::format(
1650 "FCrDNS registered domain {} on static blocklist", tld);
1651 out_() << "554 5.7.1 blocklisted\r\n" << std::flush;
1652 return false;
1657 else {
1658 client_ = fmt::format("{}", sock_.them_address_literal());
1661 if (IP4::is_address(sock_.them_c_str())) {
1663 auto const reversed{IP4::reverse(sock_.them_c_str())};
1666 // Check with allow list.
1667 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1668 random_device_);
1670 for (auto wl : Config::wls) {
1671 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1672 if (q.has_record()) {
1673 using namespace boost::xpressive;
1675 auto const as = q.get_strings()[0];
1676 LOG(INFO) << "on allow list " << wl << " as " << as;
1678 mark_tag x_(1);
1679 mark_tag y_(2);
1680 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1681 smatch what;
1683 if (regex_match(as, what, rex)) {
1684 auto const x = what[x_].str();
1685 auto const y = what[y_].str();
1687 int value = 0;
1688 std::from_chars(y.data(), y.data() + y.size(), value);
1689 if (value > 0) {
1690 ip_allowed_ = true;
1691 LOG(INFO) << "allowed";
1695 LOG(INFO) << "Any A record skips check on block list";
1696 return true;
1701 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1702 std::shuffle(std::begin(Config::bls), std::end(Config::bls),
1703 random_device_);
1705 for (auto bl : Config::bls) {
1707 DNS::Query q(res_, DNS::RR_type::A, reversed + bl);
1708 if (q.has_record()) {
1709 auto const as = q.get_strings()[0];
1710 if (as == "127.0.1.1") {
1711 LOG(INFO) << "Query blocked by " << bl;
1713 else if (as == "127.0.0.10" || as == "127.0.0.11") {
1714 LOG(INFO) << "PBL listed, ignoring " << bl;
1716 else if (as == "127.255.255.252") {
1717 LOG(INFO) << "Typing error in DNSBL name " << bl;
1719 else if (as == "127.255.255.254") {
1720 LOG(INFO) << "Anonymous query through public resolver " << bl;
1722 else if (as == "127.255.255.255") {
1723 LOG(INFO) << "Excessive number of queries " << bl;
1725 else {
1726 error_msg = fmt::format("IP address {} blocked: {} returned {}",
1727 sock_.them_c_str(), bl, as);
1728 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1729 return false;
1733 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1736 LOG(INFO) << "IP address okay";
1737 return true;
1740 bool domain_blocked(DNS::Resolver& res, Domain const& identity)
1742 Domain lookup{fmt::format("{}.dbl.spamhaus.org", identity.ascii())};
1743 DNS::Query q(res, DNS::RR_type::A, lookup.ascii());
1744 if (q.has_record()) {
1745 auto const as = q.get_strings()[0];
1746 if (istarts_with(as, "127.0.1.")) {
1747 LOG(INFO) << "Domain " << identity << " blocked by spamhaus";
1748 return true;
1751 return false;
1754 // check the identity from HELO/EHLO
1755 bool Session::verify_client_(Domain const& client_identity,
1756 std::string& error_msg)
1758 if (!client_fcrdns_.empty()) {
1759 if (auto id = std::find(begin(client_fcrdns_), end(client_fcrdns_),
1760 client_identity);
1761 id != end(client_fcrdns_)) {
1762 // If the HELO ident is one of the FCrDNS names...
1763 if (id != begin(client_fcrdns_)) {
1764 // ...then rotate that one to the front of the list
1765 std::rotate(begin(client_fcrdns_), id, id + 1);
1767 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1768 sock_.them_address_literal());
1769 return true;
1771 LOG(INFO) << "claimed identity " << client_identity
1772 << " does NOT match any FCrDNS: ";
1773 for (auto const& client_fcrdns : client_fcrdns_) {
1774 LOG(INFO) << " " << client_fcrdns;
1778 // Bogus clients claim to be us or some local host.
1779 if (sock_.has_peername() && ((client_identity == server_identity_) ||
1780 (client_identity == "localhost") ||
1781 (client_identity == "localhost.localdomain"))) {
1783 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1784 (sock_.them_address_literal() == IP6::loopback_literal)) {
1785 return true;
1788 // Give 'em a pass.
1789 if (ip_allowed_) {
1790 LOG(INFO) << "allow-listed IP address can claim to be "
1791 << client_identity;
1792 return true;
1795 // Ease up in test mode.
1796 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1797 return true;
1800 error_msg = fmt::format("liar, claimed to be {}", client_identity.ascii());
1801 out_() << "550 5.7.1 liar\r\n" << std::flush;
1802 return false;
1805 std::vector<std::string> labels;
1806 boost::algorithm::split(labels, client_identity.ascii(),
1807 boost::algorithm::is_any_of("."));
1808 if (labels.size() < 2) {
1809 error_msg =
1810 fmt::format("claimed bogus identity {}", client_identity.ascii());
1811 out_() << "550 4.7.1 bogus identity\r\n" << std::flush;
1812 return false;
1813 // // Sometimes we may want to look at mail from non conforming
1814 // // sending systems.
1815 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1816 // << client_ << " claiming " << client_identity;
1817 // return true;
1820 if (lookup_domain(block_, client_identity)) {
1821 error_msg =
1822 fmt::format("claimed blocked identity {}", client_identity.ascii());
1823 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1824 return false;
1827 auto const tld{tld_db_.get_registered_domain(client_identity.ascii())};
1828 if (!tld) {
1829 // Sometimes we may want to look at mail from misconfigured
1830 // sending systems.
1831 // LOG(WARNING) << "claimed identity has no registered domain";
1832 // return true;
1834 else if (block_.contains(tld)) {
1835 error_msg =
1836 fmt::format("claimed identity has blocked registered domain {}", tld);
1837 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush;
1838 return false;
1841 if (domain_blocked(res_, client_identity) ||
1842 domain_blocked(res_, Domain(tld))) {
1843 error_msg = fmt::format("claimed identity {} blocked by spamhaus",
1844 client_identity.ascii());
1845 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1846 return false;
1849 DNS::Query q(res_, DNS::RR_type::A, client_identity.ascii());
1850 if (!q.has_record()) {
1851 LOG(WARNING) << "claimed identity " << client_identity.ascii()
1852 << " not DNS resolvable";
1855 // not otherwise objectionable
1856 return true;
1859 // check sender from RFC5321 MAIL FROM:
1860 bool Session::verify_sender_(Mailbox const& sender, std::string& error_msg)
1862 do_spf_check_(sender);
1864 std::string const sender_str{sender};
1866 if (sender.empty()) {
1867 // MAIL FROM:<>
1868 // is used to send bounce messages.
1869 return true;
1872 if (domain_blocked(res_, sender.domain())) {
1873 error_msg = fmt::format("{} sender domain blocked by spamhaus", sender_str);
1874 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1875 return false;
1878 auto bad_senders_db_name = config_path_ / "bad_senders";
1879 CDB bad_senders;
1880 if (bad_senders.open(bad_senders_db_name) &&
1881 bad_senders.contains(sender_str)) {
1882 error_msg = fmt::format("{} bad sender", sender_str);
1883 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1884 return false;
1887 // We don't accept mail /from/ a domain we are expecting to accept
1888 // mail for on an external network connection.
1890 if (sock_.them_address_literal() != sock_.us_address_literal()) {
1891 if ((accept_domains_.is_open() &&
1892 (accept_domains_.contains(sender.domain().ascii()) ||
1893 accept_domains_.contains(sender.domain().utf8()))) ||
1894 (sender.domain() == server_identity_)) {
1896 // Ease up in test mode.
1897 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1898 return true;
1900 out_() << "550 5.7.1 liar\r\n" << std::flush;
1901 error_msg = fmt::format("liar, claimed to be {}", sender.domain());
1902 return false;
1906 if (sender.domain().is_address_literal()) {
1907 if (sender.domain() != sock_.them_address_literal()) {
1908 LOG(WARNING) << "sender domain " << sender.domain() << " does not match "
1909 << sock_.them_address_literal();
1911 return true;
1914 if (!verify_sender_domain_(sender.domain(), error_msg)) {
1915 return false;
1918 return true;
1921 // this sender is the RFC5321 MAIL FROM: domain part
1922 bool Session::verify_sender_domain_(Domain const& sender,
1923 std::string& error_msg)
1925 if (sender.empty()) {
1926 // MAIL FROM:<>
1927 // is used to send bounce messages.
1928 return true;
1931 // Break sender domain into labels:
1933 std::vector<std::string> labels;
1934 boost::algorithm::split(labels, sender.ascii(),
1935 boost::algorithm::is_any_of("."));
1937 if (labels.size() < 2) { // This is not a valid domain.
1938 error_msg = fmt::format("{} invalid syntax", sender.ascii());
1939 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1940 return false;
1943 if (lookup_domain(block_, sender)) {
1944 error_msg =
1945 fmt::format("SPF sender domain ({}) is blocked", spf_sender_domain_);
1946 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1947 return false;
1950 if (spf_result_ == SPF::Result::PASS) {
1951 if (allow_.contains(spf_sender_domain_.ascii())) {
1952 LOG(INFO) << "sender " << spf_sender_domain_.ascii() << " allowed";
1953 return true;
1956 auto const reg_dom{
1957 tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
1958 if (reg_dom) {
1959 if (allow_.contains(reg_dom)) {
1960 LOG(INFO) << "sender registered domain \"" << reg_dom << "\" allowed";
1961 return true;
1966 LOG(INFO) << "sender \"" << sender << "\" not disallowed";
1967 return true;
1970 void Session::do_spf_check_(Mailbox const& sender)
1972 if (!sock_.has_peername()) {
1973 auto const ip_addr = "127.0.0.1"; // use localhost for local socket
1974 spf_received_ =
1975 fmt::format("Received-SPF: pass ({}: allow-listed) client-ip={}; "
1976 "envelope-from={}; helo={};",
1977 server_id_(), ip_addr, sender, client_identity_);
1978 spf_sender_domain_ = "localhost";
1979 return;
1982 auto const spf_srv = SPF::Server{server_id_().c_str()};
1983 auto spf_request = SPF::Request{spf_srv};
1985 if (IP4::is_address(sock_.them_c_str())) {
1986 spf_request.set_ipv4_str(sock_.them_c_str());
1988 else if (IP6::is_address(sock_.them_c_str())) {
1989 spf_request.set_ipv6_str(sock_.them_c_str());
1991 else {
1992 LOG(FATAL) << "bogus address " << sock_.them_address_literal() << ", "
1993 << sock_.them_c_str();
1996 auto const from{static_cast<std::string>(sender)};
1998 spf_request.set_env_from(from.c_str());
1999 spf_request.set_helo_dom(client_identity_.ascii().c_str());
2001 auto const spf_res{SPF::Response{spf_request}};
2002 spf_result_ = spf_res.result();
2003 spf_received_ = spf_res.received_spf();
2004 spf_sender_domain_ = spf_request.get_sender_dom();
2006 LOG(INFO) << "spf_received_ == " << spf_received_;
2008 if (spf_result_ == SPF::Result::FAIL) {
2009 LOG(INFO) << "FAIL " << spf_res.header_comment();
2011 else if (spf_result_ == SPF::Result::NEUTRAL) {
2012 LOG(INFO) << "NEUTRAL " << spf_res.header_comment();
2014 else if (spf_result_ == SPF::Result::PASS) {
2015 LOG(INFO) << "PASS " << spf_res.header_comment();
2017 else {
2018 LOG(INFO) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
2022 bool Session::verify_from_params_(parameters_t const& parameters)
2024 // Take a look at the optional parameters:
2025 for (auto const& [name, value] : parameters) {
2026 if (iequal(name, "BODY")) {
2027 if (iequal(value, "8BITMIME")) {
2028 // everything is cool, this is our default...
2030 else if (iequal(value, "7BIT")) {
2031 // nothing to see here, move along...
2033 else if (iequal(value, "BINARYMIME")) {
2034 binarymime_ = true;
2036 else {
2037 LOG(WARNING) << "unrecognized BODY type \"" << value << "\" requested";
2040 else if (iequal(name, "SMTPUTF8")) {
2041 if (!value.empty()) {
2042 LOG(WARNING) << "SMTPUTF8 parameter has a value: " << value;
2044 smtputf8_ = true;
2047 // else if (iequal(name, "PRDR")) {
2048 // LOG(INFO) << "using PRDR";
2049 // prdr_ = true;
2050 // }
2052 else if (iequal(name, "SIZE")) {
2053 if (value.empty()) {
2054 LOG(WARNING) << "SIZE parameter has no value.";
2056 else {
2057 try {
2058 auto const sz = stoull(value);
2059 if (sz > max_msg_size()) {
2060 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
2061 LOG(WARNING) << "SIZE parameter too large: " << sz;
2062 return false;
2065 catch (std::invalid_argument const& e) {
2066 LOG(WARNING) << "SIZE parameter has invalid value: " << value;
2068 catch (std::out_of_range const& e) {
2069 LOG(WARNING) << "SIZE parameter has out-of-range value: " << value;
2071 // I guess we just ignore bad size parameters.
2074 else if (iequal(name, "REQUIRETLS")) {
2075 if (!sock_.tls()) {
2076 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush;
2077 LOG(WARNING) << "REQUIRETLS needed";
2078 return false;
2081 else {
2082 LOG(WARNING) << "unrecognized 'MAIL FROM' parameter " << name << "="
2083 << value;
2087 return true;
2090 bool Session::verify_rcpt_params_(parameters_t const& parameters)
2092 // Take a look at the optional parameters:
2093 for (auto const& [name, value] : parameters) {
2094 if (iequal(name, "RRVS")) {
2095 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
2096 LOG(INFO) << name << "=" << value;
2098 else {
2099 LOG(WARNING) << "unrecognized 'RCPT TO' parameter " << name << "="
2100 << value;
2104 return true;
2107 // check recipient from RFC5321 RCPT TO:
2108 bool Session::verify_recipient_(Mailbox const& recipient)
2110 if ((recipient.local_part() == "Postmaster") && (recipient.domain() == "")) {
2111 LOG(INFO) << "magic Postmaster address";
2112 return true;
2115 auto const accepted_domain{[this, &recipient] {
2116 if (recipient.domain().is_address_literal()) {
2117 if (recipient.domain() != sock_.us_address_literal()) {
2118 LOG(WARNING) << "recipient.domain address " << recipient.domain()
2119 << " does not match ours " << sock_.us_address_literal();
2121 return false;
2124 return true;
2127 // Domains we accept mail for.
2128 if (accept_domains_.is_open()) {
2129 if (accept_domains_.contains(recipient.domain().ascii()) ||
2130 accept_domains_.contains(recipient.domain().utf8())) {
2131 return true;
2134 else {
2135 // If we have no list of domains to accept, at least take our own.
2136 if (recipient.domain() == server_id_()) {
2137 return true;
2141 return false;
2142 }()};
2144 if (!accepted_domain) {
2145 out_() << "550 5.7.1 relay access denied\r\n" << std::flush;
2146 LOG(WARNING) << "relay access denied for domain " << recipient.domain();
2147 return false;
2150 // Check for local addresses we reject.
2152 auto bad_recipients_db_name = config_path_ / "bad_recipients";
2153 CDB bad_recipients_db;
2154 if (bad_recipients_db.open(bad_recipients_db_name) &&
2155 bad_recipients_db.contains(recipient.local_part())) {
2156 out_() << "550 5.1.1 bad recipient " << recipient << "\r\n" << std::flush;
2157 LOG(WARNING) << "bad recipient " << recipient;
2158 return false;
2163 auto fail_db_name = config_path_ / "fail_554";
2164 if (fs::exists(fail_db_name)) {
2165 CDB fail_db;
2166 if (fail_db.open(fail_db_name) &&
2167 fail_db.contains(recipient.local_part())) {
2168 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2169 << "\r\n"
2170 << std::flush;
2171 LOG(WARNING) << "fail_554 recipient " << recipient;
2172 return false;
2178 auto temp_fail_db_name = config_path_ / "temp_fail";
2179 CDB temp_fail;
2180 if (temp_fail.open(temp_fail_db_name) &&
2181 temp_fail.contains(recipient.local_part())) {
2182 out_() << "432 4.3.0 recipient's incoming mail queue has been stopped\r\n"
2183 << std::flush;
2184 LOG(WARNING) << "temp fail for recipient " << recipient;
2185 return false;
2189 // Check for and act on magic "wait" address.
2191 using namespace boost::xpressive;
2193 sregex const rex = icase("wait-rcpt-") >> (secs_ = +_d);
2194 smatch what;
2196 if (regex_match(recipient.local_part(), what, rex) ||
2197 regex_match(recipient.local_part(), what, all_rex)) {
2198 auto const str = what[secs_].str();
2199 LOG(INFO) << "waiting at RCPT TO " << str << " seconds";
2200 long value = 0;
2201 std::from_chars(str.data(), str.data() + str.size(), value);
2202 google::FlushLogFiles(google::INFO);
2203 out_() << std::flush;
2204 sleep(value);
2205 LOG(INFO) << "done waiting";
2209 // This is a trap for a probe done by some senders to see if we
2210 // accept just any old local-part.
2211 if (!extensions_) {
2212 if (recipient.local_part().length() > 8) {
2213 out_() << "550 5.1.1 unknown recipient " << recipient << "\r\n"
2214 << std::flush;
2215 LOG(WARNING) << "unknown recipient for HELO " << recipient;
2216 return false;
2220 return true;