/*
**  Copyright (c) 2005-2007 Sendmail, Inc. and its suppliers.
**    All rights reserved.
*/

#ifndef lint
static char dkim_test_c_id[] = "@(#)$Id: dkim-test.c,v 1.8 2007/12/03 07:43:25 msk Exp $";
#endif /* !lint */

/* system includes */
#include <sys/param.h>
#include <sys/types.h>
#include <assert.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>

/* libsm includes */
#include <sm/string.h>

/* openssl includes */
#include <openssl/bio.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>

/* libdkim includes */
#include "dkim-types.h"
#include "dkim-keys.h"
#include "dkim-util.h"
#include "dkim-test.h"
#include "dkim.h"

/* prototypes from elsewhere */
extern DKIM_STAT dkim_get_key __P((DKIM *, DKIM_SIGINFO *));

/*
**  DKIM_TEST_KEY -- retrieve a public key and verify it against a provided
**                   private key
**
**  Parameters:
**  	lib -- DKIM library handle
**  	selector -- selector
**  	domain -- domain name
**  	key -- private key to verify (PEM format)
**  	keylen -- size of private key
**  	err -- error buffer (may be NULL)
**  	errlen -- size of error buffer
**
**  Return value:
**  	1 -- keys don't match
**  	0 -- keys match (or no key provided)
**  	-1 -- error
*/

int
dkim_test_key(DKIM_LIB *lib, char *selector, char *domain,
              char *key, size_t keylen, char *err, size_t errlen)
{
	int status = 0;
	DKIM_STAT stat;
	DKIM *dkim;
	DKIM_SIGINFO *sig;
	BIO *keybuf;
	BIO *outkey;
	void *ptr;
	struct dkim_rsa *rsa;

	assert(lib != NULL);
	assert(selector != NULL);
	assert(domain != NULL);

	dkim = dkim_verify(lib, "test", NULL, &stat);
	if (dkim == NULL)
	{
		if (err != NULL)
			sm_strlcpy(err, dkim_getresultstr(stat), errlen);
		return -1;
	}

	dkim->dkim_siglist = DKIM_MALLOC(dkim, sizeof(sig) * 2);
	if (dkim->dkim_siglist == NULL)
	{
		(void) dkim_free(dkim);
		if (err != NULL)
		{
			snprintf(err, errlen, "unable to allocate %d byte(s)",
			         sizeof(sig) * 2);
		}
		return -1;
	}

	dkim->dkim_siglist[1] = NULL;
	dkim->dkim_siglist[0] = DKIM_MALLOC(dkim, sizeof *sig);
	if (dkim->dkim_siglist[0] == NULL)
	{
		(void) dkim_free(dkim);
		if (err != NULL)
		{
			snprintf(err, errlen, "unable to allocate %d byte(s)",
			         sizeof *sig);
		}
		return -1;
	}

	sig = dkim->dkim_siglist[0];
	memset(sig, '\0', sizeof *sig);

	sig->sig_domain = domain;
	sig->sig_selector = selector;

	dkim->dkim_user = dkim_strdup(dkim, "nobody", 0);
	if (dkim->dkim_user == NULL)
	{
		(void) dkim_free(dkim);
		return -1;
	}

	stat = dkim_get_key(dkim, sig);
	if (stat != DKIM_STAT_OK)
	{
		if (err != NULL)
		{
			const char *errstr;

			errstr = dkim_geterror(dkim);
			if (errstr != NULL)
			{
				sm_strlcpy(err, errstr, errlen);
			}
			else
			{
				sm_strlcpy(err, dkim_getresultstr(stat),
				           errlen);
			}
		}
		(void) dkim_free(dkim);
		return -1;
	}

	if (key != NULL)
	{
		keybuf = BIO_new_mem_buf(key, keylen);
		if (keybuf == NULL)
		{
			if (err != NULL)
			{
				sm_strlcpy(err, "BIO_new_mem_buf() failed",
				           errlen);
			}
			(void) dkim_free(dkim);
			return -1;
		}

		rsa = DKIM_MALLOC(dkim, sizeof(struct dkim_rsa));
		if (rsa == NULL)
		{
			BIO_free(keybuf);
			(void) dkim_free(dkim);
			if (err != NULL)
			{
				snprintf(err, errlen,
				         "unable to allocate %d byte(s)",
				         sizeof(struct dkim_rsa));
			}
			return -1;
		}
		memset(rsa, '\0', sizeof(struct dkim_rsa));

		sig->sig_signature = (void *) rsa;
		sig->sig_keytype = DKIM_KEYTYPE_RSA;

		rsa->rsa_pkey = PEM_read_bio_PrivateKey(keybuf, NULL,
		                                        NULL, NULL);
		if (rsa->rsa_pkey == NULL)
		{
			BIO_free(keybuf);
			(void) dkim_free(dkim);
			if (err != NULL)
			{
				sm_strlcpy(err,
				           "PEM_read_bio_PrivateKey() failed",
				           errlen);
			}
			return -1;
		}

		rsa->rsa_rsa = EVP_PKEY_get1_RSA(rsa->rsa_pkey);
		if (rsa->rsa_rsa == NULL)
		{
			BIO_free(keybuf);
			(void) dkim_free(dkim);
			if (err != NULL)
			{
				sm_strlcpy(err, "EVP_PKEY_get1_RSA() failed",
				           errlen);
			}
			return -1;
		}
	
		rsa->rsa_keysize = RSA_size(rsa->rsa_rsa);
		rsa->rsa_pad = RSA_PKCS1_PADDING;

		outkey = BIO_new(BIO_s_mem());
		if (outkey == NULL)
		{
			BIO_free(keybuf);
			(void) dkim_free(dkim);
			if (err != NULL)
				sm_strlcpy(err, "BIO_new() failed", errlen);
			return -1;
		}

		status = i2d_RSA_PUBKEY_bio(outkey, rsa->rsa_rsa);
		if (status == 0)
		{
			BIO_free(keybuf);
			BIO_free(outkey);
			(void) dkim_free(dkim);
			if (err != NULL)
			{
				sm_strlcpy(err, "i2d_RSA_PUBKEY_bio() failed",
				           errlen);
			}
			return -1;
		}

		(void) BIO_get_mem_data(outkey, &ptr);

		if (BIO_number_written(outkey) == sig->sig_keylen)
			status = memcmp(ptr, sig->sig_key, sig->sig_keylen);
		else
			status = 1;

		if (status != 0)
			sm_strlcpy(err, "keys do not match", errlen);

		BIO_free(keybuf);
		BIO_free(outkey);
	}

	(void) dkim_free(dkim);

	return (status == 0 ? 0 : 1);
}

