comes in plain
[ghsmtp.git] / Session.cpp
blob297d7ee01a79c7963caf8b8531e66912c76422de
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 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", reverse_path_);
721 // Received-SPF:
722 if (!spf_received_.empty()) {
723 fmt::format_to(std::back_inserter(headers), "{}\r\n", spf_received_);
726 // Received:
727 // <https://tools.ietf.org/html/rfc5321#section-4.4>
728 fmt::format_to(std::back_inserter(headers), "Received: from {}", client_identity_.utf8());
729 if (sock_.has_peername()) {
730 fmt::format_to(std::back_inserter(headers), " ({})", client_);
732 fmt::format_to(std::back_inserter(headers), "\r\n\tby {} with {} id {}", server_identity_.utf8(),
733 protocol, msg.id());
734 if (forward_path_.size()) {
735 fmt::format_to(std::back_inserter(headers), "\r\n\tfor <{}>", forward_path_[0]);
736 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
737 // “If the FOR clause appears, it MUST contain exactly one <path>
738 // entry, even when multiple RCPT commands have been given. Multiple
739 // <path>s raise some security issues and have been deprecated, see
740 // Section 7.2.”
741 // for (auto i = 1u; i < forward_path_.size(); ++i)
742 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
744 std::string const tls_info{sock_.tls_info()};
745 if (tls_info.length()) {
746 fmt::format_to(std::back_inserter(headers), "\r\n\t({})", tls_info);
748 fmt::format_to(std::back_inserter(headers), ";\r\n\t{}\r\n", msg.when());
750 return fmt::to_string(headers);
753 namespace {
754 bool lookup_domain(CDB& cdb, Domain const& domain)
756 if (!domain.empty()) {
757 if (cdb.contains(domain.ascii())) {
758 return true;
760 if (domain.is_unicode() && cdb.contains(domain.utf8())) {
761 return true;
764 return false;
766 } // namespace
768 std::tuple<Session::SpamStatus, std::string> Session::spam_status_()
770 if (spf_result_ == SPF::Result::FAIL && !ip_allowed_)
771 return {SpamStatus::spam, "SPF failed"};
773 // These should have already been rejected by verify_client_().
774 if ((reverse_path_.domain() == "localhost.local") ||
775 (reverse_path_.domain() == "localhost"))
776 return {SpamStatus::spam, "bogus reverse_path"};
778 std::vector<std::string> why_ham;
780 // Anything enciphered tastes a lot like ham.
781 if (sock_.tls())
782 why_ham.emplace_back("they used TLS");
784 if (spf_result_ == SPF::Result::PASS) {
785 if (lookup_domain(allow_, spf_sender_domain_)) {
786 why_ham.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
787 spf_sender_domain_.utf8()));
789 else {
790 auto tld_dom{tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
791 if (tld_dom && allow_.contains(tld_dom)) {
792 why_ham.emplace_back(fmt::format(
793 "SPF sender registered domain ({}) is allowed", tld_dom));
798 if (fcrdns_allowed_)
799 why_ham.emplace_back(
800 fmt::format("FCrDNS (or it's registered domain) is allowed"));
802 if (!why_ham.empty())
803 return {SpamStatus::ham,
804 fmt::format("{}", fmt::join(std::begin(why_ham), std::end(why_ham),
805 ", and "))};
807 return {SpamStatus::spam, "it's not ham"};
810 static std::string folder(Session::SpamStatus status,
811 std::vector<Mailbox> const& forward_path,
812 Mailbox const& reverse_path)
814 if (reverse_path ==
815 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
816 return ".Gmail";
818 if (reverse_path == Mailbox("ietf-smtp-bounces@ietf.org"))
819 return ".smtp";
821 struct assignment {
822 std::string_view local_part;
823 std::string_view folder;
826 assignment assignments[] = {
827 {"Emailcore", ".emailcore"},
828 {"bootstrappable", ".bootstrappable"},
829 {"coreboot.org", ".coreboot"},
830 {"dmarc", ".dmarc"},
831 {"dns-privacy", ".dns-privacy"},
832 {"fucking-facebook", ".FB"},
833 {"gene-ebay", ".EBay"},
834 {"i-hate-linked-in", ".linkedin"},
835 {"mailop", ".INBOX.mailop"},
836 {"modelfkeyboards.com", ""},
837 {"nest", ".INBOX.Nest"},
838 {"opendmarc-dev", ".dmarc"},
839 {"opendmarc-users", ".dmarc"},
840 {"theatlantic.com", ""},
841 {"time-nutz", ".time-nutz"},
842 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
845 for (auto ass : assignments) {
846 if (forward_path[0].local_part() == ass.local_part)
847 return std::string(ass.folder);
850 if (iends_with(forward_path[0].local_part(), "-at-duck"))
851 return ".JunkDuck";
853 if (status == Session::SpamStatus::spam)
854 return ".Junk";
856 return "";
859 bool Session::msg_new()
861 CHECK((state_ == xact_step::data) || (state_ == xact_step::bdat));
863 auto const& [status, reason]{spam_status_()};
865 LOG(INFO) << ((status == SpamStatus::ham) ? "ham since " : "spam since ")
866 << reason;
868 // All sources of ham get a fresh 5 minute timeout per message.
869 if (status == SpamStatus::ham) {
870 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr))
871 alarm(5 * 60);
874 msg_ = std::make_unique<MessageStore>();
876 if (!FLAGS_max_write)
877 FLAGS_max_write = max_msg_size();
879 try {
880 msg_->open(server_id_(), FLAGS_max_write,
881 folder(status, forward_path_, reverse_path_));
882 auto const hdrs{added_headers_(*(msg_.get()))};
883 msg_->write(hdrs);
885 // fmt::memory_buffer spam_status;
886 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
887 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
888 // msg_->write(spam_status.data(), spam_status.size());
890 LOG(INFO) << "Spam-Status: "
891 << ((status == SpamStatus::spam) ? "Yes" : "No") << ", "
892 << reason;
894 return true;
896 catch (std::system_error const& e) {
897 switch (errno) {
898 case ENOSPC:
899 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
900 LOG(ERROR) << "no space";
901 msg_->trash();
902 msg_.reset();
903 return false;
905 default:
906 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
907 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
908 LOG(ERROR) << e.what();
909 msg_->trash();
910 msg_.reset();
911 return false;
914 catch (std::exception const& e) {
915 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
916 LOG(ERROR) << e.what();
917 msg_->trash();
918 msg_.reset();
919 return false;
922 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
923 LOG(ERROR) << "msg_new failed with no exception caught";
924 msg_->trash();
925 msg_.reset();
926 return false;
929 bool Session::msg_write(char const* s, std::streamsize count)
931 if ((state_ != xact_step::data) && (state_ != xact_step::bdat))
932 return false;
934 if (!msg_)
935 return false;
937 try {
938 if (msg_->write(s, count))
939 return true;
941 catch (std::system_error const& e) {
942 switch (errno) {
943 case ENOSPC:
944 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
945 LOG(ERROR) << "no space";
946 msg_->trash();
947 msg_.reset();
948 return false;
950 default:
951 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
952 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
953 LOG(ERROR) << e.what();
954 msg_->trash();
955 msg_.reset();
956 return false;
959 catch (std::exception const& e) {
960 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
961 LOG(ERROR) << e.what();
962 msg_->trash();
963 msg_.reset();
964 return false;
967 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
968 LOG(ERROR) << "msg_write failed with no exception caught";
969 msg_->trash();
970 msg_.reset();
971 return false;
974 bool Session::data_start()
976 last_in_group_("DATA");
978 switch (state_) {
979 case xact_step::helo:
980 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
981 LOG(WARNING) << "'DATA' before HELO/EHLO"
982 << (sock_.has_peername() ? " from " : "") << client_;
983 return false;
984 case xact_step::mail:
985 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
986 LOG(WARNING) << "'DATA' before 'MAIL FROM'"
987 << (sock_.has_peername() ? " from " : "") << client_;
988 return false;
989 case xact_step::rcpt:
991 /******************************************************************
992 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
994 The DATA command can fail at only two points in the protocol exchange:
996 If there was no MAIL, or no RCPT, command, or all such commands were
997 rejected, the server MAY return a "command out of sequence" (503) or
998 "no valid recipients" (554) reply in response to the DATA command.
1000 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
1002 The additional restriction is that when there have been no successful
1003 RCPT commands in the mail transaction, the DATA command MUST fail
1004 with a 503 reply code.
1006 Therefore I will send the reply code that is valid for both, and
1007 do the same for the BDAT case.
1008 *******************************************************************/
1010 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1011 LOG(WARNING) << "no valid recipients"
1012 << (sock_.has_peername() ? " from " : "") << client_;
1013 return false;
1014 case xact_step::data: break;
1015 case xact_step::bdat:
1016 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
1017 LOG(WARNING) << "'DATA' during BDAT transfer"
1018 << (sock_.has_peername() ? " from " : "") << client_;
1019 return false;
1020 case xact_step::rset:
1021 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1022 LOG(WARNING) << "error state must be cleared with a RSET"
1023 << (sock_.has_peername() ? " from " : "") << client_;
1024 return false;
1027 if (binarymime_) {
1028 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
1029 << std::flush;
1030 LOG(WARNING) << "DATA does not support BINARYMIME";
1031 state_ = xact_step::rset; // RFC 3030 section 3 page 5
1032 return false;
1035 if (!msg_new()) {
1036 LOG(ERROR) << "msg_new() failed";
1037 return false;
1040 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush;
1041 LOG(INFO) << "DATA";
1042 return true;
1045 // bool Session::do_forward_(message::parsed& msg)
1046 // {
1047 // auto msg_fwd = msg;
1049 // // Generate a reply address
1050 // Reply::from_to reply;
1051 // reply.mail_from = msg_fwd.dmarc_from;
1052 // reply.rcpt_to_local_part = fwd_from_.local_part();
1054 // auto const reply_addr =
1055 // fmt::format("{}@{}", srs_.enc_reply(reply), server_id_());
1057 // auto const munging = false;
1059 // auto const sender = server_identity_.ascii().c_str();
1060 // auto const selector = FLAGS_selector.c_str();
1061 // auto const key_file =
1062 // (config_path_ / FLAGS_selector).replace_extension("private");
1063 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1065 // if (munging) {
1066 // auto const from_hdr =
1067 // fmt::format("From: \"{} via\" <@>", msg_fwd.dmarc_from, reply_addr);
1068 // message::rewrite_from_to(msg_fwd, from_hdr, "", sender, selector,
1069 // key_file);
1070 // }
1071 // else {
1072 // auto const reply_to_hdr = fmt::format("Reply-To: {}", reply_addr);
1073 // message::rewrite_from_to(msg_fwd, "", reply_to_hdr, sender, selector,
1074 // key_file);
1075 // }
1077 // // Forward it on
1078 // if (!send_.send(msg_fwd.as_string())) {
1079 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1080 // "stopped\r\n"
1081 // << std::flush;
1083 // LOG(ERROR) << "failed to send for " << fwd_path_;
1084 // return false;
1085 // }
1087 // LOG(INFO) << "successfully sent for " << fwd_path_;
1088 // return true;
1089 // }
1091 // bool Session::do_reply_(message::parsed& msg)
1092 // {
1093 // Mailbox to_mbx(rep_info_.mail_from);
1094 // Mailbox from_mbx(rep_info_.rcpt_to_local_part, server_identity_);
1096 // auto reply = std::make_unique<MessageStore>();
1097 // reply->open(server_id_(), FLAGS_max_write, ".Drafts");
1099 // auto const date{Now{}};
1100 // auto const pill{Pill{}};
1101 // auto const mid_str =
1102 // fmt::format("<{}.{}@{}>", date.sec(), pill, server_identity_);
1104 // fmt::memory_buffer bfr;
1106 // fmt::format_to(bfr, "From: <{}>\r\n", from_mbx);
1107 // fmt::format_to(bfr, "To: <{}>\r\n", to_mbx);
1109 // fmt::format_to(bfr, "Date: {}\r\n", date.c_str());
1111 // fmt::format_to(bfr, "Message-ID: {}\r\n", mid_str.c_str());
1113 // if (!msg.get_header(message::Subject).empty()) {
1114 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1115 // msg.get_header(message::Subject));
1116 // }
1117 // else {
1118 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1119 // "Reply to your message");
1120 // }
1122 // if (!msg.get_header(message::In_Reply_To).empty()) {
1123 // fmt::format_to(bfr, "{}: {}\r\n", message::In_Reply_To,
1124 // msg.get_header(message::In_Reply_To));
1125 // }
1127 // if (!msg.get_header(message::MIME_Version).empty() &&
1128 // msg.get_header(message::Content_Type).empty()) {
1129 // fmt::format_to(bfr, "{}: {}\r\n", message::MIME_Version,
1130 // msg.get_header(message::MIME_Version));
1131 // fmt::format_to(bfr, "{}: {}\r\n", message::Content_Type,
1132 // msg.get_header(message::Content_Type));
1133 // }
1135 // reply->write(fmt::to_string(bfr));
1137 // if (!msg.body.empty()) {
1138 // reply->write("\r\n");
1139 // reply->write(msg.body);
1140 // }
1142 // auto const msg_data = reply->freeze();
1143 // message::parsed msg_reply;
1144 // CHECK(msg_reply.parse(msg_data));
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 // message::dkim_sign(msg_reply, sender, selector, key_file);
1154 // if (!send_.send(msg_reply.as_string())) {
1155 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1156 // "stopped\r\n"
1157 // << std::flush;
1159 // LOG(ERROR) << "send failed for reply to " << to_mbx << " from " <<
1160 // from_mbx; return false;
1161 // }
1163 // LOG(INFO) << "successful reply to " << to_mbx << " from " << from_mbx;
1164 // return true;
1165 // }
1167 bool Session::do_deliver_()
1169 CHECK(msg_);
1171 // auto const sender = server_identity_.ascii().c_str();
1172 // auto const selector = FLAGS_selector.c_str();
1173 // auto const key_file =
1174 // (config_path_ / FLAGS_selector).replace_extension("private");
1175 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1177 try {
1178 // auto const msg_data = msg_->freeze();
1180 // message::parsed msg;
1182 // // Only deal in RFC-5322 Mail Objects.
1183 // bool const message_parsed = msg.parse(msg_data);
1184 // if (message_parsed) {
1186 // // remove any Return-Path
1187 // message::remove_delivery_headers(msg);
1189 // auto const authentic =
1190 // message_parsed &&
1191 // message::authentication(msg, sender, selector, key_file);
1193 // // write a new Return-Path
1194 // msg_->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_));
1196 // for (auto const h : msg.headers) {
1197 // msg_->write(h.as_string());
1198 // msg_->write("\r\n");
1199 // }
1200 // if (!msg.body.empty()) {
1201 // msg_->write("\r\n");
1202 // msg_->write(msg.body);
1203 // }
1205 msg_->deliver();
1207 // if (authentic && !fwd_path_.empty()) {
1208 // if (!do_forward_(msg))
1209 // return false;
1210 // }
1211 // if (authentic && !rep_info_.empty()) {
1212 // if (!do_reply_(msg))
1213 // return false;
1214 // }
1215 // }
1217 msg_->close();
1219 catch (std::system_error const& e) {
1220 switch (errno) {
1221 case ENOSPC:
1222 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
1223 LOG(ERROR) << "no space";
1224 msg_->trash();
1225 reset_();
1226 return false;
1228 default:
1229 out_() << "550 5.0.0 mail system error\r\n" << std::flush;
1230 if (errno)
1231 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
1232 LOG(ERROR) << e.what();
1233 msg_->trash();
1234 reset_();
1235 return false;
1239 return true;
1242 void Session::data_done()
1244 CHECK((state_ == xact_step::data));
1246 if (msg_ && msg_->size_error()) {
1247 data_size_error();
1248 return;
1251 // if (prdr_) {
1252 // out_() << "353\r\n";
1253 // for (auto fp : forward_path_) {
1254 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1255 // }
1256 // }
1258 // Check for and act on magic "wait" address.
1260 using namespace boost::xpressive;
1262 sregex const rex = icase("wait-data-") >> (secs_ = +_d);
1263 smatch what;
1265 for (auto fp : forward_path_) {
1266 if (regex_match(fp.local_part(), what, rex) ||
1267 regex_match(fp.local_part(), what, all_rex)) {
1268 auto const str = what[secs_].str();
1269 LOG(INFO) << "waiting at DATA " << str << " seconds";
1270 long value = 0;
1271 std::from_chars(str.data(), str.data() + str.size(), value);
1272 google::FlushLogFiles(google::INFO);
1273 out_() << std::flush;
1274 sleep(value);
1275 LOG(INFO) << "done waiting";
1280 if (do_deliver_()) {
1281 auto temp_fail_db_name = config_path_ / "temp_fail_data";
1282 CDB temp_fail;
1284 for (auto fp : forward_path_) {
1285 if (temp_fail.open(temp_fail_db_name) &&
1286 temp_fail.contains(fp.local_part())) {
1287 out_() << "450 4.2.2 Mailbox full.\r\n" << std::flush;
1288 LOG(WARNING) << "temp fail at DATA for recipient " << fp;
1289 reset_();
1290 return;
1295 // Check for addresses we reject after data.
1297 auto bad_recipients_db_name = config_path_ / "bad_recipients_data";
1298 CDB bad_recipients_db;
1299 if (bad_recipients_db.open(bad_recipients_db_name)) {
1300 for (auto fp : forward_path_) {
1301 if (bad_recipients_db.contains(fp.local_part())) {
1302 out_() << "550 5.1.1 bad recipient " << fp << "\r\n" << std::flush;
1303 LOG(WARNING) << "bad recipient " << fp;
1304 reset_();
1305 return;
1307 else {
1308 LOG(INFO) << "unbad recipient " << fp.local_part();
1312 else {
1313 LOG(WARNING) << "can't open bad_recipients_data";
1317 out_() << "250 2.0.0 DATA OK\r\n" << std::flush;
1318 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1319 << msg_->id();
1320 reset_();
1323 void Session::data_size_error()
1325 out_().clear(); // clear possible eof from input side
1326 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1327 if (msg_) {
1328 msg_->trash();
1330 LOG(WARNING) << "DATA size error";
1331 reset_();
1334 void Session::data_error()
1336 out_().clear(); // clear possible eof from input side
1337 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush;
1338 if (msg_) {
1339 msg_->trash();
1341 LOG(WARNING) << "DATA error";
1342 reset_();
1345 bool Session::bdat_start(size_t n)
1347 // In practice, this one gets pipelined.
1348 // last_in_group_("BDAT");
1350 switch (state_) {
1351 case xact_step::helo:
1352 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
1353 LOG(WARNING) << "'BDAT' before HELO/EHLO"
1354 << (sock_.has_peername() ? " from " : "") << client_;
1355 return false;
1356 case xact_step::mail:
1357 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
1358 LOG(WARNING) << "'BDAT' before 'MAIL FROM'"
1359 << (sock_.has_peername() ? " from " : "") << client_;
1360 return false;
1361 case xact_step::rcpt:
1362 // See comment in data_start()
1363 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1364 LOG(WARNING) << "no valid recipients"
1365 << (sock_.has_peername() ? " from " : "") << client_;
1366 return false;
1367 case xact_step::data: // first bdat
1368 break;
1369 case xact_step::bdat: return true;
1370 case xact_step::rset:
1371 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1372 LOG(WARNING) << "error state must be cleared with a RSET"
1373 << (sock_.has_peername() ? " from " : "") << client_;
1374 return false;
1377 state_ = xact_step::bdat;
1379 return msg_new();
1382 void Session::bdat_done(size_t n, bool last)
1384 if (state_ != xact_step::bdat) {
1385 bdat_seq_error();
1386 return;
1389 if (!msg_) {
1390 return;
1393 if (msg_->size_error()) {
1394 bdat_size_error();
1395 return;
1398 if (!last) {
1399 out_() << "250 2.0.0 BDAT " << n << " OK\r\n" << std::flush;
1400 LOG(INFO) << "BDAT " << n;
1401 return;
1404 // Check for and act on magic "wait" address.
1406 using namespace boost::xpressive;
1408 sregex const rex = icase("wait-bdat-") >> (secs_ = +_d);
1409 smatch what;
1411 for (auto fp : forward_path_) {
1412 if (regex_match(fp.local_part(), what, rex) ||
1413 regex_match(fp.local_part(), what, all_rex)) {
1414 auto const str = what[secs_].str();
1415 LOG(INFO) << "waiting at BDAT " << str << " seconds";
1416 long value = 0;
1417 std::from_chars(str.data(), str.data() + str.size(), value);
1418 google::FlushLogFiles(google::INFO);
1419 out_() << std::flush;
1420 sleep(value);
1421 LOG(INFO) << "done waiting";
1426 do_deliver_();
1428 out_() << "250 2.0.0 BDAT " << n << " LAST OK\r\n" << std::flush;
1429 LOG(INFO) << "BDAT " << n << " LAST";
1430 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1431 << msg_->id();
1432 reset_();
1435 void Session::bdat_size_error()
1437 out_().clear(); // clear possible eof from input side
1438 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1439 if (msg_) {
1440 msg_->trash();
1442 LOG(WARNING) << "BDAT size error";
1443 reset_();
1446 void Session::bdat_seq_error()
1448 out_().clear(); // clear possible eof from input side
1449 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush;
1450 if (msg_) {
1451 msg_->trash();
1453 LOG(WARNING) << "BDAT sequence error";
1454 reset_();
1457 void Session::bdat_io_error()
1459 out_().clear(); // clear possible eof from input side
1460 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush;
1461 if (msg_) {
1462 msg_->trash();
1464 LOG(WARNING) << "BDAT I/O error";
1465 reset_();
1468 void Session::rset()
1470 out_() << "250 2.1.5 RSET OK\r\n";
1471 // No flush RFC-2920 section 3.1, this could be part of a command group.
1472 LOG(INFO) << "RSET";
1473 reset_();
1476 void Session::noop(std::string_view str)
1478 last_in_group_("NOOP");
1479 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush;
1480 LOG(INFO) << "NOOP" << (str.length() ? " " : "") << str;
1483 void Session::vrfy(std::string_view str)
1485 last_in_group_("VRFY");
1486 out_() << "252 2.1.5 try it\r\n" << std::flush;
1487 LOG(INFO) << "VRFY" << (str.length() ? " " : "") << str;
1490 void Session::help(std::string_view str)
1492 if (iequal(str, "help\r\n")) {
1493 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush;
1495 else {
1496 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1497 << std::flush;
1499 LOG(INFO) << "HELP" << (str.length() ? " " : "") << str;
1502 void Session::quit()
1504 // send_.quit();
1505 // last_in_group_("QUIT");
1506 out_() << "221 2.0.0 closing connection\r\n" << std::flush;
1507 LOG(INFO) << "QUIT";
1508 exit_();
1511 void Session::auth()
1513 out_() << "454 4.7.0 authentication failure\r\n" << std::flush;
1514 LOG(INFO) << "AUTH";
1515 bad_host_("auth");
1518 void Session::error(std::string_view log_msg)
1520 out_() << "421 4.3.5 system error: " << log_msg << "\r\n" << std::flush;
1521 LOG(WARNING) << log_msg;
1524 void Session::cmd_unrecognized(std::string_view cmd)
1526 auto const escaped{esc(cmd)};
1527 LOG(WARNING) << "command unrecognized: \"" << escaped << "\"";
1529 if (++n_unrecognized_cmds_ >= Config::max_unrecognized_cmds) {
1530 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1531 << "\" exceeds limit\r\n"
1532 << std::flush;
1533 LOG(WARNING) << n_unrecognized_cmds_
1534 << " unrecognized commands is too many";
1535 exit_();
1538 out_() << "500 5.5.1 command unrecognized: \"" << escaped << "\"\r\n"
1539 << std::flush;
1542 void Session::bare_lf()
1544 // Error code used by Office 365.
1545 out_() << "554 5.6.11 bare LF\r\n" << std::flush;
1546 LOG(WARNING) << "bare LF";
1547 exit_();
1550 void Session::max_out()
1552 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1553 LOG(WARNING) << "message size maxed out";
1554 exit_();
1557 void Session::time_out()
1559 out_() << "421 4.4.2 time-out\r\n" << std::flush;
1560 LOG(WARNING) << "time-out" << (sock_.has_peername() ? " from " : "")
1561 << client_;
1562 exit_();
1565 void Session::starttls()
1567 last_in_group_("STARTTLS");
1568 if (sock_.tls()) {
1569 out_() << "554 5.5.1 TLS already active\r\n" << std::flush;
1570 LOG(WARNING) << "STARTTLS issued with TLS already active";
1572 else if (!extensions_) {
1573 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1574 << std::flush;
1575 LOG(WARNING) << "STARTTLS issued without using EHLO";
1577 else {
1578 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush;
1579 if (sock_.starttls_server(config_path_)) {
1580 reset_();
1581 max_msg_size(Config::max_msg_size_bro);
1582 LOG(INFO) << "STARTTLS " << sock_.tls_info();
1584 else {
1585 LOG(INFO) << "failed STARTTLS";
1590 void Session::exit_()
1592 // sock_.log_totals();
1594 timespec time_used{};
1595 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time_used);
1597 LOG(INFO) << "CPU time " << time_used.tv_sec << "." << std::setw(9)
1598 << std::setfill('0') << time_used.tv_nsec << " seconds";
1600 std::exit(EXIT_SUCCESS);
1603 /////////////////////////////////////////////////////////////////////////////
1605 // All of the verify_* functions send their own error messages back to
1606 // the client on failure, and return false.
1608 bool Session::verify_ip_address_(std::string& error_msg)
1610 auto ip_block_db_name = config_path_ / "ip-block";
1611 CDB ip_block;
1612 if (ip_block.open(ip_block_db_name) &&
1613 ip_block.contains(sock_.them_c_str())) {
1614 error_msg =
1615 fmt::format("IP address {} on static blocklist", sock_.them_c_str());
1616 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1617 return false;
1620 client_fcrdns_.clear();
1622 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1623 (sock_.them_address_literal() == IP6::loopback_literal)) {
1624 LOG(INFO) << "loopback address allowed";
1625 ip_allowed_ = true;
1626 client_fcrdns_.emplace_back("localhost");
1627 client_ = fmt::format("localhost {}", sock_.them_address_literal());
1628 return true;
1631 auto const fcrdns = DNS::fcrdns(res_, sock_.them_c_str());
1632 for (auto const& fcr : fcrdns) {
1633 client_fcrdns_.emplace_back(fcr);
1636 if (IP::is_private(sock_.them_address_literal())) {
1637 LOG(INFO) << "private address allowed";
1638 ip_allowed_ = true;
1639 client_ = sock_.them_address_literal();
1640 return true;
1643 if (!client_fcrdns_.empty()) {
1644 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1645 sock_.them_address_literal());
1646 // check allow list
1647 for (auto const& client_fcrdns : client_fcrdns_) {
1648 if (allow_.contains(client_fcrdns.ascii())) {
1649 LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1650 fcrdns_allowed_ = true;
1651 return true;
1653 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1654 if (tld) {
1655 if (allow_.contains(tld)) {
1656 LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1657 fcrdns_allowed_ = true;
1658 return true;
1662 // check blocklist
1663 for (auto const& client_fcrdns : client_fcrdns_) {
1664 if (block_.contains(client_fcrdns.ascii())) {
1665 error_msg =
1666 fmt::format("FCrDNS {} on static blocklist", client_fcrdns.ascii());
1667 out_() << "554 5.7.1 blocklisted\r\n" << std::flush;
1668 return false;
1671 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1672 if (tld) {
1673 if (block_.contains(tld)) {
1674 error_msg = fmt::format(
1675 "FCrDNS registered domain {} on static blocklist", tld);
1676 out_() << "554 5.7.1 blocklisted\r\n" << std::flush;
1677 return false;
1682 else {
1683 client_ = fmt::format("{}", sock_.them_address_literal());
1686 if (IP4::is_address(sock_.them_c_str())) {
1688 auto const reversed{IP4::reverse(sock_.them_c_str())};
1691 // Check with allow list.
1692 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1693 random_device_);
1695 for (auto wl : Config::wls) {
1696 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1697 if (q.has_record()) {
1698 using namespace boost::xpressive;
1700 auto const as = q.get_strings()[0];
1701 LOG(INFO) << "on allow list " << wl << " as " << as;
1703 mark_tag x_(1);
1704 mark_tag y_(2);
1705 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1706 smatch what;
1708 if (regex_match(as, what, rex)) {
1709 auto const x = what[x_].str();
1710 auto const y = what[y_].str();
1712 int value = 0;
1713 std::from_chars(y.data(), y.data() + y.size(), value);
1714 if (value > 0) {
1715 ip_allowed_ = true;
1716 LOG(INFO) << "allowed";
1720 LOG(INFO) << "Any A record skips check on block list";
1721 return true;
1726 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1727 std::shuffle(std::begin(Config::bls), std::end(Config::bls),
1728 random_device_);
1730 for (auto bl : Config::bls) {
1732 DNS::Query q(res_, DNS::RR_type::A, reversed + bl);
1733 if (q.has_record()) {
1734 auto const as = q.get_strings()[0];
1735 if (as == "127.0.1.1") {
1736 LOG(INFO) << "Query blocked by " << bl;
1738 else if (as == "127.0.0.10" || as == "127.0.0.11") {
1739 LOG(INFO) << "PBL listed, ignoring " << bl;
1741 else if (as == "127.255.255.252") {
1742 LOG(INFO) << "Typing error in DNSBL name " << bl;
1744 else if (as == "127.255.255.254") {
1745 LOG(INFO) << "Anonymous query through public resolver " << bl;
1747 else if (as == "127.255.255.255") {
1748 LOG(INFO) << "Excessive number of queries " << bl;
1750 else {
1751 error_msg = fmt::format("IP address {} blocked: {} returned {}",
1752 sock_.them_c_str(), bl, as);
1753 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1754 return false;
1758 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1761 LOG(INFO) << "IP address okay";
1762 return true;
1765 bool domain_blocked(DNS::Resolver& res, Domain const& identity)
1767 Domain lookup{fmt::format("{}.dbl.spamhaus.org", identity.ascii())};
1768 DNS::Query q(res, DNS::RR_type::A, lookup.ascii());
1769 if (q.has_record()) {
1770 auto const as = q.get_strings()[0];
1771 if (istarts_with(as, "127.0.1.")) {
1772 LOG(INFO) << "Domain " << identity << " blocked by spamhaus";
1773 return true;
1776 return false;
1779 // check the identity from HELO/EHLO
1780 bool Session::verify_client_(Domain const& client_identity,
1781 std::string& error_msg)
1783 if (!client_fcrdns_.empty()) {
1784 if (auto id = std::find(begin(client_fcrdns_), end(client_fcrdns_),
1785 client_identity);
1786 id != end(client_fcrdns_)) {
1787 // If the HELO ident is one of the FCrDNS names...
1788 if (id != begin(client_fcrdns_)) {
1789 // ...then rotate that one to the front of the list
1790 std::rotate(begin(client_fcrdns_), id, id + 1);
1792 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1793 sock_.them_address_literal());
1794 return true;
1796 LOG(INFO) << "claimed identity " << client_identity
1797 << " does NOT match any FCrDNS: ";
1798 for (auto const& client_fcrdns : client_fcrdns_) {
1799 LOG(INFO) << " " << client_fcrdns;
1803 // Bogus clients claim to be us or some local host.
1804 if (sock_.has_peername() && ((client_identity == server_identity_) ||
1805 (client_identity == "localhost") ||
1806 (client_identity == "localhost.localdomain"))) {
1808 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1809 (sock_.them_address_literal() == IP6::loopback_literal)) {
1810 return true;
1813 // Give 'em a pass.
1814 if (ip_allowed_) {
1815 LOG(INFO) << "allow-listed IP address can claim to be "
1816 << client_identity;
1817 return true;
1820 // Ease up in test mode.
1821 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1822 return true;
1825 error_msg = fmt::format("liar, claimed to be {}", client_identity.ascii());
1826 out_() << "550 5.7.1 liar\r\n" << std::flush;
1827 return false;
1830 std::vector<std::string> labels;
1831 boost::algorithm::split(labels, client_identity.ascii(),
1832 boost::algorithm::is_any_of("."));
1833 if (labels.size() < 2) {
1834 error_msg =
1835 fmt::format("claimed bogus identity {}", client_identity.ascii());
1836 out_() << "550 4.7.1 bogus identity\r\n" << std::flush;
1837 return false;
1838 // // Sometimes we may want to look at mail from non conforming
1839 // // sending systems.
1840 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1841 // << client_ << " claiming " << client_identity;
1842 // return true;
1845 if (lookup_domain(block_, client_identity)) {
1846 error_msg =
1847 fmt::format("claimed blocked identity {}", client_identity.ascii());
1848 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1849 return false;
1852 auto const tld{tld_db_.get_registered_domain(client_identity.ascii())};
1853 if (!tld) {
1854 // Sometimes we may want to look at mail from misconfigured
1855 // sending systems.
1856 // LOG(WARNING) << "claimed identity has no registered domain";
1857 // return true;
1859 else if (block_.contains(tld)) {
1860 error_msg =
1861 fmt::format("claimed identity has blocked registered domain {}", tld);
1862 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush;
1863 return false;
1866 if (domain_blocked(res_, client_identity) ||
1867 domain_blocked(res_, Domain(tld))) {
1868 error_msg = fmt::format("claimed identity {} blocked by spamhaus",
1869 client_identity.ascii());
1870 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1871 return false;
1874 DNS::Query q(res_, DNS::RR_type::A, client_identity.ascii());
1875 if (!q.has_record()) {
1876 LOG(WARNING) << "claimed identity " << client_identity.ascii()
1877 << " not DNS resolvable";
1880 // not otherwise objectionable
1881 return true;
1884 // check sender from RFC5321 MAIL FROM:
1885 bool Session::verify_sender_(Mailbox const& sender, std::string& error_msg)
1887 do_spf_check_(sender);
1889 std::string const sender_str{sender};
1891 if (sender.empty()) {
1892 // MAIL FROM:<>
1893 // is used to send bounce messages.
1894 return true;
1897 if (domain_blocked(res_, sender.domain())) {
1898 error_msg = fmt::format("{} sender domain blocked by spamhaus", sender_str);
1899 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1900 return false;
1903 auto bad_senders_db_name = config_path_ / "bad_senders";
1904 CDB bad_senders;
1905 if (bad_senders.open(bad_senders_db_name) &&
1906 bad_senders.contains(sender_str)) {
1907 error_msg = fmt::format("{} bad sender", sender_str);
1908 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1909 return false;
1912 // We don't accept mail /from/ a domain we are expecting to accept
1913 // mail for on an external network connection.
1915 if (sock_.them_address_literal() != sock_.us_address_literal()) {
1916 if ((accept_domains_.is_open() &&
1917 (accept_domains_.contains(sender.domain().ascii()) ||
1918 accept_domains_.contains(sender.domain().utf8()))) ||
1919 (sender.domain() == server_identity_)) {
1921 // Ease up in test mode.
1922 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1923 return true;
1925 out_() << "550 5.7.1 liar\r\n" << std::flush;
1926 error_msg = fmt::format("liar, claimed to be {}", sender.domain());
1927 return false;
1931 if (sender.domain().is_address_literal()) {
1932 if (sender.domain() != sock_.them_address_literal()) {
1933 LOG(WARNING) << "sender domain " << sender.domain() << " does not match "
1934 << sock_.them_address_literal();
1936 return true;
1939 if (!verify_sender_domain_(sender.domain(), error_msg)) {
1940 return false;
1943 return true;
1946 // this sender is the RFC5321 MAIL FROM: domain part
1947 bool Session::verify_sender_domain_(Domain const& sender,
1948 std::string& error_msg)
1950 if (sender.empty()) {
1951 // MAIL FROM:<>
1952 // is used to send bounce messages.
1953 return true;
1956 // Break sender domain into labels:
1958 std::vector<std::string> labels;
1959 boost::algorithm::split(labels, sender.ascii(),
1960 boost::algorithm::is_any_of("."));
1962 if (labels.size() < 2) { // This is not a valid domain.
1963 error_msg = fmt::format("{} invalid syntax", sender.ascii());
1964 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1965 return false;
1968 if (lookup_domain(block_, sender)) {
1969 error_msg =
1970 fmt::format("SPF sender domain ({}) is blocked", spf_sender_domain_);
1971 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1972 return false;
1975 if (spf_result_ == SPF::Result::PASS) {
1976 if (allow_.contains(spf_sender_domain_.ascii())) {
1977 LOG(INFO) << "sender " << spf_sender_domain_.ascii() << " allowed";
1978 return true;
1981 auto const reg_dom{
1982 tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
1983 if (reg_dom) {
1984 if (allow_.contains(reg_dom)) {
1985 LOG(INFO) << "sender registered domain \"" << reg_dom << "\" allowed";
1986 return true;
1991 LOG(INFO) << "sender \"" << sender << "\" not disallowed";
1992 return true;
1995 void Session::do_spf_check_(Mailbox const& sender)
1997 if (!sock_.has_peername()) {
1998 auto const ip_addr = "127.0.0.1"; // use localhost for local socket
1999 spf_received_ =
2000 fmt::format("Received-SPF: pass ({}: allow-listed) client-ip={}; "
2001 "envelope-from={}; helo={};",
2002 server_id_(), ip_addr, sender, client_identity_);
2003 spf_sender_domain_ = "localhost";
2004 return;
2007 auto const spf_srv = SPF::Server{server_id_().c_str()};
2008 auto spf_request = SPF::Request{spf_srv};
2010 if (IP4::is_address(sock_.them_c_str())) {
2011 spf_request.set_ipv4_str(sock_.them_c_str());
2013 else if (IP6::is_address(sock_.them_c_str())) {
2014 spf_request.set_ipv6_str(sock_.them_c_str());
2016 else {
2017 LOG(FATAL) << "bogus address " << sock_.them_address_literal() << ", "
2018 << sock_.them_c_str();
2021 auto const from{static_cast<std::string>(sender)};
2023 spf_request.set_env_from(from.c_str());
2024 spf_request.set_helo_dom(client_identity_.ascii().c_str());
2026 auto const spf_res{SPF::Response{spf_request}};
2027 spf_result_ = spf_res.result();
2028 spf_received_ = spf_res.received_spf();
2029 spf_sender_domain_ = spf_request.get_sender_dom();
2031 LOG(INFO) << "spf_received_ == " << spf_received_;
2033 if (spf_result_ == SPF::Result::FAIL) {
2034 LOG(INFO) << "FAIL " << spf_res.header_comment();
2036 else if (spf_result_ == SPF::Result::NEUTRAL) {
2037 LOG(INFO) << "NEUTRAL " << spf_res.header_comment();
2039 else if (spf_result_ == SPF::Result::PASS) {
2040 LOG(INFO) << "PASS " << spf_res.header_comment();
2042 else {
2043 LOG(INFO) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
2047 bool Session::verify_from_params_(parameters_t const& parameters)
2049 // Take a look at the optional parameters:
2050 for (auto const& [name, value] : parameters) {
2051 if (iequal(name, "BODY")) {
2052 if (iequal(value, "8BITMIME")) {
2053 // everything is cool, this is our default...
2055 else if (iequal(value, "7BIT")) {
2056 // nothing to see here, move along...
2058 else if (iequal(value, "BINARYMIME")) {
2059 binarymime_ = true;
2061 else {
2062 LOG(WARNING) << "unrecognized BODY type \"" << value << "\" requested";
2065 else if (iequal(name, "SMTPUTF8")) {
2066 if (!value.empty()) {
2067 LOG(WARNING) << "SMTPUTF8 parameter has a value: " << value;
2069 smtputf8_ = true;
2072 // else if (iequal(name, "PRDR")) {
2073 // LOG(INFO) << "using PRDR";
2074 // prdr_ = true;
2075 // }
2077 else if (iequal(name, "SIZE")) {
2078 if (value.empty()) {
2079 LOG(WARNING) << "SIZE parameter has no value.";
2081 else {
2082 try {
2083 auto const sz = stoull(value);
2084 if (sz > max_msg_size()) {
2085 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
2086 LOG(WARNING) << "SIZE parameter too large: " << sz;
2087 return false;
2090 catch (std::invalid_argument const& e) {
2091 LOG(WARNING) << "SIZE parameter has invalid value: " << value;
2093 catch (std::out_of_range const& e) {
2094 LOG(WARNING) << "SIZE parameter has out-of-range value: " << value;
2096 // I guess we just ignore bad size parameters.
2099 else if (iequal(name, "REQUIRETLS")) {
2100 if (!sock_.tls()) {
2101 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush;
2102 LOG(WARNING) << "REQUIRETLS needed";
2103 return false;
2106 else {
2107 LOG(WARNING) << "unrecognized 'MAIL FROM' parameter " << name << "="
2108 << value;
2112 return true;
2115 bool Session::verify_rcpt_params_(parameters_t const& parameters)
2117 // Take a look at the optional parameters:
2118 for (auto const& [name, value] : parameters) {
2119 if (iequal(name, "RRVS")) {
2120 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
2121 LOG(INFO) << name << "=" << value;
2123 else {
2124 LOG(WARNING) << "unrecognized 'RCPT TO' parameter " << name << "="
2125 << value;
2129 return true;
2132 // check recipient from RFC5321 RCPT TO:
2133 bool Session::verify_recipient_(Mailbox const& recipient)
2135 if ((recipient.local_part() == "Postmaster") && (recipient.domain() == "")) {
2136 LOG(INFO) << "magic Postmaster address";
2137 return true;
2140 auto const accepted_domain{[this, &recipient] {
2141 if (recipient.domain().is_address_literal()) {
2142 if (recipient.domain() != sock_.us_address_literal()) {
2143 LOG(WARNING) << "recipient.domain address " << recipient.domain()
2144 << " does not match ours " << sock_.us_address_literal();
2146 return false;
2149 return true;
2152 // Domains we accept mail for.
2153 if (accept_domains_.is_open()) {
2154 if (accept_domains_.contains(recipient.domain().ascii()) ||
2155 accept_domains_.contains(recipient.domain().utf8())) {
2156 return true;
2159 else {
2160 // If we have no list of domains to accept, at least take our own.
2161 if (recipient.domain() == server_id_()) {
2162 return true;
2166 return false;
2167 }()};
2169 if (!accepted_domain) {
2170 out_() << "550 5.7.1 relay access denied\r\n" << std::flush;
2171 LOG(WARNING) << "relay access denied for domain " << recipient.domain();
2172 return false;
2175 // Check for local addresses we reject.
2177 auto bad_recipients_db_name = config_path_ / "bad_recipients";
2178 CDB bad_recipients_db;
2179 if (bad_recipients_db.open(bad_recipients_db_name) &&
2180 bad_recipients_db.contains(recipient.local_part())) {
2181 out_() << "550 5.1.1 bad recipient " << recipient << "\r\n" << std::flush;
2182 LOG(WARNING) << "bad recipient " << recipient;
2183 return false;
2188 auto fail_db_name = config_path_ / "fail_554";
2189 if (fs::exists(fail_db_name)) {
2190 CDB fail_db;
2191 if (fail_db.open(fail_db_name) &&
2192 fail_db.contains(recipient.local_part())) {
2193 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2194 << "\r\n"
2195 << std::flush;
2196 LOG(WARNING) << "fail_554 recipient " << recipient;
2197 return false;
2203 auto temp_fail_db_name = config_path_ / "temp_fail";
2204 CDB temp_fail;
2205 if (temp_fail.open(temp_fail_db_name) &&
2206 temp_fail.contains(recipient.local_part())) {
2207 out_() << "432 4.3.0 recipient's incoming mail queue has been stopped\r\n"
2208 << std::flush;
2209 LOG(WARNING) << "temp fail for recipient " << recipient;
2210 return false;
2214 // Check for and act on magic "wait" address.
2216 using namespace boost::xpressive;
2218 sregex const rex = icase("wait-rcpt-") >> (secs_ = +_d);
2219 smatch what;
2221 if (regex_match(recipient.local_part(), what, rex) ||
2222 regex_match(recipient.local_part(), what, all_rex)) {
2223 auto const str = what[secs_].str();
2224 LOG(INFO) << "waiting at RCPT TO " << str << " seconds";
2225 long value = 0;
2226 std::from_chars(str.data(), str.data() + str.size(), value);
2227 google::FlushLogFiles(google::INFO);
2228 out_() << std::flush;
2229 sleep(value);
2230 LOG(INFO) << "done waiting";
2234 // This is a trap for a probe done by some senders to see if we
2235 // accept just any old local-part.
2236 if (!extensions_) {
2237 if (recipient.local_part().length() > 8) {
2238 out_() << "550 5.1.1 unknown recipient " << recipient << "\r\n"
2239 << std::flush;
2240 LOG(WARNING) << "unknown recipient for HELO " << recipient;
2241 return false;
2245 return true;