test PRDR split result
[ghsmtp.git] / OpenDKIM.cpp
blob737eaa8151057098508257db7775435e948a804d
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)))
29 char const* signhdrs[] = {"cc",
30 "content-language",
31 "content-transfer-encoding",
32 "content-type",
33 "date",
34 "from",
35 "in-reply-to",
36 "list-archive",
37 "list-help",
38 "list-id",
39 "list-owner",
40 "list-post",
41 "list-subscribe",
42 "list-unsubscribe",
43 "references",
44 "message-id",
45 "mime-version",
46 "precedence",
47 "references",
48 "reply-to",
49 "resent-cc",
50 "resent-date",
51 "resent-from",
52 "resent-sender",
53 "resent-to",
54 "sender",
55 "subject",
56 "to",
57 nullptr};
59 CHECK_EQ(dkim_options(lib_, DKIM_OP_SETOPT, DKIM_OPTS_SIGNHDRS, signhdrs,
60 sizeof(char const**)),
61 DKIM_STAT_OK);
63 char const* oversign[] = {"cc", "date", "from",
64 "in-reply-to", "message-id", "sender",
65 "subject", "to", nullptr};
67 CHECK_EQ(dkim_options(lib_, DKIM_OP_SETOPT, DKIM_OPTS_OVERSIGNHDRS, oversign,
68 sizeof(char const**)),
69 DKIM_STAT_OK);
72 lib::~lib()
74 dkim_free(dkim_);
75 dkim_close(lib_);
78 void lib::header(std::string_view header)
80 if (header.size() && header.back() == '\n')
81 header.remove_suffix(1);
82 if (header.size() && header.back() == '\r')
83 header.remove_suffix(1);
85 CHECK_EQ((status_ = dkim_header(dkim_, uc(header.data()), header.length())),
86 DKIM_STAT_OK)
87 << "dkim_header error: " << dkim_getresultstr(status_);
89 // LOG(INFO) << "processed: " << std::string(header.data(),
90 // header.length());
93 void lib::eoh()
95 status_ = dkim_eoh(dkim_);
96 switch (status_) {
97 case DKIM_STAT_OK:
98 case DKIM_STAT_NOSIG:
99 // all good
100 break;
102 default:
103 LOG(WARNING) << "dkim_eoh error: " << dkim_getresultstr(status_);
104 break;
108 void lib::body(std::string_view body)
110 CHECK_EQ((status_ = dkim_body(dkim_, uc(body.data()), body.length())),
111 DKIM_STAT_OK)
112 << "dkim_body error: " << dkim_getresultstr(status_);
115 void lib::chunk(std::string_view chunk)
117 CHECK_EQ((status_ = dkim_chunk(dkim_, uc(chunk.data()), chunk.length())),
118 DKIM_STAT_OK)
119 << "dkim_chunk error: " << dkim_getresultstr(status_);
122 void lib::eom()
124 status_ = dkim_eom(dkim_, nullptr);
126 switch (status_) {
127 case DKIM_STAT_OK:
128 case DKIM_STAT_NOSIG:
129 // all good
130 break;
132 default:
133 LOG(WARNING) << "dkim_eom error: " << dkim_getresultstr(status_);
134 break;
138 //.............................................................................
140 sign::sign(char const* secretkey,
141 char const* selector,
142 char const* domain,
143 body_type typ)
145 // clang-format off
146 dkim_ = dkim_sign(lib_,
147 id_s,
148 nullptr,
149 uc(secretkey),
150 uc(selector),
151 uc(domain),
152 DKIM_CANON_RELAXED,
153 (typ == body_type::binary) ? DKIM_CANON_SIMPLE
154 : DKIM_CANON_RELAXED,
155 DKIM_SIGN_RSASHA256,
157 &status_);
158 // clang-format on
159 CHECK_NOTNULL(dkim_);
160 CHECK_EQ(status_, DKIM_STAT_OK);
163 std::string sign::getsighdr()
165 auto const initial{strlen(DKIM_SIGNHEADER) + 2};
166 unsigned char* buf = nullptr;
167 size_t len = 0;
168 status_ = dkim_getsighdr_d(dkim_, initial, &buf, &len);
169 CHECK_EQ(status_, DKIM_STAT_OK);
170 return std::string(c(buf), len);
173 //.............................................................................
175 void verify::foreach_sig(std::function<void(char const* domain,
176 bool passed,
177 char const* identity,
178 char const* selector,
179 char const* b)> func)
181 int nsigs = 0;
182 DKIM_SIGINFO** sigs = nullptr;
183 status_ = dkim_getsiglist(dkim_, &sigs, &nsigs);
184 if (status_ == DKIM_STAT_INVALID) {
185 LOG(WARNING) << "skipping DKIM sigs";
186 return;
188 CHECK_EQ(status_, DKIM_STAT_OK);
190 iobuffer<u_char> identity(4 * 1024);
192 for (auto i{0}; i < nsigs; ++i) {
193 auto const dom = CHECK_NOTNULL(dkim_sig_getdomain(sigs[i]));
195 auto const flg = dkim_sig_getflags(sigs[i]);
196 if ((flg & DKIM_SIGFLAG_IGNORE) != 0) {
197 LOG(INFO) << "ignoring signature for domain " << dom;
198 continue;
200 if ((flg & DKIM_SIGFLAG_TESTKEY) != 0) {
201 LOG(INFO) << "testkey for domain " << dom;
204 if ((flg & DKIM_SIGFLAG_PROCESSED) == 0) {
205 LOG(INFO) << "ignoring unprocessed sig for domain " << dom;
206 continue;
209 auto const bh = dkim_sig_getbh(sigs[i]);
210 if (bh != DKIM_SIGBH_MATCH) {
211 LOG(INFO) << "body hash mismatch for domain " << dom;
214 auto bits{0u};
215 status_ = dkim_sig_getkeysize(sigs[i], &bits);
216 if (status_ == DKIM_STAT_OK) {
217 if (bits < 1024) {
218 LOG(WARNING) << "keysize " << bits << " too small for domain " << dom;
221 else {
222 LOG(WARNING) << "getkeysize failed for domain " << dom << " with "
223 << dkim_getresultstr(status_);
226 auto const passed =
227 ((flg & DKIM_SIGFLAG_PASSED) != 0) && (bh == DKIM_SIGBH_MATCH);
229 CHECK_EQ(
230 dkim_sig_getidentity(dkim_, sigs[i], identity.data(), identity.size()),
231 DKIM_STAT_OK);
233 auto const selector = dkim_sig_getselector(sigs[i]);
235 auto const b = dkim_sig_gettagvalue(sigs[i], false, uc("b"));
237 func(c(dom), passed, c(identity.data()), c(selector), c(b));
241 verify::verify()
243 dkim_ = CHECK_NOTNULL(dkim_verify(lib_, id_v, nullptr, &status_));
246 bool verify::check()
248 int nsigs = 0;
249 DKIM_SIGINFO** sigs = nullptr;
250 status_ = dkim_getsiglist(dkim_, &sigs, &nsigs);
251 CHECK_EQ(status_, DKIM_STAT_OK);
253 LOG(INFO) << "nsigs == " << nsigs;
255 for (auto i{0}; i < nsigs; ++i) {
256 LOG(INFO) << i << " domain == " << dkim_sig_getdomain(sigs[i]);
257 auto flg = dkim_sig_getflags(sigs[i]);
258 if ((flg & DKIM_SIGFLAG_IGNORE) != 0) {
259 LOG(INFO) << "DKIM_SIGFLAG_IGNORE";
261 if ((flg & DKIM_SIGFLAG_PROCESSED) != 0) {
262 LOG(INFO) << "DKIM_SIGFLAG_PROCESSED";
264 if ((flg & DKIM_SIGFLAG_PASSED) != 0) {
265 LOG(INFO) << "DKIM_SIGFLAG_PASSED";
267 if ((flg & DKIM_SIGFLAG_TESTKEY) != 0) {
268 LOG(INFO) << "DKIM_SIGFLAG_TESTKEY";
270 if ((flg & DKIM_SIGFLAG_NOSUBDOMAIN) != 0) {
271 LOG(INFO) << "DKIM_SIGFLAG_NOSUBDOMAIN";
275 if (nsigs) {
276 auto sig{dkim_getsignature(dkim_)};
277 if (sig) {
279 LOG(INFO) << "dkim_getsignature domain == " << dkim_sig_getdomain(sig);
281 ssize_t msglen;
282 ssize_t canonlen;
283 ssize_t signlen;
285 status_ = dkim_sig_getcanonlen(dkim_, sig, &msglen, &canonlen, &signlen);
287 CHECK_EQ(status_, DKIM_STAT_OK);
289 LOG(INFO) << "msglen == " << msglen;
290 LOG(INFO) << "canonlen == " << canonlen;
291 LOG(INFO) << "signlen == " << signlen;
293 auto nhdrs{0u};
294 status_ = dkim_sig_getsignedhdrs(dkim_, sig, nullptr, 0, &nhdrs);
295 if (status_ != DKIM_STAT_NORESOURCE) {
296 return false;
299 LOG(INFO) << "nhdrs == " << nhdrs;
301 auto constexpr hdr_sz{DKIM_MAXHEADER + 1};
302 auto signedhdrs{std::vector<unsigned char>(nhdrs * hdr_sz, '\0')};
304 status_ =
305 dkim_sig_getsignedhdrs(dkim_, sig, &signedhdrs[0], hdr_sz, &nhdrs);
306 CHECK_EQ(status_, DKIM_STAT_OK);
308 for (auto i{0u}; i < nhdrs; ++i)
309 LOG(INFO) << &signedhdrs[i * hdr_sz];
311 return true;
315 return false;
318 bool verify::sig_syntax(std::string_view sig)
320 return dkim_sig_syntax(dkim_, uc(sig.data()), sig.length()) == DKIM_STAT_OK;
323 } // namespace OpenDKIM