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