/* ** 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 #include #include #include #include #ifdef ARTEST # include #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 */