/*
**  DKIM_TEST_SSP -- verify that a valid sender signing policy exists in dns
**
**  Parameters:
**  	lib -- DKIM library handle
**  	domain -- domain name
**
**  Return value:
**  	0 -- some kind of result was made available
**  	-1 -- error
*/

int
dkim_test_ssp(DKIM_LIB *lib, char *domain, dkim_policy_t *presult,
              dkim_handling_t *hresult, int *presult2,
              char *err, size_t errlen)
{
	DKIM_STAT stat;
	bool test = FALSE;
	bool susp = FALSE;
	dkim_policy_t pcode = DKIM_POLICY_DEFAULT;
	dkim_handling_t hcode = DKIM_HANDLING_DEFAULT;
	DKIM *dkim;

	assert(lib != NULL);
	assert(presult != NULL);
	assert(hresult != NULL);
	assert(presult2 != NULL);

	dkim = dkim_verify(lib, "test", NULL, &stat);
	if (dkim == NULL)
	{
		if (err != NULL)
			sm_strlcpy(err, dkim_getresultstr(stat), errlen);
		return -1;
	}

	dkim->dkim_mode = DKIM_MODE_VERIFY;
	dkim->dkim_domain = domain;
	dkim->dkim_sigcount = 0;

	stat = dkim_policy(dkim, &test, &susp, &pcode, &hcode, NULL);
	if (stat != DKIM_STAT_OK)
	{
		if (err != NULL)
		{
			const char *errstr;

			errstr = dkim_geterror(dkim);
			if (errstr != NULL)
			{
				sm_strlcpy(err, errstr, errlen);
			}
			else
			{
				sm_strlcpy(err, dkim_getresultstr(stat),
				           errlen);
			}
		}

		dkim->dkim_domain = NULL;
		(void) dkim_free(dkim);

		return -1;
	}

	*presult = pcode;
	*hresult = hcode;
	*presult2 = dkim_getpresult(dkim);

	dkim->dkim_domain = NULL;
	(void) dkim_free(dkim);

	return (stat == DKIM_STAT_OK ? 0 : -1);
}


syntax highlighted by Code2HTML, v. 0.9.1