/* $Id: milter-regex.c,v 1.24 2007/06/10 16:49:55 ca Exp $ */ /* * Copyright (c) 2003-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. * */ static const char rcsid[] = "$Id: milter-regex.c,v 1.24 2007/06/10 16:49:55 ca Exp $"; #include "sm/generic.h" SM_RCSID("@(#)$Id: milter-regex.c,v 1.24 2007/06/10 16:49:55 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/log.h" #include "sm/regex.h" #include "sm/signal.h" #include "sm/sm_extracthdr.h" #include "sm/smreplycodes.h" #include "sm/pmilter.h" #include "sm/pmfdef.h" #include "sm/pmfapi.h" #if MTA_USE_PMILTER #include #include "milter-regex-eval.h" extern int parse_ruleset(const char *, struct ruleset **, char *, size_t); static const char *rule_file_name = "/etc/meta1/milter-regex.conf"; static int debug = 0; static int Xhdr_perm = 0; struct context { struct ruleset *rs; int *res; char buf[2048]; /* longer body lines are wrapped */ unsigned pos; /* write position within buf */ char host_name[128]; char host_addr[64]; char hdr_from[128]; char hdr_to[128]; char hdr_subject[128]; char ta_id[20]; char *quarantine; uint32_t pmexse_flags; sm_xhdr_ctx_P pmexse_xhdr_ctx; }; typedef struct context context_T, *context_P, *pmexse_ctx_P; #define PMSE_FL_NONE 0x0000 #define PMSE_FL_MSG_INIT 0x0001 #define PMSE_FL_MSG_HDR 0x0002 #define PMSE_FL_MSG_EOH 0x0004 #define PMSE_FL_MSG_BODY 0x0008 #define PMSE_SET_FLAG(pmexse_ctx, fl) (pmexse_ctx)->pmexse_flags |= (fl) #define PMSE_CLR_FLAG(pmexse_ctx, fl) (pmexse_ctx)->pmexse_flags &= ~(fl) #define PMSE_IS_FLAG(pmexse_ctx, fl) (((pmexse_ctx)->pmexse_flags & (fl)) != 0) #define sfsistat sfsistat_T #define SMFICTX pmse_ctx_T #define MI_SUCCESS SM_PMI_SUCCESS #define MI_FAILURE SM_PMI_FAILURE #define _SOCK_ADDR sm_sockaddr_T #define smfi_getpriv sm_pmfi_get_ctx_se #define smfi_setpriv sm_pmfi_set_ctx_se static pmg_ctx_P pmg_ctx; #define smfi_stop() sm_pmfi_stop(pmg_ctx) void die(const char *); static sfsistat setreply(SMFICTX *, struct context *, const struct action *); static struct ruleset *get_ruleset(void); static sfsistat cb_connect(SMFICTX *, const char *, _SOCK_ADDR *); static sfsistat cb_helo(SMFICTX *, const char *, bool); static sfsistat cb_envfrom(SMFICTX *, const char *, char **); static sfsistat cb_envrcpt(SMFICTX *, const char *, char **); static sfsistat cb_header(SMFICTX *, char *, char *); static sfsistat cb_eoh(SMFICTX *); static sfsistat cb_body(SMFICTX *, u_char *, size_t); static sfsistat cb_eom(SMFICTX *); static sfsistat cb_close(SMFICTX *); static void usage(const char *); static void msg(int, struct context *, const char *, ...); #define OCONN "/var/spool/milter-regex/sock" #define RCODE_REJECT "554" #define RCODE_TEMPFAIL "451" #define XCODE_REJECT "5.7.1" #define XCODE_TEMPFAIL "4.7.1" #define MAXRS 16 #if SM_STAT_st_mtimespec #define ST_MTIME st_mtimespec #else #define ST_MTIME st_mtime #endif static sfsistat setreply(SMFICTX *ctx, struct context *context, const struct action *action) { int result = SMFIS_CONTINUE; char *what, *rhs; char m[64]; /* ** strlen("454 4.7.1 \r\n")=12, hence restrict len for reply to 64-12=52 ** (2 for paranoia's sake) */ #define MR_TXT_LEN "50" what = rhs = NULL; switch (action->type) { case ACTION_REJECT: what = "REJECT"; rhs = action->msg; result = SMFIS_REJECT; break; case ACTION_REJECT_QUICK: what = "REJECT_QUICK"; rhs = action->msg; result = SMTP_R_SET_QUICK(SMFIS_REJECT); break; case ACTION_TEMPFAIL: what = "TEMPFAIL"; rhs = action->msg; result = SMFIS_TEMPFAIL; break; case ACTION_QUARANTINE: if (context->quarantine != NULL) free(context->quarantine); context->quarantine = strdup(action->msg); break; case ACTION_DISCARD: what = "status"; rhs = "DISCARD"; result = SMFIS_DISCARD; break; case ACTION_ACCEPT: #if 0 what = "status"; rhs = "ACCEPT"; #endif result = SMFIS_ACCEPT; break; } if (what != NULL && msg != NULL) { msg(LOG_NOTICE, context, "ta_id=%s, %s=%s, From=%@s, To=%@s, Subject=%@s", context->ta_id, what, rhs, context->hdr_from, context->hdr_to, context->hdr_subject); } if (action->type == ACTION_REJECT) { snprintf(m, sizeof(m), "554 5.7.1 %." MR_TXT_LEN "s\r\n", (char *)action->msg); if (sm_pmfi_setreply(ctx, m) != MI_SUCCESS) msg(LOG_ERR, context, "smfi_setreply"); } if (action->type == ACTION_TEMPFAIL) { snprintf(m, sizeof(m), "454 4.7.1 %." MR_TXT_LEN "s\r\n", (char *)action->msg); if (sm_pmfi_setreply(ctx, m) != MI_SUCCESS) msg(LOG_ERR, context, "smfi_setreply"); } return (result); } static struct ruleset * get_ruleset(void) { static struct ruleset *rs[MAXRS] = { NULL, NULL, 0, 0 }; static int cur = 0; static time_t last_check = 0; static struct stat sbo; time_t t = time(NULL); int load = 0; if (!last_check) memset(&sbo, 0, sizeof(sbo)); if (t - last_check >= 10) { struct stat sb; last_check = t; memset(&sb, 0, sizeof(sb)); if (stat(rule_file_name, &sb)) msg(LOG_ERR, NULL, "get_ruleset: stat: %s: %s", rule_file_name, strerror(errno)); else if (memcmp(&sb.ST_MTIME, &sbo.ST_MTIME, sizeof(sb.ST_MTIME))) { memcpy(&sbo.ST_MTIME, &sb.ST_MTIME, sizeof(sb.ST_MTIME)); load = 1; } } if (load || rs[cur] == NULL) { int i; char err[8192]; msg(LOG_DEBUG, NULL, "loading new configuration file"); for (i = 0; i < MAXRS; ++i) if (rs[i] != NULL && rs[i]->refcnt == 0) { msg(LOG_DEBUG, NULL, "freeing unused ruleset " "%d/%d", i, MAXRS); free_ruleset(rs[i]); rs[i] = NULL; } for (i = 0; i < MAXRS; ++i) if (rs[i] == NULL) break; if (i == MAXRS) msg(LOG_ERR, NULL, "all rulesets are in use, cannot " "load new one", MAXRS); else if (parse_ruleset(rule_file_name, &rs[i], err, sizeof(err)) || rs[i] == NULL) msg(LOG_ERR, NULL, "parse_ruleset: %s", err); else { msg(LOG_INFO, NULL, "configuration file %s loaded " "successfully", rule_file_name); cur = i; } } return (rs[cur]); } 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)) msg(LOG_ERR, NULL, "sev=ERROR, func=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)) msg(LOG_ERR, NULL, "sev=ERROR, func=cb_negotiate, sm_pmfi_setmaclist=%#x, macro=taid\n", ret); return SM_SUCCESS; } static sfsistat cb_connect(SMFICTX *ctx, const char *name, _SOCK_ADDR *sa) { struct context *context; context = calloc(1, sizeof(*context)); if (context == NULL) { msg(LOG_ERR, NULL, "cb_connect: calloc: %s", strerror(errno)); return (SMFIS_ACCEPT); } context->rs = get_ruleset(); if (context->rs == NULL) { free(context); msg(LOG_ERR, NULL, "cb_connect: get_ruleset"); return (SMFIS_ACCEPT); } context->res = calloc(context->rs->maxidx, sizeof(*context->res)); if (context->res == NULL) { free(context); msg(LOG_ERR, NULL, "cb_connect: calloc: %s", strerror(errno)); return (SMFIS_ACCEPT); } if (smfi_setpriv(ctx, context) != MI_SUCCESS) { free(context->res); free(context); msg(LOG_ERR, NULL, "cb_connect: smfi_setpriv"); return (SMFIS_ACCEPT); } context->rs->refcnt++; context->pmexse_flags = 0; strlcpy(context->host_name, name, sizeof(context->host_name)); strlcpy(context->host_addr, "unknown", sizeof(context->host_addr)); 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, context->host_addr, sizeof(context->host_addr)) == 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, context->host_addr, sizeof(context->host_addr)) == NULL) msg(LOG_ERR, NULL, "cb_connect: inet_ntop: %s", strerror(errno)); break; } #endif } msg(LOG_DEBUG, context, "cb_connect('%s', '%s')", context->host_name, context->host_addr); return (SMFIS_CONTINUE); } static sfsistat cb_helo(SMFICTX *ctx, const char *arg, bool ehlo) { struct context *context; const struct action *action; if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_helo: smfi_getpriv"); return (SMFIS_ACCEPT); } /* multiple HELO imply RSET in sendmail */ /* evaluate connect arguments here, because we can't call */ /* setreply from cb_connect */ eval_clear(context->rs, context->res, COND_CONNECT); if ((action = eval_cond(context->rs, context->res, COND_CONNECT, context->host_name, context->host_addr)) != NULL) return (setreply(ctx, context, action)); if ((action = eval_end(context->rs, context->res, COND_CONNECT)) != NULL) return (setreply(ctx, context, action)); msg(LOG_DEBUG, context, "cb_helo('%@s')", arg); eval_clear(context->rs, context->res, COND_HELO); if ((action = eval_cond(context->rs, context->res, COND_HELO, arg, NULL)) != NULL) return (setreply(ctx, context, action)); if ((action = eval_end(context->rs, context->res, COND_HELO)) != NULL) return (setreply(ctx, context, action)); return (SMFIS_CONTINUE); } static sfsistat cb_envfrom(SMFICTX *ctx, const char *mail, char **args) { struct context *context; const struct action *action; char *ta_id; if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_envfrom: smfi_getpriv"); return (SMFIS_ACCEPT); } context->pmexse_flags = 0; /* multiple MAIL FROM indicate separate messages */ eval_clear(context->rs, context->res, COND_ENVFROM); ta_id = NULL; sm_pmfi_getmac(ctx, PMM_MAIL_TAID, &ta_id); if (ta_id == NULL) ta_id = "unknown_ta_id"; strlcpy(context->ta_id, ta_id, sizeof(context->ta_id)); if (mail != NULL) { msg(LOG_DEBUG, context, "cb_envfrom('%@s')", mail); if ((action = eval_cond(context->rs, context->res, COND_ENVFROM, mail, NULL)) != NULL) return (setreply(ctx, context, action)); } if ((action = eval_end(context->rs, context->res, COND_ENVFROM)) != NULL) return (setreply(ctx, context, action)); return (SMFIS_CONTINUE); } static sfsistat cb_envrcpt(SMFICTX *ctx, const char *rcpt, char **args) { struct context *context; const struct action *action; if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_envrcpt: smfi_getpriv"); return (SMFIS_ACCEPT); } /* multiple RCPT TO: possible */ eval_clear(context->rs, context->res, COND_ENVRCPT); if (rcpt != NULL) { msg(LOG_DEBUG, context, "cb_envrcpt('%@s')", rcpt); if ((action = eval_cond(context->rs, context->res, COND_ENVRCPT, rcpt, NULL)) != NULL) return (setreply(ctx, context, action)); } if ((action = eval_end(context->rs, context->res, COND_ENVRCPT)) != NULL) return (setreply(ctx, context, action)); return (SMFIS_CONTINUE); } static sfsistat cb_header(SMFICTX *ctx, char *name, char *value) { struct context *context; const struct action *action; if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) { msg(LOG_ERR, context, "cb_header: smfi_getpriv"); return (SMFIS_ACCEPT); } msg(LOG_DEBUG, context, "cb_header('%@s', '%@s')", 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)); if ((action = eval_cond(context->rs, context->res, COND_HEADER, name, value)) != NULL) return (setreply(ctx, context, action)); return (SMFIS_CONTINUE); } static sfsistat cb_eoh(SMFICTX *ctx) { struct context *context; const struct action *action; if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_eoh: smfi_getpriv"); return (SMFIS_ACCEPT); } msg(LOG_DEBUG, context, "cb_eoh()"); memset(context->buf, 0, sizeof(context->buf)); context->pos = 0; if ((action = eval_end(context->rs, context->res, COND_HEADER)) != NULL) return (setreply(ctx, context, action)); return (SMFIS_CONTINUE); } static sfsistat cb_body(SMFICTX *ctx, u_char *chunk, size_t size) { struct context *context; if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_body: smfi_getpriv"); return (SMFIS_ACCEPT); } for (; size > 0; size--, chunk++) { context->buf[context->pos] = *chunk; if (context->buf[context->pos] == '\n' || context->pos == sizeof(context->buf) - 1) { const struct action *action; if (context->pos > 0 && context->buf[context->pos - 1] == '\r') context->buf[context->pos - 1] = 0; else context->buf[context->pos] = 0; context->pos = 0; msg(LOG_DEBUG, context, "cb_body('%@s')", context->buf); if ((action = eval_cond(context->rs, context->res, COND_BODY, context->buf, NULL)) != NULL) return (setreply(ctx, context, action)); } else context->pos++; } return (SMFIS_CONTINUE); } static sfsistat cb_eom(SMFICTX *ctx) { struct context *context; const struct action *action; int result = SMFIS_ACCEPT; if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) { msg(LOG_ERR, NULL, "cb_eom: smfi_getpriv"); return (SMFIS_ACCEPT); } msg(LOG_DEBUG, context, "cb_eom()"); if ((action = eval_end(context->rs, context->res, COND_BODY)) != NULL) result = setreply(ctx, context, action); else msg(LOG_DEBUG, context, "ACCEPT, From: %@s, To: %@s, " "Subject: %s", context->hdr_from, context->hdr_to, context->hdr_subject); #ifdef SMFIF_QUARANTINE if (context->quarantine != NULL) { msg(LOG_NOTICE, context, "QUARANTINE: %@s, From: %@s, To: %@s, " "Subject: %@s", action->msg, context->hdr_from, context->hdr_to, context->hdr_subject); if (smfi_quarantine(ctx, context->quarantine) != MI_SUCCESS) msg(LOG_ERR, context, "cb_eom: smfi_quarantine"); } #endif context->pmexse_flags = 0; return (result); } static sfsistat cb_close(SMFICTX *ctx) { struct context *context; context = (struct context *)smfi_getpriv(ctx); msg(LOG_DEBUG, context, "cb_close()"); if (context != NULL) { smfi_setpriv(ctx, NULL); free(context->res); if (context->quarantine != NULL) free(context->quarantine); context->rs->refcnt--; free(context); } return (SMFIS_CONTINUE); } static sfsistat_T pm_hdr(pmse_ctx_P pmse_ctx, pmexse_ctx_P pmexse_ctx, unsigned char *buf, size_t len) { sm_ret_T ret, rv; bool newchunk; sm_xhdr_ctx_P sm_xhdr_ctx; struct context *context; sm_xhdr_ctx = pmexse_ctx->pmexse_xhdr_ctx; if ((context = (struct context *)smfi_getpriv(pmse_ctx)) == NULL) { msg(LOG_ERR, NULL, "pm_hdr: smfi_getpriv"); return (SMFIS_ACCEPT); } sm_xhdr_ctx->sm_xhdr_chunk_off = 0; newchunk = true; do { ret = sm_xhdr(buf, len, newchunk, sm_xhdr_ctx); newchunk = false; if (sm_is_err(ret)) { int plen; bool perm; char m[64]; plen = (len > 256) ? 256 : (int) len; msg(LOG_NOTICE, context, "ta_id=%s, sm_xhdr=%m, buf=\"%.*@s\", len=%lu" ", off=%lu, state=%#x, name=%@S, value=%@S", context->ta_id, ret, plen, buf, (unsigned long) len, (unsigned long) sm_xhdr_ctx->sm_xhdr_chunk_off, sm_xhdr_ctx->sm_xhdr_state, sm_xhdr_ctx->sm_xhdr_name, sm_xhdr_ctx->sm_xhdr_value); perm = sm_is_perm_err(ret) && Xhdr_perm; snprintf(m, sizeof(m), "%d %d.7.1 error parsing headers\r\n", perm ? 554 : 451, perm ? 5 : 4); if (sm_pmfi_setreply(pmse_ctx, m) != MI_SUCCESS) msg(LOG_ERR, context, "smfi_setreply"); return perm ? SMFIS_REJECT : SMFIS_TEMPFAIL; } if (ret == SM_XHDR_GOT1 || ret == SM_XHDR_GOTA || ret == SM_XHDR_GOTL) { char *hdr_name, *hdr_value; hdr_name = (char *) sm_str_getdata( sm_xhdr_ctx->sm_xhdr_name); hdr_value = (char *) sm_str_getdata( sm_xhdr_ctx->sm_xhdr_value); if (hdr_name == NULL || hdr_value == NULL) { msg(LOG_NOTICE, context, "ta_id=%s, hdr_name=%p, hdr_value=%p", context->ta_id, hdr_name, hdr_value); return SMFIS_TEMPFAIL; } rv = cb_header(pmse_ctx, hdr_name, hdr_value); if (rv != SMFIS_CONTINUE) return rv; } } while (!sm_is_err(ret) && ret == SM_XHDR_GOTA); if (ret == SM_XHDR_EOHDR || ret == SM_XHDR_GOTL) { PMSE_CLR_FLAG(pmexse_ctx, PMSE_FL_MSG_HDR); PMSE_SET_FLAG(pmexse_ctx, PMSE_FL_MSG_EOH); /* invoke end-of-header function */ rv = cb_eoh(pmse_ctx); PMSE_CLR_FLAG(pmexse_ctx, PMSE_FL_MSG_EOH); PMSE_SET_FLAG(pmexse_ctx, PMSE_FL_MSG_BODY); if (rv != SMFIS_CONTINUE) return rv; } return SMFIS_CONTINUE; } static sfsistat_T pm_msg(pmse_ctx_P pmse_ctx, unsigned char *buf, size_t len) { sm_ret_T ret; pmexse_ctx_P pmexse_ctx; pmexse_ctx = (pmexse_ctx_P) sm_pmfi_get_ctx_se(pmse_ctx); if (pmexse_ctx == NULL) return SMTP_R_TEMP; ret = SMTP_R_CONT; if (!PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_MSG_INIT)) { ret = sm_xhdr_init(SM_XHDR_FL_SKIP_FIRST_BLANK, &(pmexse_ctx->pmexse_xhdr_ctx)); if (sm_is_err(ret)) { return SMTP_R_TEMP; } PMSE_SET_FLAG(pmexse_ctx, PMSE_FL_MSG_INIT|PMSE_FL_MSG_HDR); } if (PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_MSG_HDR)) { ret = pm_hdr(pmse_ctx, pmexse_ctx, buf, len); if (ret != SMFIS_CONTINUE) return ret; if (PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_MSG_BODY)) { sm_xhdr_ctx_P sm_xhdr_ctx; sm_xhdr_ctx = pmexse_ctx->pmexse_xhdr_ctx; if (sm_xhdr_ctx->sm_xhdr_chunk_off < len) { len -= sm_xhdr_ctx->sm_xhdr_chunk_off; buf += sm_xhdr_ctx->sm_xhdr_chunk_off; ret = sm_xhdr_end(sm_xhdr_ctx); } else len = 0; } } if (PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_MSG_BODY) && len > 0) ret = cb_body(pmse_ctx, buf, len); return ret; } #define smfilter pmilter static pmilter_T pmilter = { "milter-regex", /* 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, cb_envfrom, cb_envrcpt, NULL, pm_msg, cb_eom, NULL, cb_close, NULL, /* cb_unknown, */ NULL /* cb_signal */ }; static void msg(int priority, struct context *context, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (debug) { char msg[8192]; sm_vsnprintf(msg, sizeof(msg), fmt, ap); sm_io_fprintf(smioerr, "syslog: %s\n", msg); } else sm_log_vwrite(NULL, NULL, NULL, priority, 1, fmt, ap); va_end(ap); } static void usage(const char *argv0) { sm_io_fprintf(smioerr, "usage: %s [-ds] [-c config] [-p socket]\n", argv0); exit(EX_USAGE); } void die(const char *reason) { msg(LOG_ERR, NULL, "die: %s", reason); smfi_stop(); sleep(60); /* not reached, smfi_stop() kills thread */ abort(); } int main(int argc, char **argv) { int ch, dbg; sfsistat_T ret = SM_PMI_FAILURE; const char *oconn = OCONN; const char *ofile = NULL; uint32_t major, minor, patchlevel; char *prog; prog = argv[0]; if (getuid() == 0 || geteuid() == 0) { sm_io_fprintf(smioerr, "%s: ERROR: do not run this as super-user!\n", prog); exit(EX_USAGE); } tzset(); (void) sm_log_opensyslog("milter-regex", LOG_PID|LOG_NDELAY, LOG_MAIL); dbg = 0; while ((ch = getopt(argc, argv, "c:D:dp:s")) != -1) { switch (ch) { case 'c': rule_file_name = optarg; break; case 'D': dbg = strtoul(optarg, NULL, 0); break; case 'd': debug = 1; break; case 'p': oconn = optarg; break; case 's': Xhdr_perm = 1; break; default: usage(argv[0]); } } if (argc != optind) { sm_io_fprintf(smioerr, "unknown command line argument: %s ...", argv[optind]); usage(argv[0]); } 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)) { sm_io_fprintf(smioerr, "sev=ERROR, sm_pmfi_init=%m\n", ret); goto done; } ret = sm_pmfi_version(pmg_ctx, &major, &minor, &patchlevel); if (sm_is_err(ret)) { sm_io_fprintf(smioerr, "sev=ERROR, sm_pmfi_version=%m\n", ret); goto done; } if (major != LPMILTER_VERSION_MAJOR) { sm_io_fprintf(smioerr, "sev=ERROR, status=version_mismatch, compile_time=%d, run_time=%d\n" , LPMILTER_VERSION_MAJOR, major); goto done; } if (dbg > 0) ret = sm_pmfi_setdbg(pmg_ctx, dbg); ret = sm_pmfi_setconn(pmg_ctx, (char *)ofile); if (sm_is_err(ret)) { sm_io_fprintf(smioerr, "sev=ERROR, sm_pmfi_setconn=%m\n", ret); goto done; } if (eval_init(ACTION_ACCEPT)) { sm_io_fprintf(smioerr, "eval_init: failed\n"); 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)) { sm_io_fprintf(smioerr, "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 */