/* $Id: milter-spamd.c,v 1.13 2006/10/05 04:27:35 ca Exp $ */ /* * Copyright (c) 2004 Daniel Hartmeier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ /* * Modified for MeTA1 pmilter */ static const char rcsid[] = "$Id: milter-spamd.c,v 1.13 2006/10/05 04:27:35 ca Exp $"; #include "sm/generic.h" SM_RCSID("@(#)$Id: milter-spamd.c,v 1.13 2006/10/05 04:27:35 ca Exp $") #include "sm/error.h" #include "sm/assert.h" #include "sm/types.h" #include "sm/ctype.h" #include "sm/sysexits.h" #include "sm/fcntl.h" #include "sm/io.h" #include "sm/regex.h" #include "sm/signal.h" #include "sm/syslog.h" #include "sm/smreplycodes.h" #include "sm/pmilter.h" #include "sm/pmfdef.h" #include "sm/pmfapi.h" #if MTA_USE_PMILTER #include #define MI_SUCCESS SM_PMI_SUCCESS static const char *spamd_user = ""; static const char *ignore_connect = ""; static int debug = 0; static int timeout = 30; static regex_t re_ignore_connect; /* when to add Spam: header: */ #define ADDHDR_NEVER 0 /* never */ #define ADDHDR_ONLY 1 /* only, don't reject */ #define ADDHDR_GT0 2 /* if score > 0 */ #define ADDHDR_ALWAYS 3 /* always */ static int AddHdr = ADDHDR_NEVER; struct context { char ctx_host[128]; char ctx_addr[64]; char ctx_helo[128]; #if 0 char hdr_from[128]; char hdr_to[128]; char hdr_subject[128]; #endif char *ctx_ta_id; int ctx_fd; int ctx_state; int ctx_flags; int ctx_spam; double ctx_score, ctx_threshold; char ctx_symbols[128]; }; typedef struct context context_T, *context_P; #define CTX_FL_NONE 0x0000 #define CTX_FL_FD_OPEN 0x0001 /* fd is ok to use */ #define CTX_FL_FD_FAIL 0x0002 /* don't try to open file */ #define CTX_FL_WR_FAIL 0x0004 /* write failed before */ #define CTX_SET_FLAG(context, fl) (context)->ctx_flags |= (fl) #define CTX_CLR_FLAG(context, fl) (context)->ctx_flags &= ~(fl) #define CTX_IS_FLAG(context, fl) (((context)->ctx_flags & (fl)) != 0) static void usage(const char *); static void msg(int, context_P, const char *, ...); #define USER "_milter-spamd" #define OCONN "/var/spool/milter-spamd/sock" #define SPAMD_ADDR "127.0.0.1" #define SPAMD_PORT 783 static short spamd_port = SPAMD_PORT; #if SM_STAT_st_mtimespec #define ST_MTIME st_mtimespec #else #define ST_MTIME st_mtime #endif static void msg(int priority, context_P context, const char *fmt, ...) { va_list ap; char msg[8192]; va_start(ap, fmt); if (context != NULL) snprintf(msg, sizeof(msg), "%s: ", context->ctx_addr); else msg[0] = 0; vsnprintf(msg + strlen(msg), sizeof(msg) - strlen(msg), fmt, ap); if (debug) printf("syslog: %s\n", msg); else syslog(priority, "%s", msg); va_end(ap); } static int get_spamd_fd(context_P context) { int fd; struct sockaddr_in sa; if (CTX_IS_FLAG(context, CTX_FL_FD_OPEN) || CTX_IS_FLAG(context, CTX_FL_FD_FAIL)) return (-1); if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { msg(LOG_ERR, context, "get_spamd_fd: socket: %s", strerror(errno)); return (-1); } memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr(SPAMD_ADDR); sa.sin_port = htons(spamd_port); if (connect(fd, (struct sockaddr *)&sa, sizeof(sa))) { msg(LOG_ERR, context, "get_spamd_fd: connect: %s", strerror(errno)); close(fd); CTX_SET_FLAG(context, CTX_FL_FD_FAIL); return (-1); } CTX_SET_FLAG(context, CTX_FL_FD_OPEN); return (fd); } static sm_ret_T cb_negotiate(pmss_ctx_P pmss_ctx, uint32_t srv_cap, uint32_t srv_fct, uint32_t srv_feat, uint32_t srv_misc, uint32_t *pm_cap, uint32_t *pm_fct, uint32_t *pm_feat, uint32_t *pm_misc) { sm_ret_T ret; *pm_cap = SM_SCAP_PM_ALL; *pm_fct = 0; *pm_feat = 0; *pm_misc = 0; ret = sm_pmfi_setmaclist(pmss_ctx, PM_SMST_CONNECT, PMM_SRVHOSTNAME, PMM_END); if (sm_is_err(ret)) fprintf(stderr, "sev=ERROR, where=cb_negotiate, sm_pmfi_setmaclist=%#x, macro=hostname\n", ret); ret = sm_pmfi_setmaclist(pmss_ctx, PM_SMST_MAIL, PMM_MAIL_TAID, PMM_END); if (sm_is_err(ret)) fprintf(stderr, "sev=ERROR, where=cb_negotiate, sm_pmfi_setmaclist=%#x, macro=taid\n", ret); return SM_SUCCESS; } static sfsistat_T cb_connect(pmse_ctx_P pmse_ctx, const char *name, sm_sockaddr_T *sa) { context_P context; char host[64]; context = calloc(1, sizeof(*context)); if (context == NULL) { msg(LOG_ERR, NULL, "cb_connect: calloc: %s", strerror(errno)); return (SMFIS_ACCEPT); } context->ctx_fd = -1; if (sm_pmfi_set_ctx_se(pmse_ctx, context) != MI_SUCCESS) { free(context); msg(LOG_ERR, NULL, "cb_connect: smfi_setpriv"); return (SMFIS_ACCEPT); } strlcpy(host, "unknown", sizeof(host)); switch (sa->sa.sa_family) { case AF_INET: { struct sockaddr_in *sin = (struct sockaddr_in *)sa; if (inet_ntop(AF_INET, &sin->sin_addr.s_addr, host, sizeof(host)) == NULL) msg(LOG_ERR, NULL, "cb_connect: inet_ntop: %s", strerror(errno)); break; } #if HAVE_INET6 case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; if (inet_ntop(AF_INET6, &sin6->sin6_addr, host, sizeof(host)) == NULL) msg(LOG_ERR, NULL, "cb_connect: inet_ntop: %s", strerror(errno)); break; } #endif } strlcpy(context->ctx_host, name, sizeof(context->ctx_host)); strlcpy(context->ctx_addr, host, sizeof(context->ctx_addr)); msg(LOG_DEBUG, context, "cb_connect('%s', '%s')", name, host); if (ignore_connect[0] && ( !regexec(&re_ignore_connect, name, 0, NULL, 0) || !regexec(&re_ignore_connect, host, 0, NULL, 0))) { msg(LOG_DEBUG, context, "cb_connect: matches host ignore RE"); return (SMFIS_ACCEPT); } return (SMFIS_CONTINUE); } static sfsistat_T cb_helo(pmse_ctx_P pmse_ctx, const char *arg, bool ehlo) { context_P context; if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_helo: smfi_getpriv"); return (SMFIS_ACCEPT); } strlcpy(context->ctx_helo, arg, sizeof(context->ctx_helo)); msg(LOG_DEBUG, context, "cb_helo('%s')", arg); return (SMFIS_CONTINUE); } #if MILTER_SPAMD_DEBUG static sfsistat_T cb_mail(pmse_ctx_P pmse_ctx, const char *mail, char **argv) { context_P context; if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_envfrom: smfi_getpriv"); return (SMFIS_ACCEPT); } if (mail != NULL) msg(LOG_DEBUG, context, "cb_envfrom('%s')", mail); return (SMFIS_CONTINUE); } static sfsistat_T cb_rcpt(pmse_ctx_P pmse_ctx, const char *rcpt, char **argv) { context_P context; if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_envrcpt: smfi_getpriv"); return (SMFIS_ACCEPT); } if (rcpt != NULL) msg(LOG_DEBUG, context, "cb_envrcpt('%s')", rcpt); return (SMFIS_CONTINUE); } #endif /* MILTER_SPAMD_DEBUG */ static int writefd(context_P context, unsigned char *chunk, size_t len) { extern sm_ret_T sm_write_wait(int _fd, int _timeout); if (!CTX_IS_FLAG(context, CTX_FL_FD_OPEN) || CTX_IS_FLAG(context, CTX_FL_WR_FAIL)) return -1; if (chunk == NULL || len <= 0) return 0; while (len > 0) { sm_ret_T r; ssize_t written; size_t wr; r = sm_write_wait(context->ctx_fd, timeout); if (r != 0) return r; written = write(context->ctx_fd, chunk, len); if (written == -1) { sm_io_fprintf(smioerr, "sev=ERROR, where=msg, write=%d\n", (int) written); CTX_SET_FLAG(context, CTX_FL_WR_FAIL); close(context->ctx_fd); context->ctx_fd = -1; CTX_CLR_FLAG(context, CTX_FL_FD_OPEN); return -1; } wr = (size_t) written; assert(len >= wr); len -= wr; chunk += wr; } return 0; } static void fdprintf(context_P context, const char *fmt, ...) { va_list ap; char s[2048]; va_start(ap, fmt); vsnprintf(s, sizeof(s), fmt, ap); va_end(ap); (void) writefd(context, (unsigned char *) s, strlen(s)); } static sm_ret_T cb_msg(pmse_ctx_P pmse_ctx, unsigned char *chunk, size_t size) { context_P context; if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_msg: smfi_getpriv"); return (SMFIS_ACCEPT); } if (CTX_IS_FLAG(context, CTX_FL_FD_FAIL)) return -1; /* first call? */ if (!CTX_IS_FLAG(context, CTX_FL_FD_OPEN) && !CTX_IS_FLAG(context, CTX_FL_WR_FAIL) && context->ctx_fd < 0) { char *sendmail_name; char *sendmail_queue; char *sendmail_date; sm_pmfi_getmac(pmse_ctx, PMM_SRVHOSTNAME, &sendmail_name); sm_pmfi_getmac(pmse_ctx, PMM_MAIL_TAID, &sendmail_queue); context->ctx_ta_id = sendmail_queue; /* FIX: $b */ sendmail_date = NULL; if ((context->ctx_fd = get_spamd_fd(context)) < 0) return (SMFIS_ACCEPT); fdprintf(context, "SYMBOLS SPAMC/1.2\r\n"); if (spamd_user[0]) fdprintf(context, "User: %s\r\n", spamd_user); fdprintf(context, "\r\n"); /* send fake Received: header */ fdprintf(context, "Received: from %s (%s [%s])", context->ctx_helo, context->ctx_host, context->ctx_addr); if (sendmail_name != NULL && sendmail_name[0]) { fdprintf(context, "\r\n\tby %s (milter-spamd)", sendmail_name); if (sendmail_queue != NULL && sendmail_queue[0]) fdprintf(context, " id %s", sendmail_queue); } if (sendmail_date != NULL && sendmail_date[0]) fdprintf(context, "; %s", sendmail_date); else { char d[128]; time_t t = time(NULL); if (strftime(d, sizeof(d), "%a, %e %b %Y %H:%M:%S %z", localtime(&t))) fdprintf(context, "; %s", d); } fdprintf(context, "\r\n"); } #if 0 fdprintf(context, "%s: %s\r\n", name, value); if (!strcasecmp(name, "From")) strlcpy(context->hdr_from, value, sizeof(context->hdr_from)); else if (!strcasecmp(name, "To")) strlcpy(context->hdr_to, value, sizeof(context->hdr_to)); else if (!strcasecmp(name, "Subject")) strlcpy(context->hdr_subject, value, sizeof(context->hdr_subject)); #endif if (CTX_IS_FLAG(context, CTX_FL_FD_OPEN) && !CTX_IS_FLAG(context, CTX_FL_WR_FAIL) && context->ctx_fd >= 0) writefd(context, chunk, size); return (SMFIS_CONTINUE); } static void spamd_reply(const char *line, context_P context, sfsistat_T *action) { const char *p; switch (context->ctx_state) { case 0: if (strncmp(line, "SPAMD/", 6)) { msg(LOG_ERR, context, "spamd_reply: first reply " "not SPAMD version: %s", line); *action = SMFIS_ACCEPT; break; } p = line + 6; while (*p && *p != ' ') ++p; while (*p == ' ') ++p; if (strncmp(p, "0 EX_OK", 7)) { msg(LOG_ERR, context, "spamd_reply: first reply " "not 0 EX_OK: %s", line); *action = SMFIS_ACCEPT; break; } context->ctx_state = 1; break; case 1: if (!strncmp(line, "Spam: ", 6)) { char decision[16]; double score, threshold; if (sscanf(line + 6, "%15s ; %lf / %lf", decision, &score, &threshold) != 3) { msg(LOG_ERR, context, "spamd_reply: malformed " "decision reply: %s", line); *action = SMFIS_ACCEPT; break; } context->ctx_spam = !strcmp(decision, "True"); context->ctx_score = score; context->ctx_threshold = threshold; context->ctx_state = 2; } break; case 2: if (!line[0]) context->ctx_state = 3; break; case 3: strlcat(context->ctx_symbols, line, sizeof(context->ctx_symbols)); break; default: msg(LOG_ERR, context, "spamd_reply: invalid context->ctx_state"); *action = SMFIS_ACCEPT; } } static sfsistat_T cb_eom(pmse_ctx_P pmse_ctx) { context_P context; sfsistat_T action = SMFIS_CONTINUE; char buf[2048]; int pos = 0, retry = 0; if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_eom: smfi_getpriv"); return (SMFIS_ACCEPT); } msg(LOG_DEBUG, context, "cb_eom()"); if (context->ctx_fd < 0) goto done; context->ctx_symbols[0] = 0; /* no more writing data to spamd, want to read result now */ if (shutdown(context->ctx_fd, SHUT_WR)) { msg(LOG_ERR, context, "cb_eom: shutdown: %s", strerror(errno)); goto done; } if (fcntl(context->ctx_fd, F_SETFL, fcntl(context->ctx_fd, F_GETFL) | O_NONBLOCK)) { msg(LOG_ERR, context, "cb_eom: fcntl: %s", strerror(errno)); goto done; } /* try at most 6 times (10 seconds timeout each) */ while (action == SMFIS_CONTINUE && retry < 6) { fd_set fds; struct timeval tv; int r, i; char b[8192]; FD_ZERO(&fds); FD_SET(context->ctx_fd, &fds); tv.tv_sec = 10; tv.tv_usec = 0; r = select(context->ctx_fd + 1, &fds, NULL, NULL, &tv); if (r < 0) { if (errno != EINTR) { msg(LOG_ERR, context, "cb_eom: select: %s", strerror(errno)); break; } continue; } else if (r == 0 || !FD_ISSET(context->ctx_fd, &fds)) { retry++; msg(LOG_DEBUG, context, "cb_eom: waiting for " "spamd reply (retry %d)", retry); continue; } r = read(context->ctx_fd, b, sizeof(b)); if (r < 0) { if (errno != EINTR) { msg(LOG_ERR, context, "cb_eom: read: %s", strerror(errno)); break; } continue; } else if (r == 0) /* connection closed by spamd */ break; for (i = 0; i < r; ++i) if (b[i] == '\n' || pos == sizeof(buf) - 1) { if (pos > 0 && buf[pos - 1] == '\r') buf[pos - 1] = 0; else buf[pos] = 0; /* sets action when done */ spamd_reply(buf, context, &action); pos = 0; } else buf[pos++] = b[i]; } if (retry == 6) msg(LOG_ERR, context, "cb_eom: spamd connection timed out"); done: if (context->ctx_fd >= 0) { close(context->ctx_fd); context->ctx_fd = -1; } /* either way, we don't want to continue */ if (action == SMFIS_CONTINUE) { action = (context->ctx_spam && ADDHDR_ONLY != AddHdr) ? SMFIS_REJECT : SMFIS_ACCEPT; } #if 0 msg(action == SMFIS_REJECT ? LOG_NOTICE : LOG_INFO, context, "%s (%s %.1f/%.1f%s%s), From: %s, To: %s, Subject: %s", (action == SMFIS_REJECT ? "REJECT" : "ACCEPT"), (context->ctx_spam ? "SPAM" : "ham"), context->ctx_score, context->ctx_threshold, (context->ctx_symbols[0] ? " " : ""), context->ctx_symbols, context->hdr_from, context->hdr_to, context->hdr_subject); #else msg(action == SMFIS_REJECT ? LOG_NOTICE : LOG_INFO, context, "%s (%s %.1f/%.1f%s%s), ta_id=%s", (action == SMFIS_REJECT ? "REJECT" : "ACCEPT"), (context->ctx_spam ? "SPAM" : "ham"), context->ctx_score, context->ctx_threshold, (context->ctx_symbols[0] ? " " : ""), context->ctx_symbols, context->ctx_ta_id); #endif if (action == SMFIS_REJECT) { char m[64]; snprintf(m, sizeof(m), "554 5.7.1 Spam (score %.1f)\r\n", context->ctx_score); if (sm_pmfi_setreply(pmse_ctx, m) != MI_SUCCESS) msg(LOG_ERR, context, "sm_pmfi_setreply"); } if (ADDHDR_ALWAYS == AddHdr || ADDHDR_ONLY == AddHdr || (ADDHDR_GT0 == AddHdr && context->ctx_score > 0)) { char m[64]; snprintf(m, sizeof(m), "X-Spam: score %.1f\r\n", context->ctx_score); if (sm_pmfi_hdr_mod(pmse_ctx, SM_HDRMOD_T_APPEND, 0, (const uchar *) m) != MI_SUCCESS) msg(LOG_ERR, context, "sm_pmfi_hdrmod"); } #if 0 context->pos = context->hdr_from[0] = context->hdr_to[0] = context->hdr_subject[0] = #endif context->ctx_state = context->ctx_spam = context->ctx_flags = context->ctx_symbols[0] = 0; context->ctx_score = context->ctx_threshold = 0.0; return (action); } static sfsistat_T cb_abort(pmse_ctx_P pmse_ctx) { context_P context; if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_abort: smfi_getpriv"); return (SMFIS_ACCEPT); } msg(LOG_DEBUG, context, "cb_abort()"); if (context->ctx_fd >= 0) { close(context->ctx_fd); context->ctx_fd = -1; } #if 0 context->pos = context->hdr_from[0] = context->hdr_to[0] = context->hdr_subject[0] = #endif context->ctx_state = context->ctx_spam = context->ctx_flags = context->ctx_symbols[0] = 0; context->ctx_score = context->ctx_threshold = 0.0; return (SMFIS_CONTINUE); } static sfsistat_T cb_close(pmse_ctx_P pmse_ctx) { context_P context; context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx); msg(LOG_DEBUG, context, "cb_close()"); if (context != NULL) { sm_pmfi_set_ctx_se(pmse_ctx, NULL); if (context->ctx_fd >= 0) { close(context->ctx_fd); context->ctx_fd = -1; } free(context); } return (SMFIS_CONTINUE); } static pmilter_T pmilter = { "milter-spamd", /* filter name */ LPMILTER_VERSION, SM_SCAP_PM_CNNCT|SM_SCAP_PM_EHLO|SM_SCAP_PM_DATA|SM_SCAP_PM_MSG, 0, 0, 0, cb_negotiate, cb_connect, cb_helo, #if MILTER_SPAMD_DEBUG cb_mail, cb_rcpt, #else NULL, NULL, #endif NULL, cb_msg, cb_eom, cb_abort, cb_close, NULL, /* cb_unknown, */ NULL /* cb_signal */ }; static void usage(const char *argv0) { fprintf(stderr, "usage: %s [-d] [-i RE] [-U spamd_user] " "[-p spamd_port] [-p pipe]\n", argv0); exit(1); } int main(int argc, char **argv) { int ch; const char *oconn = OCONN; sfsistat_T ret = SM_PMI_FAILURE; const char *ofile = NULL; pmg_ctx_P pmg_ctx; uint32_t major, minor, patchlevel; char *prog; prog = argv[0]; if (getuid() == 0 || geteuid() == 0) { fprintf(stderr, "%s: ERROR: do not run this as super-user!\n", prog); exit(EX_USAGE); } tzset(); openlog("milter-spamd", LOG_PID | LOG_NDELAY, LOG_DAEMON); while ((ch = getopt(argc, argv, "di:P:p:t:U:")) != -1) { switch (ch) { case 'd': debug = 1; break; case 'i': { int r; ignore_connect = optarg; r = regcomp(&re_ignore_connect, ignore_connect, REG_EXTENDED | REG_ICASE); if (r) { char e[8192]; regerror(r, &re_ignore_connect, e, sizeof(e)); fprintf(stderr, "regcomp: %s: %s\n", ignore_connect, e); usage(prog); } break; } case 'P': spamd_port = (short) strtol(optarg, NULL, 0); break; case 'p': oconn = optarg; break; case 't': timeout = strtol(optarg, NULL, 0); break; case 'U': spamd_user = optarg; break; default: usage(prog); } } if (argc != optind) { fprintf(stderr, "unknown command line argument: %s ...", argv[optind]); usage(prog); } if (!strncmp(oconn, "unix:", 5)) ofile = oconn + 5; else if (!strncmp(oconn, "local:", 6)) ofile = oconn + 6; else ofile = oconn; if (ofile != NULL) (void) unlink(ofile); pmg_ctx = NULL; ret = sm_pmfi_init(&pmg_ctx); if (sm_is_err(ret)) { fprintf(stderr, "sev=ERROR, sm_pmfi_init=%#x\n", ret); goto done; } ret = sm_pmfi_version(pmg_ctx, &major, &minor, &patchlevel); if (sm_is_err(ret)) { fprintf(stderr, "sev=ERROR, sm_pmfi_version=%#x\n", ret); goto done; } if (major != LPMILTER_VERSION_MAJOR) { fprintf(stderr, "sev=ERROR, status=version_mismatch, compile_time=%d, run_time=%d\n" , LPMILTER_VERSION_MAJOR, major); goto done; } ret = sm_pmfi_setconn(pmg_ctx, (char *)ofile); if (sm_is_err(ret)) { fprintf(stderr, "sev=ERROR, sm_pmfi_setconn=%#x\n", ret); goto done; } umask(0177); signal(SIGPIPE, SIG_IGN); msg(LOG_INFO, NULL, "started: %s", rcsid); ret = sm_pmfi_start(pmg_ctx, &pmilter); if (sm_is_err(ret)) { fprintf(stderr, "sev=ERROR, sm_pmfi_start=%#x\n", ret); goto done; } if (ret != MI_SUCCESS) msg(LOG_ERR, NULL, "smfi_main: terminating due to error"); else msg(LOG_INFO, NULL, "smfi_main: terminating without error"); done: return ret; } #else /* MTA_USE_PMILTER */ int main(int argc, char *argv[]) { return 0; } #endif /* MTA_USE_PMILTER */