7 #include "imemstream.hpp"
11 #include <fmt/format.h>
13 #include <gflags/gflags.h>
15 // This needs to be at least the length of each string it's trying to match.
16 DEFINE_uint64(pbfr_size
, 4 * 1024, "parser buffer size");
18 DEFINE_bool(use_esmtp
, true, "use ESMTP (EHLO)");
20 DEFINE_string(local_address
, "", "local address to bind");
22 #include <boost/algorithm/string/case_conv.hpp>
24 #include <tao/pegtl.hpp>
25 #include <tao/pegtl/contrib/abnf.hpp>
27 using namespace tao::pegtl
;
28 using namespace tao::pegtl::abnf
;
30 using namespace std::string_literals
;
38 struct tail
: range
<'\x80', '\xBF'> {};
40 struct ch_1
: range
<'\x00', '\x7F'> {};
42 struct ch_2
: seq
<range
<'\xC2', '\xDF'>, tail
> {};
44 struct ch_3
: sor
<seq
<one
<'\xE0'>, range
<'\xA0', '\xBF'>, tail
>,
45 seq
<range
<'\xE1', '\xEC'>, rep
<2, tail
>>,
46 seq
<one
<'\xED'>, range
<'\x80', '\x9F'>, tail
>,
47 seq
<range
<'\xEE', '\xEF'>, rep
<2, tail
>>> {};
49 struct ch_4
: sor
<seq
<one
<'\xF0'>, range
<'\x90', '\xBF'>, rep
<2, tail
>>,
50 seq
<range
<'\xF1', '\xF3'>, rep
<3, tail
>>,
51 seq
<one
<'\xF4'>, range
<'\x80', '\x8F'>, rep
<2, tail
>>> {};
53 struct u8char
: sor
<ch_1
, ch_2
, ch_3
, ch_4
> {};
55 struct non_ascii
: sor
<ch_2
, ch_3
, ch_4
> {};
57 struct ascii_only
: seq
<star
<ch_1
>, eof
> {};
59 struct utf8_only
: seq
<star
<u8char
>, eof
> {};
67 using colon
= one
<':'>;
68 using dash
= one
<'-'>;
70 struct u_let_dig
: sor
<ALPHA
, DIGIT
, chars::non_ascii
> {};
72 struct u_ldh_tail
: star
<sor
<seq
<plus
<one
<'-'>>, u_let_dig
>, u_let_dig
>> {};
74 struct u_label
: seq
<u_let_dig
, u_ldh_tail
> {};
76 struct let_dig
: sor
<ALPHA
, DIGIT
> {};
78 struct ldh_tail
: star
<sor
<seq
<plus
<one
<'-'>>, let_dig
>, let_dig
>> {};
80 struct ldh_str
: seq
<let_dig
, ldh_tail
> {};
82 struct label
: ldh_str
{};
84 struct sub_domain
: sor
<label
, u_label
> {};
86 struct domain
: list
<sub_domain
, dot
> {};
88 struct dec_octet
: sor
<seq
<string
<'2','5'>, range
<'0','5'>>,
89 seq
<one
<'2'>, range
<'0','4'>, DIGIT
>,
90 seq
<range
<'0', '1'>, rep
<2, DIGIT
>>,
91 rep_min_max
<1, 2, DIGIT
>> {};
93 struct IPv4_address_literal
94 : seq
<dec_octet
, dot
, dec_octet
, dot
, dec_octet
, dot
, dec_octet
> {};
96 struct h16
: rep_min_max
<1, 4, HEXDIG
> {};
98 struct ls32
: sor
<seq
<h16
, colon
, h16
>, IPv4_address_literal
> {};
100 struct dcolon
: two
<':'> {};
102 struct IPv6address
: sor
<seq
< rep
<6, h16
, colon
>, ls32
>,
103 seq
< dcolon
, rep
<5, h16
, colon
>, ls32
>,
104 seq
<opt
<h16
>, dcolon
, rep
<4, h16
, colon
>, ls32
>,
105 seq
<opt
<h16
, opt
< colon
, h16
>>, dcolon
, rep
<3, h16
, colon
>, ls32
>,
106 seq
<opt
<h16
, rep_opt
<2, colon
, h16
>>, dcolon
, rep
<2, h16
, colon
>, ls32
>,
107 seq
<opt
<h16
, rep_opt
<3, colon
, h16
>>, dcolon
, h16
, colon
, ls32
>,
108 seq
<opt
<h16
, rep_opt
<4, colon
, h16
>>, dcolon
, ls32
>,
109 seq
<opt
<h16
, rep_opt
<5, colon
, h16
>>, dcolon
, h16
>,
110 seq
<opt
<h16
, rep_opt
<6, colon
, h16
>>, dcolon
>> {};
112 struct IPv6_address_literal
: seq
<TAO_PEGTL_ISTRING("IPv6:"), IPv6address
> {};
114 struct dcontent
: ranges
<33, 90, 94, 126> {};
116 struct standardized_tag
: ldh_str
{};
118 struct general_address_literal
: seq
<standardized_tag
, colon
, plus
<dcontent
>> {};
120 // See rfc 5321 Section 4.1.3
121 struct address_literal
: seq
<one
<'['>,
122 sor
<IPv4_address_literal
,
123 IPv6_address_literal
,
124 general_address_literal
>,
128 struct qtextSMTP
: sor
<ranges
<32, 33, 35, 91, 93, 126>, chars::non_ascii
> {};
129 struct graphic
: range
<32, 126> {};
130 struct quoted_pairSMTP
: seq
<one
<'\\'>, graphic
> {};
131 struct qcontentSMTP
: sor
<qtextSMTP
, quoted_pairSMTP
> {};
133 // excluded from atext: "(),.@[]"
134 struct atext
: sor
<ALPHA
, DIGIT
,
145 chars::non_ascii
> {};
146 struct atom
: plus
<atext
> {};
147 struct dot_string
: list
<atom
, dot
> {};
148 struct quoted_string
: seq
<one
<'"'>, plus
<qcontentSMTP
>, one
<'"'>> {};
149 struct local_part
: sor
<dot_string
, quoted_string
> {};
150 struct non_local_part
: sor
<domain
, address_literal
> {};
151 struct mailbox
: seq
<local_part
, one
<'@'>, non_local_part
> {};
153 struct at_domain
: seq
<one
<'@'>, domain
> {};
155 struct path
: seq
<one
<'<'>, mailbox
, one
<'>'>> {};
157 struct path_only
: seq
<path
, eof
> {};
159 // textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
161 // Although not explicit in the grammar of RFC-6531, in practice UTF-8
162 // is used in the replys.
164 // struct textstring : plus<sor<one<9>, range<32, 126>>> {};
166 struct textstring
: plus
<sor
<one
<9>, range
<32, 126>, chars::non_ascii
>> {};
168 struct server_id
: sor
<domain
, address_literal
> {};
170 // Greeting = ( "220 " (Domain / address-literal) [ SP textstring ] CRLF )
172 // ( "220-" (Domain / address-literal) [ SP textstring ] CRLF
173 // *( "220-" [ textstring ] CRLF )
174 // "220 " [ textstring ] CRLF )
177 : sor
<seq
<TAO_PEGTL_ISTRING("220 "), server_id
, opt
<textstring
>, CRLF
>,
178 seq
<TAO_PEGTL_ISTRING("220-"), server_id
, opt
<textstring
>, CRLF
,
179 star
<seq
<TAO_PEGTL_ISTRING("220-"), opt
<textstring
>, CRLF
>>,
180 seq
<TAO_PEGTL_ISTRING("220 "), opt
<textstring
>, CRLF
>>> {};
182 // Reply-code = %x32-35 %x30-35 %x30-39
185 : seq
<range
<0x32, 0x35>, range
<0x30, 0x35>, range
<0x30, 0x39>> {};
187 // Reply-line = *( Reply-code "-" [ textstring ] CRLF )
188 // Reply-code [ SP textstring ] CRLF
191 : seq
<star
<seq
<reply_code
, one
<'-'>, opt
<textstring
>, CRLF
>>,
192 seq
<reply_code
, opt
<seq
<SP
, textstring
>>, CRLF
>> {};
195 : sor
<greeting_ok
, reply_lines
> {};
197 // ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
198 // ; string of any characters other than CR or LF
200 struct ehlo_greet
: plus
<ranges
<0, 9, 11, 12, 14, 127>> {};
202 // ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
203 // ; additional syntax of ehlo-params depends on
206 // The '.' we also allow in ehlo-keyword since it has been seen in the
207 // wild at least at 263.net.
209 struct ehlo_keyword
: seq
<sor
<ALPHA
, DIGIT
>, star
<sor
<ALPHA
, DIGIT
, dash
, dot
>>> {};
211 // ehlo-param = 1*(%d33-126)
212 // ; any CHAR excluding <SP> and all
213 // ; control characters (US-ASCII 0-31 and 127
216 struct ehlo_param
: plus
<range
<33, 126>> {};
218 // ehlo-line = ehlo-keyword *( SP ehlo-param )
220 // The AUTH= thing is so common with some servers (postfix) that I
221 // guess we have to accept it.
224 : seq
<ehlo_keyword
, star
<seq
<sor
<SP
,one
<'='>>, ehlo_param
>>> {};
226 // ehlo-ok-rsp = ( "250 " Domain [ SP ehlo-greet ] CRLF )
228 // ( "250-" Domain [ SP ehlo-greet ] CRLF
229 // *( "250-" ehlo-line CRLF )
230 // "250 " ehlo-line CRLF )
232 // The last line having the optional ehlo_line is not strictly correct.
233 // Was added to work with postfix/src/smtpstone/smtp-sink.c.
236 : sor
<seq
<TAO_PEGTL_ISTRING("250 "), server_id
, opt
<ehlo_greet
>, CRLF
>,
238 seq
<TAO_PEGTL_ISTRING("250-"), server_id
, opt
<ehlo_greet
>, CRLF
,
239 star
<seq
<TAO_PEGTL_ISTRING("250-"), ehlo_line
, CRLF
>>,
240 seq
<TAO_PEGTL_ISTRING("250 "), opt
<ehlo_line
>, CRLF
>>
244 : sor
<ehlo_ok_rsp
, reply_lines
> {};
247 : seq
<TAO_PEGTL_ISTRING("250 "), server_id
, opt
<ehlo_greet
>, CRLF
> {};
249 struct auth_login_username
250 : seq
<TAO_PEGTL_STRING("334 VXNlcm5hbWU6"), CRLF
> {};
252 struct auth_login_password
253 : seq
<TAO_PEGTL_STRING("334 UGFzc3dvcmQ6"), CRLF
> {};
257 template <typename Rule
>
258 struct inaction
: nothing
<Rule
> {
261 template <typename Rule
>
262 struct action
: nothing
<Rule
> {
266 struct action
<server_id
> {
267 template <typename Input
>
268 static void apply(Input
const& in
, Connection
& conn
)
270 conn
.server_id
= in
.string();
275 struct action
<local_part
> {
276 template <typename Input
>
277 static void apply(Input
const& in
, Mailbox
& mbx
)
279 mbx
.set_local(in
.string());
284 struct action
<non_local_part
> {
285 template <typename Input
>
286 static void apply(Input
const& in
, Mailbox
& mbx
)
288 mbx
.set_domain(in
.string());
293 struct action
<greeting_ok
> {
294 template <typename Input
>
295 static void apply(Input
const& in
, Connection
& conn
)
297 conn
.greeting_ok
= true;
298 imemstream stream
{begin(in
), size(in
)};
300 while (std::getline(stream
, line
)) {
301 LOG(INFO
) << "S: " << line
;
307 struct action
<ehlo_ok_rsp
> {
308 template <typename Input
>
309 static void apply(Input
const& in
, Connection
& conn
)
312 imemstream stream
{begin(in
), size(in
)};
314 while (std::getline(stream
, line
)) {
315 LOG(INFO
) << "S: " << line
;
321 struct action
<ehlo_keyword
> {
322 template <typename Input
>
323 static void apply(Input
const& in
, Connection
& conn
)
325 conn
.ehlo_keyword
= in
.string();
326 boost::to_upper(conn
.ehlo_keyword
);
331 struct action
<ehlo_param
> {
332 template <typename Input
>
333 static void apply(Input
const& in
, Connection
& conn
)
335 conn
.ehlo_param
.push_back(in
.string());
340 struct action
<ehlo_line
> {
341 template <typename Input
>
342 static void apply(Input
const& in
, Connection
& conn
)
344 conn
.ehlo_params
.emplace(std::move(conn
.ehlo_keyword
),
345 std::move(conn
.ehlo_param
));
350 struct action
<reply_lines
> {
351 template <typename Input
>
352 static void apply(Input
const& in
, Connection
& conn
)
354 imemstream stream
{begin(in
), size(in
)};
356 while (std::getline(stream
, line
)) {
357 LOG(INFO
) << "S: " << line
;
363 struct action
<reply_code
> {
364 template <typename Input
>
365 static void apply(Input
const& in
, Connection
& conn
)
367 conn
.reply_code
= in
.string();
373 bool is_localhost(DNS::RR
const& rr
)
375 if (std::holds_alternative
<DNS::RR_MX
>(rr
)) {
376 if (iequal(std::get
<DNS::RR_MX
>(rr
).exchange(), "localhost"))
382 std::vector
<Domain
> get_mxs(DNS::Resolver
& res
, Domain
const& domain
)
384 auto mxs
{std::vector
<Domain
>{}};
386 // Non-local part is an address literal.
387 if (domain
.is_address_literal()) {
388 mxs
.emplace_back(domain
);
392 // RFC 5321 section 5.1 "Locating the Target Host"
394 // “The lookup first attempts to locate an MX record associated with
395 // the name. If a CNAME record is found, the resulting name is
396 // processed as if it were the initial name.”
398 // Our (full) resolver will traverse any CNAMEs for us and return
399 // the CNAME and MX records all together.
401 auto const& dom
= domain
.ascii();
403 auto q
{DNS::Query
{res
, DNS::RR_type::MX
, dom
}};
404 auto mx_recs
{q
.get_records()};
406 mx_recs
.erase(std::remove_if(begin(mx_recs
), end(mx_recs
), is_localhost
),
410 std::count_if(begin(mx_recs
), end(mx_recs
), [](auto const& rr
) {
411 return std::holds_alternative
<DNS::RR_MX
>(rr
);
415 for (auto const& mx
: mx_recs
) {
416 if (std::holds_alternative
<DNS::RR_MX
>(mx
)) {
417 // RFC 7505 null MX record
418 if ((std::get
<DNS::RR_MX
>(mx
).preference() == 0) &&
419 (std::get
<DNS::RR_MX
>(mx
).exchange().empty() ||
420 (std::get
<DNS::RR_MX
>(mx
).exchange() == "."))) {
421 LOG(WARNING
) << "domain " << dom
<< " does not accept mail";
429 // domain must have address record
430 mxs
.emplace_back(dom
);
434 // […] then the sender-SMTP MUST randomize them to spread the load
435 // across multiple mail exchangers for a specific organization.
436 std::shuffle(begin(mx_recs
), end(mx_recs
), std::random_device());
437 std::sort(begin(mx_recs
), end(mx_recs
), [](auto const& a
, auto const& b
) {
438 if (std::holds_alternative
<DNS::RR_MX
>(a
) &&
439 std::holds_alternative
<DNS::RR_MX
>(b
)) {
440 return std::get
<DNS::RR_MX
>(a
).preference() <
441 std::get
<DNS::RR_MX
>(b
).preference();
446 LOG(INFO
) << "MXs for " << domain
<< " are:";
447 for (auto const& mx
: mx_recs
) {
448 if (std::holds_alternative
<DNS::RR_MX
>(mx
)) {
449 mxs
.emplace_back(std::get
<DNS::RR_MX
>(mx
).exchange());
450 LOG(INFO
) << std::setfill(' ') << std::setw(3)
451 << std::get
<DNS::RR_MX
>(mx
).preference() << " "
452 << std::get
<DNS::RR_MX
>(mx
).exchange();
456 for (auto const& mx
: mxs
) {
457 if (mx
.is_address_literal()) {
458 LOG(WARNING
) << "MX record for " << dom
459 << " contains address literal: " << mx
;
466 int conn(DNS::Resolver
& res
, Domain
const& node
, uint16_t port
)
468 int fd
= socket(AF_INET
, SOCK_STREAM
, 0);
469 PCHECK(fd
>= 0) << "socket() failed";
471 if (!FLAGS_local_address
.empty()) {
472 auto loc
{sockaddr_in
{}};
473 loc
.sin_family
= AF_INET
;
474 if (1 != inet_pton(AF_INET
, FLAGS_local_address
.c_str(),
475 reinterpret_cast<void*>(&loc
.sin_addr
))) {
476 LOG(FATAL
) << "can't interpret " << FLAGS_local_address
477 << " as IPv4 address";
479 PCHECK(0 == bind(fd
, reinterpret_cast<sockaddr
*>(&loc
), sizeof(loc
)));
482 auto addrs
{std::vector
<std::string
>{}};
483 if (node
.is_address_literal()) {
484 if (IP4::is_address(node
.ascii())) {
485 addrs
.push_back(node
.ascii());
487 if (IP4::is_address_literal(node
.ascii())) {
488 auto const addr
= IP4::as_address(node
.ascii());
489 addrs
.push_back(std::string(addr
.data(), addr
.length()));
493 addrs
= res
.get_strings(DNS::RR_type::A
, node
.ascii());
495 for (auto addr
: addrs
) {
496 auto in4
{sockaddr_in
{}};
497 in4
.sin_family
= AF_INET
;
498 in4
.sin_port
= htons(port
);
499 CHECK_EQ(inet_pton(AF_INET
, addr
.c_str(),
500 reinterpret_cast<void*>(&in4
.sin_addr
)),
502 if (connect(fd
, reinterpret_cast<const sockaddr
*>(&in4
), sizeof(in4
))) {
503 PLOG(WARNING
) << "connect failed " << addr
<< ":" << port
;
507 // LOG(INFO) << fd << " connected to " << addr << ":" << port;
515 std::optional
<std::unique_ptr
<SMTP::Connection
>>
516 open_session(DNS::Resolver
& res
,
517 fs::path config_path
,
522 auto const port
{osutil::get_port(service
, "tcp")};
524 int fd
= conn(res
, mx
, port
);
526 LOG(WARNING
) << mx
<< " no connection";
530 // Listen for greeting
532 auto constexpr read_hook
{[]() {}};
533 auto conn
= std::make_unique
<SMTP::Connection
>(fd
, fd
, read_hook
);
536 istream_input
<eol::crlf
, 1>{conn
->sock
.in(), FLAGS_pbfr_size
, "session"};
537 if (!parse
<SMTP::greeting
, SMTP::action
>(in
, *conn
)) {
538 LOG(WARNING
) << "greeting was unrecognizable";
542 if (!conn
->greeting_ok
) {
543 LOG(WARNING
) << "greeting was not in the affirmative";
550 auto use_esmtp
= FLAGS_use_esmtp
;
552 LOG(INFO
) << "C: EHLO " << sender
.ascii();
553 conn
->sock
.out() << "EHLO " << sender
.ascii() << "\r\n" << std::flush
;
554 if (!parse
<SMTP::ehlo_rsp
, SMTP::action
>(in
, *conn
) || !conn
->ehlo_ok
) {
555 LOG(WARNING
) << "EHLO response was unrecognizable, trying HELO";
560 LOG(INFO
) << "C: HELO " << sender
.ascii();
561 conn
->sock
.out() << "HELO " << sender
.ascii() << "\r\n" << std::flush
;
562 if (!parse
<SMTP::helo_ok_rsp
, SMTP::action
>(in
, *conn
)) {
563 LOG(ERROR
) << "HELO response was unrecognizable";
571 if (conn
->has_extension("STARTTLS")) {
572 LOG(INFO
) << "C: STARTTLS";
573 conn
->sock
.out() << "STARTTLS\r\n" << std::flush
;
574 if (!parse
<SMTP::reply_lines
, SMTP::action
>(in
, *conn
)) {
575 LOG(ERROR
) << "STARTTLS response was unrecognizable";
580 DNS::RR_collection tlsa_rrs
; // FIXME
581 if (!conn
->sock
.starttls_client(config_path
, sender
.ascii().c_str(),
582 mx
.ascii().c_str(), tlsa_rrs
, false)) {
583 LOG(WARNING
) << "failed to STARTTLS";
588 LOG(INFO
) << "C: EHLO " << sender
.ascii();
589 conn
->sock
.out() << "EHLO " << sender
.ascii() << "\r\n" << std::flush
;
590 if (!parse
<SMTP::ehlo_rsp
, SMTP::action
>(in
, *conn
) || !conn
->ehlo_ok
) {
591 LOG(WARNING
) << "EHLO response was unrecognizable, trying HELO";
597 return std::optional
<std::unique_ptr
<SMTP::Connection
>>(std::move(conn
));
600 std::string
from_params(SMTP::Connection
& conn
)
602 std::ostringstream param_stream
;
603 // param_stream << " SIZE=" << total_size;
605 if (conn
.has_extension("BINARYMIME")) {
606 param_stream
<< " BODY=BINARYMIME";
608 else if (conn
.has_extension("8BITMIME")) {
609 param_stream
<< " BODY=8BITMIME";
612 if (conn
.has_extension("SMTPUTF8")) {
613 param_stream
<< " SMTPUTF8";
616 return param_stream
.str();
619 bool do_reply_lines(SMTP::Connection
& conn
,
620 std::string_view info
,
621 std::string
& error_msg
)
623 auto in
{istream_input
<eol::crlf
, 1>{conn
.sock
.in(), FLAGS_pbfr_size
,
625 if (!parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
)) {
626 LOG(ERROR
) << info
<< ": reply unparseable";
628 "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n";
631 if (conn
.reply_code
.at(0) == '5') {
632 LOG(WARNING
) << info
<< ": negative reply " << conn
.reply_code
;
633 error_msg
= "554 5.5.4 Permanent error\r\n";
636 if (conn
.reply_code
.at(0) != '2') {
637 LOG(WARNING
) << info
<< ": negative reply " << conn
.reply_code
;
639 "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n";
646 bool do_mail_from(SMTP::Connection
& conn
,
648 std::string
& error_msg
)
650 auto const param_str
= from_params(conn
);
652 LOG(INFO
) << "C: MAIL FROM:<" << mail_from
<< '>' << param_str
;
653 conn
.sock
.out() << "MAIL FROM:<" << mail_from
<< '>' << param_str
<< "\r\n";
655 conn
.sock
.out() << std::flush
;
657 return do_reply_lines(conn
, "MAIL FROM", error_msg
);
660 bool do_rcpt_to(SMTP::Connection
& conn
, Mailbox rcpt_to
, std::string
& error_msg
)
662 LOG(INFO
) << "C: RCPT TO:<" << rcpt_to
<< '>';
663 conn
.sock
.out() << "RCPT TO:<" << rcpt_to
<< ">\r\n";
665 conn
.sock
.out() << std::flush
;
667 return do_reply_lines(conn
, "RCPT TO", error_msg
);
670 bool mail_from_rcpt_to_pipelined(SMTP::Connection
& conn
,
673 std::string
& error_msg
)
675 auto const param_str
= from_params(conn
);
677 LOG(INFO
) << "C: MAIL FROM:<" << mail_from
<< '>' << param_str
;
678 conn
.sock
.out() << "MAIL FROM:<" << mail_from
<< '>' << param_str
<< "\r\n";
680 LOG(INFO
) << "C: RCPT TO:<" << rcpt_to
<< '>';
681 conn
.sock
.out() << "RCPT TO:<" << rcpt_to
<< ">\r\n";
683 conn
.sock
.out() << std::flush
;
685 return do_reply_lines(conn
, "MAIL FROM", error_msg
) &&
686 do_reply_lines(conn
, "RCPT TO", error_msg
);
689 bool do_mail_from_rcpt_to(SMTP::Connection
& conn
,
692 std::string
& error_msg
)
694 if (conn
.has_extension("PIPELINING"))
695 return mail_from_rcpt_to_pipelined(conn
, mail_from
, rcpt_to
, error_msg
);
697 if (!do_mail_from(conn
, mail_from
, error_msg
)) {
698 LOG(ERROR
) << "MAIL FROM: failed";
701 if (!do_rcpt_to(conn
, rcpt_to
, error_msg
)) {
702 LOG(ERROR
) << "RCPT TO: failed";
709 bool do_data(SMTP::Connection
& conn
, std::istream
& is
)
711 LOG(INFO
) << "C: DATA";
712 conn
.sock
.out() << "DATA\r\n" << std::flush
;
713 auto in
{istream_input
<eol::crlf
, 1>{conn
.sock
.in(), FLAGS_pbfr_size
, "data"}};
714 if (!parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
)) {
715 LOG(ERROR
) << "DATA command reply unparseable";
718 if (conn
.reply_code
!= "354") {
719 LOG(ERROR
) << "DATA returned " << conn
.reply_code
;
724 auto line
{std::string
{}};
726 while (std::getline(is
, line
)) {
728 if (!conn
.sock
.out().good()) {
729 conn
.sock
.log_stats();
730 LOG(ERROR
) << "output no good at line " << lineno
;
733 if (line
.length() && (line
.at(0) == '.')) {
734 conn
.sock
.out() << '.';
736 conn
.sock
.out() << line
;
737 if (line
.back() != '\r') {
738 LOG(WARNING
) << "bare new line in message body at line " << lineno
;
739 conn
.sock
.out() << '\r';
741 conn
.sock
.out() << '\n';
743 if (!conn
.sock
.out().good()) {
744 LOG(ERROR
) << "socket error of some sort after DATA";
749 conn
.sock
.out() << ".\r\n" << std::flush
;
751 if (!parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
)) {
752 LOG(ERROR
) << "DATA reply unparseable";
756 LOG(INFO
) << "reply_code == " << conn
.reply_code
;
757 return conn
.reply_code
.at(0) == '2';
760 bool do_bdat(SMTP::Connection
& conn
, std::istream
& is
)
762 auto bdat_error
= false;
763 std::streamsize
const bfr_size
= 1024 * 1024;
764 iobuffer
<char> bfr(bfr_size
);
767 istream_input
<eol::crlf
, 1>{conn
.sock
.in(), FLAGS_pbfr_size
, "bdat"};
769 is
.read(bfr
.data(), bfr_size
);
770 auto const size_read
= is
.gcount();
772 conn
.sock
.out() << "BDAT " << size_read
<< "\r\n";
773 LOG(INFO
) << "C: BDAT " << size_read
;
775 conn
.sock
.out().write(bfr
.data(), size_read
);
776 conn
.sock
.out() << std::flush
;
778 if (!parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
)) {
779 LOG(ERROR
) << "BDAT reply unparseable";
783 if (conn
.reply_code
!= "250") {
784 LOG(ERROR
) << "BDAT returned " << conn
.reply_code
;
790 conn
.sock
.out() << "BDAT 0 LAST\r\n" << std::flush
;
791 LOG(INFO
) << "C: BDAT 0 LAST";
793 CHECK((parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
)));
794 if (conn
.reply_code
!= "250") {
795 LOG(ERROR
) << "BDAT 0 LAST returned " << conn
.reply_code
;
802 bool do_send(SMTP::Connection
& conn
, std::istream
& is
)
804 if (conn
.has_extension("CHUNKING"))
805 return do_bdat(conn
, is
);
806 return do_data(conn
, is
);
809 bool do_rset(SMTP::Connection
& conn
)
811 LOG(INFO
) << "C: RSET";
812 conn
.sock
.out() << "RSET\r\n" << std::flush
;
814 istream_input
<eol::crlf
, 1>{conn
.sock
.in(), FLAGS_pbfr_size
, "rset"};
815 return parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
);
818 bool do_quit(SMTP::Connection
& conn
)
820 LOG(INFO
) << "C: QUIT";
821 conn
.sock
.out() << "QUIT\r\n" << std::flush
;
823 istream_input
<eol::crlf
, 1>{conn
.sock
.in(), FLAGS_pbfr_size
, "quit"};
824 return parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
);
829 Send::Send(fs::path config_path
, char const* service
)
830 : config_path_(config_path
)
835 bool Send::mail_from_rcpt_to(DNS::Resolver
& res
,
836 Mailbox
const& mail_from
,
837 Mailbox
const& rcpt_to
,
838 std::string
& error_msg
)
841 conn_
->sock
.close_fds();
842 conn_
.reset(nullptr);
844 // Get a connection to an MX for this domain
845 std::vector
<Domain
> mxs
= get_mxs(res
, rcpt_to
.domain());
847 for (auto& mx
: mxs
) {
848 LOG(INFO
) << "### trying " << mx
;
849 // Open new connection.
850 if (auto new_conn
= open_session(res
, config_path_
, mail_from
.domain(), mx
,
853 LOG(INFO
) << "### opened new connection to " << mx
;
854 conn_
= std::move(*new_conn
);
855 return do_mail_from_rcpt_to(*conn_
, mail_from
, rcpt_to
, error_msg
);
859 LOG(WARNING
) << "ran out of mail exchangers for " << rcpt_to
;
860 error_msg
= "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n";
864 bool Send::send(std::string_view msg_input
)
869 auto is
{imemstream
{msg_input
.data(), msg_input
.length()}};
870 if (!do_send(*conn_
, is
)) {
871 LOG(WARNING
) << "failed to send to " << conn_
->server_id
;
882 if (!do_rset(*conn_
)) {
883 LOG(WARNING
) << "failed to rset " << conn_
->server_id
;
892 if (!do_quit(*conn_
)) {
893 LOG(WARNING
) << "failed to quit " << conn_
->server_id
;
895 conn_
->sock
.close_fds();