remove IP allow list, depend on domain reputation from FCrDNS
[ghsmtp.git] / arcsign.cpp
blob69abd4cb45031c4e0eb898860694a134fff474c3
1 #include "Mailbox.hpp"
2 #include "OpenARC.hpp"
3 #include "OpenDKIM.hpp"
4 #include "OpenDMARC.hpp"
5 #include "esc.hpp"
6 #include "fs.hpp"
7 #include "iequal.hpp"
8 #include "imemstream.hpp"
9 #include "message.hpp"
10 #include "osutil.hpp"
12 #include <cstring>
13 #include <map>
15 #include <fmt/format.h>
16 #include <fmt/ostream.h>
18 #include <boost/algorithm/string.hpp>
19 #include <boost/iostreams/device/mapped_file.hpp>
21 using namespace std::string_literals;
23 DEFINE_string(signer, "digilicious.com", "signing domain");
24 DEFINE_string(selector, "ghsmtp", "DKIM selector");
26 bool arc_sign(message::parsed& msg,
27 char const* sender,
28 char const* selector,
29 fs::path key_file,
30 char const* server_id)
32 CHECK(!msg.headers.empty());
34 // ARC
36 OpenARC::verify arv;
37 for (auto const& header : msg.headers) {
38 arv.header(header.as_view());
40 arv.eoh();
41 arv.body(msg.body);
42 arv.eom();
44 LOG(INFO) << "ARC status == " << arv.chain_status_str();
45 LOG(INFO) << "ARC custody == " << arv.chain_custody_str();
47 auto const arc_status = arv.chain_status_str();
49 // Run our message through ARC::sign
51 OpenARC::sign ars;
53 if (iequal(arc_status, "none")) {
54 ars.set_cv_none();
56 else if (iequal(arc_status, "fail")) {
57 ars.set_cv_fail();
59 else if (iequal(arc_status, "pass")) {
60 ars.set_cv_pass();
62 else {
63 ars.set_cv_unkn();
66 for (auto const& header : msg.headers) {
67 ars.header(header.as_view());
69 ars.eoh();
70 ars.body(msg.body);
71 ars.eom();
73 boost::iostreams::mapped_file_source priv;
74 priv.open(key_file);
76 std::string ar_results = "None";
77 for (auto hdr : msg.headers) {
78 if (hdr == message::Authentication_Results) {
79 std::string authservid;
80 if (message::authentication_results_parse(hdr.as_view(), authservid,
81 ar_results)) {
82 if (Domain::match(authservid, sender))
83 break;
84 LOG(INFO) << "ignoring AR: " << hdr.as_string();
86 LOG(WARNING) << "failed to parse «"
87 << esc(hdr.as_string(), esc_line_option::multi) << "»";
88 ar_results = "None";
92 if (ars.seal(sender, selector, sender, priv.data(), priv.size(),
93 ar_results.c_str())) {
94 msg.arc_hdrs = ars.whole_seal();
95 for (auto const& hdr : msg.arc_hdrs) {
96 CHECK(msg.parse_hdr(hdr));
99 else {
100 LOG(INFO) << "failed to generate seal";
103 OpenARC::verify arv2;
104 for (auto const& header : msg.headers) {
105 arv2.header(header.as_view());
107 arv2.eoh();
108 arv2.body(msg.body);
109 arv2.eom();
111 LOG(INFO) << "check ARC status == " << arv2.chain_status_str();
112 LOG(INFO) << "check ARC custody == " << arv2.chain_custody_str();
114 return "fail"s != arv2.chain_status_str();
117 int main(int argc, char* argv[])
119 google::ParseCommandLineFlags(&argc, &argv, true);
121 auto const server_identity = [] {
122 auto const id_from_env{getenv("GHSMTP_SERVER_ID")};
123 if (id_from_env)
124 return std::string{id_from_env};
126 auto const hostname{osutil::get_hostname()};
127 if (hostname.find('.') != std::string::npos)
128 return hostname;
130 LOG(FATAL) << "can't determine my server ID, set GHSMTP_SERVER_ID maybe";
131 return "(none)"s;
132 }();
134 auto const config_path = osutil::get_config_dir();
136 auto const selector = FLAGS_selector.c_str();
138 auto const key_file = (config_path / selector).replace_extension("private");
139 CHECK(fs::exists(key_file)) << "can't find key file " << key_file;
141 auto const dom_signer{Domain(FLAGS_signer)};
143 for (int a = 1; a < argc; ++a) {
144 if (!fs::exists(argv[a]))
145 LOG(FATAL) << "can't find mail file " << argv[a];
146 boost::iostreams::mapped_file_source file;
147 file.open(argv[a]);
148 message::parsed msg;
149 CHECK(msg.parse(std::string_view(file.data(), file.size())));
150 CHECK(arc_sign(msg, dom_signer.ascii().c_str(), selector, key_file,
151 server_identity.c_str()));
152 std::cout << msg.as_string();