/*
** 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