/*
**  Copyright (c) 2007 Sendmail, Inc. and its suppliers.
**	All rights reserved.
**
**  $Id: dkim-ar.c,v 1.16 2007/12/18 22:44:13 msk Exp $
*/

#ifndef lint
static char dkim_ar_c_id[] = "@(#)$Id: dkim-ar.c,v 1.16 2007/12/18 22:44:13 msk Exp $";
#endif /* !lint */

/* system includes */
#include <sys/types.h>
#include <sys/param.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>
#ifdef ARTEST
# include <sysexits.h>
#endif /* ARTEST */

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

/* macros */
#define	ARES_ENDOF(x)		((x) + sizeof(x) - 1)
#define	ARES_STRORNULL(x)	((x) == NULL ? "(null)" : (x))

/* states */
#define AUTHRES_STATE_UNKNOWN	(-1)
#define AUTHRES_STATE_HOST	0
#define AUTHRES_STATE_METHOD	1
#define AUTHRES_STATE_RESULT	2
#define AUTHRES_STATE_PTYPE	3
#define AUTHRES_STATE_PROPERTY	4
#define AUTHRES_STATE_VALUE	5
#define AUTHRES_STATE_POSTVALUE	6

/* tables */
struct lookup
{
	char *	str;
	int	code;
};

struct lookup methods[] =
{
	{ "auth",		ARES_METHOD_AUTH },
	{ "dkim",		ARES_METHOD_DKIM },
	{ "dkim-ssp",		ARES_METHOD_DKIMSSP },
	{ "domainkeys",		ARES_METHOD_DOMAINKEYS },
	{ "iprev",		ARES_METHOD_IPREV },
	{ "senderid",		ARES_METHOD_SENDERID },
	{ "spf",		ARES_METHOD_SPF },
	{ NULL,			ARES_METHOD_UNKNOWN }
};

struct lookup aresults[] =
{
	{ "pass",		ARES_RESULT_PASS },
	{ "hardfail",		ARES_RESULT_HARDFAIL },
	{ "softfail",		ARES_RESULT_SOFTFAIL },
	{ "neutral",		ARES_RESULT_NEUTRAL },
	{ "temperror",		ARES_RESULT_TEMPERROR },
	{ "permerror",		ARES_RESULT_PERMERROR },
	{ NULL,			ARES_RESULT_UNKNOWN }
};

struct lookup ptypes[] =
{
	{ "smtp",		ARES_PTYPE_SMTP },
	{ "header",		ARES_PTYPE_HEADER },
	{ "body",		ARES_PTYPE_BODY },
	{ "policy",		ARES_PTYPE_POLICY },
	{ NULL,			ARES_PTYPE_UNKNOWN }
};

/*
**  ARES_TRIMSPACES -- trim trailing whitespace
**
**  Parameters:
**  	str -- string to modify
**
**  Return value:
**  	None.
*/

static void
ares_trimspaces(u_char *str)
{
	u_char *p;
	u_char *last;

	assert(str != NULL);

	last = NULL;

	for (p = str; *p != '\0'; p++)
	{
		if (isascii(*p) && isspace(*p) && last == NULL)
		{
			last = p;
			continue;
		}

		if (!isascii(*p) || !isspace(*p))
			last = NULL;
	}

	if (last != NULL)
		*last = '\0';
}

/*
**  ARES_CONVERT -- convert a string to its code
**
**  Parameters:
**  	table -- in which table to look up
**  	str -- string to find
**
**  Return value:
**  	A code translation of "str".
*/

static int
ares_convert(struct lookup *table, char *str)
{
	int c;

	assert(table != NULL);
	assert(str != NULL);

	for (c = 0; ; c++)
	{
		if (table[c].str == NULL ||
		    strcmp(table[c].str, str) == 0)
			return table[c].code;
	}

	/* NOTREACHED */
}

#ifdef ARTEST
/*
**  ARES_XCONVERT -- convert a code to its string
**
**  Parameters:
**  	table -- in which table to look up
**  	code -- code to find
**
**  Return value:
**  	A string translation of "code".
*/

static char *
ares_xconvert(struct lookup *table, int code)
{
	int c;

	assert(table != NULL);

	for (c = 0; ; c++)
	{
		if (table[c].str == NULL || table[c].code == code)
			return table[c].str;
	}

	/* NOTREACHED */
}

#endif /* ARTEST */

/*
**  AUTHRES_PARSE -- parse an Authentication-Results: header, return a
**                   structure containing a parsed result
**
**  Parameters:
**  	hdr -- NULL-terminated contents of an Authentication-Results:
**  	       header field
**  	ar -- a pointer to a (struct authres) loaded by values after parsing
**  
**  Return value:
**  	0 on success, -1 on failure.
*/

