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