/*
** Copyright (c) 2004-2007 Sendmail, Inc. and its suppliers.
** All rights reserved.
**
** $Id: dk-filter.c,v 1.172 2007/05/31 18:59:00 msk Exp $
*/
#ifndef lint
static char dk_filter_c_id[] = "@(#)$Id: dk-filter.c,v 1.172 2007/05/31 18:59:00 msk Exp $";
#endif /* !lint */
/* system includes */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/socket.h>
#if DEBUG
# include <netinet/in.h>
# include <arpa/inet.h>
#endif /* DEBUG */
#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 <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <pthread.h>
#include <netdb.h>
#include <signal.h>
#include <regex.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 */
/* openssl includes */
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
/* sendmail includes */
#include <sm/cdefs.h>
#include <sm/string.h>
/* libmilter includes */
#ifndef DEBUG
#include "libmilter/mfapi.h"
#endif /* !DEBUG */
/* libdk includes */
#include <dk.h>
#if POPAUTH
/* libdb includes */
# include <db.h>
#endif /* POPAUTH */
/* dk-filter includes */
#include "dk-filter.h"
#include "util.h"
#ifdef DEBUG
/* DEBUGGING STUFF */
# define MI_SUCCESS 1
# define MI_FAILURE (-1)
# define SMFIS_CONTINUE 0
# define SMFIS_ACCEPT 1
# define SMFIS_REJECT 2
# define SMFIS_DISCARD 3
# define SMFIS_TEMPFAIL 4
# define sfsistat int
# define SMFICTX void
# define _SOCK_ADDR struct sockaddr
int smfi_addheader __P((void *, char *, char *));
int smfi_chgheader __P((void *, char *, int, char *));
void *smfi_getpriv __P((void *));
char *smfi_getsymval __P((void *, char *));
int smfi_insheader __P((void *, int, char *, char *));
int smfi_replacebody __P((void *, char *, size_t));
void smfi_setconn __P((char *));
void smfi_setpriv __P((void *, void *));
void smfi_setreply __P((void *, char *, char *, char *));
char *smfis_ret[] = {
"SMFIS_CONTINUE",
"SMFIS_ACCEPT",
"SMFIS_REJECT",
"SMFIS_DISCARD",
"SMFIS_TEMPFAIL",
};
#endif /* DEBUG */
/*
** Header -- a handle referring to a header
*/
typedef struct Header * Header;
struct Header
{
char * hdr_hdr;
char * hdr_val;
struct Header * hdr_next;
};
#if _FFR_MULTIPLE_KEYS
/*
** KEYTABLE -- table of keys
*/
struct keytable
{
char * key_selector; /* selector */
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 */
};
#endif /* _FFR_MULTIPLE_KEYS */
/*
** 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 */
int mctx_status; /* status to report back */
dk_canon_t mctx_canonalg; /* canonicalization algorithm */
dk_alg_t mctx_signalg; /* signature algorithm */
int mctx_queryalg; /* query algorithm */
char * mctx_jobid; /* job ID */
char * mctx_domain; /* domain doing the signing */
DK * mctx_dk; /* DomainKeys handle */
struct Header * mctx_hqhead; /* header queue head */
struct Header * mctx_hqtail; /* header queue tail */
#if _FFR_MULTIPLE_KEYS
struct keytable * mctx_key; /* key information */
#endif /* _FFR_MULTIPLE_KEYS */
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[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 */
};
struct Config defaults =
{
SMFIS_ACCEPT,
/* SMFIS_REJECT, */ SMFIS_ACCEPT,
/* SMFIS_REJECT, */ SMFIS_ACCEPT,
SMFIS_TEMPFAIL,
SMFIS_TEMPFAIL
};
/*
** LOOKUP -- lookup table
*/
struct lookup
{
char * str;
int code;
};
#define CFG_NOSIGNATURE 1
#define CFG_BADSIGNATURE 2
#define CFG_SIGMISSING 3
#define CFG_DNSERROR 4
#define CFG_INTERNAL 5
#define DKF_MODE_SIGNER 0x01
#define DKF_MODE_VERIFIER 0x02
#define DKF_MODE_DEFAULT (DKF_MODE_SIGNER|DKF_MODE_VERIFIER)
#define DKF_STATUS_GOOD 0
#define DKF_STATUS_BAD 1
#define DKF_STATUS_NOKEY 2
#define DKF_STATUS_REVOKED 3
#define DKF_STATUS_NOSIGNATURE 4
#define DKF_STATUS_BADFORMAT 6
#define DKF_STATUS_NONPART 7
#define DKF_STATUS_UNKNOWN 8
struct lookup dkf_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 },
{ NULL, -1 },
};
struct lookup dkf_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 dkf_status[] =
{
{ "unknown", DKF_STATUS_UNKNOWN },
{ "good", DKF_STATUS_GOOD },
{ "bad", DKF_STATUS_BAD },
{ "no key", DKF_STATUS_NOKEY },
{ "revoked", DKF_STATUS_REVOKED },
{ "no signature", DKF_STATUS_NOSIGNATURE },
{ "bad format", DKF_STATUS_BADFORMAT },
{ "non-participant", DKF_STATUS_NONPART },
{ NULL, -1 },
};
struct lookup dkf_canon[] =
{
{ "nofws", DK_CANON_NOFWS },
{ "simple", DK_CANON_SIMPLE },
{ NULL, -1 },
};
/* PROTOTYPES */
sfsistat mlfi_abort __P((SMFICTX *));
sfsistat mlfi_body __P((SMFICTX *, u_char *, size_t));
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 dkf_cleanup __P((SMFICTX *));
/* GLOBALS */
bool addxhdr; /* add identifying header? */
bool hdrlist; /* signature includes hdrlist */
bool dolog; /* syslog */
bool quarantine; /* quarantine failures? */
bool no_i_whine; /* noted ${i} is undefined */
bool send_reports; /* send failure reports */
#if _FFR_FLUSH_HEADERS
bool flushheaders; /* flush old DK headers */
#endif /* _FFR_FLUSH_HEADERS */
#if _FFR_REQUIRED_HEADERS
bool req_hdrs; /* required header checks */
#endif /* _FFR_REQUIRED_HEADERS */
bool subdomains; /* sign subdomains */
bool die; /* global "die" flag */
unsigned int tmo; /* DNS timeout */
unsigned int mode; /* operating mode */
int diesig; /* signal to distribute */
size_t keylen; /* size of secret key */
dk_canon_t canon; /* canon. method for signing */
char *progname; /* program name */
unsigned char *seckey; /* secret key data */
char *selector; /* key selector */
struct Config config; /* configuration */
DK_LIB *libdk; /* libdk handle */
#if _FFR_MULTIPLE_KEYS
struct keytable *keyhead; /* key list */
struct keytable *keytail; /* key list */
#endif /* _FFR_MULTIPLE_KEYS */
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 **macros; /* macros/values to check */
char **values; /* macros/values to check */
#if POPAUTH
DB *popdb; /* POP auth DB */
#endif /* POPAUTH */
pthread_mutex_t count_lock; /* counter lock */
int thread_count; /* thread count */
pthread_mutex_t popen_lock; /* popen() lock */
#ifdef DEBUG
static void *fakepriv; /* fake private space */
#endif /* DEBUG */
/* Other useful definitions */
#define CRLF "\r\n" /* CRLF */
#define DK_DEFAULT_SELECTOR "main" /* default selector name */
#ifndef SENDMAIL_OPTIONS
# define SENDMAIL_OPTIONS "" /* options for reports */
#endif /* SENDMAIL_OPTIONS */
/* MACROS */
#define DK_DEBUG(x) (getenv("DKDEBUG") != NULL && \
strchr(getenv("DKDEBUG"), (x)) != NULL)
#define JOBID(x) ((x) == NULL ? JOBIDUNKNOWN : (x))
#define TRYFREE(x) do { \
if ((x) != NULL) \
{ \
free(x); \
(x) = NULL; \
} \
} while (0)
/*
** ==================================================================
** BEGIN private section
*/
/*
** DKF_SIGHANDLER -- signal handler
**
** Parameters:
** sig -- signal received
**
** Return value:
** None.
*/
static void
dkf_sighandler(int sig)
{
if (sig == SIGINT || sig == SIGTERM || sig == SIGHUP)
{
diesig = sig;
die = TRUE;
}
}
/*
** DKF_KILLCHILD -- kill child process
**
** Parameters:
** pid -- process ID to signal
** sig -- signal to use
**
** Return value:
** None.
*/
static void
dkf_killchild(pid_t pid, int sig)
{
if (kill(pid, sig) == -1 && dolog)
{
syslog(LOG_ERR, "kill(%d, %d): %s", pid, sig,
strerror(errno));
}
}
/*
** DKF_ZAPKEY -- clobber the copy of the private key
**
** Parameters:
** None.
**
** Return value:
** None.
*/
static void
dkf_zapkey(void)
{
if (seckey != NULL)
{
memset(seckey, '\0', keylen);
free(seckey);
}
#if _FFR_MULTIPLE_KEYS
if (keyhead != NULL)
{
struct keytable *key;
for (key = keyhead; key != NULL; key = key->key_next)
{
memset(key->key_data, '\0', key->key_len);
free(key->key_data);
}
}
#endif /* _FFR_MULTIPLE_KEYS */
}
/*
** DKF_STDIO -- set up the base descriptors to go nowhere
**
** Parameters:
** None.
**
** Return value:
** None.
*/
static void
dkf_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);
(void) close(devnull);
}
(void) setsid();
}
/*
** DKF_SIGNALG -- return text version of the signing algorithm
**
** Parameters:
** alg -- A dk_alg_t
**
** Return value:
** Pointer to a string which is the text version of the provided code.
*/
static char *
dkf_signalg(dk_alg_t alg)
{
switch (alg)
{
case DK_SIGN_RSASHA1:
return "rsa-sha1";
case DK_SIGN_UNKNOWN:
return "unknown";
default:
return NULL;
}
}
/*
** DKF_CANONALG -- return text version of the canonicalization algorithm
**
** Parameters:
** alg -- A dk_canon_t
**
** Return value:
** Pointer to a string which is the text version of the provided code.
*/
static char *
dkf_canonalg(dk_canon_t alg)
{
switch (alg)
{
case DK_CANON_NOFWS:
return "nofws";
case DK_CANON_SIMPLE:
return "simple";
case DK_CANON_UNKNOWN:
return "unknown";
default:
return NULL;
}
}
/*
** DKF_QUERYALG -- return text version of the query algorithm
**
** Parameters:
** alg -- integer representing the query algorithm used
**
** Return value:
** Pointer to a string which is the text version of the provided code.
*/
static char *
dkf_queryalg(int alg)
{
switch (alg)
{
case DK_QUERY_DNS:
return "dns";
case DK_QUERY_UNKNOWN:
return "unknown";
default:
return NULL;
}
}
/*
** DKF_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
dkf_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 = DKF_STATUS_UNKNOWN;
ctx->mctx_canonalg = canon;
ctx->mctx_signalg = DK_SIGN_DEFAULT;
ctx->mctx_queryalg = DK_QUERY_DEFAULT;
return ctx;
}
/*
** DKF_CLEANUP -- release local resources related to a message
**
** Parameters:
** ctx -- milter context
**
** Return value:
** None.
*/
static void
dkf_cleanup(SMFICTX *ctx)
{
msgctx dfc;
connctx cc;
#ifndef DEBUG
assert(ctx != NULL);
#endif /* !DEBUG */
cc = (connctx) smfi_getpriv(ctx);
if (cc == NULL)
return;
dfc = cc->cctx_msg;
/* release memory */
if (dfc != NULL)
{
/* TRYFREE(dfc->mctx_jobid); */
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_dk != NULL)
dk_free(dfc->mctx_dk);
free(dfc);
cc->cctx_msg = NULL;
}
}
/*
** DKF_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
dkf_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;
}
}
/*
** DKF_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
dkf_parseconfig(char *confstr)
{
int vs;
char *p;
char *v;
char *tmp;
/* load defaults */
memcpy(&config, &defaults, sizeof(config));
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)
return FALSE;
*v = '\0';
v++;
vs = dkf_configlookup(v, dkf_values);
if (vs == -1)
{
fprintf(stderr,
"%s: invalid configuration value `%s'\n",
progname, v);
return FALSE;
}
/* apply what's been found */
switch (dkf_configlookup(p, dkf_params))
{
case CFG_NOSIGNATURE:
config.cfg_nosig = vs;
break;
case CFG_BADSIGNATURE:
config.cfg_badsig = vs;
break;
case CFG_SIGMISSING:
config.cfg_sigmissing = vs;
break;
case CFG_DNSERROR:
config.cfg_dnserr = vs;
break;
case CFG_INTERNAL:
config.cfg_internal = vs;
break;
default:
*v = '=';
fprintf(stderr,
"%s: invalid configuration parameter `%s'\n",
progname, p);
return FALSE;
}
}
return TRUE;
}
/*
** DKF_LIBSTATUS -- process a final status returned from libdk
**
** Parameters:
** ctx -- milter context
** where -- what function reported the error
** status -- status returned by a libdk call (DK_STAT_*)
**
** Return value:
** An smfistat value to be returned to libmilter.
*/
static sfsistat
dkf_libstatus(SMFICTX *ctx, char *where, int status)
{
int retcode = SMFIS_CONTINUE;
msgctx dfc;
connctx cc;
#ifndef DEBUG
assert(ctx != NULL);
#endif /* ! DEBUG */
cc = smfi_getpriv(ctx);
assert(cc != NULL);
dfc = cc->cctx_msg;
assert(dfc != NULL);
switch (status)
{
case DK_STAT_OK:
retcode = SMFIS_CONTINUE;
break;
case DK_STAT_INTERNAL:
retcode = config.cfg_internal;
if (dolog)
{
const char *err;
err = dk_geterror(dfc->mctx_dk);
if (err == NULL)
err = strerror(errno);
syslog(LOG_ERR,
"%s: %s%sinternal error from libdk: %s",
JOBID(dfc->mctx_jobid),
where == NULL ? "" : where,
where == NULL ? "" : ": ", err);
}
break;
case DK_STAT_BADSIG:
retcode = config.cfg_badsig;
if (dolog)
{
syslog(LOG_ERR, "%s: bad signature data",
JOBID(dfc->mctx_jobid));
}
break;
case DK_STAT_NOSIG:
retcode = config.cfg_nosig;
if (dolog)
{
syslog(LOG_ERR, "%s: no signature data",
JOBID(dfc->mctx_jobid));
}
break;
case DK_STAT_NORESOURCE:
retcode = config.cfg_internal;
if (dolog)
{
const char *err;
err = dk_geterror(dfc->mctx_dk);
if (err == NULL)
err = strerror(errno);
syslog(LOG_ERR, "%s: %s%sresource unavailable: %s",
JOBID(dfc->mctx_jobid),
where == NULL ? "" : where,
where == NULL ? "" : ": ", err);
}
break;
case DK_STAT_CANTVRFY:
retcode = config.cfg_badsig;
if (dolog)
{
const char *err;
err = dk_geterror(dfc->mctx_dk);
if (err == NULL)
err = "unknown cause";
syslog(LOG_ERR,
"%s: signature verification failed: %s",
JOBID(dfc->mctx_jobid), err);
}
break;
case DK_STAT_SYNTAX:
retcode = config.cfg_badsig;
if (dolog)
{
const char *err;
err = dk_geterror(dfc->mctx_dk);
if (err == NULL)
err = "unspecified";
syslog(LOG_ERR, "%s: syntax error: %s",
JOBID(dfc->mctx_jobid), err);
}
break;
}
if (ERR_peek_error() != 0 && dolog)
{
int n;
unsigned long e;
char errbuf[BUFRSZ + 1];
char tmp[BUFRSZ + 1];
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(dfc->mctx_jobid), errbuf);
}
if (status != DK_STAT_OK)
dkf_cleanup(ctx);
return retcode;
}
/*
** DKF_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.
*/
Header
dkf_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;
}
#if _FFR_MULTIPLE_KEYS
/*
** DKF_LOADKEYS -- load multiple keys
**
** Parameters:
** file -- input file
**
** Return value:
** 0 on success, !0 on failure
**
** Side effects:
** keyhead, keytail are updated.
*/
static int
dkf_loadkeys(char *file)
{
bool blank = TRUE;
int line = 0;
FILE *f;
char *p;
char buf[BUFRSZ + 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(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++;
p = strchr(buf, ':');
if (p == NULL)
{
fprintf(stderr, "%s: %s: line %d: malformed\n",
progname, file, line);
fclose(f);
return -1;
}
else
{
int status;
char *q;
char *r;
char *end;
size_t n;
struct keytable *new;
int keyfd;
struct stat s;
char retmp[BUFRSZ];
keyfd = open(p + 1, O_RDONLY, 0);
if (keyfd == -1)
{
fprintf(stderr, "%s: %s: open(): %s\n",
progname, p + 1, strerror(errno));
fclose(f);
return -1;
}
status = fstat(keyfd, &s);
if (status == -1)
{
fprintf(stderr, "%s: %s: fstat(): %s\n",
progname, p + 1, strerror(errno));
close(keyfd);
fclose(f);
return -1;
}
q = p + 1;
*p = '\0';
end = retmp + sizeof retmp - 1;
memset(retmp, '\0', sizeof retmp);
retmp[0] = '^';
r = &retmp[1];
for (p = buf; *p != '\0'; p++)
{
switch (*p)
{
case '*':
if (r + 3 >= end)
{
fprintf(stderr,
"%s: %s: line %d: \"%s\": expression too large\n",
progname, file, line,
buf);
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,
buf);
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(q, '/');
new->key_selector = strdup (r == NULL ? q : r + 1);
if (new->key_selector == NULL)
{
fprintf(stderr, "%s: strdup(): %s\n",
progname, strerror(errno));
close(keyfd);
fclose(f);
return -1;
}
status = regcomp(&new->key_re, retmp, 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, q, strerror(errno));
}
else
{
fprintf(stderr,
"%s: %s: read() truncated\n",
progname, q);
}
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;
}
#endif /* _FFR_MULTIPLE_KEYS */
/*
** DKF_REPORT -- generate a report on failure if possible
**
** Parameters:
** dk -- DK handle
** reason -- reason to report
**
** Return value:
** None.
*/
void
dkf_report(msgctx dfc, char *result, char *reason)
{
int cfd;
int status;
DK_STAT dkstatus;
size_t inl;
size_t outl;
FILE *out;
BIO *b64;
BIO *bout;
struct Header *hdr;
char buf[BUFRSZ];
char addr[MAXADDRESS + 1];
char hostname[MAXHOSTNAMELEN + 1];
assert(dfc != NULL);
if (!send_reports)
return;
memset(addr, '\0', sizeof addr);
memset(hostname, '\0', sizeof hostname);
(void) gethostname(hostname, sizeof hostname);
dkstatus = dk_reportinfo(dfc->mctx_dk, &cfd, addr, sizeof addr);
if (dkstatus != DK_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: DomainKeys failure report for %s\n",
dfc->mctx_jobid);
/* MIME stuff */
fprintf(out, "MIME-Version: 1.0\n");
fprintf(out,
"Content-Type: multipart/report; boundary=\"dkreport/%s/%s\"",
hostname, dfc->mctx_jobid);
/* ok, now then... */
fprintf(out, "\n");
/* first part: a text blob explaining what this is */
fprintf(out, "--dkreport/%s/%s\n", hostname, dfc->mctx_jobid);
fprintf(out, "Content-Type: text/plain\n");
fprintf(out, "\n");
fprintf(out, "DomainKeys 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, "--dkreport/%s/%s\n", hostname, dfc->mctx_jobid);
fprintf(out, "Content-Type: message/x-dk-report\n");
fprintf(out, "\n");
fprintf(out, "MTA: %s\n", hostname);
fprintf(out, "Agent: %s %s\n", DKF_PRODUCT, DKF_VERSION);
fprintf(out, "Result: %s\n", result == NULL ? "(none)" : result);
fprintf(out, "Reason: %s\n", reason == NULL ? "(none)" : reason);
fprintf(out, "\n");
/* third part: headers */
fprintf(out, "--dkreport/%s/%s\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");
/* fourth part: canonicalized form */
fprintf(out, "--dkreport/%s/%s\n", hostname, dfc->mctx_jobid);
fprintf(out, "Content-Type: text/plain\n"); /* ? */
fprintf(out, "Content-Disposition: attachment; filename=\"%s.txt\"\n",
dfc->mctx_jobid);
fprintf(out, "Content-Transfer-Encoding: base64\n");
fprintf(out, "\n");
(void) lseek(cfd, 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(cfd, buf, sizeof buf);
if (inl == 0)
break;
outl = BIO_write(bout, (char *) buf, inl);
if (outl != inl || inl < sizeof buf)
break;
}
BIO_free(bout);
/* end */
fprintf(out, "\n--dkreport/%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,
unsigned long f2 __attribute__((unused)),
unsigned long f3 __attribute__((unused)),
unsigned long *pf0, unsigned long *pf1,
unsigned long *pf2, unsigned long *pf3)
{
connctx cc;
#ifdef SMFIF_QUARANTINE
*pf0 = SMFIF_ADDHDRS|SMFIF_CHGHDRS|SMFIF_QUARANTINE;
#else /* SMFIF_QUARANTINE */
*pf0 = SMFIF_ADDHDRS|SMFIF_CHGHDRS;
#endif /* SMFIF_QUARANTINE */
*pf1 = 0;
#ifdef SMFIP_HDR_LEADSPC
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;
smfi_setpriv(ctx, cc);
}
}
#endif /* SMFIP_HDR_LEADSPC */
*pf2 = 0;
*pf3 = 0;
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 (DK_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] != '[')
{
dkf_lowercase(host);
if (dkf_checkhost(peerlist, host))
return SMFIS_ACCEPT;
}
/* try IP address, if available */
if (ip != NULL && ip->sa_family == AF_INET)
{
if (dkf_checkip(peerlist, ip))
return SMFIS_ACCEPT;
}
}
/* copy hostname and IP information to a connection context */
cc = smfi_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;
smfi_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;
#ifndef DEBUG
assert(ctx != NULL);
assert(envfrom != NULL);
#endif /* !DEBUG */
cc = (connctx) smfi_getpriv(ctx);
assert(cc != NULL);
if (DK_DEBUG('t'))
syslog(LOG_INFO, "thread %p envfrom", pthread_self());
/*
** Initialize a filter context.
*/
dkf_cleanup(ctx);
dfc = dkf_initcontext();
if (dfc == NULL)
{
if (dolog)
{
syslog(LOG_INFO,
"message requeueing (internal error)");
}
dkf_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;
char *q;
char hv[MAXHEADER];
#endif /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */
#ifndef DEBUG
assert(ctx != NULL);
#endif /* !DEBUG */
assert(headerf != NULL);
assert(headerv != NULL);
if (DK_DEBUG('t'))
syslog(LOG_INFO, "thread %p header", pthread_self());
cc = (connctx) smfi_getpriv(ctx);
assert(cc != NULL);
dfc = cc->cctx_msg;
assert(dfc != NULL);
newhdr = (Header) malloc(sizeof(struct Header));
if (newhdr == NULL)
{
if (dolog)
syslog(LOG_ERR, "malloc(): %s", strerror(errno));
dkf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
(void) memset(newhdr, '\0', sizeof(struct Header));
newhdr->hdr_hdr = strdup(headerf);
#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.
*/
(void) memset(hv, '\0', sizeof hv);
end = hv + sizeof hv;
for (p = headerv, q = hv; *p != '\0' && q < end; p++)
{
/* skip initial spaces */
if (q == hv && isascii(*p) && isspace(*p))
continue;
*q = *p;
q++;
}
newhdr->hdr_val = strdup(hv);
#else /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */
newhdr->hdr_val = strdup(headerv);
#endif /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */
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);
dkf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
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;
c = dkf_configlookup(headerv, dkf_canon);
if (c != -1)
dfc->mctx_canonalg = (dk_canon_t) c;
}
#endif /* _FFR_SELECT_CANONICALIZATION */
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)
{
bool msgsigned;
bool domainok;
bool originok;
bool skip;
int status;
connctx cc;
msgctx dfc;
char *p;
Header from;
Header hdr;
char addr[MAXADDRESS + 1];
char hdrbuf[MAXHEADER + 1];
#ifndef DEBUG
assert(ctx != NULL);
#endif /* !DEBUG */
if (DK_DEBUG('t'))
syslog(LOG_INFO, "thread %p eoh", pthread_self());
cc = (connctx) smfi_getpriv(ctx);
assert(cc != NULL);
dfc = cc->cctx_msg;
assert(dfc != NULL);
/*
** Determine the message ID for logging.
*/
dfc->mctx_jobid = smfi_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 (dkf_findheader(dfc, "From", 0) == NULL ||
dkf_findheader(dfc, "From", 1) != NULL)
ok = FALSE;
/* exactly one Date: */
if (dkf_findheader(dfc, "Date", 0) == NULL ||
dkf_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 = DKF_STATUS_BADFORMAT;
return SMFIS_CONTINUE;
}
}
#endif /* _FFR_REQUIRED_HEADERS */
msgsigned = (dkf_findheader(dfc, DK_SIGNHEADER, 0) != NULL);
/* find the Sender: or From: header */
memset(addr, '\0', sizeof addr);
from = dkf_findheader(dfc, "Sender", 0);
if (from == NULL)
from = dkf_findheader(dfc, "From", 0);
if (from == NULL)
{
if (dolog)
{
syslog(LOG_INFO,
"%s: no From: or Sender: header; accepting",
dfc->mctx_jobid);
}
if (msgsigned)
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
dfc->mctx_status = DKF_STATUS_BADFORMAT;
return SMFIS_CONTINUE;
}
/* extract the sender's domain */
sm_strlcpy(addr, from->hdr_val, sizeof addr);
status = rfc2822_mailbox_split(addr, &p, &dfc->mctx_domain);
if (status != 0 || p == NULL || dfc->mctx_domain == NULL)
{
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 = DKF_STATUS_BADFORMAT;
return SMFIS_CONTINUE;
}
/* assume we're not signing */
dfc->mctx_signalg = DK_SIGN_UNKNOWN;
dfc->mctx_signing = FALSE;
domainok = FALSE;
originok = FALSE;
/* is it a domain we sign for? */
if (!msgsigned && 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)
{
p = strchr(dfc->mctx_domain, '.');
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;
}
}
}
}
}
}
#if _FFR_MULTIPLE_KEYS
/* is it a domain we sign for? (take two) */
if (keyhead != NULL)
{
struct keytable *curkey;
char srchaddr[MAXADDRESS + 1];
snprintf(srchaddr, sizeof srchaddr, "%s@%s",
p, 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);
}
dkf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
}
if (curkey != NULL)
{
dfc->mctx_key = curkey;
domainok = TRUE;
}
}
#endif /* _FFR_MULTIPLE_KEYS */
/* see if it came in on an authorized MSA/MTA connection */
if (mtas != NULL)
{
int n;
char *mtaname;
mtaname = smfi_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)
{
int n;
char *val;
char name[BUFRSZ + 1];
for (n = 0; macros[n] != NULL; n++)
{
snprintf(name, sizeof name, "{%s}", macros[n]);
val = smfi_getsymval(ctx, name);
if (val == NULL)
continue;
if (values[n] == NULL ||
(values[n] != NULL &&
strcasecmp(values[n], val) == 0))
{
originok = TRUE;
break;
}
}
}
/* see if it came from an internal or authenticated source */
if (!originok)
{
char *authtype;
authtype = smfi_getsymval(ctx, "{auth_type}");
if ((authtype == NULL || authtype[0] == '\0') &&
#if POPAUTH
!dkf_checkpopauth(popdb, &cc->cctx_ip) &&
#endif /* POPAUTH */
!dkf_checkhost(internal, cc->cctx_host) &&
!dkf_checkip(internal, &cc->cctx_ip))
{
if (domainok && dolog &&
!dkf_checkhost(exignore, cc->cctx_host) &&
!dkf_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 = DK_SIGN_RSASHA1;
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 & DKF_MODE_SIGNER) == 0) ||
(!dfc->mctx_signing && (mode & DKF_MODE_VERIFIER) == 0))
return SMFIS_ACCEPT;
/* grab an appropriate handle for message processing */
if (!dfc->mctx_signing)
{
dfc->mctx_dk = dk_verify(libdk, dfc->mctx_jobid, NULL,
&status);
}
else
#if _FFR_MULTIPLE_KEYS
if (dfc->mctx_key != NULL)
{
dfc->mctx_dk = dk_sign(libdk, dfc->mctx_jobid, NULL,
(dk_sigkey_t *) dfc->mctx_key->key_data,
dfc->mctx_canonalg, dfc->mctx_signalg,
&status);
}
else
{
dfc->mctx_dk = dk_sign(libdk, dfc->mctx_jobid, NULL,
(dk_sigkey_t *) seckey,
dfc->mctx_canonalg, dfc->mctx_signalg,
&status);
}
#else /* _FFR_MULTIPLE_KEYS */
{
dfc->mctx_dk = dk_sign(libdk, dfc->mctx_jobid, NULL,
(dk_sigkey_t *) seckey,
dfc->mctx_canonalg, dfc->mctx_signalg,
&status);
}
#endif /* _FFR_MULTIPLE_KEYS */
if (dfc->mctx_dk == NULL && status != DK_STAT_OK)
return dkf_libstatus(ctx, "dk_new()", status);
(void) dk_timeout(dfc->mctx_dk, tmo, NULL);
/* run the headers */
memset(hdrbuf, '\0', sizeof hdrbuf);
for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
{
skip = FALSE;
if (omithdrs != NULL && dfc->mctx_signing)
{
int c;
for (c = 0; omithdrs[c] != NULL; c++)
{
if (strcasecmp(hdr->hdr_hdr, omithdrs[c]) == 0)
{
skip = TRUE;
break;
}
}
if (skip)
continue;
}
snprintf(hdrbuf, MAXHEADER, "%s:%s%s%s", hdr->hdr_hdr,
cc->cctx_noleadspc ? "" : " ",
hdr->hdr_val, CRLF);
status = dk_header(dfc->mctx_dk, hdrbuf, strlen(hdrbuf));
if (status != DK_STAT_OK)
return dkf_libstatus(ctx, "dk_header()", status);
}
/* signal end of headers */
status = dk_eoh(dfc->mctx_dk);
switch (status)
{
case DK_STAT_REVOKED:
dfc->mctx_status = DKF_STATUS_REVOKED;
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
return SMFIS_CONTINUE;
case DK_STAT_BADSIG:
dfc->mctx_status = DKF_STATUS_BAD;
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
return SMFIS_CONTINUE;
case DK_STAT_NOSIG:
dfc->mctx_status = DKF_STATUS_NOSIGNATURE;
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
return SMFIS_CONTINUE;
case DK_STAT_NOKEY:
dfc->mctx_status = DKF_STATUS_NOKEY;
dfc->mctx_addheader = TRUE;
dfc->mctx_headeronly = TRUE;
return SMFIS_CONTINUE;
/* XXX -- other codes? */
case DK_STAT_OK:
return SMFIS_CONTINUE;
default:
return dkf_libstatus(ctx, "dk_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;
#ifndef DEBUG
assert(ctx != NULL);
#endif /* !DEBUG */
assert(bodyp != NULL);
if (DK_DEBUG('t'))
syslog(LOG_INFO, "thread %p body", pthread_self());
cc = (connctx) smfi_getpriv(ctx);
assert(cc != NULL);
dfc = cc->cctx_msg;
assert(dfc != NULL);
/*
** No need to do anything if the body was empty.
*/
if (bodylen == 0 || dfc->mctx_headeronly)
return SMFIS_CONTINUE;
status = dk_body(dfc->mctx_dk, bodyp, bodylen);
if (status != DK_STAT_OK)
return dkf_libstatus(ctx, "dk_body()", status);
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)
{
int status = DK_STAT_OK;
int c;
int n;
int w;
int s;
int dkf;
sfsistat ret;
connctx cc;
msgctx dfc;
char *hostname;
#if _FFR_FLUSH_HEADERS
Header hdr;
#endif /* _FFR_FLUSH_HEADERS */
unsigned char sig[MAXSIGNATURE];
unsigned char header[MAXHEADER + 1];
#ifndef DEBUG
assert(ctx != NULL);
#endif /* !DEBUG */
if (DK_DEBUG('t'))
syslog(LOG_INFO, "thread %p eom", pthread_self());
cc = (connctx) smfi_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 = smfi_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 = smfi_getsymval(ctx, "j");
if (hostname == NULL)
hostname = HOSTUNKNOWN;
#if _FFR_FLUSH_HEADERS
if (flushheaders)
{
for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
{
if ((dfc->mctx_signing &&
strcasecmp(hdr->hdr_hdr, DK_SIGNHEADER) == 0) ||
strcasecmp(hdr->hdr_hdr, AUTHRESULTSHDR) == 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);
}
}
}
}
}
#endif /* _FFR_FLUSH_HEADERS */
if (!dfc->mctx_headeronly)
{
/*
** Signal end-of-message to DomainKeys
*/
dkf = 0;
status = dk_eom(dfc->mctx_dk, &dkf);
switch (status)
{
case DK_STAT_OK:
if (!dfc->mctx_signing &&
dkf_findheader(dfc, DK_SIGNHEADER, 0) != NULL)
{
dfc->mctx_addheader = TRUE;
dfc->mctx_status = DKF_STATUS_GOOD;
}
break;
case DK_STAT_BADSIG:
dfc->mctx_addheader = TRUE;
dfc->mctx_status = DKF_STATUS_BAD;
break;
case DK_STAT_NOSIG:
dfc->mctx_addheader = TRUE;
dfc->mctx_status = DKF_STATUS_NOSIGNATURE;
break;
case DK_STAT_NOKEY:
dfc->mctx_addheader = TRUE;
dfc->mctx_status = DKF_STATUS_NOKEY;
break;
default:
return dkf_libstatus(ctx, "dk_eom()", status);
}
#ifdef SMFIF_QUARANTINE
/* quarantine for "bad" results if requested */
if (quarantine &&
(status == DK_STAT_BADSIG ||
(status == DK_STAT_NOSIG && dfc->mctx_addheader)))
{
char *failstatus;
char qreason[BUFRSZ + 1];
failstatus = "bad signature";
if (status == DK_STAT_NOSIG)
failstatus = "no signature";
snprintf(qreason, sizeof qreason, "%s: %s",
progname, failstatus);
if (smfi_quarantine(ctx, qreason) != MI_SUCCESS)
{
if (dolog)
{
syslog(LOG_ERR,
"%s smfi_quarantine() failed",
dfc->mctx_jobid);
}
}
}
#endif /* SMFIF_QUARANTINE */
/* compute and insert the signature, if we're signing */
if (dfc->mctx_signing)
{
int hcnt = 0;
#if _FFR_MULTIPLE_KEYS
char *sel;
sel = selector;
if (dfc->mctx_key != NULL)
sel = dfc->mctx_key->key_selector;
#endif /* _FFR_MULTIPLE_KEYS */
memset(sig, '\0', sizeof sig);
status = dk_getsig(dfc->mctx_dk, sig, sizeof sig);
if (status != DK_STAT_OK)
{
return dkf_libstatus(ctx, "dk_getsig()",
status);
}
memset(dfc->mctx_hlist, '\0', sizeof dfc->mctx_hlist);
if (hdrlist)
{
sm_strlcpy(dfc->mctx_hlist, "h=",
sizeof dfc->mctx_hlist);
status = dk_gethdrs(dfc->mctx_dk, &hcnt,
dfc->mctx_hlist + 2,
sizeof dfc->mctx_hlist - 4);
if (status != DK_STAT_OK)
{
return dkf_libstatus(ctx,
"dk_gethdrs()",
status);
}
if (sm_strlcat(dfc->mctx_hlist, ";\n\t",
sizeof dfc->mctx_hlist) >= sizeof dfc->mctx_hlist)
{
if (dolog)
{
syslog(LOG_ERR,
"%s header list too long",
dfc->mctx_jobid);
}
dkf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
if (hcnt == 0)
{
dfc->mctx_hlist[0] = '\0';
}
else
{
dkf_splithdrs(dfc->mctx_hlist,
sizeof(dfc->mctx_hlist));
}
}
memset(header, '\0', sizeof header);
snprintf(header, sizeof header,
"%sa=%s; s=%s; d=%s; c=%s; q=%s;\n\t%sb=",
cc->cctx_noleadspc ? " " : "",
dkf_signalg(dfc->mctx_signalg),
#if _FFR_MULTIPLE_KEYS
sel == NULL ? DK_DEFAULT_SELECTOR : sel,
#else /* _FFR_MULTIPLE_KEYS */
selector == NULL ? DK_DEFAULT_SELECTOR
: selector,
#endif /* _FFR_MULTIPLE_KEYS */
dfc->mctx_domain,
dkf_canonalg(dfc->mctx_canonalg),
dkf_queryalg(dfc->mctx_queryalg),
dfc->mctx_hlist);
n = strlen(header);
s = strlen(sig);
w = 10;
for (c = 0; c < s && n < sizeof header; c++)
{
header[n] = sig[c];
n++;
w++;
if (w >= HEADERMARGIN && (c < s - 1))
{
sm_strlcat(header, "\n\t",
sizeof header);
n += 2;
w = 8;
}
}
if (smfi_insheader(ctx, 1, DK_SIGNHEADER,
header) == MI_FAILURE)
{
if (dolog)
{
syslog(LOG_ERR,
"%s smfi_insheader() failed",
dfc->mctx_jobid);
}
}
}
}
/* insert DomainKeys status */
if (dfc->mctx_addheader && dfc->mctx_status != DKF_STATUS_UNKNOWN)
{
char *authresult;
char *comment = NULL;
switch (dfc->mctx_status)
{
case DKF_STATUS_GOOD:
authresult = "pass";
break;
case DKF_STATUS_BAD:
case DKF_STATUS_REVOKED:
case DKF_STATUS_NOSIGNATURE:
authresult = "fail";
if (dfc->mctx_status == DKF_STATUS_REVOKED)
comment = "revoked";
else if (dfc->mctx_status == DKF_STATUS_NOSIGNATURE)
comment = "no signature";
break;
case DKF_STATUS_BADFORMAT:
authresult = "permerror";
comment = "bad format";
break;
default:
authresult = "neutral";
break;
}
if (dfc->mctx_dk != NULL)
{
char hdr[MAXHEADER + 1];
char val[MAXADDRESS + 1];
sm_strlcpy(hdr, "unknown", sizeof hdr);
sm_strlcpy(val, "unknown", sizeof val);
(void) dk_getidentity(dfc->mctx_dk, hdr, sizeof hdr,
val, sizeof val);
snprintf(header, sizeof header,
"%s%s %s=%s; domainkeys=%s%s%s%s%s",
cc->cctx_noleadspc ? " " : "",
hostname, hdr, val, authresult,
comment == NULL ? "" : " (",
comment == NULL ? "" : comment,
comment == NULL ? "" : ")",
!(dkf & DK_FLAG_TESTING) ? "" : " (testing)");
}
else
{
snprintf(header, sizeof header,
"%s%s; domainkeys=%s%s%s%s%s",
cc->cctx_noleadspc ? " " : "",
hostname, authresult,
comment == NULL ? "" : " (",
comment == NULL ? "" : comment,
comment == NULL ? "" : ")",
!(dkf & DK_FLAG_TESTING) ? "" : " (testing)");
}
if (smfi_insheader(ctx, 1, AUTHRESULTSHDR,
header) == MI_FAILURE)
{
if (dolog)
{
syslog(LOG_ERR,
"%s smfi_insheader() failed",
dfc->mctx_jobid);
}
}
if (dfc->mctx_status == DKF_STATUS_BAD &&
dfc->mctx_dk != NULL)
dkf_report(dfc, authresult, comment);
}
/*
** Identify the filter, if requested.
*/
if (addxhdr)
{
char xfhdr[MAXHEADER + 1];
memset(xfhdr, '\0', sizeof xfhdr);
snprintf(xfhdr, sizeof xfhdr, "%s%s v%s %s %s",
cc->cctx_noleadspc ? " " : "",
DKF_PRODUCT, DKF_VERSION, hostname,
dfc->mctx_jobid != NULL ? dfc->mctx_jobid
: JOBIDUNKNOWN);
if (smfi_insheader(ctx, 1, XHEADERNAME, xfhdr) != MI_SUCCESS)
{
if (dolog)
syslog(LOG_ERR, "smfi_insheader() failed");
dkf_cleanup(ctx);
return SMFIS_TEMPFAIL;
}
}
/*
** If we got this far, we're ready to complete.
*/
ret = SMFIS_ACCEPT;
/* translate the stored status */
switch (dfc->mctx_status)
{
case DKF_STATUS_GOOD:
break;
case DKF_STATUS_BAD:
ret = dkf_libstatus(ctx, "mlfi_eom()", DK_STAT_BADSIG);
break;
case DKF_STATUS_NOKEY:
ret = dkf_libstatus(ctx, "mlfi_eom()", DK_STAT_NOKEY);
break;
case DKF_STATUS_REVOKED:
ret = SMFIS_TEMPFAIL;
break;
case DKF_STATUS_NOSIGNATURE:
ret = dkf_libstatus(ctx, "mlfi_eom()", DK_STAT_NOSIG);
break;
case DKF_STATUS_BADFORMAT:
case DKF_STATUS_NONPART:
ret = SMFIS_ACCEPT;
break;
case DKF_STATUS_UNKNOWN:
break;
default:
if (status != DK_STAT_OK)
ret = dkf_libstatus(ctx, "mlfi_eom()", status);
break;
}
if ((dkf & DK_FLAG_TESTING) != 0)
ret = SMFIS_ACCEPT;
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 (DK_DEBUG('t'))
syslog(LOG_INFO, "thread %p abort", pthread_self());
dkf_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;
#ifndef DEBUG
assert(ctx != NULL);
#endif /* !DEBUG */
if (DK_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);
}
dkf_cleanup(ctx);
cc = (connctx) smfi_getpriv(ctx);
free(cc);
smfi_setpriv(ctx, NULL);
return SMFIS_CONTINUE;
}
#ifndef DEBUG
/*
** smfilter -- the milter module description
*/
struct smfiDesc smfilter =
{
DKF_PRODUCT, /* filter name */
SMFI_VERSION, /* version code -- do not change */
#ifdef SMFIF_QUARANTINE
(SMFIF_ADDHDRS|SMFIF_CHGHDRS|SMFIF_QUARANTINE), /* flags */
#else /* SMFIF_QUARANTINE */
(SMFIF_ADDHDRS|SMFIF_CHGHDRS), /* flags */
#endif /* SMFIF_QUARANTINE */
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
};
#endif /* !DEBUG */
#ifdef DEBUG
int
smfi_chgheader(void *ctx, char *hdr, int idx, char *val)
{
printf("smfi_chgheader(<ctx>, `%s', `%d', `%s')\n", hdr, idx,
val == NULL ? "(null)" : val);
return MI_SUCCESS;
}
int
smfi_replacebody(void *ctx, char *p, size_t len)
{
printf("smfi_replacebody(<ctx>, `%.20s%s', `%d')\n", p,
strlen(p) > 20 ? "..." : "", len);
return MI_SUCCESS;
}
int
smfi_addheader(void *ctx, char *hdr, char *val)
{
printf("smfi_addheader(<ctx>, `%s', `%s')\n", hdr, val);
return MI_SUCCESS;
}
int
smfi_insheader(void *ctx, int idx, char *hdr, char *val)
{
printf("smfi_insheader(<ctx>, %d, `%s', `%s')\n", idx, hdr, val);
return MI_SUCCESS;
}
void
smfi_setconn(char *file)
{
printf("smfi_setconn(`%s')\n", file);
}
void
smfi_setpriv(void *ctx, void *priv)
{
fakepriv = priv;
}
void *
smfi_getpriv(void *ctx)
{
return fakepriv;
}
void
smfi_setreply(void *ctx, char *sc, char *esc, char *reply)
{
printf("smfi_setreply(<ctx>, `%s', `%s', `%s')\n",
sc, esc, reply);
}
char *
smfi_getsymval(void *ctx, char *sym)
{
char *ret;
size_t l;
connctx cc;
l = strlen(sym) + 6 + 1;
cc = fakepriv;
printf("smfi_getsymval(<ctx>, `%s')\n", sym);
ret = malloc(l);
snprintf(ret, l, "DEBUG-%s", sym);
return ret;
}
/*
** DKF_DEBUG -- debugging code; simulates libmilter calls
**
** Parameters:
** None.
**
** Return value:
** None.
*/
int
dkf_debug(void)
{
bool done;
int status;
size_t len;
time_t now;
char *p;
char *env[2];
char tmphdr[4096];
char data[513];
char block[4096];
struct sockaddr_in sin;
time(&now);
srandom(now);
memset(data, '\0', sizeof data);
memset(tmphdr, '\0', sizeof tmphdr);
memset(&sin, '\0', sizeof sin);
sin.sin_family = AF_INET;
sin.sin_port = time(NULL) % 65536;
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
status = mlfi_connect(NULL, "localhost", (_SOCK_ADDR *) &sin);
printf("mlfi_connect(NULL, `%s', <sockaddr>) returns %s\n",
"localhost", smfis_ret[status]);
for (;;)
{
if (fgets(data, 512, stdin) == NULL)
return 1;
for (p = data; *p != '\0'; p++)
if (*p == '\r' || *p == '\n')
{
*p = '\0';
break;
}
if (strcmp(data, ".") == 0)
break;
env[0] = &data[1];
env[1] = NULL;
if (data[0] == 'F')
{
status = mlfi_envfrom(NULL, env);
printf("mlfi_envfrom(NULL, `%s') returns %s\n", env[0],
smfis_ret[status]);
}
/*
else if (data[0] == 'T')
{
status = mlfi_envrcpt(NULL, env);
printf("mlfi_envrcpt(NULL, `%s') returns %s\n", env[0],
smfis_ret[status]);
}
*/
else
{
return 1;
}
if (status != SMFIS_CONTINUE)
return 0;
}
for (;;)
{
memset(data, '\0', 513);
if (fgets(data, 512, stdin) == NULL)
return 1;
for (p = data; *p != '\0'; p++)
{
if (*p == '\r' || *p == '\n')
{
*p = '\0';
break;
}
}
if (strlen(data) > 0 && isascii(data[0]) && isspace(data[0]))
{
sm_strlcat(tmphdr, "\r\n", sizeof tmphdr);
sm_strlcat(tmphdr, data, sizeof tmphdr);
continue;
}
if (strlen(tmphdr) != 0)
{
char *q;
p = strchr(tmphdr, ':');
*p = '\0';
for (q = p + 1; isspace(*q); q++)
continue;
status = mlfi_header(NULL, tmphdr, q);
printf("mlfi_header(NULL, `%s', `%s') returns %s\n",
tmphdr, q, smfis_ret[status]);
if (status != SMFIS_CONTINUE)
return 0;
memset(tmphdr, '\0', sizeof tmphdr);
}
if (strlen(data) == 0)
break;
sm_strlcat(tmphdr, data, sizeof tmphdr);
}
status = mlfi_eoh(NULL);
printf("mlfi_eoh(NULL) returns %s\n", smfis_ret[status]);
if (status != SMFIS_CONTINUE)
return 0;
done = FALSE;
while (!done)
{
len = fread(block, 1, 4096, stdin);
status = mlfi_body(NULL, block, len);
printf("mlfi_body(NULL, <body>, %d) returns %s\n",
len, smfis_ret[status]);
if (status != SMFIS_CONTINUE)
return 0;
if (len < 4096)
done = TRUE;
}
status = mlfi_eom(NULL);
printf("mlfi_eom(NULL) returns %s\n", smfis_ret[status]);
status = mlfi_close(NULL);
printf("mlfi_close(NULL) returns %s\n", smfis_ret[status]);
return 0;
}
#endif /* DEBUG */
/*
** 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"
"-a peerlist \tfile containing list of hosts to ignore\n"
"-A \tauto-restart\n"
"-b modes \tselect operating modes\n"
"-c canon \tcanonicalization to use when signing\n"
"-C config \tconfiguration info (see man page)\n"
"-d domlist \tdomains to sign\n"
"-D \talso sign subdomains\n"
"-f \tdon't fork-and-exit\n"
#if _FFR_FLUSH_HEADERS
"-F \tflush special headers before signing\n"
#endif /* _FFR_FLUSH_HEADERS */
"-h \tappend identifying header\n"
"-H \tsign with explicit header lists\n"
"-i ilist \tfile containing list of internal (signing) hosts\n"
"-I elist \tfile containing list of external domain clients\n"
#if _FFR_MULTIPLE_KEYS
"-k \tload a key set instead of a single key\n"
#endif /* _FFR_MULTIPLE_KEYS */
"-l \tlog activity to system log\n"
"-m mtalist \tMTA daemon names for which to sign\n"
"-M macrolist\tMTA macros which enable signing\n"
"-o hdrlist \tlist of headers to omit from signing\n"
"-P pidfile \tfile to which to write pid\n"
#if _FFR_REQUIRED_HEADERS
"-r \trequire basic RFC2822 header compliance\n"
#endif /* _FFR_REQUIRED_HEADERS */
"-R \tgenerate verification failure reports\n"
"-s keyfile \tlocation of secret key file\n"
"-S selector \tselector to use when signing\n"
"-u userid \tchange to specified userid\n"
#if POPAUTH
"-U dbfile \tuser POP AUTH database\n"
#endif /* POPAUTH */
"-V \tprint version number and terminate\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 gotp = FALSE;
bool dofork = TRUE;
#if _FFR_MULTIPLE_KEYS
bool multikey = FALSE;
#endif /* _FFR_MULTIPLE_KEYS */
int c;
int status;
#ifndef DEBUG
int n;
#endif /* ! DEBUG */
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 *confstr = NULL;
char *peerfile = NULL;
char *ilist = NULL;
char *omitlist = NULL;
#if POPAUTH
char *popdbfile = NULL;
#endif /* POPAUTH */
char *elist = NULL;
char *canonstr = NULL;
char *macrolist = NULL;
unsigned char *s33krit = NULL;
#ifndef DEBUG
char *end;
char argstr[MAXARGV];
#endif /* ! DEBUG */
/* initialize */
addxhdr = FALSE;
dolog = FALSE;
subdomains = FALSE;
hdrlist = FALSE;
dompats = NULL;
omithdrs = NULL;
#if POPAUTH
popdb = NULL;
#endif /* POPAUTH */
#if _FFR_FLUSH_HEADERS
flushheaders = FALSE;
#endif /* _FFR_FLUSH_HEADERS */
send_reports = FALSE;
#if _FFR_MULTIPLE_KEYS
keyhead = NULL;
keytail = NULL;
#endif /* _FFR_MULTIPLE_KEYS */
no_i_whine = TRUE;
selector = NULL;
seckey = NULL;
libdk = NULL;
domains = NULL;
mtas = NULL;
macros = NULL;
values = NULL;
peerlist = NULL;
internal = NULL;
exignore = NULL;
quarantine = FALSE;
canon = DK_CANON_SIMPLE;
tmo = DEFTIMEOUT;
if (DK_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();
confstr = 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;
#if _FFR_FLUSH_HEADERS
case 'F':
flushheaders = TRUE;
break;
#endif /* _FFR_FLUSH_HEADERS */
case 'h':
addxhdr = TRUE;
break;
case 'H':
hdrlist = 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;
#if _FFR_MULTIPLE_KEYS
case 'k':
multikey = TRUE;
break;
#endif /* _FFR_MULTIPLE_KEYS*/
case 'l':
#ifndef DEBUG
dolog = TRUE;
#endif /* !DEBUG */
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();
(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();
keyfile = optarg;
break;
case 'S':
if (optarg == NULL || *optarg == '\0')
return usage();
selector = optarg;
break;
case 'T':
if (optarg == NULL || *optarg == '\0')
return usage();
tmo = strtoul(optarg, &p, 10);
if (*p != '\0')
{
fprintf(stderr, "%s: invalid value for -%c\n",
progname, c);
return EX_USAGE;
}
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':
printf("%s: %s v%s\n", progname, DKF_PRODUCT,
DKF_VERSION);
dkf_optlist(stdout);
return EX_OK;
default:
return usage();
}
}
if (optind != argc)
return usage();
if (!dkf_parseconfig(confstr))
return EX_USAGE;
if (!gotp)
return usage();
if (be == NULL)
{
mode = DKF_MODE_DEFAULT;
}
else
{
mode = 0;
for (p = be; *p != '\0'; p++)
{
switch (*p)
{
case 's':
mode |= DKF_MODE_SIGNER;
break;
case 'v':
mode |= DKF_MODE_VERIFIER;
break;
default:
fprintf(stderr, "%s: unknown role flag '%c'\n",
progname, *p);
return EX_USAGE;
}
}
}
if (canonstr != NULL)
{
canon = dkf_configlookup(canonstr, dkf_canon);
if (canon == -1)
{
fprintf(stderr, "%s: unknown canonicalization \"%s\"\n",
progname, canonstr);
return EX_USAGE;
}
}
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;
}
}
dkf_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 (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;
}
/* peer list */
if (peerfile != NULL)
{
FILE *f;
f = fopen(peerfile, "r");
if (f == NULL)
{
fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
peerfile, strerror(errno));
return EX_UNAVAILABLE;
}
if (!dkf_load_list(f, &peerlist))
{
fclose(f);
return EX_UNAVAILABLE;
}
fclose(f);
}
/* internal list */
if (ilist != NULL)
{
FILE *f;
f = fopen(ilist, "r");
if (f == NULL)
{
fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
ilist, strerror(errno));
return EX_UNAVAILABLE;
}
if (!dkf_load_list(f, &internal))
{
fclose(f);
return EX_UNAVAILABLE;
}
fclose(f);
}
else
{
Peer newpeer;
newpeer = (struct Peer *) malloc(sizeof(struct Peer));
if (newpeer == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n", progname,
strerror(errno));
return EX_UNAVAILABLE;
}
newpeer->peer_next = NULL;
newpeer->peer_info = LOCALHOST;
internal = newpeer;
}
/* external ignore list */
if (elist != NULL)
{
FILE *f;
f = fopen(elist, "r");
if (f == NULL)
{
fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
elist, strerror(errno));
return EX_UNAVAILABLE;
}
if (!dkf_load_list(f, &exignore))
{
fclose(f);
return EX_UNAVAILABLE;
}
fclose(f);
}
/* activate logging */
if (dolog)
{
#ifdef LOG_MAIL
openlog(progname, LOG_PID, LOG_MAIL);
#else /* LOG_MAIL */
openlog(progname, LOG_PID);
#endif /* LOG_MAIL */
}
dkf_setmaxfd();
/* change user if appropriate */
if (become != NULL)
{
struct passwd *pw;
pw = getpwnam(become);
if (pw == NULL)
{
uid_t uid;
uid = atoi(become);
if (uid != 0 && uid != LONG_MIN && uid != LONG_MAX)
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;
}
}
(void) endpwent();
if (setgid(pw->pw_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;
}
}
/* load the secret key, if one was specified */
#if _FFR_MULTIPLE_KEYS
if (keyfile != NULL && multikey)
{
status = dkf_loadkeys(keyfile);
if (status != 0)
return EX_UNAVAILABLE;
}
else
#endif /* _FFR_MULTIPLE_KEYS */
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));
dkf_zapkey();
return EX_OSERR;
case 0:
dkf_stdio();
break;
default:
dkf_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 = dkf_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)
{
pid = fork();
switch (pid)
{
case -1:
if (dolog)
{
syslog(LOG_ERR, "fork(): %s",
strerror(errno));
}
dkf_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)
{
dkf_killchild(pid,
diesig);
dkf_zapkey();
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;
}
}
}
#ifndef DEBUG
/* 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);
dkf_zapkey();
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);
dkf_zapkey();
return EX_UNAVAILABLE;
}
#endif /* !DEBUG */
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));
dkf_zapkey();
return EX_OSERR;
case 0:
dkf_stdio();
break;
default:
dkf_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 DomainKeys package */
libdk = dk_init(NULL, NULL);
if (libdk == NULL)
{
if (dolog)
syslog(LOG_ERR, "can't initialize DomainKeys library");
dkf_zapkey();
return EX_UNAVAILABLE;
}
if (hdrlist)
{
int opts;
opts = DK_OPTS_HDRLIST;
(void) dk_options(libdk, DK_OP_SETOPT, &opts);
}
if (send_reports)
{
int opts;
(void) dk_options(libdk, DK_OP_GETOPT, &opts);
opts |= DK_OPTS_TMPFILES;
(void) dk_options(libdk, DK_OP_SETOPT, &opts);
}
pthread_mutex_init(&popen_lock, NULL);
#ifdef DEBUG
return dkf_debug();
#else /* DEBUG */
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 = dkf_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(dbfile, 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\n",
progname, db_strerror(errno));
syslog(LOG_ERR, "can't open database %s",
popdbfile);
dkf_zapkey();
return EX_UNAVAILABLE;
}
}
#endif /* POPAUTH */
if (dolog)
{
syslog(LOG_INFO, "%s v%s starting (%s)", DKF_PRODUCT,
DKF_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",
DKF_PRODUCT, DKF_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 */
dkf_zapkey();
return status;
#endif /* DEBUG */
}
syntax highlighted by Code2HTML, v. 0.9.1