/*
**  Copyright (c) 2007 Sendmail, Inc. and its suppliers.
**	All rights reserved.
**
**  $Id: test.c,v 1.27 2007/11/14 18:49:01 msk Exp $
*/

#ifndef lint
static char test_c_id[] = "@(#)$Id: test.c,v 1.27 2007/11/14 18:49:01 msk Exp $";
#endif /* !lint */

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

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

/* libdkim includes */
#include <dkim.h>

/* dkim-filter includes */
#include "test.h"
#include "dkim-filter.h"

/* local types and definitions*/
#define	CRLF		"\r\n"

struct test_context
{
	void *	tc_priv;		/* private data pointer */
};

char *milter_status[] =
{
	"SMFIS_CONTINUE",
	"SMFIS_REJECT",
	"SMFIS_DISCARD",
	"SMFIS_ACCEPT",
	"SMFIS_TEMPFAIL"
};

char *envfrom[] =
{
	"",
	NULL
};

#define	FCLOSE(x)		if ((x) != stdin) \
					fclose((x));
#define	MLFI_OUTPUT(x,y)	((y) > 1 || ((y) == 1 && (x) != SMFIS_CONTINUE))
#define	STRORNULL(x)		((x) == NULL ? "(null)" : (x))

/* globals */
static int tverbose = 0;

/*
**  DKIMF_TEST_SETPRIV -- store private pointer
**
**  Parameters:
**  	ctx -- context pointer
**  	ptr -- pointer to store
**
**  Return value:
**  	MI_SUCCESS
*/

int
dkimf_test_setpriv(void *ctx, void *ptr)
{
	struct test_context *tc;

	assert(ctx != NULL);

	tc = ctx;
	tc->tc_priv = ptr;

	return MI_SUCCESS;
}

/*
**  DKIMF_TEST_GETPRIV -- retrieve private pointer
**
**  Parameters:
**  	ctx -- context pointer
**
**  Return value:
**  	The private pointer.
*/

void *
dkimf_test_getpriv(void *ctx)
{
	struct test_context *tc;

	assert(ctx != NULL);

	tc = ctx;

	return tc->tc_priv;
}

/*
**  DKIMF_TEST_SETREPLY -- set reply to use
**
**  Parameters:
**  	ctx -- context pointer
**  	rcode -- SMTP reply code
**  	xcode -- SMTP enhanced reply code
**  	replytxt -- SMTP reply text
**
**  Return value:
**  	MI_SUCCESS
*/

int
dkimf_test_setreply(void *ctx, char *rcode, char *xcode, char *replytxt)
{
	assert(ctx != NULL);

	if (tverbose > 1)
	{
		fprintf(stdout,
		        "### SETREPLY: rcode=`%s' xcode=`%s' replytxt=`%s'\n",
		        STRORNULL(rcode), STRORNULL(xcode),
		        STRORNULL(replytxt));
	}

	return MI_SUCCESS;
}

/*
**  DKIMF_TEST_INSHEADER -- insert a header
**
**  Parameters:
**  	ctx -- context pointer
**  	idx -- insertion index
**  	hname -- header name
**  	hvalue -- header value
**
**  Return value:
**  	MI_SUCCESS
*/

int
dkimf_test_insheader(void *ctx, int idx, char *hname, char *hvalue)
{
	assert(ctx != NULL);

	if (tverbose > 1)
	{
		fprintf(stdout,
		        "### INSHEADER: idx=%d hname=`%s' hvalue=`%s'\n",
		        idx, STRORNULL(hname), STRORNULL(hvalue));
	}

	return MI_SUCCESS;
}

/*
**  DKIMF_TEST_GETSYMVAL -- retrieve a symbol value
**
**  Parameters:
**  	ctx -- context pointer
**  	sym -- symbol name
**
**  Return value:
**  	Pointer to (static) string name.
**
**  Note:
**  	This isn't thread-safe, but test mode is single-threaded anyway.
**  	This is also a memory leak, but it's a short-lived test program
**  	anyway.
*/

char *
dkimf_test_getsymval(void *ctx, char *sym)
{
	static char symout[MAXBUFRSZ];

	assert(ctx != NULL);
	assert(sym != NULL);

	snprintf(symout, sizeof symout, "DEBUG-%s", sym);

	return strdup(symout);
}

