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
<'"'>, star
<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 a_d_l
: list
<at_domain
, one
<','>> {};
157 struct path
: seq
<opt
<seq
<a_d_l
, colon
>>, mailbox
> {};
159 struct path_only
: seq
<path
, eof
> {};
161 // textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
163 // Although not explicit in the grammar of RFC-6531, in practice UTF-8
164 // is used in the replys.
166 // struct textstring : plus<sor<one<9>, range<32, 126>>> {};
168 struct textstring
: plus
<sor
<one
<9>, range
<32, 126>, chars::non_ascii
>> {};
170 struct server_id
: sor
<domain
, address_literal
> {};
172 // Greeting = ( "220 " (Domain / address-literal) [ SP textstring ] CRLF )
174 // ( "220-" (Domain / address-literal) [ SP textstring ] CRLF
175 // *( "220-" [ textstring ] CRLF )
176 // "220 " [ textstring ] CRLF )
179 : sor
<seq
<TAO_PEGTL_ISTRING("220 "), server_id
, opt
<textstring
>, CRLF
>,
180 seq
<TAO_PEGTL_ISTRING("220-"), server_id
, opt
<textstring
>, CRLF
,
181 star
<seq
<TAO_PEGTL_ISTRING("220-"), opt
<textstring
>, CRLF
>>,
182 seq
<TAO_PEGTL_ISTRING("220 "), opt
<textstring
>, CRLF
>>> {};
184 // Reply-code = %x32-35 %x30-35 %x30-39
187 : seq
<range
<0x32, 0x35>, range
<0x30, 0x35>, range
<0x30, 0x39>> {};
189 // Reply-line = *( Reply-code "-" [ textstring ] CRLF )
190 // Reply-code [ SP textstring ] CRLF
193 : seq
<star
<seq
<reply_code
, one
<'-'>, opt
<textstring
>, CRLF
>>,
194 seq
<reply_code
, opt
<seq
<SP
, textstring
>>, CRLF
>> {};
197 : sor
<greeting_ok
, reply_lines
> {};
199 // ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
200 // ; string of any characters other than CR or LF
202 struct ehlo_greet
: plus
<ranges
<0, 9, 11, 12, 14, 127>> {};
204 // ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
205 // ; additional syntax of ehlo-params depends on
208 // The '.' we also allow in ehlo-keyword since it has been seen in the
209 // wild at least at 263.net.
211 struct ehlo_keyword
: seq
<sor
<ALPHA
, DIGIT
>, star
<sor
<ALPHA
, DIGIT
, dash
, dot
>>> {};
213 // ehlo-param = 1*(%d33-126)
214 // ; any CHAR excluding <SP> and all
215 // ; control characters (US-ASCII 0-31 and 127
218 struct ehlo_param
: plus
<range
<33, 126>> {};
220 // ehlo-line = ehlo-keyword *( SP ehlo-param )
222 // The AUTH= thing is so common with some servers (postfix) that I
223 // guess we have to accept it.
226 : seq
<ehlo_keyword
, star
<seq
<sor
<SP
,one
<'='>>, ehlo_param
>>> {};
228 // ehlo-ok-rsp = ( "250 " Domain [ SP ehlo-greet ] CRLF )
230 // ( "250-" Domain [ SP ehlo-greet ] CRLF
231 // *( "250-" ehlo-line CRLF )
232 // "250 " ehlo-line CRLF )
234 // The last line having the optional ehlo_line is not strictly correct.
235 // Was added to work with postfix/src/smtpstone/smtp-sink.c.
238 : sor
<seq
<TAO_PEGTL_ISTRING("250 "), server_id
, opt
<ehlo_greet
>, CRLF
>,
240 seq
<TAO_PEGTL_ISTRING("250-"), server_id
, opt
<ehlo_greet
>, CRLF
,
241 star
<seq
<TAO_PEGTL_ISTRING("250-"), ehlo_line
, CRLF
>>,
242 seq
<TAO_PEGTL_ISTRING("250 "), opt
<ehlo_line
>, CRLF
>>
246 : sor
<ehlo_ok_rsp
, reply_lines
> {};
249 : seq
<TAO_PEGTL_ISTRING("250 "), server_id
, opt
<ehlo_greet
>, CRLF
> {};
251 struct auth_login_username
252 : seq
<TAO_PEGTL_STRING("334 VXNlcm5hbWU6"), CRLF
> {};
254 struct auth_login_password
255 : seq
<TAO_PEGTL_STRING("334 UGFzc3dvcmQ6"), CRLF
> {};
259 template <typename Rule
>
260 struct inaction
: nothing
<Rule
> {
263 template <typename Rule
>
264 struct action
: nothing
<Rule
> {
268 struct action
<server_id
> {
269 template <typename Input
>
270 static void apply(Input
const& in
, Connection
& conn
)
272 conn
.server_id
= in
.string();
277 struct action
<local_part
> {
278 template <typename Input
>
279 static void apply(Input
const& in
, Mailbox
& mbx
)
281 mbx
.set_local(in
.string());
286 struct action
<non_local_part
> {
287 template <typename Input
>
288 static void apply(Input
const& in
, Mailbox
& mbx
)
290 mbx
.set_domain(in
.string());
295 struct action
<greeting_ok
> {
296 template <typename Input
>
297 static void apply(Input
const& in
, Connection
& conn
)
299 conn
.greeting_ok
= true;
300 imemstream stream
{begin(in
), size(in
)};
302 while (std::getline(stream
, line
)) {
303 LOG(INFO
) << "S: " << line
;
309 struct action
<ehlo_ok_rsp
> {
310 template <typename Input
>
311 static void apply(Input
const& in
, Connection
& conn
)
314 imemstream stream
{begin(in
), size(in
)};
316 while (std::getline(stream
, line
)) {
317 LOG(INFO
) << "S: " << line
;
323 struct action
<ehlo_keyword
> {
324 template <typename Input
>
325 static void apply(Input
const& in
, Connection
& conn
)
327 conn
.ehlo_keyword
= in
.string();
328 boost::to_upper(conn
.ehlo_keyword
);
333 struct action
<ehlo_param
> {
334 template <typename Input
>
335 static void apply(Input
const& in
, Connection
& conn
)
337 conn
.ehlo_param
.push_back(in
.string());
342 struct action
<ehlo_line
> {
343 template <typename Input
>
344 static void apply(Input
const& in
, Connection
& conn
)
346 conn
.ehlo_params
.emplace(std::move(conn
.ehlo_keyword
),
347 std::move(conn
.ehlo_param
));
352 struct action
<reply_lines
> {
353 template <typename Input
>
354 static void apply(Input
const& in
, Connection
& conn
)
356 imemstream stream
{begin(in
), size(in
)};
358 while (std::getline(stream
, line
)) {
359 LOG(INFO
) << "S: " << line
;
365 struct action
<reply_code
> {
366 template <typename Input
>
367 static void apply(Input
const& in
, Connection
& conn
)
369 conn
.reply_code
= in
.string();
375 bool is_localhost(DNS::RR
const& rr
)
377 if (std::holds_alternative
<DNS::RR_MX
>(rr
)) {
378 if (iequal(std::get
<DNS::RR_MX
>(rr
).exchange(), "localhost"))
384 std::vector
<Domain
> get_mxs(DNS::Resolver
& res
, Domain
const& domain
)
386 auto mxs
{std::vector
<Domain
>{}};
388 // Non-local part is an address literal.
389 if (domain
.is_address_literal()) {
390 mxs
.emplace_back(domain
);
394 // RFC 5321 section 5.1 "Locating the Target Host"
396 // “The lookup first attempts to locate an MX record associated with
397 // the name. If a CNAME record is found, the resulting name is
398 // processed as if it were the initial name.”
400 // Our (full) resolver will traverse any CNAMEs for us and return
401 // the CNAME and MX records all together.
403 auto const& dom
= domain
.ascii();
405 auto q
{DNS::Query
{res
, DNS::RR_type::MX
, dom
}};
406 auto mx_recs
{q
.get_records()};
408 mx_recs
.erase(std::remove_if(begin(mx_recs
), end(mx_recs
), is_localhost
),
412 std::count_if(begin(mx_recs
), end(mx_recs
), [](auto const& rr
) {
413 return std::holds_alternative
<DNS::RR_MX
>(rr
);
417 for (auto const& mx
: mx_recs
) {
418 if (std::holds_alternative
<DNS::RR_MX
>(mx
)) {
419 // RFC 7505 null MX record
420 if ((std::get
<DNS::RR_MX
>(mx
).preference() == 0) &&
421 (std::get
<DNS::RR_MX
>(mx
).exchange().empty() ||
422 (std::get
<DNS::RR_MX
>(mx
).exchange() == "."))) {
423 LOG(WARNING
) << "domain " << dom
<< " does not accept mail";
431 // domain must have address record
432 mxs
.emplace_back(dom
);
436 // […] then the sender-SMTP MUST randomize them to spread the load
437 // across multiple mail exchangers for a specific organization.
438 std::shuffle(begin(mx_recs
), end(mx_recs
), std::random_device());
439 std::sort(begin(mx_recs
), end(mx_recs
), [](auto const& a
, auto const& b
) {
440 if (std::holds_alternative
<DNS::RR_MX
>(a
) &&
441 std::holds_alternative
<DNS::RR_MX
>(b
)) {
442 return std::get
<DNS::RR_MX
>(a
).preference() <
443 std::get
<DNS::RR_MX
>(b
).preference();
448 LOG(INFO
) << "MXs for " << domain
<< " are:";
449 for (auto const& mx
: mx_recs
) {
450 if (std::holds_alternative
<DNS::RR_MX
>(mx
)) {
451 mxs
.emplace_back(std::get
<DNS::RR_MX
>(mx
).exchange());
452 LOG(INFO
) << std::setfill(' ') << std::setw(3)
453 << std::get
<DNS::RR_MX
>(mx
).preference() << " "
454 << std::get
<DNS::RR_MX
>(mx
).exchange();
458 for (auto const& mx
: mxs
) {
459 if (mx
.is_address_literal()) {
460 LOG(WARNING
) << "MX record for " << dom
461 << " contains address literal: " << mx
;
468 int conn(DNS::Resolver
& res
, Domain
const& node
, uint16_t port
)
470 int fd
= socket(AF_INET
, SOCK_STREAM
, 0);
471 PCHECK(fd
>= 0) << "socket() failed";
473 if (!FLAGS_local_address
.empty()) {
474 auto loc
{sockaddr_in
{}};
475 loc
.sin_family
= AF_INET
;
476 if (1 != inet_pton(AF_INET
, FLAGS_local_address
.c_str(),
477 reinterpret_cast<void*>(&loc
.sin_addr
))) {
478 LOG(FATAL
) << "can't interpret " << FLAGS_local_address
479 << " as IPv4 address";
481 PCHECK(0 == bind(fd
, reinterpret_cast<sockaddr
*>(&loc
), sizeof(loc
)));
484 auto addrs
{std::vector
<std::string
>{}};
485 if (node
.is_address_literal()) {
486 if (IP4::is_address(node
.ascii())) {
487 addrs
.push_back(node
.ascii());
489 if (IP4::is_address_literal(node
.ascii())) {
490 auto const addr
= IP4::as_address(node
.ascii());
491 addrs
.push_back(std::string(addr
.data(), addr
.length()));
495 addrs
= res
.get_strings(DNS::RR_type::A
, node
.ascii());
497 for (auto addr
: addrs
) {
498 auto in4
{sockaddr_in
{}};
499 in4
.sin_family
= AF_INET
;
500 in4
.sin_port
= htons(port
);
501 CHECK_EQ(inet_pton(AF_INET
, addr
.c_str(),
502 reinterpret_cast<void*>(&in4
.sin_addr
)),
504 if (connect(fd
, reinterpret_cast<const sockaddr
*>(&in4
), sizeof(in4
))) {
505 PLOG(WARNING
) << "connect failed " << addr
<< ":" << port
;
509 // LOG(INFO) << fd << " connected to " << addr << ":" << port;
517 std::optional
<std::unique_ptr
<SMTP::Connection
>>
518 open_session(DNS::Resolver
& res
,
519 fs::path config_path
,
524 auto const port
{osutil::get_port(service
, "tcp")};
526 int fd
= conn(res
, mx
, port
);
528 LOG(WARNING
) << mx
<< " no connection";
532 // Listen for greeting
534 auto constexpr read_hook
{[]() {}};
535 auto conn
= std::make_unique
<SMTP::Connection
>(fd
, fd
, read_hook
);
538 istream_input
<eol::crlf
, 1>{conn
->sock
.in(), FLAGS_pbfr_size
, "session"};
539 if (!parse
<SMTP::greeting
, SMTP::action
>(in
, *conn
)) {
540 LOG(WARNING
) << "greeting was unrecognizable";
544 if (!conn
->greeting_ok
) {
545 LOG(WARNING
) << "greeting was not in the affirmative";
552 auto use_esmtp
= FLAGS_use_esmtp
;
554 LOG(INFO
) << "C: EHLO " << sender
.ascii();
555 conn
->sock
.out() << "EHLO " << sender
.ascii() << "\r\n" << std::flush
;
556 if (!parse
<SMTP::ehlo_rsp
, SMTP::action
>(in
, *conn
) || !conn
->ehlo_ok
) {
557 LOG(WARNING
) << "EHLO response was unrecognizable, trying HELO";
562 LOG(INFO
) << "C: HELO " << sender
.ascii();
563 conn
->sock
.out() << "HELO " << sender
.ascii() << "\r\n" << std::flush
;
564 if (!parse
<SMTP::helo_ok_rsp
, SMTP::action
>(in
, *conn
)) {
565 LOG(ERROR
) << "HELO response was unrecognizable";
573 if (conn
->has_extension("STARTTLS")) {
574 LOG(INFO
) << "C: STARTTLS";
575 conn
->sock
.out() << "STARTTLS\r\n" << std::flush
;
576 if (!parse
<SMTP::reply_lines
, SMTP::action
>(in
, *conn
)) {
577 LOG(ERROR
) << "STARTTLS response was unrecognizable";
582 DNS::RR_collection tlsa_rrs
; // FIXME
583 if (!conn
->sock
.starttls_client(config_path
, sender
.ascii().c_str(),
584 mx
.ascii().c_str(), tlsa_rrs
, false)) {
585 LOG(WARNING
) << "failed to STARTTLS";
590 LOG(INFO
) << "C: EHLO " << sender
.ascii();
591 conn
->sock
.out() << "EHLO " << sender
.ascii() << "\r\n" << std::flush
;
592 if (!parse
<SMTP::ehlo_rsp
, SMTP::action
>(in
, *conn
) || !conn
->ehlo_ok
) {
593 LOG(WARNING
) << "EHLO response was unrecognizable, trying HELO";
599 return std::optional
<std::unique_ptr
<SMTP::Connection
>>(std::move(conn
));
602 std::string
from_params(SMTP::Connection
& conn
)
604 std::ostringstream param_stream
;
605 // param_stream << " SIZE=" << total_size;
607 if (conn
.has_extension("BINARYMIME")) {
608 param_stream
<< " BODY=BINARYMIME";
610 else if (conn
.has_extension("8BITMIME")) {
611 param_stream
<< " BODY=8BITMIME";
614 if (conn
.has_extension("SMTPUTF8")) {
615 param_stream
<< " SMTPUTF8";
618 return param_stream
.str();
621 bool do_reply_lines(SMTP::Connection
& conn
,
622 std::string_view info
,
623 std::string
& error_msg
)
625 auto in
{istream_input
<eol::crlf
, 1>{conn
.sock
.in(), FLAGS_pbfr_size
,
627 if (!parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
)) {
628 LOG(ERROR
) << info
<< ": reply unparseable";
630 "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n";
633 if (conn
.reply_code
.at(0) == '5') {
634 LOG(WARNING
) << info
<< ": negative reply " << conn
.reply_code
;
635 error_msg
= "554 5.5.4 Permanent error\r\n";
638 if (conn
.reply_code
.at(0) != '2') {
639 LOG(WARNING
) << info
<< ": negative reply " << conn
.reply_code
;
641 "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n";
648 bool do_mail_from(SMTP::Connection
& conn
,
650 std::string
& error_msg
)
652 auto const param_str
= from_params(conn
);
654 LOG(INFO
) << "C: MAIL FROM:<" << mail_from
<< '>' << param_str
;
655 conn
.sock
.out() << "MAIL FROM:<" << mail_from
<< '>' << param_str
<< "\r\n";
657 conn
.sock
.out() << std::flush
;
659 return do_reply_lines(conn
, "MAIL FROM", error_msg
);
662 bool do_rcpt_to(SMTP::Connection
& conn
, Mailbox rcpt_to
, std::string
& error_msg
)
664 LOG(INFO
) << "C: RCPT TO:<" << rcpt_to
<< '>';
665 conn
.sock
.out() << "RCPT TO:<" << rcpt_to
<< ">\r\n";
667 conn
.sock
.out() << std::flush
;
669 return do_reply_lines(conn
, "RCPT TO", error_msg
);
672 bool mail_from_rcpt_to_pipelined(SMTP::Connection
& conn
,
675 std::string
& error_msg
)
677 auto const param_str
= from_params(conn
);
679 LOG(INFO
) << "C: MAIL FROM:<" << mail_from
<< '>' << param_str
;
680 conn
.sock
.out() << "MAIL FROM:<" << mail_from
<< '>' << param_str
<< "\r\n";
682 LOG(INFO
) << "C: RCPT TO:<" << rcpt_to
<< '>';
683 conn
.sock
.out() << "RCPT TO:<" << rcpt_to
<< ">\r\n";
685 conn
.sock
.out() << std::flush
;
687 return do_reply_lines(conn
, "MAIL FROM", error_msg
) &&
688 do_reply_lines(conn
, "RCPT TO", error_msg
);
691 bool do_mail_from_rcpt_to(SMTP::Connection
& conn
,
694 std::string
& error_msg
)
696 if (conn
.has_extension("PIPELINING"))
697 return mail_from_rcpt_to_pipelined(conn
, mail_from
, rcpt_to
, error_msg
);
699 if (!do_mail_from(conn
, mail_from
, error_msg
)) {
700 LOG(ERROR
) << "MAIL FROM: failed";
703 if (!do_rcpt_to(conn
, rcpt_to
, error_msg
)) {
704 LOG(ERROR
) << "RCPT TO: failed";
711 bool do_data(SMTP::Connection
& conn
, std::istream
& is
)
713 LOG(INFO
) << "C: DATA";
714 conn
.sock
.out() << "DATA\r\n" << std::flush
;
715 auto in
{istream_input
<eol::crlf
, 1>{conn
.sock
.in(), FLAGS_pbfr_size
, "data"}};
716 if (!parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
)) {
717 LOG(ERROR
) << "DATA command reply unparseable";
720 if (conn
.reply_code
!= "354") {
721 LOG(ERROR
) << "DATA returned " << conn
.reply_code
;
726 auto line
{std::string
{}};
728 while (std::getline(is
, line
)) {
730 if (!conn
.sock
.out().good()) {
731 conn
.sock
.log_stats();
732 LOG(ERROR
) << "output no good at line " << lineno
;
735 if (line
.length() && (line
.at(0) == '.')) {
736 conn
.sock
.out() << '.';
738 conn
.sock
.out() << line
;
739 if (line
.back() != '\r') {
740 LOG(WARNING
) << "bare new line in message body at line " << lineno
;
741 conn
.sock
.out() << '\r';
743 conn
.sock
.out() << '\n';
745 if (!conn
.sock
.out().good()) {
746 LOG(ERROR
) << "socket error of some sort after DATA";
751 conn
.sock
.out() << ".\r\n" << std::flush
;
753 if (!parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
)) {
754 LOG(ERROR
) << "DATA reply unparseable";
758 LOG(INFO
) << "reply_code == " << conn
.reply_code
;
759 return conn
.reply_code
.at(0) == '2';
762 bool do_bdat(SMTP::Connection
& conn
, std::istream
& is
)
764 auto bdat_error
= false;
765 std::streamsize
const bfr_size
= 1024 * 1024;
766 iobuffer
<char> bfr(bfr_size
);
769 istream_input
<eol::crlf
, 1>{conn
.sock
.in(), FLAGS_pbfr_size
, "bdat"};
771 is
.read(bfr
.data(), bfr_size
);
772 auto const size_read
= is
.gcount();
774 conn
.sock
.out() << "BDAT " << size_read
<< "\r\n";
775 LOG(INFO
) << "C: BDAT " << size_read
;
777 conn
.sock
.out().write(bfr
.data(), size_read
);
778 conn
.sock
.out() << std::flush
;
780 if (!parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
)) {
781 LOG(ERROR
) << "BDAT reply unparseable";
785 if (conn
.reply_code
!= "250") {
786 LOG(ERROR
) << "BDAT returned " << conn
.reply_code
;
792 conn
.sock
.out() << "BDAT 0 LAST\r\n" << std::flush
;
793 LOG(INFO
) << "C: BDAT 0 LAST";
795 CHECK((parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
)));
796 if (conn
.reply_code
!= "250") {
797 LOG(ERROR
) << "BDAT 0 LAST returned " << conn
.reply_code
;
804 bool do_send(SMTP::Connection
& conn
, std::istream
& is
)
806 if (conn
.has_extension("CHUNKING"))
807 return do_bdat(conn
, is
);
808 return do_data(conn
, is
);
811 bool do_rset(SMTP::Connection
& conn
)
813 LOG(INFO
) << "C: RSET";
814 conn
.sock
.out() << "RSET\r\n" << std::flush
;
816 istream_input
<eol::crlf
, 1>{conn
.sock
.in(), FLAGS_pbfr_size
, "rset"};
817 return parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
);
820 bool do_quit(SMTP::Connection
& conn
)
822 LOG(INFO
) << "C: QUIT";
823 conn
.sock
.out() << "QUIT\r\n" << std::flush
;
825 istream_input
<eol::crlf
, 1>{conn
.sock
.in(), FLAGS_pbfr_size
, "quit"};
826 return parse
<SMTP::reply_lines
, SMTP::action
>(in
, conn
);
831 Send::Send(fs::path config_path
, char const* service
)
832 : config_path_(config_path
)
837 bool Send::mail_from_rcpt_to(DNS::Resolver
& res
,
838 Mailbox
const& mail_from
,
839 Mailbox
const& rcpt_to
,
840 std::string
& error_msg
)
843 conn_
->sock
.close_fds();
844 conn_
.reset(nullptr);
846 // Get a connection to an MX for this domain
847 std::vector
<Domain
> mxs
= get_mxs(res
, rcpt_to
.domain());
849 for (auto& mx
: mxs
) {
850 LOG(INFO
) << "### trying " << mx
;
851 // Open new connection.
852 if (auto new_conn
= open_session(res
, config_path_
, mail_from
.domain(), mx
,
855 LOG(INFO
) << "### opened new connection to " << mx
;
856 conn_
= std::move(*new_conn
);
857 return do_mail_from_rcpt_to(*conn_
, mail_from
, rcpt_to
, error_msg
);
861 LOG(WARNING
) << "ran out of mail exchangers for " << rcpt_to
;
862 error_msg
= "432 4.3.0 Recipient's incoming mail queue has been stopped\r\n";
866 bool Send::send(std::string_view msg_input
)
871 auto is
{imemstream
{msg_input
.data(), msg_input
.length()}};
872 if (!do_send(*conn_
, is
)) {
873 LOG(WARNING
) << "failed to send to " << conn_
->server_id
;
884 if (!do_rset(*conn_
)) {
885 LOG(WARNING
) << "failed to rset " << conn_
->server_id
;
894 if (!do_quit(*conn_
)) {
895 LOG(WARNING
) << "failed to quit " << conn_
->server_id
;
897 conn_
->sock
.close_fds();