summaryrefslogtreecommitdiff
path: root/sqmail-4.3.07/src/dkimverify.cpp
diff options
context:
space:
mode:
authorJannis Hoffmann <jannis@fehcom.de>2024-07-09 11:44:11 +0200
committerJannis Hoffmann <jannis@fehcom.de>2024-07-09 11:44:11 +0200
commitf1b71c9fe7dbb4886588a036399cf5ebe16b7c47 (patch)
treee07786aa479c9fb6ee3e537078470aaab5454f80 /sqmail-4.3.07/src/dkimverify.cpp
parenta293489ee83c8b05d845a162dc2a4de026f3775d (diff)
removed top level directory
Diffstat (limited to 'sqmail-4.3.07/src/dkimverify.cpp')
-rw-r--r--sqmail-4.3.07/src/dkimverify.cpp1443
1 files changed, 0 insertions, 1443 deletions
diff --git a/sqmail-4.3.07/src/dkimverify.cpp b/sqmail-4.3.07/src/dkimverify.cpp
deleted file mode 100644
index c9f1003..0000000
--- a/sqmail-4.3.07/src/dkimverify.cpp
+++ /dev/null
@@ -1,1443 +0,0 @@
-/*****************************************************************************
-*
-* 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 <string.h>
-#include <ctype.h>
-#include <assert.h>
-#include <vector>
-#include <algorithm>
-#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<string> &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<string> SuccessfulDomains; // can contain duplicates
-
- for (list<SignatureInfo>::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<string>::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<string> 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<string>::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<string>::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<SignatureInfo>::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<list<string>::reverse_iterator> used;
-
- for (vector<string>::iterator x = sig.SignedHeaders.begin(); x != sig.SignedHeaders.end(); ++x) {
- list<string>::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<string>::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<SignatureInfo>::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<SelectorInfo>::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;
-}