Beispiel #1
0
/*
 * GetPublicKey()
 *
 * Get the publickey from supported sources (ie. dns/txt)
 */
void Validatory::GetPublicKey(const DKIM::Signature& sig,
		DKIM::PublicKey& pubkey)
	throw (DKIM::PermanentError, DKIM::TemporaryError)
{
	if (sig.GetQueryType() == DKIM::Signature::DKIM_Q_DNSTXT)
	{
		std::string query = sig.GetSelector() + "._domainkey." + sig.GetDomain();
		std::string publicKey;

		if ((CustomDNSResolver?
					CustomDNSResolver(query, publicKey, CustomDNSData):
					DKIM::Util::Resolver().GetTXT(query, publicKey)
			))
		{
			if (publicKey.empty())
				throw DKIM::PermanentError(StringFormat("No key for signature %s._domainkey.%s",
							sig.GetSelector().c_str(),
							sig.GetDomain().c_str()
							)
						);
			pubkey.Parse(publicKey);
			return;
		}
		throw DKIM::TemporaryError(StringFormat("DNS query failed for %s._domainkey.%s",
					sig.GetSelector().c_str(),
					sig.GetDomain().c_str()
					)
				);
	}
	throw DKIM::PermanentError(StringFormat("Unsupported query type %d",
				(int)sig.GetQueryType()
				)
			);
}
Beispiel #2
0
/*
 * GetSignature()
 *
 * Get the signature from supported header iterator
 */
void Validatory::GetSignature(const Message::HeaderList::const_iterator& headerIter,
		DKIM::Signature& sig)
	throw (DKIM::PermanentError)
{
	sig.Parse(*headerIter);
	CheckBodyHash(sig);
}
Beispiel #3
0
/*
 * CheckBodyHash()
 *
 * Validate the message according to rfc6376
 */
void Validatory::CheckBodyHash(const DKIM::Signature& sig)
	throw (DKIM::PermanentError)
{
	std::unique_ptr<EVP_MD_CTX, std::function<void(EVP_MD_CTX*)>> evpmdbody(EVP_MD_CTX_create(), [] (EVP_MD_CTX* p) { EVP_MD_CTX_destroy(p); });

	// create signature for our body (message data)
	switch (sig.GetDigestAlgorithm())
	{
		case DKIM::DKIM_A_SHA1:
			EVP_DigestInit_ex(evpmdbody.get(), EVP_sha1(), nullptr);
			break;
		case DKIM::DKIM_A_SHA256:
			EVP_DigestInit_ex(evpmdbody.get(), EVP_sha256(), nullptr);
			break;
	}

	DKIM::Conversion::EVPDigest evpupd;
	evpupd.ctx = evpmdbody.get();

	CanonicalizationBody(m_file,
			sig.GetCanonModeBody(),
			m_msg.GetBodyOffset(),
			sig.GetBodySizeLimit(),
			sig.GetBodySize(),
			std::bind(&DKIM::Conversion::EVPDigest::update, &evpupd, std::placeholders::_1, std::placeholders::_2));

	unsigned char md_value[EVP_MAX_MD_SIZE];
	unsigned int md_len;
	EVP_DigestFinal_ex(evpmdbody.get(), md_value, &md_len);

	if (sig.GetBodyHash().size() != md_len ||
			memcmp(sig.GetBodyHash().c_str(), md_value, md_len) != 0)
	{
		throw DKIM::PermanentError("Body hash did not verify");
	}
}
Beispiel #4
0
/*
 * CheckSignature()
 *
 * Validate the message according to rfc6376
 */
