Allow any old and crufty protocol version
[ghsmtp.git] / OpenDKIM.cpp
blob52134d3c83b40a8c66163a3a5ecf744bdf61823e
1 #include "OpenDKIM.hpp"
3 #include "iobuffer.hpp"
5 #include <stdbool.h> // needs to be above <dkim.h>
7 #include <dkim.h>
9 #include <glog/logging.h>
11 namespace {
12 // Not nice to use "unsigned char" for character data.
13 u_char* uc(char const* cp)
15 return reinterpret_cast<u_char*>(const_cast<char*>(cp));
18 char const* c(unsigned char* ucp) { return reinterpret_cast<char const*>(ucp); }
20 constexpr unsigned char id_v[]{"DKIM::verify"};
21 constexpr unsigned char id_s[]{"DKIM::sign"};
22 } // namespace
24 namespace OpenDKIM {
26 lib::lib()
27 : lib_(CHECK_NOTNULL(dkim_init(nullptr, nullptr)))
31 lib::~lib()
33 dkim_free(dkim_);
34 dkim_close(lib_);
37 void lib::header(std::string_view header)
39 if (header.size() && header.back() == '\n')
40 header.remove_suffix(1);
41 if (header.size() && header.back() == '\r')
42 header.remove_suffix(1);
44 CHECK_EQ((status_ = dkim_header(dkim_, uc(header.data()), header.length())),
45 DKIM_STAT_OK)
46 << "dkim_header error: " << dkim_getresultstr(status_);
48 // LOG(INFO) << "processed: " << std::string(header.data(),
49 // header.length());
52 void lib::eoh()
54 status_ = dkim_eoh(dkim_);
55 switch (status_) {
56 case DKIM_STAT_OK:
57 case DKIM_STAT_NOSIG:
58 // all good
59 break;
61 default:
62 LOG(WARNING) << "dkim_eoh error: " << dkim_getresultstr(status_);
63 break;
67 void lib::body(std::string_view body)
69 CHECK_EQ((status_ = dkim_body(dkim_, uc(body.data()), body.length())),
70 DKIM_STAT_OK)
71 << "dkim_body error: " << dkim_getresultstr(status_);
74 void lib::chunk(std::string_view chunk)
76 CHECK_EQ((status_ = dkim_chunk(dkim_, uc(chunk.data()), chunk.length())),
77 DKIM_STAT_OK)
78 << "dkim_chunk error: " << dkim_getresultstr(status_);
81 void lib::eom()
83 status_ = dkim_eom(dkim_, nullptr);
85 switch (status_) {
86 case DKIM_STAT_OK:
87 case DKIM_STAT_NOSIG:
88 // all good
89 break;
91 default:
92 LOG(WARNING) << "dkim_eom error: " << dkim_getresultstr(status_);
93 break;
97 //.............................................................................
99 sign::sign(char const* secretkey,
100 char const* selector,
101 char const* domain,
102 body_type typ)
104 // clang-format off
105 dkim_ = dkim_sign(lib_,
106 id_s,
107 nullptr,
108 uc(secretkey),
109 uc(selector),
110 uc(domain),
111 DKIM_CANON_RELAXED,
112 (typ == body_type::binary) ? DKIM_CANON_SIMPLE
113 : DKIM_CANON_RELAXED,
114 DKIM_SIGN_RSASHA256,
116 &status_);
117 // clang-format on
118 CHECK_NOTNULL(dkim_);
119 CHECK_EQ(status_, DKIM_STAT_OK);
121 char const* hdrlist[] = {"cc",
122 "content-language",
123 "content-transfer-encoding",
124 "content-type",
125 "date",
126 "feedback-id",
127 "from",
128 "in-reply-to",
129 "list-archive",
130 "list-help",
131 "list-id",
132 "list-owner",
133 "list-post",
134 "list-subscribe",
135 "list-unsubscribe",
136 "message-id",
137 "mime-version",
138 "precedence",
139 "references",
140 "reply-to",
141 "resent-cc",
142 "resent-date",
143 "resent-from",
144 "resent-to",
145 "subject",
146 "to",
147 nullptr};
149 dkim_signhdrs(dkim_, hdrlist);
152 std::string sign::getsighdr()
154 auto const initial{strlen(DKIM_SIGNHEADER) + 2};
155 unsigned char* buf = nullptr;
156 size_t len = 0;
157 status_ = dkim_getsighdr_d(dkim_, initial, &buf, &len);
158 return std::string(c(buf), len);
161 //.............................................................................
163 void verify::foreach_sig(std::function<void(char const* domain,
164 bool passed,
165 char const* identity,
166 char const* selector,
167 char const* b)> func)
169 int nsigs = 0;
170 DKIM_SIGINFO** sigs = nullptr;
171 status_ = dkim_getsiglist(dkim_, &sigs, &nsigs);
172 if (status_ == DKIM_STAT_INVALID) {
173 LOG(WARNING) << "skipping DKIM sigs";
174 return;
176 CHECK_EQ(status_, DKIM_STAT_OK);
178 iobuffer<u_char> identity(4 * 1024);
180 for (auto i{0}; i < nsigs; ++i) {
181 auto const dom = CHECK_NOTNULL(dkim_sig_getdomain(sigs[i]));
183 auto const flg = dkim_sig_getflags(sigs[i]);
184 if ((flg & DKIM_SIGFLAG_IGNORE) != 0) {
185 LOG(INFO) << "ignoring signature for domain " << dom;
186 continue;
188 if ((flg & DKIM_SIGFLAG_TESTKEY) != 0) {
189 LOG(INFO) << "testkey for domain " << dom;
192 if ((flg & DKIM_SIGFLAG_PROCESSED) == 0) {
193 LOG(INFO) << "ignoring unprocessed sig for domain " << dom;
194 continue;
197 auto const bh = dkim_sig_getbh(sigs[i]);
198 if (bh != DKIM_SIGBH_MATCH) {
199 LOG(INFO) << "body hash mismatch for domain " << dom;
202 auto bits{0u};
203 status_ = dkim_sig_getkeysize(sigs[i], &bits);
204 if (status_ == DKIM_STAT_OK) {
205 if (bits < 1024) {
206 LOG(WARNING) << "keysize " << bits << " too small for domain " << dom;
209 else {
210 LOG(WARNING) << "getkeysize failed for domain " << dom << " with "
211 << dkim_getresultstr(status_);
214 auto const passed =
215 ((flg & DKIM_SIGFLAG_PASSED) != 0) && (bh == DKIM_SIGBH_MATCH);
217 CHECK_EQ(
218 dkim_sig_getidentity(dkim_, sigs[i], identity.data(), identity.size()),
219 DKIM_STAT_OK);
221 auto const selector = dkim_sig_getselector(sigs[i]);
223 auto const b = dkim_sig_gettagvalue(sigs[i], false, uc("b"));
225 func(c(dom), passed, c(identity.data()), c(selector), c(b));
229 verify::verify()
231 dkim_ = CHECK_NOTNULL(dkim_verify(lib_, id_v, nullptr, &status_));
234 bool verify::check()
236 int nsigs = 0;
237 DKIM_SIGINFO** sigs = nullptr;
238 status_ = dkim_getsiglist(dkim_, &sigs, &nsigs);
239 CHECK_EQ(status_, DKIM_STAT_OK);
241 LOG(INFO) << "nsigs == " << nsigs;
243 for (auto i{0}; i < nsigs; ++i) {
244 LOG(INFO) << i << " domain == " << dkim_sig_getdomain(sigs[i]);
245 auto flg = dkim_sig_getflags(sigs[i]);
246 if ((flg & DKIM_SIGFLAG_IGNORE) != 0) {
247 LOG(INFO) << "DKIM_SIGFLAG_IGNORE";
249 if ((flg & DKIM_SIGFLAG_PROCESSED) != 0) {
250 LOG(INFO) << "DKIM_SIGFLAG_PROCESSED";
252 if ((flg & DKIM_SIGFLAG_PASSED) != 0) {
253 LOG(INFO) << "DKIM_SIGFLAG_PASSED";
255 if ((flg & DKIM_SIGFLAG_TESTKEY) != 0) {
256 LOG(INFO) << "DKIM_SIGFLAG_TESTKEY";
258 if ((flg & DKIM_SIGFLAG_NOSUBDOMAIN) != 0) {
259 LOG(INFO) << "DKIM_SIGFLAG_NOSUBDOMAIN";
263 if (nsigs) {
264 auto sig{dkim_getsignature(dkim_)};
265 if (sig) {
267 LOG(INFO) << "dkim_getsignature domain == " << dkim_sig_getdomain(sig);
269 ssize_t msglen;
270 ssize_t canonlen;
271 ssize_t signlen;
273 status_ = dkim_sig_getcanonlen(dkim_, sig, &msglen, &canonlen, &signlen);
275 CHECK_EQ(status_, DKIM_STAT_OK);
277 LOG(INFO) << "msglen == " << msglen;
278 LOG(INFO) << "canonlen == " << canonlen;
279 LOG(INFO) << "signlen == " << signlen;
281 auto nhdrs{0u};
282 status_ = dkim_sig_getsignedhdrs(dkim_, sig, nullptr, 0, &nhdrs);
283 if (status_ != DKIM_STAT_NORESOURCE) {
284 return false;
287 LOG(INFO) << "nhdrs == " << nhdrs;
289 auto constexpr hdr_sz{DKIM_MAXHEADER + 1};
290 auto signedhdrs{std::vector<unsigned char>(nhdrs * hdr_sz, '\0')};
292 status_ =
293 dkim_sig_getsignedhdrs(dkim_, sig, &signedhdrs[0], hdr_sz, &nhdrs);
294 CHECK_EQ(status_, DKIM_STAT_OK);
296 for (auto i{0u}; i < nhdrs; ++i)
297 LOG(INFO) << &signedhdrs[i * hdr_sz];
299 return true;
303 return false;
306 bool verify::sig_syntax(std::string_view sig)
308 return dkim_sig_syntax(dkim_, uc(sig.data()), sig.length()) == DKIM_STAT_OK;
311 } // namespace OpenDKIM