/* ** 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 #include #include #include #if DEBUG # include # include #endif /* DEBUG */ #include #if SOLARIS # if SOLARIS > 20700 # include # else /* SOLARIS > 20700 */ # include # endif /* SOLARIS > 20700 */ #endif /* SOLARIS */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if SOLARIS # define _PATH_DEVNULL "/dev/null" # ifndef _PATH_SENDMAIL # define _PATH_SENDMAIL "/usr/sbin/sendmail" # endif /* ! _PATH_SENDMAIL */ #else /* SOLARIS */ # include #endif /* SOLARIS */ /* openssl includes */ #include #include #include /* sendmail includes */ #include #include /* libmilter includes */ #ifndef DEBUG #include "libmilter/mfapi.h" #endif /* !DEBUG */ /* libdk includes */ #include #if POPAUTH /* libdb includes */ # include #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(, `%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(, `%.20s%s', `%d')\n", p, strlen(p) > 20 ? "..." : "", len); return MI_SUCCESS; } int smfi_addheader(void *ctx, char *hdr, char *val) { printf("smfi_addheader(, `%s', `%s')\n", hdr, val); return MI_SUCCESS; } int smfi_insheader(void *ctx, int idx, char *hdr, char *val) { printf("smfi_insheader(, %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(, `%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(, `%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', ) 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, , %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 */ }