/*
**  DKIMF_TESTFILE -- read a message and test it
**
**  Parameters:
**  	libdkim -- DKIM_LIB handle
**  	file -- input file path
**  	fixedtime -- time to use on signatures (or -1)
**  	strict -- strict CRLF mode?
**
**  Return value:
**  	An EX_* constant (see sysexits.h)
*/

int
dkimf_testfile(DKIM_LIB *libdkim, char *file, time_t fixedtime, bool strict,
               int verbose)
{
	char buf[MAXBUFRSZ];
	char line[MAXBUFRSZ];
	DKIM *dkim;
	char *p;
	FILE *f;
	struct test_context *tctx;
	int len = 0;
	int buflen = 0;
	int lineno = 0;
	int hslineno = 0;
	u_int opts;
	int c;
	DKIM_STAT status;
	sfsistat ms;
	bool inheaders = TRUE;

	assert(libdkim != NULL);
	assert(file != NULL);

	tverbose = verbose;

	/* pass fixed signing time to the library */
	if (fixedtime != (time_t) -1)
	{
		(void) dkim_options(libdkim, DKIM_OP_SETOPT,
		                    DKIM_OPTS_FIXEDTIME,
		                    &fixedtime, sizeof fixedtime);
	}

	/* open the input */
	if (strcmp(file, "-") == 0)
	{
		f = stdin;
		file = "(stdin)";
	}
	else
	{
		f = fopen(file, "r");
		if (f == NULL)
		{
			fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
			        file, strerror(errno));
			return EX_UNAVAILABLE;
		}
	}

	memset(buf, '\0', sizeof buf);
	memset(line, '\0', sizeof buf);

	/* set up a fake SMFICTX */
	tctx = (struct test_context *) malloc(sizeof(struct test_context));
	if (tctx == NULL)
	{
		fprintf(stderr, "%s: malloc(): %s\n", progname,
		        strerror(errno));
		FCLOSE(f);
		return EX_OSERR;
	}
	tctx->tc_priv = NULL;

	ms = mlfi_connect((SMFICTX *) tctx, "localhost", NULL);
	if (MLFI_OUTPUT(ms, tverbose))
	{
		fprintf(stderr, "%s: %s: mlfi_connect() returned %s\n",
		        progname, file, milter_status[ms]);
	}
	if (ms != SMFIS_CONTINUE)
	{
		FCLOSE(f);
		return EX_SOFTWARE;
	}

	ms = mlfi_envfrom((SMFICTX *) tctx, envfrom);
	if (MLFI_OUTPUT(ms, tverbose))
	{
		fprintf(stderr, "%s: %s: mlfi_envfrom() returned %s\n",
		        progname, file, milter_status[ms]);
	}
	if (ms != SMFIS_CONTINUE)
	{
		FCLOSE(f);
		return EX_SOFTWARE;
	}

	while (!feof(f))
	{
		if (fgets(line, sizeof line, f) == NULL)
			break;

		lineno++;

		c = '\0';
		for (p = line; *p != '\0'; p++)
		{
			if (*p == '\n')
			{
				*p = '\0';
				break;
			}

			c = *p;
		}

		if (c != '\r')
		{
			if (strict)			/* error */
			{
				fprintf(stderr,
				        "%s: %s: line %d: not CRLF-terminated\n",
				        progname, file, lineno);
				FCLOSE(f);
				return EX_DATAERR;
			}
		}
		else if (p != line)			/* eat the CR */
		{
			*(p - 1) = '\0';
		}

		if (inheaders)
		{
			if (line[0] == '\0')
			{
				if (buf[0] != '\0')
				{
					char *colon;

					colon = strchr(buf, ':');
					if (colon == NULL)
					{
						fprintf(stderr,
						        "%s: %s: line %d: header malformed\n",
						        progname, file,
						        lineno);
						FCLOSE(f);
						return EX_DATAERR;
					}

					*colon = '\0';
					if (*(colon + 1) == ' ')
						colon++;

					ms = mlfi_header((SMFICTX *) tctx, buf,
					                 colon + 1);
					if (MLFI_OUTPUT(ms, tverbose))
					{
						fprintf(stderr,
						        "%s: %s: line %d: mlfi_header() returned %s\n",
						         progname, file,
						         hslineno,
						         milter_status[ms]);
					}

					if (ms != SMFIS_CONTINUE)
					{
						FCLOSE(f);
						return EX_SOFTWARE;
					}
				}

				inheaders = FALSE;
				memset(buf, '\0', sizeof buf);
				memset(line, '\0', sizeof buf);

				ms = mlfi_eoh((SMFICTX *) tctx);
				if (MLFI_OUTPUT(ms, tverbose))
				{
					fprintf(stderr,
					        "%s: %s: mlfi_eoh() returned %s\n",
					         progname, file,
					         milter_status[ms]);
				}
				if (ms != SMFIS_CONTINUE)
				{
					FCLOSE(f);
					return EX_SOFTWARE;
				}

				continue;
			}

			if (line[0] == ' ' || line[0] == '\t')
			{
				(void) sm_strlcat(buf, CRLF, sizeof buf);

				if (sm_strlcat(buf, line,
				               sizeof buf) >= sizeof buf)
				{
					fprintf(stderr,
					        "%s: %s: line %d: header `%*s...' too large\n",
					        progname, file, lineno,
					        20, buf);
					FCLOSE(f);
					return EX_DATAERR;
				}
			}
			else
			{
				if (buf[0] != '\0')
				{
					char *colon;

					colon = strchr(buf, ':');
					if (colon == NULL)
					{
						fprintf(stderr,
						        "%s: %s: line %d: header malformed\n",
						        progname, file,
						        lineno);
						FCLOSE(f);
						return EX_DATAERR;
					}

					*colon = '\0';
					if (*(colon + 1) == ' ')
						colon++;

					ms = mlfi_header((SMFICTX *) tctx, buf,
					                 colon + 1);
					if (MLFI_OUTPUT(ms, tverbose))
					{
						fprintf(stderr,
						        "%s: %s: line %d: mlfi_header() returned %s\n",
						        progname, file,
						        hslineno,
						        milter_status[ms]);
					}
					if (ms != SMFIS_CONTINUE)
					{
						FCLOSE(f);
						return EX_SOFTWARE;
					}
					hslineno = 0;
				}

				if (hslineno == 0)
					hslineno = lineno;

				sm_strlcpy(buf, line, sizeof buf);
			}
		}
		else
		{
			len = strlen(line);

			if (len + buflen >= sizeof buf - 3)
			{
				ms = mlfi_body((SMFICTX *) tctx, buf,
				               strlen(buf));
				if (MLFI_OUTPUT(ms, tverbose))
				{
					fprintf(stderr,
					        "%s: %s: mlfi_body() returned %s\n",
					        progname, file,
					        milter_status[ms]);
				}
				if (ms != SMFIS_CONTINUE)
				{
					FCLOSE(f);
					return EX_SOFTWARE;
				}

				memset(buf, '\0', sizeof buf);
				buflen = 0;
			}

			memcpy(&buf[buflen], line, len);
			buflen += len;
			memcpy(&buf[buflen], CRLF, 2);
			buflen += 2;
		}
	}

	FCLOSE(f);

	/* unprocessed partial header? */
	if (inheaders && buf[0] != '\0')
	{
		char *colon;

		colon = strchr(buf, ':');
		if (colon == NULL)
		{
			fprintf(stderr,
			        "%s: %s: line %d: header malformed\n",
			        progname, file, lineno);
			FCLOSE(f);
			return EX_DATAERR;
		}

		*colon = '\0';
		if (*(colon + 1) == ' ')
			colon++;

		ms = mlfi_header((SMFICTX *) tctx, buf, colon + 1);
		if (MLFI_OUTPUT(ms, tverbose))
		{
			fprintf(stderr,
			        "%s: %s: line %d: mlfi_header() returned %s\n",
			        progname, file, lineno, milter_status[ms]);
		}
		if (ms != SMFIS_CONTINUE)
		{
			FCLOSE(f);
			return EX_SOFTWARE;
		}

		ms = mlfi_eoh((SMFICTX *) tctx);
		if (MLFI_OUTPUT(ms, tverbose))
		{
			fprintf(stderr,
			        "%s: %s: mlfi_eoh() returned %s\n",
			         progname, file, milter_status[ms]);
		}
		if (ms != SMFIS_CONTINUE)
		{
			FCLOSE(f);
			return EX_SOFTWARE;
		}

		inheaders = FALSE;
		memset(buf, '\0', sizeof buf);
	}

	/* no headers found */
	if (inheaders)
	{
		fprintf(stderr, "%s: %s: warning: no headers on input\n",
		        progname, file);

		ms = mlfi_eoh((SMFICTX *) tctx);
		if (MLFI_OUTPUT(ms, tverbose))
		{
			fprintf(stderr, "%s: %s: mlfi_eoh() returned %s\n",
			        progname, file, milter_status[ms]);
		}
		if (ms != SMFIS_CONTINUE)
		{
			FCLOSE(f);
			return EX_SOFTWARE;
		}
	}

	/* some body left */
	if (!inheaders && buf[0] != '\0')
	{
		ms = mlfi_body((SMFICTX *) tctx, buf, strlen(buf));
		if (MLFI_OUTPUT(ms, tverbose))
		{
			fprintf(stderr, "%s: %s: mlfi_body() returned %s\n",
			        progname, file, milter_status[ms]);
		}
		if (ms != SMFIS_CONTINUE)
			return EX_SOFTWARE;
	}

	ms = mlfi_eom((SMFICTX *) tctx);
	if (MLFI_OUTPUT(ms, tverbose))
	{
		fprintf(stderr, "%s: %s: mlfi_eom() returned %s\n",
		        progname, file, milter_status[ms]);
	}

	dkim = dkimf_getdkim(tctx->tc_priv);
	if (dkim != NULL)
	{
		int mode;
		DKIM_SIGINFO *sig;

		sig = dkim_getsignature(dkim);
		mode = dkim_getmode(dkim);

		if (sig != NULL && mode == DKIM_MODE_VERIFY)
		{
			const char *domain;
			const char *selector;
			u_int flags;
			u_int bh;
			u_int keysize;

			domain = dkim_sig_getdomain(sig);
			selector = dkim_sig_getselector(sig);
			flags = dkim_sig_getflags(sig);
			bh = dkim_sig_getbh(sig);
			dkim_sig_getkeysize(sig, &keysize);

			if ((flags & DKIM_SIGFLAG_PASSED) != 0 &&
			    bh == DKIM_SIGBH_MATCH)
			{
				fprintf(stdout,
				        "%s: %s: verification (s=%s, d=%s, %d-bit key) succeeded\n",
				        progname, file, selector, domain,
				        keysize);
			}
			else
			{
				const char *err;
				int errcode;

				errcode = dkim_sig_geterror(sig);
				err = dkim_sig_geterrorstr(errcode);

				if (selector != NULL || domain != NULL)
				{
					fprintf(stdout,
					        "%s: %s: verification (s=%s d=%s, %d-bit key) failed: %s\n",
					        progname, file, selector,
					        domain, keysize, err);
				}
				else
				{
					fprintf(stdout,
					        "%s: %s: verification failed: %s\n",
					        progname, file, err);
				}
			}
		}
		else if (sig == NULL && mode == DKIM_MODE_VERIFY)
		{
			fprintf(stdout, "%s: %s: message not signed\n",
			        progname, file);
		}
		else if (sig == NULL && mode == DKIM_MODE_SIGN)
		{
			const char *err;

			err = dkim_geterror(dkim);
			if (err == NULL)
				err = "unknown error";

			fprintf(stdout, "%s: %s: no signature added: %s\n",
			        progname, file, err);
		}
		else
		{
			if (tverbose < 2)
			{
				DKIM_STAT status;
				size_t hlen;
				size_t rem;
				unsigned char hdr[DKIM_MAXHEADER + 1];

				hlen = strlen(DKIM_SIGNHEADER);
				rem = sizeof hdr - hlen - 2;
				memset(hdr, '\0', sizeof hdr);

				sm_strlcpy(hdr, DKIM_SIGNHEADER ": ",
				           sizeof hdr);

				status = dkim_getsighdr(dkim,
				                        hdr + hlen + 2,
				                        rem,
				                        DKIM_HDRMARGIN,
				                        hlen + 2);

				if (status != DKIM_STAT_OK)
				{
					fprintf(stderr,
					        "%s: %s: dkim_getsighdr(): %s\n",
					        progname, file,
					        dkim_getresultstr(status));
				}
				else
				{
					fprintf(stdout,
					        "%s: %s:\n%s\n",
					        progname, file, hdr);
				}
			}
		}
	}
	else
	{
		fprintf(stdout, "%s: %s: no action\n", progname, file);
	}

	ms = mlfi_close((SMFICTX *) tctx);
	if (MLFI_OUTPUT(ms, tverbose))
	{
		fprintf(stderr, "%s: %s: mlfi_close() returned %s\n",
		        progname, file, milter_status[ms]);
	}

	return EX_OK;
}


syntax highlighted by Code2HTML, v. 0.9.1