From 89b7b67a13ebb7965cc7f13ad0595e2194a2d34c Mon Sep 17 00:00:00 2001 From: Jannis Hoffmann Date: Wed, 3 Jul 2024 15:48:04 +0200 Subject: add sqmail-4.2.29a --- src/dkimverify.cpp | 1443 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1443 insertions(+) create mode 100644 src/dkimverify.cpp (limited to 'src/dkimverify.cpp') diff --git a/src/dkimverify.cpp b/src/dkimverify.cpp new file mode 100644 index 0000000..c9f1003 --- /dev/null +++ b/src/dkimverify.cpp @@ -0,0 +1,1443 @@ +/***************************************************************************** +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* This code incorporates intellectual property owned by Yahoo! and licensed +* pursuant to the Yahoo! DomainKeys Patent License Agreement. +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* Changes done by ¢feh@fehcom.de obeying the above license +* +*****************************************************************************/ +#include +#include +#include +#include +#include +#include "dkim.h" +#include "dkimverify.h" +#include "dnsgettxt.h" +extern "C" { +#include "dns.h" +#include "stralloc.h" +} + +/***************************************************************************** +* +* Verifying DKIM Ed25519 signatures: +* +* The received DKIM header includes two cryptographic relevant informations: +* +* a) The 'body hash' => bh=[sha1|sha256] +* b) The signature => b=[RSA-SHA1|RSA-SHA256|PureEd25519] +* +* Several DKIM headers (=signatures) may be present in the email. +* Here, it is limited to max. Shall we really evaluate all? +* +* Caution: Using hybrid signatures, calling the destructor will core dump +* given EVP_MD_CTX_free() upon the next call of EVP_DigestInit. +* Using the destructor with EVP_MD_CTX_reset() however works. +* +*****************************************************************************/ + +#define _strnicmp strncasecmp +#define _stricmp strcasecmp +#define MAX_SIGNATURES 10 // maximum number of DKIM signatures to process/message +#define FDLOG stderr /* writing to another FD requires a method */ + +string SigHdr; +size_t m_SigHdr; + +extern "C" int stralloc_copys(stralloc *,char const *); + +int dig_ascii(char *digascii,unsigned const char *digest,const int len) +{ + static const char hextab[] = "0123456789abcdef"; + int j; + + for (j = 0; j < len; j++) { + digascii[2 * j] = hextab[(unsigned char)digest[j] >> 4]; + digascii[2 * j + 1] = hextab[(unsigned char)digest[j] & 0x0f]; + } + digascii[2 * len] = '\0'; + + return (2 * j); // 2*len +} + + +int _DNSGetTXT(const char *szFQDN,char *Buffer,int nBufLen) +{ + stralloc out = {0}; + stralloc sa = {0}; + Buffer[0] = '\0'; // need to be initialized + + if (!stralloc_copys(&sa,szFQDN)) return -1; + + DNS_INIT + + switch (dns_txt(&out,&sa)) { + case -1: return -1; + case 0: return 0; + } + + if (nBufLen < out.len) + return -2; + + if (!stralloc_0(&out)) return -1; + memcpy(Buffer,out.s,out.len); // Return-by-value; sigh + + return out.len; +} + +int _DKIM_ReportResult(const char* ResFile,const char* result,const char* reason) +{ + int len = 0; + + FILE* out = fopen(ResFile,"wb+"); + if (out == NULL) return -1; + + if (result) { + len = strlen(result); + fwrite(result,1,len,out); + fwrite("\r",1,1,out); + } + + if (reason) { + fwrite(reason,1,strlen(reason),out); + fwrite("\r",1,1,out); + } + fclose(out); + + return len; +} + +const char* DKIM_ErrorResult(const int res) +{ + const char* errormsg = ""; + + switch (res) { + case DKIM_FAIL: + errormsg = " (verify error: message is suspicious)"; + break; + case DKIM_BAD_SYNTAX: + errormsg = " (signature error: could not parse or has bad tags/values)"; + break; + case DKIM_SIGNATURE_BAD: + errormsg = " (signature error: RSA/ED25519 verify failed)"; + break; + case DKIM_SIGNATURE_BAD_BUT_TESTING: + errormsg = " (signature error: RSA/ED25519 verify failed but testing)"; + break; + case DKIM_SIGNATURE_EXPIRED: + errormsg = " (signature error: signature x= value expired)"; + break; + case DKIM_SELECTOR_INVALID: + errormsg = " (signature error: selector doesn't parse or contains invalid values)"; + break; + case DKIM_SELECTOR_GRANULARITY_MISMATCH: + errormsg = " (signature error: selector g= doesn't match i=)"; + break; + case DKIM_SELECTOR_KEY_REVOKED: + errormsg = " (signature error: revoked p= empty)"; + break; + case DKIM_SELECTOR_DOMAIN_NAME_TOO_LONG: + errormsg = " (dns error: selector domain name too long to request)"; + break; + case DKIM_SELECTOR_DNS_TEMP_FAILURE: + errormsg = " (dns error: temporary dns failure requesting selector)"; + break; + case DKIM_SELECTOR_DNS_PERM_FAILURE: + errormsg = " (dns error: permanent dns failure requesting selector)"; + break; + case DKIM_SELECTOR_PUBLIC_KEY_INVALID: + errormsg = " (signature error: selector p= value invalid or wrong format)"; + break; + case DKIM_NO_SIGNATURES: + errormsg = " (process error: no signatures)"; + break; + case DKIM_NO_VALID_SIGNATURES: + errormsg = " (process error: no valid signatures)"; + break; + case DKIM_BODY_HASH_MISMATCH: + errormsg = " (signature verify error: message body does not hash to bh= value)"; + break; + case DKIM_SELECTOR_ALGORITHM_MISMATCH: + errormsg = " (signature error: selector h= doesn't match signature a=)"; + break; + case DKIM_STAT_INCOMPAT: + errormsg = " (signature error: incompatible v= value)"; + break; + case DKIM_UNSIGNED_FROM: + errormsg = " (signature error: not all message's From headers in signature)"; + break; + case DKIM_OUT_OF_MEMORY: + errormsg = " (internal error: memory allocation failed)"; + break; + case DKIM_INVALID_CONTEXT: + errormsg = " (internal error: DKIMContext structure invalid for this operation)"; + break; + case DKIM_NO_SENDER: + errormsg = " (signing error: Could not find From: or Sender: header in message)"; + break; + case DKIM_BAD_PRIVATE_KEY: + errormsg = " (signing error: Could not parse private key)"; + break; + case DKIM_BUFFER_TOO_SMALL: + errormsg = " (signing error: Buffer passed in is not large enough)"; + break; + } + + return errormsg; +} + +SignatureInfo::SignatureInfo(bool s) +{ + VerifiedBodyCount = 0; + UnverifiedBodyCount = 0; + +#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) + EVP_MD_CTX_init(&m_Hdr_ctx); + EVP_MD_CTX_init(&m_Bdy_ctx); +#else + m_Hdr_ctx = EVP_MD_CTX_new(); + m_Bdy_ctx = EVP_MD_CTX_new(); +#endif +#if (OPENSSL_VERSION_NUMBER > 0x10101000L) + m_Msg_ctx = EVP_MD_CTX_new(); +#endif + m_pSelector = NULL; + Status = DKIM_SUCCESS; + m_nHash = 0; + EmptyLineCount = 0; + m_SaveCanonicalizedData = s; +} + +SignatureInfo::~SignatureInfo() +{ +#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) + EVP_MD_CTX_cleanup(&m_Hdr_ctx); + EVP_MD_CTX_cleanup(&m_Bdy_ctx); +#else + /** FIXME: No free but reset ! **/ + EVP_MD_CTX_reset(m_Hdr_ctx); + EVP_MD_CTX_reset(m_Bdy_ctx); +#endif +#if (OPENSSL_VERSION_NUMBER > 0x10101000L) + EVP_MD_CTX_reset(m_Msg_ctx); +#endif +} + +inline bool isswsp(char ch) +{ + return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Parse a DKIM tag-list. Returns true for success +// +//////////////////////////////////////////////////////////////////////////////// +bool ParseTagValueList(char *tagvaluelist,const char *wanted[],char *values[]) +{ + char *s = tagvaluelist; + + for (;;) { + // skip whitespace + while (isswsp(*s)) + s++; + + // if at the end of the string, return success. Note: this allows a list with no entries + if (*s == '\0') + return true; + + // get tag name + if (!isalpha(*s)) + return false; + + char *tag = s; + do { + s++; + } while (isalnum(*s) || *s == '-'); + + char *endtag = s; + + // skip whitespace before equals + while (isswsp(*s)) + s++; + + // next character must be equals + if (*s != '=') + return false; + s++; + + // null-terminate tag name + *endtag = '\0'; + + // skip whitespace after equals + while (isswsp(*s)) + s++; + + // get tag value + char *value = s; + + while (*s != ';' && ((*s == '\t' || *s == '\r' || *s == '\n') || (*s >= ' ' && *s <= '~'))) + s++; + + char *e = s; + + // make sure the next character is the null terminator (which means we're done) or a semicolon (not done) + bool done = false; + if (*s == '\0') + done = true; + else { + if (*s != ';') + return false; + s++; + } + + // skip backwards past any trailing whitespace + while (e > value && isswsp(e[-1])) + e--; + + // null-terminate tag value + *e = '\0'; + + // check to see if we want this tag + for (unsigned i = 0; wanted[i] != NULL; i++) { + if (strcmp(wanted[i],tag) == 0) { + // return failure if we already have a value for this tag (duplicates not allowed) + if (values[i] != NULL) + return false; + values[i] = value; + break; + } + } + + if (done) + return true; + } +} +//////////////////////////////////////////////////////////////////////////////// +// +// Convert hex char to value (0-15) +// +//////////////////////////////////////////////////////////////////////////////// +char Tohex(char ch) +{ + if (ch >= '0' && ch <= '9') + return (ch - '0'); + else if (ch >= 'A' && ch <= 'F') + return (ch - 'A' + 10); + else if (ch >= 'a' && ch <= 'f') + return (ch - 'a' + 10); + else { + assert(0); + return 0; + } +} +//////////////////////////////////////////////////////////////////////////////// +// +// Decode quoted printable string in-place +// +//////////////////////////////////////////////////////////////////////////////// +void DecodeQuotedPrintable(char* ptr) +{ + char *s = ptr; + while (*s != '\0' && *s != '=') + s++; + + if (*s == '\0') + return; + + char *d = s; + do { + if (*s == '=' && isxdigit(s[1]) && isxdigit(s[2])) { + *d++ = (Tohex(s[1]) << 4) | Tohex(s[2]); + s += 3; + } else { + *d++ = *s++; + } + } while (*s != '\0'); + *d = '\0'; +} +//////////////////////////////////////////////////////////////////////////////// +// +// Decode base64 string in-place, returns number of bytes output +// +//////////////////////////////////////////////////////////////////////////////// +unsigned DecodeBase64(char *ptr) +{ + static const char base64_table[256] = { + -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, + -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, + -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, + -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, + -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, + -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, + -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, + -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}; + + unsigned char* s = (unsigned char* )ptr; + unsigned char* d = (unsigned char* )ptr; + unsigned b64accum = 0; + unsigned char b64shift = 0; + + while (*s != '\0') { + unsigned char value = base64_table[*s++]; + if ((signed char) value >= 0) { + b64accum = (b64accum << 6) | value; + b64shift += 6; + if (b64shift >= 8) { + b64shift -= 8; + *d++ = (b64accum >> b64shift); + } + } + } + + return (char* )d-ptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Match a string with a pattern (used for g= value) +// Supports a single, optional "*" wildcard character. +// +//////////////////////////////////////////////////////////////////////////////// +bool WildcardMatch(const char *p, const char *s) +{ + // special case: An empty "g=" value never matches any addresses + if (*p == '\0') + return false; + + const char* wildcard = strchr(p,'*'); + if (wildcard == NULL) { + return strcmp(s, p) == 0; + } else { + unsigned beforewildcardlen = wildcard - p; + unsigned afterwildcardlen = strlen(wildcard + 1); + unsigned slen = strlen(s); + return (slen >= beforewildcardlen + afterwildcardlen) && + (strncmp(s,p,beforewildcardlen) == 0) && strcmp(s + slen - afterwildcardlen,wildcard + 1) == 0; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Parse addresses from a string. Returns true if at least one address found +// +//////////////////////////////////////////////////////////////////////////////// +bool ParseAddresses(string str,vector &Addresses) +{ + char* s = (char* )str.c_str(); + + while (*s != '\0') { + char* start = s; + char* from = s; + char* to = s; + char* lt = NULL; // pointer to less than character (<) which starts the address if found + + while (*from != '\0') { + if (*from == '(') { + // skip over comment + from++; + for (int depth = 1; depth != 0; from++) { + if (*from == '\0') + break; + else if (*from == '(') + depth++; + else if (*from == ')') + depth--; + else if (*from == '\\' && from[1] != '\0') + from++; + } + } + else if (*from == ')') { + // ignore closing parenthesis outside of comment + from++; + } else if (*from == ',' || *from == ';') { + // comma/semicolon ends the address + from++; + break; + } + else if (*from == ' ' || *from == '\t' || *from == '\r' || *from == '\n') { + // ignore whitespace + from++; + } else if (*from == '"') { + // copy the contents of a quoted string + from++; + while (*from != '\0') { + if (*from == '"') { + from++; + break; + } else if (*from == '\\' && from[1] != '\0') + *to++ = *from++; + *to++ = *from++; + } + } else if (*from == '\\' && from[1] != '\0') { + // copy quoted-pair + *to++ = *from++; + *to++ = *from++; + } else { + // copy any other char + *to = *from++; + // save pointer to '<' for later... + if (*to == '<') + lt = to; + to++; + } + } + + *to = '\0'; + + // if there's < > get what's inside + if (lt != NULL) { + start = lt+1; + char *gt = strchr(start, '>'); + if (gt != NULL) + *gt = '\0'; + } else { + // look for and strip group name + char *colon = strchr(start, ':'); + if (colon != NULL) { + char *at = strchr(start, '@'); + if (at == NULL || colon < at) + start = colon+1; + } + } + + if (*start != '\0' && strchr(start, '@') != NULL) { + Addresses.push_back(start); // save address + } + + s = from; + } + + return !Addresses.empty(); +} + +//////////////////////////////////////////////////////////////////////////////// + +CDKIMVerify::CDKIMVerify() +{ + m_pfnSelectorCallback = NULL; +// m_pfnPracticesCallback = NULL; + m_HonorBodyLengthTag = false; + m_CheckPractices = false; +// Kai: +// m_SubjectIsRequired = true; + m_SubjectIsRequired = false; + m_SaveCanonicalizedData = false; + m_AllowUnsignedFromHeaders = false; +} + +CDKIMVerify::~CDKIMVerify() {} // Destructor + +//////////////////////////////////////////////////////////////////////////////// +// +// Init - save the options +// +//////////////////////////////////////////////////////////////////////////////// +int CDKIMVerify::Init(DKIMVerifyOptions* pOptions) +{ + int nRet = CDKIMBase::Init(); + + m_pfnSelectorCallback = pOptions->pfnSelectorCallback; + // m_pfnPracticesCallback = pOptions->pfnPracticesCallback; + + m_HonorBodyLengthTag = pOptions->nHonorBodyLengthTag != 0; + m_CheckPractices = pOptions->nCheckPractices != 0; +// Kai: +// m_SubjectIsRequired = pOptions->nSubjectRequired == 0; + m_SubjectIsRequired = pOptions->nSubjectRequired == 1; + m_SaveCanonicalizedData = pOptions->nSaveCanonicalizedData != 0; + m_AllowUnsignedFromHeaders = pOptions->nAllowUnsignedFromHeaders != 0; + + return nRet; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// GetResults - return the pass/fail/neutral verification result +// +//////////////////////////////////////////////////////////////////////////////// +int CDKIMVerify::GetResults(void) +{ + // char mdi[128]; + // char digi[128]; + + ProcessFinal(); + + unsigned char *SignMsg; + unsigned SuccessCount = 0; + int TestingFailures = 0; + int RealFailures = 0; + int res = 0; + + list SuccessfulDomains; // can contain duplicates + + for (list::iterator i = Signatures.begin(); i != Signatures.end(); ++i) { + if (i->Status == DKIM_SUCCESS) { + if (!i->BodyHashData.empty()) { // FIRST: Get the body hash + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned len = 0; + +#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) + res = EVP_DigestFinal(&i->m_Bdy_ctx,md,&len); +#else + res = EVP_DigestFinal_ex(i->m_Bdy_ctx,md,&len); + EVP_MD_CTX_reset(i->m_Bdy_ctx); +#endif + // dig_ascii(digi,md,32); + // dig_ascii(mdi,(unsigned const char *)i->BodyHashData.data(),32); + + if (!res || len != i->BodyHashData.length() || memcmp(i->BodyHashData.data(),md,len) != 0) { + // body hash mismatch + + // if the selector is in testing mode... + if (i->m_pSelector->Testing) { + i->Status = DKIM_SIGNATURE_BAD_BUT_TESTING; // todo: make a new error code for this? + TestingFailures++; + } else { + i->Status = DKIM_BODY_HASH_MISMATCH; + RealFailures++; + } + continue; // next signature + } + } else { + // hash CRLF separating the body from the signature + i->Hash("\r\n",2); + } + + // SECOND: Fetch the signature + + string sSignedSig = i->Header; + string sSigValue = sSignedSig.substr(sSignedSig.find(':') + 1); + + static const char* tags[] = {"b",NULL}; + char* values[sizeof(tags)/sizeof(tags[0])] = {NULL}; + + char* pSigValue = (char* ) sSigValue.c_str(); // our signature + if (ParseTagValueList(pSigValue,tags,values) && values[0] != NULL) { + sSignedSig.erase(15 + values[0] - pSigValue,strlen(values[0])); + } + + if (i->HeaderCanonicalization == DKIM_CANON_RELAXED) { + sSignedSig = RelaxHeader(sSignedSig); + } + else if (i->HeaderCanonicalization == DKIM_CANON_NOWSP) { + RemoveSWSP(sSignedSig); + // convert "DKIM-Signature" to lower case + sSignedSig.replace(0,14,"dkim-signature",14); + } + + i->Hash(sSignedSig.c_str(),sSignedSig.length()); // include generated DKIM signature header + assert(i->m_pSelector != NULL); + + if (EVP_PKEY_base_id(i->m_pSelector->PublicKey) != EVP_PKEY_ED25519) +#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) + res = EVP_VerifyFinal(&i->m_Hdr_ctx,(unsigned char *)i->SignatureData.data(),i->SignatureData.length(),i->m_pSelector->PublicKey); +#else + res = EVP_VerifyFinal(i->m_Hdr_ctx,(unsigned char *)i->SignatureData.data(),i->SignatureData.length(),i->m_pSelector->PublicKey); +#endif +#if (OPENSSL_VERSION_NUMBER > 0x10101000L) + else if (EVP_PKEY_base_id(i->m_pSelector->PublicKey) == EVP_PKEY_ED25519) { + EVP_DigestVerifyInit(i->m_Msg_ctx,NULL,NULL,NULL,i->m_pSelector->PublicKey); // late initialization + + SignMsg = (unsigned char *) SigHdr.data(); + res = EVP_DigestVerify(i->m_Msg_ctx,(unsigned char *)i->SignatureData.data(),(size_t)i->SignatureData.length(), + SignMsg,m_SigHdr); + } +#endif + + if (res == 1) { + if (i->UnverifiedBodyCount == 0) + i->Status = DKIM_SUCCESS; + else + i->Status = DKIM_SUCCESS_BUT_EXTRA; + SuccessCount++; + SuccessfulDomains.push_back(i->Domain); + } else { + // if the selector is in testing mode... + if (i->m_pSelector->Testing) { + i->Status = DKIM_SIGNATURE_BAD_BUT_TESTING; + TestingFailures++; + } else { + i->Status = DKIM_SIGNATURE_BAD; + RealFailures++; + } + } + } else if (i->Status == DKIM_SELECTOR_GRANULARITY_MISMATCH || + i->Status == DKIM_SELECTOR_ALGORITHM_MISMATCH || + i->Status == DKIM_SELECTOR_KEY_REVOKED) { + // treat these as failures + // todo: maybe see if the selector is in testing mode? + RealFailures++; + } + } // loop over signature infos done + + + // get the From address's domain if we might need it + string sFromDomain; + if (SuccessCount > 0 || m_CheckPractices) { + for (list::iterator i = HeaderList.begin(); i != HeaderList.end(); ++i) { + if (_strnicmp(i->c_str(),"From",4) == 0) { + // skip over whitespace between the header name and : + const char* s = i->c_str() + 4; + while (*s == ' ' || *s == '\t') + s++; + if (*s == ':') { + vector Addresses; + if (ParseAddresses(s + 1, Addresses)) { + unsigned atpos = Addresses[0].find('@'); + sFromDomain = Addresses[0].substr(atpos + 1); + break; + } + } + } + } + } + + // if a signature from the From domain verified successfully, return success now + // without checking the author domain signing practices + if (SuccessCount > 0 && !sFromDomain.empty()) { + for (list::iterator i = SuccessfulDomains.begin(); i != SuccessfulDomains.end(); ++i) { + // see if the successful domain is the same as or a parent of the From domain + if (i->length() > sFromDomain.length()) + continue; + if (_stricmp(i->c_str(),sFromDomain.c_str() + sFromDomain.length() - i->length()) != 0) + continue; + if (i->length() == sFromDomain.length() || sFromDomain.c_str()[sFromDomain.length() - i->length() - 1] == '.') { + return SuccessCount == Signatures.size() ? DKIM_SUCCESS : DKIM_PARTIAL_SUCCESS; + } + } + } + + /* Removed obsolete ADSP check */ + + // return neutral for everything else + return DKIM_NEUTRAL; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Hash - update the hash or update the Ed25519 signature input +// +//////////////////////////////////////////////////////////////////////////////// +void SignatureInfo::Hash(const char* szBuffer,unsigned nBufLength,bool IsBody) +{ +#if 0 + /** START DEBUG CODE **/ + if(nBufLength == 2 && szBuffer[0] == '\r' && szBuffer[1] == '\n') + { + printf("[CRLF]\n"); + } else { + char* szDbg = new char[nBufLength+1]; + strncpy(szDbg, szBuffer, nBufLength); + szDbg[nBufLength] = '\0'; + printf("[%s]\n", szDbg); + } + /** END DEBUG CODE **/ +#endif + + if (IsBody && BodyLength != (unsigned) -1) { // trick: 2's complement + VerifiedBodyCount += nBufLength; + if (VerifiedBodyCount > BodyLength) { + nBufLength = BodyLength - (VerifiedBodyCount - nBufLength); + UnverifiedBodyCount += VerifiedBodyCount - BodyLength; + VerifiedBodyCount = BodyLength; + if (nBufLength == 0) return; + } + } + + if (IsBody && !BodyHashData.empty()) { +#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) + EVP_DigestUpdate(&m_Bdy_ctx,szBuffer,nBufLength); + } else { + EVP_VerifyUpdate(&m_Hdr_ctx,szBuffer,nBufLength); +#else + EVP_DigestUpdate(m_Bdy_ctx,szBuffer,nBufLength); + } else { + EVP_VerifyUpdate(m_Hdr_ctx,szBuffer,nBufLength); +#endif +#if (OPENSSL_VERSION_NUMBER > 0x10101000L) + SigHdr.append(szBuffer,nBufLength); + m_SigHdr += nBufLength; +#endif + } + + if (m_SaveCanonicalizedData) { + CanonicalizedData.append(szBuffer,nBufLength); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// +// ProcessHeaders - Look for DKIM-Signatures and start processing them +// look for DKIM-Signature header(s) +// +//////////////////////////////////////////////////////////////////////////////// +int CDKIMVerify::ProcessHeaders(void) +{ + for (list::iterator i = HeaderList.begin(); i != HeaderList.end(); ++i) { + if (strlen(i->c_str()) < 14) continue; // too short + if (_strnicmp(i->c_str(),"DKIM-Signature",14) == 0) { + // skip over whitespace between the header name and : + const char *s = i->c_str() + 14; + while (*s == ' ' || *s == '\t') + s++; + if (*s == ':') { + // found + SignatureInfo sig(m_SaveCanonicalizedData); + sig.Status = ParseDKIMSignature(*i,sig); + Signatures.push_back(sig); // save signature + + if (Signatures.size() >= MAX_SIGNATURES) + break; + } + } + } + + if (Signatures.empty()) + return DKIM_NO_SIGNATURES; + + bool ValidSigFound = false; + + for (list::iterator s = Signatures.begin(); s != Signatures.end(); ++s) { + SignatureInfo &sig = *s; + if (sig.Status != DKIM_SUCCESS) continue; + SelectorInfo &sel = GetSelector(sig.Selector,sig.Domain); + sig.m_pSelector = &sel; + + if (sel.Status != DKIM_SUCCESS) { + sig.Status = sel.Status; + } else { + // check the granularity + if (!WildcardMatch(sel.Granularity.c_str(),sig.IdentityLocalPart.c_str())) + sig.Status = DKIM_SELECTOR_GRANULARITY_MISMATCH; // this error causes the signature to fail + + // check the hash algorithm + if ((sig.m_nHash == DKIM_HASH_SHA1 && !sel.AllowSHA1) || + (sig.m_nHash == DKIM_HASH_SHA256 && !sel.AllowSHA256)) + sig.Status = DKIM_SELECTOR_ALGORITHM_MISMATCH; // causes signature to fail + + // check for same domain + if (sel.SameDomain && _stricmp(sig.Domain.c_str(),sig.IdentityDomain.c_str()) != 0) + sig.Status = DKIM_BAD_SYNTAX; + } + + if (sig.Status != DKIM_SUCCESS) continue; + + // initialize the hashes + if (sig.m_nHash == DKIM_HASH_SHA1) { +#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) + EVP_VerifyInit(&sig.m_Hdr_ctx,EVP_sha1()); + EVP_DigestInit(&sig.m_Bdy_ctx,EVP_sha1()); +#else + EVP_VerifyInit_ex(sig.m_Hdr_ctx,EVP_sha1(),NULL); + EVP_DigestInit_ex(sig.m_Bdy_ctx,EVP_sha1(),NULL); +#endif + } + if (sig.m_nHash == DKIM_HASH_SHA256) { +#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) + EVP_VerifyInit(&sig.m_Hdr_ctx,EVP_sha256()); + EVP_DigestInit(&sig.m_Bdy_ctx,EVP_sha256()); +#else + EVP_VerifyInit_ex(sig.m_Hdr_ctx,EVP_sha256(),NULL); + EVP_DigestInit_ex(sig.m_Bdy_ctx,EVP_sha256(),NULL); +#endif +#if (OPENSSL_VERSION_NUMBER > 0x10101000L) + SigHdr.assign(""); + m_SigHdr = 0; + } +#endif + + // compute the hash of the header + vector::reverse_iterator> used; + + for (vector::iterator x = sig.SignedHeaders.begin(); x != sig.SignedHeaders.end(); ++x) { + list::reverse_iterator i; + for (i = HeaderList.rbegin(); i != HeaderList.rend(); ++i) { + if (_strnicmp(i->c_str(),x->c_str(),x->length()) == 0) { + // skip over whitespace between the header name and : + const char* s = i->c_str()+x->length(); + while (*s == ' ' || *s == '\t') + s++; + if (*s == ':' && find(used.begin(),used.end(),i) == used.end()) + break; + } + } + + if (i != HeaderList.rend()) { + used.push_back(i); + + // hash this header + if (sig.HeaderCanonicalization == DKIM_CANON_SIMPLE) { + sig.Hash(i->c_str(),i->length()); + } else if (sig.HeaderCanonicalization == DKIM_CANON_RELAXED) { + string sTemp = RelaxHeader(*i); + sig.Hash(sTemp.c_str(),sTemp.length()); + } else if (sig.HeaderCanonicalization == DKIM_CANON_NOWSP) { + string sTemp = *i; + RemoveSWSP(sTemp); + + // convert characters before ':' to lower case + for (char* s = (char*)sTemp.c_str(); *s != '\0' && *s != ':'; s++) { + if (*s >= 'A' && *s <= 'Z') + *s += 'a' - 'A'; + } + sig.Hash(sTemp.c_str(),sTemp.length()); + } + sig.Hash("\r\n",2); + } + } + + if (sig.BodyHashData.empty()) { + // hash CRLF separating headers from body + sig.Hash("\r\n",2); + } + + if (!m_AllowUnsignedFromHeaders) { + // make sure the message has no unsigned From headers + list::reverse_iterator i; + for (i = HeaderList.rbegin(); i != HeaderList.rend(); ++i) { + if (_strnicmp(i->c_str(),"From",4) == 0) { + // skip over whitespace between the header name and : + const char *s = i->c_str() + 4; + while (*s == ' ' || *s == '\t') + s++; + if (*s == ':') { + if (find(used.begin(),used.end(),i) == used.end()) { + // this From header was not signed + break; + } + } + } + } + if (i != HeaderList.rend()) { + // treat signature as invalid + sig.Status = DKIM_UNSIGNED_FROM; + continue; + } + } + + ValidSigFound = true; + } + + if (!ValidSigFound) + return DKIM_NO_VALID_SIGNATURES; + + return DKIM_SUCCESS; +} + + +//////////////////////////////////////////////////////////////////////////////// +// +// Strictly parse an unsigned integer. Don't allow spaces, negative sign, +// 0x prefix, etc. Values greater than 2^32-1 are capped at 2^32-1 +// +//////////////////////////////////////////////////////////////////////////////// +bool ParseUnsigned(const char *s, unsigned *result) +{ + unsigned temp = 0, last = 0; + bool overflowed = false; + + do { + if (*s < '0' || *s > '9') + return false; // returns false for an initial '\0' + + temp = temp * 10 + (*s - '0'); + if (temp < last) + overflowed = true; + last = temp; + + s++; + } while (*s != '\0'); + + *result = overflowed ? -1 : temp; + return true; +} + + +//////////////////////////////////////////////////////////////////////////////// +// +// ParseDKIMSignature - Parse a DKIM-Signature header field +// +//////////////////////////////////////////////////////////////////////////////// +int CDKIMVerify::ParseDKIMSignature(const string& sHeader,SignatureInfo &sig) +{ + // for strtok_r() + char *saveptr; + + // save header for later + sig.Header = sHeader; + + string sValue = sHeader.substr(sHeader.find(':') + 1); + + static const char *tags[] = {"v","a","b","d","h","s","c","i","l","q","t","x","bh",NULL}; + char *values[sizeof(tags)/sizeof(tags[0])] = {NULL}; + + if (!ParseTagValueList((char*) sValue.c_str(),tags,values)) + return DKIM_BAD_SYNTAX; + + // check signature version + if (values[0] == NULL) return DKIM_BAD_SYNTAX; + + // signature MUST have a=, b=, d=, h=, s= + if (values[1] == NULL || values[2] == NULL || values[3] == NULL || values[4] == NULL || values[5] == NULL) + return DKIM_BAD_SYNTAX; + + // algorithm ('a=') can be "rsa-sha1" or "rsa-sha256" or "ed25519" + if (strcmp(values[1],"rsa-sha1") == 0) { + sig.m_nHash = DKIM_HASH_SHA1; + } else if (strcmp(values[1],"rsa-sha256") == 0) { + sig.m_nHash = DKIM_HASH_SHA256; +#if (OPENSSL_VERSION_NUMBER > 0x10101000L) + } else if (strcmp(values[1],"ed25519-sha256") == 0) { + sig.m_nHash = DKIM_HASH_SHA256; +#endif + } else { + return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for unknown algorithm + } + + // make sure the signature data is not empty: b=[...] + unsigned SigDataLen = DecodeBase64(values[2]); + + if (SigDataLen == 0) + return DKIM_BAD_SYNTAX; + + sig.SignatureData.assign(values[2],SigDataLen); + + // check for body hash in DKIM header: bh=[...]; + unsigned BodyHashLen = DecodeBase64(values[12]); + if (BodyHashLen == 0) return DKIM_BAD_SYNTAX; + sig.BodyHashData.assign(values[12],BodyHashLen); + + // domain must not be empty + if (*values[3] == '\0') + return DKIM_BAD_SYNTAX; + sig.Domain = values[3]; + + // signed headers must not be empty (more verification is done later) + if (*values[4] == '\0') + return DKIM_BAD_SYNTAX; + + // selector must not be empty + if (*values[5] == '\0') + return DKIM_BAD_SYNTAX; + sig.Selector = values[5]; + + // canonicalization + if (values[6] == NULL) { + sig.HeaderCanonicalization = sig.BodyCanonicalization = DKIM_CANON_SIMPLE; + } else { + char* slash = strchr(values[6],'/'); + if (slash != NULL) + *slash = '\0'; + + if (strcmp(values[6],"simple") == 0) + sig.HeaderCanonicalization = DKIM_CANON_SIMPLE; + else if (strcmp(values[6],"relaxed") == 0) + sig.HeaderCanonicalization = DKIM_CANON_RELAXED; + else + return DKIM_BAD_SYNTAX; + + if (slash == NULL || strcmp(slash + 1,"simple") == 0) + sig.BodyCanonicalization = DKIM_CANON_SIMPLE; + else if (strcmp(slash + 1,"relaxed") == 0) + sig.BodyCanonicalization = DKIM_CANON_RELAXED; + else + return DKIM_BAD_SYNTAX; + } + + // identity + if (values[7] == NULL) { + sig.IdentityLocalPart.erase(); + sig.IdentityDomain = sig.Domain; + } else { + // quoted-printable decode the value + DecodeQuotedPrintable(values[7]); + + // must have a '@' separating the local part from the domain + char* at = strchr(values[7],'@'); + if (at == NULL) + return DKIM_BAD_SYNTAX; + *at = '\0'; + + char* ilocalpart = values[7]; + char* idomain = at + 1; + + // i= domain must be the same as or a subdomain of the d= domain + int idomainlen = strlen(idomain); + int ddomainlen = strlen(values[3]); + + // todo: maybe create a new error code for invalid identity domain + if (idomainlen < ddomainlen) + return DKIM_BAD_SYNTAX; + if (_stricmp(idomain + idomainlen - ddomainlen,values[3]) != 0) + return DKIM_BAD_SYNTAX; + if (idomainlen > ddomainlen && idomain[idomainlen - ddomainlen - 1] != '.') + return DKIM_BAD_SYNTAX; + + sig.IdentityLocalPart = ilocalpart; + sig.IdentityDomain = idomain; + } + + // body count + if (values[8] == NULL || !m_HonorBodyLengthTag) { + sig.BodyLength = (unsigned) -1; + } else { + if (!ParseUnsigned(values[8],&sig.BodyLength)) + return DKIM_BAD_SYNTAX; + } + + // query methods + if (values[9] != NULL) { + // make sure "dns" is in the list + bool HasDNS = false; + char* s = strtok_r(values[9],":",&saveptr); + while (s != NULL) { + if (strncmp(s,"dns",3) == 0 && (s[3] == '\0' || s[3] == '/')) { + HasDNS = true; + break; + } + s = strtok_r(NULL,": \t",&saveptr); /* FIXME */ +// s = strtok_r(NULL,": ",&saveptr); /* FIXME */ + } + if (!HasDNS) + return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for unknown query method + } + + // signature time + unsigned SignedTime = -1; + if (values[10] != NULL) { + if (!ParseUnsigned(values[10],&SignedTime)) + return DKIM_BAD_SYNTAX; + } + + // expiration time + if (values[11] == NULL) { + sig.ExpireTime = (unsigned) -1; // common trick; feh + } else { + if (!ParseUnsigned(values[11],&sig.ExpireTime)) + return DKIM_BAD_SYNTAX; + + if (sig.ExpireTime != (unsigned) -1) { + // the value of x= MUST be greater than the value of t= if both are present + if (SignedTime != (unsigned) -1 && sig.ExpireTime <= SignedTime) + return DKIM_BAD_SYNTAX; + + // todo: if possible, use the received date/time instead of the current time + unsigned curtime = time(NULL); + if (curtime > sig.ExpireTime) + return DKIM_SIGNATURE_EXPIRED; + } + } + + // parse the signed headers list + bool HasFrom = false, HasSubject = false; + RemoveSWSP(values[4]); // header names shouldn't have spaces in them so this should be ok... + char* s = strtok_r(values[4],":",&saveptr); + while (s != NULL) { + if (_stricmp(s,"From") == 0) + HasFrom = true; + else if (_stricmp(s,"Subject") == 0) + HasSubject = true; + + sig.SignedHeaders.push_back(s); + s = strtok_r(NULL,":",&saveptr); + } + + if (!HasFrom) + return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for h= missing From + if (m_SubjectIsRequired && !HasSubject) + return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for h= missing Subject + + return DKIM_SUCCESS; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// ProcessBody - Process message body data +// +//////////////////////////////////////////////////////////////////////////////// +int CDKIMVerify::ProcessBody(char* szBuffer,int nBufLength,bool bEOF) +{ + bool MoreBodyNeeded = false; + + for (list::iterator i = Signatures.begin(); i != Signatures.end(); ++i) { + if (i->Status == DKIM_SUCCESS) { + if (i->BodyCanonicalization == DKIM_CANON_SIMPLE) { + if (nBufLength > 0) { + while (i->EmptyLineCount > 0) { + i->Hash("\r\n",2,true); + i->EmptyLineCount--; + } + i->Hash(szBuffer,nBufLength,true); + i->Hash("\r\n",2,true); + } else { + i->EmptyLineCount++; + if (bEOF) + i->Hash("\r\n",2,true); + } + } else if (i->BodyCanonicalization == DKIM_CANON_RELAXED) { + CompressSWSP(szBuffer, nBufLength); + if (nBufLength > 0) { + while (i->EmptyLineCount > 0) { + i->Hash("\r\n",2,true); + i->EmptyLineCount--; + } + i->Hash(szBuffer,nBufLength,true); + if (!bEOF) + i->Hash("\r\n",2,true); + } else i->EmptyLineCount++; + } else if (i->BodyCanonicalization == DKIM_CANON_NOWSP) { + RemoveSWSP(szBuffer,nBufLength); + i->Hash(szBuffer,nBufLength,true); + } + + if (i->UnverifiedBodyCount == 0) + MoreBodyNeeded = true; + } + } + + if (!MoreBodyNeeded) + return DKIM_FINISHED_BODY; + + return DKIM_SUCCESS; +} + +SelectorInfo::SelectorInfo(const string &sSelector,const string &sDomain) : Domain(sDomain),Selector(sSelector) +{ + AllowSHA1 = true; + AllowSHA256 = true; + PublicKey = NULL; + Testing = false; + SameDomain = false; + Status = DKIM_SUCCESS; +} + +SelectorInfo::~SelectorInfo() +{ + if (PublicKey != NULL) { + EVP_PKEY_free(PublicKey); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Parse - Parse a DKIM selector from DNS data +// +//////////////////////////////////////////////////////////////////////////////// +int SelectorInfo::Parse(char* Buffer) +{ + // for strtok_r() + char *saveptr; + char *PubKeyBase64; /*- public key Base64 encoded */ + char ed25519PubKey[61]; + + static const char *tags[] = {"v","g","h","k","p","s","t","n",NULL}; // 0, 1, 2, 3, 4 + char *values[sizeof(tags)/sizeof(tags[0])] = {NULL}; + + ParseTagValueList(Buffer,tags,values); + + // return DKIM_SELECTOR_INVALID; + if (values[0] != NULL) { + // make sure the version is "DKIM1" + if (strcmp(values[0],"DKIM1") != 0) + return DKIM_SELECTOR_INVALID; // todo: maybe create a new error code for unsupported selector version + + // make sure v= is the first tag in the response // todo: maybe don't enforce this, it seems unnecessary + for (unsigned j = 1; j < sizeof(values)/sizeof(values[0]); j++) { + if (values[j] != NULL && values[j] < values[0]) { + return DKIM_SELECTOR_INVALID; + } + } + } + + // selector MUST have p= tag + if (values[4] == NULL) + return DKIM_SELECTOR_INVALID; + + PubKeyBase64 = values[4]; // gotcha + + // granularity -- [g= ... ] + if (values[1] == NULL) + Granularity = "*"; + else + Granularity = values[1]; + + // hash algorithm -- [h=sha1|sha256] (not required) + if (values[2] == NULL) { + AllowSHA1 = true; + AllowSHA256 = true; + } else { + // MUST include "sha1" or "sha256" + char* s = strtok_r(values[2],":",&saveptr); + while (s != NULL) { + if (strcmp(s,"sha1") == 0) + { AllowSHA1 = true; AllowSHA256 = false; } + else if (strcmp(s,"sha256") == 0) + { AllowSHA256 = true; AllowSHA1 = false; } + s = strtok_r(NULL,":",&saveptr); + } + if (!(AllowSHA1 || AllowSHA256)) + return DKIM_SELECTOR_INVALID; // todo: maybe create a new error code for unsupported hash algorithm + } + + // key type -- [k=rsa|ed25519] (not required) + if (values[3] != NULL) { + // key type MUST be "rsa" or "ed25519" + if (strcmp(values[3],"rsa") != 0 && strcmp(values[3],"ed25519") != 0) // none of either + return DKIM_SELECTOR_INVALID; + if (strcmp(values[3],"ed25519") == 0) { + AllowSHA1 = false; + AllowSHA256 = true; + strcpy(ed25519PubKey,"MCowBQYDK2VwAyEA"); + /* + * rfc8463 + * since Ed25519 public keys are 256 bits long, + * the base64-encoded key is only 44 octets + */ + if (strlen(values[4]) > 44) + return DKIM_SELECTOR_PUBLIC_KEY_INVALID; + strcat(ed25519PubKey,values[4]); + PubKeyBase64 = ed25519PubKey; + } + } + + // service type -- [s= ...] (not required) + if (values[5] != NULL) { + // make sure "*" or "email" is in the list + bool ServiceTypeMatch = false; + char* s = strtok_r(values[5],":",&saveptr); + while (s != NULL) { + if (strcmp(s, "*") == 0 || strcmp(s,"email") == 0) { + ServiceTypeMatch = true; + break; + } + s = strtok_r(NULL,":",&saveptr); + } + if (!ServiceTypeMatch) + return DKIM_SELECTOR_INVALID; + } + + // flags -- [t= ...] (not required) + if (values[6] != NULL) { + char *s = strtok_r(values[6],":",&saveptr); + while (s != NULL) { + if (strcmp(s,"y") == 0) { + Testing = true; + } else if (strcmp(s,"s") == 0) { + SameDomain = true; + } + s = strtok_r(NULL,":",&saveptr); + } + } + + // public key data + unsigned PublicKeyLen = DecodeBase64(PubKeyBase64); + + if (PublicKeyLen == 0) { + return DKIM_SELECTOR_KEY_REVOKED; // this error causes the signature to fail + } else { + const unsigned char *PublicKeyData = (unsigned char* )PubKeyBase64; // 0-terminated + + EVP_PKEY *pkey = d2i_PUBKEY(NULL,&PublicKeyData,PublicKeyLen); /* retrieve and return PubKey from data */ + + if (pkey == NULL) + return DKIM_SELECTOR_PUBLIC_KEY_INVALID; + + // make sure public key is the correct type (we only support rsa & ed25519) +#if ((OPENSSL_VERSION_NUMBER < 0x10101000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) + if (pkey->type == EVP_PKEY_RSA || pkey->type == EVP_PKEY_RSA2) { +#else + if ((EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) || + (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA2) || + (EVP_PKEY_base_id(pkey) == EVP_PKEY_ED25519)) { +#endif + PublicKey = pkey; + } else { + EVP_PKEY_free(pkey); + return DKIM_SELECTOR_PUBLIC_KEY_INVALID; + } + } + + return DKIM_SUCCESS; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// GetSelector - Get a DKIM selector for a domain +// +//////////////////////////////////////////////////////////////////////////////// +SelectorInfo& CDKIMVerify::GetSelector(const string &sSelector,const string &sDomain) +{ + // see if we already have this selector + for (list::iterator i = Selectors.begin(); i != Selectors.end(); ++i) { + if (_stricmp(i->Selector.c_str(),sSelector.c_str()) == 0 && _stricmp(i->Domain.c_str(),sDomain.c_str()) == 0) { + return *i; + } + } + + Selectors.push_back(SelectorInfo(sSelector,sDomain)); + SelectorInfo& sel = Selectors.back(); + + string sFQDN = sSelector; + sFQDN += "._domainkey."; + sFQDN += sDomain; + + int BufLen = 1024; + char Buffer[BufLen]; + + int DNSResult; + + if (m_pfnSelectorCallback) { + DNSResult = m_pfnSelectorCallback(sFQDN.c_str(),Buffer,BufLen); + } else + DNSResult = _DNSGetTXT(sFQDN.c_str(),Buffer,BufLen); + +// Buffer++; BufLen--; + + switch (DNSResult) { + case -1: case -2: case -3: case -5: sel.Status = DKIM_SELECTOR_DNS_TEMP_FAILURE; break; + case 0: case -6: sel.Status = DKIM_SELECTOR_DNS_PERM_FAILURE; break; + default: sel.Status = sel.Parse(Buffer); + } + + return sel; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// GetDetails - Get DKIM verification details (per signature) +// +//////////////////////////////////////////////////////////////////////////////// +int CDKIMVerify::GetDetails(int* nSigCount,DKIMVerifyDetails** pDetails) +{ + Details.clear(); + + for (list < SignatureInfo>::iterator i = Signatures.begin(); i != Signatures.end(); ++i) { + DKIMVerifyDetails d; + d.szSignature = (char* )i->Header.c_str(); + d.szSignatureDomain = (char* )i->Domain.c_str(); + d.szIdentityDomain = (char* )i->IdentityDomain.c_str(); + d.szCanonicalizedData = (char* )i->CanonicalizedData.c_str(); + d.nResult = i->Status; + Details.push_back(d); + } + + *nSigCount = Details.size(); + *pDetails = (*nSigCount != 0) ? &Details[0] : NULL; + + return DKIM_SUCCESS; +} -- cgit v1.2.3