/*
** Copyright (c) 2005-2007 Sendmail, Inc. and its suppliers.
** All rights reserved.
**
** $Id: dkim-filter.c,v 1.290 2007/12/18 22:17:31 msk Exp $
*/
#ifndef lint
static char dkim_filter_c_id[] = "@(#)$Id: dkim-filter.c,v 1.290 2007/12/18 22:17:31 msk Exp $";
#endif /* !lint */
/* system includes */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#if SOLARIS
# if SOLARIS > 20700
# include <iso/limits_iso.h>
# else /* SOLARIS > 20700 */
# include <limits.h>
# endif /* SOLARIS > 20700 */
#endif /* SOLARIS */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <sysexits.h>
#include <errno.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <pthread.h>
#include <netdb.h>
#include <signal.h>
#include <regex.h>
#include <openssl/sha.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#if SOLARIS
# define _PATH_DEVNULL "/dev/null"
# ifndef _PATH_SENDMAIL
# define _PATH_SENDMAIL "/usr/sbin/sendmail"
# endif /* ! _PATH_SENDMAIL */
#else /* SOLARIS */
# include <paths.h>
#endif /* SOLARIS */
/* sendmail includes */
#include <sm/cdefs.h>
#include <sm/string.h>
/* libmilter includes */
#include "libmilter/mfapi.h"
/* libdkim includes */
#include <dkim.h>
#ifdef _FFR_VBR
# include <vbr.h>
#endif /* _FFR_VBR */
#if VERIFY_DOMAINKEYS
/* libdk includes */
#include <dk.h>
#endif /* VERIFY_DOMAINKEYS */
#if POPAUTH
/* libdb includes */
# include <db.h>
#endif /* POPAUTH */
/* dkim-filter includes */
#include "config.h"
#include "dkim-config.h"
#include "dkim-filter.h"
#include "dkim-ar.h"
#include "util.h"
#include "test.h"
#ifdef _FFR_STATS
# include "stats.h"
#endif /* _FFR_STATS */
/*
** Header -- a handle referring to a header
*/
typedef struct Header * Header;
struct Header
{
char * hdr_hdr;
char * hdr_val;
struct Header * hdr_next;
};
/*
** KEYTABLE -- table of keys
*/
struct keytable
{
char * key_selector; /* selector */
char * key_domain; /* domain */
regex_t key_re; /* regex for matching */
size_t key_len; /* key length */
unsigned char * key_data; /* private key data */
struct keytable * key_next; /* next record */
};
/*
** MSGCTX -- message context, containing transaction-specific data
*/
typedef struct msgctx * msgctx;
struct msgctx
{
bool mctx_addheader; /* Authentication-Results: */
bool mctx_signing; /* true iff signing */
bool mctx_headeronly; /* in EOM, only add headers */
#if VERIFY_DOMAINKEYS
bool mctx_dksigned; /* DK signature present */
#endif /* VERIFY_DOMAINKEYS */
#if _FFR_CAPTURE_UNKNOWN_ERRORS
bool mctx_capture; /* capture message? */
#endif /* _FFR_CAPTURE_UNKNOWN_ERRORS */
int mctx_status; /* status to report back */
dkim_canon_t mctx_hdrcanon; /* header canonicalization */
dkim_canon_t mctx_bodycanon; /* body canonicalization */
dkim_alg_t mctx_signalg; /* signature algorithm */
int mctx_queryalg; /* query algorithm */
int mctx_hdrbytes; /* header space allocated */
struct dkimf_dstring * mctx_tmpstr; /* temporary string */
char * mctx_jobid; /* job ID */
char * mctx_domain; /* domain doing the signing */
DKIM * mctx_dkim; /* DKIM handle */
#if VERIFY_DOMAINKEYS
DK * mctx_dk; /* DK handle */
#endif /* VERIFY_DOMAINKEYS */
#ifdef _FFR_VBR
VBR * mctx_vbr; /* VBR handle */
#endif /* _FFR_VBR */
struct Header * mctx_hqhead; /* header queue head */
struct Header * mctx_hqtail; /* header queue tail */
struct keytable * mctx_key; /* key information */
char mctx_hlist[MAXHEADERS]; /* header buffer */
};
/*
** CONNCTX -- connection context, containing thread-specific data
*/
typedef struct connctx * connctx;
struct connctx
{
bool cctx_noleadspc; /* no leading spaces */
char cctx_host[DKIM_MAXHOSTNAMELEN + 1];
/* hostname */
_SOCK_ADDR cctx_ip; /* IP info */
struct msgctx * cctx_msg; /* message context */
};
/*
** CONFIG -- configuration information
*/
typedef struct Config * Config;
struct Config
{
int cfg_nosig; /* no signature */
int cfg_badsig; /* bad signature */
int cfg_sigmissing; /* missing signature */
int cfg_dnserr; /* DNS error */
int cfg_internal; /* internal error */
int cfg_security; /* security concerns */
};
struct Config defaults =
{
SMFIS_ACCEPT,
/* SMFIS_REJECT, */ SMFIS_ACCEPT,
/* SMFIS_REJECT, */ SMFIS_ACCEPT,
SMFIS_TEMPFAIL,
SMFIS_TEMPFAIL,
SMFIS_TEMPFAIL
};
/*
** LOOKUP -- lookup table
*/
struct lookup
{
char * str;
int code;
};
#define CFG_DEFAULT 0
#define CFG_NOSIGNATURE 1
#define CFG_BADSIGNATURE 2
#define CFG_SIGMISSING 3
#define CFG_DNSERROR 4
#define CFG_INTERNAL 5
#define CFG_SECURITY 6
#define DKIMF_MODE_SIGNER 0x01
#define DKIMF_MODE_VERIFIER 0x02
#define DKIMF_MODE_DEFAULT (DKIMF_MODE_SIGNER|DKIMF_MODE_VERIFIER)
#define DKIMF_STATUS_GOOD 0
#define DKIMF_STATUS_BAD 1
#define DKIMF_STATUS_NOKEY 2
#define DKIMF_STATUS_REVOKED 3
#define DKIMF_STATUS_NOSIGNATURE 4
#define DKIMF_STATUS_BADFORMAT 5
#define DKIMF_STATUS_PARTIAL 6
#define DKIMF_STATUS_VERIFYERR 7
#define DKIMF_STATUS_SUSPICIOUS 8
#define DKIMF_STATUS_UNKNOWN 9
#define SIGMIN_BYTES 0
#define SIGMIN_PERCENT 1
#define SIGMIN_MAXADD 2
#define SSPDENYSMTP "550"
#define SSPDENYESC "5.7.1"
#define SSPDENYTEXT "rejected due to DKIM SSP evaluation"
struct lookup dkimf_params[] =
{
{ "no", CFG_NOSIGNATURE },
{ "nosignature", CFG_NOSIGNATURE },
{ "bad", CFG_BADSIGNATURE },
{ "badsignature", CFG_BADSIGNATURE },
{ "miss", CFG_SIGMISSING },
{ "signaturemissing", CFG_SIGMISSING },
{ "dns", CFG_DNSERROR },
{ "dnserror", CFG_DNSERROR },
{ "int", CFG_INTERNAL },
{ "internal", CFG_INTERNAL },
{ "sec", CFG_SECURITY },
{ "security", CFG_SECURITY },
{ "def", CFG_DEFAULT },
{ "default", CFG_DEFAULT },
{ NULL, -1 },
};
struct lookup dkimf_values[] =
{
{ "a", SMFIS_ACCEPT },
{ "accept", SMFIS_ACCEPT },
{ "d", SMFIS_DISCARD },
{ "discard", SMFIS_DISCARD },
{ "r", SMFIS_REJECT },
{ "reject", SMFIS_REJECT },
{ "t", SMFIS_TEMPFAIL },
{ "tempfail", SMFIS_TEMPFAIL },
{ NULL, -1 },
};
struct lookup dkimf_canon[] =
{
{ "relaxed", DKIM_CANON_RELAXED },
{ "simple", DKIM_CANON_SIMPLE },
{ NULL, -1 },
};
struct lookup dkimf_sign[] =
{
{ "rsa-sha1", DKIM_SIGN_RSASHA1 },
#ifdef SHA256_DIGEST_LENGTH
{ "rsa-sha256", DKIM_SIGN_RSASHA256 },
#endif /* SHA256_DIGEST_LENGTH */
{ NULL, -1 },
};
/* sender headers, in order */
char *senderhdr[] =
{
"Resent-Sender",
"Resent-From",
"Sender",
"From"
};
#define NSENDERHDRS 4
/* default internal list */
char *defilist[] =
{
"127.0.0.1",
NULL
};
/* PROTOTYPES */
sfsistat mlfi_abort __P((SMFICTX *));
sfsistat mlfi_body __P((SMFICTX *, u_char *, size_t));
sfsistat mlfi_close __P((SMFICTX *));
sfsistat mlfi_connect __P((SMFICTX *, char *, _SOCK_ADDR *));
sfsistat mlfi_envfrom __P((SMFICTX *, char **));
sfsistat mlfi_envrcpt __P((SMFICTX *, char **));
sfsistat mlfi_eoh __P((SMFICTX *));
sfsistat mlfi_eom __P((SMFICTX *));
sfsistat mlfi_header __P((SMFICTX *, char *, char *));
static void dkimf_cleanup __P((SMFICTX *));
static Header dkimf_findheader __P((msgctx, char *, int));
void *dkimf_getpriv __P((SMFICTX *));
char * dkimf_getsymval __P((SMFICTX *, char *));
sfsistat dkimf_insheader __P((SMFICTX *, int, char *, char *));
static void dkimf_report __P((msgctx, char *, char *));
void dkimf_sendprogress __P((void *));
sfsistat dkimf_setpriv __P((SMFICTX *, void *));
sfsistat dkimf_setreply __P((SMFICTX *, char *, char *, char *));
/* GLOBALS */
bool addxhdr; /* add identifying header? */
bool dolog; /* syslog interesting stuff? */
bool dolog_success; /* syslog successes too? */
bool quarantine; /* quarantine failures? */
bool no_i_whine; /* noted ${i} is undefined */
bool send_reports; /* send failure reports */
bool use_ssp_deny; /* use SSP "deny" directive? */
#if _FFR_REQUIRED_HEADERS
bool req_hdrs; /* required header checks */
#endif /* _FFR_REQUIRED_HEADERS */
bool subdomains; /* sign subdomains */
bool die; /* global "die" flag */
bool remarall; /* remove all matching ARs? */
bool remsigs; /* remove current signatures? */
bool testmode; /* test mode */
bool milterv2; /* using milter v2? */
#ifdef QUERY_CACHE
bool querycache; /* local query cache */
#endif /* QUERY_CACHE */
unsigned int tmo; /* DNS timeout */
unsigned int mode; /* operating mode */
int maxhdrsz; /* max header bytes */
int diesig; /* signal to distribute */
size_t keylen; /* size of secret key */
size_t sigmin; /* signature minimum */
int sigmintype; /* signature minimum type */
int thread_count; /* thread count */
#ifdef QUERY_CACHE
time_t cache_lastlog; /* last cache stats logged */
#endif /* QUERY_CACHE */
dkim_canon_t hdrcanon; /* canon. method for headers */
dkim_canon_t bodycanon; /* canon. method for body */
dkim_alg_t signalg; /* signing algorithm */
off_t signbytes; /* bytes to sign */
char *sock; /* listening socket */
char *progname; /* program name */
dkim_sigkey_t seckey; /* secret key data */
char *selector; /* key selector */
#ifdef _FFR_ZTAGS
char *diagdir; /* diagnostics directory */
#endif /* _FFR_ZTAGS */
#ifdef _FFR_SELECTOR_HEADER
char *selectorhdr; /* selector header */
#endif /* _FFR_SELECTOR_HEADER */
#ifdef _FFR_VBR
char *vbr_defcert; /* default VBR type */
char *vbr_deftype; /* default VBR certifiers */
char **vbr_trusted; /* trusted certifiers */
#endif /* _FFR_VBR */
struct Config conf; /* configuration */
DKIM_LIB *libdkim; /* libdkim handle */
#if VERIFY_DOMAINKEYS
DK_LIB *libdk; /* libdk handle */
#endif /* VERIFY_DOMAINKEYS */
struct keytable *keyhead; /* key list */
struct keytable *keytail; /* key list */
#ifdef _FFR_REPLACE_RULES
struct replace *replist; /* replacement list */
#endif /* _FFR_REPLACE_RULES */
Peer peerlist; /* queue of "peers" */
Peer internal; /* queue of "internal" hosts */
Peer exignore; /* "external ignore" hosts */
char **domains; /* domains to sign */
regex_t **dompats; /* domain patterns */
char **mtas; /* MTA ports to sign */
char **omithdrs; /* headers to omit */
char **alwayshdrs; /* always include headers */
char **macros; /* macros/values to check */
char **values; /* macros/values to check */
char **remar; /* A-R header removal list */
#if POPAUTH
DB *popdb; /* POP auth DB */
#endif /* POPAUTH */
pthread_mutex_t count_lock; /* counter lock */
pthread_mutex_t popen_lock; /* popen() lock */
#ifdef _FFR_STATS
char *statspath; /* path for stats DB */
#endif /* _FFR_STATS */
/* Other useful definitions */
#define CRLF "\r\n" /* CRLF */
#ifndef SENDMAIL_OPTIONS
# define SENDMAIL_OPTIONS "" /* options for reports */
#endif /* SENDMAIL_OPTIONS */
/* MACROS */
#define DKIM_DEBUG(x) (getenv("DKIMDEBUG") != NULL && \
strchr(getenv("DKIMDEBUG"), (x)) != NULL)
#define JOBID(x) ((x) == NULL ? JOBIDUNKNOWN : (x))
#define TRYFREE(x) do { \
if ((x) != NULL) \
{ \
free(x); \
(x) = NULL; \
} \
} while (0)
#define DKIMF_EOHMACROS "i {daemon_name} {auth_type}"
/*
** ==================================================================
** BEGIN private section
*/
#if NO_SMFI_INSHEADER
/*
** SMFI_INSHEADER -- stub for smfi_insheader() which didn't exist before
** sendmail 8.13.0
**
** Parameters:
** ctx -- milter context
** idx -- insertion index
** hname -- header name
** hvalue -- header value
**
** Return value:
** An sfsistat.
*/
sfsistat
smfi_insheader(SMFICTX *ctx, int idx, char *hname, char *hvalue)
{
assert(ctx != NULL);
assert(hname != NULL);
assert(hvalue != NULL);
return smfi_addheader(ctx, hname, hvalue);
}
#endif /* NO_SMFI_INSHEADER */
/*
** DKIMF_GETPRIV -- wrapper for smfi_getpriv()
**
** Parameters:
** ctx -- milter (or test) context
**
** Return value:
** The stored private pointer, or NULL.
*/
void *
dkimf_getpriv(SMFICTX *ctx)
{
assert(ctx != NULL);
if (testmode)
return dkimf_test_getpriv((void *) ctx);
else
return smfi_getpriv(ctx);
}
/*
** DKIMF_SETPRIV -- wrapper for smfi_setpriv()
**
** Parameters:
** ctx -- milter (or test) context
**
** Return value:
** An sfsistat.
*/
sfsistat
dkimf_setpriv(SMFICTX *ctx, void *ptr)
{
assert(ctx != NULL);
if (testmode)
return dkimf_test_setpriv((void *) ctx, ptr);
else
return smfi_setpriv(ctx, ptr);
}
/*
** DKIMF_INSHEADER -- wrapper for smfi_insheader()
**
** Parameters:
** ctx -- milter (or test) context
** idx -- index at which to insert
** hname -- header name
** hvalue -- header value
**
** Return value:
** An sfsistat.
*/
sfsistat
dkimf_insheader(SMFICTX *ctx, int idx, char *hname, char *hvalue)
{
assert(ctx != NULL);
assert(hname != NULL);
assert(hvalue != NULL);
if (testmode)
return dkimf_test_insheader(ctx, idx, hname, hvalue);
else
return smfi_insheader(ctx, idx, hname, hvalue);
}
/*
** DKIMF_SETREPLY -- wrapper for smfi_setreply()
**
** Parameters:
** ctx -- milter (or test) context
** rcode -- SMTP reply code
** xcode -- SMTP enhanced status code
** replytxt -- reply text
**
** Return value:
** An sfsistat.
*/
sfsistat
dkimf_setreply(SMFICTX *ctx, char *rcode, char *xcode, char *replytxt)
{
assert(ctx != NULL);
if (testmode)
return dkimf_test_setreply(ctx, rcode, xcode, replytxt);
else
return smfi_setreply(ctx, rcode, xcode, replytxt);
}
/*
** DKIMF_GETSYMVAL -- wrapper for smfi_getsymval()
**
** Parameters:
** ctx -- milter (or test) context
** sym -- symbol to retrieve
**
** Return value:
** Pointer to the value of the requested MTA symbol.
*/
char *
dkimf_getsymval(SMFICTX *ctx, char *sym)
{
assert(ctx != NULL);
assert(sym != NULL);
if (testmode)
return dkimf_test_getsymval(ctx, sym);
else
return smfi_getsymval(ctx, sym);
}
/*
** DKIMF_GETDKIM -- retrieve DKIM handle in use
**
** Parameters:
** vp -- opaque pointer (from test.c)
**
** Return value:
** DKIM handle in use, or NULL.
*/
DKIM *
dkimf_getdkim(void *vp)
{
struct connctx *cc;
assert(vp != NULL);
cc = vp;
if (cc->cctx_msg != NULL)
return cc->cctx_msg->mctx_dkim;
else
return NULL;
}
/*
** DKIMF_SIGHANDLER -- signal handler
**
** Parameters:
** sig -- signal received
**
** Return value:
** None.
*/
static void
dkimf_sighandler(int sig)
{
if (sig == SIGINT || sig == SIGTERM || sig == SIGHUP)
{
diesig = sig;
die = TRUE;
}
}
/*
** DKIMF_KILLCHILD -- kill child process
**
** Parameters:
** pid -- process ID to signal
** sig -- signal to use
**
** Return value:
** None.
*/
static void
dkimf_killchild(pid_t pid, int sig)
{
if (kill(pid, sig) == -1 && dolog)
{
syslog(LOG_ERR, "kill(%d, %d): %s", pid, sig,
strerror(errno));
}
}
/*
** DKIMF_ZAPKEY -- clobber the copy of the private key
**
** Parameters:
** None.
**
** Return value:
** None.
*/
static void
dkimf_zapkey(void)
{
if (seckey != NULL)
{
memset(seckey, '\0', keylen);
free(seckey);
}
if (keyhead != NULL)
{
struct keytable *key;
struct keytable *next;
key = keyhead;
while (key != NULL)
{
next = key->key_next;
memset(key->key_data, '\0', key->key_len);
free(key->key_data);
free(key->key_domain);
free(key);
key = next;
}
}
}
/*
** DKIMF_STDIO -- set up the base descriptors to go nowhere
**
** Parameters:
** None.
**
** Return value:
** None.
*/
static void
dkimf_stdio(void)
{
int devnull;
/* this only fails silently, but that's OK */
devnull = open(_PATH_DEVNULL, O_RDWR, 0);
if (devnull != -1)
{
(void) dup2(devnull, 0);
(void) dup2(devnull, 1);
(void) dup2(devnull, 2);
if (devnull > 2)
(void) close(devnull);
}
(void) setsid();
}
/*
** DKIMF_SENDPROGRESS -- tell the MTA "we're working on it!"
**
** Parameters:
** ctx -- context
**
** Return value:
** None (yet).
*/
void
dkimf_sendprogress(void *ctx)
{
assert(ctx != NULL);
(void) smfi_progress((SMFICTX *) ctx);
}
/*
** DKIMF_INITCONTEXT -- initialize filter context
**
** Parameters:
** None.
**
** Return value:
** A pointer to an allocated and initialized filter context, or NULL
** on failure.
**
** Side effects:
** Crop circles near Birmingham.
*/
static msgctx
dkimf_initcontext(void)
{
msgctx ctx;
ctx = (msgctx) malloc(sizeof(struct msgctx));
if (ctx == NULL)
return NULL;
(void) memset(ctx, '\0', sizeof(struct msgctx));
ctx->mctx_status = DKIMF_STATUS_UNKNOWN;
ctx->mctx_hdrcanon = hdrcanon;
ctx->mctx_bodycanon = bodycanon;
ctx->mctx_signalg = DKIM_SIGN_DEFAULT;
ctx->mctx_queryalg = DKIM_QUERY_DEFAULT;
return ctx;
}
/*
** DKIMF_LOG_SSL_ERRORS -- log any queued SSL library errors
**
** Parameters:
** jobid -- job ID to include in log messages
**
** Return value:
** None.
*/
static void
dkimf_log_ssl_errors(char *jobid)
{
assert(jobid != NULL);
/* log any queued SSL error messages */
if (ERR_peek_error() != 0 && dolog)
{
int n;
int saveerr;
u_long e;
char errbuf[BUFRSZ + 1];
char tmp[BUFRSZ + 1];
saveerr = errno;
memset(errbuf, '\0', sizeof errbuf);
for (n = 0; ; n++)
{
e = ERR_get_error();
if (e == 0)
break;
memset(tmp, '\0', sizeof tmp);
(void) ERR_error_string_n(e, tmp, sizeof tmp);
if (n != 0)
sm_strlcat(errbuf, "; ", sizeof errbuf);
sm_strlcat(errbuf, tmp, sizeof errbuf);
}
syslog(LOG_INFO, "%s SSL %s", jobid, errbuf);
errno = saveerr;
}
}
/*
** DKIMF_CLEANUP -- release local resources related to a message
**
** Parameters:
** ctx -- milter context
**
** Return value:
** None.
*/
static void
dkimf_cleanup(SMFICTX *ctx)
{
msgctx dfc;
connctx cc;
assert(ctx != NULL);
cc = (connctx) dkimf_getpriv(ctx);
if (cc == NULL)
return;
dfc = cc->cctx_msg;
/* release memory */
if (dfc != NULL)
{
if (dfc->mctx_hqhead != NULL)
{
Header hdr;
Header prev;
hdr = dfc->mctx_hqhead;
while (hdr != NULL)
{
TRYFREE(hdr->hdr_hdr);
TRYFREE(hdr->hdr_val);
prev = hdr;
hdr = hdr->hdr_next;
TRYFREE(prev);
}
}
if (dfc->mctx_dkim != NULL)
dkim_free(dfc->mctx_dkim);
#ifdef _FFR_VBR
if (dfc->mctx_vbr != NULL)
vbr_close(dfc->mctx_vbr);
#endif /* _FFR_VBR */
#if VERIFY_DOMAINKEYS
if (dfc->mctx_dk != NULL)
dk_free(dfc->mctx_dk);
#endif /* VERIFY_DOMAINKEYS */
if (dfc->mctx_tmpstr != NULL)
dkimf_dstring_free(dfc->mctx_tmpstr);
free(dfc);
cc->cctx_msg = NULL;
}
}
/*
** DKIMF_CONFIGLOOKUP -- look up the integer code for a config option or value
**
** Parameters:
** opt -- option to look up
** table -- lookup table to use
**
** Return value:
** Integer version of the option, or -1 on error.
*/
static int
dkimf_configlookup(char *opt, struct lookup *table)
{
int c;
for (c = 0; ; c++)
{
if (table[c].str == NULL ||
strcasecmp(opt, table[c].str) == 0)
return table[c].code;
}
}
/*
** DKIMF_PARSECONFIG2 -- parse a single configuration value
**
** Parameters:
** cfg -- pointer to data loaded from a configuration file
** name -- name of the parameter to query
** code -- equivalent code for the -C option
** str -- string to update
** len -- size of "str"
**
** Return value:
** None.
**
** Notes:
** This is transitional, for use during the time that the -C option
** and the configuration file coexist. The -C option overrides
** the values found in the file.
*/
static void
dkimf_parseconfig2(struct config *cfg, const char *name, const char *code,
char *str, size_t len)
{
char *v;
size_t offset;
assert(cfg != NULL);
assert(name != NULL);
assert(code != NULL);
assert(str != NULL);
assert(len > 0);
if (config_get(cfg, name, &v, sizeof(char *)) == 1)
{
offset = strlen(str);
snprintf(str + offset, len - offset, "%s%s=%s",
offset == 0 ? "" : ",", code, v);
}
}
/*
** DKIMF_PARSECONFIG -- parse configuration and/or apply defaults
**
** Parameters:
** config -- configuration string, or NULL to apply defaults only
**
** Return value:
** TRUE on success, FALSE on failure.
*/
static bool
dkimf_parseconfig(char *confstr)
{
int vs;
char *p;
char *v;
char *tmp;
/* load defaults */
memcpy(&conf, &defaults, sizeof(conf));
if (confstr == NULL)
return TRUE;
tmp = strdup(confstr);
if (tmp == NULL)
{
fprintf(stderr, "%s: strdup(): %s\n", progname,
strerror(errno));
return FALSE;
}
/* process configuration */
for (p = strtok(tmp, ","); p != NULL; p = strtok(NULL, ","))
{
v = strchr(p, '=');
if (v == NULL)
{
fprintf(stderr,
"%s: syntax error in configuration string\n",
progname);
return FALSE;
}
*v = '\0';
v++;
vs = dkimf_configlookup(v, dkimf_values);
if (vs == -1)
{
fprintf(stderr,
"%s: invalid configuration value `%s'\n",
progname, v);
return FALSE;
}
/* apply what's been found */
switch (dkimf_configlookup(p, dkimf_params))
{
case CFG_NOSIGNATURE:
conf.cfg_nosig = vs;
break;
case CFG_BADSIGNATURE:
conf.cfg_badsig = vs;
break;
case CFG_SIGMISSING:
conf.cfg_sigmissing = vs;
break;
case CFG_DNSERROR:
conf.cfg_dnserr = vs;
break;
case CFG_INTERNAL:
conf.cfg_internal = vs;
break;
case CFG_SECURITY:
conf.cfg_security = vs;
break;
case CFG_DEFAULT:
conf.cfg_nosig = vs;
conf.cfg_badsig = vs;
conf.cfg_sigmissing = vs;
conf.cfg_dnserr = vs;
conf.cfg_internal = vs;
conf.cfg_security = vs;
break;
default:
*v = '=';
fprintf(stderr,
"%s: invalid configuration parameter `%s'\n",
progname, p);
return FALSE;
}
}
return TRUE;
}
/*
** DKIMF_LIBSTATUS -- process a final status returned from libdkim
**
** Parameters:
** ctx -- milter context
** where -- what function reported the error
** status -- status returned by a libdk call (DKIM_STAT_*)
**
** Return value:
** An smfistat value to be returned to libmilter.
*/
static sfsistat
dkimf_libstatus(SMFICTX *ctx, char *where, int status)
{
int retcode = SMFIS_CONTINUE;
msgctx dfc;
connctx cc;
char *rcode = NULL;
char *xcode = NULL;
char *replytxt = NULL;
assert(ctx != NULL);
cc = dkimf_getpriv(ctx);
assert(cc != NULL);
dfc = cc->cctx_msg;
assert(dfc != NULL);
switch (status)
{
case DKIM_STAT_OK:
retcode = SMFIS_CONTINUE;
break;
case DKIM_STAT_INTERNAL:
retcode = conf.cfg_internal;
#ifdef _FFR_CAPTURE_UNKNOWN_ERRORS
dfc->mctx_capture = TRUE;
#endif /* _FFR_CAPTURE_UNKNOWN_ERRORS */
if (dolog)
{
const char *err;
err = dkim_geterror(dfc->mctx_dkim);
if (err == NULL)
err = strerror(errno);
syslog(LOG_ERR,
"%s: %s%sinternal error from libdkim: %s",
JOBID(dfc->mctx_jobid),
where == NULL ? "" : where,
where == NULL ? "" : ": ", err);
}
replytxt = "internal DKIM error";
break;
case DKIM_STAT_BADSIG:
retcode = conf.cfg_badsig;
if (dolog)
{
syslog(LOG_ERR, "%s: bad signature data",
JOBID(dfc->mctx_jobid));
}
replytxt = "bad DKIM signature data";
break;
case DKIM_STAT_NOSIG:
retcode = conf.cfg_nosig;
if (dolog)
{
syslog(LOG_ERR, "%s: no signature data",
JOBID(dfc->mctx_jobid));
}
replytxt = "no DKIM signature data";
break;
case DKIM_STAT_NORESOURCE:
retcode = conf.cfg_internal;
#ifdef _FFR_CAPTURE_UNKNOWN_ERRORS
dfc->mctx_capture = TRUE;
#endif /* _FFR_CAPTURE_UNKNOWN_ERRORS */
if (dolog)
{
const char *err;
err = dkim_geterror(dfc->mctx_dkim);
if (err == NULL)
err = strerror(errno);
syslog(LOG_ERR, "%s: %s%sresource unavailable: %s",
JOBID(dfc->mctx_jobid),
where == NULL ? "" : where,
where == NULL ? "" : ": ", err);
}
replytxt = "resource unavailable";
break;
case DKIM_STAT_CANTVRFY:
retcode = conf.cfg_badsig;
if (dolog)
{
const char *err;
err = dkim_geterror(dfc->mctx_dkim);
if (err == NULL)
err = "unknown cause";
syslog(LOG_ERR, "%s: signature verification failed: %s",
JOBID(dfc->mctx_jobid), err);
}
replytxt = "DKIM signature verification failed";
break;
case DKIM_STAT_KEYFAIL:
retcode = conf.cfg_dnserr;
if (dolog)
{
syslog(LOG_ERR, "%s: key retrieval failed",
JOBID(dfc->mctx_jobid));
}
replytxt = "DKIM key retrieval failed";
break;
case DKIM_STAT_SYNTAX:
retcode = conf.cfg_badsig;
if (dolog)
{
const char *err;
err = dkim_geterror(dfc->mctx_dkim);
if (err == NULL)
err = "unspecified";
syslog(LOG_ERR, "%s: syntax error: %s",
JOBID(dfc->mctx_jobid), err);
}
replytxt = "DKIM signature syntax error";
break;
}
switch (retcode)
{
case SMFIS_REJECT:
rcode = "550";
xcode = "5.7.0";
break;
case SMFIS_DISCARD:
rcode = "451";
xcode = "4.7.0";
break;
default:
break;
}
if (rcode != NULL && xcode != NULL && replytxt != NULL)
(void) dkimf_setreply(ctx, rcode, xcode, replytxt);
return retcode;
}
/*
** DKIMF_FINDHEADER -- find a header
**
** Parameters:
** dfc -- filter context
** hname -- name of the header of interest
** instance -- which instance is wanted (0 = first)
**
** Return value:
** Header handle, or NULL if not found.
*/
static Header
dkimf_findheader(msgctx dfc, char *hname, int instance)
{
Header hdr;
assert(dfc != NULL);
assert(hname != NULL);
hdr = dfc->mctx_hqhead;
while (hdr != NULL)
{
if (strcasecmp(hdr->hdr_hdr, hname) == 0)
{
if (instance == 0)
return hdr;
else
instance--;
}
hdr = hdr->hdr_next;
}
return NULL;
}
/*
** DKIMF_LOADKEYS -- load multiple keys
**
** Parameters:
** file -- input file
**
** Return value:
** 0 on success, !0 on failure
**
** Side effects:
** keyhead, keytail are updated.
*/
static int
dkimf_loadkeys(char *file)
{
bool blank = TRUE;
int line = 0;
FILE *f;
u_char *p;
char *re;
char *domain;
char *path;
char buf[BUFRSZ + 1];
char root[MAXPATHLEN + 1];
assert(file != NULL);
f = fopen(file, "r");
if (f == NULL)
{
fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
file, strerror(errno));
return -1;
}
memset(root, '\0', sizeof root);
memset(buf, '\0', sizeof buf);
while (fgets(buf, sizeof buf, f) != NULL)
{
/* skip comments */
if (buf[0] == '#')
continue;
blank = TRUE;
/* chomp trailing newline */
for (p = buf; *p != '\0'; p++)
{
if (*p == '\n')
{
*p = '\0';
break;
}
if (isascii(*p) && !isspace(*p))
blank = FALSE;
}
/* skip blank lines */
if (blank)
continue;
line++;
if (buf[0] == '/')
{
sm_strlcpy(root, buf, sizeof root);
if (root[strlen(root) - 1] != '/')
sm_strlcat(root, "/", sizeof root);
}
re = NULL;
domain = NULL;
path = NULL;
/*
** File format: <sender-glob>:<signing-domain>:<path-to-key>
**
** This means if the sender matches <sender-glob>,
** sign with d=<signing-domain> and use the private
** key found in <path-to-key>, using the filename portion
** of the latter as the name of the selector.
**
** <sender-glob> is string matching using "*" as a wildcard
** character.
**
** #-delimited comments are allowed. Blank lines are ignored.
**
** A line starting with "/" is interpreted as a root directory
** for keys, meaning the <path-to-key> values after that
** in the file are taken relative to that path.
*/
re = strtok(buf, ":");
domain = strtok(NULL, ":");
if (domain != NULL)
path = strtok(NULL, ":");
if (re == NULL || domain == NULL || path == NULL)
{
fprintf(stderr, "%s: %s: line %d: malformed\n",
progname, file, line);
fclose(f);
return -1;
}
else
{
int status;
char *r;
char *end;
size_t n;
struct keytable *new;
int keyfd;
struct stat s;
char retmp[BUFRSZ];
char fn[MAXPATHLEN + 1];
dkimf_mkpath(fn, sizeof fn, root, path);
keyfd = open(fn, O_RDONLY, 0);
if (keyfd == -1 && errno == ENOENT)
{
/* try appending ".pem" */
sm_strlcat(fn, ".pem", sizeof fn);
keyfd = open(fn, O_RDONLY, 0);
}
if (keyfd == -1 && errno == ENOENT)
{
/* try appending ".private" */
dkimf_mkpath(fn, sizeof fn, root, path);
sm_strlcat(fn, ".private", sizeof fn);
keyfd = open(fn, O_RDONLY, 0);
}
if (keyfd == -1)
{
dkimf_mkpath(fn, sizeof fn, root, path);
fprintf(stderr, "%s: %s: open(): %s\n",
progname, fn, strerror(errno));
fclose(f);
return -1;
}
status = fstat(keyfd, &s);
if (status == -1)
{
fprintf(stderr, "%s: %s: fstat(): %s\n",
progname, path, strerror(errno));
close(keyfd);
fclose(f);
return -1;
}
*p = '\0';
end = retmp + sizeof retmp - 1;
memset(retmp, '\0', sizeof retmp);
retmp[0] = '^';
r = &retmp[1];
for (p = re; *p != '\0'; p++)
{
switch (*p)
{
case '*':
if (r + 3 >= end)
{
fprintf(stderr,
"%s: %s: line %d: \"%s\": expression too large\n",
progname, file, line,
re);
close(keyfd);
fclose(f);
return -1;
}
(void) sm_strlcat(retmp, ".*",
sizeof retmp);
r += 2;
break;
case '.':
case '$':
case '[':
case ']':
case '(':
case ')':
if (r + 3 >= end)
{
fprintf(stderr,
"%s: %s: line %d: \"%s\": expression too large\n",
progname, file, line,
re);
close(keyfd);
fclose(f);
return -1;
}
*r = '\\';
r++;
/* FALLTHROUGH */
default:
*r = *p;
r++;
break;
}
}
(void) sm_strlcat(retmp, "$", sizeof retmp);
new = (struct keytable *) malloc(sizeof(struct keytable));
if (new == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
close(keyfd);
fclose(f);
return -1;
}
new->key_next = NULL;
r = strrchr(path, '/');
new->key_selector = strdup(r == NULL ? path : r + 1);
if (new->key_selector == NULL)
{
fprintf(stderr, "%s: strdup(): %s\n",
progname, strerror(errno));
close(keyfd);
fclose(f);
return -1;
}
if (domain[0] != '*')
new->key_domain = strdup(domain);
else
new->key_domain = NULL;
status = regcomp(&new->key_re, retmp,
(REG_ICASE|REG_EXTENDED));
if (status != 0)
{
char err[BUFRSZ];
memset(err, '\0', sizeof err);
(void) regerror(status, &new->key_re,
err, sizeof err);
fprintf(stderr,
"%s: %s: line %d: regcomp(): %s\n",
progname, file, line, err);
close(keyfd);
fclose(f);
return -1;
}
new->key_data = malloc(s.st_size);
if (new->key_data == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
close(keyfd);
fclose(f);
return -1;
}
new->key_len = s.st_size;
n = read(keyfd, new->key_data, s.st_size);
if (n < s.st_size)
{
if (n == -1)
{
fprintf(stderr, "%s: %s: read(): %s\n",
progname, path,
strerror(errno));
}
else
{
fprintf(stderr,
"%s: %s: read() truncated\n",
progname, path);
}
close(keyfd);
fclose(f);
return -1;
}
close(keyfd);
if (keyhead == NULL)
{
keyhead = new;
keytail = new;
}
else
{
keytail->key_next = new;
keytail = new;
}
}
}
fclose(f);
return 0;
}
/*
** DKIMF_REPORT -- generate a report on failure if possible
**
** Parameters:
** dfc -- message context
** result -- result to report
** reason -- reason to report
**
** Return value:
** None.
*/
static void
dkimf_report(msgctx dfc, char *result, char *reason)
{
int bfd = -1;
int hfd = -1;
int status;
DKIM_STAT dkstatus;
size_t inl;
size_t outl;
FILE *out;
BIO *b64;
BIO *bout;
DKIM_SIGINFO *sig;
struct Header *hdr;
char buf[BUFRSZ];
char addr[MAXADDRESS + 1];
char hostname[DKIM_MAXHOSTNAMELEN + 1];
assert(dfc != NULL);
if (!send_reports)
return;
memset(addr, '\0', sizeof addr);
memset(hostname, '\0', sizeof hostname);
(void) gethostname(hostname, sizeof hostname);
sig = dkim_getsignature(dfc->mctx_dkim);
dkstatus = dkim_reportinfo(dfc->mctx_dkim, sig, &hfd, &bfd,
addr, sizeof addr);
if (dkstatus != DKIM_STAT_OK || addr[0] == '\0')
return;
pthread_mutex_lock(&popen_lock);
out = popen(_PATH_SENDMAIL " -t" SENDMAIL_OPTIONS, "w");
pthread_mutex_unlock(&popen_lock);
if (out == NULL)
{
if (dolog)
{
syslog(LOG_ERR, "%s: popen(): %s", dfc->mctx_jobid,
strerror(errno));
}
return;
}
/* we presume sendmail will add From: and Date: ... */
/* To: */
fprintf(out, "To: %s\n", addr);
/* Subject: */
fprintf(out, "Subject: DKIM failure report for %s\n",
dfc->mctx_jobid);
/* MIME stuff */
fprintf(out, "MIME-Version: 1.0\n");
fprintf(out,
"Content-Type: multipart/report; boundary=\"dkimreport/%s/%s\"",
hostname, dfc->mctx_jobid);
/* ok, now then... */
fprintf(out, "\n");
/* first part: a text blob explaining what this is */
fprintf(out, "--dkimreport/%s/%s\n", hostname, dfc->mctx_jobid);
fprintf(out, "Content-Type: text/plain\n");
fprintf(out, "\n");
fprintf(out, "DKIM failure report for job %s on %s\n\n",
dfc->mctx_jobid, hostname);
fprintf(out,
"The canonicalized form of the failed message is attached.\n");
fprintf(out, "\n");
/* second part: formatted gunk */
fprintf(out, "--dkimreport/%s/%s\n", hostname, dfc->mctx_jobid);
fprintf(out, "Content-Type: message/x-dkim-report\n");
fprintf(out, "\n");
fprintf(out, "MTA: %s\n", hostname);
fprintf(out, "Agent: %s %s\n", DKIMF_PRODUCT, DKIMF_VERSION);
fprintf(out, "Result: %s\n", result == NULL ? "(none)" : result);
fprintf(out, "Reason: %s\n", reason == NULL ? "(none)" : reason);
fprintf(out, "\n");
/* third part: a multipart containing the interesting stuff */
fprintf(out, "--dkimreport/%s/%s\n", hostname, dfc->mctx_jobid);
fprintf(out, "Content-Type: multipart/mixed; boundary=\"dkimreport/%s/%s-parts\"\n",
hostname, dfc->mctx_jobid);
fprintf(out, "\n");
/* first subpart: headers */
fprintf(out, "--dkimreport/%s/%s-parts\n", hostname, dfc->mctx_jobid);
fprintf(out, "Content-Type: text/rfc822-headers\n");
fprintf(out, "\n");
for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
fprintf(out, "%s: %s\n", hdr->hdr_hdr, hdr->hdr_val);
fprintf(out, "\n");
/* second subpart: canonicalized form */
fprintf(out, "--dkimreport/%s/%s-parts\n", hostname, dfc->mctx_jobid);
fprintf(out, "Content-Type: text/plain\n"); /* ? */
fprintf(out, "Content-Disposition: attachment; filename=\"%s.txt\"\n",
dfc->mctx_jobid);
if (bfd != -1)
fprintf(out, "Content-Description: Canonicalized headers\n");
else
fprintf(out, "Content-Description: Canonicalized message\n");
fprintf(out, "Content-Transfer-Encoding: base64\n");
fprintf(out, "\n");
(void) lseek(hfd, SEEK_SET, 0);
bout = BIO_new(BIO_s_file());
BIO_set_fp(bout, out, BIO_NOCLOSE);
b64 = BIO_new(BIO_f_base64());
bout = BIO_push(b64, bout);
for (;;)
{
inl = read(hfd, buf, sizeof buf);
if (inl == 0)
break;
outl = BIO_write(bout, (char *) buf, inl);
if (outl != inl || inl < sizeof buf)
break;
}
BIO_flush(bout);
BIO_free(bout);
/* third subpart: canonicalized form (body) */
if (bfd != -1)
{
fprintf(out,
"\n--dkimreport/%s/%s-parts\n", hostname,
dfc->mctx_jobid);
fprintf(out, "Content-Type: text/plain\n"); /* ? */
fprintf(out,
"Content-Disposition: attachment; filename=\"%s-body.txt\"\n",
dfc->mctx_jobid);
fprintf(out, "Content-Description: Canonicalized body\n");
fprintf(out, "Content-Transfer-Encoding: base64\n");
fprintf(out, "\n");
(void) lseek(bfd, SEEK_SET, 0);
bout = BIO_new(BIO_s_file());
BIO_set_fp(bout, out, BIO_NOCLOSE);
b64 = BIO_new(BIO_f_base64());
bout = BIO_push(b64, bout);
for (;;)
{
inl = read(bfd, buf, sizeof buf);
if (inl == 0)
break;
outl = BIO_write(bout, (char *) buf, inl);
if (outl != inl || inl < sizeof buf)
break;
}
BIO_flush(bout);
BIO_free(bout);
}
/* end */
fprintf(out, "\n--dkimreport/%s/%s-parts--\n", hostname,
dfc->mctx_jobid);
fprintf(out, "\n--dkimreport/%s/%s--\n", hostname, dfc->mctx_jobid);
/* send it */
pthread_mutex_lock(&popen_lock);
status = pclose(out);
pthread_mutex_unlock(&popen_lock);
if (status != 0 && dolog)
{
if (dolog)
{
syslog(LOG_ERR, "%s: pclose(): %s", dfc->mctx_jobid,
strerror(errno));
}
}
}
/*
** END private section
** ==================================================================
** BEGIN milter section
*/
#if SMFI_VERSION >= 0x01000000
/*
** MLFI_NEGOTIATE -- handler called on new SMTP connection to negotiate
** MTA options
**
** Parameters:
** ctx -- milter context
** f0 -- actions offered by the MTA
** f1 -- protocol steps offered by the MTA
** f2 -- reserved for future extensions
** f3 -- reserved for future extensions
** pf0 -- actions requested by the milter
** pf1 -- protocol steps requested by the milter
** pf2 -- reserved for future extensions
** pf3 -- reserved for future extensions
**
** Return value:
** An SMFIS_* constant.
*/
static sfsistat
mlfi_negotiate(SMFICTX *ctx,
unsigned long f0, unsigned long f1,
SM_UNUSED(unsigned long f2),
SM_UNUSED(unsigned long f3),
unsigned long *pf0, unsigned long *pf1,
unsigned long *pf2, unsigned long *pf3)
{
unsigned long reqactions = (SMFIF_ADDHDRS|SMFIF_CHGHDRS);
unsigned long wantactions = (SMFIF_SETSYMLIST);
unsigned long protosteps = (SMFIP_NOHELO|SMFIP_NORCPT|SMFIP_NOUNKNOWN|SMFIP_NODATA|SMFIP_SKIP);
connctx cc;
/* verify the actions we need are available */
if (quarantine)
reqactions |= SMFIF_QUARANTINE;
# ifdef _FFR_CAPTURE_UNKNOWN_ERRORS
reqactions |= SMFIF_QUARANTINE;
# endif /* _FFR_CAPTURE_UNKNOWN_ERRORS */
if ((f0 & reqactions) != reqactions)
{
if (dolog)
{
syslog(LOG_ERR,
"mlfi_negotiate(): required milter action(s) not available (got 0x%lx, need 0x%lx)",
f0, reqactions);
}
return SMFIS_REJECT;
}
/* also try to get some nice features */
wantactions = (wantactions & f0);
/* set the actions we want */
*pf0 = (reqactions | wantactions);
/* disable as many protocol steps we don't need as are available */
*pf1 = (protosteps & f1);
# ifdef SMFIP_HDR_LEADSPC
/* request preservation of leading spaces if possible */
if ((f1 & SMFIP_HDR_LEADSPC) != 0)
{
cc = malloc(sizeof(struct connctx));
if (cc != NULL)
{
memset(cc, '\0', sizeof(struct connctx));
cc->cctx_noleadspc = TRUE;
*pf1 |= SMFIP_HDR_LEADSPC;
dkimf_setpriv(ctx, cc);
}
}
# endif /* SMFIP_HDR_LEADSPC */
*pf2 = 0;
*pf3 = 0;
/* request macros if able */
if (macros != NULL && (wantactions & SMFIF_SETSYMLIST) != 0)
{
int c;
char macrolist[BUFRSZ];
memset(macrolist, '\0', sizeof macrolist);
sm_strlcpy(macrolist, DKIMF_EOHMACROS, sizeof macrolist);
for (c = 0; macros[c] != NULL; c++)
{
if (macrolist[0] != '\0')
sm_strlcat(macrolist, " ", sizeof macrolist);
if (sm_strlcat(macrolist, macros[c],
sizeof macrolist) >= sizeof macrolist)
{
if (dolog)
{
syslog(LOG_ERR,
"mlfi_negotiate(): macro list overflow");
}
return SMFIS_REJECT;
}
if (smfi_setsymlist(ctx, SMFIM_EOH,
macrolist) != MI_SUCCESS)
{
if (dolog)
{
syslog(LOG_ERR,
"smfi_setsymlist() failed");
}
return SMFIS_REJECT;
}
}
}
/* set "milterv2" flag if SMFIP_SKIP was available */
if ((f1 & SMFIP_SKIP) != 0)
milterv2 = TRUE;
return SMFIS_CONTINUE;
}
#endif /* SMFI_VERSION >= 0x01000000 */
/*
** MLFI_CONNECT -- connection handler
**
** Parameters:
** ctx -- milter context
** host -- hostname
** ip -- address, in in_addr form
**
** Return value:
** An SMFIS_* constant.
*/
sfsistat
mlfi_connect(SMFICTX *ctx, char *host, _SOCK_ADDR *ip)
{
connctx cc;
if (DKIM_DEBUG('t'))
{
pthread_mutex_lock(&count_lock);
thread_count++;
syslog(LOG_INFO, "thread %p connect (%d)", pthread_self(),
thread_count);
pthread_mutex_unlock(&count_lock);
}
/* if the client is on an ignored host, then ignore it */
if (peerlist != NULL)
{
/* try hostname, if available */
if (host != NULL && host[0] != '\0' && host[0] != '[')
{
dkimf_lowercase(host);
if (dkimf_checkhost(peerlist, host))
return SMFIS_ACCEPT;
}
/* try IP address, if available */
if (ip != NULL && ip->sa_family == AF_INET)
{
if (dkimf_checkip(peerlist, ip))
return SMFIS_ACCEPT;
}
}
/* copy hostname and IP information to a connection context */
cc = dkimf_getpriv(ctx);
if (cc == NULL)
{
cc = malloc(sizeof(struct connctx));
if (cc == NULL)
{
if (dolog)
{
syslog(LOG_ERR, "%s malloc(): %s", host,
strerror(errno));
}
return SMFIS_TEMPFAIL;
}
memset(cc, '\0', sizeof(struct connctx));
}
sm_strlcpy(cc->cctx_host, host, sizeof cc->cctx_host);
if (ip == NULL)
{
struct sockaddr_in sin;
memset(&sin, '\0', sizeof sin);
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
memcpy(&cc->cctx_ip, &sin, sizeof(cc->cctx_ip));
}
else
{
memcpy(&cc->cctx_ip, ip, sizeof(cc->cctx_ip));
}
cc->cctx_msg = NULL;
dkimf_setpriv(ctx, cc);
return SMFIS_CONTINUE;
}
/*
** MLFI_ENVFROM -- handler for MAIL FROM command (start of message)
**
** Parameters:
** ctx -- milter context
** envfrom -- envelope from arguments
**
** Return value:
** An SMFIS_* constant.
*/
sfsistat
mlfi_envfrom(SMFICTX *ctx, char **envfrom)
{
connctx cc;
msgctx dfc;
assert(ctx != NULL);
assert(envfrom != NULL);
cc = (connctx) dkimf_getpriv(ctx);
assert(cc != NULL);
if (DKIM_DEBUG('t'))
syslog(LOG_INFO, "thread %p envfrom", pthread_self());
/*
** Initialize a filter context.
*/
dkimf_cleanup(ctx);
dfc = dkimf_initcontext();
if (dfc == NULL)
{
if (dolog)
{
syslog(LOG_INFO,
"message requeueing (internal error)");
}
dkimf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
/*
** Save it in this thread's private space.
*/
cc->cctx_msg = dfc;
/*
** Continue processing.
*/
return SMFIS_CONTINUE;
}
/*
** MLFI_HEADER -- handler for mail headers; stores the header in a vector
** of headers for later perusal, removing RFC822 comment
** substrings
**
** Parameters:
** ctx -- milter context
** headerf -- header
** headerv -- value
**
** Return value:
** An SMFIS_* constant.
*/
sfsistat
mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
{
msgctx dfc;
connctx cc;
Header newhdr;
#ifdef _FFR_ANTICIPATE_SENDMAIL_MUNGE
char *end;
char *p;
#endif /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */
assert(ctx != NULL);
assert(headerf != NULL);
assert(headerv != NULL);
if (DKIM_DEBUG('t'))
syslog(LOG_INFO, "thread %p header", pthread_self());
cc = (connctx) dkimf_getpriv(ctx);
assert(cc != NULL);
dfc = cc->cctx_msg;
assert(dfc != NULL);
/* check for too much header data */
if (maxhdrsz > 0 &&
dfc->mctx_hdrbytes + strlen(headerf) + strlen(headerv) + 2 > maxhdrsz)
{
if (dolog)
syslog(LOG_NOTICE, "too much header data");
return conf.cfg_security;
}
newhdr = (Header) malloc(sizeof(struct Header));
if (newhdr == NULL)
{
if (dolog)
syslog(LOG_ERR, "malloc(): %s", strerror(errno));
dkimf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
(void) memset(newhdr, '\0', sizeof(struct Header));
newhdr->hdr_hdr = strdup(headerf);
if (dfc->mctx_tmpstr == NULL)
{
dfc->mctx_tmpstr = dkimf_dstring_new(BUFRSZ, 0);
if (dfc->mctx_tmpstr == NULL)
{
if (dolog)
syslog(LOG_ERR, "dkimf_dstring_new() failed");
return SMFIS_TEMPFAIL;
}
}
else
{
dkimf_dstring_blank(dfc->mctx_tmpstr);
}
#ifdef _FFR_ANTICIPATE_SENDMAIL_MUNGE
/*
** The sendmail MTA does some minor header rewriting on outgoing
** mail. This makes things slightly prettier for the MUA, but
** these changes are made after this filter has already generated
** and added a signature. As a result, verification of the
** signature will fail because what got signed isn't the same
** as what actually goes out. This chunk of code attempts to
** compensate by arranging to feed to the canonicalization
** algorithms the headers exactly as the MTA will modify them, so
** verification should still work. This is based on experimentation
** and on reading sendmail/headers.c, and may require more tweaking
** before it's precisely right.
**
** There are other munges the sendmail MTA makes which are not
** addressed by this patch.
**
** This should not be used with sendmail 8.14 and later as it
** is not required; that version of sendmail and libmilter
** handles the munging correctly (by suppressing it).
*/
for (p = headerv; *p != '\0'; p++)
{
/* skip initial spaces */
if (p == headerv && isascii(*p) && isspace(*p))
continue;
dkimf_dstring_cat1(dfc->mctx_tmpstr, *p);
}
#else /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */
dkimf_dstring_copy(dfc->mctx_tmpstr, headerv);
#endif /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */
#ifdef _FFR_REPLACE_RULES
if (replist != NULL) /* XXX -- signing mode only? */
{
int status;
regmatch_t match;
char *str;
struct dkimf_dstring *tmphdr = NULL;
struct replace *rep;
tmphdr = dkimf_dstring_new(BUFRSZ, 0);
if (tmphdr == NULL)
{
if (dolog)
syslog(LOG_ERR, "dkimf_dstring_new() failed");
return SMFIS_TEMPFAIL;
}
for (rep = replist; rep != NULL; rep = rep->repl_next)
{
str = dkimf_dstring_get(dfc->mctx_tmpstr);
for (;;)
{
status = regexec(&rep->repl_re, str, 1,
&match, 0);
if (status == REG_NOMATCH)
{
break;
}
else if (status != 0)
{
if (dolog)
{
syslog(LOG_ERR,
"regexec() failed");
}
return SMFIS_TEMPFAIL;
}
dkimf_dstring_blank(tmphdr);
dkimf_dstring_copy(tmphdr, str);
dkimf_dstring_chop(tmphdr, match.rm_so);
dkimf_dstring_cat(tmphdr, rep->repl_txt);
dkimf_dstring_cat(tmphdr, str + match.rm_eo);
dkimf_dstring_blank(dfc->mctx_tmpstr);
str = dkimf_dstring_get(tmphdr);
dkimf_dstring_cat(dfc->mctx_tmpstr, str);
}
}
dkimf_dstring_free(tmphdr);
}
#endif /* _FFR_REPLACE_RULES */
newhdr->hdr_val = strdup(dkimf_dstring_get(dfc->mctx_tmpstr));
newhdr->hdr_next = NULL;
if (newhdr->hdr_hdr == NULL || newhdr->hdr_val == NULL)
{
if (dolog)
syslog(LOG_ERR, "malloc(): %s", strerror(errno));
TRYFREE(newhdr->hdr_hdr);
TRYFREE(newhdr->hdr_val);
TRYFREE(newhdr);
dkimf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
dfc->mctx_hdrbytes += strlen(newhdr->hdr_hdr) + 1;
dfc->mctx_hdrbytes += strlen(newhdr->hdr_val) + 1;
if (dfc->mctx_hqhead == NULL)
dfc->mctx_hqhead = newhdr;
if (dfc->mctx_hqtail != NULL)
dfc->mctx_hqtail->hdr_next = newhdr;
dfc->mctx_hqtail = newhdr;
#if _FFR_SELECT_CANONICALIZATION
if (strcasecmp(headerf, XSELECTCANONHDR) == 0)
{
int c;
char *slash;
slash = strchr(headerv, '/');
if (slash != NULL)
{
*slash = '\0';
c = dkimf_configlookup(headerv, dkimf_canon);
if (c != -1)
dfc->mctx_hdrcanon = (dkim_canon_t) c;
c = dkimf_configlookup(slash + 1, dkimf_canon);
if (c != -1)
dfc->mctx_bodycanon = (dkim_canon_t) c;
*slash = '/';
}
else
{
c = dkimf_configlookup(headerv, dkimf_canon);
if (c != -1)
dfc->mctx_hdrcanon = (dkim_canon_t) c;
}
/* XXX -- eat this header? */
}
#endif /* _FFR_SELECT_CANONICALIZATION */
#if VERIFY_DOMAINKEYS
if (strcasecmp(headerf, DK_SIGNHEADER) == 0)
dfc->mctx_dksigned = TRUE;
#endif /* VERIFY_DOMAINKEYS */
return SMFIS_CONTINUE;
}
/*
** MLFI_EOH -- handler called when there are no more headers
**
** Parameters:
** ctx -- milter context
**
** Return value:
** An SMFIS_* constant.
*/
sfsistat
mlfi_eoh(SMFICTX *ctx)
{
char last;
bool domainok;
bool originok;
int c;
DKIM_STAT status;
connctx cc;
msgctx dfc;
char *p;
char *user;
#ifdef _FFR_VBR
char *vbr_cert = NULL;
char *vbr_type = NULL;
#endif /* _FFR_VBR */
Header from;
Header hdr;
char addr[MAXADDRESS + 1];
assert(ctx != NULL);
if (DKIM_DEBUG('t'))
syslog(LOG_INFO, "thread %p eoh", pthread_self());
cc = (connctx) dkimf_getpriv(ctx);
assert(cc != NULL);
dfc = cc->cctx_msg;
assert(dfc != NULL);
/*
** Determine the message ID for logging.
*/
dfc->mctx_jobid = dkimf_getsymval(ctx, "i");
if (dfc->mctx_jobid == NULL)
dfc->mctx_jobid = JOBIDUNKNOWN;
#if _FFR_REQUIRED_HEADERS
/* if requested, verify RFC2822-required headers */
if (req_hdrs)
{
bool ok = TRUE;
/* exactly one From: */
if (dkimf_findheader(dfc, "From", 0) == NULL ||
dkimf_findheader(dfc, "From", 1) != NULL)
ok = FALSE;
/* exactly one Date: */
if (dkimf_findheader(dfc, "Date", 0) == NULL ||
dkimf_findheader(dfc, "Date", 1) != NULL)
ok = FALSE;
if (!ok)
{
if (dolog)
{
syslog(LOG_INFO,
"%s RFC2822-required header(s) missing",
dfc->mctx_jobid);
}
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
dfc->mctx_status = DKIMF_STATUS_BADFORMAT;
return SMFIS_CONTINUE;
}
}
#endif /* _FFR_REQUIRED_HEADERS */
/* find the Sender: or From: header */
memset(addr, '\0', sizeof addr);
for (c = 0; c < NSENDERHDRS; c++)
{
from = dkimf_findheader(dfc, senderhdr[c], 0);
if (from != NULL)
break;
}
if (from == NULL)
{
if (dolog)
{
syslog(LOG_INFO,
"%s: no sender header found; accepting",
dfc->mctx_jobid);
}
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
dfc->mctx_status = DKIMF_STATUS_BADFORMAT;
return SMFIS_CONTINUE;
}
/* extract the sender's domain */
sm_strlcpy(addr, from->hdr_val, sizeof addr);
status = rfc2822_mailbox_split(addr, &user, &dfc->mctx_domain);
if (status != 0 || user == NULL || dfc->mctx_domain == NULL ||
user[0] == '\0' || dfc->mctx_domain[0] == '\0')
{
if (dolog)
{
syslog(LOG_INFO, "%s: can't parse From: header",
dfc->mctx_jobid);
}
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
dfc->mctx_status = DKIMF_STATUS_BADFORMAT;
return SMFIS_CONTINUE;
}
/* assume we're not signing */
dfc->mctx_signalg = DKIM_SIGN_UNKNOWN;
dfc->mctx_signing = FALSE;
domainok = FALSE;
originok = FALSE;
/* is it a domain we sign for? */
if (domains != NULL && dfc->mctx_domain != NULL)
{
int n;
if (dompats != NULL)
{
for (n = 0; dompats[n] != NULL; n++)
{
status = regexec(dompats[n],
dfc->mctx_domain,
0, NULL, 0);
if (status == 0)
{
domainok = TRUE;
break;
}
}
}
else
{
for (n = 0; domains[n] != NULL; n++)
{
if (strcasecmp(dfc->mctx_domain,
domains[n]) == 0)
{
dfc->mctx_domain = domains[n];
domainok = TRUE;
break;
}
}
}
if (subdomains)
{
for (p = strchr(dfc->mctx_domain, '.');
p != NULL && !domainok;
p = strchr(p, '.'))
{
p++;
if (*p == '\0')
break;
if (dompats != NULL)
{
for (n = 0; dompats[n] != NULL; n++)
{
status = regexec(dompats[n],
dfc->mctx_domain,
0, NULL, 0);
if (status == 0)
{
domainok = TRUE;
break;
}
}
}
else
{
for (n = 0; domains[n] != NULL; n++)
{
if (strcasecmp(p,
domains[n]) == 0)
{
dfc->mctx_domain = domains[n];
domainok = TRUE;
break;
}
}
}
}
}
}
#ifdef _FFR_SELECTOR_HEADER
/* was there a header naming the selector to use? */
if (domainok && selectorhdr != NULL)
{
/* find the header */
hdr = dkimf_findheader(dfc, selectorhdr, 0);
/* did it match a selector in the keylist? */
if (hdr != NULL)
{
struct keytable *curkey;
for (curkey = keyhead;
curkey != NULL;
curkey = curkey->key_next)
{
if (strcasecmp(curkey->key_selector,
hdr->hdr_val) == 0)
{
dfc->mctx_key = curkey;
break;
}
}
}
}
#endif /* _FFR_SELECTOR_HEADER */
/* still no key selected; check the keylist (if any) */
if (dfc->mctx_key == NULL && keyhead != NULL)
{
struct keytable *curkey;
char srchaddr[MAXADDRESS + 1];
snprintf(srchaddr, sizeof srchaddr, "%s@%s",
user, dfc->mctx_domain);
/* select the key */
for (curkey = keyhead;
curkey != NULL;
curkey = curkey->key_next)
{
status = regexec(&curkey->key_re, srchaddr,
0, NULL, 0);
if (status == 0)
break;
if (status != REG_NOMATCH)
{
if (dolog)
{
char err[BUFRSZ];
(void) regerror(status,
&curkey->key_re,
err, sizeof err);
syslog(LOG_ERR,
"%s regexec(): %s",
dfc->mctx_jobid, err);
}
dkimf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
}
if (curkey != NULL)
{
dfc->mctx_key = curkey;
domainok = TRUE;
}
}
/* see if it came in on an authorized MSA/MTA connection */
if (mtas != NULL)
{
int n;
char *mtaname;
mtaname = dkimf_getsymval(ctx, "{daemon_name}");
if (mtaname != NULL)
{
for (n = 0; mtas[n] != NULL; n++)
{
if (strcasecmp(mtaname, mtas[n]) == 0)
{
originok = TRUE;
break;
}
}
}
}
/* see if macro tests passed */
if (macros != NULL)
{
bool done = FALSE;
int n;
char *val;
char *vals;
char *last;
char name[BUFRSZ + 1];
if (dfc->mctx_tmpstr == NULL)
{
dfc->mctx_tmpstr = dkimf_dstring_new(BUFRSZ, 0);
if (dfc->mctx_tmpstr == NULL)
{
if (dolog)
{
syslog(LOG_ERR,
"%s dkimf_dstring_new() failed",
dfc->mctx_jobid);
}
dkimf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
}
for (n = 0; !done && macros[n] != NULL; n++)
{
/* retrieve the macro */
snprintf(name, sizeof name, "{%s}", macros[n]);
val = dkimf_getsymval(ctx, name);
/* short-circuit if the macro's not set */
if (val == NULL)
continue;
/* macro set and we don't care about the value */
if (val != NULL && values[n] == NULL)
{
originok = TRUE;
break;
}
dkimf_dstring_blank(dfc->mctx_tmpstr);
dkimf_dstring_copy(dfc->mctx_tmpstr, values[n]);
vals = dkimf_dstring_get(dfc->mctx_tmpstr);
for (p = strtok_r(vals, "|", &last);
!done && p != NULL;
p = strtok_r(NULL, "|", &last))
{
if (strcasecmp(val, p) == 0)
{
originok = TRUE;
done = TRUE;
}
}
}
}
/* see if it came from an internal or authenticated source */
if (!originok)
{
char *authtype;
authtype = dkimf_getsymval(ctx, "{auth_type}");
if ((authtype == NULL || authtype[0] == '\0') &&
#if POPAUTH
!dkimf_checkpopauth(popdb, &cc->cctx_ip) &&
#endif /* POPAUTH */
!dkimf_checkhost(internal, cc->cctx_host) &&
!dkimf_checkip(internal, &cc->cctx_ip))
{
if (domainok && dolog &&
!dkimf_checkhost(exignore, cc->cctx_host) &&
!dkimf_checkip(exignore, &cc->cctx_ip))
{
syslog(LOG_NOTICE,
"%s external host %s attempted to send as %s",
dfc->mctx_jobid, cc->cctx_host,
dfc->mctx_domain);
}
}
else
{
originok = TRUE;
}
}
/* set signing mode if the tests passed */
if (domainok && originok)
{
dfc->mctx_signalg = signalg;
dfc->mctx_signing = TRUE;
dfc->mctx_addheader = TRUE;
}
/*
** If we're not operating in the role matching the required operation,
** just accept the message and be done with it.
*/
if ((dfc->mctx_signing && (mode & DKIMF_MODE_SIGNER) == 0) ||
(!dfc->mctx_signing && (mode & DKIMF_MODE_VERIFIER) == 0))
return SMFIS_ACCEPT;
/* confirm that we've got a key for signing when a keylist is in use */
if (dfc->mctx_signing && dfc->mctx_key == NULL && keyhead != NULL)
{
if (dolog)
{
syslog(LOG_NOTICE, "%s no key selected for signing",
dfc->mctx_jobid);
}
return SMFIS_TEMPFAIL;
}
/* grab an appropriate handle for message processing */
if (!dfc->mctx_signing)
{
dfc->mctx_dkim = dkim_verify(libdkim, dfc->mctx_jobid, NULL,
&status);
}
else if (dfc->mctx_key != NULL)
{
char *sdomain;
if (dfc->mctx_key->key_domain != NULL)
sdomain = dfc->mctx_key->key_domain;
else
sdomain = dfc->mctx_domain;
dfc->mctx_dkim = dkim_sign(libdkim, dfc->mctx_jobid, NULL,
(dkim_sigkey_t) dfc->mctx_key->key_data,
dfc->mctx_key->key_selector,
sdomain,
dfc->mctx_hdrcanon,
dfc->mctx_bodycanon,
dfc->mctx_signalg,
signbytes, &status);
}
else
{
dfc->mctx_dkim = dkim_sign(libdkim, dfc->mctx_jobid, NULL,
seckey,
selector,
dfc->mctx_domain,
dfc->mctx_hdrcanon,
dfc->mctx_bodycanon,
dfc->mctx_signalg,
signbytes, &status);
}
if (dfc->mctx_dkim == NULL && status != DKIM_STAT_OK)
return dkimf_libstatus(ctx, "dkim_new()", status);
#if _FFR_VBR
/* establish a VBR handle */
dfc->mctx_vbr = vbr_init(NULL, NULL, NULL);
if (dfc->mctx_vbr == NULL)
{
syslog(LOG_ERR, "%s can't create VBR context",
dfc->mctx_jobid);
dkimf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
/* store trusted certifiers */
if (vbr_trusted != NULL)
vbr_trustedcerts(dfc->mctx_vbr, vbr_trusted);
/* if signing, store the values needed to make a header */
if (dfc->mctx_signing)
{
/* set the sending domain */
vbr_setdomain(dfc->mctx_vbr, dfc->mctx_domain);
/* VBR-Type; get value from headers or use default */
hdr = dkimf_findheader(dfc, XVBRTYPEHEADER, 0);
if (hdr != NULL)
vbr_type = hdr->hdr_val;
else
vbr_type = vbr_deftype;
/* X-VBR-Certifiers; get value from headers or use default */
hdr = dkimf_findheader(dfc, XVBRCERTHEADER, 0);
if (hdr != NULL)
vbr_cert = hdr->hdr_val;
else
vbr_cert = vbr_defcert;
/* set the message type and certifiers */
if (vbr_type != NULL && vbr_cert != NULL)
{
/* set the VBR transaction type */
(void) vbr_settype(dfc->mctx_vbr, vbr_type);
/* set the VBR certifier list */
(void) vbr_setcert(dfc->mctx_vbr, vbr_cert);
}
}
#endif /* _FFR_VBR */
#if VERIFY_DOMAINKEYS
if (dfc->mctx_dksigned && !dfc->mctx_signing)
{
dfc->mctx_dk = dk_verify(libdk, dfc->mctx_jobid, NULL,
&status);
if (dfc->mctx_dk == NULL && status != DKIM_STAT_OK)
{
if (dolog)
{
syslog(LOG_ERR,
"%s: dk_verify() returned status %d",
dfc->mctx_jobid, status);
}
/* XXX -- temp-fail or continue? */
}
}
#endif /* VERIFY_DOMAINKEYS */
/* run the headers */
for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
{
/* colon, space, CR, LF, NULL = 5 */
c = strlen(hdr->hdr_hdr) + strlen(hdr->hdr_val) + 5;
if (dfc->mctx_tmpstr == NULL)
{
dfc->mctx_tmpstr = dkimf_dstring_new(BUFRSZ, 0);
if (dfc->mctx_tmpstr == NULL)
{
if (dolog)
{
syslog(LOG_ERR,
"%s: dkimf_dstring_new() failed",
dfc->mctx_jobid);
}
}
}
else
{
dkimf_dstring_blank(dfc->mctx_tmpstr);
}
dkimf_dstring_copy(dfc->mctx_tmpstr, hdr->hdr_hdr);
dkimf_dstring_cat1(dfc->mctx_tmpstr, ':');
if (!cc->cctx_noleadspc)
dkimf_dstring_cat1(dfc->mctx_tmpstr, ' ');
last = '\0';
/* do milter-ized continuation conversion */
for (p = hdr->hdr_val; *p != '\0'; p++)
{
if (*p == '\n' && last != '\r')
dkimf_dstring_cat1(dfc->mctx_tmpstr, '\r');
dkimf_dstring_cat1(dfc->mctx_tmpstr, *p);
last = *p;
}
status = dkim_header(dfc->mctx_dkim,
dkimf_dstring_get(dfc->mctx_tmpstr),
dkimf_dstring_len(dfc->mctx_tmpstr));
if (status != DKIM_STAT_OK)
return dkimf_libstatus(ctx, "dkim_header()", status);
#if VERIFY_DOMAINKEYS
if (dfc->mctx_dk != NULL)
{
dkimf_dstring_cat(dfc->mctx_tmpstr, CRLF);
status = dk_header(dfc->mctx_dk,
dkimf_dstring_get(dfc->mctx_tmpstr),
dkimf_dstring_len(dfc->mctx_tmpstr));
if (status != DK_STAT_OK)
{
if (dolog)
{
syslog(LOG_ERR,
"%s: dk_header() returned status %d",
dfc->mctx_jobid, status);
}
dk_free(dfc->mctx_dk);
dfc->mctx_dk = NULL;
}
}
#endif /* VERIFY_DOMAINKEYS */
}
#if VERIFY_DOMAINKEYS
/* signal end of headers to libdk */
if (dfc->mctx_dk != NULL)
{
status = dk_eoh(dfc->mctx_dk);
if (status != DK_STAT_OK)
{
if (dolog)
{
syslog(LOG_ERR,
"%s: dk_eoh() returned status %d",
dfc->mctx_jobid, status);
}
dk_free(dfc->mctx_dk);
dfc->mctx_dk = NULL;
}
}
#endif /* VERIFY_DOMAINKEYS */
(void) dkim_set_user_context(dfc->mctx_dkim, ctx);
/* signal end of headers to libdkim */
status = dkim_eoh(dfc->mctx_dkim);
switch (status)
{
case DKIM_STAT_REVOKED:
dfc->mctx_status = DKIMF_STATUS_REVOKED;
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
return SMFIS_CONTINUE;
case DKIM_STAT_BADSIG:
dfc->mctx_status = DKIMF_STATUS_BAD;
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
return SMFIS_CONTINUE;
case DKIM_STAT_NOSIG:
dfc->mctx_status = DKIMF_STATUS_NOSIGNATURE;
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
return SMFIS_CONTINUE;
case DKIM_STAT_NOKEY:
dfc->mctx_status = DKIMF_STATUS_NOKEY;
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
return SMFIS_CONTINUE;
/* XXX -- other codes? */
case DKIM_STAT_OK:
return SMFIS_CONTINUE;
default:
return dkimf_libstatus(ctx, "dkim_eoh()", status);
}
}
/*
** MLFI_BODY -- handler for an arbitrary body block
**
** Parameters:
** ctx -- milter context
** bodyp -- body block
** bodylen -- amount of data available at bodyp
**
** Return value:
** An SMFIS_* constant.
**
** Description:
** This function reads the body chunks passed by the MTA and
** stores them for later wrapping, if needed.
*/
sfsistat
mlfi_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen)
{
int status;
msgctx dfc;
connctx cc;
assert(ctx != NULL);
assert(bodyp != NULL);
if (DKIM_DEBUG('t'))
syslog(LOG_INFO, "thread %p body", pthread_self());
cc = (connctx) dkimf_getpriv(ctx);
assert(cc != NULL);
dfc = cc->cctx_msg;
assert(dfc != NULL);
#if VERIFY_DOMAINKEYS
if (dfc->mctx_dk != NULL)
{
status = dk_body(dfc->mctx_dk, bodyp, bodylen);
if (status != DK_STAT_OK)
{
if (dolog)
{
syslog(LOG_ERR,
"%s: dk_body() returned status %d",
dfc->mctx_jobid, status);
}
dk_free(dfc->mctx_dk);
dfc->mctx_dk = NULL;
}
}
#endif /* VERIFY_DOMAINKEYS */
/*
** No need to do anything if the body was empty.
*/
if (bodylen == 0 || dfc->mctx_headeronly)
return SMFIS_CONTINUE;
status = dkim_body(dfc->mctx_dkim, bodyp, bodylen);
if (status != DKIM_STAT_OK)
return dkimf_libstatus(ctx, "dkim_body()", status);
#ifdef SMFIS_SKIP
if (milterv2 && dkim_minbody(dfc->mctx_dkim) == 0)
return SMFIS_SKIP;
#endif /* SMFIS_SKIP */
return SMFIS_CONTINUE;
}
/*
** MLFI_EOM -- handler called at the end of the message; we can now decide
** based on the configuration if and how to add the text
** to this message, then release resources
**
** Parameters:
** ctx -- milter context
**
** Return value:
** An SMFIS_* constant.
*/
sfsistat
mlfi_eom(SMFICTX *ctx)
{
bool testkey = FALSE;
bool testpolicy = FALSE;
bool susp = FALSE;
int status = DKIM_STAT_OK;
int c;
sfsistat ret;
connctx cc;
msgctx dfc;
char *hostname;
Header hdr;
unsigned char header[DKIM_MAXHEADER + 1];
assert(ctx != NULL);
if (DKIM_DEBUG('t'))
syslog(LOG_INFO, "thread %p eom", pthread_self());
cc = (connctx) dkimf_getpriv(ctx);
assert(cc != NULL);
dfc = cc->cctx_msg;
assert(dfc != NULL);
/*
** If necessary, try again to get the job ID in case it came down
** later than expected (e.g. postfix).
*/
if (dfc->mctx_jobid == JOBIDUNKNOWN)
{
dfc->mctx_jobid = dkimf_getsymval(ctx, "i");
if (dfc->mctx_jobid == NULL)
{
if (no_i_whine && dolog)
{
syslog(LOG_WARNING,
"WARNING: sendmail symbol 'i' not available");
no_i_whine = FALSE;
}
dfc->mctx_jobid = JOBIDUNKNOWN;
}
}
/* get hostname; used in the X header and in new MIME boundaries */
hostname = dkimf_getsymval(ctx, "j");
if (hostname == NULL)
hostname = HOSTUNKNOWN;
/* remove old signatures when signing */
if (remsigs && dfc->mctx_signing)
{
for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
{
if (strcasecmp(hdr->hdr_hdr, DKIM_SIGNHEADER) == 0)
{
if (smfi_chgheader(ctx, hdr->hdr_hdr,
0, NULL) != MI_SUCCESS)
{
if (dolog)
{
syslog(LOG_WARNING,
"failed to remove %s: header",
hdr->hdr_hdr);
}
}
}
}
}
/*
** Remove all Authentication-Results: headers as per configuration
** options when verifying.
*/
if (!dfc->mctx_signing)
{
struct authres *ares;
ares = (struct authres *) malloc(sizeof(struct authres));
if (ares == NULL)
{
syslog(LOG_WARNING,
"%s malloc(): %s", dfc->mctx_jobid,
strerror(errno));
return SMFIS_TEMPFAIL;
}
c = 0;
for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
{
memset(ares, '\0', sizeof(struct authres));
if (strcasecmp(hdr->hdr_hdr, AUTHRESULTSHDR) == 0)
{
bool dkimres = FALSE;
bool hostmatch = FALSE;
int arstat;
/* remember index */
c++;
/* parse the header */
arstat = ares_parse(hdr->hdr_val, ares);
if (arstat == -1)
{
if (dolog)
{
syslog(LOG_WARNING,
"failed to parse %s: header",
hdr->hdr_hdr);
}
continue;
}
/* method match? */
if (remarall)
{
dkimres = TRUE;
}
else
{
int d;
for (d = 0; d < ares->ares_count; d++)
{
if (ares->ares_result[d].result_method == ARES_METHOD_DKIM)
dkimres = TRUE;
}
}
/* hostname match? */
if (remar != NULL)
{
if (dkimf_hostlist(ares->ares_host,
remar))
hostmatch = TRUE;
}
else
{
if (strcasecmp(hostname,
ares->ares_host) == 0)
hostmatch = TRUE;
}
/* delete if we found both */
if (dkimres && hostmatch)
{
if (smfi_chgheader(ctx, hdr->hdr_hdr,
c,
NULL) != MI_SUCCESS)
{
if (dolog)
{
syslog(LOG_WARNING,
"failed to remove %s: header",
hdr->hdr_hdr);
}
}
}
}
}
free(ares);
}
if (!dfc->mctx_headeronly && dfc->mctx_dkim != NULL)
{
(void) dkim_set_user_context(dfc->mctx_dkim, ctx);
/*
** Signal end-of-message to DKIM
*/
status = dkim_eom(dfc->mctx_dkim, &testkey);
switch (status)
{
case DKIM_STAT_OK:
if (!dfc->mctx_signing &&
dkimf_findheader(dfc, DKIM_SIGNHEADER, 0) != NULL)
{
if (dolog_success)
{
syslog(LOG_INFO,
"%s DKIM verification successful",
dfc->mctx_jobid);
}
dfc->mctx_addheader = TRUE;
dfc->mctx_status = DKIMF_STATUS_GOOD;
}
break;
case DKIM_STAT_CANTVRFY:
dfc->mctx_addheader = TRUE;
dfc->mctx_status = DKIMF_STATUS_VERIFYERR;
break;
case DKIM_STAT_BADSIG:
dfc->mctx_addheader = TRUE;
dfc->mctx_status = DKIMF_STATUS_BAD;
break;
case DKIM_STAT_NOSIG:
/* don't know yet... */
break;
case DKIM_STAT_NOKEY:
dfc->mctx_addheader = TRUE;
dfc->mctx_status = DKIMF_STATUS_NOKEY;
break;
default:
dkimf_log_ssl_errors(dfc->mctx_jobid);
status = dkimf_libstatus(ctx, "dkim_eom()", status);
#ifdef _FFR_CAPTURE_UNKNOWN_ERRORS
# ifdef SMFIF_QUARANTINE
if (dfc->mctx_capture)
{
if (smfi_quarantine(ctx,
"capture requested") != MI_SUCCESS)
{
if (dolog)
{
syslog(LOG_ERR,
"%s smfi_quarantine() failed",
dfc->mctx_jobid);
}
}
status = SMFIS_ACCEPT;
}
# endif /* ! SMFIF_QUARANTINE */
#endif /* _FFR_CAPTURE_UNKNOWN_ERRORS */
return status;
}
#ifdef _FFR_STATS
if ((status == DKIM_STAT_OK || status == DKIM_STAT_BADSIG) &&
!dfc->mctx_signing && statspath != NULL)
{
bool tmp_lengths = FALSE;
dkim_alg_t tmp_signalg = DKIM_SIGN_UNKNOWN;
dkim_canon_t tmp_hdrcanon = DKIM_CANON_UNKNOWN;
dkim_canon_t tmp_bodycanon = DKIM_CANON_UNKNOWN;
off_t signlen;
const char *tmp_signdomain = NULL;
DKIM_SIGINFO *sig = NULL;
sig = dkim_getsignature(dfc->mctx_dkim);
(void) dkim_sig_getsignalg(sig, &tmp_signalg);
(void) dkim_sig_getcanons(sig, &tmp_hdrcanon,
&tmp_bodycanon);
(void) dkim_sig_getcanonlen(dfc->mctx_dkim, sig,
NULL, NULL, &signlen);
tmp_lengths = (signlen != (off_t) -1);
tmp_signdomain = dkim_sig_getdomain(sig);
dkimf_stats_record(statspath,
tmp_signdomain,
tmp_hdrcanon,
tmp_bodycanon,
tmp_signalg,
(status == DKIM_STAT_OK),
testkey,
tmp_lengths);
}
#endif /* _FFR_STATS */
#ifdef _FFR_ZTAGS
if (diagdir != NULL && dfc->mctx_status == DKIMF_STATUS_BAD)
{
int nhdrs;
DKIM_SIGINFO *sig;
char *ohdrs[MAXHDRCNT];
nhdrs = MAXHDRCNT;
memset(ohdrs, '\0', sizeof ohdrs);
sig = dkim_getsignature(dfc->mctx_dkim);
status = dkim_ohdrs(dfc->mctx_dkim, sig,
ohdrs, &nhdrs);
if (status == DKIM_STAT_OK)
{
FILE *f;
char dpath[MAXPATHLEN + 1];
snprintf(dpath, sizeof dpath, "%s/%s",
diagdir, dfc->mctx_jobid);
f = fopen(dpath, "w");
if (f == NULL)
{
if (dolog)
{
syslog(LOG_ERR,
"%s %s: fopen(): %s",
dfc->mctx_jobid,
dpath, strerror(errno));
}
}
else
{
int c;
# ifdef _FFR_DIFFHEADERS
int ndiffs;
struct dkim_hdrdiff *diffs;
# endif /* _FFR_DIFFHEADERS */
struct Header *hdr;
fprintf(f, "z tag headers:\n\n");
for (c = 0; c < nhdrs; c++)
fprintf(f, "%s\r\n", ohdrs[c]);
fprintf(f, "--------------------\n\n");
fprintf(f, "Received headers:\n\n");
for (hdr = dfc->mctx_hqhead;
hdr != NULL;
hdr = hdr->hdr_next)
{
fprintf(f, "%s:%s%s",
hdr->hdr_hdr,
cc->cctx_noleadspc ? ""
: " ",
hdr->hdr_val);
}
# ifdef _FFR_DIFFHEADERS
/* XXX -- make the "5" configurable */
status = dkim_diffheaders(dfc->mctx_dkim,
5,
ohdrs,
nhdrs,
&diffs,
&ndiffs);
if (status == DKIM_STAT_OK &&
diffs != NULL && ndiffs > 0)
{
fprintf(f, "--------------------\n\n");
fprintf(f, "Munging detected:\n\n");
for (c = 0; c < ndiffs; c++)
{
fprintf(f,
"-%s\n+%s\n\n",
diffs[c].hd_old,
diffs[c].hd_new);
}
}
# endif /* _FFR_DIFFHEADERS */
fclose(f);
}
}
}
#endif /* _FFR_ZTAGS */
if (dfc->mctx_status == DKIMF_STATUS_GOOD)
{
if (sigmin > 0)
{
off_t canonlen;
off_t bodylen;
DKIM_SIGINFO *sig;
sig = dkim_getsignature(dfc->mctx_dkim);
(void) dkim_sig_getcanonlen(dfc->mctx_dkim,
sig, &bodylen,
&canonlen, NULL);
if (sigmintype == SIGMIN_PERCENT)
{
size_t signpct;
signpct = (100 * canonlen) / bodylen;
if (signpct < sigmin)
dfc->mctx_status = DKIMF_STATUS_PARTIAL;
}
else if (sigmintype == SIGMIN_MAXADD)
{
if (canonlen + sigmin < bodylen)
dfc->mctx_status = DKIMF_STATUS_PARTIAL;
}
else
{
size_t required;
required = MIN(sigmin, bodylen);
if (canonlen < sigmin)
dfc->mctx_status = DKIMF_STATUS_PARTIAL;
}
}
}
/* compute and insert the signature, if we're signing */
if (dfc->mctx_signing)
{
size_t len;
char *start;
memset(header, '\0', sizeof header);
len = sizeof header;
start = header;
if (cc->cctx_noleadspc)
{
len--;
header[0] = ' ';
start++;
}
status = dkim_getsighdr(dfc->mctx_dkim, start, len,
DKIM_HDRMARGIN,
strlen(DKIM_SIGNHEADER) + 2);
if (status != DKIM_STAT_OK)
return status;
dkimf_stripcr(header);
if (dkimf_insheader(ctx, 1, DKIM_SIGNHEADER,
header) == MI_FAILURE)
{
if (dolog)
{
syslog(LOG_ERR,
"%s \"%s\" header add failed",
dfc->mctx_jobid,
DKIM_SIGNHEADER);
}
}
if (dolog_success)
{
syslog(LOG_INFO, "%s \"%s\" header added",
dfc->mctx_jobid, DKIM_SIGNHEADER);
}
#ifdef _FFR_VBR
/* generate and add a VBR-Info header */
memset(header, '\0', sizeof header);
status = vbr_getheader(dfc->mctx_vbr, header,
sizeof header);
/* XXX -- log errors */
if (status == DKIM_STAT_OK)
{
if (dkimf_insheader(ctx, 1, VBR_INFOHEADER,
header) == MI_FAILURE)
{
if (dolog)
{
syslog(LOG_ERR,
"%s \"%s\" header add failed",
dfc->mctx_jobid,
VBR_INFOHEADER);
}
}
}
#endif /* _FFR_VBR */
}
}
if (dfc->mctx_dkim != NULL && !dfc->mctx_signing)
{
/*
** Evaluate sender signing policy for failed or unsigned
** messages.
*/
if (status == DKIM_STAT_OK ||
status == DKIM_STAT_BADSIG ||
status == DKIM_STAT_CANTVRFY ||
status == DKIM_STAT_SYNTAX ||
status == DKIM_STAT_REVOKED ||
status == DKIM_STAT_KEYFAIL ||
status == DKIM_STAT_NOSIG)
{
dkim_handling_t hcode = DKIM_HANDLING_NONE;
status = dkim_policy(dfc->mctx_dkim, &testpolicy,
&susp, NULL, &hcode, NULL);
if (status == DKIM_STAT_OK)
{
if (susp)
{
dfc->mctx_addheader = TRUE;
dfc->mctx_status = DKIMF_STATUS_SUSPICIOUS;
}
if (hcode == DKIM_HANDLING_DENY &&
use_ssp_deny && !testpolicy)
{
if (dolog)
{
syslog(LOG_NOTICE,
"%s rejected per sender domain policy",
dfc->mctx_jobid);
}
if (smfi_setreply(ctx,
SSPDENYSMTP,
SSPDENYESC,
SSPDENYTEXT) != MI_SUCCESS &&
dolog)
{
syslog(LOG_NOTICE,
"%s smfi_setreply() failed",
dfc->mctx_jobid);
}
dkimf_cleanup(ctx);
return SMFIS_REJECT;
}
}
else if (dolog)
{
const char *err;
err = dkim_geterror(dfc->mctx_dkim);
if (err != NULL)
{
syslog(LOG_ERR, "%s SSP query: %s",
dfc->mctx_jobid, err);
}
}
}
}
/* insert DKIM status */
if (dfc->mctx_addheader && dfc->mctx_status != DKIMF_STATUS_UNKNOWN)
{
bool test;
u_int keybits;
char *authresult;
char *failstatus;
char *method;
DKIM_SIGINFO *sig;
char comment[BUFRSZ + 1];
memset(comment, '\0', sizeof comment);
test = FALSE;
failstatus = (testkey ? "neutral" : "hardfail");
switch (dfc->mctx_status)
{
case DKIMF_STATUS_GOOD:
authresult = "pass";
sig = dkim_getsignature(dfc->mctx_dkim);
assert(sig != NULL);
(void) dkim_sig_getkeysize(sig, &keybits);
snprintf(comment, sizeof comment, "%u-bit key",
keybits);
break;
case DKIMF_STATUS_NOSIGNATURE:
authresult = failstatus;
sm_strlcpy(comment, "no signature", sizeof comment);
if (!susp)
dfc->mctx_addheader = FALSE;
break;
case DKIMF_STATUS_BAD:
case DKIMF_STATUS_REVOKED:
case DKIMF_STATUS_PARTIAL:
case DKIMF_STATUS_VERIFYERR:
authresult = failstatus;
if (dfc->mctx_status == DKIMF_STATUS_REVOKED)
{
sm_strlcpy(comment, "revoked", sizeof comment);
}
else if (dfc->mctx_status == DKIMF_STATUS_PARTIAL)
{
authresult = "permerror";
sm_strlcpy(comment, "partial verification",
sizeof comment);
}
else if (dfc->mctx_status == DKIMF_STATUS_VERIFYERR)
{
const char *err;
authresult = "permerror";
err = dkim_geterror(dfc->mctx_dkim);
if (err != NULL)
{
snprintf(comment, sizeof comment,
"verification error: %s",
err);
}
else
{
sm_strlcpy(comment,
"verification error",
sizeof comment);
}
}
else
{
sm_strlcpy(comment, "verification failed",
sizeof comment);
}
break;
case DKIMF_STATUS_SUSPICIOUS:
authresult = (testpolicy ? "neutral" : "hardfail");
sm_strlcpy(comment, "SSP", sizeof comment);
break;
case DKIMF_STATUS_BADFORMAT:
authresult = "permerror";
sm_strlcpy(comment, "bad format", sizeof comment);
break;
default:
authresult = "neutral";
break;
}
#ifdef SMFIF_QUARANTINE
/* quarantine for "bad" results if requested */
if (quarantine &&
(dfc->mctx_status == DKIMF_STATUS_BAD ||
dfc->mctx_status == DKIMF_STATUS_REVOKED ||
dfc->mctx_status == DKIMF_STATUS_PARTIAL ||
dfc->mctx_status == DKIMF_STATUS_VERIFYERR ||
(dfc->mctx_status == DKIMF_STATUS_NOSIGNATURE &&
dfc->mctx_addheader)))
{
char qreason[BUFRSZ + 1];
snprintf(qreason, sizeof qreason,
"%s: %s: %s", progname, failstatus, comment);
if (smfi_quarantine(ctx, qreason) != MI_SUCCESS)
{
if (dolog)
{
syslog(LOG_ERR,
"%s smfi_quarantine() failed",
dfc->mctx_jobid);
}
}
}
#endif /* SMFIF_QUARANTINE */
if (test)
{
if (comment[0] == '\0')
sm_strlcpy(comment, "testing", sizeof comment);
else
sm_strlcat(comment, "/testing", sizeof comment);
}
memset(header, '\0', sizeof header);
method = "dkim";
if (susp && (dfc->mctx_status == DKIMF_STATUS_BAD ||
dfc->mctx_status == DKIMF_STATUS_GOOD ||
dfc->mctx_status == DKIMF_STATUS_REVOKED ||
dfc->mctx_status == DKIMF_STATUS_PARTIAL ||
dfc->mctx_status == DKIMF_STATUS_VERIFYERR ||
dfc->mctx_status == DKIMF_STATUS_NOSIGNATURE))
method = "dkim-ssp";
if (dfc->mctx_dkim != NULL)
{
char val[MAXADDRESS + 1];
memset(val, '\0', sizeof val);
sm_strlcpy(val, "unknown", sizeof val);
(void) dkim_sig_getidentity(dfc->mctx_dkim, NULL,
val, sizeof val);
snprintf(header, sizeof header,
"%s%s; %s=%s%s%s%s header.i=%s",
cc->cctx_noleadspc ? " " : "",
hostname,
method,
authresult,
comment[0] == '\0' ? "" : " (",
comment[0] == '\0' ? "" : comment,
comment[0] == '\0' ? "" : ")",
val);
if (dfc->mctx_addheader &&
dkimf_insheader(ctx, 1, AUTHRESULTSHDR,
header) == MI_FAILURE)
{
if (dolog)
{
syslog(LOG_ERR,
"%s \"%s\" header add failed",
dfc->mctx_jobid,
AUTHRESULTSHDR);
}
}
}
if (dfc->mctx_status == DKIMF_STATUS_BAD &&
dfc->mctx_dkim != NULL)
dkimf_report(dfc, authresult, comment);
}
#ifdef _FFR_VBR
if (!dfc->mctx_signing &&
dkimf_findheader(dfc, VBR_INFOHEADER, 0) != NULL)
{
bool add_vbr_header = FALSE;
VBR_STAT vbr_status;
int c;
char *vbr_result;
char *vbr_domain;
char *vbr_certifier;
char *vbr_vouchers;
char *vbr_type;
char *p;
char *sctx;
char *eq;
u_char *param;
u_char *value;
Header vbr_header;
char tmp[DKIM_MAXHEADER + 1];
for (c = 0; ; c++)
{
vbr_header = dkimf_findheader(dfc, VBR_INFOHEADER, c);
if (vbr_header == NULL)
break;
vbr_result = NULL;
vbr_domain = NULL;
vbr_certifier = NULL;
vbr_vouchers = NULL;
vbr_type = NULL;
/* break out the VBR-Info header contents */
sm_strlcpy(tmp, vbr_header->hdr_val, sizeof tmp);
for (p = strtok_r(tmp, ";", &sctx);
p != NULL;
p = strtok_r(NULL, ";", &sctx))
{
eq = strchr(p, '=');
if (eq == NULL)
continue;
*eq = '\0';
for (param = p; *param != '\0'; param++)
{
if (!(isascii(*param) &&
isspace(*param)))
break;
}
dkimf_trimspaces(param);
for (value = eq + 1; *value != '\0'; value++)
{
if (!(isascii(*value) &&
isspace(*value)))
break;
}
dkimf_trimspaces(value);
if (strcasecmp(param, "md") == 0)
{
vbr_domain = value;
}
else if (strcasecmp(param, "mc") == 0)
{
vbr_type = value;
}
else if (strcasecmp(param, "mv") == 0)
{
vbr_vouchers = value;
}
}
/* use accessors to set parsed values */
vbr_setcert(dfc->mctx_vbr, vbr_vouchers);
vbr_settype(dfc->mctx_vbr, vbr_type);
vbr_setdomain(dfc->mctx_vbr, vbr_domain);
/* attempt the query */
vbr_status = vbr_query(dfc->mctx_vbr, &vbr_result,
&vbr_certifier);
switch (vbr_status)
{
case VBR_STAT_DNSERROR:
if (dolog)
{
const char *err;
err = vbr_geterror(dfc->mctx_vbr);
syslog(LOG_NOTICE,
"%s: can't verify VBR information%s%s",
dfc->mctx_jobid,
err == NULL ? "" : ": ",
err == NULL ? "" : err);
}
vbr_result = "neutral";
break;
case VBR_STAT_INVALID:
case VBR_STAT_NORESOURCE:
if (dolog)
{
const char *err;
err = vbr_geterror(dfc->mctx_vbr);
syslog(LOG_NOTICE,
"%s: error handling VBR information%s%s",
dfc->mctx_jobid,
err == NULL ? "" : ": ",
err == NULL ? "" : err);
}
vbr_result = "neutral";
break;
case DKIM_STAT_OK:
add_vbr_header = TRUE;
break;
default:
assert(0);
}
if (add_vbr_header)
{
char hdr[DKIM_MAXHEADER + 1];
memset(hdr, '\0', sizeof hdr);
snprintf(hdr, sizeof hdr, "%s.md",
VBR_INFOHEADER);
dkimf_lowercase(hdr);
snprintf(header, sizeof header,
"%s%s %s=%s; vbr=%s%s%s%s",
cc->cctx_noleadspc ? " " : "",
hostname, hdr, vbr_domain, vbr_result,
vbr_certifier == NULL ? "" : " (",
vbr_certifier == NULL ? "" : vbr_certifier,
vbr_certifier == NULL ? "" : ")");
if (dkimf_insheader(ctx, 1, AUTHRESULTSHDR,
header) == MI_FAILURE)
{
if (dolog)
{
syslog(LOG_ERR,
"%s \"%s\" header add failed",
dfc->mctx_jobid,
AUTHRESULTSHDR);
}
}
break;
}
}
}
#endif /* _FFR_VBR */
#if VERIFY_DOMAINKEYS
if (dfc->mctx_dk != NULL)
{
DK_FLAGS flags;
char *authresult = NULL;
char *comment = NULL;
char hdr[DKIM_MAXHEADER + 1];
char val[MAXADDRESS + 1];
flags = 0;
dfc->mctx_addheader = FALSE;
status = dk_eom(dfc->mctx_dk, &flags);
switch (status)
{
case DK_STAT_OK:
dfc->mctx_addheader = dfc->mctx_dksigned;
authresult = "pass";
break;
case DK_STAT_BADSIG:
dfc->mctx_addheader = TRUE;
authresult = "hardfail";
break;
case DK_STAT_NOSIG:
/* XXX -- extract policy */
dfc->mctx_addheader = TRUE;
authresult = "neutral";
comment = "no signature";
break;
case DK_STAT_NOKEY:
/* XXX -- extract policy */
dfc->mctx_addheader = TRUE;
authresult = "neutral";
comment = "no key";
break;
default:
/* XXX -- do better? */
if (dolog)
{
#if (DK_LIB_VERSION >= 0x00050000)
const char *err;
err = dk_geterror(dfc->mctx_dk);
if (err == NULL)
err = strerror(errno);
syslog(LOG_INFO,
"%s dk_eom() returned status %d: %s",
dfc->mctx_jobid, status, err);
#else /* (DK_LIB_VERSION >= 0x00050000) */
syslog(LOG_INFO,
"%s dk_eom() returned status %d",
dfc->mctx_jobid, status);
#endif /* (DK_LIB_VERSION >= 0x00050000) */
}
break;
}
if (dfc->mctx_addheader)
{
sm_strlcpy(hdr, "unknown", sizeof hdr);
sm_strlcpy(val, "unknown", sizeof val);
(void) dk_getidentity(dfc->mctx_dk, hdr, sizeof hdr,
val, sizeof val);
memset(header, '\0', sizeof header);
snprintf(header, sizeof header,
"%s%s; domainkeys=%s%s%s%s%s header.%s=%s",
cc->cctx_noleadspc ? " " : "",
hostname, authresult,
comment == NULL ? "" : " (",
comment == NULL ? "" : comment,
comment == NULL ? "" : ")",
!(flags & DK_FLAG_TESTING) ? "" : " (testing)",
hdr, val);
if (dkimf_insheader(ctx, 1, AUTHRESULTSHDR,
header) == MI_FAILURE)
{
if (dolog)
{
syslog(LOG_ERR,
"%s \"%s\" header add failed",
dfc->mctx_jobid,
AUTHRESULTSHDR);
}
}
}
}
#endif /* VERIFY_DOMAINKEYS */
/*
** Identify the filter, if requested.
*/
if (addxhdr)
{
char xfhdr[DKIM_MAXHEADER + 1];
memset(xfhdr, '\0', sizeof xfhdr);
snprintf(xfhdr, DKIM_MAXHEADER, "%s%s v%s %s %s",
cc->cctx_noleadspc ? " " : "",
DKIMF_PRODUCT, DKIMF_VERSION, hostname,
dfc->mctx_jobid != NULL ? dfc->mctx_jobid
: JOBIDUNKNOWN);
if (dkimf_insheader(ctx, 1, XHEADERNAME, xfhdr) != MI_SUCCESS)
{
if (dolog)
{
syslog(LOG_ERR, "%s \"%s\" header add failed",
dfc->mctx_jobid, XHEADERNAME);
}
dkimf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
}
dkimf_log_ssl_errors(dfc->mctx_jobid);
/*
** If we got this far, we're ready to complete.
*/
ret = SMFIS_ACCEPT;
/* translate the stored status */
switch (dfc->mctx_status)
{
case DKIMF_STATUS_GOOD:
break;
case DKIMF_STATUS_BAD:
ret = dkimf_libstatus(ctx, "mlfi_eom()", DKIM_STAT_BADSIG);
break;
case DKIMF_STATUS_NOKEY:
ret = dkimf_libstatus(ctx, "mlfi_eom()", DKIM_STAT_NOKEY);
break;
case DKIMF_STATUS_REVOKED:
ret = SMFIS_TEMPFAIL;
break;
case DKIMF_STATUS_NOSIGNATURE:
/*
** If no header was added, we've already decided that we
** don't care that there was no signature here, so don't
** log anything or react either.
*/
if (dfc->mctx_addheader)
{
ret = dkimf_libstatus(ctx, "mlfi_eom()",
DKIM_STAT_NOSIG);
}
break;
case DKIMF_STATUS_BADFORMAT:
ret = SMFIS_ACCEPT;
break;
case DKIMF_STATUS_UNKNOWN:
break;
default:
if (status != DKIM_STAT_OK)
ret = dkimf_libstatus(ctx, "mlfi_eom()", status);
break;
}
return ret;
}
/*
** MLFI_ABORT -- handler called if an earlier filter in the filter process
** rejects the message
**
** Parameters:
** ctx -- milter context
**
** Return value:
** An SMFIS_* constant.
*/
sfsistat
mlfi_abort(SMFICTX *ctx)
{
if (DKIM_DEBUG('t'))
syslog(LOG_INFO, "thread %p abort", pthread_self());
dkimf_cleanup(ctx);
return SMFIS_CONTINUE;
}
/*
** MLFI_CLOSE -- handler called on connection shutdown
**
** Parameters:
** ctx -- milter context
**
** Return value:
** An SMFIS_* constant.
*/
sfsistat
mlfi_close(SMFICTX *ctx)
{
connctx cc;
if (DKIM_DEBUG('t'))
{
pthread_mutex_lock(&count_lock);
thread_count--;
syslog(LOG_INFO, "thread %p close (%d)", pthread_self(),
thread_count);
pthread_mutex_unlock(&count_lock);
}
dkimf_cleanup(ctx);
cc = (connctx) dkimf_getpriv(ctx);
free(cc);
dkimf_setpriv(ctx, NULL);
#ifdef QUERY_CACHE
if (querycache)
{
time_t now;
(void) time(&now);
if (cache_lastlog + CACHESTATSINT < now)
{
u_int c_hits;
u_int c_queries;
u_int c_expired;
dkim_getcachestats(&c_queries, &c_hits, &c_expired);
cache_lastlog = now;
syslog(LOG_INFO,
"cache: %u quer%s, %u hit%s (%d%%), %u expired",
c_queries, c_queries == 1 ? "y" : "ies",
c_hits, c_hits == 1 ? "" : "s",
(c_hits * 100) / c_queries,
c_expired);
}
}
#endif /* QUERY_CACHE */
return SMFIS_CONTINUE;
}
/*
** smfilter -- the milter module description
*/
struct smfiDesc smfilter =
{
DKIMF_PRODUCT, /* filter name */
SMFI_VERSION, /* version code -- do not change */
(SMFIF_ADDHDRS | SMFIF_CHGHDRS |
#ifdef SMFIF_QUARANTINE
SMFIF_QUARANTINE |
#endif /* SMFIF_QUARANTINE */
#ifdef SMFIF_SETSYMLIST
SMFIF_SETSYMLIST |
#endif /* SMFIF_SETSYMLIST */
0), /* flags */
mlfi_connect, /* connection info filter */
NULL, /* SMTP HELO command filter */
mlfi_envfrom, /* envelope sender filter */
NULL, /* envelope recipient filter */
mlfi_header, /* header filter */
mlfi_eoh, /* end of header */
mlfi_body, /* body block filter */
mlfi_eom, /* end of message */
mlfi_abort, /* message aborted */
mlfi_close, /* shutdown */
#if SMFI_VERSION > 2
NULL, /* unrecognised command */
#endif
#if SMFI_VERSION > 3
NULL, /* DATA */
#endif
#if SMFI_VERSION >= 0x01000000
mlfi_negotiate /* negotiation callback */
#endif
};
/*
** USAGE -- print a usage message and return the appropriate exit status
**
** Parameters:
** None.
**
** Return value:
** EX_USAGE.
*/
static int
usage(void)
{
fprintf(stderr, "%s: usage: %s -p socketfile [options]\n"
"\t-a peerlist \tfile containing list of hosts to ignore\n"
"\t-A \tauto-restart\n"
"\t-b modes \tselect operating modes\n"
"\t-c canon \tcanonicalization to use when signing\n"
"\t-C config \tconfiguration info (see man page)\n"
"\t-d domlist \tdomains to sign\n"
"\t-D \talso sign subdomains\n"
"\t-f \tdon't fork-and-exit\n"
"\t-F time \tfixed timestamp to use when signing (test mode only)\n"
"\t-h \tappend identifying header\n"
"\t-H hdrlist \twhich additional headers to sign\n"
"\t-i ilist \tfile containing list of internal (signing) hosts\n"
"\t-I elist \tfile containing list of external domain clients\n"
"\t-k keyfile \tlocation of secret key file\n"
"\t-K \tload a key set instead of a single key\n"
"\t-l \tlog activity to system log\n"
"\t-L limit \tsignature limit requirements\n"
"\t-m mtalist \tMTA daemon names for which to sign\n"
"\t-M macrolist\tMTA macros which enable signing\n"
"\t-o hdrlist \tlist of headers to omit from signing\n"
"\t-P pidfile \tfile to which to write pid\n"
#if _FFR_REQUIRE_HEADERS
"\t-r \trequire basic RFC2822 header compliance\n"
#endif /* _FFR_REQUIRE_HEADERS */
"\t-R \tgenerate verification failure reports\n"
"\t-s selector \tselector to use when signing\n"
"\t-S signalg \tsignature algorithm to use when signing\n"
"\t-t testfile \tevaluate RFC2822 message in \"testfile\"\n"
"\t-T timeout \tDNS timeout (seconds)\n"
"\t-u userid \tchange to specified userid\n"
#if POPAUTH
"\t-U dbfile \tuser POP AUTH database\n"
#endif /* POPAUTH */
"\t-v \tincrease verbosity during testing\n"
"\t-V \tprint version number and terminate\n"
"\t-x conffile \tread configuration from conffile\n",
progname, progname);
return EX_USAGE;
}
/*
** MAIN -- program mainline
**
** Process command line arguments and call the milter mainline.
*/
int
main(int argc, char **argv)
{
bool autorestart = FALSE;
bool ztags = FALSE;
bool blen = FALSE;
bool gotp = FALSE;
bool dofork = TRUE;
bool multikey = FALSE;
bool stricttest = FALSE;
int c;
int status;
int n;
int verbose = 0;
int sigttl = 0;
int clockdrift = 0;
int maxsign;
int filemask = -1;
time_t fixedtime = (time_t) -1;
unsigned long tmpl;
const char *args = CMDLINEOPTS;
FILE *f;
char *be = NULL;
char *become = NULL;
char *domlist = NULL;
char *mtalist = NULL;
char *p;
char *pidfile = NULL;
char *keyfile = NULL;
char *config = NULL;
char *peerfile = NULL;
#ifdef _FFR_REPLACE_RULES
char *repfile = NULL;
#endif /* _FFR_REPLACE_RULES */
char *ilist = NULL;
char *omitlist = NULL;
char *alwayslist = NULL;
char *siglimit = NULL;
char *remarlist = NULL;
#if POPAUTH
char *popdbfile = NULL;
#endif /* POPAUTH */
char *elist = NULL;
char *canonstr = NULL;
char *signalgstr = NULL;
char *macrolist = NULL;
char *testfile = NULL;
char *testpubkeys = NULL;
char *signhdrs = NULL;
unsigned char *s33krit = NULL;
struct config *cfg = NULL;
char *end;
char argstr[MAXARGV];
char confstr[BUFRSZ + 1];
/* initialize */
signbytes = -1L;
milterv2 = FALSE;
testmode = FALSE;
addxhdr = FALSE;
dolog = FALSE;
dolog_success = FALSE;
subdomains = FALSE;
remarall = FALSE;
remsigs = FALSE;
use_ssp_deny = FALSE;
#ifdef QUERY_CACHE
querycache = FALSE;
#endif /* QUERY_CACHE */
sock = NULL;
dompats = NULL;
omithdrs = NULL;
alwayshdrs = NULL;
#ifdef _FFR_SELECTOR_HEADER
selectorhdr = NULL;
#endif /* _FFR_SELECTOR_HEADER */
#if POPAUTH
popdb = NULL;
#endif /* POPAUTH */
send_reports = FALSE;
keyhead = NULL;
keytail = NULL;
no_i_whine = TRUE;
selector = NULL;
#ifdef _FFR_ZTAGS
diagdir = NULL;
#endif /* _FFR_ZTAGS */
seckey = NULL;
libdkim = NULL;
domains = NULL;
mtas = NULL;
macros = NULL;
values = NULL;
peerlist = NULL;
remar = NULL;
#ifdef _FFR_REPLACE_RULES
replist = NULL;
#endif /* _FFR_REPLACE_RULES */
internal = NULL;
exignore = NULL;
#ifdef _FFR_VBR
vbr_deftype = NULL;
vbr_defcert = NULL;
vbr_trusted = NULL;
#endif /* _FFR_VBR */
quarantine = FALSE;
hdrcanon = DKIM_CANON_DEFAULT;
bodycanon = DKIM_CANON_DEFAULT;
tmo = DEFTIMEOUT;
maxhdrsz = DEFMAXHDRSZ;
sigmin = 0;
sigmintype = SIGMIN_BYTES;
if (DKIM_DEBUG('t'))
{
thread_count = 0;
pthread_mutex_init(&count_lock, NULL);
}
progname = (p = strrchr(argv[0], '/')) == NULL ? argv[0] : p + 1;
/* process command line options */
while ((c = getopt(argc, argv, args)) != -1)
{
switch (c)
{
case 'a':
if (optarg == NULL || *optarg == '\0')
return usage();
peerfile = optarg;
break;
case 'A':
autorestart = TRUE;
break;
case 'b':
if (optarg == NULL || *optarg == '\0')
return usage();
be = optarg;
break;
case 'c':
if (optarg == NULL || *optarg == '\0')
return usage();
canonstr = optarg;
break;
case 'C':
if (optarg == NULL || *optarg == '\0')
return usage();
config = optarg;
break;
case 'd':
if (optarg == NULL || *optarg == '\0')
return usage();
domlist = strdup(optarg);
if (domlist == NULL)
{
fprintf(stderr, "%s: strdup(): %s\n", progname,
strerror(errno));
return EX_SOFTWARE;
}
break;
case 'D':
subdomains = TRUE;
break;
case 'f':
dofork = FALSE;
break;
case 'F':
if (optarg == NULL || *optarg == '\0')
return usage();
errno = 0;
if (optarg[0] == '-')
{
errno = ERANGE;
fixedtime = ULONG_MAX;
}
else
{
fixedtime = strtoul(optarg, &p, 10);
}
if (fixedtime == ULONG_MAX || errno != 0 || *p != '\0')
{
fprintf(stderr, "%s: invalid time value\n",
progname);
return EX_USAGE;
}
break;
case 'h':
addxhdr = TRUE;
break;
case 'i':
if (optarg == NULL || *optarg == '\0')
return usage();
ilist = optarg;
break;
case 'I':
if (optarg == NULL || *optarg == '\0')
return usage();
elist = optarg;
break;
case 'k':
if (optarg == NULL || *optarg == '\0')
return usage();
keyfile = optarg;
break;
case 'K':
multikey = TRUE;
break;
case 'l':
dolog = TRUE;
break;
case 'L':
if (optarg == NULL || *optarg == '\0')
return usage();
siglimit = optarg;
break;
case 'm':
if (optarg == NULL || *optarg == '\0')
return usage();
mtalist = optarg;
break;
case 'M':
if (optarg == NULL || *optarg == '\0')
return usage();
macrolist = optarg;
break;
case 'o':
if (optarg == NULL || *optarg == '\0')
return usage();
omitlist = optarg;
break;
case 'p':
if (optarg == NULL || *optarg == '\0')
return usage();
sock = optarg;
(void) smfi_setconn(optarg);
gotp = TRUE;
break;
case 'P':
if (optarg == NULL || *optarg == '\0')
return usage();
pidfile = optarg;
break;
case 'q':
quarantine = TRUE;
break;
#if _FFR_REQUIRED_HEADERS
case 'r':
req_hdrs = TRUE;
break;
#endif /* _FFR_REQUIRED_HEADERS */
case 'R':
send_reports = TRUE;
break;
case 's':
if (optarg == NULL || *optarg == '\0')
return usage();
selector = optarg;
break;
case 'S':
if (optarg == NULL || *optarg == '\0')
return usage();
signalgstr = optarg;
break;
case 't':
if (optarg == NULL || *optarg == '\0')
return usage();
testmode = TRUE;
testfile = optarg;
break;
case 'T':
if (optarg == NULL || *optarg == '\0')
return usage();
errno = 0;
if (optarg[0] == '-')
{
errno = ERANGE;
tmpl = ULONG_MAX;
}
else
{
tmpl = strtoul(optarg, &p, 10);
}
if (tmpl > UINT_MAX || errno != 0 || *p != '\0')
{
fprintf(stderr, "%s: invalid value for -%c\n",
progname, c);
return EX_USAGE;
}
tmo = (unsigned int) tmpl;
break;
case 'u':
if (optarg == NULL || *optarg == '\0')
return usage();
become = optarg;
break;
#if POPAUTH
case 'U':
if (optarg == NULL || *optarg == '\0')
return usage();
popdbfile = optarg;
break;
#endif /* POPAUTH */
case 'v':
verbose++;
break;
case 'V':
printf("%s: %s v%s\n", progname, DKIMF_PRODUCT,
DKIMF_VERSION);
printf("\tCompiled with %s\n",
SSLeay_version(SSLEAY_VERSION));
printf("\tSupported signing algorithms:\n");
for (c = 0; dkimf_sign[c].str != NULL; c++)
printf("\t\t%s\n", dkimf_sign[c].str);
printf("\tSupported canonicalization algorithms:\n");
for (c = 0; dkimf_canon[c].str != NULL; c++)
printf("\t\t%s\n", dkimf_canon[c].str);
dkimf_optlist(stdout);
return EX_OK;
case 'x':
if (optarg == NULL || *optarg == '\0')
{
return usage();
}
else
{
u_int line = 0;
char *missing;
FILE *cf;
cf = fopen(optarg, "r");
if (cf == NULL)
{
fprintf(stderr,
"%s: %s: fopen(): %s\n",
progname, optarg,
strerror(errno));
return EX_UNAVAILABLE;
}
cfg = config_load(cf, dkimf_config, &line);
fclose(cf);
if (cfg == NULL)
{
fprintf(stderr,
"%s: %s: error at line %u\n",
progname, optarg, line);
return EX_CONFIG;
}
missing = config_check(cfg, dkimf_config);
if (missing != NULL)
{
fprintf(stderr,
"%s: %s: required parameter \"%s\" missing\n",
progname, optarg, missing);
config_free(cfg);
return EX_CONFIG;
}
}
break;
default:
return usage();
}
}
if (optind != argc)
return usage();
if (dkim_ssl_version() != OPENSSL_VERSION_NUMBER)
{
fprintf(stderr,
"%s: incompatible OpenSSL versions (library = 0x%09lx, filter = %09lx)\n",
progname, dkim_ssl_version(), OPENSSL_VERSION_NUMBER);
return EX_SOFTWARE;
}
memset(confstr, '\0', sizeof confstr);
/* use what was found in the configuration file, if any */
if (cfg != NULL)
{
char *tmp = NULL;
dkimf_parseconfig2(cfg, "On-Default", "def", confstr,
sizeof confstr);
dkimf_parseconfig2(cfg, "On-BadSignature", "bad", confstr,
sizeof confstr);
dkimf_parseconfig2(cfg, "On-DNSError", "dns", confstr,
sizeof confstr);
dkimf_parseconfig2(cfg, "On-InternalError", "int", confstr,
sizeof confstr);
dkimf_parseconfig2(cfg, "On-NoSignature", "no", confstr,
sizeof confstr);
dkimf_parseconfig2(cfg, "On-SignatureMissing", "miss", confstr,
sizeof confstr);
dkimf_parseconfig2(cfg, "On-Security", "sec", confstr,
sizeof confstr);
if (peerfile == NULL)
{
(void) config_get(cfg, "PeerList", &peerfile,
sizeof peerfile);
}
if (!autorestart)
{
(void) config_get(cfg, "AutoRestart", &autorestart,
sizeof autorestart);
}
(void) config_get(cfg, "BodyLengths", &blen, sizeof blen);
(void) config_get(cfg, "Diagnostics", &ztags, sizeof ztags);
#ifdef _FFR_ZTAGS
(void) config_get(cfg, "DiagnosticDirectory", &diagdir,
sizeof diagdir);
#endif /* _FFR_ZTAGS */
if (be == NULL)
(void) config_get(cfg, "Mode", &be, sizeof be);
if (canonstr == NULL)
{
(void) config_get(cfg, "Canonicalization", &canonstr,
sizeof canonstr);
}
if (domlist == NULL)
{
(void) config_get(cfg, "Domain", &tmp, sizeof tmp);
if (tmp != NULL)
{
domlist = strdup(tmp);
if (domlist == NULL)
{
fprintf(stderr,
"%s: strdup(): %s\n",
progname, strerror(errno));
config_free(cfg);
return EX_OSERR;
}
}
}
if (!subdomains)
{
(void) config_get(cfg, "SubDomains", &subdomains,
sizeof subdomains);
}
if (dofork)
{
(void) config_get(cfg, "Background", &dofork,
sizeof dofork);
}
if (!addxhdr)
{
(void) config_get(cfg, "X-Header", &addxhdr,
sizeof addxhdr);
}
if (ilist == NULL)
{
(void) config_get(cfg, "InternalHosts", &ilist,
sizeof ilist);
}
if (elist == NULL)
{
(void) config_get(cfg, "ExternalIgnoreList", &elist,
sizeof elist);
}
if (keyfile == NULL)
{
(void) config_get(cfg, "KeyList", &keyfile,
sizeof keyfile);
if (keyfile != NULL)
multikey = TRUE;
}
if (keyfile == NULL)
{
(void) config_get(cfg, "KeyFile", &keyfile,
sizeof keyfile);
}
if (omitlist == NULL)
{
(void) config_get(cfg, "OmitHeaders", &omitlist,
sizeof omitlist);
}
(void) config_get(cfg, "AlwaysSignHeaders", &alwayslist,
sizeof alwayslist);
if (!dolog)
(void) config_get(cfg, "Syslog", &dolog, sizeof dolog);
(void) config_get(cfg, "SyslogSuccess", &dolog_success,
sizeof dolog_success);
if (siglimit == NULL)
{
(void) config_get(cfg, "Minimum", &siglimit,
sizeof siglimit);
}
if (mtalist == NULL)
{
(void) config_get(cfg, "MTA", &mtalist,
sizeof mtalist);
}
if (macrolist == NULL)
{
(void) config_get(cfg, "MacroList", ¯olist,
sizeof macrolist);
}
if (!gotp)
{
(void) config_get(cfg, "Socket", &sock, sizeof sock);
if (sock != NULL)
{
gotp = TRUE;
(void) smfi_setconn(sock);
}
}
if (pidfile == NULL)
{
(void) config_get(cfg, "PidFile", &pidfile,
sizeof pidfile);
}
if (!quarantine)
{
(void) config_get(cfg, "Quarantine", &quarantine,
sizeof quarantine);
}
#if _FFR_REQUIRED_HEADERS
if (!req_hdrs)
{
(void) config_get(cfg, "RequiredHeaders", &req_hdrs,
sizeof req_hdrs);
}
#endif /* _FFR_REQUIRED_HEADERS */
if (!send_reports)
{
(void) config_get(cfg, "SendReports", &send_reports,
sizeof send_reports);
}
if (selector == NULL)
{
(void) config_get(cfg, "Selector", &selector,
sizeof selector);
}
if (signalgstr == NULL)
{
(void) config_get(cfg, "SignatureAlgorithm",
&signalgstr, sizeof signalgstr);
}
if (tmo == DEFTIMEOUT)
(void) config_get(cfg, "DNSTimeout", &tmo, sizeof tmo);
maxsign = -1;
(void) config_get(cfg, "MaximumSignedBytes", &maxsign,
sizeof maxsign);
if (maxsign != -1)
{
signbytes = (long) maxsign;
blen = TRUE;
}
if (become == NULL)
{
(void) config_get(cfg, "Userid", &become,
sizeof become);
}
#if POPAUTH
if (popdbfile == NULL)
{
(void) config_get(cfg, "POPDBFile", &popdbfile,
sizeof popdbfile);
}
#endif /* POPAUTH */
(void) config_get(cfg, "SignHeaders", &signhdrs,
sizeof signhdrs);
#if _FFR_VBR
(void) config_get(cfg, "VBR-Type", &vbr_deftype,
sizeof vbr_deftype);
(void) config_get(cfg, "VBR-Certifiers", &vbr_defcert,
sizeof vbr_defcert);
tmp = NULL;
(void) config_get(cfg, "VBR-TrustedCertifiers", &tmp,
sizeof tmp);
if (tmp != NULL)
{
for (p = tmp, c = 1; *p != '\0'; p++)
{
if (*p == ':' || *p == ',')
c++;
}
vbr_trusted = (char **) malloc(sizeof(char *) * (c + 1));
if (vbr_trusted == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
return EX_OSERR;
}
for (p = strtok(tmp, ",:"), c = 0;
p != NULL;
p = strtok(NULL, ",:"), c++)
{
vbr_trusted[c] = p;
vbr_trusted[c + 1] = NULL;
}
}
#endif /* _FFR_VBR */
(void) config_get(cfg, "TestPublicKeys",
&testpubkeys, sizeof testpubkeys);
#ifdef _FFR_STATS
(void) config_get(cfg, "Statistics", &statspath,
sizeof statspath);
#endif /* _FFR_STATS */
(void) config_get(cfg, "ClockDrift", &clockdrift,
sizeof clockdrift);
(void) config_get(cfg, "SignatureTTL", &sigttl, sizeof sigttl);
(void) config_get(cfg, "RemoveARFrom", &remarlist,
sizeof remarlist);
(void) config_get(cfg, "RemoveARAll", &remarall,
sizeof remarall);
(void) config_get(cfg, "RemoveOldSignatures", &remsigs,
sizeof remsigs);
(void) config_get(cfg, "UseSSPDeny", &use_ssp_deny,
sizeof use_ssp_deny);
(void) config_get(cfg, "StrictTestMode", &stricttest,
sizeof stricttest);
(void) config_get(cfg, "UMask", &filemask, sizeof filemask);
#ifdef _FFR_SELECTOR_HEADER
(void) config_get(cfg, "SelectorHeader", &selectorhdr,
sizeof selectorhdr);
#endif /* _FFR_SELECTOR_HEADER */
#ifdef _FFR_REPLACE_RULES
(void) config_get(cfg, "ReplaceRules", &repfile,
sizeof repfile);
#endif /* _FFR_REPLACE_RULES */
#ifdef QUERY_CACHE
(void) config_get(cfg, "QueryCache", &querycache,
sizeof querycache);
#endif /* QUERY_CACHE */
(void) config_get(cfg, "MaximumHeaders", &maxhdrsz,
sizeof maxhdrsz);
}
#ifndef SMFIF_QUARANTINE
if (quarantine)
{
fprintf(stderr, "%s: quarantine service not available\n",
progname);
return EX_SOFTWARE;
}
#endif /* ! SMFIF_QUARANTINE */
if (config == NULL && confstr[0] != '\0')
config = confstr;
if (!dkimf_parseconfig(config))
return EX_USAGE;
if (!gotp && !testmode)
{
fprintf(stderr, "%s: milter socket must be specified\n",
progname);
if (argc == 1)
fprintf(stderr, "\t(use \"-?\" for help)\n");
return EX_CONFIG;
}
if (siglimit != NULL)
{
errno = 0;
if (siglimit[0] == '-')
{
tmpl = ULONG_MAX;
errno = ERANGE;
}
tmpl = strtoul(siglimit, &p, 10);
if (tmpl > UINT_MAX || errno != 0)
{
fprintf(stderr, "%s: invalid value for -L: `%s'\n",
progname, siglimit);
return EX_USAGE;
}
sigmin = (unsigned int) tmpl;
if (*p == '%')
{
if (sigmin > 100)
{
fprintf(stderr,
"%s: invalid value for -L: `%s'\n",
progname, siglimit);
return EX_USAGE;
}
sigmintype = SIGMIN_PERCENT;
}
else if (*p == '+')
{
sigmintype = SIGMIN_MAXADD;
}
else if (*p != '\0')
{
fprintf(stderr, "%s: invalid value for -L: `%s'\n",
progname, siglimit);
return EX_USAGE;
}
}
if (be == NULL)
{
mode = (testmode ? DKIMF_MODE_VERIFIER : DKIMF_MODE_DEFAULT);
}
else
{
mode = 0;
for (p = be; *p != '\0'; p++)
{
switch (*p)
{
case 's':
mode |= DKIMF_MODE_SIGNER;
break;
case 'v':
mode |= DKIMF_MODE_VERIFIER;
break;
default:
fprintf(stderr, "%s: unknown role flag '%c'\n",
progname, *p);
return EX_USAGE;
}
}
}
if ((mode & DKIMF_MODE_SIGNER) != 0 &&
((multikey && keyfile == NULL) ||
(!multikey && (keyfile == NULL || selector == NULL))))
{
fprintf(stderr,
"%s: at least one selector and key required for signing mode\n",
progname);
return EX_USAGE;
}
if (canonstr != NULL)
{
p = strchr(canonstr, '/');
if (p == NULL)
{
hdrcanon = dkimf_configlookup(canonstr, dkimf_canon);
if (hdrcanon == -1)
{
fprintf(stderr,
"%s: unknown canonicalization \"%s\"\n",
progname, canonstr);
return EX_USAGE;
}
bodycanon = DKIM_CANON_DEFAULT;
}
else
{
*p = '\0';
hdrcanon = dkimf_configlookup(canonstr, dkimf_canon);
if (hdrcanon == -1)
{
fprintf(stderr,
"%s: unknown canonicalization \"%s\"\n",
progname, canonstr);
return EX_USAGE;
}
bodycanon = dkimf_configlookup(p + 1, dkimf_canon);
if (bodycanon == -1)
{
fprintf(stderr,
"%s: unknown canonicalization \"%s\"\n",
progname, p + 1);
return EX_USAGE;
}
*p = '/';
}
}
if (signalgstr != NULL)
{
signalg = dkimf_configlookup(signalgstr, dkimf_sign);
if (signalg == -1)
{
fprintf(stderr,
"%s: unknown signature algorithm \"%s\"\n",
progname, signalgstr);
return EX_USAGE;
}
}
else
{
signalg = DKIM_SIGN_DEFAULT;
}
if (domlist != NULL)
{
int n;
bool makepats = FALSE;
if (domlist[0] == '/' && access(domlist, F_OK) == 0)
{
int nalloc = 0;
FILE *f;
char line[BUFRSZ + 1];
n = 0;
f = fopen(domlist, "r");
if (f == NULL)
{
fprintf(stderr, "%s: %s: fopen(): %s\n",
progname, domlist, strerror(errno));
return EX_UNAVAILABLE;
}
memset(line, '\0', sizeof line);
while (fgets(line, BUFRSZ, f) != NULL)
{
for (p = line; *p != '\0'; p++)
{
if (*p == '\n' || *p == '#')
{
*p = '\0';
break;
}
}
dkimf_trimspaces(line);
if (strlen(line) == 0)
continue;
if (nalloc <= n)
{
if (nalloc == 0)
{
domains = (char **) malloc(2 * sizeof(char *));
nalloc = 1;
}
else
{
domains = (char **) realloc(domains,
(nalloc * 2 + 1) * sizeof(char *));
nalloc = nalloc * 2;
}
if (domains == NULL)
{
fprintf(stderr,
"%s: malloc(): %s\n",
progname,
strerror(errno));
return EX_UNAVAILABLE;
}
}
domains[n] = strdup(line);
if (domains[n] == NULL)
{
fprintf(stderr, "%s: strdup(): %s\n",
progname, strerror(errno));
return EX_UNAVAILABLE;
}
if (strchr(domains[n], '*') != NULL)
makepats = TRUE;
n++;
}
if (domains != NULL)
domains[n] = NULL;
fclose(f);
}
else
{
n = 1;
for (p = domlist; *p != '\0'; p++)
{
if (*p == ',')
n++;
}
domains = (char **) malloc((n + 1) * sizeof(char *));
if (domains == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
return EX_UNAVAILABLE;
}
n = 0;
for (p = strtok(domlist, ",");
p != NULL;
p = strtok(NULL, ","))
{
domains[n] = p;
if (strchr(domains[n], '*') != NULL)
makepats = TRUE;
n++;
}
domains[n] = NULL;
}
if (makepats)
{
char *end;
char *q;
char patbuf[BUFRSZ + 1];
dompats = (regex_t **) malloc ((n + 1) * sizeof (regex_t *));
if (dompats == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
return EX_UNAVAILABLE;
}
memset(dompats, '\0', (n + 1) * sizeof (regex_t *));
for (c = 0; c < n; c++)
{
memset(patbuf, '\0', sizeof patbuf);
end = patbuf + sizeof patbuf;
patbuf[0] = '^';
for (p = domains[c], q = patbuf + 1;
*p != '\0' && q < end;
p++)
{
switch (*p)
{
case '*':
*q = '.';
q++;
*q = '*';
q++;
break;
case '.':
*q = '\\';
q++;
*q = '.';
q++;
break;
default:
*q = *p;
q++;
break;
}
}
*q++ = '$';
if (q >= end)
{
fprintf(stderr,
"%s: regular expression for \"%s\" too large\n",
progname, domains[c]);
return EX_UNAVAILABLE;
}
dompats[c] = (regex_t *) malloc(sizeof(regex_t));
if (dompats[c] == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
return EX_UNAVAILABLE;
}
status = regcomp(dompats[c], patbuf,
(REG_EXTENDED|REG_ICASE));
if (status != 0)
{
(void) regerror(status, dompats[c],
patbuf, sizeof patbuf);
fprintf(stderr, "%s: regcomp(): %s\n",
progname, patbuf);
return EX_UNAVAILABLE;
}
}
}
}
if (omitlist != NULL)
{
int n = 1;
for (p = omitlist; *p != '\0'; p++)
{
if (*p == ',')
n++;
}
omithdrs = (char **) malloc((n + 1) * sizeof(char *));
if (omithdrs == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
return EX_UNAVAILABLE;
}
n = 0;
for (p = strtok(omitlist, ",");
p != NULL;
p = strtok(NULL, ","))
omithdrs[n++] = p;
omithdrs[n] = NULL;
}
if (alwayslist != NULL)
{
int n = 1;
for (p = alwayslist; *p != '\0'; p++)
{
if (*p == ',')
n++;
}
alwayshdrs = (char **) malloc((n + 1) * sizeof(char *));
if (alwayshdrs == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
return EX_UNAVAILABLE;
}
n = 0;
for (p = strtok(alwayslist, ",");
p != NULL;
p = strtok(NULL, ","))
alwayshdrs[n++] = p;
alwayshdrs[n] = NULL;
}
if (macrolist != NULL)
{
int n = 1;
char *macrocopy;
for (p = macrolist; *p != '\0'; p++)
{
if (*p == ',')
n++;
}
macros = (char **) malloc((n + 1) * sizeof(char *));
values = (char **) malloc((n + 1) * sizeof(char *));
macrocopy = strdup(macrolist);
if (macros == NULL || values == NULL || macrocopy == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
return EX_UNAVAILABLE;
}
n = 0;
for (p = strtok(macrocopy, ",");
p != NULL;
p = strtok(NULL, ","))
{
macros[n] = p;
values[n] = strchr(p, '=');
if (values[n] != NULL)
{
*(values[n]) = '\0';
values[n] += 1;
}
n++;
}
macros[n] = NULL;
values[n] = NULL;
}
if (mtalist != NULL)
{
int n = 1;
for (p = mtalist; *p != '\0'; p++)
{
if (*p == ',')
n++;
}
mtas = (char **) malloc((n + 1) * sizeof(char *));
if (mtas == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
return EX_UNAVAILABLE;
}
n = 0;
for (p = strtok(mtalist, ",");
p != NULL;
p = strtok(NULL, ","))
mtas[n++] = p;
mtas[n] = NULL;
}
if (remarlist != NULL)
{
int n = 1;
for (p = remarlist; *p != '\0'; p++)
{
if (*p == ',')
n++;
}
remar = (char **) malloc((n + 1) * sizeof(char *));
if (remar == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
return EX_UNAVAILABLE;
}
n = 0;
for (p = strtok(remarlist, ",");
p != NULL;
p = strtok(NULL, ","))
remar[n++] = p;
remar[n] = NULL;
}
/* peer list */
if (peerfile != NULL && !testmode)
{
FILE *f;
f = fopen(peerfile, "r");
if (f == NULL)
{
fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
peerfile, strerror(errno));
return EX_UNAVAILABLE;
}
if (!dkimf_load_list(f, NULL, &peerlist))
{
fclose(f);
return EX_UNAVAILABLE;
}
fclose(f);
}
/* internal list */
if (ilist != NULL && !testmode)
{
FILE *f;
f = fopen(ilist, "r");
if (f == NULL)
{
fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
ilist, strerror(errno));
return EX_UNAVAILABLE;
}
if (!dkimf_load_list(f, NULL, &internal))
{
fclose(f);
return EX_UNAVAILABLE;
}
fclose(f);
}
else
{
if (!dkimf_load_list(NULL, defilist, &internal))
return EX_UNAVAILABLE;
}
/* external ignore list */
if (elist != NULL && !testmode)
{
FILE *f;
f = fopen(elist, "r");
if (f == NULL)
{
fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
elist, strerror(errno));
return EX_UNAVAILABLE;
}
if (!dkimf_load_list(f, NULL, &exignore))
{
fclose(f);
return EX_UNAVAILABLE;
}
fclose(f);
}
/* suppress a bunch of things if we're in test mode */
if (testmode)
{
dolog = FALSE;
autorestart = FALSE;
dofork = FALSE;
become = NULL;
pidfile = NULL;
}
#ifdef _FFR_REPLACE_RULES
/* replacement list */
if (repfile != NULL)
{
FILE *f;
f = fopen(repfile, "r");
if (f == NULL)
{
fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
repfile, strerror(errno));
return EX_UNAVAILABLE;
}
if (!dkimf_load_replist(f, &replist))
{
fclose(f);
return EX_UNAVAILABLE;
}
fclose(f);
}
#endif /* _FFR_REPLACE_RULES */
/* activate logging */
if (dolog)
{
#ifdef LOG_MAIL
openlog(progname, LOG_PID, LOG_MAIL);
#else /* LOG_MAIL */
openlog(progname, LOG_PID);
#endif /* LOG_MAIL */
}
dkimf_setmaxfd();
/* change user if appropriate */
if (become != NULL)
{
gid_t gid;
char *colon;
struct passwd *pw;
struct group *gr = NULL;
/* see if there was a group specified; if so, validate */
colon = strchr(become, ':');
if (colon != NULL)
{
*colon = '\0';
gr = getgrnam(colon + 1);
if (gr == NULL)
{
char *q;
gid = (gid_t) strtol(colon + 1, &q, 10);
if (*q == '\0')
gr = getgrgid(gid);
if (gr == NULL)
{
if (dolog)
{
syslog(LOG_ERR,
"no such group or gid `%s'",
colon + 1);
}
fprintf(stderr,
"%s: no such group `%s'\n",
progname, colon + 1);
return EX_DATAERR;
}
}
}
/* validate the user */
pw = getpwnam(become);
if (pw == NULL)
{
char *q;
uid_t uid;
uid = (uid_t) strtol(become, &q, 10);
if (*q == '\0')
pw = getpwuid(uid);
if (pw == NULL)
{
if (dolog)
{
syslog(LOG_ERR,
"no such user or uid `%s'",
become);
}
fprintf(stderr, "%s: no such user `%s'\n",
progname, become);
return EX_DATAERR;
}
}
if (gr == NULL)
gid = pw->pw_gid;
/* make all the process changes */
if (getuid() != pw->pw_uid)
{
if (initgroups(pw->pw_name, gid) != 0)
{
if (dolog)
{
syslog(LOG_ERR, "initgroups(): %s",
strerror(errno));
}
fprintf(stderr, "%s: initgroups(): %s\n",
progname, strerror(errno));
return EX_NOPERM;
}
else if (setgid(gid) != 0)
{
if (dolog)
{
syslog(LOG_ERR, "setgid(): %s",
strerror(errno));
}
fprintf(stderr, "%s: setgid(): %s\n", progname,
strerror(errno));
return EX_NOPERM;
}
else if (setuid(pw->pw_uid) != 0)
{
if (dolog)
{
syslog(LOG_ERR, "setuid(): %s",
strerror(errno));
}
fprintf(stderr, "%s: setuid(): %s\n", progname,
strerror(errno));
return EX_NOPERM;
}
}
(void) endpwent();
}
/* load the secret key, if one was specified */
if (keyfile != NULL && multikey)
{
status = dkimf_loadkeys(keyfile);
if (status != 0)
return EX_UNAVAILABLE;
}
else if (keyfile != NULL)
{
int fd;
size_t rlen;
struct stat s;
status = stat(keyfile, &s);
if (status != 0)
{
if (dolog)
{
int saveerrno;
saveerrno = errno;
syslog(LOG_ERR, "%s: stat(): %s", keyfile,
strerror(errno));
errno = saveerrno;
}
fprintf(stderr, "%s: %s: stat(): %s\n", progname,
keyfile, strerror(errno));
return EX_UNAVAILABLE;
}
s33krit = malloc(s.st_size + 1);
if (s33krit == NULL)
{
if (dolog)
{
int saveerrno;
saveerrno = errno;
syslog(LOG_ERR, "malloc(): %s",
strerror(errno));
errno = saveerrno;
}
fprintf(stderr, "%s: malloc(): %s\n", progname,
strerror(errno));
return EX_UNAVAILABLE;
}
keylen = s.st_size + 1;
fd = open(keyfile, O_RDONLY, 0);
if (fd < 0)
{
if (dolog)
{
int saveerrno;
saveerrno = errno;
syslog(LOG_ERR, "%s: open(): %s", keyfile,
strerror(errno));
errno = saveerrno;
}
fprintf(stderr, "%s: %s: open(): %s\n", progname,
keyfile, strerror(errno));
free(s33krit);
return EX_UNAVAILABLE;
}
rlen = read(fd, s33krit, s.st_size + 1);
if (rlen == -1)
{
if (dolog)
{
int saveerrno;
saveerrno = errno;
syslog(LOG_ERR, "%s: read(): %s", keyfile,
strerror(errno));
errno = saveerrno;
}
fprintf(stderr, "%s: %s: read(): %s\n", progname,
keyfile, strerror(errno));
close(fd);
free(s33krit);
return EX_UNAVAILABLE;
}
else if (rlen != s.st_size)
{
if (dolog)
{
syslog(LOG_ERR, "%s: read() wrong size (%u)",
keyfile, rlen);
}
fprintf(stderr, "%s: %s: read() wrong size (%u)\n",
progname, keyfile, rlen);
close(fd);
free(s33krit);
return EX_UNAVAILABLE;
}
close(fd);
s33krit[s.st_size] = '\0';
seckey = s33krit;
}
die = FALSE;
if (autorestart)
{
bool quitloop = FALSE;
int status;
pid_t pid;
pid_t wpid;
struct sigaction sa;
if (dofork)
{
pid = fork();
switch (pid)
{
case -1:
if (dolog)
{
int saveerrno;
saveerrno = errno;
syslog(LOG_ERR, "fork(): %s",
strerror(errno));
errno = saveerrno;
}
fprintf(stderr, "%s: fork(): %s\n",
progname, strerror(errno));
dkimf_zapkey();
return EX_OSERR;
case 0:
dkimf_stdio();
break;
default:
dkimf_zapkey();
return EX_OK;
}
}
if (pidfile != NULL)
{
f = fopen(pidfile, "w");
if (f != NULL)
{
fprintf(f, "%ld\n", (long) getpid());
(void) fclose(f);
}
else
{
if (dolog)
{
syslog(LOG_ERR,
"can't write pid to %s: %s",
pidfile, strerror(errno));
}
}
}
sa.sa_handler = dkimf_sighandler;
/* XXX -- HAHAHAH => sa.sa_sigaction = NULL; */
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGHUP);
sigaddset(&sa.sa_mask, SIGINT);
sigaddset(&sa.sa_mask, SIGTERM);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) != 0 ||
sigaction(SIGINT, &sa, NULL) != 0 ||
sigaction(SIGTERM, &sa, NULL) != 0)
{
if (dolog)
{
syslog(LOG_ERR, "[parent] sigaction(): %s",
strerror(errno));
}
}
while (!quitloop)
{
if (dkimf_socket_cleanup(sock) != 0)
{
syslog(LOG_ERR,
"[parent] socket cleanup failed: %s",
strerror(errno));
return EX_UNAVAILABLE;
}
pid = fork();
switch (pid)
{
case -1:
if (dolog)
{
syslog(LOG_ERR, "fork(): %s",
strerror(errno));
}
dkimf_zapkey();
return EX_OSERR;
case 0:
sa.sa_handler = SIG_DFL;
if (sigaction(SIGHUP, &sa, NULL) != 0 ||
sigaction(SIGINT, &sa, NULL) != 0 ||
sigaction(SIGTERM, &sa, NULL) != 0)
{
if (dolog)
{
syslog(LOG_ERR,
"[child] sigaction(): %s",
strerror(errno));
}
}
quitloop = TRUE;
break;
default:
for (;;)
{
wpid = wait(&status);
if (wpid == -1 && errno == EINTR)
{
if (die)
{
dkimf_killchild(pid,
diesig);
dkimf_zapkey();
if (pidfile != NULL)
(void) unlink(pidfile);
exit(EX_OK);
}
}
if (pid != wpid)
continue;
if (wpid != -1 && dolog)
{
if (WIFSIGNALED(status))
{
syslog(LOG_NOTICE,
"terminated with signal %d, restarting",
WTERMSIG(status));
}
else if (WIFEXITED(status))
{
syslog(LOG_NOTICE,
"exited with status %d, restarting",
WEXITSTATUS(status));
}
}
break;
}
break;
}
}
}
if (filemask != -1)
(void) umask((mode_t) filemask);
if (!testmode)
{
/* register with the milter interface */
if (smfi_register(smfilter) == MI_FAILURE)
{
if (dolog)
syslog(LOG_ERR, "smfi_register() failed");
fprintf(stderr, "%s: smfi_register() failed\n",
progname);
dkimf_zapkey();
if (!autorestart && pidfile != NULL)
(void) unlink(pidfile);
return EX_UNAVAILABLE;
}
/* try to establish the milter socket */
if (smfi_opensocket(FALSE) == MI_FAILURE)
{
if (dolog)
syslog(LOG_ERR, "smfi_opensocket() failed");
fprintf(stderr, "%s: smfi_opensocket() failed\n",
progname);
dkimf_zapkey();
return EX_UNAVAILABLE;
}
}
if (!autorestart && dofork)
{
pid_t pid;
pid = fork();
switch (pid)
{
case -1:
if (dolog)
{
int saveerrno;
saveerrno = errno;
syslog(LOG_ERR, "fork(): %s", strerror(errno));
errno = saveerrno;
}
fprintf(stderr, "%s: fork(): %s\n", progname,
strerror(errno));
dkimf_zapkey();
return EX_OSERR;
case 0:
dkimf_stdio();
break;
default:
dkimf_zapkey();
return EX_OK;
}
}
/* write out the pid */
if (!autorestart && pidfile != NULL)
{
f = fopen(pidfile, "w");
if (f != NULL)
{
fprintf(f, "%ld\n", (long) getpid());
(void) fclose(f);
}
else
{
if (dolog)
{
syslog(LOG_ERR, "can't write pid to %s: %s",
pidfile, strerror(errno));
}
}
}
ERR_load_crypto_strings();
/* initialize the DKIM package */
libdkim = dkim_init(NULL, NULL);
if (libdkim == NULL)
{
if (dolog)
syslog(LOG_ERR, "can't initialize DKIM library");
dkimf_zapkey();
if (!autorestart && pidfile != NULL)
(void) unlink(pidfile);
return EX_UNAVAILABLE;
}
else
{
u_int opts;
(void) dkim_options(libdkim, DKIM_OP_GETOPT, DKIM_OPTS_FLAGS,
&opts, sizeof opts);
opts |= DKIM_LIBFLAGS_ACCEPTV05;
#ifdef QUERY_CACHE
if (querycache)
{
opts |= DKIM_LIBFLAGS_CACHE;
(void) time(&cache_lastlog);
}
#endif /* QUERY_CACHE */
(void) dkim_options(libdkim, DKIM_OP_SETOPT, DKIM_OPTS_FLAGS,
&opts, sizeof opts);
}
#ifdef USE_ARLIB
/* set the DNS callback */
(void) dkim_set_dns_callback(dfc->mctx_dkim, dkimf_sendprogress,
CBINTERVAL);
#endif /* USE_ARLIB */
(void) dkim_options(libdkim, DKIM_OP_SETOPT, DKIM_OPTS_SKIPHDRS,
(void *) should_not_signhdrs, sizeof (u_char **));
(void) dkim_options(libdkim, DKIM_OP_SETOPT, DKIM_OPTS_TIMEOUT,
&tmo, sizeof tmo);
if (sigttl != 0)
{
time_t sigtime = (time_t) sigttl;
(void) dkim_options(libdkim, DKIM_OP_SETOPT,
DKIM_OPTS_SIGNATURETTL, &sigtime,
sizeof sigtime);
}
if (clockdrift != 0)
{
time_t drift = (time_t) clockdrift;
(void) dkim_options(libdkim, DKIM_OP_SETOPT,
DKIM_OPTS_CLOCKDRIFT, &drift,
sizeof drift);
}
if (send_reports || DKIM_DEBUG('c') || blen || ztags)
{
u_int opts;
(void) dkim_options(libdkim, DKIM_OP_GETOPT, DKIM_OPTS_FLAGS,
&opts, sizeof opts);
opts |= DKIM_LIBFLAGS_TMPFILES;
if (DKIM_DEBUG('c'))
opts |= DKIM_LIBFLAGS_KEEPFILES;
if (blen)
opts |= DKIM_LIBFLAGS_SIGNLEN;
if (ztags)
opts |= DKIM_LIBFLAGS_ZTAGS;
(void) dkim_options(libdkim, DKIM_OP_SETOPT, DKIM_OPTS_FLAGS,
&opts, sizeof opts);
}
if (omithdrs != NULL)
{
status = dkim_options(libdkim, DKIM_OP_SETOPT,
DKIM_OPTS_SKIPHDRS,
omithdrs, sizeof omithdrs);
if (status != DKIM_STAT_OK)
{
fprintf(stderr,
"%s: dkim_options(DKIM_OPTS_SKIPHDRS) returned %d\n",
progname, status);
if (!autorestart && pidfile != NULL)
(void) unlink(pidfile);
return EX_SOFTWARE;
}
}
if (alwayshdrs != NULL)
{
status = dkim_options(libdkim, DKIM_OP_SETOPT,
DKIM_OPTS_ALWAYSHDRS,
alwayshdrs, sizeof alwayshdrs);
if (status != DKIM_STAT_OK)
{
fprintf(stderr,
"%s: dkim_options(DKIM_OPTS_ALWAYSHDRS) returned %d\n",
progname, status);
if (!autorestart && pidfile != NULL)
(void) unlink(pidfile);
return EX_SOFTWARE;
}
}
if (signhdrs != NULL)
{
size_t len;
char **signhdrlist;
c = 1;
for (p = signhdrs; p != NULL; p = strchr(p + 1, ','))
c++;
len = (c + 1) * sizeof(char *);
signhdrlist = malloc(len);
if (signhdrlist == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n",
progname, strerror(errno));
if (!autorestart && pidfile != NULL)
(void) unlink(pidfile);
return EX_OSERR;
}
memset(signhdrlist, '\0', len);
for (c = 0, p = strtok(signhdrs, ",");
p != NULL;
c++, p = strtok(NULL, ","))
signhdrlist[c] = p;
status = dkim_options(libdkim, DKIM_OP_SETOPT,
DKIM_OPTS_SIGNHDRS, signhdrlist,
sizeof signhdrlist);
}
if (testpubkeys != NULL)
{
dkim_query_t qtype = DKIM_QUERY_FILE;
(void) dkim_options(libdkim, DKIM_OP_SETOPT,
DKIM_OPTS_QUERYMETHOD,
&qtype, sizeof qtype);
(void) dkim_options(libdkim, DKIM_OP_SETOPT,
DKIM_OPTS_QUERYINFO,
testpubkeys, strlen(testpubkeys));
}
#if VERIFY_DOMAINKEYS
libdk = dk_init(NULL, NULL);
if (libdk == NULL)
{
if (dolog)
syslog(LOG_ERR, "can't initialize DK library");
if (!autorestart && pidfile != NULL)
(void) unlink(pidfile);
return EX_UNAVAILABLE;
}
#endif /* VERIFY_DOMAINKEYS */
/* perform test mode */
if (testfile != NULL)
{
status = dkimf_testfile(libdkim, testfile, fixedtime,
stricttest, verbose);
dkim_close(libdkim);
return status;
}
pthread_mutex_init(&popen_lock, NULL);
memset(argstr, '\0', sizeof argstr);
end = &argstr[sizeof argstr - 1];
n = sizeof argstr;
for (c = 1, p = argstr; c < argc && p < end; c++)
{
if (strchr(argv[c], ' ') != NULL)
{
status = snprintf(p, n, "%s \"%s\"",
c == 1 ? "args:" : "",
argv[c]);
}
else
{
status = snprintf(p, n, "%s %s",
c == 1 ? "args:" : "",
argv[c]);
}
p += status;
n -= status;
}
#if POPAUTH
if (popdbfile != NULL)
{
status = dkimf_initpopauth();
if (status != 0)
{
fprintf(stderr, "%s: can't initialize mutex: %s\n",
progname, strerror(status));
syslog(LOG_ERR, "can't initialize mutex: %s",
popdbfile);
}
status = 0;
# if DB_VERSION_MAJOR > 2
status = db_create(&popdb, NULL, 0);
if (status == 0)
{
# if DB_VERSION_MAJOR > 3
status = popdb->open(popdb, NULL, popdbfile, NULL,
DB_UNKNOWN, (DB_RDONLY|DB_THREAD),
0);
# else /* DB_VERSION_MAJOR > 3 */
status = popdb->open(popdb, popdbfile, NULL, DB_UNKNOWN,
(DB_RDONLY|DB_THREAD), 0);
# endif /* DB_VERSION_MAJOR > 3 */
}
# elif DB_VERSION_MAJOR == 2
status = db_open(popdbfile, DB_HASH, DB_RDONLY, DB_MODE, NULL,
NULL, &popdb);
# else /* DB_VERSION_MAJOR < 2 */
popdb = dbopen(popdbfile, O_RDONLY, DB_HASH, NULL);
if (popdb == NULL)
status = errno;
# endif /* DB_VERSION_MAJOR */
if (status != 0)
{
fprintf(stderr, "%s: can't open database %s: %s\n",
progname, popdbfile, db_strerror(errno));
syslog(LOG_ERR, "can't open database %s",
popdbfile);
dkimf_zapkey();
if (!autorestart && pidfile != NULL)
(void) unlink(pidfile);
return EX_UNAVAILABLE;
}
}
#endif /* POPAUTH */
#ifdef _FFR_STATS
dkimf_stats_init();
#endif /* _FFR_STATS */
if (dolog)
{
syslog(LOG_INFO, "%s v%s starting (%s)", DKIMF_PRODUCT,
DKIMF_VERSION, argstr);
}
/* call the milter mainline */
errno = 0;
status = smfi_main();
if (dolog)
{
syslog(LOG_INFO,
"%s v%s terminating with status %d, errno = %d",
DKIMF_PRODUCT, DKIMF_VERSION, status, errno);
}
#if POPAUTH
if (popdb != NULL)
{
# if DB_VERSION_MAJOR < 2
(void) popdb->close(popdb);
# else /* DB_VERSION_MAJOR < 2 */
(void) popdb->close(popdb, 0);
# endif /* DB_VERSION_MAJOR */
}
#endif /* POPAUTH */
dkim_close(libdkim);
dkimf_zapkey();
if (!autorestart && pidfile != NULL)
(void) unlink(pidfile);
return status;
}
syntax highlighted by Code2HTML, v. 0.9.1