int
ares_parse(u_char *hdr, struct authres *ar)
{
	bool escaped;
	int paren;
	int n;
	int c;
	int state;
	u_char *p;
	u_char *q;
	u_char *end;
	char tmp[DKIM_MAXHEADER + 2];
	char tmp2[BUFRSZ + 1];

	assert(hdr != NULL);
	assert(ar != NULL);

	memset(ar, '\0', sizeof *ar);

	memset(tmp, '\0', sizeof tmp);

	/* first, a decomment pass */
	q = tmp;
	end = tmp + DKIM_MAXHEADER - 1;
	escaped = FALSE;
	paren = 0;

	for (p = hdr; *p != '\0' && q <= end; p++)
	{
		/* escaped character */
		if (escaped)
		{
			if (paren == 0)
			{
				/* preserve the escape on non-parentheses */
				if (*p != '(' && *p != ')')
				{
					*q = '\\';
					q++;
				}

				*q = *p;
				q++;
			}

			escaped = FALSE;
			continue;
		}

		/* backslash */
		if (*p == '\\' && !escaped)
		{
			escaped = TRUE;
			continue;
		}

		/* ) */
		if (*p == ')' && !escaped)
		{
			paren--;
			continue;
		}

		/* ( */
		if (*p == '(' && !escaped)
		{
			paren++;
			continue;
		}

		if (paren == 0)
		{
			*q = *p;
			q++;
		}
	}

	state = AUTHRES_STATE_HOST;
	q = ar->ares_host;
	end = ar->ares_host + sizeof ar->ares_host - 1;

	escaped = FALSE;
	n = 0;
	c = 0;

	/* now break it into its component pieces */
	for (p = tmp; *p != '\0'; p++)
	{
		switch (state)
		{
		  case AUTHRES_STATE_HOST:		/* hostname */
			if (*p == ';')
			{
				ares_trimspaces(ar->ares_host);
				state = AUTHRES_STATE_METHOD;

				q = tmp2;
				end = tmp2 + sizeof tmp2 - 1;
				memset(tmp2, '\0', sizeof tmp2);

				break;
			}

			if (ar->ares_host[0] != '\0' ||
			    !(isascii(*p) && isspace(*p)))
			{
				if (q < end)
				{
					*q = *p;
					q++;
				}
			}

			break;

		  case AUTHRES_STATE_METHOD:
			if (*p == '=')
			{
				ares_trimspaces(tmp2);

				n++;
				c = 0;

				/* too many! */
				if (n >= MAXARESULTS)
					return -1;

				ar->ares_result[n - 1].result_method = ares_convert(methods,
				                                                    tmp2);

				state = AUTHRES_STATE_RESULT;

				q = tmp2;
				memset(tmp2, '\0', sizeof tmp2);

				break;
			}

			if (tmp2[0] != '\0' || !(isascii(*p) && isspace(*p)))
			{
				if (q < end)
				{
					*q = *p;
					q++;
				}
			}

			break;

		  case AUTHRES_STATE_RESULT:
			if (isascii(*p) && isspace(*p))
			{
				ares_trimspaces(tmp2);

				ar->ares_result[n - 1].result_result = ares_convert(aresults,
				                                                    tmp2);

				state = AUTHRES_STATE_PTYPE;

				q = tmp2;
				memset(tmp2, '\0', sizeof tmp2);

				break;
			}

			if (tmp2[0] != '\0' || !(isascii(*p) && isspace(*p)))
			{
				if (q < end)
				{
					*q = *p;
					q++;
				}
			}

			break;

		  case AUTHRES_STATE_PTYPE:
			if (*p == '.')
			{
				ares_trimspaces(tmp2);

				c++;

				/* too many! */
				if (c >= MAXPROPS)
					return -1;

				ar->ares_result[n - 1].result_props = c;


				ar->ares_result[n - 1].result_ptype[c - 1] = ares_convert(ptypes,
				                                                          tmp2);

				state = AUTHRES_STATE_PROPERTY;

				q = ar->ares_result[n - 1].result_property[c - 1];
				end = ARES_ENDOF(ar->ares_result[n - 1].result_property[c - 1]);

				break;
			}

			if (ar->ares_result[n - 1].result_property[c - 1][0] != '\0' ||
			    !(isascii(*p) && isspace(*p)))
			{
				if (q < end)
				{
					*q = *p;
					q++;
				}
			}

			break;

		  case AUTHRES_STATE_PROPERTY:
			if (*p == '=')
			{
				ares_trimspaces(ar->ares_result[n - 1].result_property[c - 1]);

				/* XXX -- convert */

				state = AUTHRES_STATE_VALUE;

				q = ar->ares_result[n - 1].result_value[c - 1];
				end = ARES_ENDOF(ar->ares_result[n - 1].result_value[c - 1]);

				break;
			}

			if (ar->ares_result[n - 1].result_property[c - 1][0] != '\0' ||
			    !(isascii(*p) && isspace(*p)))
			{
				if (q < end)
				{
					*q = *p;
					q++;
				}
			}

			break;

		  case AUTHRES_STATE_VALUE:
			if (*p == ';')
			{
				ares_trimspaces(ar->ares_result[n - 1].result_value[c - 1]);

				state = AUTHRES_STATE_METHOD;

				ar->ares_result[n - 1].result_props = c;

				q = tmp2;
				end = tmp2 + sizeof tmp2 - 1;
				memset(tmp2, '\0', sizeof tmp2);

				break;
			}
			else if (isascii(*p) && isspace(*p))
			{
				ares_trimspaces(ar->ares_result[n - 1].result_value[c - 1]);

				state = AUTHRES_STATE_POSTVALUE;

				q = tmp2;
				end = tmp2 + sizeof tmp2 - 1;
				memset(tmp2, '\0', sizeof tmp2);

				break;
			}

			if (ar->ares_result[n - 1].result_value[c - 1][0] != '\0' ||
			    !(isascii(*p) && isspace(*p)))
			{
				if (q < end)
				{
					*q = *p;
					q++;
				}
			}

			break;

		  case AUTHRES_STATE_POSTVALUE:
			if (*p == ';')
			{
				state = AUTHRES_STATE_METHOD;
			}
			else if (!(isascii(*p) && isspace(*p)))
			{
				state = AUTHRES_STATE_PTYPE;

				q = tmp2;
				end = tmp2 + sizeof tmp2 - 1;
				memset(tmp2, '\0', sizeof tmp2);
				*q++ = *p;
			}
			break;

		  default:
			return -1;
		}
	}

	/* end-of-string may mean there was data to process */
	if (state == AUTHRES_STATE_VALUE &&
	    ar->ares_result[n - 1].result_value[c - 1][0] != '\0')
	{
		ares_trimspaces(ar->ares_result[n - 1].result_value[c - 1]);
	}
	else if (state == AUTHRES_STATE_RESULT && tmp2[0] != '\0')
	{
		ares_trimspaces(tmp2);
		ar->ares_result[n - 1].result_result = ares_convert(aresults,
		                                                    tmp2);
	}
	else
	{
		return -1;
	}

	ar->ares_count = n;

	return 0;
}

