support 2nd RCPT TO and PRDR
[ghsmtp.git] / Session.cpp
blob3d4b07ae152cd0094742ddce5ae89890b93ab2c6
1 #include <algorithm>
2 #include <charconv>
3 #include <iomanip>
4 #include <iostream>
5 #include <string>
6 #include <vector>
8 #include "DNS.hpp"
9 #include "Domain.hpp"
10 #include "IP.hpp"
11 #include "IP4.hpp"
12 #include "IP6.hpp"
13 #include "MessageStore.hpp"
14 #include "Session.hpp"
15 #include "esc.hpp"
16 #include "iequal.hpp"
17 #include "is_ascii.hpp"
18 #include "osutil.hpp"
20 #include <fmt/format.h>
21 #include <fmt/ostream.h>
23 #include <boost/algorithm/string/classification.hpp>
24 #include <boost/algorithm/string/split.hpp>
26 #include <boost/xpressive/xpressive.hpp>
28 #include <syslog.h>
30 #include <gflags/gflags.h>
32 using namespace std::string_literals;
34 namespace Config {
36 char const* wls[]{
37 "list.dnswl.org",
42 <https://www.dnswl.org/?page_id=15#query>
44 Return codes
46 The return codes are structured as 127.0.x.y, with “x” indicating the category
47 of an entry and “y” indicating how trustworthy an entry has been judged.
49 Categories (127.0.X.y):
51 2 – Financial services
52 3 – Email Service Providers
53 4 – Organisations (both for-profit [ie companies] and non-profit)
54 5 – Service/network providers
55 6 – Personal/private servers
56 7 – Travel/leisure industry
57 8 – Public sector/governments
58 9 – Media and Tech companies
59 10 – some special cases
60 11 – Education, academic
61 12 – Healthcare
62 13 – Manufacturing/Industrial
63 14 – Retail/Wholesale/Services
64 15 – Email Marketing Providers
65 20 – Added through Self Service without specific category
67 Trustworthiness / Score (127.0.x.Y):
69 0 = none – only avoid outright blocking (eg large ESP mailservers, -0.1)
70 1 = low – reduce chance of false positives (-1.0)
71 2 = medium – make sure to avoid false positives but allow override for clear
72 cases (-10.0) 3 = high – avoid override (-100.0).
74 The scores in parantheses are typical SpamAssassin scores.
76 Special return code 127.0.0.255
78 In cases where your nameserver issues more than 100’000 queries / 24 hours, you
79 may be blocked from further queries. The return code “127.0.0.255” indicates
80 this situation.
84 char const* bls[]{
85 "b.barracudacentral.org",
86 "sbl-xbl.spamhaus.org",
89 /*** Last octet from A record returned by blocklists ***
91 <https://www.spamhaus.org/faq/section/DNSBL%20Usage#200>
93 Spamhaus uses this general convention for return codes:
95 Return Code Description
96 127.0.0.0/24 Spamhaus IP Blocklists
97 127.0.1.0/24 Spamhaus Domain Blocklists
98 127.0.2.0/24 Spamhaus Zero Reputation Domains list
99 127.255.255.0/24 ERRORS (not implying a "listed" response)
101 Currently used return codes for Spamhaus public IP zones:
103 Return Code Zone Description
104 127.0.0.2 SBL Spamhaus SBL Data
105 127.0.0.3 SBL Spamhaus SBL CSS Data
106 127.0.0.4 XBL CBL Data
107 127.0.0.9 SBL Spamhaus DROP/EDROP Data
108 (in addition to 127.0.0.2, since 01-Jun-2016)
109 127.0.0.10 PBL ISP Maintained
110 127.0.0.11 PBL Spamhaus Maintained
112 127.0.0.5-7 are allocated to XBL for possible future use;
113 127.0.0.8 is allocated to SBL for possible future use.
115 From <https://www.spamhaus.org/faq/section/Spamhaus%20DBL#291>
117 Return Codes Data Source
118 127.0.1.2 spam domain
119 127.0.1.4 phish domain
120 127.0.1.5 malware domain
121 127.0.1.6 botnet C&C domain
122 127.0.1.102 abused legit spam
123 127.0.1.103 abused spammed redirector domain
124 127.0.1.104 abused legit phish
125 127.0.1.105 abused legit malware
126 127.0.1.106 abused legit botnet C&C
127 127.0.1.255 IP queries prohibited!
129 The following special codes indicate an error condition and should not
130 be taken to imply that the queried domain is "listed":
132 Return Code Description
133 127.255.255.252 Typing error in DNSBL name
134 127.255.255.254 Anonymous query through public resolver
135 127.255.255.255 Excessive number of queries
138 From <http://www.surbl.org/lists#multi>
140 last octet indicates which lists it belongs to. The bit positions in
141 that last octet for membership in the different lists are:
143 8 = listed on PH
144 16 = listed on MW
145 64 = listed on ABUSE
146 128 = listed on CR
151 char const* uribls[]{
152 "dbl.spamhaus.org",
153 "multi.uribl.com",
157 constexpr auto greeting_wait = std::chrono::seconds{6};
158 constexpr int max_recipients_per_message = 100;
159 constexpr int max_unrecognized_cmds = 20;
161 // Read timeout value gleaned from RFC-1123 section 5.3.2 and RFC-5321
162 // section 4.5.3.2.7.
163 constexpr auto read_timeout = std::chrono::minutes{5};
164 constexpr auto write_timeout = std::chrono::seconds{30};
165 } // namespace Config
167 DEFINE_bool(immortal, false, "don't set process timout");
169 DEFINE_uint64(max_read, 0, "max data to read");
170 DEFINE_uint64(max_write, 0, "max data to write");
172 DEFINE_bool(test_mode, false, "ease up on some checks");
174 DEFINE_bool(use_binarymime, true, "support BINARYMIME extension, RFC 3030");
175 DEFINE_bool(use_chunking, true, "support CHUNKING extension, RFC 3030");
176 DEFINE_bool(use_pipelining, true, "support PIPELINING extension, RFC 2920");
177 DEFINE_bool(use_rrvs, false, "support RRVS extension, RFC 7293");
178 DEFINE_bool(use_smtputf8, true, "support SMTPUTF8 extension, RFC 6531");
180 boost::xpressive::mark_tag secs_(1);
181 boost::xpressive::sregex const all_rex = boost::xpressive::icase("wait-all-") >>
182 (secs_ = +boost::xpressive::_d);
184 Session::Session(fs::path config_path,
185 std::function<void(void)> read_hook,
186 int fd_in,
187 int fd_out)
188 : config_path_(config_path)
189 , res_(config_path)
190 , sock_(fd_in, fd_out, read_hook, Config::read_timeout, Config::write_timeout)
191 //, send_(config_path, "smtp")
192 //, srs_(config_path)
194 auto accept_db_name = config_path_ / "accept_domains";
195 auto allow_db_name = config_path_ / "allow";
196 auto block_db_name = config_path_ / "block";
197 auto forward_db_name = config_path_ / "forward";
199 accept_domains_.open(accept_db_name);
200 allow_.open(allow_db_name);
201 block_.open(block_db_name);
202 forward_.open(forward_db_name);
204 if (sock_.has_peername() && !IP::is_private(sock_.us_c_str())) {
205 auto fcrdns = DNS::fcrdns(res_, sock_.us_c_str());
206 for (auto const& fcr : fcrdns) {
207 server_fcrdns_.emplace_back(fcr);
211 server_identity_ = [this] {
212 auto const id_from_env{getenv("GHSMTP_SERVER_ID")};
213 if (id_from_env)
214 return std::string{id_from_env};
216 auto const hostname{osutil::get_hostname()};
217 if (hostname.find('.') != std::string::npos)
218 return hostname;
220 if (!server_fcrdns_.empty()) {
221 // first result should be shortest
222 return server_fcrdns_.front().ascii();
225 auto const us_c_str = sock_.us_c_str();
226 if (us_c_str && !IP::is_private(us_c_str)) {
227 return IP::to_address_literal(us_c_str);
230 LOG(FATAL) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
231 return ""s;
232 }();
234 // send_.set_sender(server_identity_);
236 max_msg_size(Config::max_msg_size_initial);
239 void Session::max_msg_size(size_t max)
241 max_msg_size_ = max; // number to advertise via RFC 1870
243 if (FLAGS_max_read) {
244 sock_.set_max_read(FLAGS_max_read);
246 else {
247 auto const overhead = std::max(max / 10, size_t(2048));
248 sock_.set_max_read(max + overhead);
252 void Session::bad_host_(char const* msg) const
254 if (sock_.has_peername()) {
255 // On my systems, this pattern triggers a fail2ban rule that
256 // blocks connections from this IP address on port 25 for a few
257 // days. See <https://www.fail2ban.org/> for more info.
258 syslog(LOG_MAIL | LOG_WARNING, "bad host [%s] %s", sock_.them_c_str(), msg);
260 std::exit(EXIT_SUCCESS);
263 void Session::reset_()
265 // RSET does not force another EHLO/HELO, the one piece of per
266 // transaction data saved is client_identity_:
268 // client_identity_.clear(); <-- not cleared!
270 reverse_path_.clear();
271 forward_path_.clear();
272 spf_received_.clear();
273 // fwd_path_.clear();
274 // fwd_from_.clear();
275 // rep_info_.clear();
277 binarymime_ = false;
278 smtputf8_ = false;
279 // prdr_ = false;
281 if (msg_) {
282 msg_.reset();
285 max_msg_size(max_msg_size());
287 state_ = xact_step::mail;
288 // send_.rset();
291 // Return codes from connection establishment are 220 or 554, according
292 // to RFC 5321. That's it.
294 void Session::greeting()
296 CHECK(state_ == xact_step::helo);
298 if (sock_.has_peername()) {
299 close(2); // if we're a networked program, never send to stderr
301 std::string error_msg;
302 if (!verify_ip_address_(error_msg)) {
303 LOG(INFO) << error_msg;
304 bad_host_(error_msg.c_str());
307 /******************************************************************
308 <https://tools.ietf.org/html/rfc5321#section-4.3.1> says:
310 4.3. Sequencing of Commands and Replies
312 4.3.1. Sequencing Overview
314 The communication between the sender and receiver is an alternating
315 dialogue, controlled by the sender. As such, the sender issues a
316 command and the receiver responds with a reply. Unless other
317 arrangements are negotiated through service extensions, the sender
318 MUST wait for this response before sending further commands. One
319 important reply is the connection greeting. Normally, a receiver
320 will send a 220 "Service ready" reply when the connection is
321 completed. The sender SHOULD wait for this greeting message before
322 sending any commands.
324 So which is it?
326 “…the receiver responds with a reply.”
327 “…the sender MUST wait for this response…”
328 “One important reply is the connection greeting.”
329 “The sender SHOULD wait for this greeting…”
331 So is it MUST or SHOULD? I enforce MUST.
332 *******************************************************************/
334 // Wait a bit of time for pre-greeting traffic.
335 if (!(ip_allowed_ || fcrdns_allowed_)) {
336 if (sock_.input_ready(Config::greeting_wait)) {
337 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush;
338 LOG(INFO) << "input before any greeting from " << client_;
339 bad_host_("input before any greeting");
341 // Give a half greeting and wait again.
342 out_() << "220-" << server_id_() << " ESMTP slowstart - ghsmtp\r\n"
343 << std::flush;
344 if (sock_.input_ready(Config::greeting_wait)) {
345 out_() << "421 4.3.2 not accepting network messages\r\n" << std::flush;
346 LOG(INFO) << "input before full greeting from " << client_;
347 bad_host_("input before full greeting");
350 <https://www.rfc-editor.org/rfc/rfc5321#section-4.2>
352 An SMTP client MUST determine its actions only by the reply code, not
353 by the text (except for the "change of address" 251 and 551 and, if
354 necessary, 220, 221, and 421 replies); in the general case, any text,
355 including no text at all (although senders SHOULD NOT send bare
356 codes), MUST be acceptable. The space (blank) following the reply
357 code is considered part of the text. Whenever possible, a receiver-
358 SMTP SHOULD test the first digit (severity indication) of the reply
359 code.
361 Except the following chokes a lot of senders:
363 out_() << "220\r\n" << std::flush;
366 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush;
368 else {
369 out_() << "220 " << server_id_() << " ESMTP faststart - ghsmtp\r\n"
370 << std::flush;
373 else {
374 out_() << "220 " << server_id_() << " ESMTP - ghsmtp\r\n" << std::flush;
377 LOG(INFO) << "connect from " << client_;
379 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr)) {
380 alarm(2 * 60); // initial alarm
384 void Session::flush() { out_() << std::flush; }
386 void Session::last_in_group_(std::string_view verb)
388 if (sock_.input_ready(std::chrono::seconds(0))) {
389 LOG(WARNING) << "pipelining error; input ready processing " << verb;
393 void Session::check_for_pipeline_error_(std::string_view verb)
395 if (!(FLAGS_use_pipelining && extensions_)) {
396 if (sock_.input_ready(std::chrono::seconds(0))) {
397 LOG(WARNING) << "pipelining error; input ready processing " << verb;
402 void Session::lo_(char const* verb, std::string_view client_identity)
404 last_in_group_(verb);
405 reset_();
407 if (client_identity_ != client_identity) {
408 client_identity_ = client_identity;
410 std::string error_msg;
411 if (!verify_client_(client_identity_, error_msg)) {
412 LOG(INFO) << "client identity blocked: " << error_msg;
413 bad_host_(error_msg.c_str());
417 if (*verb == 'H') {
418 extensions_ = false;
419 out_() << "250 " << server_id_() << "\r\n";
422 if (*verb == 'E') {
423 extensions_ = true;
425 if (sock_.has_peername()) {
426 out_() << "250-" << server_id_() << " at your service, " << client_
427 << "\r\n";
429 else {
430 out_() << "250-" << server_id_() << "\r\n";
433 // https://datatracker.ietf.org/doc/draft-freed-smtp-limits/
434 out_() << "250-LIMITS RCPTMAX=" << Config::max_recipients_per_message
435 << "\r\n";
436 out_() << "250-SIZE " << max_msg_size() << "\r\n"; // RFC 1870
437 out_() << "250-8BITMIME\r\n"; // RFC 6152
439 if (FLAGS_use_rrvs) {
440 out_() << "250-RRVS\r\n"; // RFC 7293
443 // out_() << "250-PRDR\r\n"; // draft-hall-prdr-00.txt
445 if (sock_.tls()) {
446 // Check sasl sources for auth types.
447 // out_() << "250-AUTH PLAIN\r\n";
448 out_() << "250-REQUIRETLS\r\n"; // RFC 8689
450 else {
451 // If we're not already TLS, offer TLS
452 out_() << "250-STARTTLS\r\n"; // RFC 3207
455 out_() << "250-ENHANCEDSTATUSCODES\r\n"; // RFC 2034
457 if (FLAGS_use_pipelining) {
458 out_() << "250-PIPELINING\r\n"; // RFC 2920
461 if (FLAGS_use_binarymime) {
462 out_() << "250-BINARYMIME\r\n"; // RFC 3030
465 if (FLAGS_use_chunking) {
466 out_() << "250-CHUNKING\r\n"; // RFC 3030
469 if (FLAGS_use_smtputf8) {
470 out_() << "250-SMTPUTF8\r\n"; // RFC 6531
473 out_() << "250 HELP\r\n";
476 out_() << std::flush;
478 if (sock_.has_peername()) {
479 if (std::find(begin(client_fcrdns_), end(client_fcrdns_),
480 client_identity_) != end(client_fcrdns_)) {
481 LOG(INFO) << verb << " " << client_identity << " from "
482 << sock_.them_address_literal();
484 else {
485 LOG(INFO) << verb << " " << client_identity << " from " << client_;
488 else {
489 LOG(INFO) << verb << " " << client_identity;
493 void Session::mail_from(Mailbox&& reverse_path, parameters_t const& parameters)
495 check_for_pipeline_error_("MAIL FROM");
497 switch (state_) {
498 case xact_step::helo:
499 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
500 LOG(WARNING) << "'MAIL FROM' before HELO/EHLO"
501 << (sock_.has_peername() ? " from " : "") << client_;
502 return;
503 case xact_step::mail: break;
504 case xact_step::rcpt:
505 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
506 LOG(WARNING) << "nested MAIL command"
507 << (sock_.has_peername() ? " from " : "") << client_;
508 return;
509 case xact_step::data:
510 case xact_step::bdat:
511 out_() << "503 5.5.1 sequence error, expecting DATA/BDAT\r\n" << std::flush;
512 LOG(WARNING) << "nested MAIL command"
513 << (sock_.has_peername() ? " from " : "") << client_;
514 return;
515 case xact_step::rset:
516 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
517 LOG(WARNING) << "error state must be cleared with a RSET"
518 << (sock_.has_peername() ? " from " : "") << client_;
519 return;
522 if (!verify_from_params_(parameters)) {
523 return;
526 if (!smtputf8_ && !is_ascii(reverse_path.local_part())) {
527 LOG(WARNING) << "non ascii reverse_path \"" << reverse_path
528 << "\" without SMTPUTF8 paramater";
531 std::string error_msg;
532 if (!verify_sender_(reverse_path, error_msg)) {
533 LOG(INFO) << "verify sender failed: " << error_msg;
534 bad_host_(error_msg.c_str());
537 reverse_path_ = std::move(reverse_path);
538 // fwd_path_.clear();
539 // fwd_from_.clear();
540 forward_path_.clear();
541 out_() << "250 2.1.0 MAIL FROM OK\r\n";
542 // No flush RFC-2920 section 3.1, this could be part of a command group.
544 fmt::memory_buffer params;
545 for (auto const& [name, value] : parameters) {
546 fmt::format_to(std::back_inserter(params), " {}", name);
547 if (!value.empty()) {
548 fmt::format_to(std::back_inserter(params), "={}", value);
551 LOG(INFO) << "MAIL FROM:<" << reverse_path_ << ">" << fmt::to_string(params);
553 state_ = xact_step::rcpt;
556 // bool Session::forward_to_(std::string const& forward, Mailbox const& rcpt_to)
557 // {
558 // // If we're already forwarding or replying, reject
559 // if (!fwd_path_.empty() || !rep_info_.empty()) {
560 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
561 // stopped\r\n"
562 // << std::flush;
563 // LOG(WARNING) << "failed to forward to <" << forward
564 // << "> already forwarding or replying for: " << rcpt_to;
565 // return false;
566 // }
568 // fwd_path_ = Mailbox(forward);
569 // fwd_from_ = rcpt_to;
571 // // New bounce address
572 // Reply::from_to bounce;
573 // bounce.mail_from = reverse_path_.as_string();
575 // auto const new_bounce = srs_.enc_bounce(bounce, server_id_().c_str());
577 // auto const mail_from = Mailbox(new_bounce);
579 // std::string error_msg;
580 // if (!send_.mail_from_rcpt_to(res_, mail_from, fwd_path_, error_msg)) {
581 // out_() << error_msg << std::flush;
582 // LOG(WARNING) << "failed to forward <" << fwd_path_ << "> " << error_msg;
583 // return false;
584 // }
586 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> forwarding to == <" << fwd_path_
587 // << ">";
588 // return true;
589 // }
591 // bool Session::reply_to_(Reply::from_to const& reply_info, Mailbox const&
592 // rcpt_to)
593 // {
594 // // If we're already forwarding or replying, reject
595 // if (!fwd_path_.empty() || !rep_info_.empty()) {
596 // out_() << "432 4.3.0 Recipient's incoming mail queue has been
597 // stopped\r\n"
598 // << std::flush;
599 // LOG(WARNING) << "failed to reply to <" << reply_info.mail_from
600 // << "> already forwarding or replying for: " << rcpt_to;
601 // return false;
602 // }
604 // rep_info_ = reply_info;
606 // Mailbox const from(rep_info_.rcpt_to_local_part, server_identity_);
607 // Mailbox const to(rep_info_.mail_from);
609 // std::string error_msg;
610 // if (!send_.mail_from_rcpt_to(res_, from, to, error_msg)) {
611 // out_() << error_msg << std::flush;
612 // LOG(WARNING) << "failed to reply from <" << from << "> to <" << to << ">
613 // "
614 // << error_msg;
615 // return false;
616 // }
618 // LOG(INFO) << "RCPT TO:<" << rcpt_to << "> is a reply to "
619 // << rep_info_.mail_from << " from " <<
620 // rep_info_.rcpt_to_local_part;
621 // return true;
622 // }
624 void Session::rcpt_to(Mailbox&& forward_path, parameters_t const& parameters)
626 check_for_pipeline_error_("RCPT TO");
628 switch (state_) {
629 case xact_step::helo:
630 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
631 LOG(WARNING) << "'RCPT TO' before HELO/EHLO"
632 << (sock_.has_peername() ? " from " : "") << client_;
633 return;
634 case xact_step::mail:
635 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
636 LOG(WARNING) << "'RCPT TO' before 'MAIL FROM'"
637 << (sock_.has_peername() ? " from " : "") << client_;
638 return;
639 case xact_step::rcpt:
640 case xact_step::data: break;
641 case xact_step::bdat:
642 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
643 LOG(WARNING) << "'RCPT TO' during BDAT transfer"
644 << (sock_.has_peername() ? " from " : "") << client_;
645 return;
646 case xact_step::rset:
647 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
648 LOG(WARNING) << "error state must be cleared with a RSET"
649 << (sock_.has_peername() ? " from " : "") << client_;
650 return;
653 if (!verify_rcpt_params_(parameters))
654 return;
656 if (!verify_recipient_(forward_path))
657 return;
659 if (!smtputf8_ && !is_ascii(forward_path.local_part())) {
660 LOG(WARNING) << "non ascii forward_path \"" << forward_path
661 << "\" without SMTPUTF8 paramater";
664 if (forward_path_.size() >= Config::max_recipients_per_message) {
665 out_() << "452 4.5.3 too many recipients\r\n" << std::flush;
666 LOG(WARNING) << "too many recipients <" << forward_path << ">";
667 return;
669 // no check for dups, postfix doesn't
670 forward_path_.emplace_back(std::move(forward_path));
672 Mailbox const& rcpt_to_mbx = forward_path_.back();
674 LOG(INFO) << "RCPT TO:<" << rcpt_to_mbx << ">";
676 // auto const rcpt_to_str = rcpt_to_mbx.as_string();
678 // if (auto reply = srs_.dec_reply(rcpt_to_mbx.local_part()); reply) {
679 // if (!reply_to_(*reply, rcpt_to_mbx))
680 // return;
681 // }
682 // else if (auto const forward = forward_.find(rcpt_to_str.c_str()); forward)
683 // {
684 // if (!forward_to_(*forward, rcpt_to_mbx))
685 // return;
686 // }
687 // else {
688 // LOG(INFO) << "RCPT TO:<" << rcpt_to_str << ">";
689 // }
691 // No flush RFC-2920 section 3.1, this could be part of a command group.
692 out_() << "250 2.1.5 RCPT TO OK\r\n";
694 state_ = xact_step::data;
697 // The headers Return-Path:, Received-SPF:, and Received: are returned
698 // as a string.
700 std::string Session::added_headers_(MessageStore const& msg)
702 auto const protocol{[this]() {
703 if (sock_.tls() && !extensions_) {
704 LOG(WARNING) << "TLS active without extensions";
706 // <https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#mail-parameters-5>
707 if (smtputf8_)
708 return sock_.tls() ? "UTF8SMTPS" : "UTF8SMTP";
709 else if (sock_.tls())
710 return "ESMTPS";
711 else if (extensions_)
712 return "ESMTP";
713 else
714 return "SMTP";
715 }()};
717 fmt::memory_buffer headers;
719 // Return-Path:
720 fmt::format_to(std::back_inserter(headers), "Return-Path: <{}>\r\n",
721 reverse_path_.as_string());
723 // Received-SPF:
724 if (!spf_received_.empty()) {
725 fmt::format_to(std::back_inserter(headers), "{}\r\n", spf_received_);
728 // Received:
729 // <https://tools.ietf.org/html/rfc5321#section-4.4>
730 fmt::format_to(std::back_inserter(headers), "Received: from {}",
731 client_identity_.utf8());
732 if (sock_.has_peername()) {
733 fmt::format_to(std::back_inserter(headers), " ({})", client_);
735 fmt::format_to(std::back_inserter(headers), "\r\n\tby {} with {} id {}",
736 server_identity_.utf8(), protocol, msg.id().as_string_view());
737 if (forward_path_.size()) {
738 fmt::format_to(std::back_inserter(headers), "\r\n\tfor <{}>",
739 forward_path_[0].as_string());
740 // From <https://datatracker.ietf.org/doc/html/rfc5321#section-4.4>:
741 // “If the FOR clause appears, it MUST contain exactly one <path>
742 // entry, even when multiple RCPT commands have been given. Multiple
743 // <path>s raise some security issues and have been deprecated, see
744 // Section 7.2.”
745 // for (auto i = 1u; i < forward_path_.size(); ++i)
746 // fmt::format_to(headers, ",\r\n\t <{}>", forward_path_[i]);
748 std::string const tls_info{sock_.tls_info()};
749 if (tls_info.length()) {
750 fmt::format_to(std::back_inserter(headers), "\r\n\t({})", tls_info);
752 fmt::format_to(std::back_inserter(headers), ";\r\n\t{}\r\n",
753 msg.when().as_string_view());
755 return fmt::to_string(headers);
758 namespace {
759 bool lookup_domain(CDB& cdb, Domain const& domain)
761 if (!domain.empty()) {
762 if (cdb.contains(domain.ascii())) {
763 return true;
765 if (domain.is_unicode() && cdb.contains(domain.utf8())) {
766 return true;
769 return false;
771 } // namespace
773 std::tuple<Session::SpamStatus, std::string> Session::spam_status_()
775 if (spf_result_ == SPF::Result::FAIL && !ip_allowed_)
776 return {SpamStatus::spam, "SPF failed"};
778 // These should have already been rejected by verify_client_().
779 if ((reverse_path_.domain() == "localhost.local") ||
780 (reverse_path_.domain() == "localhost"))
781 return {SpamStatus::spam, "bogus reverse_path"};
783 std::vector<std::string> why_ham;
785 // Anything enciphered tastes a lot like ham.
786 if (sock_.tls())
787 why_ham.emplace_back("they used TLS");
789 if (spf_result_ == SPF::Result::PASS) {
790 if (lookup_domain(allow_, spf_sender_domain_)) {
791 why_ham.emplace_back(fmt::format("SPF sender domain ({}) is allowed",
792 spf_sender_domain_.utf8()));
794 else {
795 auto tld_dom{tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
796 if (tld_dom && allow_.contains(tld_dom)) {
797 why_ham.emplace_back(fmt::format(
798 "SPF sender registered domain ({}) is allowed", tld_dom));
803 if (fcrdns_allowed_)
804 why_ham.emplace_back(
805 fmt::format("FCrDNS (or it's registered domain) is allowed"));
807 if (!why_ham.empty())
808 return {SpamStatus::ham,
809 fmt::format("{}", fmt::join(std::begin(why_ham), std::end(why_ham),
810 ", and "))};
812 return {SpamStatus::spam, "it's not ham"};
815 static std::string folder(Session::SpamStatus status,
816 std::vector<Mailbox> const& forward_path,
817 Mailbox const& reverse_path)
819 if (reverse_path ==
820 Mailbox("gene.hightower+caf_=forwarded-gmail=digilicious.com@gmail.com"))
821 return ".Gmail";
823 if (reverse_path == Mailbox("ietf-smtp-bounces@ietf.org"))
824 return ".smtp";
826 struct assignment {
827 std::string_view local_part;
828 std::string_view folder;
831 assignment assignments[] = {
832 {"Emailcore", ".emailcore"},
833 {"bootstrappable", ".bootstrappable"},
834 {"coreboot.org", ".coreboot"},
835 {"dmarc", ".dmarc"},
836 {"dns-privacy", ".dns-privacy"},
837 {"fucking-facebook", ".FB"},
838 {"gene-ebay", ".EBay"},
839 {"i-hate-facebook", ".FB"},
840 {"i-hate-linked-in", ".linkedin"},
841 {"mailop", ".INBOX.mailop"},
842 {"modelfkeyboards.com", ""},
843 {"nest", ".INBOX.Nest"},
844 {"opendmarc-dev", ".dmarc"},
845 {"opendmarc-users", ".dmarc"},
846 {"postmaster-rua", ".INBOX.rua"},
847 {"quic=ietf.org", ".INBOX.quic"},
848 {"shadowserver-reports@digilicious.com", ".INBOX.shadowserver"},
849 {"theatlantic.com", ""},
850 {"time-nutz", ".time-nutz"},
851 {"zfsonlinux.topicbox.com", ".INBOX.zfs"},
854 for (auto ass : assignments) {
855 if (forward_path[0].local_part() == ass.local_part)
856 return std::string(ass.folder);
859 if (iends_with(forward_path[0].local_part(), "-at-duck"))
860 return ".JunkDuck";
862 if (status == Session::SpamStatus::spam)
863 return ".Junk";
865 return "";
868 bool Session::msg_new()
870 CHECK((state_ == xact_step::data) || (state_ == xact_step::bdat));
872 auto const& [status, reason]{spam_status_()};
874 LOG(INFO) << ((status == SpamStatus::ham) ? "ham since " : "spam since ")
875 << reason;
877 // All sources of ham get a fresh 5 minute timeout per message.
878 if (status == SpamStatus::ham) {
879 if ((!FLAGS_immortal) && (getenv("GHSMTP_IMMORTAL") == nullptr))
880 alarm(5 * 60);
883 msg_ = std::make_unique<MessageStore>();
885 if (!FLAGS_max_write)
886 FLAGS_max_write = max_msg_size();
888 try {
889 msg_->open(server_id_(), FLAGS_max_write,
890 folder(status, forward_path_, reverse_path_));
891 auto const hdrs{added_headers_(*(msg_.get()))};
892 msg_->write(hdrs);
894 // fmt::memory_buffer spam_status;
895 // fmt::format_to(spam_status, "X-Spam-Status: {}, {}\r\n",
896 // ((status == SpamStatus::spam) ? "Yes" : "No"), reason);
897 // msg_->write(spam_status.data(), spam_status.size());
899 LOG(INFO) << "Spam-Status: "
900 << ((status == SpamStatus::spam) ? "Yes" : "No") << ", "
901 << reason;
903 return true;
905 catch (std::system_error const& e) {
906 switch (errno) {
907 case ENOSPC:
908 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
909 LOG(ERROR) << "no space";
910 msg_->trash();
911 msg_.reset();
912 return false;
914 default:
915 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
916 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
917 LOG(ERROR) << e.what();
918 msg_->trash();
919 msg_.reset();
920 return false;
923 catch (std::exception const& e) {
924 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
925 LOG(ERROR) << e.what();
926 msg_->trash();
927 msg_.reset();
928 return false;
931 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
932 LOG(ERROR) << "msg_new failed with no exception caught";
933 msg_->trash();
934 msg_.reset();
935 return false;
938 bool Session::msg_write(char const* s, std::streamsize count)
940 if ((state_ != xact_step::data) && (state_ != xact_step::bdat))
941 return false;
943 if (!msg_)
944 return false;
946 try {
947 if (msg_->write(s, count))
948 return true;
950 catch (std::system_error const& e) {
951 switch (errno) {
952 case ENOSPC:
953 out_() << "452 4.3.1 insufficient system storage\r\n" << std::flush;
954 LOG(ERROR) << "no space";
955 msg_->trash();
956 msg_.reset();
957 return false;
959 default:
960 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
961 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
962 LOG(ERROR) << e.what();
963 msg_->trash();
964 msg_.reset();
965 return false;
968 catch (std::exception const& e) {
969 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
970 LOG(ERROR) << e.what();
971 msg_->trash();
972 msg_.reset();
973 return false;
976 out_() << "451 4.0.0 mail system error\r\n" << std::flush;
977 LOG(ERROR) << "msg_write failed with no exception caught";
978 msg_->trash();
979 msg_.reset();
980 return false;
983 bool Session::data_start()
985 last_in_group_("DATA");
987 switch (state_) {
988 case xact_step::helo:
989 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
990 LOG(WARNING) << "'DATA' before HELO/EHLO"
991 << (sock_.has_peername() ? " from " : "") << client_;
992 return false;
993 case xact_step::mail:
994 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
995 LOG(WARNING) << "'DATA' before 'MAIL FROM'"
996 << (sock_.has_peername() ? " from " : "") << client_;
997 return false;
998 case xact_step::rcpt:
1000 /******************************************************************
1001 <https://tools.ietf.org/html/rfc5321#section-3.3> says:
1003 The DATA command can fail at only two points in the protocol exchange:
1005 If there was no MAIL, or no RCPT, command, or all such commands were
1006 rejected, the server MAY return a "command out of sequence" (503) or
1007 "no valid recipients" (554) reply in response to the DATA command.
1009 However, <https://tools.ietf.org/html/rfc2033#section-4.2> says:
1011 The additional restriction is that when there have been no successful
1012 RCPT commands in the mail transaction, the DATA command MUST fail
1013 with a 503 reply code.
1015 Therefore I will send the reply code that is valid for both, and
1016 do the same for the BDAT case.
1017 *******************************************************************/
1019 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1020 LOG(WARNING) << "no valid recipients"
1021 << (sock_.has_peername() ? " from " : "") << client_;
1022 return false;
1023 case xact_step::data: break;
1024 case xact_step::bdat:
1025 out_() << "503 5.5.1 sequence error, expecting BDAT\r\n" << std::flush;
1026 LOG(WARNING) << "'DATA' during BDAT transfer"
1027 << (sock_.has_peername() ? " from " : "") << client_;
1028 return false;
1029 case xact_step::rset:
1030 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1031 LOG(WARNING) << "error state must be cleared with a RSET"
1032 << (sock_.has_peername() ? " from " : "") << client_;
1033 return false;
1036 if (binarymime_) {
1037 out_() << "503 5.5.1 sequence error, DATA does not support BINARYMIME\r\n"
1038 << std::flush;
1039 LOG(WARNING) << "DATA does not support BINARYMIME";
1040 state_ = xact_step::rset; // RFC 3030 section 3 page 5
1041 return false;
1044 if (!msg_new()) {
1045 LOG(ERROR) << "msg_new() failed";
1046 return false;
1049 out_() << "354 go, end with <CR><LF>.<CR><LF>\r\n" << std::flush;
1050 LOG(INFO) << "DATA";
1051 return true;
1054 // bool Session::do_forward_(message::parsed& msg)
1055 // {
1056 // auto msg_fwd = msg;
1058 // // Generate a reply address
1059 // Reply::from_to reply;
1060 // reply.mail_from = msg_fwd.dmarc_from;
1061 // reply.rcpt_to_local_part = fwd_from_.local_part();
1063 // auto const reply_addr =
1064 // fmt::format("{}@{}", srs_.enc_reply(reply), server_id_());
1066 // auto const munging = false;
1068 // auto const sender = server_identity_.ascii().c_str();
1069 // auto const selector = FLAGS_selector.c_str();
1070 // auto const key_file =
1071 // (config_path_ / FLAGS_selector).replace_extension("private");
1072 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1074 // if (munging) {
1075 // auto const from_hdr =
1076 // fmt::format("From: \"{} via\" <@>", msg_fwd.dmarc_from, reply_addr);
1077 // message::rewrite_from_to(msg_fwd, from_hdr, "", sender, selector,
1078 // key_file);
1079 // }
1080 // else {
1081 // auto const reply_to_hdr = fmt::format("Reply-To: {}", reply_addr);
1082 // message::rewrite_from_to(msg_fwd, "", reply_to_hdr, sender, selector,
1083 // key_file);
1084 // }
1086 // // Forward it on
1087 // if (!send_.send(msg_fwd.as_string())) {
1088 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1089 // "stopped\r\n"
1090 // << std::flush;
1092 // LOG(ERROR) << "failed to send for " << fwd_path_;
1093 // return false;
1094 // }
1096 // LOG(INFO) << "successfully sent for " << fwd_path_;
1097 // return true;
1098 // }
1100 // bool Session::do_reply_(message::parsed& msg)
1101 // {
1102 // Mailbox to_mbx(rep_info_.mail_from);
1103 // Mailbox from_mbx(rep_info_.rcpt_to_local_part, server_identity_);
1105 // auto reply = std::make_unique<MessageStore>();
1106 // reply->open(server_id_(), FLAGS_max_write, ".Drafts");
1108 // auto const date{Now{}};
1109 // auto const pill{Pill{}};
1110 // auto const mid_str =
1111 // fmt::format("<{}.{}@{}>", date.sec(), pill, server_identity_);
1113 // fmt::memory_buffer bfr;
1115 // fmt::format_to(bfr, "From: <{}>\r\n", from_mbx);
1116 // fmt::format_to(bfr, "To: <{}>\r\n", to_mbx);
1118 // fmt::format_to(bfr, "Date: {}\r\n", date.c_str());
1120 // fmt::format_to(bfr, "Message-ID: {}\r\n", mid_str.c_str());
1122 // if (!msg.get_header(message::Subject).empty()) {
1123 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1124 // msg.get_header(message::Subject));
1125 // }
1126 // else {
1127 // fmt::format_to(bfr, "{}: {}\r\n", message::Subject,
1128 // "Reply to your message");
1129 // }
1131 // if (!msg.get_header(message::In_Reply_To).empty()) {
1132 // fmt::format_to(bfr, "{}: {}\r\n", message::In_Reply_To,
1133 // msg.get_header(message::In_Reply_To));
1134 // }
1136 // if (!msg.get_header(message::MIME_Version).empty() &&
1137 // msg.get_header(message::Content_Type).empty()) {
1138 // fmt::format_to(bfr, "{}: {}\r\n", message::MIME_Version,
1139 // msg.get_header(message::MIME_Version));
1140 // fmt::format_to(bfr, "{}: {}\r\n", message::Content_Type,
1141 // msg.get_header(message::Content_Type));
1142 // }
1144 // reply->write(fmt::to_string(bfr));
1146 // if (!msg.body.empty()) {
1147 // reply->write("\r\n");
1148 // reply->write(msg.body);
1149 // }
1151 // auto const msg_data = reply->freeze();
1152 // message::parsed msg_reply;
1153 // CHECK(msg_reply.parse(msg_data));
1155 // auto const sender = server_identity_.ascii().c_str();
1156 // auto const selector = FLAGS_selector.c_str();
1157 // auto const key_file =
1158 // (config_path_ / FLAGS_selector).replace_extension("private");
1159 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1161 // message::dkim_sign(msg_reply, sender, selector, key_file);
1163 // if (!send_.send(msg_reply.as_string())) {
1164 // out_() << "432 4.3.0 Recipient's incoming mail queue has been "
1165 // "stopped\r\n"
1166 // << std::flush;
1168 // LOG(ERROR) << "send failed for reply to " << to_mbx << " from " <<
1169 // from_mbx; return false;
1170 // }
1172 // LOG(INFO) << "successful reply to " << to_mbx << " from " << from_mbx;
1173 // return true;
1174 // }
1176 bool Session::do_deliver_()
1178 CHECK(msg_);
1180 // auto const sender = server_identity_.ascii().c_str();
1181 // auto const selector = FLAGS_selector.c_str();
1182 // auto const key_file =
1183 // (config_path_ / FLAGS_selector).replace_extension("private");
1184 // CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
1186 try {
1187 // auto const msg_data = msg_->freeze();
1189 // message::parsed msg;
1191 // // Only deal in RFC-5322 Mail Objects.
1192 // bool const message_parsed = msg.parse(msg_data);
1193 // if (message_parsed) {
1195 // // remove any Return-Path
1196 // message::remove_delivery_headers(msg);
1198 // auto const authentic =
1199 // message_parsed &&
1200 // message::authentication(msg, sender, selector, key_file);
1202 // // write a new Return-Path
1203 // msg_->write(fmt::format("Return-Path: <{}>\r\n", reverse_path_));
1205 // for (auto const h : msg.headers) {
1206 // msg_->write(h.as_string());
1207 // msg_->write("\r\n");
1208 // }
1209 // if (!msg.body.empty()) {
1210 // msg_->write("\r\n");
1211 // msg_->write(msg.body);
1212 // }
1214 msg_->deliver();
1216 // if (authentic && !fwd_path_.empty()) {
1217 // if (!do_forward_(msg))
1218 // return false;
1219 // }
1220 // if (authentic && !rep_info_.empty()) {
1221 // if (!do_reply_(msg))
1222 // return false;
1223 // }
1224 // }
1226 msg_->close();
1228 catch (std::system_error const& e) {
1229 switch (errno) {
1230 case ENOSPC:
1231 out_() << "452 4.3.1 mail system full\r\n" << std::flush;
1232 LOG(ERROR) << "no space";
1233 msg_->trash();
1234 reset_();
1235 return false;
1237 default:
1238 out_() << "550 5.0.0 mail system error\r\n" << std::flush;
1239 if (errno)
1240 LOG(ERROR) << "errno==" << errno << ": " << strerror(errno);
1241 LOG(ERROR) << e.what();
1242 msg_->trash();
1243 reset_();
1244 return false;
1248 return true;
1251 void Session::data_done()
1253 CHECK((state_ == xact_step::data));
1255 if (msg_ && msg_->size_error()) {
1256 data_size_error();
1257 return;
1260 // if (prdr_) {
1261 // out_() << "353\r\n";
1262 // for (auto fp : forward_path_) {
1263 // out_() << "250 2.1.5 RCPT TO OK\r\n";
1264 // }
1265 // }
1267 // Check for and act on magic "wait" address.
1269 using namespace boost::xpressive;
1271 sregex const rex = icase("wait-data-") >> (secs_ = +_d);
1272 smatch what;
1274 for (auto fp : forward_path_) {
1275 if (regex_match(fp.local_part(), what, rex) ||
1276 regex_match(fp.local_part(), what, all_rex)) {
1277 auto const str = what[secs_].str();
1278 LOG(INFO) << "waiting at DATA " << str << " seconds";
1279 long value = 0;
1280 std::from_chars(str.data(), str.data() + str.size(), value);
1281 google::FlushLogFiles(google::INFO);
1282 out_() << std::flush;
1283 sleep(value);
1284 LOG(INFO) << "done waiting";
1289 if (do_deliver_()) {
1290 auto temp_fail_db_name = config_path_ / "temp_fail_data";
1291 CDB temp_fail;
1293 for (auto fp : forward_path_) {
1294 if (temp_fail.open(temp_fail_db_name) &&
1295 temp_fail.contains(fp.local_part())) {
1296 out_() << "450 4.2.2 Mailbox full.\r\n" << std::flush;
1297 LOG(WARNING) << "temp fail at DATA for recipient " << fp;
1298 reset_();
1299 return;
1304 // Check for addresses we reject after data.
1306 auto bad_recipients_db_name = config_path_ / "bad_recipients_data";
1307 CDB bad_recipients_db;
1308 if (bad_recipients_db.open(bad_recipients_db_name)) {
1309 for (auto fp : forward_path_) {
1310 std::string loc = fp.local_part();
1311 std::transform(loc.begin(), loc.end(), loc.begin(),
1312 [](unsigned char c) { return std::tolower(c); });
1313 if (bad_recipients_db.contains(loc)) {
1314 out_() << "550 5.1.1 bad recipient " << fp << "\r\n" << std::flush;
1315 LOG(WARNING) << "bad recipient " << fp;
1316 reset_();
1317 return;
1319 else {
1320 LOG(INFO) << "unbad recipient " << fp.local_part();
1324 else {
1325 LOG(WARNING) << "can't open bad_recipients_data";
1329 out_() << "250 2.0.0 DATA OK\r\n" << std::flush;
1330 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1331 << msg_->id();
1332 reset_();
1335 void Session::data_size_error()
1337 out_().clear(); // clear possible eof from input side
1338 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1339 if (msg_) {
1340 msg_->trash();
1342 LOG(WARNING) << "DATA size error";
1343 reset_();
1346 void Session::data_error()
1348 out_().clear(); // clear possible eof from input side
1349 out_() << "554 5.3.0 message error of some kind\r\n" << std::flush;
1350 if (msg_) {
1351 msg_->trash();
1353 LOG(WARNING) << "DATA error";
1354 reset_();
1357 bool Session::bdat_start(size_t n)
1359 // In practice, this one gets pipelined.
1360 // last_in_group_("BDAT");
1362 switch (state_) {
1363 case xact_step::helo:
1364 out_() << "503 5.5.1 sequence error, expecting HELO/EHLO\r\n" << std::flush;
1365 LOG(WARNING) << "'BDAT' before HELO/EHLO"
1366 << (sock_.has_peername() ? " from " : "") << client_;
1367 return false;
1368 case xact_step::mail:
1369 out_() << "503 5.5.1 sequence error, expecting MAIL\r\n" << std::flush;
1370 LOG(WARNING) << "'BDAT' before 'MAIL FROM'"
1371 << (sock_.has_peername() ? " from " : "") << client_;
1372 return false;
1373 case xact_step::rcpt:
1374 // See comment in data_start()
1375 out_() << "503 5.5.1 sequence error, expecting RCPT\r\n" << std::flush;
1376 LOG(WARNING) << "no valid recipients"
1377 << (sock_.has_peername() ? " from " : "") << client_;
1378 return false;
1379 case xact_step::data: // first bdat
1380 break;
1381 case xact_step::bdat: return true;
1382 case xact_step::rset:
1383 out_() << "503 5.5.1 sequence error, expecting RSET\r\n" << std::flush;
1384 LOG(WARNING) << "error state must be cleared with a RSET"
1385 << (sock_.has_peername() ? " from " : "") << client_;
1386 return false;
1389 state_ = xact_step::bdat;
1391 return msg_new();
1394 void Session::bdat_done(size_t n, bool last)
1396 if (state_ != xact_step::bdat) {
1397 bdat_seq_error();
1398 return;
1401 if (!msg_) {
1402 return;
1405 if (msg_->size_error()) {
1406 bdat_size_error();
1407 return;
1410 if (!last) {
1411 out_() << "250 2.0.0 BDAT " << n << " OK\r\n" << std::flush;
1412 LOG(INFO) << "BDAT " << n;
1413 return;
1416 // Check for and act on magic "wait" address.
1418 using namespace boost::xpressive;
1420 sregex const rex = icase("wait-bdat-") >> (secs_ = +_d);
1421 smatch what;
1423 for (auto fp : forward_path_) {
1424 if (regex_match(fp.local_part(), what, rex) ||
1425 regex_match(fp.local_part(), what, all_rex)) {
1426 auto const str = what[secs_].str();
1427 LOG(INFO) << "waiting at BDAT " << str << " seconds";
1428 long value = 0;
1429 std::from_chars(str.data(), str.data() + str.size(), value);
1430 google::FlushLogFiles(google::INFO);
1431 out_() << std::flush;
1432 sleep(value);
1433 LOG(INFO) << "done waiting";
1438 do_deliver_();
1440 out_() << "250 2.0.0 BDAT " << n << " LAST OK\r\n" << std::flush;
1441 LOG(INFO) << "BDAT " << n << " LAST";
1442 LOG(INFO) << "message delivered, " << msg_->size() << " octets, with id "
1443 << msg_->id();
1444 reset_();
1447 void Session::bdat_size_error()
1449 out_().clear(); // clear possible eof from input side
1450 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1451 if (msg_) {
1452 msg_->trash();
1454 LOG(WARNING) << "BDAT size error";
1455 reset_();
1458 void Session::bdat_seq_error()
1460 out_().clear(); // clear possible eof from input side
1461 out_() << "503 5.5.1 BDAT sequence error\r\n" << std::flush;
1462 if (msg_) {
1463 msg_->trash();
1465 LOG(WARNING) << "BDAT sequence error";
1466 reset_();
1469 void Session::bdat_io_error()
1471 out_().clear(); // clear possible eof from input side
1472 out_() << "503 5.5.1 BDAT I/O error\r\n" << std::flush;
1473 if (msg_) {
1474 msg_->trash();
1476 LOG(WARNING) << "BDAT I/O error";
1477 reset_();
1480 void Session::rset()
1482 out_() << "250 2.1.5 RSET OK\r\n";
1483 // No flush RFC-2920 section 3.1, this could be part of a command group.
1484 LOG(INFO) << "RSET";
1485 reset_();
1488 void Session::noop(std::string_view str)
1490 last_in_group_("NOOP");
1491 out_() << "250 2.0.0 NOOP OK\r\n" << std::flush;
1492 LOG(INFO) << "NOOP" << (str.length() ? " " : "") << str;
1495 void Session::vrfy(std::string_view str)
1497 last_in_group_("VRFY");
1498 out_() << "252 2.1.5 try it\r\n" << std::flush;
1499 LOG(INFO) << "VRFY" << (str.length() ? " " : "") << str;
1502 void Session::help(std::string_view str)
1504 if (iequal(str, "help\r\n")) {
1505 out_() << "214 2.0.0 Now you're sounding desperate.\r\n" << std::flush;
1507 else {
1508 out_() << "214 2.0.0 see https://digilicious.com/smtp.html\r\n"
1509 << std::flush;
1511 LOG(INFO) << "HELP" << (str.length() ? " " : "") << str;
1514 void Session::quit()
1516 // send_.quit();
1517 // last_in_group_("QUIT");
1518 out_() << "221 2.0.0 closing connection\r\n" << std::flush;
1519 LOG(INFO) << "QUIT";
1520 exit_();
1523 void Session::auth()
1525 out_() << "454 4.7.0 authentication failure\r\n" << std::flush;
1526 LOG(INFO) << "AUTH";
1527 bad_host_("auth");
1530 void Session::error(std::string_view log_msg)
1532 out_() << "421 4.3.5 system error: " << log_msg << "\r\n" << std::flush;
1533 LOG(WARNING) << log_msg;
1536 void Session::cmd_unrecognized(std::string_view cmd)
1538 auto const escaped{esc(cmd)};
1539 LOG(WARNING) << "command unrecognized: \"" << escaped << "\"";
1541 if (++n_unrecognized_cmds_ >= Config::max_unrecognized_cmds) {
1542 out_() << "500 5.5.1 command unrecognized: \"" << escaped
1543 << "\" exceeds limit\r\n"
1544 << std::flush;
1545 LOG(WARNING) << n_unrecognized_cmds_
1546 << " unrecognized commands is too many";
1547 exit_();
1550 out_() << "500 5.5.1 command unrecognized: \"" << escaped << "\"\r\n"
1551 << std::flush;
1554 void Session::bare_lf()
1556 // Error code used by Office 365.
1557 out_() << "554 5.6.11 bare LF\r\n" << std::flush;
1558 LOG(WARNING) << "bare LF";
1559 exit_();
1562 void Session::max_out()
1564 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
1565 LOG(WARNING) << "message size maxed out";
1566 exit_();
1569 void Session::time_out()
1571 out_() << "421 4.4.2 time-out\r\n" << std::flush;
1572 LOG(WARNING) << "time-out" << (sock_.has_peername() ? " from " : "")
1573 << client_;
1574 exit_();
1577 void Session::starttls()
1579 last_in_group_("STARTTLS");
1580 if (sock_.tls()) {
1581 out_() << "554 5.5.1 TLS already active\r\n" << std::flush;
1582 LOG(WARNING) << "STARTTLS issued with TLS already active";
1584 else if (!extensions_) {
1585 out_() << "554 5.5.1 TLS not avaliable without using EHLO\r\n"
1586 << std::flush;
1587 LOG(WARNING) << "STARTTLS issued without using EHLO";
1589 else {
1590 out_() << "220 2.0.0 STARTTLS OK\r\n" << std::flush;
1591 if (sock_.starttls_server(config_path_)) {
1592 reset_();
1593 max_msg_size(Config::max_msg_size_bro);
1594 LOG(INFO) << "STARTTLS " << sock_.tls_info();
1596 else {
1597 LOG(INFO) << "failed STARTTLS";
1602 void Session::exit_()
1604 // sock_.log_totals();
1606 timespec time_used{};
1607 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time_used);
1609 LOG(INFO) << "CPU time " << time_used.tv_sec << "." << std::setw(9)
1610 << std::setfill('0') << time_used.tv_nsec << " seconds";
1612 std::exit(EXIT_SUCCESS);
1615 /////////////////////////////////////////////////////////////////////////////
1617 // All of the verify_* functions send their own error messages back to
1618 // the client on failure, and return false.
1620 bool Session::verify_ip_address_(std::string& error_msg)
1622 auto ip_block_db_name = config_path_ / "ip-block";
1623 CDB ip_block;
1624 if (ip_block.open(ip_block_db_name) &&
1625 ip_block.contains(sock_.them_c_str())) {
1626 error_msg =
1627 fmt::format("IP address {} on static blocklist", sock_.them_c_str());
1628 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1629 return false;
1632 client_fcrdns_.clear();
1634 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1635 (sock_.them_address_literal() == IP6::loopback_literal)) {
1636 LOG(INFO) << "loopback address allowed";
1637 ip_allowed_ = true;
1638 client_fcrdns_.emplace_back("localhost");
1639 client_ = fmt::format("localhost {}", sock_.them_address_literal());
1640 return true;
1643 auto const fcrdns = DNS::fcrdns(res_, sock_.them_c_str());
1644 for (auto const& fcr : fcrdns) {
1645 client_fcrdns_.emplace_back(fcr);
1648 if (IP::is_private(sock_.them_address_literal())) {
1649 LOG(INFO) << "private address allowed";
1650 ip_allowed_ = true;
1651 client_ = sock_.them_address_literal();
1652 return true;
1655 if (!client_fcrdns_.empty()) {
1656 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1657 sock_.them_address_literal());
1658 // check allow list
1659 for (auto const& client_fcrdns : client_fcrdns_) {
1660 if (allow_.contains(client_fcrdns.ascii())) {
1661 LOG(INFO) << "FCrDNS " << client_fcrdns << " allowed";
1662 fcrdns_allowed_ = true;
1663 return true;
1665 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1666 if (tld) {
1667 if (allow_.contains(tld)) {
1668 LOG(INFO) << "FCrDNS registered domain " << tld << " allowed";
1669 fcrdns_allowed_ = true;
1670 return true;
1674 // check blocklist
1675 for (auto const& client_fcrdns : client_fcrdns_) {
1676 if (block_.contains(client_fcrdns.ascii())) {
1677 error_msg =
1678 fmt::format("FCrDNS {} on static blocklist", client_fcrdns.ascii());
1679 out_() << "554 5.7.1 blocklisted\r\n" << std::flush;
1680 return false;
1683 auto const tld{tld_db_.get_registered_domain(client_fcrdns.ascii())};
1684 if (tld) {
1685 if (block_.contains(tld)) {
1686 error_msg = fmt::format(
1687 "FCrDNS registered domain {} on static blocklist", tld);
1688 out_() << "554 5.7.1 blocklisted\r\n" << std::flush;
1689 return false;
1694 else {
1695 client_ = fmt::format("{}", sock_.them_address_literal());
1698 if (IP4::is_address(sock_.them_c_str())) {
1700 auto const reversed{IP4::reverse(sock_.them_c_str())};
1703 // Check with allow list.
1704 std::shuffle(std::begin(Config::wls), std::end(Config::wls),
1705 random_device_);
1707 for (auto wl : Config::wls) {
1708 DNS::Query q(res_, DNS::RR_type::A, reversed + wl);
1709 if (q.has_record()) {
1710 using namespace boost::xpressive;
1712 auto const as = q.get_strings()[0];
1713 LOG(INFO) << "on allow list " << wl << " as " << as;
1715 mark_tag x_(1);
1716 mark_tag y_(2);
1717 sregex const rex = as_xpr("127.0.") >> (x_ = +_d) >> '.' >> (y_ = +_d);
1718 smatch what;
1720 if (regex_match(as, what, rex)) {
1721 auto const x = what[x_].str();
1722 auto const y = what[y_].str();
1724 int value = 0;
1725 std::from_chars(y.data(), y.data() + y.size(), value);
1726 if (value > 0) {
1727 ip_allowed_ = true;
1728 LOG(INFO) << "allowed";
1732 LOG(INFO) << "Any A record skips check on block list";
1733 return true;
1738 // Check with block lists. <https://en.wikipedia.org/wiki/DNSBL>
1739 std::shuffle(std::begin(Config::bls), std::end(Config::bls),
1740 random_device_);
1742 for (auto bl : Config::bls) {
1743 DNS::Query q(res_, DNS::RR_type::A, reversed + bl);
1744 if (q.has_record()) {
1745 const auto a_strings = q.get_strings();
1746 for (auto const& as : a_strings) {
1747 LOG(INFO) << bl << " returned " << as;
1749 for (auto const& as : a_strings) {
1750 if (as == "127.0.0.1") {
1751 LOG(INFO) << "Should never get 127.0.0.1, from " << bl;
1753 else if (as == "127.0.0.10" || as == "127.0.0.11") {
1754 LOG(INFO) << "PBL listed, ignoring " << bl;
1756 else if (as == "127.255.255.252") {
1757 LOG(INFO) << "Typing error in DNSBL name " << bl;
1759 else if (as == "127.255.255.254") {
1760 LOG(INFO) << "Anonymous query through public resolver " << bl;
1762 else if (as == "127.255.255.255") {
1763 LOG(INFO) << "Excessive number of queries " << bl;
1765 else {
1766 error_msg = fmt::format("IP address {} blocked: {} returned {}",
1767 sock_.them_c_str(), bl, as);
1768 out_() << "554 5.7.1 " << error_msg << "\r\n" << std::flush;
1769 return false;
1774 // LOG(INFO) << "IP address " << sock_.them_c_str() << " cleared by dnsbls";
1777 LOG(INFO) << "IP address okay";
1778 return true;
1781 bool domain_blocked(DNS::Resolver& res, Domain const& identity)
1783 Domain lookup{fmt::format("{}.dbl.spamhaus.org", identity.ascii())};
1784 DNS::Query q(res, DNS::RR_type::A, lookup.ascii());
1785 if (q.has_record()) {
1786 const auto a_strings = q.get_strings();
1787 for (auto const& as : a_strings) {
1788 if (istarts_with(as, "127.0.1.")) {
1789 LOG(INFO) << "Domain " << identity << " blocked by spamhaus, " << as;
1790 return true;
1794 return false;
1797 // check the identity from HELO/EHLO
1798 bool Session::verify_client_(Domain const& client_identity,
1799 std::string& error_msg)
1801 if (!client_fcrdns_.empty()) {
1802 if (auto id = std::find(begin(client_fcrdns_), end(client_fcrdns_),
1803 client_identity);
1804 id != end(client_fcrdns_)) {
1805 // If the HELO ident is one of the FCrDNS names...
1806 if (id != begin(client_fcrdns_)) {
1807 // ...then rotate that one to the front of the list
1808 std::rotate(begin(client_fcrdns_), id, id + 1);
1810 client_ = fmt::format("{} {}", client_fcrdns_.front().ascii(),
1811 sock_.them_address_literal());
1812 return true;
1814 LOG(INFO) << "claimed identity " << client_identity
1815 << " does NOT match any FCrDNS: ";
1816 for (auto const& client_fcrdns : client_fcrdns_) {
1817 LOG(INFO) << " " << client_fcrdns;
1821 // Bogus clients claim to be us or some local host.
1822 if (sock_.has_peername() && ((client_identity == server_identity_) ||
1823 (client_identity == "localhost") ||
1824 (client_identity == "localhost.localdomain"))) {
1826 if ((sock_.them_address_literal() == IP4::loopback_literal) ||
1827 (sock_.them_address_literal() == IP6::loopback_literal)) {
1828 return true;
1831 // Give 'em a pass.
1832 if (ip_allowed_) {
1833 LOG(INFO) << "allow-listed IP address can claim to be "
1834 << client_identity;
1835 return true;
1838 // Ease up in test mode.
1839 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1840 return true;
1843 error_msg = fmt::format("liar, claimed to be {}", client_identity.ascii());
1844 out_() << "550 5.7.1 liar\r\n" << std::flush;
1845 return false;
1848 std::vector<std::string> labels;
1849 boost::algorithm::split(labels, client_identity.ascii(),
1850 boost::algorithm::is_any_of("."));
1851 if (labels.size() < 2) {
1852 error_msg =
1853 fmt::format("claimed bogus identity {}", client_identity.ascii());
1854 out_() << "550 4.7.1 bogus identity\r\n" << std::flush;
1855 return false;
1856 // // Sometimes we may want to look at mail from non conforming
1857 // // sending systems.
1858 // LOG(WARNING) << "invalid sender" << (sock_.has_peername() ? " " : "")
1859 // << client_ << " claiming " << client_identity;
1860 // return true;
1863 if (lookup_domain(block_, client_identity)) {
1864 error_msg =
1865 fmt::format("claimed blocked identity {}", client_identity.ascii());
1866 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1867 return false;
1870 auto const tld{tld_db_.get_registered_domain(client_identity.ascii())};
1871 if (!tld) {
1872 // Sometimes we may want to look at mail from misconfigured
1873 // sending systems.
1874 // LOG(WARNING) << "claimed identity has no registered domain";
1875 // return true;
1877 else if (block_.contains(tld)) {
1878 error_msg =
1879 fmt::format("claimed identity has blocked registered domain {}", tld);
1880 out_() << "550 4.7.1 blocked registered domain\r\n" << std::flush;
1881 return false;
1884 if (domain_blocked(res_, client_identity) ||
1885 domain_blocked(res_, Domain(tld))) {
1886 error_msg = fmt::format("claimed identity {} blocked by spamhaus",
1887 client_identity.ascii());
1888 out_() << "550 4.7.1 blocked identity\r\n" << std::flush;
1889 return false;
1892 DNS::Query q(res_, DNS::RR_type::A, client_identity.ascii());
1893 if (!q.has_record()) {
1894 LOG(WARNING) << "claimed identity " << client_identity.ascii()
1895 << " not DNS resolvable";
1898 // not otherwise objectionable
1899 return true;
1902 // check sender from RFC5321 MAIL FROM:
1903 bool Session::verify_sender_(Mailbox const& sender, std::string& error_msg)
1905 do_spf_check_(sender);
1907 std::string const sender_str{sender};
1909 if (sender.empty()) {
1910 // MAIL FROM:<>
1911 // is used to send bounce messages.
1912 return true;
1915 if (domain_blocked(res_, sender.domain())) {
1916 error_msg = fmt::format("{} sender domain blocked by spamhaus", sender_str);
1917 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1918 return false;
1921 auto bad_senders_db_name = config_path_ / "bad_senders";
1922 CDB bad_senders;
1923 if (bad_senders.open(bad_senders_db_name) &&
1924 bad_senders.contains(sender_str)) {
1925 error_msg = fmt::format("{} bad sender", sender_str);
1926 out_() << "550 5.1.8 " << error_msg << "\r\n" << std::flush;
1927 return false;
1930 // We don't accept mail /from/ a domain we are expecting to accept
1931 // mail for on an external network connection.
1933 if (sock_.them_address_literal() != sock_.us_address_literal()) {
1934 if ((accept_domains_.is_open() &&
1935 (accept_domains_.contains(sender.domain().ascii()) ||
1936 accept_domains_.contains(sender.domain().utf8()))) ||
1937 (sender.domain() == server_identity_)) {
1939 // Ease up in test mode.
1940 if (FLAGS_test_mode || getenv("GHSMTP_TEST_MODE")) {
1941 return true;
1943 out_() << "550 5.7.1 liar\r\n" << std::flush;
1944 error_msg = fmt::format("liar, claimed to be {}", sender.domain().utf8());
1945 return false;
1949 if (sender.domain().is_address_literal()) {
1950 if (sender.domain() != sock_.them_address_literal()) {
1951 LOG(WARNING) << "sender domain " << sender.domain() << " does not match "
1952 << sock_.them_address_literal();
1954 return true;
1957 if (!verify_sender_domain_(sender.domain(), error_msg)) {
1958 return false;
1961 return true;
1964 // this sender is the RFC5321 MAIL FROM: domain part
1965 bool Session::verify_sender_domain_(Domain const& sender,
1966 std::string& error_msg)
1968 if (sender.empty()) {
1969 // MAIL FROM:<>
1970 // is used to send bounce messages.
1971 return true;
1974 // Break sender domain into labels:
1976 std::vector<std::string> labels;
1977 boost::algorithm::split(labels, sender.ascii(),
1978 boost::algorithm::is_any_of("."));
1980 if (labels.size() < 2) { // This is not a valid domain.
1981 error_msg = fmt::format("{} invalid syntax", sender.ascii());
1982 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1983 return false;
1986 if (lookup_domain(block_, sender)) {
1987 error_msg = fmt::format("SPF sender domain ({}) is blocked",
1988 spf_sender_domain_.ascii());
1989 out_() << "550 5.7.1 " << error_msg << "\r\n" << std::flush;
1990 return false;
1993 if (spf_result_ == SPF::Result::PASS) {
1994 if (allow_.contains(spf_sender_domain_.ascii())) {
1995 LOG(INFO) << "sender " << spf_sender_domain_.ascii() << " allowed";
1996 return true;
1999 auto const reg_dom{
2000 tld_db_.get_registered_domain(spf_sender_domain_.ascii())};
2001 if (reg_dom) {
2002 if (allow_.contains(reg_dom)) {
2003 LOG(INFO) << "sender registered domain \"" << reg_dom << "\" allowed";
2004 return true;
2009 LOG(INFO) << "sender \"" << sender << "\" not disallowed";
2010 return true;
2013 void Session::do_spf_check_(Mailbox const& sender)
2015 if (!sock_.has_peername()) {
2016 auto const ip_addr = "127.0.0.1"; // use localhost for local socket
2017 spf_received_ = fmt::format(
2018 "Received-SPF: pass ({}: allow-listed) client-ip={}; "
2019 "envelope-from={}; helo={};",
2020 server_id_(), ip_addr, sender.as_string(), client_identity_.ascii());
2021 spf_sender_domain_ = "localhost";
2022 return;
2025 auto const spf_srv = SPF::Server{server_id_().c_str()};
2026 auto spf_request = SPF::Request{spf_srv};
2028 if (IP4::is_address(sock_.them_c_str())) {
2029 spf_request.set_ipv4_str(sock_.them_c_str());
2031 else if (IP6::is_address(sock_.them_c_str())) {
2032 spf_request.set_ipv6_str(sock_.them_c_str());
2034 else {
2035 LOG(FATAL) << "bogus address " << sock_.them_address_literal() << ", "
2036 << sock_.them_c_str();
2039 auto const from{static_cast<std::string>(sender)};
2041 spf_request.set_env_from(from.c_str());
2042 spf_request.set_helo_dom(client_identity_.ascii().c_str());
2044 auto const spf_res{SPF::Response{spf_request}};
2045 spf_result_ = spf_res.result();
2046 spf_received_ = spf_res.received_spf();
2047 spf_sender_domain_ = spf_request.get_sender_dom();
2049 LOG(INFO) << "spf_received_ == " << spf_received_;
2051 if (spf_result_ == SPF::Result::FAIL) {
2052 LOG(INFO) << "FAIL " << spf_res.header_comment();
2054 else if (spf_result_ == SPF::Result::NEUTRAL) {
2055 LOG(INFO) << "NEUTRAL " << spf_res.header_comment();
2057 else if (spf_result_ == SPF::Result::PASS) {
2058 LOG(INFO) << "PASS " << spf_res.header_comment();
2060 else {
2061 LOG(INFO) << "INVALID/SOFTFAIL/NONE/xERROR " << server_id_().c_str();
2065 bool Session::verify_from_params_(parameters_t const& parameters)
2067 // Take a look at the optional parameters:
2068 for (auto const& [name, value] : parameters) {
2069 if (iequal(name, "BODY")) {
2070 if (iequal(value, "8BITMIME")) {
2071 // everything is cool, this is our default...
2073 else if (iequal(value, "7BIT")) {
2074 // nothing to see here, move along...
2076 else if (iequal(value, "BINARYMIME")) {
2077 binarymime_ = true;
2079 else {
2080 LOG(WARNING) << "unrecognized BODY type \"" << value << "\" requested";
2083 else if (iequal(name, "SMTPUTF8")) {
2084 if (!value.empty()) {
2085 LOG(WARNING) << "SMTPUTF8 parameter has a value: " << value;
2087 smtputf8_ = true;
2090 // else if (iequal(name, "PRDR")) {
2091 // LOG(INFO) << "using PRDR";
2092 // prdr_ = true;
2093 // }
2095 else if (iequal(name, "SIZE")) {
2096 if (value.empty()) {
2097 LOG(WARNING) << "SIZE parameter has no value.";
2099 else {
2100 try {
2101 auto const sz = stoull(value);
2102 if (sz > max_msg_size()) {
2103 out_() << "552 5.3.4 message size limit exceeded\r\n" << std::flush;
2104 LOG(WARNING) << "SIZE parameter too large: " << sz;
2105 return false;
2108 catch (std::invalid_argument const& e) {
2109 LOG(WARNING) << "SIZE parameter has invalid value: " << value;
2111 catch (std::out_of_range const& e) {
2112 LOG(WARNING) << "SIZE parameter has out-of-range value: " << value;
2114 // I guess we just ignore bad size parameters.
2117 else if (iequal(name, "REQUIRETLS")) {
2118 if (!sock_.tls()) {
2119 out_() << "554 5.7.1 REQUIRETLS needed\r\n" << std::flush;
2120 LOG(WARNING) << "REQUIRETLS needed";
2121 return false;
2124 else {
2125 LOG(WARNING) << "unrecognized 'MAIL FROM' parameter " << name << "="
2126 << value;
2130 return true;
2133 bool Session::verify_rcpt_params_(parameters_t const& parameters)
2135 // Take a look at the optional parameters:
2136 for (auto const& [name, value] : parameters) {
2137 if (iequal(name, "RRVS")) {
2138 // rrvs-param = "RRVS=" date-time [ ";" ( "C" / "R" ) ]
2139 LOG(INFO) << name << "=" << value;
2141 else {
2142 LOG(WARNING) << "unrecognized 'RCPT TO' parameter " << name << "="
2143 << value;
2147 return true;
2150 // check recipient from RFC5321 RCPT TO:
2151 bool Session::verify_recipient_(Mailbox const& recipient)
2153 if ((recipient.local_part() == "Postmaster") && (recipient.domain() == "")) {
2154 LOG(INFO) << "magic Postmaster address";
2155 return true;
2158 auto const accepted_domain{[this, &recipient] {
2159 if (recipient.domain().is_address_literal()) {
2160 if (recipient.domain() != sock_.us_address_literal()) {
2161 LOG(WARNING) << "recipient.domain address " << recipient.domain()
2162 << " does not match ours " << sock_.us_address_literal();
2164 return false;
2167 return true;
2170 // Domains we accept mail for.
2171 if (accept_domains_.is_open()) {
2172 if (accept_domains_.contains(recipient.domain().ascii()) ||
2173 accept_domains_.contains(recipient.domain().utf8())) {
2174 return true;
2177 else {
2178 // If we have no list of domains to accept, at least take our own.
2179 if (recipient.domain() == server_id_()) {
2180 return true;
2184 return false;
2185 }()};
2187 if (!accepted_domain) {
2188 out_() << "550 5.7.1 relay access denied\r\n" << std::flush;
2189 LOG(WARNING) << "relay access denied for domain " << recipient.domain();
2190 return false;
2193 if (recipient.local_part() == "gene" && client_fcrdns_.size() &&
2194 client_fcrdns_[0].ascii().ends_with("outlook.com")) {
2195 // Getting Spam'ed by MS
2196 if (reverse_path_.empty() || (reverse_path_.length() > 40) ||
2197 reverse_path_.domain().ascii().ends_with(".onmicrosoft.com")) {
2198 std::string error_msg = fmt::format("rejecting spammy message from {}",
2199 client_fcrdns_[0].ascii());
2200 LOG(WARNING) << error_msg;
2201 out_() << "550 5.7.0 " << error_msg << "\r\n" << std::flush;
2202 return false;
2206 // Check for local addresses we reject.
2208 auto bad_recipients_db_name = config_path_ / "bad_recipients";
2209 CDB bad_recipients_db;
2211 std::string loc = recipient.local_part();
2212 std::transform(loc.begin(), loc.end(), loc.begin(),
2213 [](unsigned char c) { return std::tolower(c); });
2215 if (bad_recipients_db.open(bad_recipients_db_name) &&
2216 bad_recipients_db.contains(loc)) {
2217 out_() << "550 5.1.1 bad recipient " << recipient << "\r\n" << std::flush;
2218 LOG(WARNING) << "bad recipient " << recipient;
2219 return false;
2224 auto fail_db_name = config_path_ / "fail_554";
2225 if (fs::exists(fail_db_name)) {
2226 CDB fail_db;
2227 if (fail_db.open(fail_db_name) &&
2228 fail_db.contains(recipient.local_part())) {
2229 out_() << "554 5.7.1 prohibited for policy reasons" << recipient
2230 << "\r\n"
2231 << std::flush;
2232 LOG(WARNING) << "fail_554 recipient " << recipient;
2233 return false;
2239 auto temp_fail_db_name = config_path_ / "temp_fail";
2240 CDB temp_fail;
2241 if (temp_fail.open(temp_fail_db_name) &&
2242 temp_fail.contains(recipient.local_part())) {
2243 out_() << "432 4.3.0 recipient's incoming mail queue has been stopped\r\n"
2244 << std::flush;
2245 LOG(WARNING) << "temp fail for recipient " << recipient;
2246 return false;
2250 // Check for and act on magic "wait" address.
2252 using namespace boost::xpressive;
2254 sregex const rex = icase("wait-rcpt-") >> (secs_ = +_d);
2255 smatch what;
2257 if (regex_match(recipient.local_part(), what, rex) ||
2258 regex_match(recipient.local_part(), what, all_rex)) {
2259 auto const str = what[secs_].str();
2260 LOG(INFO) << "waiting at RCPT TO " << str << " seconds";
2261 long value = 0;
2262 std::from_chars(str.data(), str.data() + str.size(), value);
2263 google::FlushLogFiles(google::INFO);
2264 out_() << std::flush;
2265 sleep(value);
2266 LOG(INFO) << "done waiting";
2270 // This is a trap for a probe done by some senders to see if we
2271 // accept just any old local-part.
2272 // if (!extensions_) {
2273 // if (recipient.local_part().length() > 8) {
2274 // out_() << "550 5.1.1 unknown recipient " << recipient << "\r\n"
2275 // << std::flush;
2276 // LOG(WARNING) << "unknown recipient for HELO " << recipient;
2277 // return false;
2278 // }
2279 // }
2281 return true;