s/qmail 4.3.20
Next generation secure email transport
Loading...
Searching...
No Matches
dkimverify.cpp
Go to the documentation of this file.
1/*****************************************************************************
2*
3* Licensed under the Apache License, Version 2.0 (the "License");
4* you may not use this file except in compliance with the License.
5* You may obtain a copy of the License at
6*
7* http://www.apache.org/licenses/LICENSE-2.0
8*
9* This code incorporates intellectual property owned by Yahoo! and licensed
10* pursuant to the Yahoo! DomainKeys Patent License Agreement.
11*
12* Unless required by applicable law or agreed to in writing, software
13* distributed under the License is distributed on an "AS IS" BASIS,
14* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15* See the License for the specific language governing permissions and
16* limitations under the License.
17*
18* Changes done by ¢feh@fehcom.de obeying the above license
19*
20*****************************************************************************/
21#include <string.h>
22#include <ctype.h>
23#include <assert.h>
24#include <vector>
25#include <algorithm>
26#include "dkim.h"
27#include "dkimverify.h"
28#include "dnsgettxt.h"
29extern "C" {
30#include "dns.h"
31#include "stralloc.h"
32}
33
34/*****************************************************************************
35*
36* Verifying DKIM Ed25519 signatures:
37*
38* The received DKIM header includes two cryptographic relevant informations:
39*
40* a) The 'body hash' => bh=[sha1|sha256]
41* b) The signature => b=[RSA-SHA1|RSA-SHA256|PureEd25519]
42*
43* Several DKIM headers (=signatures) may be present in the email.
44* Here, it is limited to max. Shall we really evaluate all?
45*
46* Caution: Using hybrid signatures, calling the destructor will core dump
47* given EVP_MD_CTX_free() upon the next call of EVP_DigestInit.
48* Using the destructor with EVP_MD_CTX_reset() however works.
49*
50*****************************************************************************/
51
52#define _strnicmp strncasecmp
53#define _stricmp strcasecmp
54#define MAX_SIGNATURES 10 // maximum number of DKIM signatures to process/message
55#define FDLOG stderr /* writing to another FD requires a method */
56
57string SigHdr;
58size_t m_SigHdr;
59
60extern "C" int stralloc_copys(stralloc *,char const *);
61
62int dig_ascii(char *digascii,unsigned const char *digest,const int len)
63{
64 static const char hextab[] = "0123456789abcdef";
65 int j;
66
67 for (j = 0; j < len; j++) {
68 digascii[2 * j] = hextab[(unsigned char)digest[j] >> 4];
69 digascii[2 * j + 1] = hextab[(unsigned char)digest[j] & 0x0f];
70 }
71 digascii[2 * len] = '\0';
72
73 return (2 * j); // 2*len
74}
75
76
77int _DNSGetTXT(const char *szFQDN,char *Buffer,int nBufLen)
78{
79 stralloc out = {0};
80 stralloc sa = {0};
81 Buffer[0] = '\0'; // need to be initialized
82
83 if (!szFQDN) return -1;
84 if (!stralloc_copys(&sa,szFQDN)) return -1;
85
87
88 switch (dns_txt(&out,&sa)) {
89 case -1: return -1;
90 case 0: return 0;
91 }
92
93 if (nBufLen < out.len)
94 return -2;
95
96 if (!stralloc_0(&out)) return -1;
97 memcpy(Buffer,out.s,out.len); // Return-by-value; sigh
98
99 return out.len;
100}
101
102int _DKIM_ReportResult(const char* ResFile,const char* result,const char* reason)
103{
104 int len = 0;
105
106 FILE* out = fopen(ResFile,"wb+");
107 if (out == NULL) return -1;
108
109 if (result) {
110 len = strlen(result);
111 fwrite(result,1,len,out);
112 fwrite("\r",1,1,out);
113 }
114
115 if (reason) {
116 fwrite(reason,1,strlen(reason),out);
117 fwrite("\r",1,1,out);
118 }
119 fclose(out);
120
121 return len;
122}
123
124const char* DKIM_ErrorResult(const int res)
125{
126 const char* errormsg = "";
127
128 switch (res) {
129 case DKIM_FAIL:
130 errormsg = " (verify error: message is suspicious)";
131 break;
132 case DKIM_BAD_SYNTAX:
133 errormsg = " (signature error: could not parse or has bad tags/values)";
134 break;
136 errormsg = " (signature error: RSA/ED25519 verify failed)";
137 break;
139 errormsg = " (signature error: RSA/ED25519 verify failed but testing)";
140 break;
142 errormsg = " (signature error: signature x= value expired)";
143 break;
145 errormsg = " (signature error: selector doesn't parse or contains invalid values)";
146 break;
148 errormsg = " (signature error: selector g= doesn't match i=)";
149 break;
151 errormsg = " (signature error: revoked p= empty)";
152 break;
154 errormsg = " (dns error: selector domain name too long to request)";
155 break;
157 errormsg = " (dns error: temporary dns failure requesting selector)";
158 break;
160 errormsg = " (dns error: permanent dns failure requesting selector)";
161 break;
163 errormsg = " (signature error: selector p= value invalid or wrong format)";
164 break;
166 errormsg = " (process error: no signatures)";
167 break;
169 errormsg = " (process error: no valid signatures)";
170 break;
172 errormsg = " (signature verify error: message body does not hash to bh= value)";
173 break;
175 errormsg = " (signature error: selector h= doesn't match signature a=)";
176 break;
178 errormsg = " (signature error: incompatible v= value)";
179 break;
181 errormsg = " (signature error: not all message's From headers in signature)";
182 break;
184 errormsg = " (internal error: memory allocation failed)";
185 break;
187 errormsg = " (internal error: DKIMContext structure invalid for this operation)";
188 break;
189 case DKIM_NO_SENDER:
190 errormsg = " (signing error: Could not find From: or Sender: header in message)";
191 break;
193 errormsg = " (signing error: Could not parse private key)";
194 break;
196 errormsg = " (signing error: Buffer passed in is not large enough)";
197 break;
198 }
199
200 return errormsg;
201}
202
204{
207
208 m_Hdr_ctx = EVP_MD_CTX_new();
209 m_Bdy_ctx = EVP_MD_CTX_new();
210 m_Msg_ctx = EVP_MD_CTX_new();
211 m_pSelector = NULL;
213 m_nHash = 0;
214 EmptyLineCount = 0;
216}
217
219{
221 EVP_MD_CTX_reset(m_Hdr_ctx);
222 EVP_MD_CTX_reset(m_Bdy_ctx);
223 EVP_MD_CTX_reset(m_Msg_ctx);
224}
225
226inline bool isswsp(char ch)
227{
228 return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
229}
230
232//
233// Parse a DKIM tag-list. Returns true for success
234//
236bool ParseTagValueList(char *tagvaluelist,const char *wanted[],char *values[])
237{
238 char *s = tagvaluelist;
239
240 for (;;) {
241 // skip whitespace
242 while (isswsp(*s))
243 s++;
244
245 // if at the end of the string, return success. Note: this allows a list with no entries
246 if (*s == '\0')
247 return true;
248
249 // get tag name
250 if (!isalpha(*s))
251 return false;
252
253 char *tag = s;
254 do {
255 s++;
256 } while (isalnum(*s) || *s == '-');
257
258 char *endtag = s;
259
260 // skip whitespace before equals
261 while (isswsp(*s))
262 s++;
263
264 // next character must be equals
265 if (*s != '=')
266 return false;
267 s++;
268
269 // null-terminate tag name
270 *endtag = '\0';
271
272 // skip whitespace after equals
273 while (isswsp(*s))
274 s++;
275
276 // get tag value
277 char *value = s;
278
279 while (*s != ';' && ((*s == '\t' || *s == '\r' || *s == '\n') || (*s >= ' ' && *s <= '~')))
280 s++;
281
282 char *e = s;
283
284 // make sure the next character is the null terminator (which means we're done) or a semicolon (not done)
285 bool done = false;
286 if (*s == '\0')
287 done = true;
288 else {
289 if (*s != ';')
290 return false;
291 s++;
292 }
293
294 // skip backwards past any trailing whitespace
295 while (e > value && isswsp(e[-1]))
296 e--;
297
298 // null-terminate tag value
299 *e = '\0';
300
301 // check to see if we want this tag
302 for (unsigned i = 0; wanted[i] != NULL; i++) {
303 if (strcmp(wanted[i],tag) == 0) {
304 // return failure if we already have a value for this tag (duplicates not allowed)
305 if (values[i] != NULL)
306 return false;
307 values[i] = value;
308 break;
309 }
310 }
311
312 if (done)
313 return true;
314 }
315}
317//
318// Convert hex char to value (0-15)
319//
321char Tohex(char ch)
322{
323 if (ch >= '0' && ch <= '9')
324 return (ch - '0');
325 else if (ch >= 'A' && ch <= 'F')
326 return (ch - 'A' + 10);
327 else if (ch >= 'a' && ch <= 'f')
328 return (ch - 'a' + 10);
329 else {
330 assert(0);
331 return 0;
332 }
333}
335//
336// Decode quoted printable string in-place
337//
340{
341 char *s = ptr;
342 while (*s != '\0' && *s != '=')
343 s++;
344
345 if (*s == '\0')
346 return;
347
348 char *d = s;
349 do {
350 if (*s == '=' && isxdigit(s[1]) && isxdigit(s[2])) {
351 *d++ = (Tohex(s[1]) << 4) | Tohex(s[2]);
352 s += 3;
353 } else {
354 *d++ = *s++;
355 }
356 } while (*s != '\0');
357 *d = '\0';
358}
360//
361// Decode base64 string in-place, returns number of bytes output
362//
364unsigned DecodeBase64(char *ptr)
365{
366 static const unsigned char base64_table[256] = {
367 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
368 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,255,255,255,
369 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
370 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
371 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
372 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
373 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
374 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255 };
375
376 unsigned char* s = (unsigned char* )ptr;
377 unsigned char* d = (unsigned char* )ptr;
378 unsigned b64accum = 0;
379 unsigned char b64shift = 0;
380
381 while (*s != '\0') {
382 unsigned char value = base64_table[*s++];
383 if ((signed char) value >= 0) {
384 b64accum = (b64accum << 6) | value;
385 b64shift += 6;
386 if (b64shift >= 8) {
387 b64shift -= 8;
388 *d++ = (b64accum >> b64shift);
389 }
390 }
391 }
392
393 return (char* )d-ptr;
394}
395
397//
398// Match a string with a pattern (used for g= value)
399// Supports a single, optional "*" wildcard character.
400//
402bool WildcardMatch(const char *p, const char *s)
403{
404 // special case: An empty "g=" value never matches any addresses
405 if (*p == '\0')
406 return false;
407
408 const char* wildcard = strchr(p,'*');
409 if (wildcard == NULL) {
410 return strcmp(s, p) == 0;
411 } else {
412 unsigned beforewildcardlen = wildcard - p;
413 unsigned afterwildcardlen = strlen(wildcard + 1);
414 unsigned slen = strlen(s);
415 return (slen >= beforewildcardlen + afterwildcardlen) &&
416 (strncmp(s,p,beforewildcardlen) == 0) && strcmp(s + slen - afterwildcardlen,wildcard + 1) == 0;
417 }
418}
419
421//
422// Parse addresses from a string. Returns true if at least one address found
423//
425bool ParseAddresses(string str,vector<string> &Addresses)
426{
427 char* s = (char* )str.c_str();
428
429 while (*s != '\0') {
430 char* start = s;
431 char* from = s;
432 char* to = s;
433 char* lt = NULL; // pointer to less than character (<) which starts the address if found
434
435 while (*from != '\0') {
436 if (*from == '(') {
437 // skip over comment
438 from++;
439 for (int depth = 1; depth != 0; from++) {
440 if (*from == '\0')
441 break;
442 else if (*from == '(')
443 depth++;
444 else if (*from == ')')
445 depth--;
446 else if (*from == '\\' && from[1] != '\0')
447 from++;
448 }
449 }
450 else if (*from == ')') {
451 // ignore closing parenthesis outside of comment
452 from++;
453 } else if (*from == ',' || *from == ';') {
454 // comma/semicolon ends the address
455 from++;
456 break;
457 }
458 else if (*from == ' ' || *from == '\t' || *from == '\r' || *from == '\n') {
459 // ignore whitespace
460 from++;
461 } else if (*from == '"') {
462 // copy the contents of a quoted string
463 from++;
464 while (*from != '\0') {
465 if (*from == '"') {
466 from++;
467 break;
468 } else if (*from == '\\' && from[1] != '\0')
469 *to++ = *from++;
470 *to++ = *from++;
471 }
472 } else if (*from == '\\' && from[1] != '\0') {
473 // copy quoted-pair
474 *to++ = *from++;
475 *to++ = *from++;
476 } else {
477 // copy any other char
478 *to = *from++;
479 // save pointer to '<' for later...
480 if (*to == '<')
481 lt = to;
482 to++;
483 }
484 }
485
486 *to = '\0';
487
488 // if there's < > get what's inside
489 if (lt != NULL) {
490 start = lt+1;
491 char *gt = strchr(start, '>');
492 if (gt != NULL)
493 *gt = '\0';
494 } else {
495 // look for and strip group name
496 char *colon = strchr(start, ':');
497 if (colon != NULL) {
498 char *at = strchr(start, '@');
499 if (at == NULL || colon < at)
500 start = colon+1;
501 }
502 }
503
504 if (*start != '\0' && strchr(start, '@') != NULL) {
505 Addresses.push_back(start); // save address
506 }
507
508 s = from;
509 }
510
511 return !Addresses.empty();
512}
513
515
517{
519// m_pfnPracticesCallback = NULL;
520 m_HonorBodyLengthTag = false;
521 m_CheckPractices = false;
522// Kai:
523// m_SubjectIsRequired = true;
524 m_SubjectIsRequired = false;
527}
528
530
532//
533// Init - save the options
534//
537{
538 int nRet = CDKIMBase::Init();
539
541 // m_pfnPracticesCallback = pOptions->pfnPracticesCallback;
542
544 m_CheckPractices = pOptions->nCheckPractices != 0;
545// Kai:
546// m_SubjectIsRequired = pOptions->nSubjectRequired == 0;
547 m_SubjectIsRequired = pOptions->nSubjectRequired == 1;
550
551 return nRet;
552}
553
555//
556// GetResults - return the pass/fail/neutral verification result
557//
560{
561 // char mdi[128];
562 // char digi[128];
563
564 ProcessFinal();
565
566 unsigned char *SignMsg;
567 unsigned SuccessCount = 0;
568 int TestingFailures = 0;
569 int RealFailures = 0;
570 int res = 0;
571
572 list<string> SuccessfulDomains; // can contain duplicates
573
574 for (list<SignatureInfo>::iterator i = Signatures.begin(); i != Signatures.end(); ++i) {
575 if (i->Status == DKIM_SUCCESS) {
576 if (!i->BodyHashData.empty()) { // FIRST: Get the body hash
577 unsigned char md[EVP_MAX_MD_SIZE];
578 unsigned len = 0;
579
580 res = EVP_DigestFinal_ex(i->m_Bdy_ctx,md,&len);
581 EVP_MD_CTX_reset(i->m_Bdy_ctx);
582 // dig_ascii(digi,md,32);
583 // dig_ascii(mdi,(unsigned const char *)i->BodyHashData.data(),32);
584
585 if (!res || len != i->BodyHashData.length() || memcmp(i->BodyHashData.data(),md,len) != 0) {
586 // body hash mismatch
587
588 // if the selector is in testing mode...
589 if (i->m_pSelector->Testing) {
590 i->Status = DKIM_SIGNATURE_BAD_BUT_TESTING; // todo: make a new error code for this?
591 TestingFailures++;
592 } else {
593 i->Status = DKIM_BODY_HASH_MISMATCH;
594 RealFailures++;
595 }
596 continue; // next signature
597 }
598 } else {
599 // hash CRLF separating the body from the signature
600 i->Hash("\r\n",2);
601 }
602
603 // SECOND: Fetch the signature
604
605 string sSignedSig = i->Header;
606 string sSigValue = sSignedSig.substr(sSignedSig.find(':') + 1);
607
608 static const char* tags[] = {"b",NULL};
609 char* values[sizeof(tags)/sizeof(tags[0])] = {NULL};
610
611 char* pSigValue = (char* ) sSigValue.c_str(); // our signature
612 if (ParseTagValueList(pSigValue,tags,values) && values[0] != NULL) {
613 sSignedSig.erase(15 + values[0] - pSigValue,strlen(values[0]));
614 }
615
616 if (i->HeaderCanonicalization == DKIM_CANON_RELAXED) {
617 sSignedSig = RelaxHeader(sSignedSig);
618 }
619 else if (i->HeaderCanonicalization == DKIM_CANON_NOWSP) {
620 RemoveSWSP(sSignedSig);
621 // convert "DKIM-Signature" to lower case
622 sSignedSig.replace(0,14,"dkim-signature",14);
623 }
624
625 i->Hash(sSignedSig.c_str(),sSignedSig.length()); // include generated DKIM signature header
626 assert(i->m_pSelector != NULL);
627
628 if (EVP_PKEY_base_id(i->m_pSelector->PublicKey) != EVP_PKEY_ED25519)
629 res = EVP_VerifyFinal(i->m_Hdr_ctx,(unsigned char *)i->SignatureData.data(),i->SignatureData.length(),i->m_pSelector->PublicKey);
630 else if (EVP_PKEY_base_id(i->m_pSelector->PublicKey) == EVP_PKEY_ED25519) {
631 EVP_DigestVerifyInit(i->m_Msg_ctx,NULL,NULL,NULL,i->m_pSelector->PublicKey); // late initialization
632
633 SignMsg = (unsigned char *) SigHdr.data();
634 res = EVP_DigestVerify(i->m_Msg_ctx,(unsigned char *)i->SignatureData.data(),(size_t)i->SignatureData.length(),
635 SignMsg,m_SigHdr);
636 }
637
638 if (res == 1) {
639 if (i->UnverifiedBodyCount == 0)
640 i->Status = DKIM_SUCCESS;
641 else
642 i->Status = DKIM_SUCCESS_BUT_EXTRA;
643 SuccessCount++;
644 SuccessfulDomains.push_back(i->Domain);
645 } else {
646 // if the selector is in testing mode...
647 if (i->m_pSelector->Testing) {
649 TestingFailures++;
650 } else {
651 i->Status = DKIM_SIGNATURE_BAD;
652 RealFailures++;
653 }
654 }
655 } else if (i->Status == DKIM_SELECTOR_GRANULARITY_MISMATCH ||
656 i->Status == DKIM_SELECTOR_ALGORITHM_MISMATCH ||
657 i->Status == DKIM_SELECTOR_KEY_REVOKED) {
658 // treat these as failures
659 // todo: maybe see if the selector is in testing mode?
660 RealFailures++;
661 }
662 } // loop over signature infos done
663
664
665 // get the From address's domain if we might need it
666 string sFromDomain;
667 if (SuccessCount > 0 || m_CheckPractices) {
668 for (list<string>::iterator i = HeaderList.begin(); i != HeaderList.end(); ++i) {
669 if (_strnicmp(i->c_str(),"From",4) == 0) {
670 // skip over whitespace between the header name and :
671 const char* s = i->c_str() + 4;
672 while (*s == ' ' || *s == '\t')
673 s++;
674 if (*s == ':') {
675 vector<string> Addresses;
676 if (ParseAddresses(s + 1, Addresses)) {
677 unsigned atpos = Addresses[0].find('@');
678 sFromDomain = Addresses[0].substr(atpos + 1);
679 break;
680 }
681 }
682 }
683 }
684 }
685
686 // if a signature from the From domain verified successfully, return success now
687 // without checking the author domain signing practices
688 if (SuccessCount > 0 && !sFromDomain.empty()) {
689 for (list<string>::iterator i = SuccessfulDomains.begin(); i != SuccessfulDomains.end(); ++i) {
690 // see if the successful domain is the same as or a parent of the From domain
691 if (i->length() > sFromDomain.length())
692 continue;
693 if (_stricmp(i->c_str(),sFromDomain.c_str() + sFromDomain.length() - i->length()) != 0)
694 continue;
695 if (i->length() == sFromDomain.length() || sFromDomain.c_str()[sFromDomain.length() - i->length() - 1] == '.') {
696 return SuccessCount == Signatures.size() ? DKIM_SUCCESS : DKIM_PARTIAL_SUCCESS;
697 }
698 }
699 }
700
701 /* Removed obsolete ADSP check */
702
703 // return neutral for everything else
704 return DKIM_NEUTRAL;
705}
706
708//
709// Hash - update the hash or update the Ed25519 signature input
710//
712void SignatureInfo::Hash(const char* szBuffer,unsigned nBufLength,bool IsBody)
713{
714#if 0
716 if(nBufLength == 2 && szBuffer[0] == '\r' && szBuffer[1] == '\n')
717 {
718 printf("[CRLF]\n");
719 } else {
720 char* szDbg = new char[nBufLength+1];
721 strncpy(szDbg, szBuffer, nBufLength);
722 szDbg[nBufLength] = '\0';
723 printf("[%s]\n", szDbg);
724 }
726#endif
727
728 if (IsBody && BodyLength != (unsigned) -1) { // trick: 2's complement
729 VerifiedBodyCount += nBufLength;
731 nBufLength = BodyLength - (VerifiedBodyCount - nBufLength);
734 if (nBufLength == 0) return;
735 }
736 }
737
738 if (IsBody && !BodyHashData.empty()) {
739 EVP_DigestUpdate(m_Bdy_ctx,szBuffer,nBufLength);
740 } else {
741 EVP_VerifyUpdate(m_Hdr_ctx,szBuffer,nBufLength);
742 SigHdr.append(szBuffer,nBufLength);
743 m_SigHdr += nBufLength;
744 }
745
747 CanonicalizedData.append(szBuffer,nBufLength);
748 }
749}
750
751
753//
754// ProcessHeaders - Look for DKIM-Signatures and start processing them
755// look for DKIM-Signature header(s)
756//
759{
760 for (list<string>::iterator i = HeaderList.begin(); i != HeaderList.end(); ++i) {
761 if (strlen(i->c_str()) < 14) continue; // too short
762 if (_strnicmp(i->c_str(),"DKIM-Signature",14) == 0) {
763 // skip over whitespace between the header name and :
764 const char *s = i->c_str() + 14;
765 while (*s == ' ' || *s == '\t')
766 s++;
767 if (*s == ':') {
768 // found
770 sig.Status = ParseDKIMSignature(*i,sig);
771 Signatures.push_back(sig); // save signature
772
773 if (Signatures.size() >= MAX_SIGNATURES)
774 break;
775 }
776 }
777 }
778
779 if (Signatures.empty())
780 return DKIM_NO_SIGNATURES;
781
782 bool ValidSigFound = false;
783
784 for (list<SignatureInfo>::iterator s = Signatures.begin(); s != Signatures.end(); ++s) {
785 SignatureInfo &sig = *s;
786 if (sig.Status != DKIM_SUCCESS) continue;
787 SelectorInfo &sel = GetSelector(sig.Selector,sig.Domain);
788 sig.m_pSelector = &sel;
789
790 if (sel.Status != DKIM_SUCCESS) {
791 sig.Status = sel.Status;
792 } else {
793 // check the granularity
794 if (!WildcardMatch(sel.Granularity.c_str(),sig.IdentityLocalPart.c_str()))
795 sig.Status = DKIM_SELECTOR_GRANULARITY_MISMATCH; // this error causes the signature to fail
796
797 // check the hash algorithm
798 if ((sig.m_nHash == DKIM_HASH_SHA1 && !sel.AllowSHA1) ||
799 (sig.m_nHash == DKIM_HASH_SHA256 && !sel.AllowSHA256))
800 sig.Status = DKIM_SELECTOR_ALGORITHM_MISMATCH; // causes signature to fail
801
802 // check for same domain
803 if (sel.SameDomain && _stricmp(sig.Domain.c_str(),sig.IdentityDomain.c_str()) != 0)
805 }
806
807 if (sig.Status != DKIM_SUCCESS) continue;
808
809 // initialize the hashes
810 if (sig.m_nHash == DKIM_HASH_SHA1) {
811 EVP_VerifyInit_ex(sig.m_Hdr_ctx,EVP_sha1(),NULL);
812 EVP_DigestInit_ex(sig.m_Bdy_ctx,EVP_sha1(),NULL);
813 }
814 if (sig.m_nHash == DKIM_HASH_SHA256) {
815 EVP_VerifyInit_ex(sig.m_Hdr_ctx,EVP_sha256(),NULL);
816 EVP_DigestInit_ex(sig.m_Bdy_ctx,EVP_sha256(),NULL);
817 SigHdr.assign("");
818 m_SigHdr = 0;
819 }
820
821 // compute the hash of the header
822 vector<list<string>::reverse_iterator> used;
823
824 for (vector<string>::iterator x = sig.SignedHeaders.begin(); x != sig.SignedHeaders.end(); ++x) {
825 list<string>::reverse_iterator i;
826 for (i = HeaderList.rbegin(); i != HeaderList.rend(); ++i) {
827 if (_strnicmp(i->c_str(),x->c_str(),x->length()) == 0) {
828 // skip over whitespace between the header name and :
829 const char* s = i->c_str()+x->length();
830 while (*s == ' ' || *s == '\t')
831 s++;
832 if (*s == ':' && find(used.begin(),used.end(),i) == used.end())
833 break;
834 }
835 }
836
837 if (i != HeaderList.rend()) {
838 used.push_back(i);
839
840 // hash this header
842 sig.Hash(i->c_str(),i->length());
843 } else if (sig.HeaderCanonicalization == DKIM_CANON_RELAXED) {
844 string sTemp = RelaxHeader(*i);
845 sig.Hash(sTemp.c_str(),sTemp.length());
846 } else if (sig.HeaderCanonicalization == DKIM_CANON_NOWSP) {
847 string sTemp = *i;
848 RemoveSWSP(sTemp);
849
850 // convert characters before ':' to lower case
851 for (char* s = (char*)sTemp.c_str(); *s != '\0' && *s != ':'; s++) {
852 if (*s >= 'A' && *s <= 'Z')
853 *s += 'a' - 'A';
854 }
855 sig.Hash(sTemp.c_str(),sTemp.length());
856 }
857 sig.Hash("\r\n",2);
858 }
859 }
860
861 if (sig.BodyHashData.empty()) {
862 // hash CRLF separating headers from body
863 sig.Hash("\r\n",2);
864 }
865
867 // make sure the message has no unsigned From headers
868 list<string>::reverse_iterator i;
869 for (i = HeaderList.rbegin(); i != HeaderList.rend(); ++i) {
870 if (_strnicmp(i->c_str(),"From",4) == 0) {
871 // skip over whitespace between the header name and :
872 const char *s = i->c_str() + 4;
873 while (*s == ' ' || *s == '\t')
874 s++;
875 if (*s == ':') {
876 if (find(used.begin(),used.end(),i) == used.end()) {
877 // this From header was not signed
878 break;
879 }
880 }
881 }
882 }
883 if (i != HeaderList.rend()) {
884 // treat signature as invalid
886 continue;
887 }
888 }
889
890 ValidSigFound = true;
891 }
892
893 if (!ValidSigFound)
895
896 return DKIM_SUCCESS;
897}
898
899
901//
902// Strictly parse an unsigned integer. Don't allow spaces, negative sign,
903// 0x prefix, etc. Values greater than 2^32-1 are capped at 2^32-1
904//
906bool ParseUnsigned(const char *s, unsigned *result)
907{
908 unsigned temp = 0, last = 0;
909 bool overflowed = false;
910
911 do {
912 if (*s < '0' || *s > '9')
913 return false; // returns false for an initial '\0'
914
915 temp = temp * 10 + (*s - '0');
916 if (temp < last)
917 overflowed = true;
918 last = temp;
919
920 s++;
921 } while (*s != '\0');
922
923 *result = overflowed ? -1 : temp;
924 return true;
925}
926
927
929//
930// ParseDKIMSignature - Parse a DKIM-Signature header field
931//
933int CDKIMVerify::ParseDKIMSignature(const string& sHeader,SignatureInfo &sig)
934{
935 // for strtok_r()
936 char *saveptr;
937
938 // save header for later
939 sig.Header = sHeader;
940
941 string sValue = sHeader.substr(sHeader.find(':') + 1);
942
943 static const char *tags[] = {"v","a","b","d","h","s","c","i","l","q","t","x","bh",NULL};
944 char *values[sizeof(tags)/sizeof(tags[0])] = {NULL};
945
946 if (!ParseTagValueList((char*) sValue.c_str(),tags,values))
947 return DKIM_BAD_SYNTAX;
948
949 // check signature version
950 if (values[0] == NULL) return DKIM_BAD_SYNTAX;
951
952 // signature MUST have a=, b=, d=, h=, s=
953 if (values[1] == NULL || values[2] == NULL || values[3] == NULL || values[4] == NULL || values[5] == NULL)
954 return DKIM_BAD_SYNTAX;
955
956 // algorithm ('a=') can be "rsa-sha1" or "rsa-sha256" or "ed25519"
957 if (strcmp(values[1],"rsa-sha1") == 0) {
959 } else if (strcmp(values[1],"rsa-sha256") == 0) {
961 } else if (strcmp(values[1],"ed25519-sha256") == 0) {
963 } else {
964 return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for unknown algorithm
965 }
966
967 // make sure the signature data is not empty: b=[...]
968 unsigned SigDataLen = DecodeBase64(values[2]);
969
970 if (SigDataLen == 0)
971 return DKIM_BAD_SYNTAX;
972
973 sig.SignatureData.assign(values[2],SigDataLen);
974
975 // check for body hash in DKIM header: bh=[...];
976 unsigned BodyHashLen = DecodeBase64(values[12]);
977 if (BodyHashLen == 0) return DKIM_BAD_SYNTAX;
978 sig.BodyHashData.assign(values[12],BodyHashLen);
979
980 // domain must not be empty
981 if (*values[3] == '\0')
982 return DKIM_BAD_SYNTAX;
983 sig.Domain = values[3];
984
985 // signed headers must not be empty (more verification is done later)
986 if (*values[4] == '\0')
987 return DKIM_BAD_SYNTAX;
988
989 // selector must not be empty
990 if (*values[5] == '\0')
991 return DKIM_BAD_SYNTAX;
992 sig.Selector = values[5];
993
994 // canonicalization
995 if (values[6] == NULL) {
997 } else {
998 char* slash = strchr(values[6],'/');
999 if (slash != NULL)
1000 *slash = '\0';
1001
1002 if (strcmp(values[6],"simple") == 0)
1004 else if (strcmp(values[6],"relaxed") == 0)
1006 else
1007 return DKIM_BAD_SYNTAX;
1008
1009 if (slash == NULL || strcmp(slash + 1,"simple") == 0)
1011 else if (strcmp(slash + 1,"relaxed") == 0)
1013 else
1014 return DKIM_BAD_SYNTAX;
1015 }
1016
1017 // identity
1018 if (values[7] == NULL) {
1019 sig.IdentityLocalPart.erase();
1020 sig.IdentityDomain = sig.Domain;
1021 } else {
1022 // quoted-printable decode the value
1023 DecodeQuotedPrintable(values[7]);
1024
1025 // must have a '@' separating the local part from the domain
1026 char* at = strchr(values[7],'@');
1027 if (at == NULL)
1028 return DKIM_BAD_SYNTAX;
1029 *at = '\0';
1030
1031 char* ilocalpart = values[7];
1032 char* idomain = at + 1;
1033
1034 // i= domain must be the same as or a subdomain of the d= domain
1035 int idomainlen = strlen(idomain);
1036 int ddomainlen = strlen(values[3]);
1037
1038 // todo: maybe create a new error code for invalid identity domain
1039 if (idomainlen < ddomainlen)
1040 return DKIM_BAD_SYNTAX;
1041 if (_stricmp(idomain + idomainlen - ddomainlen,values[3]) != 0)
1042 return DKIM_BAD_SYNTAX;
1043 if (idomainlen > ddomainlen && idomain[idomainlen - ddomainlen - 1] != '.')
1044 return DKIM_BAD_SYNTAX;
1045
1046 sig.IdentityLocalPart = ilocalpart;
1047 sig.IdentityDomain = idomain;
1048 }
1049
1050 // body count
1051 if (values[8] == NULL || !m_HonorBodyLengthTag) {
1052 sig.BodyLength = (unsigned) -1;
1053 } else {
1054 if (!ParseUnsigned(values[8],&sig.BodyLength))
1055 return DKIM_BAD_SYNTAX;
1056 }
1057
1058 // query methods
1059 if (values[9] != NULL) {
1060 // make sure "dns" is in the list
1061 bool HasDNS = false;
1062 char* s = strtok_r(values[9],":",&saveptr);
1063 while (s != NULL) {
1064 if (strncmp(s,"dns",3) == 0 && (s[3] == '\0' || s[3] == '/')) {
1065 HasDNS = true;
1066 break;
1067 }
1068 s = strtok_r(NULL,": \t",&saveptr); /* FIXME */
1069// s = strtok_r(NULL,": ",&saveptr); /* FIXME */
1070 }
1071 if (!HasDNS)
1072 return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for unknown query method
1073 }
1074
1075 // signature time
1076 unsigned SignedTime = -1;
1077 if (values[10] != NULL) {
1078 if (!ParseUnsigned(values[10],&SignedTime))
1079 return DKIM_BAD_SYNTAX;
1080 }
1081
1082 // expiration time
1083 if (values[11] == NULL) {
1084 sig.ExpireTime = (unsigned) -1; // common trick; feh
1085 } else {
1086 if (!ParseUnsigned(values[11],&sig.ExpireTime))
1087 return DKIM_BAD_SYNTAX;
1088
1089 if (sig.ExpireTime != (unsigned) -1) {
1090 // the value of x= MUST be greater than the value of t= if both are present
1091 if (SignedTime != (unsigned) -1 && sig.ExpireTime <= SignedTime)
1092 return DKIM_BAD_SYNTAX;
1093
1094 // todo: if possible, use the received date/time instead of the current time
1095 unsigned curtime = time(NULL);
1096 if (curtime > sig.ExpireTime)
1098 }
1099 }
1100
1101 // parse the signed headers list
1102 bool HasFrom = false, HasSubject = false;
1103 RemoveSWSP(values[4]); // header names shouldn't have spaces in them so this should be ok...
1104 char* s = strtok_r(values[4],":",&saveptr);
1105 while (s != NULL) {
1106 if (_stricmp(s,"From") == 0)
1107 HasFrom = true;
1108 else if (_stricmp(s,"Subject") == 0)
1109 HasSubject = true;
1110
1111 sig.SignedHeaders.push_back(s);
1112 s = strtok_r(NULL,":",&saveptr);
1113 }
1114
1115 if (!HasFrom)
1116 return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for h= missing From
1117 if (m_SubjectIsRequired && !HasSubject)
1118 return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for h= missing Subject
1119
1120 return DKIM_SUCCESS;
1121}
1122
1124//
1125// ProcessBody - Process message body data
1126//
1128int CDKIMVerify::ProcessBody(char* szBuffer,int nBufLength,bool bEOF)
1129{
1130 bool MoreBodyNeeded = false;
1131
1132 for (list<SignatureInfo>::iterator i = Signatures.begin(); i != Signatures.end(); ++i) {
1133 if (i->Status == DKIM_SUCCESS) {
1134 if (i->BodyCanonicalization == DKIM_CANON_SIMPLE) {
1135 if (nBufLength > 0) {
1136 while (i->EmptyLineCount > 0) {
1137 i->Hash("\r\n",2,true);
1138 i->EmptyLineCount--;
1139 }
1140 i->Hash(szBuffer,nBufLength,true);
1141 i->Hash("\r\n",2,true);
1142 } else {
1143 i->EmptyLineCount++;
1144 if (bEOF)
1145 i->Hash("\r\n",2,true);
1146 }
1147 } else if (i->BodyCanonicalization == DKIM_CANON_RELAXED) {
1148 CompressSWSP(szBuffer, nBufLength);
1149 if (nBufLength > 0) {
1150 while (i->EmptyLineCount > 0) {
1151 i->Hash("\r\n",2,true);
1152 i->EmptyLineCount--;
1153 }
1154 i->Hash(szBuffer,nBufLength,true);
1155 if (!bEOF)
1156 i->Hash("\r\n",2,true);
1157 } else i->EmptyLineCount++;
1158 } else if (i->BodyCanonicalization == DKIM_CANON_NOWSP) {
1159 RemoveSWSP(szBuffer,nBufLength);
1160 i->Hash(szBuffer,nBufLength,true);
1161 }
1162
1163 if (i->UnverifiedBodyCount == 0)
1164 MoreBodyNeeded = true;
1165 }
1166 }
1167
1168 if (!MoreBodyNeeded)
1169 return DKIM_FINISHED_BODY;
1170
1171 return DKIM_SUCCESS;
1172}
1173
1174SelectorInfo::SelectorInfo(const string &sSelector,const string &sDomain) : Domain(sDomain),Selector(sSelector)
1175{
1176 AllowSHA1 = true;
1177 AllowSHA256 = true;
1178 PublicKey = NULL;
1179 Testing = false;
1180 SameDomain = false;
1182}
1183
1185{
1186 if (PublicKey != NULL) {
1187 EVP_PKEY_free(PublicKey);
1188 }
1189}
1190
1192//
1193// Parse - Parse a DKIM selector from DNS data
1194//
1196int SelectorInfo::Parse(char* Buffer)
1197{
1198 // for strtok_r()
1199 char *saveptr;
1200 char *PubKeyBase64; /*- public key Base64 encoded */
1201 char ed25519PubKey[61];
1202
1203 static const char *tags[] = {"v","g","h","k","p","s","t","n",NULL}; // 0, 1, 2, 3, 4
1204 char *values[sizeof(tags)/sizeof(tags[0])] = {NULL};
1205
1206 ParseTagValueList(Buffer,tags,values);
1207
1208 // return DKIM_SELECTOR_INVALID;
1209 if (values[0] != NULL) {
1210 // make sure the version is "DKIM1"
1211 if (strcmp(values[0],"DKIM1") != 0)
1212 return DKIM_SELECTOR_INVALID; // todo: maybe create a new error code for unsupported selector version
1213
1214 // make sure v= is the first tag in the response // todo: maybe don't enforce this, it seems unnecessary
1215 for (unsigned j = 1; j < sizeof(values)/sizeof(values[0]); j++) {
1216 if (values[j] != NULL && values[j] < values[0]) {
1217 return DKIM_SELECTOR_INVALID;
1218 }
1219 }
1220 }
1221
1222 // selector MUST have p= tag
1223 if (values[4] == NULL)
1224 return DKIM_SELECTOR_INVALID;
1225
1226 PubKeyBase64 = values[4]; // gotcha
1227
1228 // granularity -- [g= ... ]
1229 if (values[1] == NULL)
1230 Granularity = "*";
1231 else
1232 Granularity = values[1];
1233
1234 // hash algorithm -- [h=sha1|sha256] (not required)
1235 if (values[2] == NULL) {
1236 AllowSHA1 = true;
1237 AllowSHA256 = true;
1238 } else {
1239 // MUST include "sha1" or "sha256"
1240 char* s = strtok_r(values[2],":",&saveptr);
1241 while (s != NULL) {
1242 if (strcmp(s,"sha1") == 0)
1243 { AllowSHA1 = true; AllowSHA256 = false; }
1244 else if (strcmp(s,"sha256") == 0)
1245 { AllowSHA256 = true; AllowSHA1 = false; }
1246 s = strtok_r(NULL,":",&saveptr);
1247 }
1248 if (!(AllowSHA1 || AllowSHA256))
1249 return DKIM_SELECTOR_INVALID; // todo: maybe create a new error code for unsupported hash algorithm
1250 }
1251
1252 // key type -- [k=rsa|ed25519] (not required)
1253 if (values[3] != NULL) {
1254 // key type MUST be "rsa" or "ed25519"
1255 if (strcmp(values[3],"rsa") != 0 && strcmp(values[3],"ed25519") != 0) // none of either
1256 return DKIM_SELECTOR_INVALID;
1257 if (strcmp(values[3],"ed25519") == 0) {
1258 AllowSHA1 = false;
1259 AllowSHA256 = true;
1260 strcpy(ed25519PubKey,"MCowBQYDK2VwAyEA");
1261 /*
1262 * rfc8463
1263 * since Ed25519 public keys are 256 bits long,
1264 * the base64-encoded key is only 44 octets
1265 */
1266 if (strlen(values[4]) > 44)
1268 strcat(ed25519PubKey,values[4]);
1269 PubKeyBase64 = ed25519PubKey;
1270 }
1271 }
1272
1273 // service type -- [s= ...] (not required)
1274 if (values[5] != NULL) {
1275 // make sure "*" or "email" is in the list
1276 bool ServiceTypeMatch = false;
1277 char* s = strtok_r(values[5],":",&saveptr);
1278 while (s != NULL) {
1279 if (strcmp(s, "*") == 0 || strcmp(s,"email") == 0) {
1280 ServiceTypeMatch = true;
1281 break;
1282 }
1283 s = strtok_r(NULL,":",&saveptr);
1284 }
1285 if (!ServiceTypeMatch)
1286 return DKIM_SELECTOR_INVALID;
1287 }
1288
1289 // flags -- [t= ...] (not required)
1290 if (values[6] != NULL) {
1291 char *s = strtok_r(values[6],":",&saveptr);
1292 while (s != NULL) {
1293 if (strcmp(s,"y") == 0) {
1294 Testing = true;
1295 } else if (strcmp(s,"s") == 0) {
1296 SameDomain = true;
1297 }
1298 s = strtok_r(NULL,":",&saveptr);
1299 }
1300 }
1301
1302 // public key data
1303 unsigned PublicKeyLen = DecodeBase64(PubKeyBase64);
1304
1305 if (PublicKeyLen == 0) {
1306 return DKIM_SELECTOR_KEY_REVOKED; // this error causes the signature to fail
1307 } else {
1308 const unsigned char *PublicKeyData = (unsigned char* )PubKeyBase64; // 0-terminated
1309
1310 EVP_PKEY *pkey = d2i_PUBKEY(NULL,&PublicKeyData,PublicKeyLen); /* retrieve and return PubKey from data */
1311
1312 if (pkey == NULL)
1314
1315 // make sure public key is the correct type (we only support rsa & ed25519)
1316 if ((EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) ||
1317 (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA2) ||
1318 (EVP_PKEY_base_id(pkey) == EVP_PKEY_ED25519)) {
1319 PublicKey = pkey;
1320 } else {
1321 EVP_PKEY_free(pkey);
1323 }
1324 }
1325
1326 return DKIM_SUCCESS;
1327}
1328
1330//
1331// GetSelector - Get a DKIM selector for a domain
1332//
1334SelectorInfo& CDKIMVerify::GetSelector(const string &sSelector,const string &sDomain)
1335{
1336 // see if we already have this selector
1337 for (list<SelectorInfo>::iterator i = Selectors.begin(); i != Selectors.end(); ++i) {
1338 if (_stricmp(i->Selector.c_str(),sSelector.c_str()) == 0 && _stricmp(i->Domain.c_str(),sDomain.c_str()) == 0) {
1339 return *i;
1340 }
1341 }
1342
1343 Selectors.push_back(SelectorInfo(sSelector,sDomain));
1344 SelectorInfo& sel = Selectors.back();
1345
1346 string sFQDN = sSelector;
1347 sFQDN += "._domainkey.";
1348 sFQDN += sDomain;
1349
1350 const int BufLen = 1024;
1351 char Buffer[BufLen];
1352
1353 int DNSResult;
1354
1355 if (m_pfnSelectorCallback) {
1356 DNSResult = m_pfnSelectorCallback(sFQDN.c_str(),Buffer,BufLen);
1357 } else
1358 DNSResult = _DNSGetTXT(sFQDN.c_str(),Buffer,BufLen);
1359
1360// Buffer++; BufLen--;
1361
1362 switch (DNSResult) {
1363 case -1: case -2: case -3: case -5: sel.Status = DKIM_SELECTOR_DNS_TEMP_FAILURE; break;
1364 case 0: case -6: sel.Status = DKIM_SELECTOR_DNS_PERM_FAILURE; break;
1365 default: sel.Status = sel.Parse(Buffer);
1366 }
1367
1368 return sel;
1369}
1370
1372//
1373// GetDetails - Get DKIM verification details (per signature)
1374//
1376int CDKIMVerify::GetDetails(int* nSigCount,DKIMVerifyDetails** pDetails)
1377{
1378 Details.clear();
1379
1380 for (list < SignatureInfo>::iterator i = Signatures.begin(); i != Signatures.end(); ++i) {
1382 d.szSignature = (char* )i->Header.c_str();
1383 d.szSignatureDomain = (char* )i->Domain.c_str();
1384 d.szIdentityDomain = (char* )i->IdentityDomain.c_str();
1385 d.szCanonicalizedData = (char* )i->CanonicalizedData.c_str();
1386 d.nResult = i->Status;
1387 Details.push_back(d);
1388 }
1389
1390 *nSigCount = Details.size();
1391 *pDetails = (*nSigCount != 0) ? &Details[0] : NULL;
1392
1393 return DKIM_SUCCESS;
1394}
static string RelaxHeader(const string &sHeader)
Definition: dkimbase.cpp:293
int ProcessFinal(void)
Definition: dkimbase.cpp:167
static void CompressSWSP(char *pBuffer, int &nBufLength)
Definition: dkimbase.cpp:235
static void RemoveSWSP(char *szBuffer)
Definition: dkimbase.cpp:214
int Init(void)
Definition: dkimbase.cpp:49
list< string > HeaderList
Definition: dkimbase.h:75
DKIMDNSCALLBACK m_pfnSelectorCallback
Definition: dkimverify.h:133
bool m_CheckPractices
Definition: dkimverify.h:137
bool m_SubjectIsRequired
Definition: dkimverify.h:138
SelectorInfo & GetSelector(const string &sSelector, const string &sDomain)
vector< DKIMVerifyDetails > Details
Definition: dkimverify.h:142
int GetResults(void)
Definition: dkimverify.cpp:559
bool m_AllowUnsignedFromHeaders
Definition: dkimverify.h:140
bool m_SaveCanonicalizedData
Definition: dkimverify.h:139
int GetDetails(int *nSigCount, DKIMVerifyDetails **pDetails)
int ParseDKIMSignature(const string &sHeader, SignatureInfo &sig)
Definition: dkimverify.cpp:933
list< SelectorInfo > Selectors
Definition: dkimverify.h:131
virtual int ProcessHeaders(void)
Definition: dkimverify.cpp:758
virtual int ProcessBody(char *szBuffer, int nBufLength, bool bEOF)
bool m_HonorBodyLengthTag
Definition: dkimverify.h:136
list< SignatureInfo > Signatures
Definition: dkimverify.h:130
int Parse(char *Buffer)
bool Testing
Definition: dkimverify.h:57
EVP_PKEY * PublicKey
Definition: dkimverify.h:56
string Granularity
Definition: dkimverify.h:53
bool AllowSHA256
Definition: dkimverify.h:55
bool SameDomain
Definition: dkimverify.h:58
bool AllowSHA1
Definition: dkimverify.h:54
SelectorInfo(const string &sSelector, const string &sDomain)
string Selector
Definition: dkimverify.h:76
string SignatureData
Definition: dkimverify.h:77
EVP_MD_CTX * m_Hdr_ctx
Definition: dkimverify.h:91
vector< string > SignedHeaders
Definition: dkimverify.h:82
unsigned EmptyLineCount
Definition: dkimverify.h:99
unsigned HeaderCanonicalization
Definition: dkimverify.h:84
unsigned ExpireTime
Definition: dkimverify.h:86
EVP_MD_CTX * m_Bdy_ctx
Definition: dkimverify.h:92
bool m_SaveCanonicalizedData
Definition: dkimverify.h:100
unsigned UnverifiedBodyCount
Definition: dkimverify.h:89
string Domain
Definition: dkimverify.h:75
unsigned BodyCanonicalization
Definition: dkimverify.h:85
string IdentityLocalPart
Definition: dkimverify.h:79
SignatureInfo(bool SaveCanonicalizedData)
Definition: dkimverify.cpp:203
void Hash(const char *szBuffer, unsigned nBufLength, bool IsBody=false)
Definition: dkimverify.cpp:712
string CanonicalizedData
Definition: dkimverify.h:81
EVP_MD_CTX * m_Msg_ctx
Definition: dkimverify.h:93
string BodyHashData
Definition: dkimverify.h:78
string IdentityDomain
Definition: dkimverify.h:80
string Header
Definition: dkimverify.h:73
unsigned VerifiedBodyCount
Definition: dkimverify.h:88
unsigned BodyLength
Definition: dkimverify.h:83
SelectorInfo * m_pSelector
Definition: dkimverify.h:95
#define DKIM_SUCCESS_BUT_EXTRA
Definition: dkim.h:80
#define DKIM_BUFFER_TOO_SMALL
Definition: dkim.h:72
#define DKIM_PARTIAL_SUCCESS
Definition: dkim.h:78
#define DKIM_SELECTOR_PUBLIC_KEY_INVALID
Definition: dkim.h:61
#define DKIM_BODY_HASH_MISMATCH
Definition: dkim.h:64
#define DKIM_SELECTOR_KEY_REVOKED
Definition: dkim.h:57
#define DKIM_CANON_SIMPLE
Definition: dkim.h:39
#define DKIM_SIGNATURE_BAD_BUT_TESTING
Definition: dkim.h:53
#define DKIM_STAT_INCOMPAT
Definition: dkim.h:66
#define DKIM_NO_VALID_SIGNATURES
Definition: dkim.h:63
#define DKIM_NO_SENDER
Definition: dkim.h:70
#define DKIM_NEUTRAL
Definition: dkim.h:79
#define DKIM_SELECTOR_ALGORITHM_MISMATCH
Definition: dkim.h:65
#define DKIM_CANON_NOWSP
Definition: dkim.h:40
#define DKIM_INVALID_CONTEXT
Definition: dkim.h:69
#define DKIM_SELECTOR_DNS_TEMP_FAILURE
Definition: dkim.h:59
#define DKIM_OUT_OF_MEMORY
Definition: dkim.h:68
#define DKIM_BAD_SYNTAX
Definition: dkim.h:51
#define DKIM_SELECTOR_GRANULARITY_MISMATCH
Definition: dkim.h:56
#define DKIM_SIGNATURE_BAD
Definition: dkim.h:52
#define DKIM_CANON_RELAXED
Definition: dkim.h:41
#define DKIM_SIGNATURE_EXPIRED
Definition: dkim.h:54
#define DKIM_BAD_PRIVATE_KEY
Definition: dkim.h:71
#define DKIM_UNSIGNED_FROM
Definition: dkim.h:67
#define DKIM_FINISHED_BODY
Definition: dkim.h:77
#define DKIM_FAIL
Definition: dkim.h:50
#define DKIM_NO_SIGNATURES
Definition: dkim.h:62
#define DKIM_SELECTOR_INVALID
Definition: dkim.h:55
#define DKIM_SELECTOR_DNS_PERM_FAILURE
Definition: dkim.h:60
#define DKIM_HASH_SHA1
Definition: dkim.h:32
#define DKIM_HASH_SHA256
Definition: dkim.h:33
#define DKIM_SELECTOR_DOMAIN_NAME_TOO_LONG
Definition: dkim.h:58
#define DKIM_SUCCESS
Definition: dkim.h:49
#define _stricmp
Definition: dkimsign.cpp:24
#define _strnicmp
Definition: dkimsign.cpp:23
int stralloc_copys(stralloc *, char const *)
int _DNSGetTXT(const char *szFQDN, char *Buffer, int nBufLen)
Definition: dkimverify.cpp:77
int _DKIM_ReportResult(const char *ResFile, const char *result, const char *reason)
Definition: dkimverify.cpp:102
bool WildcardMatch(const char *p, const char *s)
Definition: dkimverify.cpp:402
#define MAX_SIGNATURES
Definition: dkimverify.cpp:54
size_t m_SigHdr
Definition: dkimverify.cpp:58
unsigned DecodeBase64(char *ptr)
Definition: dkimverify.cpp:364
string SigHdr
Definition: dkimverify.cpp:57
bool ParseAddresses(string str, vector< string > &Addresses)
Definition: dkimverify.cpp:425
bool ParseUnsigned(const char *s, unsigned *result)
Definition: dkimverify.cpp:906
char Tohex(char ch)
Definition: dkimverify.cpp:321
bool ParseTagValueList(char *tagvaluelist, const char *wanted[], char *values[])
Definition: dkimverify.cpp:236
const char * DKIM_ErrorResult(const int res)
Definition: dkimverify.cpp:124
void DecodeQuotedPrintable(char *ptr)
Definition: dkimverify.cpp:339
int dig_ascii(char *digascii, unsigned const char *digest, const int len)
Definition: dkimverify.cpp:62
#define DNS_INIT
Definition: dns.h:12
stralloc out
Definition: dnscname.c:12
stralloc sa
Definition: dnscname.c:11
strset done
Definition: fastforward.c:75
void p(char *, char *, int, int, int)
Definition: install.c:49
int last
Definition: qmail-pop3d.c:128
int j
Definition: qmail-send.c:926
struct del * d[CHANNELS]
Definition: qmail-send.c:726
int nHonorBodyLengthTag
Definition: dkim.h:120
DKIMDNSCALLBACK pfnSelectorCallback
Definition: dkim.h:118
int nAllowUnsignedFromHeaders
Definition: dkim.h:124
int nSaveCanonicalizedData
Definition: dkim.h:123
int nSubjectRequired
Definition: dkim.h:122
int nCheckPractices
Definition: dkim.h:121