still must match obs first
[ghsmtp.git] / message-test.cpp
blob698953bedde2986b97edd7b7e8048801ce38b59a
1 #include "message.hpp"
3 #include "Now.hpp"
4 #include "Pill.hpp"
5 // #include "Reply.hpp"
6 #include "esc.hpp"
7 #include "osutil.hpp"
9 #include <fmt/format.h>
10 #include <fmt/ostream.h>
12 #include <gflags/gflags.h>
14 #include <glog/logging.h>
16 #include <boost/iostreams/device/mapped_file.hpp>
18 #include <iostream>
20 #include <tao/pegtl.hpp>
21 #include <tao/pegtl/contrib/abnf.hpp>
23 using namespace tao::pegtl;
24 using namespace tao::pegtl::abnf;
26 constexpr char srs_secret[] = "Not a real secret, of course.";
28 DEFINE_bool(arc, false, "check ARC set");
29 DEFINE_bool(dkim, false, "check DKIM sigs");
30 DEFINE_bool(print_from, false, "print envelope froms");
32 DEFINE_string(selector, "ghsmtp", "DKIM selector");
34 static std::string make_string(std::string_view v)
36 return std::string(v.begin(),
37 static_cast<size_t>(std::distance(v.begin(), v.end())));
40 int main(int argc, char* argv[])
42 google::ParseCommandLineFlags(&argc, &argv, true);
44 auto server_identity = [] {
45 auto const id_from_env{getenv("GHSMTP_SERVER_ID")};
46 if (id_from_env)
47 return std::string{id_from_env};
49 auto const hostname{osutil::get_hostname()};
50 if (hostname.find('.') != std::string::npos)
51 return hostname;
53 LOG(FATAL) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
54 return std::string("(none)");
55 }();
57 auto constexpr authentication_results_str =
58 "Authentication-Results: digilicious.com;\r\n"
59 " spf=pass smtp.helo=mta122b.pmx1.epsl1.com;\r\n"
60 " dkim=pass header.i=@mail.paypal.com header.s=pp-epsilon1 "
61 "header.b=\"A4JA0zWd\";\r\n"
62 " dmarc=fail header.from=mail.paypal.com;\r\n"
63 " arc=none";
65 std::string authservid;
66 std::string ar_result;
67 CHECK(message::authentication_results_parse(authentication_results_str,
68 authservid, ar_result));
69 CHECK_EQ(authservid, "digilicious.com");
71 auto const dom_from{Domain(server_identity)};
72 auto const dom_to{Domain(server_identity)};
74 auto const config_path = osutil::get_config_dir();
76 auto const selector = FLAGS_selector.c_str();
78 auto const key_file = (config_path / selector).replace_extension("private");
79 CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
81 Mailbox from("gene", dom_from);
82 Mailbox to("anything", dom_to);
84 auto const date{Now{}};
85 auto const pill{Pill{}};
86 auto const mid_str = fmt::format("<{}.{}@{}>", date.sec(),
87 pill.as_string_view(), server_identity);
89 fmt::memory_buffer bfr;
90 fmt::format_to(std::back_inserter(bfr), "Message-ID: {}\r\n",
91 mid_str.c_str());
92 fmt::format_to(std::back_inserter(bfr),
93 "From: \"Gene (more \\) comments) Hightower\" <{}>\r\n",
94 from.as_string(Mailbox::domain_encoding::utf8));
95 fmt::format_to(std::back_inserter(bfr), "To: \"Gene Hightower\" <{}>\r\n",
96 to.as_string(Mailbox::domain_encoding::utf8));
97 fmt::format_to(std::back_inserter(bfr),
98 "Subject: Testing, one, two, three.\r\n");
99 fmt::format_to(std::back_inserter(bfr), "Date: {}\r\n", date.c_str());
100 fmt::format_to(std::back_inserter(bfr),
101 "Authentication-Results: {}; none\r\n", server_identity);
102 fmt::format_to(std::back_inserter(bfr), "MIME-Version: 1.0\r\n");
103 fmt::format_to(std::back_inserter(bfr),
104 "Content-Type: text/plain; charset=utf-8\r\n");
106 fmt::format_to(std::back_inserter(bfr), "\r\n");
108 fmt::format_to(std::back_inserter(bfr), "This is the body of the email.\r\n");
109 auto const msg_str = fmt::to_string(bfr);
111 #if 0
112 message::parsed msg;
113 bool const message_parsed = msg.parse(msg_str);
115 if (message_parsed) {
116 LOG(INFO) << "message parsed";
118 auto const authentic =
119 authentication(msg, server_identity.c_str(), selector, key_file);
121 if (authentic)
122 LOG(INFO) << "authentic";
124 Reply::from_to reply;
126 reply.mail_from = msg.dmarc_from;
127 reply.rcpt_to_local_part = "local-alias";
129 auto const rfc22_from = fmt::format(
130 "From: {}@{}", Reply::enc_reply(reply, srs_secret), server_identity);
132 auto const reply_to =
133 fmt::format("Reply-To: {}@{}", Reply::enc_reply(reply, srs_secret),
134 server_identity);
136 message::rewrite_from_to(msg, "", reply_to, server_identity.c_str(),
137 selector, key_file);
139 std::cout << msg.as_string();
141 // auth again
142 // authentication(msg, server_identity.c_str(), selector, key_file);
144 auto const count = std::count_if(
145 begin(msg.headers), end(msg.headers),
146 [](auto const& hdr) { return hdr == message::Authentication_Results; });
147 // should have only one header, no matter what
148 CHECK_EQ(count, 1);
150 else {
151 LOG(INFO) << "message failed to parse";
154 if (FLAGS_arc) {
155 for (int a = 1; a < argc; ++a) {
156 if (!fs::exists(argv[a]))
157 LOG(FATAL) << "can't find mail file " << argv[a];
158 boost::iostreams::mapped_file_source file;
159 file.open(argv[a]);
160 message::parsed msg;
161 CHECK(msg.parse(std::string_view(file.data(), file.size())));
162 message::authentication(msg, server_identity.c_str(), selector, key_file);
163 std::cout << msg.as_string();
165 return 0;
168 if (FLAGS_dkim) {
169 for (int a = 1; a < argc; ++a) {
170 if (!fs::exists(argv[a]))
171 LOG(FATAL) << "can't find mail file " << argv[a];
172 boost::iostreams::mapped_file_source file;
173 file.open(argv[a]);
174 message::parsed msg;
175 CHECK(msg.parse(std::string_view(file.data(), file.size())));
176 message::dkim_check(msg, server_identity.c_str());
178 return 0;
181 if (FLAGS_print_from) {
182 for (int a = 1; a < argc; ++a) {
183 if (!fs::exists(argv[a]))
184 LOG(FATAL) << "can't find mail file " << argv[a];
185 boost::iostreams::mapped_file_source file;
186 file.open(argv[a]);
187 message::parsed msg;
188 CHECK(msg.parse(std::string_view(file.data(), file.size())));
189 message::print_spf_envelope_froms(argv[a], msg);
191 return 0;
193 #endif
195 for (int a = 1; a < argc; ++a) {
196 if (!fs::exists(argv[a]))
197 LOG(FATAL) << "can't find mail file " << argv[a];
198 boost::iostreams::mapped_file_source file;
199 file.open(argv[a]);
200 message::parsed msg;
201 CHECK(msg.parse(std::string_view(file.data(), file.size())));
203 message::mailbox_name_addr_list from_parsed;
204 // Should be only one From:
205 if (auto hdr =
206 std::find(begin(msg.headers), end(msg.headers), message::From);
207 hdr != end(msg.headers)) {
208 auto const from_str = make_string(hdr->value);
210 LOG(INFO) << "about to parse From:" << from_str;
211 if (message::mailbox_list_parse(from_str, from_parsed)) {
212 LOG(INFO) << "success parsing From:" << from_str;
214 else {
215 LOG(WARNING) << "failed to parse From:" << from_str;
218 for (auto hdr_next = std::next(hdr); hdr_next != end(msg.headers);
219 hdr_next = std::next(hdr_next)) {
220 if (*hdr_next == message::From) {
221 LOG(WARNING) << "additional RFC5322.From header found:«"
222 << hdr_next->as_string() << "»";
227 if (from_parsed.name_addr_list.empty()) {
228 LOG(WARNING) << "No address in RFC5322.From header";
229 return false;
232 for (auto const& na : from_parsed.name_addr_list) {
233 auto from_name = na.name;
234 LOG(INFO) << "from_name «" << from_name << "»";
236 auto from_addr = na.addr;
237 LOG(INFO) << "from_addr «" << from_addr << "»";
239 if (!Mailbox::validate(from_addr)) {
240 LOG(WARNING) << "Mailbox syntax valid for RFC-5322, not for RFC-5321: «"
241 << from_addr << "»";
245 // std::cout << msg.as_string();