accept more TLS versions, ignore zero return
[ghsmtp.git] / OpenDKIM.cpp
blob6488506608de67bf59db63109d4e3ef51d8fc3c4
1 #define _Bool bool
2 #include "OpenDKIM.hpp"
4 #include "iobuffer.hpp"
6 #include <stdbool.h> // needs to be above <dkim.h>
8 #include <dkim.h>
10 #include <glog/logging.h>
12 namespace {
13 // Not nice to use "unsigned char" for character data.
14 u_char* uc(char const* cp)
16 return reinterpret_cast<u_char*>(const_cast<char*>(cp));
19 char const* c(unsigned char* ucp) { return reinterpret_cast<char const*>(ucp); }
21 constexpr unsigned char id_v[]{"DKIM::verify"};
22 constexpr unsigned char id_s[]{"DKIM::sign"};
23 } // namespace
25 namespace OpenDKIM {
27 lib::lib()
28 : lib_(CHECK_NOTNULL(dkim_init(nullptr, nullptr)))
30 char const* signhdrs[] = {"cc",
31 "content-language",
32 "content-transfer-encoding",
33 "content-type",
34 "date",
35 "from",
36 "in-reply-to",
37 "list-archive",
38 "list-help",
39 "list-id",
40 "list-owner",
41 "list-post",
42 "list-subscribe",
43 "list-unsubscribe",
44 "references",
45 "message-id",
46 "mime-version",
47 "precedence",
48 "references",
49 "reply-to",
50 "resent-cc",
51 "resent-date",
52 "resent-from",
53 "resent-sender",
54 "resent-to",
55 "sender",
56 "subject",
57 "to",
58 nullptr};
60 CHECK_EQ(dkim_options(lib_, DKIM_OP_SETOPT, DKIM_OPTS_SIGNHDRS, signhdrs,
61 sizeof(char const**)),
62 DKIM_STAT_OK);
64 char const* oversign[] = {"cc", "date", "from",
65 "in-reply-to", "message-id", "sender",
66 "subject", "to", nullptr};
68 CHECK_EQ(dkim_options(lib_, DKIM_OP_SETOPT, DKIM_OPTS_OVERSIGNHDRS, oversign,
69 sizeof(char const**)),
70 DKIM_STAT_OK);
73 lib::~lib()
75 dkim_free(dkim_);
76 dkim_close(lib_);
79 void lib::header(std::string_view header)
81 if (header.size() && header.back() == '\n')
82 header.remove_suffix(1);
83 if (header.size() && header.back() == '\r')
84 header.remove_suffix(1);
86 CHECK_EQ((status_ = dkim_header(dkim_, uc(header.data()), header.length())),
87 DKIM_STAT_OK)
88 << "dkim_header error: " << dkim_getresultstr(status_);
90 // LOG(INFO) << "processed: " << std::string(header.data(),
91 // header.length());
94 void lib::eoh()
96 status_ = dkim_eoh(dkim_);
97 switch (status_) {
98 case DKIM_STAT_OK:
99 case DKIM_STAT_NOSIG:
100 // all good
101 break;
103 default:
104 LOG(WARNING) << "dkim_eoh error: " << dkim_getresultstr(status_);
105 break;
109 void lib::body(std::string_view body)
111 CHECK_EQ((status_ = dkim_body(dkim_, uc(body.data()), body.length())),
112 DKIM_STAT_OK)
113 << "dkim_body error: " << dkim_getresultstr(status_);
116 void lib::chunk(std::string_view chunk)
118 CHECK_EQ((status_ = dkim_chunk(dkim_, uc(chunk.data()), chunk.length())),
119 DKIM_STAT_OK)
120 << "dkim_chunk error: " << dkim_getresultstr(status_);
123 void lib::eom()
125 status_ = dkim_eom(dkim_, nullptr);
127 switch (status_) {
128 case DKIM_STAT_OK:
129 case DKIM_STAT_NOSIG:
130 // all good
131 break;
133 default:
134 LOG(WARNING) << "dkim_eom error: " << dkim_getresultstr(status_);
135 break;
139 //.............................................................................
141 sign::sign(char const* secretkey,
142 char const* selector,
143 char const* domain,
144 body_type typ)
146 // clang-format off
147 dkim_ = dkim_sign(lib_,
148 id_s,
149 nullptr,
150 uc(secretkey),
151 uc(selector),
152 uc(domain),
153 DKIM_CANON_RELAXED,
154 (typ == body_type::binary) ? DKIM_CANON_SIMPLE
155 : DKIM_CANON_RELAXED,
156 DKIM_SIGN_RSASHA256,
158 &status_);
159 // clang-format on
160 CHECK_NOTNULL(dkim_);
161 CHECK_EQ(status_, DKIM_STAT_OK);
164 std::string sign::getsighdr()
166 auto const initial{strlen(DKIM_SIGNHEADER) + 2};
167 unsigned char* buf = nullptr;
168 size_t len = 0;
169 status_ = dkim_getsighdr_d(dkim_, initial, &buf, &len);
170 CHECK_EQ(status_, DKIM_STAT_OK);
171 return std::string(c(buf), len);
174 //.............................................................................
176 void verify::foreach_sig(std::function<void(char const* domain,
177 bool passed,
178 char const* identity,
179 char const* selector,
180 char const* b)> func)
182 int nsigs = 0;
183 DKIM_SIGINFO** sigs = nullptr;
184 status_ = dkim_getsiglist(dkim_, &sigs, &nsigs);
185 if (status_ == DKIM_STAT_INVALID) {
186 LOG(WARNING) << "skipping DKIM sigs";
187 return;
189 CHECK_EQ(status_, DKIM_STAT_OK);
191 iobuffer<u_char> identity(4 * 1024);
193 for (auto i{0}; i < nsigs; ++i) {
194 auto const dom = CHECK_NOTNULL(dkim_sig_getdomain(sigs[i]));
196 auto const flg = dkim_sig_getflags(sigs[i]);
197 if ((flg & DKIM_SIGFLAG_IGNORE) != 0) {
198 LOG(INFO) << "ignoring signature for domain " << dom;
199 continue;
201 if ((flg & DKIM_SIGFLAG_TESTKEY) != 0) {
202 LOG(INFO) << "testkey for domain " << dom;
205 if ((flg & DKIM_SIGFLAG_PROCESSED) == 0) {
206 LOG(INFO) << "ignoring unprocessed sig for domain " << dom;
207 continue;
210 auto const bh = dkim_sig_getbh(sigs[i]);
211 if (bh != DKIM_SIGBH_MATCH) {
212 LOG(INFO) << "body hash mismatch for domain " << dom;
215 auto bits{0u};
216 status_ = dkim_sig_getkeysize(sigs[i], &bits);
217 if (status_ == DKIM_STAT_OK) {
218 if (bits < 1024) {
219 LOG(WARNING) << "keysize " << bits << " too small for domain " << dom;
222 else {
223 LOG(WARNING) << "getkeysize failed for domain " << dom << " with "
224 << dkim_getresultstr(status_);
227 auto const passed =
228 ((flg & DKIM_SIGFLAG_PASSED) != 0) && (bh == DKIM_SIGBH_MATCH);
230 CHECK_EQ(
231 dkim_sig_getidentity(dkim_, sigs[i], identity.data(), identity.size()),
232 DKIM_STAT_OK);
234 auto const selector = dkim_sig_getselector(sigs[i]);
236 auto const b = dkim_sig_gettagvalue(sigs[i], false, uc("b"));
238 func(c(dom), passed, c(identity.data()), c(selector), c(b));
242 verify::verify()
244 dkim_ = CHECK_NOTNULL(dkim_verify(lib_, id_v, nullptr, &status_));
247 bool verify::check()
249 int nsigs = 0;
250 DKIM_SIGINFO** sigs = nullptr;
251 status_ = dkim_getsiglist(dkim_, &sigs, &nsigs);
252 CHECK_EQ(status_, DKIM_STAT_OK);
254 LOG(INFO) << "nsigs == " << nsigs;
256 for (auto i{0}; i < nsigs; ++i) {
257 LOG(INFO) << i << " domain == " << dkim_sig_getdomain(sigs[i]);
258 auto flg = dkim_sig_getflags(sigs[i]);
259 if ((flg & DKIM_SIGFLAG_IGNORE) != 0) {
260 LOG(INFO) << "DKIM_SIGFLAG_IGNORE";
262 if ((flg & DKIM_SIGFLAG_PROCESSED) != 0) {
263 LOG(INFO) << "DKIM_SIGFLAG_PROCESSED";
265 if ((flg & DKIM_SIGFLAG_PASSED) != 0) {
266 LOG(INFO) << "DKIM_SIGFLAG_PASSED";
268 if ((flg & DKIM_SIGFLAG_TESTKEY) != 0) {
269 LOG(INFO) << "DKIM_SIGFLAG_TESTKEY";
271 if ((flg & DKIM_SIGFLAG_NOSUBDOMAIN) != 0) {
272 LOG(INFO) << "DKIM_SIGFLAG_NOSUBDOMAIN";
276 if (nsigs) {
277 auto sig{dkim_getsignature(dkim_)};
278 if (sig) {
280 LOG(INFO) << "dkim_getsignature domain == " << dkim_sig_getdomain(sig);
282 ssize_t msglen;
283 ssize_t canonlen;
284 ssize_t signlen;
286 status_ = dkim_sig_getcanonlen(dkim_, sig, &msglen, &canonlen, &signlen);
288 CHECK_EQ(status_, DKIM_STAT_OK);
290 LOG(INFO) << "msglen == " << msglen;
291 LOG(INFO) << "canonlen == " << canonlen;
292 LOG(INFO) << "signlen == " << signlen;
294 auto nhdrs{0u};
295 status_ = dkim_sig_getsignedhdrs(dkim_, sig, nullptr, 0, &nhdrs);
296 if (status_ != DKIM_STAT_NORESOURCE) {
297 return false;
300 LOG(INFO) << "nhdrs == " << nhdrs;
302 auto constexpr hdr_sz{DKIM_MAXHEADER + 1};
303 auto signedhdrs{std::vector<unsigned char>(nhdrs * hdr_sz, '\0')};
305 status_ =
306 dkim_sig_getsignedhdrs(dkim_, sig, &signedhdrs[0], hdr_sz, &nhdrs);
307 CHECK_EQ(status_, DKIM_STAT_OK);
309 for (auto i{0u}; i < nhdrs; ++i)
310 LOG(INFO) << &signedhdrs[i * hdr_sz];
312 return true;
316 return false;
319 bool verify::sig_syntax(std::string_view sig)
321 return dkim_sig_syntax(dkim_, uc(sig.data()), sig.length()) == DKIM_STAT_OK;
324 } // namespace OpenDKIM