void Validatory::CheckSignature(const std::shared_ptr<DKIM::Header> header,
		const DKIM::Signature& sig,
		const DKIM::PublicKey& pub)
	throw (DKIM::PermanentError)
{
	// sanity checking (between sig and pub)
	if (pub.GetDigestAlgorithms().size() > 0)
		if (find(pub.GetDigestAlgorithms().begin(), pub.GetDigestAlgorithms().end(), sig.GetDigestAlgorithm()) == pub.GetDigestAlgorithms().end())
			throw DKIM::PermanentError("Algorithm is not allowed");

	if (sig.GetSignatureAlgorithm() != pub.GetSignatureAlgorithm())
		throw DKIM::PermanentError("Signature algorithm type mismatch");

	if (!sig.IsARC())
	{
		if (find(pub.GetFlags().begin(), pub.GetFlags().end(), "s") != pub.GetFlags().end())
			if (sig.GetDomain() != sig.GetMailDomain())
				throw DKIM::PermanentError("Domain must match sub-domain (flag s)");
	}

	// create signature for our header
	std::unique_ptr<EVP_MD_CTX, std::function<void(EVP_MD_CTX*)>> evpmdhead(EVP_MD_CTX_create(), [] (EVP_MD_CTX* p) { EVP_MD_CTX_destroy(p); });
	int md_nid;
	switch (sig.GetDigestAlgorithm())
	{
		case DKIM::DKIM_A_SHA1:
			EVP_DigestInit_ex(evpmdhead.get(), EVP_sha1(), nullptr);
			md_nid = NID_sha1;
			break;
		case DKIM::DKIM_A_SHA256:
			EVP_DigestInit_ex(evpmdhead.get(), EVP_sha256(), nullptr);
			md_nid = NID_sha256;
			break;
	}

	CanonicalizationHeader canonicalhead(sig.GetCanonModeHeader());

	// add all headers to our cache (they will be pop of the end)
	std::map<std::string, Message::HeaderList> headerCache;
	for (const auto & hIter : m_msg.GetHeaders())
	{
		std::string headerName = hIter->GetName();
		transform(headerName.begin(), headerName.end(), headerName.begin(), tolower);
		headerCache[headerName].push_back(hIter);
	}

	// add all signed headers to our hash
	for (auto name : sig.GetSignedHeaders())
	{
		std::string tmp;
		transform(name.begin(), name.end(), name.begin(), tolower);

		std::map<std::string, Message::HeaderList>::iterator head = headerCache.find(name);

		// if this occurred
		// 1. we do not have a header of that name at all
		// 2. all headers with that name has been included...
		if (head == headerCache.end() || head->second.size() == 0)
			continue;

#ifdef DEBUG
		printf("[%s]\n", canonicalhead.FilterHeader(head->second.back()->GetHeader()).c_str());
		printf("[CRLF]\n");
#endif
		tmp = canonicalhead.FilterHeader(head->second.back()->GetHeader()) + "\r\n";
		head->second.pop_back();
		EVP_DigestUpdate(evpmdhead.get(), tmp.c_str(), tmp.size());
	}

	// add our dkim-signature to the calculation (remove the "b"-tag)
	std::string h = header->GetHeader().substr(0, header->GetValueOffset());
	std::string v = header->GetHeader().substr(header->GetValueOffset());

	DKIM::TagListEntry bTag;
	sig.GetTag("b", bTag);
	v.erase((int)bTag.GetValueOffset(), bTag.GetValue().size());

	std::string tmp = canonicalhead.FilterHeader(h + v);
#ifdef DEBUG
	printf("[%s]\n", tmp.c_str());
#endif
	EVP_DigestUpdate(evpmdhead.get(), tmp.c_str(), tmp.size());

	unsigned char md[EVP_MAX_MD_SIZE];
	unsigned int md_len;
	EVP_DigestFinal_ex(evpmdhead.get(), md, &md_len);

	// verify the header signature
	switch (sig.GetSignatureAlgorithm())
	{
		case DKIM::DKIM_SA_RSA:
		{
			int r = RSA_verify(md_nid,
						md,
						md_len,
						(const unsigned char *)sig.GetSignatureData().c_str(),
						(unsigned int)sig.GetSignatureData().size(),
						pub.GetRSAPublicKey());
			if (r != 1)
				throw DKIM::PermanentError("Signature did not verify");
		}
		break;
		case DKIM::DKIM_SA_ED25519:
			if (crypto_sign_verify_detached((const unsigned char*)sig.GetSignatureData().c_str(),
						md,
						md_len,
						(const unsigned char *)pub.GetED25519PublicKey().c_str()) != 0)
					throw DKIM::PermanentError("Signature did not verify");
		break;
	}

	// success!
}
Beispiel #5
0
/*
 * GetSignature()
 *
 * Get the signature from supported header iterator
 */
