/* ** 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 #include #include #include #include #include #include #include /* libsm includes */ #include #include #include /* libdkim includes */ #include /* 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; }