#ifdef ARTEST
/*
**  MAIN -- program mainline
**
**  Parameters:
**  	argc, argv -- the usual
**
**  Return value:
**  	EX_USAGE or EX_OK
*/

int
main(int argc, char **argv)
{
	int c;
	int d;
	int status;
	char *p;
	char *progname;
	struct authres ar;

	progname = (p = strrchr(argv[0], '/')) == NULL ? argv[0] : p + 1;

	if (argc != 2)
	{
		printf("%s: usage: %s header-value\n", progname, progname);
		return EX_USAGE;
	}

	status = ares_parse(argv[1], &ar);
	if (status == -1)
	{
		printf("%s: ares_parse() returned -1\n", progname);
		return EX_OK;
	}

	printf("%d result%s found\n", ar.ares_count,
	       ar.ares_count == 1 ? "" : "s");

	for (c = 0; c < ar.ares_count; c++)
	{
		printf("result #%d, %d propert%s\n", c,
		       ar.ares_result[c].result_props,
		       ar.ares_result[c].result_props == 1 ? "y" : "ies");

		printf("\tmethod \"%s\"\n",
		       ares_xconvert(methods,
		                     ar.ares_result[c].result_method));
		printf("\tresult \"%s\"\n",
		       ares_xconvert(aresults,
		                     ar.ares_result[c].result_result));

		for (d = 0; d < ar.ares_result[c].result_props; d++)
		{
			printf("\tproperty #%d\n", d);
			printf("\t\tptype \"%s\"\n",
			       ares_xconvert(ptypes,
			                     ar.ares_result[c].result_ptype[d]));
			printf("\t\tproperty \"%s\"\n",
			       ar.ares_result[c].result_property[d]);
			printf("\t\tvalue \"%s\"\n",
			       ar.ares_result[c].result_value[d]);
		}
	}
}
#endif /* ARTEST */


syntax highlighted by Code2HTML, v. 0.9.1