void Validatory::GetSignature(const Message::HeaderList::const_iterator& headerIter,
		DKIM::Signature& sig)	
	throw (DKIM::PermanentError)
{
	sig.Parse((*headerIter)->GetHeader().substr((*headerIter)->GetValueOffset()));

	// create signature for our body (message data)
	switch (sig.GetAlgorithm())
	{
		case DKIM::DKIM_A_SHA1:
			EVP_DigestInit(&m_ctx_body, EVP_sha1());
			break;
		case DKIM::DKIM_A_SHA256:
			EVP_DigestInit(&m_ctx_body, EVP_sha256());
			break;
	}

	CanonicalizationBody canonicalbody(sig.GetCanonModeBody());

	// if we should limit the size of the body we hash
	bool limitBody = sig.GetBodySizeLimit();
	size_t bodySize = sig.GetBodySize();

	// if we have a message: seek to GetBodyOffset()
	if (m_msg.GetBodyOffset() != -1)
	{
		m_file.clear();
		m_file.seekg(m_msg.GetBodyOffset(), std::istream::beg);

		std::string s;
		while (std::getline(m_file, s) || m_file.peek() != EOF)
		{
			// double dots (postfix file may have .., instead of .)
			if (m_doubleDots && s.substr(0, 2) == "..")
			{
				s.erase(0, 1);
			}

			// remove possible \r (if not removed by getline *probably not*)
			if (s.size() > 0 && s[s.size()-1] == '\r')
				s.erase(s.size()-1);

			// canonical body
			std::vector<std::string> output;
			if (canonicalbody.FilterLine(s, output))
			{
				for (std::vector<std::string>::const_iterator i = output.begin();
						i != output.end(); ++i)
				{
					if (limitBody && bodySize == 0) break;
#ifdef DEBUG
					if (*i == "\r\n") printf("[CRLF]\n");
					else printf("[%s]\n", i->c_str());
#endif
					EVP_DigestUpdate(&m_ctx_body, i->c_str(),
							limitBody?std::min(i->size(), bodySize):i->size());
					bodySize -= std::min(i->size(), bodySize);
				}
			}
		}
	}

	// else call (Done) -- which may insert a last CRLF if the body was empty
	if (m_msg.GetBodyOffset() != -1 || m_file.peek() == EOF)
	{
		std::vector<std::string> output;
		if (canonicalbody.Done(output))
		{
			for (std::vector<std::string>::const_iterator i = output.begin();
					i != output.end(); ++i)
			{
				if (limitBody && bodySize == 0) break;
#ifdef DEBUG
				if (*i == "\r\n") printf("[CRLF]\n");
				else printf("[%s]\n", i->c_str());
#endif
				EVP_DigestUpdate(&m_ctx_body, i->c_str(),
						limitBody?std::min(i->size(), bodySize):i->size());
				bodySize -= std::min(i->size(), bodySize);
			}
		}
	}

	unsigned char md_value[EVP_MAX_MD_SIZE];
	unsigned int md_len;
	EVP_DigestFinal_ex(&m_ctx_body, md_value, &md_len);
	EVP_MD_CTX_cleanup(&m_ctx_body);

	if (sig.GetBodyHash().size() != md_len ||
			memcmp(sig.GetBodyHash().c_str(), md_value, md_len) != 0)
	{
		throw DKIM::PermanentError("Body hash did not verify");
	}
}
Beispiel #6
0
/*
 * GetADSP()
 *
 * Get ADSP status for message rfc5617
 */
