1 // Toy program to send email. This is used to test my SMTP server,
2 // mostly. It's overgrown a bit.
4 #include <gflags/gflags.h>
6 // in case we didn't have one
9 #include <range/v3/numeric/accumulate.hpp>
11 namespace rng
= ranges
;
13 DEFINE_uint64(reps
, 1, "now many duplicate transactions per connection");
15 // This needs to be at least the length of each string it's trying to match.
16 DEFINE_uint64(bfr_size
, 4 * 1024, "parser buffer size");
18 DEFINE_bool(selftest
, false, "run a self test");
20 DEFINE_bool(pipeline_quit
, false, "pipeline the QUIT command");
21 DEFINE_bool(badpipline
, false, "send two NOOPs back-to-back");
22 DEFINE_bool(bare_lf
, false, "send a bare LF");
23 DEFINE_bool(huge_size
, false, "attempt with huge size");
24 DEFINE_bool(long_line
, false, "super long text line");
25 DEFINE_bool(noconn
, false, "don't connect to any host");
26 DEFINE_bool(noop
, false, "send a NOOP right after EHLO");
27 DEFINE_bool(nosend
, false, "don't actually send any mail");
28 DEFINE_bool(pipe
, false, "send to stdin/stdout");
29 DEFINE_bool(rawmsg
, false, "the body file includes the headers");
32 "send the body exactly as is, don't fix CRLF issues "
33 "or escape leading dots");
34 DEFINE_bool(require_tls
, true, "use STARTTLS or die");
35 DEFINE_bool(save
, false, "save mail in .Sent");
36 DEFINE_bool(slow_strangle
, false, "super slow mo");
37 DEFINE_bool(to_the_neck
, false, "shove data forever");
39 DEFINE_bool(use_8bitmime
, true, "use 8BITMIME extension");
40 DEFINE_bool(use_binarymime
, true, "use BINARYMIME extension");
41 DEFINE_bool(use_chunking
, true, "use CHUNKING extension");
42 DEFINE_bool(use_deliverby
, false, "use DELIVERBY extension");
43 DEFINE_bool(use_esmtp
, true, "use ESMTP (EHLO)");
44 DEFINE_bool(use_pipelining
, true, "use PIPELINING extension");
45 DEFINE_bool(use_prdr
, true, "use PRDR extension");
46 DEFINE_bool(use_size
, true, "use SIZE extension");
47 DEFINE_bool(use_smtputf8
, true, "use SMTPUTF8 extension");
48 DEFINE_bool(use_tls
, true, "use STARTTLS extension");
50 // To force it, set if you have UTF8 in the local part of any RFC5321
52 DEFINE_bool(force_smtputf8
, false, "force SMTPUTF8 extension");
54 DEFINE_string(sender
, "", "FQDN of sending node");
56 DEFINE_string(local_address
, "", "local address to bind");
57 DEFINE_string(mx_host
, "", "FQDN of receiving node");
58 DEFINE_string(service
, "smtp-test", "service name");
59 DEFINE_string(client_id
, "", "client name (ID) for EHLO/HELO");
61 DEFINE_string(from
, "", "RFC5322 From: address");
62 DEFINE_string(from_name
, "", "RFC5322 From: name");
64 DEFINE_string(to
, "", "RFC5322 To: address");
65 DEFINE_string(to_name
, "", "RFC5322 To: name");
67 DEFINE_string(smtp_from
, "", "RFC5321 MAIL FROM address");
68 DEFINE_string(smtp_to
, "", "RFC5321 RCPT TO address");
69 DEFINE_string(smtp_to2
, "", "second RFC5321 RCPT TO address");
70 DEFINE_string(smtp_to3
, "", "third RFC5321 RCPT TO address");
72 DEFINE_string(content_type
, "", "RFC5322 Content-Type");
73 DEFINE_string(content_transfer_encoding
,
75 "RFC5322 Content-Transfer-Encoding");
77 DEFINE_string(subject
, "testing one, two, three...", "RFC5322 Subject");
78 DEFINE_string(keywords
, "", "RFC5322 Keywords: header");
79 DEFINE_string(references
, "", "RFC5322 References: header");
80 DEFINE_string(in_reply_to
, "", "RFC5322 In-Reply-To: header");
81 DEFINE_string(reply_to
, "", "RFC5322 Reply-To: header");
82 DEFINE_string(reply_2
, "", "Second RFC5322 Reply-To: header");
84 DEFINE_bool(4, false, "use only IP version 4");
85 DEFINE_bool(6, false, "use only IP version 6");
87 DEFINE_string(username
, "", "AUTH username");
88 DEFINE_string(password
, "", "AUTH password");
90 DEFINE_bool(use_dkim
, true, "sign with DKIM");
91 DEFINE_bool(bogus_dkim
, false, "sign with bogus DKIM");
92 DEFINE_string(selector
, "ghsmtp", "DKIM selector");
93 DEFINE_string(dkim_key_file
, "", "DKIM key file");
96 #include "DNS-fcrdns.hpp"
102 #include "Mailbox.hpp"
103 #include "MessageStore.hpp"
105 #include "OpenDKIM.hpp"
109 #include "imemstream.hpp"
110 #include "osutil.hpp"
115 #include <functional>
121 #include <string_view>
122 #include <unordered_map>
125 #include <sys/socket.h>
126 #include <sys/types.h>
128 #include <fmt/format.h>
129 #include <fmt/ostream.h>
131 #include <boost/algorithm/string/case_conv.hpp>
133 #include <boost/iostreams/device/mapped_file.hpp>
135 #include <tao/pegtl.hpp>
136 #include <tao/pegtl/contrib/abnf.hpp>
138 using namespace tao::pegtl
;
139 using namespace tao::pegtl::abnf
;
141 using namespace std::string_literals
;
144 constexpr auto read_timeout
= std::chrono::minutes(24 * 60);
145 constexpr auto write_timeout
= std::chrono::minutes(24 * 60);
146 } // namespace Config
151 struct tail
: range
<'\x80', '\xBF'> {};
153 struct ch_1
: range
<'\x00', '\x7F'> {};
155 struct ch_2
: seq
<range
<'\xC2', '\xDF'>, tail
> {};
157 struct ch_3
: sor
<seq
<one
<'\xE0'>, range
<'\xA0', '\xBF'>, tail
>,
158 seq
<range
<'\xE1', '\xEC'>, rep
<2, tail
>>,
159 seq
<one
<'\xED'>, range
<'\x80', '\x9F'>, tail
>,
160 seq
<range
<'\xEE', '\xEF'>, rep
<2, tail
>>> {};
162 struct ch_4
: sor
<seq
<one
<'\xF0'>, range
<'\x90', '\xBF'>, rep
<2, tail
>>,
163 seq
<range
<'\xF1', '\xF3'>, rep
<3, tail
>>,
164 seq
<one
<'\xF4'>, range
<'\x80', '\x8F'>, rep
<2, tail
>>> {};
166 struct u8char
: sor
<ch_1
, ch_2
, ch_3
, ch_4
> {};
168 struct non_ascii
: sor
<ch_2
, ch_3
, ch_4
> {};
170 struct ascii_only
: seq
<star
<ch_1
>, eof
> {};
172 struct utf8_only
: seq
<star
<u8char
>, eof
> {};
177 struct VUCHAR
: sor
<VCHAR
, chars::non_ascii
> {};
179 using dot
= one
<'.'>;
180 using colon
= one
<':'>;
182 // All 7-bit ASCII except NUL (0), LF (10) and CR (13).
183 struct text_ascii
: tao::pegtl::ranges
<1, 9, 11, 12, 14, 127> {};
185 // Short lines of ASCII text. LF or CRLF line separators.
186 struct body_ascii
: seq
<star
<seq
<rep_max
<998, text_ascii
>, eol
>>,
187 opt
<rep_max
<998, text_ascii
>>, eof
> {};
189 struct text_utf8
: sor
<text_ascii
, chars::non_ascii
> {};
191 // Short lines of UTF-8 text. LF or CRLF line separators.
192 struct body_utf8
: seq
<star
<seq
<rep_max
<998, text_utf8
>, eol
>>,
193 opt
<rep_max
<998, text_utf8
>>, eof
> {};
195 struct FWS
: seq
<opt
<seq
<star
<WSP
>, eol
>>, plus
<WSP
>> {};
197 struct qtext
: sor
<one
<33>, tao::pegtl::ranges
<35, 91, 93, 126>, chars::non_ascii
> {};
199 struct quoted_pair
: seq
<one
<'\\'>, sor
<VUCHAR
, WSP
>> {};
201 struct atext
: sor
<ALPHA
, DIGIT
,
212 chars::non_ascii
> {};
214 // ctext is ASCII not '(' or ')' or '\\'
215 struct ctext
: sor
<tao::pegtl::ranges
<33, 39, 42, 91, 93, 126>, chars::non_ascii
> {};
219 struct ccontent
: sor
<ctext
, quoted_pair
, comment
> {};
222 : seq
<one
<'('>, star
<seq
<opt
<FWS
>, ccontent
>>, opt
<FWS
>, one
<')'>> {};
224 struct CFWS
: sor
<seq
<plus
<seq
<opt
<FWS
>, comment
>, opt
<FWS
>>>, FWS
> {};
226 struct qcontent
: sor
<qtext
, quoted_pair
> {};
228 // Corrected in errata ID: 3135
232 sor
<seq
<star
<seq
<opt
<FWS
>, qcontent
>>, opt
<FWS
>>, FWS
>,
236 // *([FWS] VCHAR) *WSP
237 struct unstructured
: seq
<star
<seq
<opt
<FWS
>, VUCHAR
>>, star
<WSP
>> {};
239 struct atom
: seq
<opt
<CFWS
>, plus
<atext
>, opt
<CFWS
>> {};
241 struct dot_atom_text
: list
<plus
<atext
>, dot
> {};
243 struct dot_atom
: seq
<opt
<CFWS
>, dot_atom_text
, opt
<CFWS
>> {};
245 struct word
: sor
<atom
, quoted_string
> {};
247 struct phrase
: plus
<word
> {};
249 struct local_part
: sor
<dot_atom
, quoted_string
> {};
251 // from '!' to '~' excluding 91 92 93 '[' '\\' ']'
253 struct dtext
: tao::pegtl::ranges
<33, 90, 94, 126> {};
255 struct domain_literal
: seq
<opt
<CFWS
>,
257 star
<seq
<opt
<FWS
>, dtext
>>,
262 struct domain
: sor
<dot_atom
, domain_literal
> {};
264 struct addr_spec
: seq
<local_part
, one
<'@'>, domain
> {};
266 struct postmaster
: TAO_PEGTL_ISTRING("Postmaster") {};
268 struct addr_spec_or_postmaster
: sor
<addr_spec
, postmaster
> {};
270 struct addr_spec_only
: seq
<addr_spec_or_postmaster
, eof
> {};
272 struct display_name
: phrase
{};
274 struct display_name_only
: seq
<display_name
, eof
> {};
278 // struct name_addr : seq<opt<display_name>, angle_addr> {};
280 // struct mailbox : sor<name_addr, addr_spec> {};
282 template <typename Rule
>
283 struct inaction
: nothing
<Rule
> {};
285 template <typename Rule
>
286 struct action
: nothing
<Rule
> {};
289 struct action
<local_part
> {
290 template <typename Input
>
291 static void apply(Input
const& in
, Mailbox
& mbx
)
293 mbx
.set_local(in
.string());
298 struct action
<domain
> {
299 template <typename Input
>
300 static void apply(Input
const& in
, Mailbox
& mbx
)
302 mbx
.set_domain(in
.string());
305 } // namespace RFC5322
312 std::string server_id
;
314 std::string ehlo_keyword
;
315 std::vector
<std::string
> ehlo_param
;
316 std::unordered_map
<std::string
, std::vector
<std::string
>> ehlo_params
;
318 std::string reply_code
;
320 bool greeting_ok
{false};
323 bool has_extension(char const* name
) const
325 return ehlo_params
.find(name
) != end(ehlo_params
);
328 Connection(int fd_in
, int fd_out
, std::function
<void(void)> read_hook
)
330 fd_in
, fd_out
, read_hook
, Config::read_timeout
, Config::write_timeout
)
337 using dot
= one
<'.'>;
338 using colon
= one
<':'>;
339 using dash
= one
<'-'>;
340 using underscore
= one
<'_'>;
342 struct u_let_dig
: sor
<ALPHA
, DIGIT
, chars::non_ascii
> {};
344 struct u_ldh_tail
: star
<sor
<seq
<plus
<one
<'-'>>, u_let_dig
>, u_let_dig
>> {};
346 struct u_label
: seq
<u_let_dig
, u_ldh_tail
> {};
348 struct let_dig
: sor
<ALPHA
, DIGIT
> {};
350 struct ldh_tail
: star
<sor
<seq
<plus
<one
<'-'>>, let_dig
>, let_dig
>> {};
352 struct ldh_str
: seq
<let_dig
, ldh_tail
> {};
354 struct label
: ldh_str
{};
356 struct sub_domain
: sor
<label
, u_label
> {};
358 struct domain
: list
<sub_domain
, dot
> {};
360 struct dec_octet
: sor
<seq
<string
<'2','5'>, range
<'0','5'>>,
361 seq
<one
<'2'>, range
<'0','4'>, DIGIT
>,
362 seq
<range
<'0', '1'>, rep
<2, DIGIT
>>,
363 rep_min_max
<1, 2, DIGIT
>> {};
365 struct IPv4_address_literal
366 : seq
<dec_octet
, dot
, dec_octet
, dot
, dec_octet
, dot
, dec_octet
> {};
368 struct h16
: rep_min_max
<1, 4, HEXDIG
> {};
370 struct ls32
: sor
<seq
<h16
, colon
, h16
>, IPv4_address_literal
> {};
372 struct dcolon
: two
<':'> {};
374 struct IPv6address
: sor
<seq
< rep
<6, h16
, colon
>, ls32
>,
375 seq
< dcolon
, rep
<5, h16
, colon
>, ls32
>,
376 seq
<opt
<h16
>, dcolon
, rep
<4, h16
, colon
>, ls32
>,
377 seq
<opt
<h16
, opt
< colon
, h16
>>, dcolon
, rep
<3, h16
, colon
>, ls32
>,
378 seq
<opt
<h16
, rep_opt
<2, colon
, h16
>>, dcolon
, rep
<2, h16
, colon
>, ls32
>,
379 seq
<opt
<h16
, rep_opt
<3, colon
, h16
>>, dcolon
, h16
, colon
, ls32
>,
380 seq
<opt
<h16
, rep_opt
<4, colon
, h16
>>, dcolon
, ls32
>,
381 seq
<opt
<h16
, rep_opt
<5, colon
, h16
>>, dcolon
, h16
>,
382 seq
<opt
<h16
, rep_opt
<6, colon
, h16
>>, dcolon
>> {};
384 struct IPv6_address_literal
: seq
<TAO_PEGTL_ISTRING("IPv6:"), IPv6address
> {};
386 struct dcontent
: tao::pegtl::ranges
<33, 90, 94, 126> {};
388 struct standardized_tag
: ldh_str
{};
390 struct general_address_literal
: seq
<standardized_tag
, colon
, plus
<dcontent
>> {};
392 // See rfc 5321 Section 4.1.3
393 struct address_literal
: seq
<one
<'['>,
394 sor
<IPv4_address_literal
,
395 IPv6_address_literal
,
396 general_address_literal
>,
400 struct qtextSMTP
: sor
<tao::pegtl::ranges
<32, 33, 35, 91, 93, 126>, chars::non_ascii
> {};
401 struct graphic
: range
<32, 126> {};
402 struct quoted_pairSMTP
: seq
<one
<'\\'>, graphic
> {};
403 struct qcontentSMTP
: sor
<qtextSMTP
, quoted_pairSMTP
> {};
405 // excluded from atext: "(),.@[]"
406 struct atext
: sor
<ALPHA
, DIGIT
,
417 chars::non_ascii
> {};
418 struct atom
: plus
<atext
> {};
419 struct dot_string
: list
<atom
, dot
> {};
420 struct quoted_string
: seq
<one
<'"'>, star
<qcontentSMTP
>, one
<'"'>> {};
421 struct local_part
: sor
<dot_string
, quoted_string
> {};
422 struct non_local_part
: sor
<domain
, address_literal
> {};
423 struct mailbox
: seq
<local_part
, one
<'@'>, non_local_part
> {};
425 struct at_domain
: seq
<one
<'@'>, domain
> {};
427 struct a_d_l
: list
<at_domain
, one
<','>> {};
429 struct path
: seq
<opt
<seq
<a_d_l
, colon
>>, mailbox
> {};
431 struct postmaster
: TAO_PEGTL_ISTRING("Postmaster") {};
433 struct path_or_postmaster
: seq
<sor
<path
, postmaster
>, eof
> {};
435 // textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
437 // Although not explicit in the grammar of RFC-6531, in practice UTF-8
438 // is used in the replies.
440 // struct textstring : plus<sor<one<9>, range<32, 126>>> {};
442 struct textstring
: plus
<sor
<one
<9>, range
<32, 126>, chars::non_ascii
>> {};
444 struct crap
: plus
<range
<32, 126>> {};
446 struct server_id
: sor
<domain
, address_literal
, crap
> {};
448 // Greeting = ( "220 " (Domain / address-literal) [ SP textstring ] CRLF )
450 // ( "220-" (Domain / address-literal) [ SP textstring ] CRLF
451 // *( "220-" [ textstring ] CRLF )
452 // "220" [ SP textstring ] CRLF )
455 : sor
<seq
<TAO_PEGTL_ISTRING("220 "), server_id
, opt
<textstring
>, CRLF
>,
456 seq
<TAO_PEGTL_ISTRING("220-"), server_id
, opt
<textstring
>, CRLF
,
457 star
<seq
<TAO_PEGTL_ISTRING("220-"), opt
<textstring
>, CRLF
>>,
458 seq
<TAO_PEGTL_ISTRING("220"), opt
<seq
<SP
, textstring
>>, CRLF
>>> {};
460 // Reply-code = %x32-35 %x30-35 %x30-39
463 : seq
<range
<0x32, 0x35>, range
<0x30, 0x35>, range
<0x30, 0x39>> {};
465 // Reply-line = *( Reply-code "-" [ textstring ] CRLF )
466 // Reply-code [ SP textstring ] CRLF
469 : seq
<star
<seq
<reply_code
, one
<'-'>, opt
<textstring
>, CRLF
>>,
470 seq
<reply_code
, opt
<seq
<SP
, textstring
>>, CRLF
>> {};
473 : sor
<greeting_ok
, reply_lines
> {};
475 // ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
476 // ; string of any characters other than CR or LF
478 struct ehlo_greet
: plus
<tao::pegtl::ranges
<0, 9, 11, 12, 14, 127>> {};
480 // ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
481 // ; additional syntax of ehlo-params depends on
484 // The '.' we also allow in ehlo-keyword since it has been seen in the
485 // wild at least at 263.net.
487 struct ehlo_keyword
: seq
<sor
<ALPHA
, DIGIT
>, star
<sor
<ALPHA
, DIGIT
, dash
, dot
, underscore
>>> {};
489 // ehlo-param = 1*(%d33-126)
490 // ; any CHAR excluding <SP> and all
491 // ; control characters (US-ASCII 0-31 and 127
494 struct ehlo_param
: plus
<range
<33, 126>> {};
496 // ehlo-line = ehlo-keyword *( SP ehlo-param )
498 // The AUTH= thing is so common with some servers (postfix) that I
499 // guess we have to accept it.
502 : seq
<ehlo_keyword
, star
<seq
<sor
<SP
,one
<'='>>, ehlo_param
>>> {};
504 // ehlo-ok-rsp = ( "250 " Domain [ SP ehlo-greet ] CRLF )
506 // ( "250-" Domain [ SP ehlo-greet ] CRLF
507 // *( "250-" ehlo-line CRLF )
508 // "250 " ehlo-line CRLF )
510 // The last line having the optional ehlo_line is not strictly correct.
511 // Was added to work with postfix/src/smtpstone/smtp-sink.c.
514 : sor
<seq
<TAO_PEGTL_ISTRING("250 "), server_id
, opt
<ehlo_greet
>, CRLF
>,
516 seq
<TAO_PEGTL_ISTRING("250-"), server_id
, opt
<ehlo_greet
>, CRLF
,
517 star
<seq
<TAO_PEGTL_ISTRING("250-"), ehlo_line
, CRLF
>>,
518 seq
<TAO_PEGTL_ISTRING("250 "), opt
<ehlo_line
>, CRLF
>>
522 : sor
<ehlo_ok_rsp
, reply_lines
> {};
525 : seq
<TAO_PEGTL_ISTRING("250 "), server_id
, opt
<ehlo_greet
>, CRLF
> {};
527 struct auth_login_username
528 : seq
<TAO_PEGTL_STRING("334 VXNlcm5hbWU6"), CRLF
> {};
530 struct auth_login_password
531 : seq
<TAO_PEGTL_STRING("334 UGFzc3dvcmQ6"), CRLF
> {};
535 template <typename Rule
>
536 struct inaction
: nothing
<Rule
> {};
538 template <typename Rule
>
539 struct action
: nothing
<Rule
> {};
542 struct action
<server_id
> {
543 template <typename Input
>
544 static void apply(Input
const& in
, Connection
& cnn
)
546 cnn
.server_id
= in
.string();
551 struct action
<local_part
> {
552 template <typename Input
>
553 static void apply(Input
const& in
, Mailbox
& mbx
)
555 mbx
.set_local(in
.string());
560 struct action
<non_local_part
> {
561 template <typename Input
>
562 static void apply(Input
const& in
, Mailbox
& mbx
)
564 mbx
.set_domain(in
.string());
569 struct action
<greeting_ok
> {
570 template <typename Input
>
571 static void apply(Input
const& in
, Connection
& cnn
)
573 cnn
.greeting_ok
= true;
574 imemstream stream
{begin(in
), size(in
)};
576 while (std::getline(stream
, line
)) {
577 LOG(INFO
) << " S: " << line
;
583 struct action
<ehlo_ok_rsp
> {
584 template <typename Input
>
585 static void apply(Input
const& in
, Connection
& cnn
)
588 imemstream stream
{begin(in
), size(in
)};
590 while (std::getline(stream
, line
)) {
591 LOG(INFO
) << " S: " << line
;
597 struct action
<ehlo_keyword
> {
598 template <typename Input
>
599 static void apply(Input
const& in
, Connection
& cnn
)
601 cnn
.ehlo_keyword
= in
.string();
602 boost::to_upper(cnn
.ehlo_keyword
);
607 struct action
<ehlo_param
> {
608 template <typename Input
>
609 static void apply(Input
const& in
, Connection
& cnn
)
611 cnn
.ehlo_param
.push_back(in
.string());
616 struct action
<ehlo_line
> {
617 template <typename Input
>
618 static void apply(Input
const& in
, Connection
& cnn
)
620 cnn
.ehlo_params
.emplace(std::move(cnn
.ehlo_keyword
),
621 std::move(cnn
.ehlo_param
));
626 struct action
<reply_lines
> {
627 template <typename Input
>
628 static void apply(Input
const& in
, Connection
& cnn
)
630 imemstream stream
{begin(in
), size(in
)};
632 while (std::getline(stream
, line
)) {
633 LOG(INFO
) << " S: " << line
;
639 struct action
<reply_code
> {
640 template <typename Input
>
641 static void apply(Input
const& in
, Connection
& cnn
)
643 cnn
.reply_code
= in
.string();
646 } // namespace RFC5321
650 int conn(DNS::Resolver
& res
, Domain
const& node
, uint16_t port
)
652 auto const use_4
{!FLAGS_6
};
653 auto const use_6
{!FLAGS_4
};
656 auto const fd
{socket(AF_INET6
, SOCK_STREAM
, 0)};
657 PCHECK(fd
>= 0) << "socket() failed";
659 if (!FLAGS_local_address
.empty()) {
660 auto loc
{sockaddr_in6
{}};
661 loc
.sin6_family
= AF_INET6
;
662 if (1 != inet_pton(AF_INET6
, FLAGS_local_address
.c_str(),
663 reinterpret_cast<void*>(&loc
.sin6_addr
))) {
664 LOG(FATAL
) << "can't interpret " << FLAGS_local_address
665 << " as IPv6 address";
667 PCHECK(0 == bind(fd
, reinterpret_cast<sockaddr
*>(&loc
), sizeof(loc
)));
670 auto addrs
{std::vector
<std::string
>{}};
672 if (node
.is_address_literal()) {
673 if (IP6::is_address(node
.ascii())) {
674 addrs
.push_back(node
.ascii());
676 if (IP6::is_address_literal(node
.ascii())) {
677 auto const addr
= IP6::as_address(node
.ascii());
678 addrs
.push_back(std::string(addr
.data(), addr
.length()));
682 addrs
= res
.get_strings(DNS::RR_type::AAAA
, node
.ascii());
684 for (auto const& addr
: addrs
) {
685 auto in6
{sockaddr_in6
{}};
686 in6
.sin6_family
= AF_INET6
;
687 in6
.sin6_port
= htons(port
);
688 CHECK_EQ(inet_pton(AF_INET6
, addr
.c_str(),
689 reinterpret_cast<void*>(&in6
.sin6_addr
)),
691 if (connect(fd
, reinterpret_cast<const sockaddr
*>(&in6
), sizeof(in6
))) {
692 PLOG(WARNING
) << "connect failed [" << addr
<< "]:" << port
;
696 LOG(INFO
) << fd
<< " connected to [" << addr
<< "]:" << port
;
703 auto fd
{socket(AF_INET
, SOCK_STREAM
, 0)};
704 PCHECK(fd
>= 0) << "socket() failed";
706 if (!FLAGS_local_address
.empty()) {
707 auto loc
{sockaddr_in
{}};
708 loc
.sin_family
= AF_INET
;
709 if (1 != inet_pton(AF_INET
, FLAGS_local_address
.c_str(),
710 reinterpret_cast<void*>(&loc
.sin_addr
))) {
711 LOG(FATAL
) << "can't interpret " << FLAGS_local_address
712 << " as IPv4 address";
714 LOG(INFO
) << "bind " << FLAGS_local_address
;
715 PCHECK(0 == bind(fd
, reinterpret_cast<sockaddr
*>(&loc
), sizeof(loc
)));
718 auto addrs
{std::vector
<std::string
>{}};
719 if (node
.is_address_literal()) {
720 if (IP4::is_address(node
.ascii())) {
721 addrs
.push_back(node
.ascii());
723 if (IP4::is_address_literal(node
.ascii())) {
724 auto const addr
= IP4::as_address(node
.ascii());
725 addrs
.push_back(std::string(addr
.data(), addr
.length()));
729 addrs
= res
.get_strings(DNS::RR_type::A
, node
.ascii());
731 for (auto const& addr
: addrs
) {
732 auto in4
{sockaddr_in
{}};
733 in4
.sin_family
= AF_INET
;
734 in4
.sin_port
= htons(port
);
735 CHECK_EQ(inet_pton(AF_INET
, addr
.c_str(),
736 reinterpret_cast<void*>(&in4
.sin_addr
)),
738 if (connect(fd
, reinterpret_cast<const sockaddr
*>(&in4
), sizeof(in4
))) {
739 PLOG(WARNING
) << "connect failed " << addr
<< ":" << port
;
743 LOG(INFO
) << " connected to " << addr
<< ":" << port
;
755 void add_hdr(std::string name
, std::string value
)
757 assert(!name
.starts_with("."s
)); // Would mess up "dot" stuffing.
758 hdrs_
.push_back(std::make_pair(name
, value
));
761 void foreach_hdr(std::function
<void(std::string
const& name
,
762 std::string
const& value
)> func
)
764 for (auto const& [name
, value
] : hdrs_
) {
770 std::vector
<std::pair
<std::string
, std::string
>> hdrs_
;
772 friend std::ostream
& operator<<(std::ostream
& os
, Eml
const& eml
)
774 for (auto const& [name
, value
] : eml
.hdrs_
) {
775 os
<< name
<< ": " << value
<< "\r\n";
777 // return os << "\r\n"; // end of headers
778 return os
/* << "\r\n" */; // end of headers
782 // // clang-format off
783 // char const* const signhdrs[] = {
799 // "Content-Transfer-Encoding",
805 enum class transfer_encoding
{
813 enum class data_type
{
814 ascii
, // 7bit, quoted-printable and base64
819 data_type
type(std::string_view d
)
822 auto in
{memory_input
<>{d
.data(), d
.size(), "data"}};
823 if (parse
<RFC5322::body_ascii
>(in
)) {
824 return data_type::ascii
;
828 auto in
{memory_input
<>{d
.data(), d
.size(), "data"}};
829 if (parse
<RFC5322::body_utf8
>(in
)) {
830 return data_type::utf8
;
834 return data_type::binary
;
839 content(char const* path
)
842 auto const body_sz
{fs::file_size(path_
)};
843 CHECK(body_sz
) << "no body";
845 type_
= ::type(*this);
848 char const* data() const { return file_
.data(); }
849 size_t size() const { return file_
.size(); }
850 data_type
type() const { return type_
; }
852 bool empty() const { return size() == 0; }
853 operator std::string_view() const { return std::string_view(data(), size()); }
858 boost::iostreams::mapped_file_source file_
;
861 template <typename Input
>
862 void fail(Input
& in
, RFC5321::Connection
& cnn
)
864 LOG(INFO
) << " C: QUIT";
865 cnn
.sock
.out() << "QUIT\r\n" << std::flush
;
866 // we might have a few error replies stacked up if we're pipelining
867 // CHECK((parse<RFC5321::reply_lines, RFC5321::action>(in, cnn)));
871 template <typename Input
>
872 void quit_on_fail(Input
& in
, RFC5321::Connection
& cnn
, std::string_view cmd
)
874 cnn
.sock
.out() << std::flush
;
875 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
876 if (cnn
.reply_code
.at(0) != '2') {
877 LOG(ERROR
) << cmd
<< " returned " << cnn
.reply_code
;
883 bool validate_name(const char* flagname
, std::string
const& value
)
885 if (value
.empty()) // empty name needs to validate, but
886 return true; // will not be used
887 memory_input
<> name_in(value
.c_str(), "name");
888 if (!parse
<RFC5322::display_name_only
, RFC5322::inaction
>(name_in
)) {
889 LOG(ERROR
) << "bad name syntax " << value
;
895 DEFINE_validator(from_name
, &validate_name
);
896 DEFINE_validator(to_name
, &validate_name
);
898 bool validate_address_RFC5322(const char* flagname
, std::string
const& value
)
900 if (value
.empty()) // empty name needs to validate, but
901 return true; // will not be used
902 memory_input
<> name_in(value
.c_str(), "address");
903 if (!parse
<RFC5322::addr_spec_only
, RFC5322::inaction
>(name_in
)) {
904 LOG(ERROR
) << "bad address syntax " << value
;
910 DEFINE_validator(from
, &validate_address_RFC5322
);
911 DEFINE_validator(to
, &validate_address_RFC5322
);
913 bool validate_address_RFC5321(const char* flagname
, std::string
const& value
)
915 if (value
.empty()) { // empty name needs to validate, but
916 return true; // will not be used
918 memory_input
<> name_in(value
.c_str(), "path");
919 if (!parse
<RFC5321::path_or_postmaster
, RFC5321::inaction
>(name_in
)) {
920 LOG(ERROR
) << "bad RFC-5321 address syntax " << value
;
926 DEFINE_validator(smtp_from
, &validate_address_RFC5321
);
927 DEFINE_validator(smtp_to
, &validate_address_RFC5321
);
928 DEFINE_validator(smtp_to2
, &validate_address_RFC5321
);
929 DEFINE_validator(smtp_to3
, &validate_address_RFC5321
);
933 CHECK(validate_name("selftest", ""s
));
934 CHECK(validate_name("selftest", "Elmer J Fudd"s
));
935 CHECK(validate_name("selftest", "\"Elmer J. Fudd\""s
));
936 CHECK(validate_name("selftest", "Elmer! J! Fudd!"s
));
938 CHECK(validate_address_RFC5321("selftest", "foo@digilicious.com"s
));
939 CHECK(validate_address_RFC5321("selftest", "\"foo\"@digilicious.com"s
));
940 CHECK(validate_address_RFC5321(
942 "\"very.(),:;<>[]\\\".VERY.\\\"very@\\\\ \\\"very\\\".unusual\"@digilicious.com"s
));
944 CHECK(validate_address_RFC5322("selftest", "foo@digilicious.com"s
));
945 CHECK(validate_address_RFC5322("selftest", "\"foo\"@digilicious.com"s
));
946 CHECK(validate_address_RFC5322(
948 "\"very.(),:;<>[]\\\".VERY.\\\"very@\\\\ \\\"very\\\".unusual\"@digilicious.com"s
));
950 auto const read_hook
{[]() {}};
952 const char* greet_list
[]{
953 "220-mtaig-aak03.mx.aol.com ESMTP Internet Inbound\r\n"
954 "220-AOL and its affiliated companies do not\r\n"
955 "220-authorize the use of its proprietary computers and computer\r\n"
956 "220-networks to accept, transmit, or distribute unsolicited bulk\r\n"
957 "220-e-mail sent from the internet.\r\n"
958 "220-Effective immediately:\r\n"
959 "220-AOL may no longer accept connections from IP addresses\r\n"
960 "220 which no do not have reverse-DNS (PTR records) assigned.\r\n",
962 "421 mtaig-maa02.mx.aol.com Service unavailable - try again later\r\n",
965 for (auto const& i
: greet_list
) {
966 auto cnn
{RFC5321::Connection(0, 1, read_hook
)};
967 auto in
{memory_input
<>{i
, i
}};
968 if (!parse
<RFC5321::greeting
, RFC5321::action
/*, tao::pegtl::tracer*/>(
970 LOG(FATAL
) << "Error parsing greeting \"" << i
<< "\"";
972 if (cnn
.greeting_ok
) {
973 LOG(WARNING
) << "greeting ok";
976 LOG(WARNING
) << "greeting was not in the affirmative";
980 const char* ehlo_rsp_list
[]{
981 "250-www77.totaalholding.nl Hello "
982 "ec2-18-205-224-193.compute-1.amazonaws.com [18.205.224.193]\r\n"
983 "250-SIZE 52428800\r\n"
986 "250-X_PIPE_CONNECT\r\n"
990 "250-HELLO, SAILOR!\r\n"
991 "250-NO-SOLICITING\r\n"
994 "250-digilicious.com at your service, localhost. [IPv6:::1]\r\n"
995 "250-SIZE 15728640\r\n"
998 "250-ENHANCEDSTATUSCODES\r\n"
1000 "250-BINARYMIME\r\n"
1005 "500 5.5.1 command unrecognized: \"EHLO digilicious.com\\r\\n\"\r\n",
1007 "250-263xmail at your service\r\n"
1010 "250-263.net\r\n" // the '.' is not RFC complaint
1011 "250-SIZE 104857600\r\n"
1013 "250-ENHANCEDSTATUSCODES\r\n"
1018 for (auto const& i
: ehlo_rsp_list
) {
1019 auto cnn
{RFC5321::Connection(0, 1, read_hook
)};
1020 auto in
{memory_input
<>{i
, i
}};
1021 if (!parse
<RFC5321::ehlo_rsp
, RFC5321::action
/*, tao::pegtl::tracer*/>(
1023 LOG(FATAL
) << "Error parsing ehlo response \"" << i
<< "\"";
1026 LOG(WARNING
) << "ehlo ok";
1029 LOG(WARNING
) << "ehlo response was not in the affirmative";
1036 if (FLAGS_client_id
.empty()) {
1037 FLAGS_client_id
= [] {
1038 auto const id_from_env
{getenv("GHSMTP_CLIENT_ID")};
1040 return std::string
{id_from_env
};
1042 auto const hostname
{osutil::get_hostname()};
1043 if (hostname
.find('.') != std::string::npos
)
1046 LOG(FATAL
) << "hostname not a FQDN, set GHSMTP_CLIENT_ID maybe?";
1050 if (FLAGS_sender
.empty()) {
1051 FLAGS_sender
= FLAGS_client_id
;
1054 auto const sender
{Domain
{FLAGS_sender
}};
1056 if (FLAGS_from
.empty()) {
1057 FLAGS_from
= "test-it@"s
+ sender
.utf8();
1060 if (FLAGS_to
.empty()) {
1061 FLAGS_to
= "test-it@"s
+ sender
.utf8();
1067 bool is_localhost(DNS::RR
const& rr
)
1069 if (std::holds_alternative
<DNS::RR_MX
>(rr
)) {
1070 if (iequal(std::get
<DNS::RR_MX
>(rr
).exchange(), "localhost"))
1076 bool starts_with(std::string_view str
, std::string_view prefix
)
1078 if (str
.size() >= prefix
.size())
1079 if (str
.compare(0, prefix
.size(), prefix
) == 0)
1084 bool sts_rec(std::string
const& sts_rec
)
1086 return starts_with(sts_rec
, "v=STSv1");
1090 get_receivers(DNS::Resolver
& res
, Mailbox
const& to_mbx
, bool& enforce_dane
)
1092 auto receivers
{std::vector
<Domain
>{}};
1094 // User provided explicit host to receive mail.
1095 if (!FLAGS_mx_host
.empty()) {
1096 receivers
.emplace_back(FLAGS_mx_host
);
1100 // Non-local part is an address literal.
1101 if (to_mbx
.domain().is_address_literal()) {
1102 receivers
.emplace_back(to_mbx
.domain());
1106 // RFC 5321 section 5.1 "Locating the Target Host"
1108 // “The lookup first attempts to locate an MX record associated with
1109 // the name. If a CNAME record is found, the resulting name is
1110 // processed as if it were the initial name.”
1112 // Our (full) resolver will traverse any CNAMEs for us and return
1113 // the CNAME and MX records all together.
1115 auto const& domain
= to_mbx
.domain().ascii();
1117 auto q_sts
{DNS::Query
{res
, DNS::RR_type::TXT
, "_mta-sts."s
+ domain
}};
1118 if (q_sts
.has_record()) {
1119 auto sts_records
= q_sts
.get_strings();
1120 sts_records
.erase(std::remove_if(begin(sts_records
), end(sts_records
),
1121 std::not_fn(sts_rec
)),
1123 if (size(sts_records
) == 1) {
1124 LOG(INFO
) << "### This domain implements MTA-STS ###";
1128 LOG(INFO
) << "MTA-STS record not found for domain " << domain
;
1131 auto q
{DNS::Query
{res
, DNS::RR_type::MX
, domain
}};
1132 if (q
.has_record()) {
1133 if (q
.authentic_data()) {
1134 LOG(INFO
) << "### MX records authentic for domain " << domain
<< " ###";
1137 LOG(INFO
) << "MX records can't be authenticated for domain " << domain
;
1138 enforce_dane
= false;
1142 LOG(INFO
) << "no MX records found for domain " << domain
;
1144 auto mxs
{q
.get_records()};
1146 mxs
.erase(std::remove_if(begin(mxs
), end(mxs
), is_localhost
), end(mxs
));
1148 auto const nmx
= std::count_if(begin(mxs
), end(mxs
), [](auto const& rr
) {
1149 return std::holds_alternative
<DNS::RR_MX
>(rr
);
1153 for (auto const& mx
: mxs
) {
1154 if (std::holds_alternative
<DNS::RR_MX
>(mx
)) {
1155 // RFC 7505 null MX record
1156 if ((std::get
<DNS::RR_MX
>(mx
).preference() == 0) &&
1157 (std::get
<DNS::RR_MX
>(mx
).exchange().empty() ||
1158 (std::get
<DNS::RR_MX
>(mx
).exchange() == "."))) {
1159 LOG(INFO
) << "domain " << domain
<< " does not accept mail";
1168 receivers
.emplace_back(domain
);
1172 // […] then the sender-SMTP MUST randomize them to spread the load
1173 // across multiple mail exchangers for a specific organization.
1174 std::shuffle(begin(mxs
), end(mxs
), std::random_device());
1175 std::sort(begin(mxs
), end(mxs
), [](auto const& a
, auto const& b
) {
1176 if (std::holds_alternative
<DNS::RR_MX
>(a
) &&
1177 std::holds_alternative
<DNS::RR_MX
>(b
)) {
1178 return std::get
<DNS::RR_MX
>(a
).preference() <
1179 std::get
<DNS::RR_MX
>(b
).preference();
1185 LOG(INFO
) << "MXs for " << domain
<< " are:";
1187 for (auto const& mx
: mxs
) {
1188 if (std::holds_alternative
<DNS::RR_MX
>(mx
)) {
1189 receivers
.emplace_back(std::get
<DNS::RR_MX
>(mx
).exchange());
1190 LOG(INFO
) << std::setfill(' ') << std::setw(3)
1191 << std::get
<DNS::RR_MX
>(mx
).preference() << " "
1192 << std::get
<DNS::RR_MX
>(mx
).exchange();
1199 auto parse_mailboxes()
1201 auto from_mbx
{Mailbox
{}};
1202 auto from_in
{memory_input
<>{FLAGS_from
, "from"}};
1203 if (!parse
<RFC5322::addr_spec_only
, RFC5322::action
>(from_in
, from_mbx
)) {
1204 LOG(FATAL
) << "bad From: address syntax <" << FLAGS_from
<< ">";
1206 LOG(INFO
) << " from_mbx == " << from_mbx
;
1208 auto local_from
{memory_input
<>{from_mbx
.local_part(), "from.local"}};
1209 FLAGS_force_smtputf8
|= !parse
<chars::ascii_only
>(local_from
);
1211 auto to_mbx
{Mailbox
{}};
1212 auto to_in
{memory_input
<>{FLAGS_to
, "to"}};
1213 if (!parse
<RFC5322::addr_spec_only
, RFC5322::action
>(to_in
, to_mbx
)) {
1214 LOG(FATAL
) << "bad To: address syntax <" << FLAGS_to
<< ">";
1216 LOG(INFO
) << " to_mbx == " << to_mbx
;
1218 auto local_to
{memory_input
<>{to_mbx
.local_part(), "to.local"}};
1219 FLAGS_force_smtputf8
|= !parse
<chars::ascii_only
>(local_to
);
1221 auto smtp_from_mbx
{Mailbox
{}};
1222 if (!FLAGS_smtp_from
.empty()) {
1223 auto smtp_from_in
{memory_input
<>{FLAGS_smtp_from
, "SMTP.from"}};
1224 if (!parse
<RFC5321::path_or_postmaster
, RFC5321::action
>(smtp_from_in
,
1226 LOG(FATAL
) << "bad MAIL FROM: address syntax <" << FLAGS_smtp_from
<< ">";
1228 LOG(INFO
) << " smtp_from_mbx == " << smtp_from_mbx
;
1229 auto local_smtp_from
{
1230 memory_input
<>{smtp_from_mbx
.local_part(), "SMTP.from.local"}};
1231 FLAGS_force_smtputf8
|= !parse
<chars::ascii_only
>(local_smtp_from
);
1234 smtp_from_mbx
= from_mbx
;
1237 auto smtp_to_mbx
{Mailbox
{}};
1238 if (!FLAGS_smtp_to
.empty()) {
1239 auto smtp_to_in
{memory_input
<>{FLAGS_smtp_to
, "SMTP.to"}};
1240 if (!parse
<RFC5321::path_or_postmaster
, RFC5321::action
>(smtp_to_in
,
1242 LOG(FATAL
) << "bad RCPT TO: address syntax <" << FLAGS_smtp_to
<< ">";
1244 LOG(INFO
) << " smtp_to_mbx == " << smtp_to_mbx
;
1247 memory_input
<>{smtp_to_mbx
.local_part(), "SMTP.to.local"}};
1248 FLAGS_force_smtputf8
|= !parse
<chars::ascii_only
>(local_smtp_to
);
1251 smtp_to_mbx
= to_mbx
;
1254 auto smtp_to2_mbx
{Mailbox
{}};
1255 if (!FLAGS_smtp_to2
.empty()) {
1256 auto smtp_to2_in
{memory_input
<>{FLAGS_smtp_to2
, "SMTP.to"}};
1257 if (!parse
<RFC5321::path_or_postmaster
, RFC5321::action
>(smtp_to2_in
,
1259 LOG(FATAL
) << "bad RCPT TO: address syntax <" << FLAGS_smtp_to2
<< ">";
1261 LOG(INFO
) << " smtp_to2_mbx == " << smtp_to2_mbx
;
1263 auto local_smtp_to2
{
1264 memory_input
<>{smtp_to2_mbx
.local_part(), "SMTP.to.local"}};
1265 FLAGS_force_smtputf8
|= !parse
<chars::ascii_only
>(local_smtp_to2
);
1268 auto smtp_to3_mbx
{Mailbox
{}};
1269 if (!FLAGS_smtp_to3
.empty()) {
1270 auto smtp_to3_in
{memory_input
<>{FLAGS_smtp_to3
, "SMTP.to"}};
1271 if (!parse
<RFC5321::path_or_postmaster
, RFC5321::action
>(smtp_to3_in
,
1273 LOG(FATAL
) << "bad RCPT TO: address syntax <" << FLAGS_smtp_to3
<< ">";
1275 LOG(INFO
) << " smtp_to3_mbx == " << smtp_to3_mbx
;
1277 auto local_smtp_to3
{
1278 memory_input
<>{smtp_to3_mbx
.local_part(), "SMTP.to.local"}};
1279 FLAGS_force_smtputf8
|= !parse
<chars::ascii_only
>(local_smtp_to3
);
1282 return std::tuple(from_mbx
, to_mbx
, smtp_from_mbx
, smtp_to_mbx
, smtp_to2_mbx
,
1286 auto create_eml(Domain
const& sender
,
1287 std::string
const& from
,
1288 std::string
const& to
,
1289 std::vector
<content
> const& bodies
,
1293 auto const date
{Now
{}};
1294 auto const pill
{Pill
{}};
1296 auto mid_str
= fmt::format("<{}.{}@{}>", date
.sec(), pill
.as_string_view(),
1298 eml
.add_hdr("Message-ID", mid_str
.c_str());
1299 eml
.add_hdr("Date", date
.c_str());
1301 if (!FLAGS_from_name
.empty())
1302 eml
.add_hdr("From", fmt::format("{} <{}>", FLAGS_from_name
, from
));
1304 eml
.add_hdr("From", from
);
1306 eml
.add_hdr("Subject", FLAGS_subject
);
1308 if (!FLAGS_to_name
.empty())
1309 eml
.add_hdr("To", fmt::format("{} <{}>", FLAGS_to_name
, to
));
1311 eml
.add_hdr("To", to
);
1313 if (!FLAGS_keywords
.empty())
1314 eml
.add_hdr("Keywords", FLAGS_keywords
);
1316 if (!FLAGS_references
.empty())
1317 eml
.add_hdr("References", FLAGS_references
);
1319 if (!FLAGS_in_reply_to
.empty())
1320 eml
.add_hdr("In-Reply-To", FLAGS_in_reply_to
);
1322 if (!FLAGS_reply_to
.empty())
1323 eml
.add_hdr("Reply-To", FLAGS_reply_to
);
1325 if (!FLAGS_reply_2
.empty())
1326 eml
.add_hdr("Reply-To", FLAGS_reply_2
);
1328 eml
.add_hdr("MIME-Version", "1.0");
1329 eml
.add_hdr("Content-Language", "en-US");
1331 auto magic
{Magic
{}}; // to ID buffer contents
1333 if (!FLAGS_content_type
.empty()) {
1334 eml
.add_hdr("Content-Type", FLAGS_content_type
);
1337 eml
.add_hdr("Content-Type", magic
.buffer(bodies
[0]));
1340 if (!FLAGS_content_transfer_encoding
.empty()) {
1341 eml
.add_hdr("Content-Transfer-Encoding", FLAGS_content_transfer_encoding
);
1347 void sign_eml(Eml
& eml
,
1348 std::string
const& from_dom
,
1349 std::vector
<content
> const& bodies
)
1351 auto const body_type
= (bodies
[0].type() == data_type::binary
)
1352 ? OpenDKIM::sign::body_type::binary
1353 : OpenDKIM::sign::body_type::text
;
1355 auto const key_file
= FLAGS_dkim_key_file
.empty()
1356 ? (FLAGS_selector
+ ".private")
1357 : FLAGS_dkim_key_file
;
1358 std::ifstream
keyfs(key_file
.c_str());
1359 CHECK(keyfs
.good()) << "can't access " << key_file
;
1360 std::string
key(std::istreambuf_iterator
<char>{keyfs
}, {});
1361 OpenDKIM::sign
dks(key
.c_str(), FLAGS_selector
.c_str(), from_dom
.c_str(),
1363 eml
.foreach_hdr([&dks
](std::string
const& name
, std::string
const& value
) {
1364 auto const header
= name
+ ": "s
+ value
;
1365 dks
.header(header
.c_str());
1368 for (auto const& body
: bodies
) {
1372 eml
.add_hdr("DKIM-Signature"s
, dks
.getsighdr());
1375 template <typename Input
>
1376 void do_auth(Input
& in
, RFC5321::Connection
& cnn
)
1378 if (FLAGS_username
.empty() && FLAGS_password
.empty())
1381 auto const auth
= cnn
.ehlo_params
.find("AUTH");
1382 if (auth
== end(cnn
.ehlo_params
)) {
1383 LOG(ERROR
) << "server doesn't support AUTH";
1387 // Perfer PLAIN mechanism.
1388 if (std::find(begin(auth
->second
), end(auth
->second
), "PLAIN") !=
1389 end(auth
->second
)) {
1390 LOG(INFO
) << "C: AUTH PLAIN";
1391 auto const tok
= fmt::format("\0{}\0{}", FLAGS_username
, FLAGS_password
);
1392 cnn
.sock
.out() << "AUTH PLAIN " << Base64::enc(tok
) << "\r\n" << std::flush
;
1393 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1394 if (cnn
.reply_code
!= "235") {
1395 LOG(ERROR
) << "AUTH PLAIN returned " << cnn
.reply_code
;
1399 // The LOGIN SASL mechanism is obsolete.
1400 else if (std::find(begin(auth
->second
), end(auth
->second
), "LOGIN") !=
1401 end(auth
->second
)) {
1402 LOG(INFO
) << "C: AUTH LOGIN";
1403 cnn
.sock
.out() << "AUTH LOGIN\r\n" << std::flush
;
1404 CHECK((parse
<RFC5321::auth_login_username
>(in
)));
1405 cnn
.sock
.out() << Base64::enc(FLAGS_username
) << "\r\n" << std::flush
;
1406 CHECK((parse
<RFC5321::auth_login_password
>(in
)));
1407 cnn
.sock
.out() << Base64::enc(FLAGS_password
) << "\r\n" << std::flush
;
1408 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1409 if (cnn
.reply_code
!= "235") {
1410 LOG(ERROR
) << "AUTH LOGIN returned " << cnn
.reply_code
;
1415 LOG(ERROR
) << "server doesn't support AUTH methods PLAIN or LOGIN";
1420 // Do various bad things during the DATA transfer.
1422 template <typename Input
>
1423 void bad_daddy(Input
& in
, RFC5321::Connection
& cnn
)
1425 LOG(INFO
) << "C: DATA";
1426 cnn
.sock
.out() << "DATA\r\n";
1427 cnn
.sock
.out() << std::flush
;
1429 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1430 if (cnn
.reply_code
!= "354") {
1431 LOG(ERROR
) << "DATA returned " << cnn
.reply_code
;
1435 // cnn.sock.out() << "\r\nThis ->\n<- is a bare LF!\r\n";
1437 cnn
.sock
.out() << "\n.\n\r\n";
1439 if (FLAGS_to_the_neck
) {
1441 cnn
.sock
.out() << "####################"
1442 "####################"
1443 "####################"
1448 if (FLAGS_long_line
) {
1449 for (auto i
{0}; i
< 10000; ++i
) {
1450 cnn
.sock
.out() << 'X';
1452 cnn
.sock
.out() << "\r\n" << std::flush
;
1455 while (FLAGS_slow_strangle
) {
1456 for (auto i
{0}; i
< 500; ++i
) {
1457 cnn
.sock
.out() << 'X' << std::flush
;
1460 cnn
.sock
.out() << "\r\n";
1464 cnn
.sock
.out() << ".\r\n" << std::flush
;
1465 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1467 LOG(INFO
) << "reply_code == " << cnn
.reply_code
;
1468 CHECK_EQ(cnn
.reply_code
.at(0), '2');
1470 LOG(INFO
) << "C: QUIT";
1471 cnn
.sock
.out() << "QUIT\r\n" << std::flush
;
1472 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1475 bool snd(fs::path config_path
,
1478 Domain
const& sender
,
1479 Domain
const& receiver
,
1480 DNS::RR_collection
const& tlsa_rrs
,
1482 Mailbox
const& from_mbx
,
1483 Mailbox
const& to_mbx
,
1484 Mailbox
const& smtp_from_mbx
,
1485 Mailbox
const& smtp_to_mbx
,
1486 Mailbox
const& smtp_to2_mbx
,
1487 Mailbox
const& smtp_to3_mbx
,
1488 std::vector
<content
> const& bodies
)
1490 auto constexpr read_hook
{[]() {}};
1492 auto cnn
{RFC5321::Connection(fd_in
, fd_out
, read_hook
)};
1495 istream_input
<eol::crlf
, 1>{cnn
.sock
.in(), FLAGS_bfr_size
, "session"}};
1496 if (!parse
<RFC5321::greeting
, RFC5321::action
>(in
, cnn
)) {
1497 LOG(WARNING
) << "can't parse greeting";
1499 LOG(WARNING
) << " us: " << cnn
.sock
.us_c_str();
1500 LOG(WARNING
) << "them: " << cnn
.sock
.them_c_str();
1502 cnn
.sock
.log_stats();
1506 if (!cnn
.greeting_ok
) {
1507 LOG(WARNING
) << "greeting was not in the affirmative, skipping";
1513 if (FLAGS_use_esmtp
) {
1514 LOG(INFO
) << "C: EHLO " << FLAGS_client_id
;
1516 if (FLAGS_slow_strangle
) {
1517 auto ehlo_str
= fmt::format("EHLO {}\r\n", FLAGS_client_id
);
1518 for (auto ch
: ehlo_str
) {
1519 cnn
.sock
.out() << ch
<< std::flush
;
1524 cnn
.sock
.out() << "EHLO " << FLAGS_client_id
<< "\r\n" << std::flush
;
1527 CHECK((parse
<RFC5321::ehlo_rsp
, RFC5321::action
>(in
, cnn
)));
1530 if (FLAGS_force_smtputf8
) {
1531 LOG(WARNING
) << "ehlo response was not in the affirmative, skipping";
1535 LOG(WARNING
) << "ehlo response was not in the affirmative, trying HELO";
1536 FLAGS_use_esmtp
= false;
1540 if (!FLAGS_use_esmtp
) {
1541 LOG(INFO
) << "C: HELO " << sender
.ascii();
1542 cnn
.sock
.out() << "HELO " << sender
.ascii() << "\r\n" << std::flush
;
1543 if (!parse
<RFC5321::helo_ok_rsp
, RFC5321::action
>(in
, cnn
)) {
1544 LOG(WARNING
) << "HELO didn't work, skipping";
1551 auto bad_dad
= FLAGS_bare_lf
|| FLAGS_slow_strangle
|| FLAGS_to_the_neck
;
1554 FLAGS_use_chunking
= false;
1555 FLAGS_use_size
= false;
1558 auto const ext_8bitmime
{FLAGS_use_8bitmime
&& cnn
.has_extension("8BITMIME")};
1560 auto const ext_chunking
{FLAGS_use_chunking
&& cnn
.has_extension("CHUNKING")};
1562 auto const ext_binarymime
{FLAGS_use_binarymime
&& ext_chunking
&&
1563 cnn
.has_extension("BINARYMIME")};
1565 auto const ext_deliverby
{FLAGS_use_deliverby
&&
1566 cnn
.has_extension("DELIVERBY")};
1568 auto const ext_pipelining
{FLAGS_use_pipelining
&&
1569 cnn
.has_extension("PIPELINING")};
1571 auto const ext_prdr
{FLAGS_use_prdr
&& cnn
.has_extension("PRDR")};
1573 auto const ext_size
{FLAGS_use_size
&& cnn
.has_extension("SIZE")};
1575 auto const ext_smtputf8
{FLAGS_use_smtputf8
&& cnn
.has_extension("SMTPUTF8")};
1577 auto const ext_starttls
{FLAGS_use_tls
&& cnn
.has_extension("STARTTLS")};
1579 if (FLAGS_force_smtputf8
&& !ext_smtputf8
) {
1580 LOG(WARNING
) << "no SMTPUTF8, skipping";
1585 LOG(INFO
) << "C: STARTTLS";
1586 cnn
.sock
.out() << "STARTTLS\r\n" << std::flush
;
1587 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1589 LOG(INFO
) << "cnn.sock.starttls_client(\"" << receiver
.ascii() << "\");";
1590 cnn
.sock
.starttls_client(config_path
, sender
.ascii().c_str(),
1591 receiver
.ascii().c_str(), tlsa_rrs
, enforce_dane
);
1593 LOG(INFO
) << "TLS: " << cnn
.sock
.tls_info();
1595 LOG(INFO
) << "C: EHLO " << FLAGS_client_id
;
1596 cnn
.sock
.out() << "EHLO " << FLAGS_client_id
<< "\r\n" << std::flush
;
1597 CHECK((parse
<RFC5321::ehlo_rsp
, RFC5321::action
>(in
, cnn
)));
1599 else if (FLAGS_require_tls
) {
1600 LOG(ERROR
) << "No TLS extension, won't send mail in plain text without "
1601 "--require_tls=false.";
1602 LOG(INFO
) << "C: QUIT";
1603 cnn
.sock
.out() << "QUIT\r\n" << std::flush
;
1604 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1609 LOG(INFO
) << "C: NOOP";
1610 cnn
.sock
.out() << "NOOP\r\n" << std::flush
;
1613 if (receiver
!= cnn
.server_id
) {
1614 LOG(INFO
) << "server identifies as " << cnn
.server_id
;
1617 if (FLAGS_force_smtputf8
&& !ext_smtputf8
) {
1618 LOG(WARNING
) << "does not support SMTPUTF8";
1622 if (ext_smtputf8
&& !ext_8bitmime
) {
1624 << "SMTPUTF8 requires 8BITMIME, see RFC-6531 section 3.1 item 8.";
1625 LOG(INFO
) << "C: QUIT";
1626 cnn
.sock
.out() << "QUIT\r\n" << std::flush
;
1627 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1631 auto max_msg_size
{0u};
1633 if (!cnn
.ehlo_params
["SIZE"].empty()) {
1635 max_msg_size
= strtoul(cnn
.ehlo_params
["SIZE"][0].c_str(), &ep
, 10);
1636 if (ep
&& (*ep
!= '\0')) {
1637 LOG(WARNING
) << "garbage in SIZE argument: "
1638 << cnn
.ehlo_params
["SIZE"][0];
1643 auto deliver_by_min
{0u};
1644 if (ext_deliverby
) {
1645 if (!cnn
.ehlo_params
["DELIVERBY"].empty()) {
1648 strtoul(cnn
.ehlo_params
["DELIVERBY"][0].c_str(), &ep
, 10);
1649 if (ep
&& (*ep
!= '\0')) {
1650 LOG(WARNING
) << "garbage in DELIVERBY argument: "
1651 << cnn
.ehlo_params
["DELIVERBY"][0];
1655 if (deliver_by_min
) {
1656 LOG(INFO
) << "DELIVERBY " << deliver_by_min
;
1662 auto enc
= FLAGS_force_smtputf8
? Mailbox::domain_encoding::utf8
1663 : Mailbox::domain_encoding::ascii
;
1666 from_mbx
.empty() ? smtp_from_mbx
.as_string(enc
) : from_mbx
.as_string(enc
);
1668 to_mbx
.empty() ? smtp_to_mbx
.as_string(enc
) : to_mbx
.as_string(enc
);
1670 auto eml
{create_eml(sender
, from
, to
, bodies
, ext_smtputf8
)};
1672 if (FLAGS_use_dkim
) {
1673 auto const dom
= Mailbox(from
).domain().ascii();
1674 sign_eml(eml
, dom
.c_str(), bodies
);
1676 else if (FLAGS_bogus_dkim
) {
1677 eml
.add_hdr("DKIM-Signature", "");
1680 // Get the header as one big string
1681 std::ostringstream hdr_stream
;
1684 hdr_stream
<< "\r\n";
1685 auto hdr_str
= hdr_stream
.view();
1687 // In the case of DATA style transfer, this total_size number is an
1688 // *estimate* only, as line endings may be translated or added
1689 // during transfer. In the BDAT case, this number must be exact.
1691 auto const total_size
= rng::accumulate(
1692 bodies
, hdr_str
.size(),
1693 [](size_t s
, const content
& body
) { return s
+ body
.size(); });
1695 // auto total_size = hdr_str.size();
1696 // for (auto const& body : bodies)
1697 // total_size += body.size();
1699 if (ext_size
&& max_msg_size
&& (total_size
> max_msg_size
)) {
1700 LOG(ERROR
) << "message size " << total_size
<< " exceeds size limit of "
1702 LOG(INFO
) << "C: QUIT";
1703 cnn
.sock
.out() << "QUIT\r\n" << std::flush
;
1704 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1708 std::ostringstream param_stream
;
1709 if (FLAGS_huge_size
&& ext_size
) {
1710 // Claim some huge size.
1711 param_stream
<< " SIZE=" << std::numeric_limits
<std::streamsize
>::max();
1713 else if (ext_size
) {
1714 param_stream
<< " SIZE=" << total_size
;
1717 if (ext_binarymime
) {
1718 param_stream
<< " BODY=BINARYMIME";
1720 else if (ext_8bitmime
) {
1721 param_stream
<< " BODY=8BITMIME";
1724 if (ext_prdr
&& (!smtp_to2_mbx
.empty() || !smtp_to3_mbx
.empty())) {
1725 param_stream
<< " PRDR";
1728 if (ext_deliverby
) {
1729 param_stream
<< " BY=1200;NT";
1733 param_stream
<< " SMTPUTF8";
1736 if (FLAGS_badpipline
) {
1737 LOG(INFO
) << "C: NOOP NOOP";
1738 cnn
.sock
.out() << "NOOP\r\nNOOP\r\n" << std::flush
;
1741 bool rcpt_to_ok
= false;
1742 bool rcpt_to2_ok
= false;
1743 bool rcpt_to3_ok
= false;
1745 auto param_str
= param_stream
.str();
1747 for (auto count
= 0UL; count
< FLAGS_reps
; ++count
) {
1749 LOG(INFO
) << "C: MAIL FROM:<" << smtp_from_mbx
.as_string(enc
) << '>'
1751 cnn
.sock
.out() << "MAIL FROM:<" << smtp_from_mbx
.as_string(enc
) << '>'
1752 << param_str
<< "\r\n";
1753 if (!ext_pipelining
) {
1754 quit_on_fail(in
, cnn
, "MAIL FROM");
1757 LOG(INFO
) << "C: RCPT TO:<" << smtp_to_mbx
.as_string(enc
) << ">";
1758 cnn
.sock
.out() << "RCPT TO:<" << smtp_to_mbx
.as_string(enc
) << ">\r\n";
1759 if (!ext_pipelining
) {
1761 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1762 rcpt_to_ok
= cnn
.reply_code
.at(0) == '2';
1765 if (!smtp_to2_mbx
.empty()) {
1766 LOG(INFO
) << "C: RCPT TO:<" << smtp_to2_mbx
.as_string(enc
) << ">";
1767 cnn
.sock
.out() << "RCPT TO:<" << smtp_to2_mbx
.as_string(enc
) << ">\r\n";
1768 if (!ext_pipelining
) {
1770 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1771 rcpt_to2_ok
= cnn
.reply_code
.at(0) == '2';
1775 if (!smtp_to3_mbx
.empty()) {
1776 LOG(INFO
) << "C: RCPT TO:<" << smtp_to3_mbx
.as_string(enc
) << ">";
1777 cnn
.sock
.out() << "RCPT TO:<" << smtp_to3_mbx
.as_string(enc
) << ">\r\n";
1778 if (!ext_pipelining
) {
1780 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1781 rcpt_to3_ok
= cnn
.reply_code
.at(0) == '2';
1786 if (ext_pipelining
) {
1787 quit_on_fail(in
, cnn
, "MAIL FROM");
1789 quit_on_fail(in
, cnn
, "RCPT TO");
1791 quit_on_fail(in
, cnn
, "RCPT TO");
1793 quit_on_fail(in
, cnn
, "RCPT TO");
1795 LOG(INFO
) << "C: QUIT";
1796 cnn
.sock
.out() << "QUIT\r\n" << std::flush
;
1797 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1798 LOG(INFO
) << "no-sending";
1803 if (ext_pipelining
) {
1804 cnn
.sock
.out() << std::flush
;
1805 quit_on_fail(in
, cnn
, "MAIL FROM");
1806 quit_on_fail(in
, cnn
, "RCPT TO");
1812 auto msg
= std::make_unique
<MessageStore
>();
1814 // if service is smtp (i.e. sending real mail, not smtp-test)
1817 msg
->open(sender
.ascii(), total_size
* 2, ".Sent");
1818 msg
->write(hdr_str
.data(), hdr_str
.size());
1819 for (auto const& body
: bodies
) {
1820 msg
->write(body
.data(), body
.size());
1824 catch (std::system_error
const& e
) {
1829 LOG(FATAL
) << "no space";
1835 LOG(ERROR
) << "errno==" << errno
<< ": " << strerror(errno
);
1836 LOG(FATAL
) << e
.what();
1839 catch (std::exception
const& e
) {
1842 LOG(FATAL
) << e
.what();
1847 if (ext_pipelining
) {
1848 quit_on_fail(in
, cnn
, "MAIL FROM");
1851 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1852 rcpt_to_ok
= cnn
.reply_code
.at(0) == '2';
1854 if (!smtp_to2_mbx
.empty()) {
1856 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1857 rcpt_to2_ok
= cnn
.reply_code
.at(0) == '2';
1860 if (!smtp_to3_mbx
.empty()) {
1862 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1863 rcpt_to3_ok
= cnn
.reply_code
.at(0) == '2';
1866 // If all RCPT TOs failed, we give up.
1867 if (!(rcpt_to_ok
|| rcpt_to2_ok
|| rcpt_to3_ok
)) {
1872 std::ostringstream bdat_stream
;
1873 bdat_stream
<< "BDAT " << total_size
<< " LAST";
1874 LOG(INFO
) << "C: " << bdat_stream
.str();
1876 cnn
.sock
.out() << bdat_stream
.str() << "\r\n";
1877 cnn
.sock
.out().write(hdr_str
.data(), hdr_str
.size());
1878 CHECK(cnn
.sock
.out().good());
1880 for (auto const& body
: bodies
) {
1881 cnn
.sock
.out().write(body
.data(), body
.size());
1882 CHECK(cnn
.sock
.out().good());
1885 // Done sending data
1886 if (FLAGS_pipeline_quit
) {
1887 LOG(INFO
) << "C: QUIT";
1888 cnn
.sock
.out() << "QUIT\r\n" << std::flush
;
1891 cnn
.sock
.out() << std::flush
;
1896 LOG(INFO
) << "C: DATA";
1897 cnn
.sock
.out() << "DATA\r\n";
1899 // Now check returns, after DATA
1900 if (ext_pipelining
) {
1901 quit_on_fail(in
, cnn
, "MAIL FROM");
1904 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1905 rcpt_to_ok
= cnn
.reply_code
.at(0) == '2';
1907 if (!smtp_to2_mbx
.empty()) {
1909 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1910 rcpt_to2_ok
= cnn
.reply_code
.at(0) == '2';
1913 if (!smtp_to3_mbx
.empty()) {
1915 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1916 rcpt_to3_ok
= cnn
.reply_code
.at(0) == '2';
1919 // If all RCPT TOs failed, we give up.
1920 if (!(rcpt_to_ok
|| rcpt_to2_ok
|| rcpt_to3_ok
)) {
1925 cnn
.sock
.out() << std::flush
;
1928 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1929 if (cnn
.reply_code
!= "354") {
1930 LOG(ERROR
) << "DATA/BDAT #1 returned " << cnn
.reply_code
;
1934 cnn
.sock
.out() << eml
;
1936 cnn
.sock
.out() << "\r\n";
1938 for (auto const& body
: bodies
) {
1939 auto isbody
{imemstream
{body
.data(), body
.size()}};
1940 auto line
{std::string
{}};
1941 for (auto lineno
= 0; std::getline(isbody
, line
); ++lineno
) {
1942 if (!cnn
.sock
.out().good()) {
1943 cnn
.sock
.log_stats();
1944 LOG(FATAL
) << "output no good at line " << lineno
;
1947 // This adds a final newline at the end of the file, if no
1948 // line ending was present.
1949 cnn
.sock
.out() << line
<< '\n';
1953 if (line
.length() && (line
.at(0) == '.')) {
1954 cnn
.sock
.out() << '.';
1957 // This code converts single LF line endings into CRLF.
1958 // This code does nothing to fix single CR characters not
1959 // part of a CRLF pair.
1961 // This loop adds a CRLF and the end of the transmission if
1962 // the file doesn't already end with one. This is a
1963 // requirement of the SMTP DATA protocol.
1965 cnn
.sock
.out() << line
;
1966 if (line
.length() && line
.back() != '\r')
1967 cnn
.sock
.out() << '\r';
1968 cnn
.sock
.out() << '\n';
1972 CHECK(cnn
.sock
.out().good());
1974 // Done sending data
1975 if (FLAGS_pipeline_quit
) {
1976 LOG(INFO
) << "C: QUIT";
1977 cnn
.sock
.out() << ".\r\nQUIT\r\n" << std::flush
;
1980 cnn
.sock
.out() << ".\r\n" << std::flush
;
1984 auto success
{false};
1985 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1987 // Now, either DATA or BDATs lets check replies
1989 if (cnn
.reply_code
== "353") {
1991 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1992 success
= cnn
.reply_code
.length() && cnn
.reply_code
.at(0) == '2';
1995 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
1996 success
= success
&&
1997 (cnn
.reply_code
.length() && cnn
.reply_code
.at(0) == '2');
2000 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
2001 success
= success
&&
2002 (cnn
.reply_code
.length() && cnn
.reply_code
.at(0) == '2');
2006 // last (and useless?) response.
2007 LOG(INFO
) << "DATA/BDAT #2 returned " << cnn
.reply_code
;
2008 success
= cnn
.reply_code
.length() && cnn
.reply_code
.at(0) == '2';
2012 LOG(INFO
) << "DATA/BDAT #3 returned " << cnn
.reply_code
;
2013 success
= cnn
.reply_code
.length() && cnn
.reply_code
.at(0) == '2';
2023 LOG(INFO
) << "all mail was sent successfully";
2026 LOG(INFO
) << "some mail was *NOT* sent successfully";
2032 if (!FLAGS_pipeline_quit
) {
2033 LOG(INFO
) << "C: QUIT";
2034 cnn
.sock
.out() << "QUIT\r\n" << std::flush
;
2036 CHECK((parse
<RFC5321::reply_lines
, RFC5321::action
>(in
, cnn
)));
2042 get_tlsa_rrs(DNS::Resolver
& res
, Domain
const& domain
, uint16_t port
)
2044 auto const tlsa
= fmt::format("_{}._tcp.{}", port
, domain
.ascii());
2046 DNS::Query
q(res
, DNS::RR_type::TLSA
, tlsa
);
2048 if (q
.nx_domain()) {
2049 LOG(INFO
) << "TLSA data not found for " << domain
<< ':' << port
;
2052 if (q
.bogus_or_indeterminate()) {
2053 LOG(WARNING
) << "TLSA data is bogus or indeterminate";
2056 if (q
.authentic_data()) {
2057 LOG(INFO
) << "### TLSA records authentic for domain " << domain
<< " ###";
2060 LOG(INFO
) << "TLSA records can't be authenticated for domain " << domain
;
2063 auto tlsa_rrs
= q
.get_records();
2064 if (!tlsa_rrs
.empty()) {
2065 LOG(INFO
) << "### TLSA data found for " << domain
<< ':' << port
<< " ###";
2072 int main(int argc
, char* argv
[])
2074 std::ios::sync_with_stdio(false);
2076 { // Need to work with either namespace.
2077 using namespace gflags
;
2078 using namespace google
;
2079 ParseCommandLineFlags(&argc
, &argv
, true);
2082 auto const config_path
= osutil::get_config_dir();
2084 auto sender
= get_sender();
2086 if (FLAGS_selftest
) {
2091 auto bodies
{std::vector
<content
>{}};
2092 for (int a
= 1; a
< argc
; ++a
) {
2093 if (!fs::exists(argv
[a
]))
2094 LOG(FATAL
) << "can't find mail body part " << argv
[a
];
2095 bodies
.push_back(argv
[a
]);
2099 bodies
.push_back("body.txt");
2101 CHECK_EQ(bodies
.size(), 1) << "only one body part for now";
2102 CHECK(!(FLAGS_4
&& FLAGS_6
)) << "must use /some/ IP version";
2104 if (FLAGS_force_smtputf8
)
2105 FLAGS_use_smtputf8
= true;
2107 auto&& [from_mbx
, to_mbx
, smtp_from_mbx
, smtp_to_mbx
, smtp_to2_mbx
,
2108 smtp_to3_mbx
] = parse_mailboxes();
2110 if (smtp_to_mbx
.domain().empty() && smtp_to2_mbx
.domain().empty() &&
2111 smtp_to3_mbx
.domain().empty()) {
2112 smtp_to_mbx
= to_mbx
;
2115 if (smtp_to_mbx
.domain().empty() && FLAGS_mx_host
.empty()) {
2116 LOG(ERROR
) << "don't know who to send this mail to";
2120 if (!smtp_to2_mbx
.domain().empty() &&
2121 smtp_to2_mbx
.domain() != smtp_to_mbx
.domain()) {
2122 LOG(ERROR
) << "can't send to both " << smtp_to_mbx
.domain() << " and "
2123 << smtp_to2_mbx
.domain();
2127 if (!smtp_to3_mbx
.domain().empty() &&
2128 smtp_to3_mbx
.domain() != smtp_to_mbx
.domain()) {
2129 LOG(ERROR
) << "can't send to both " << smtp_to_mbx
.domain() << " and "
2130 << smtp_to3_mbx
.domain();
2134 auto const port
{osutil::get_port(FLAGS_service
.c_str(), "tcp")};
2136 auto res
{DNS::Resolver
{config_path
}};
2137 auto tlsa_rrs
{get_tlsa_rrs(res
, smtp_to_mbx
.domain(), port
)};
2140 return snd(config_path
, STDIN_FILENO
, STDOUT_FILENO
, sender
,
2141 smtp_to_mbx
.domain(), tlsa_rrs
, false, from_mbx
, to_mbx
,
2142 smtp_from_mbx
, smtp_to_mbx
, smtp_to2_mbx
, smtp_to3_mbx
, bodies
)
2147 bool enforce_dane
= true;
2148 auto const receivers
= get_receivers(res
, smtp_to_mbx
, enforce_dane
);
2150 if (receivers
.empty()) {
2151 LOG(INFO
) << "no place to send this mail";
2152 return EXIT_SUCCESS
;
2155 for (auto const& receiver
: receivers
) {
2156 LOG(INFO
) << "trying " << receiver
<< ":" << FLAGS_service
;
2159 LOG(INFO
) << "skipping";
2163 auto fd
= conn(res
, receiver
, port
);
2165 LOG(WARNING
) << "no connection, skipping";
2169 // Get our local IP address as "us".
2171 sa::sockaddrs us_addr
{};
2172 socklen_t us_addr_len
{sizeof us_addr
};
2173 char us_addr_str
[INET6_ADDRSTRLEN
]{'\0'};
2174 std::vector
<std::string
> fcrdns
;
2175 bool private_addr
= false;
2177 if (-1 == getsockname(fd
, &us_addr
.addr
, &us_addr_len
)) {
2178 // Ignore ENOTSOCK errors from getsockname, useful for testing.
2179 PLOG_IF(WARNING
, ENOTSOCK
!= errno
) << "getsockname failed";
2182 switch (us_addr_len
) {
2183 case sizeof(sockaddr_in
):
2184 PCHECK(inet_ntop(AF_INET
, &us_addr
.addr_in
.sin_addr
, us_addr_str
,
2185 sizeof us_addr_str
) != nullptr);
2186 if (IP4::is_private(us_addr_str
))
2187 private_addr
= true;
2189 fcrdns
= DNS::fcrdns4(res
, us_addr_str
);
2192 case sizeof(sockaddr_in6
):
2193 PCHECK(inet_ntop(AF_INET6
, &us_addr
.addr_in6
.sin6_addr
, us_addr_str
,
2194 sizeof us_addr_str
) != nullptr);
2195 if (IP6::is_private(us_addr_str
))
2196 private_addr
= true;
2198 fcrdns
= DNS::fcrdns6(res
, us_addr_str
);
2202 LOG(FATAL
) << "bogus address length (" << us_addr_len
2203 << ") returned from getsockname";
2207 if (fcrdns
.size()) {
2208 LOG(INFO
) << "our names are:";
2209 for (auto const& fc
: fcrdns
) {
2210 LOG(INFO
) << " " << fc
;
2214 if (!private_addr
) {
2215 // look at from_mbx.domain() and get SPF records
2217 // also look at our ID to get SPF records.
2220 if (smtp_to_mbx
.domain() == receiver
) {
2221 if (snd(config_path
, fd
, fd
, sender
, receiver
, tlsa_rrs
, enforce_dane
,
2222 from_mbx
, to_mbx
, smtp_from_mbx
, smtp_to_mbx
, smtp_to2_mbx
,
2223 smtp_to3_mbx
, bodies
)) {
2224 return EXIT_SUCCESS
;
2228 auto tlsa_rrs_mx
{get_tlsa_rrs(res
, receiver
, port
)};
2229 tlsa_rrs_mx
.insert(end(tlsa_rrs_mx
), begin(tlsa_rrs
), end(tlsa_rrs
));
2230 if (snd(config_path
, fd
, fd
, sender
, receiver
, tlsa_rrs_mx
, enforce_dane
,
2231 from_mbx
, to_mbx
, smtp_from_mbx
, smtp_to_mbx
, smtp_to2_mbx
,
2232 smtp_to3_mbx
, bodies
)) {
2233 return EXIT_SUCCESS
;
2240 LOG(ERROR
) << "we ran out of hosts to try";