void Validatory::GetADSP(std::list<DKIM::ADSP>& adsp)
	throw (DKIM::PermanentError, DKIM::TemporaryError)
{
	/**
	 * Start by collecting all From: <> addresses (this runtime-time free...)
	 */
	std::list<std::string> senders;

	for (DKIM::Message::HeaderList::const_iterator i = m_msg.GetHeaders().begin();
		i != m_msg.GetHeaders().end(); ++i)
	{
		std::string headerName = (*i)->GetName();
		transform(headerName.begin(), headerName.end(), headerName.begin(), tolower);

		// find from: <...> header(s)...
		if (headerName == "from")
		{
			std::string header = (*i)->GetHeader().substr((*i)->GetValueOffset());

			header = DKIM::Conversion::EncodedWord::Decode(header);

			std::list<std::string> addrlist = DKIM::Tokenizer::ParseAddressList(header);
			for (std::list<std::string>::const_iterator aIter = addrlist.begin(); aIter != addrlist.end(); ++aIter)
			{
				// if no address is claimed to follow a ADSP, try the next one
				if (aIter->empty()) continue;

				size_t atSign = aIter->rfind("@");
				if (atSign == std::string::npos)
					throw DKIM::PermanentError("Found invalid sender address: " + *aIter);

				std::string host = aIter->substr(atSign + 1);
				transform(host.begin(), host.end(), host.begin(), tolower);

				senders.push_back(host);
			}
		}
	}

	std::map<std::string, std::pair<int, std::string> > dkimResult;

	for (Validatory::SignatureList::const_iterator i = GetSignatures().begin();
			i != GetSignatures().end(); ++i)
	{
		DKIM::PublicKey pub;
		DKIM::Signature sig;
		try {
			GetSignature(i, sig);
			GetPublicKey(sig, pub);
			CheckSignature(i, sig, pub);

			dkimResult[sig.GetDomain()] = std::make_pair(1, "pass");
		} catch (DKIM::TemporaryError& e) {
			dkimResult[sig.GetDomain()] = std::make_pair(-1, e.what());
		} catch (DKIM::PermanentError& e) {
			if (pub.SoftFail())
				dkimResult[sig.GetDomain()] = std::make_pair(1, e.what());
			else
				dkimResult[sig.GetDomain()] = std::make_pair(0, e.what());
		}
	}

	for (std::list<std::string>::const_iterator i = senders.begin(); i != senders.end(); ++i)
	{
		std::string query = "_adsp._domainkey." + *i;
		std::string adspRecord;

		ADSP tmp;
		tmp.SetDomain(*i);

		std::map<std::string, std::pair<int, std::string> >::const_iterator dIter = dkimResult.find(*i);

		if (dIter != dkimResult.end() && dIter->second.first == -1) {
			tmp.SetResult(ADSP::DKIM_ADSP_TEMPERROR, dIter->second.second);
		} else if (dIter != dkimResult.end() && dIter->second.first == 1) {
			tmp.SetResult(ADSP::DKIM_ADSP_PASS, dIter->second.second);
		} else {
			std::string error;
			if (dIter != dkimResult.end())
				error = dIter->second.second;
			else
				error = "no dkim signature found for d=" + *i;

			if ((CustomDNSResolver?
						CustomDNSResolver(query, adspRecord, CustomDNSData):
						DKIM::Util::Resolver().GetTXT(query, adspRecord)
				))
			{
				// only bad queries goes here..
				if (!adspRecord.empty())
				{
					try {
						TagList tagList;
						tagList.Parse(adspRecord);
						TagListEntry v;
						if (tagList.GetTag("dkim", v))
						{
							if (v.GetValue() == "all") {
								tmp.SetResult(ADSP::DKIM_ADSP_FAIL, error);
							} else if (v.GetValue() == "discardable") {
								tmp.SetResult(ADSP::DKIM_ADSP_DISCARD, error);
							} else {
								tmp.SetResult(ADSP::DKIM_ADSP_UNKNOWN, error);
							}
						} else {
							tmp.SetResult(ADSP::DKIM_ADSP_UNKNOWN, error);
						}
					} catch (DKIM::TemporaryError& e) {
						tmp.SetResult(ADSP::DKIM_ADSP_TEMPERROR, e.what());
					} catch (DKIM::PermanentError& e) {
						tmp.SetResult(ADSP::DKIM_ADSP_PERMERROR, e.what());
					}
				} else {
					tmp.SetResult(ADSP::DKIM_ADSP_NONE, error);
				}
			} else {
				tmp.SetResult(ADSP::DKIM_ADSP_TEMPERROR, DKIM::Util::StringFormat("dns query failed for %s", query.c_str()));
			}
		}
		adsp.push_back(tmp);
	}
}
Beispiel #7
0
/*
 * CheckSignature()
 *
 * Validate the message according to rfc4871
 */
void Validatory::CheckSignature(const Message::HeaderList::const_iterator& headerIter,
		const DKIM::Signature& sig,
		const DKIM::PublicKey& pub)
	throw (DKIM::PermanentError)
{
	// multiple signatures (must cleanup)
	EVP_MD_CTX_cleanup(&m_ctx_body);
	EVP_MD_CTX_cleanup(&m_ctx_head);

	// sanity checking (between sig and pub)
	if (pub.GetAlgorithms().size() > 0)
		if (find(pub.GetAlgorithms().begin(), pub.GetAlgorithms().end(), sig.GetAlgorithm()) == pub.GetAlgorithms().end())
			throw DKIM::PermanentError("Algorithm is not allowed");

	if (find(pub.GetFlags().begin(), pub.GetFlags().end(), "s") != pub.GetFlags().end())
		if (sig.GetDomain() != sig.GetMailDomain())
			throw DKIM::PermanentError("Domain must match sub-domain (flag s)");

	/*
	 If a DKIM verifier finds a selector record that has an empty "g" field ("g=;")
	 and it does not have a "v" field ("v=DKIM1;") at its beginning, it is faced with deciding if this record was

	 1. from a DK signer that transitioned to supporting DKIM but forgot to remove the "g"
	 	field (so that it could be used by both DK and DKIM verifiers), or
	 2. from a DKIM signer that truly meant to use the empty "g" field but forgot to put in
	 	the "v" field. It is RECOMMENDED that you treat such records using the first
		interpretation, and treat such records as if the signer did not have a "g" field in the record.
	*/

	// if we have a "g"-tag
	if (find(pub.GetFlags().begin(), pub.GetFlags().end(), "g") != pub.GetFlags().end())
	{
		// if it's empty... and we don't have a version...
		if (pub.GetMailLocalPart().empty() && find(pub.GetFlags().begin(), pub.GetFlags().end(), "v") == pub.GetFlags().end())
		{
			// do the RECOMMENDED interpretation and treat such records as if the signer did not have a "g" field in the record.
		} else
		{
			if (DKIM::Util::MatchWithWildCard(pub.GetMailLocalPart(), sig.GetMailLocalPart()) != true)
				throw DKIM::PermanentError("Unmatched local-part");
		}
	}

	// create signature for our header
	switch (sig.GetAlgorithm())
	{
		case DKIM::DKIM_A_SHA1:
			EVP_VerifyInit(&m_ctx_head, EVP_sha1());
			break;
		case DKIM::DKIM_A_SHA256:
			EVP_VerifyInit(&m_ctx_head, EVP_sha256());
			break;
	}

	CanonicalizationHeader canonicalhead(sig.GetCanonModeHeader());

	// add all headers to our cache (they will be pop of the end)
	std::map<std::string, Message::HeaderList> headerCache;
	for (Message::HeaderList::const_iterator hIter = m_msg.GetHeaders().begin();
			hIter != m_msg.GetHeaders().end();
			++hIter)
	{
		std::string headerName = (*hIter)->GetName();
		transform(headerName.begin(), headerName.end(), headerName.begin(), tolower);
		headerCache[headerName].push_back(*hIter);
	}

	// add all signed headers to our hash
	for (std::list<std::string>::const_iterator hIter = sig.GetSignedHeaders().begin();
			hIter != sig.GetSignedHeaders().end(); ++hIter)
	{
		std::string tmp;
		std::string name = *hIter;
		transform(name.begin(), name.end(), name.begin(), tolower);

		std::map<std::string, Message::HeaderList>::iterator head = headerCache.find(name);

		// if this occurred
		// 1. we do not have a header of that name at all
		// 2. all headers with that name has been included...
		if (head == headerCache.end() || head->second.size() == 0)
			continue;	

#ifdef DEBUG
		printf("[%s]\n", canonicalhead.FilterHeader(head->second.back()->GetHeader()).c_str());
		printf("[CRLF]\n");
#endif
		tmp = canonicalhead.FilterHeader(head->second.back()->GetHeader()) + "\r\n";
		head->second.pop_back();
		EVP_VerifyUpdate(&m_ctx_head, tmp.c_str(), tmp.size());
	}

	// add our dkim-signature to the calculation (remove the "b"-tag)
	std::string h = (*headerIter)->GetHeader().substr(0, (*headerIter)->GetValueOffset());
	std::string v = (*headerIter)->GetHeader().substr((*headerIter)->GetValueOffset());

	DKIM::TagListEntry bTag;
	sig.GetTag("b", bTag);
	v.erase((int)bTag.GetValueOffset(), bTag.GetValue().size());

	std::string tmp = canonicalhead.FilterHeader(h + v);
#ifdef DEBUG
	printf("[%s]\n", tmp.c_str());
#endif
	EVP_VerifyUpdate(&m_ctx_head, tmp.c_str(), tmp.size());

	// verify the header signature
	if (EVP_VerifyFinal(&m_ctx_head,
				(const unsigned char*)sig.GetSignatureData().c_str(),
				sig.GetSignatureData().size(),
				pub.GetPublicKey()
				) != 1)
		throw DKIM::PermanentError("Signature did not verify");
	EVP_MD_CTX_cleanup(&m_ctx_head);

	// success!
}
Beispiel #8
0
int main(int argc, char* argv[])
{
	__progname = argv[0];

	bool validate = false;
	bool doubleDots = false;
	std::string selector;
	std::string domain;
	std::string keyfile;

	// no arguments
	if (argc < 2)
		usage(stderr, 2);

	// longopts
	static struct option longopts[] = {
		{ "help",		no_argument,		NULL,		'h'	},
		{ "doubledots",	no_argument,		NULL,		'D'	},
		{ "validate",	no_argument,		NULL,		'v'	},
		{ "selector",	required_argument,	NULL,		's'	},
		{ "domain",		required_argument,	NULL,		'd'	},
		{ "keyfile",	required_argument,	NULL,		'k'	},
		{ NULL,			0,					NULL,		0	}
	};

	// fetching arguments..
	opterr = 0;
	optind = 0;
	int ch;
	while ((ch = getopt_long(argc, argv, "hvs:d:k:D", longopts, NULL)) != -1) {
		switch (ch)
		{
			case 'D':
				doubleDots = true;
				break;
			case 'v':
				validate = true;
				break;
			case 'h':
				usage(stdout, 0);
				break;
			case 's':
				selector = optarg;
				break;
			case 'd':
				domain = optarg;
				break;
			case 'k':
				keyfile = optarg;
				break;
			case 0:
				break;
			default:
				usage(stderr, 2);
				break;
		}
	}
	argc -= optind;
	argv += optind;

	if (argc < 1)
		usage(stderr, 2);

	if (!validate && (selector.empty() || domain.empty() || keyfile.empty()))
		usage(stderr, 2);

	//  sign the message..
	if (!validate)
	{
		// readkey
		std::ifstream kfp(keyfile);
		if (!kfp) {
			fprintf(stderr, "keyfile %s could not be open\n", keyfile.c_str());
			return 1;
		}
		std::string key((std::istreambuf_iterator<char>(kfp)),
				std::istreambuf_iterator<char>());

		std::ifstream fp(argv[0]);
		try {
			printf("%s\r\n",
					Signatory(fp, doubleDots).CreateSignature(
						SignatoryOptions()
						.SetPrivateKey(key)
						.SetDomain(domain)
						.SetSelector(selector)
						.SetCanonModeHeader(DKIM::DKIM_C_RELAXED)
						.SetCanonModeBody(DKIM::DKIM_C_RELAXED)
						).c_str() );
		} catch (std::runtime_error& e) {
			fprintf(stderr, "%s\n", e.what());
			return 1;
		}

		return 0;
	}

	// validate messages (0 .. argv)
	for (int x = 0; x < argc; x++)
	{
		std::ifstream fp(argv[x]);
		Validatory mail(fp, doubleDots);

		mail.CustomDNSResolver = MyResolver;

		// first check ADSP status
		try {
			std::list<ADSP> adsp;
			mail.GetADSP(adsp);
			for (std::list<ADSP>::const_iterator i = adsp.begin(); i != adsp.end(); ++i)
			{
				printf("[%s][ADSP][%s] %s/%s\n", argv[x], i->GetDomain().c_str(), i->GetResultAsString().c_str(), i->GetReason().c_str());
			}
		} catch (DKIM::TemporaryError& e) {
			printf("[%s][ADSP] TEMPERR:%s\n", argv[x], e.what());
		} catch (DKIM::PermanentError& e) {
			printf("[%s][ADSP] PERMERR:%s\n", argv[x], e.what());
		}

		// then list all valid SDID's
		for (Validatory::SignatureList::const_iterator i = mail.GetSignatures().begin();
				i != mail.GetSignatures().end(); ++i)
		{
			DKIM::PublicKey pub;
			DKIM::Signature sig;
			try {
				mail.GetSignature(i, sig);
				mail.GetPublicKey(sig, pub);
				mail.CheckSignature(i, sig, pub);
				printf("[%s][%s] OK\n", argv[x], sig.GetDomain().c_str());
			} catch (DKIM::TemporaryError& e) {
				printf("[%s][%s] TEMPERR:%s\n", argv[x], sig.GetDomain().c_str(), e.what());
			} catch (DKIM::PermanentError& e) {
				if (pub.SoftFail())
					printf("[%s][%s] SOFT:%s\n", argv[x], sig.GetDomain().c_str(), e.what());
				else
					printf("[%s][%s] = %s\n", argv[x], sig.GetDomain().c_str(), e.what());
			}
		}
	}

	return 0;
}