sfsistat mlfi_eom(SMFICTX * ctx) { struct mlfiPriv *priv = GETCONTEXT(ctx); if (config.stamp == 0) return mlfi_cleanup(ctx); switch (priv->stamp) { case STAMP_PASSED: smfi_addheader(ctx, "X-DNSBL-MILTER", "Passed"); break; case STAMP_WHITELISTED: smfi_addheader(ctx, "X-DNSBL-MILTER", "Whitelisted"); break; case STAMP_SKIPPED: smfi_addheader(ctx, "X-DNSBL-MILTER", "Skipped"); break; case STAMP_BLACKLISTED: smfi_addheader(ctx, "X-DNSBL-MILTER", "Blacklisted"); break; default: smfi_addheader(ctx, "X-DNSBL-MILTER", "Unknown error"); break; } return mlfi_cleanup(ctx); }
int msgmod_add_header(void *ctx, int argc, var_t *args[]) { char *headerf; char *headerv; if (argc != 2) { log_error("msgmod_add_header: bad argument count"); return -1; } headerf = args[0]->v_data; headerv = args[1]->v_data; if (smfi_addheader(ctx, headerf, headerv) != MI_SUCCESS) { log_error("msgmod_add_header: smfi_addheader failed"); return -1; } log_debug("msgmod_add_header: %s: %s", headerf, headerv); return 0; }
/*}}}*/ } priv_t; static char * xfree (char *s) /*{{{*/ { if (s) free (s); return NULL; }/*}}}*/ static bool_t xcopy (char **buf, const char *str) /*{{{*/ { if (*buf) free (*buf); *buf = str ? strdup (str) : NULL; return (! str) || *buf ? true : false; }/*}}}*/ static void priv_clear (priv_t *p) /*{{{*/ { if (p) { p -> x_agn = 0; p -> from = xfree (p -> from); p -> receiver = charc_free_all (p -> receiver); p -> prev = NULL; p -> info = xfree (p -> info); } }/*}}}*/ static priv_t * priv_free (priv_t *p) /*{{{*/ { if (p) { priv_clear (p); if (p -> lg) log_free (p -> lg); if (p -> cfg) cfg_free (p -> cfg); free (p); } return NULL; }/*}}}*/ static priv_t * priv_alloc (void) /*{{{*/ { priv_t *p; if (p = (priv_t *) malloc (sizeof (priv_t))) if (p -> cfg = cfg_alloc (cfgfile)) { p -> is_local = false; p -> x_agn = 0; p -> from = NULL; p -> receiver = NULL; p -> prev = NULL; p -> info = NULL; if (! (p -> lg = log_alloc (NULL, program, loglevel))) p = priv_free (p); } else { free (p); p = NULL; } return p; }/*}}}*/ static bool_t priv_setfrom (priv_t *p, const char *from) /*{{{*/ { return xcopy (& p -> from, from); }/*}}}*/ static bool_t priv_setto (priv_t *p, const char *to) /*{{{*/ { charc_t *r; if (r = charc_alloc (to)) { if (p -> prev) p -> prev -> next = r; else p -> receiver = r; p -> prev = r; } return r ? true : false; }/*}}}*/ static bool_t priv_addinfo (priv_t *p, const char *info) /*{{{*/ { char *temp; if ((! p -> info) || (! p -> info[0])) return xcopy (& p -> info, info); if (temp = malloc (strlen (p -> info) + strlen (info) + 2)) { sprintf (temp, "%s,%s", p -> info, info); free (p -> info); p -> info = temp; return true; } return false; }/*}}}*/ static bool_t priv_addinfopair (priv_t *p, const char *var, const char *val) /*{{{*/ { bool_t rc; char *scratch, *ptr; if (scratch = malloc (strlen (var) + strlen (val) + 2)) { for (ptr = scratch; *var; *ptr++ = *var++) ; *ptr++ = '='; for (;*val; ++val) *ptr++ = *val == ',' ? '_' : *val; *ptr = '\0'; rc = priv_addinfo (p, scratch); free (scratch); } else rc = false; return rc; }/*}}}*/ static sfsistat handle_connect (SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr) /*{{{*/ { priv_t *p; if (! (p = priv_alloc ())) return SMFIS_TEMPFAIL; if (hostaddr -> sa_family == AF_INET) { struct sockaddr_in *iaddr = (struct sockaddr_in *) hostaddr; if (ntohl (iaddr -> sin_addr.s_addr) == INADDR_LOOPBACK) p -> is_local = true; } # ifdef AF_INET6 else if (hostaddr -> sa_family == AF_INET6) { struct sockaddr_in6 *i6addr = (struct sockaddr_in6 *) hostaddr; static struct in6_addr loopback = IN6ADDR_LOOPBACK_INIT; if (memcmp (& i6addr -> sin6_addr, & loopback, sizeof (i6addr -> sin6_addr)) == 0) p -> is_local = true; } # endif /* AF_INET6 */ smfi_setpriv (ctx, p); return SMFIS_CONTINUE; }/*}}}*/ static sfsistat handle_from (SMFICTX *ctx, char **argv) /*{{{*/ { priv_t *p = (priv_t *) smfi_getpriv (ctx); if (! p) return SMFIS_TEMPFAIL; priv_clear (p); if (! priv_setfrom (p, argv[0])) return SMFIS_TEMPFAIL; priv_addinfopair (p, "from", argv[0]); return SMFIS_CONTINUE; }/*}}}*/ static sfsistat handle_to (SMFICTX *ctx, char **argv) /*{{{*/ { priv_t *p = (priv_t *) smfi_getpriv (ctx); char *chk, *opt; bool_t reject, tempfail; if (! p) return SMFIS_TEMPFAIL; if (p -> is_local) return SMFIS_CONTINUE; if (! (chk = cfg_valid_address (p -> cfg, argv[0]))) { log_out (p -> lg, LV_ERROR, "Unable to setup initial data for `%s'", argv[0]); return SMFIS_TEMPFAIL; } if (opt = strchr (chk, ':')) *opt++ = '\0'; reject = false; tempfail = false; if (! strcmp (chk, ID_REJECT)) reject = true; else if (! strcmp (chk, ID_TEMPFAIL)) tempfail = true; else if ((! strcmp (chk, ID_ACCEPT)) && opt) priv_addinfo (p, opt); priv_addinfopair (p, "to", argv[0]); free (chk); if (reject) { log_out (p -> lg, LV_INFO, "Receiver `%s' is rejected", argv[0]); smfi_setreply (ctx, (char *) "550", (char *) "5.1.1", (char *) "No such user"); return SMFIS_REJECT; } if (tempfail) { log_out (p -> lg, LV_INFO, "Receiver `%s' is temp. disbaled", argv[0]); smfi_setreply (ctx, (char *) "400", (char *) "4.0.0", (char *) "Please try again later"); return SMFIS_TEMPFAIL; } if (! priv_setto (p, argv[0])) return SMFIS_TEMPFAIL; return SMFIS_CONTINUE; }/*}}}*/ static sfsistat handle_header (SMFICTX *ctx, char *field, char *value) /*{{{*/ { priv_t *p = (priv_t *) smfi_getpriv (ctx); if (! p) return SMFIS_TEMPFAIL; if (p -> is_local) return SMFIS_CONTINUE; if (! strcasecmp (field, X_LOOP)) { log_out (p -> lg, LV_WARNING, "Mail from `%s' has already loop marker set, rejected", p -> from); smfi_setreply (ctx, (char *) "500", (char *) "5.4.6", (char *) "Loop detected"); return SMFIS_REJECT; } if (! strcasecmp (field, X_AGN)) p -> x_agn++; return SMFIS_CONTINUE; }/*}}}*/ static sfsistat handle_eom (SMFICTX *ctx) /*{{{*/ { priv_t *p = (priv_t *) smfi_getpriv (ctx); int n; if (! p) return SMFIS_TEMPFAIL; for (n = 0; n < p -> x_agn; ++n) smfi_chgheader (ctx, (char *) X_AGN, 0, NULL); if (! p -> is_local) { if (p -> info) smfi_addheader (ctx, (char *) X_AGN, p -> info); smfi_addheader (ctx, (char *) X_LOOP, (char *) LOOP_SET); } return SMFIS_CONTINUE; }/*}}}*/
static void add_x_header(SMFICTX *ctx, char *st, unsigned int scanned, unsigned int status) { if(addxvirus == 1) { /* Replace/Yes */ while(scanned) if(smfi_chgheader(ctx, (char *)"X-Virus-Scanned", scanned--, NULL) != MI_SUCCESS) logg("^Failed to remove existing X-Virus-Scanned header\n"); while(status) if(smfi_chgheader(ctx, (char *)"X-Virus-Status", status--, NULL) != MI_SUCCESS) logg("^Failed to remove existing X-Virus-Status header\n"); if(smfi_addheader(ctx, (char *)"X-Virus-Scanned", xvirushdr) != MI_SUCCESS) logg("^Failed to add X-Virus-Scanned header\n"); if(smfi_addheader(ctx, (char *)"X-Virus-Status", st) != MI_SUCCESS) logg("^Failed to add X-Virus-Status header\n"); } else { /* Add */ if(smfi_insheader(ctx, 1, (char *)"X-Virus-Scanned", xvirushdr) != MI_SUCCESS) logg("^Failed to insert X-Virus-Scanned header\n"); if(smfi_insheader(ctx, 1, (char *)"X-Virus-Status", st) != MI_SUCCESS) logg("^Failed to insert X-Virus-Status header\n"); } }
static sfsistat cb_eom(SMFICTX *ctx) { struct context *context; char sclstr[16]; 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 (context->sclvalue != -1) { snprintf(sclstr, 16, "%d", context->sclvalue); if (smfi_addheader(ctx, "X-MS-Exchange-Organization-SCL", sclstr) != MI_SUCCESS) msg(LOG_ERR, context, "cb_eom: smfi_addheader"); } return (SMFIS_ACCEPT); }
int spamdscan(SMFICTX *ctx, struct mlfi_priv *priv, struct config_file *cfg, char **subject, int extra) { int retry, r = -2, hr = 0, to_trace = 0; struct timeval t; double ts, tf; struct spamd_server *selected = NULL; char rbuf[BUFSIZ], hdrbuf[BUFSIZ]; char *prefix = "s", *mid = NULL, *c; rspamd_result_t res; struct rspamd_metric_result *cur = NULL, *tmp; struct rspamd_symbol *cur_symbol, *tmp_symbol; enum rspamd_metric_action res_action = METRIC_ACTION_NOACTION; struct timespec sleep_ts; gettimeofday(&t, NULL); ts = t.tv_sec + t.tv_usec / 1000000.0; retry = cfg->spamd_retry_count; sleep_ts.tv_sec = cfg->spamd_retry_timeout / 1000; sleep_ts.tv_nsec = (cfg->spamd_retry_timeout % 1000) * 1000000ULL; TAILQ_INIT(&res); /* try to scan with available servers */ while (1) { if (extra) { selected = (struct spamd_server *) get_random_upstream ((void *)cfg->extra_spamd_servers, cfg->extra_spamd_servers_num, sizeof (struct spamd_server), t.tv_sec, cfg->spamd_error_time, cfg->spamd_dead_time, cfg->spamd_maxerrors); } else { selected = (struct spamd_server *) get_random_upstream ((void *)cfg->spamd_servers, cfg->spamd_servers_num, sizeof (struct spamd_server), t.tv_sec, cfg->spamd_error_time, cfg->spamd_dead_time, cfg->spamd_maxerrors); } if (selected == NULL) { msg_err ("spamdscan: upstream get error, %s", priv->file); return -1; } if (selected->type == SPAMD_SPAMASSASSIN) { prefix = "s"; r = spamdscan_socket (priv->file, selected, cfg, &res); } else { prefix = "rs"; r = rspamdscan_socket (ctx, priv, selected, cfg, &res, &mid); } if (r == 0 || r == 1) { upstream_ok (&selected->up, t.tv_sec); break; } upstream_fail (&selected->up, t.tv_sec); if (r == -2) { msg_warn("%spamdscan: unexpected problem, %s, %s", prefix, selected->name, priv->file); break; } if (--retry < 1) { msg_warn("%spamdscan: retry limit exceeded, %s, %s", prefix, selected->name, priv->file); break; } msg_warn("%spamdscan: failed to scan, retry, %s, %s", prefix, selected->name, priv->file); nanosleep (&sleep_ts, NULL); } /* * print scanning time, server and result */ gettimeofday(&t, NULL); tf = t.tv_sec + t.tv_usec / 1000000.0; /* Parse res tailq */ cur = TAILQ_FIRST(&res); while (cur) { if (cur->metric_name) { if (cfg->extended_spam_headers) { hr = snprintf (hdrbuf, sizeof (hdrbuf), "%s: %s [%.2f / %.2f]%c", cur->metric_name, cur->score > cur->required_score ? "True" : "False", cur->score, cur->required_score, TAILQ_FIRST(&cur->symbols) != NULL ? '\n' : ' '); } r = snprintf (rbuf, sizeof (rbuf), "spamdscan: scan qid: <%s>, mid: <%s>, %f, %s, metric: %s: [%f / %f], symbols: ", priv->mlfi_id, (mid != NULL) ? mid : "undef", tf - ts, selected->name, cur->metric_name, cur->score, cur->required_score); free (cur->metric_name); } else { if (cfg->extended_spam_headers) { hr = snprintf (hdrbuf, sizeof (hdrbuf), "%s: %s [%.2f / %.2f]%c", "default", cur->score > cur->required_score ? "True" : "False", cur->score, cur->required_score, TAILQ_FIRST(&cur->symbols) != NULL ? '\n' : ' '); } r = snprintf (rbuf, sizeof (rbuf), "spamdscan: scan <%s>, %f, %s, metric: default: [%f / %f], symbols: ", priv->mlfi_id, tf - ts, selected->name, cur->score, cur->required_score); } if (cur->action > res_action) { res_action = cur->action; if (res_action == METRIC_ACTION_REWRITE_SUBJECT && cur->subject != NULL) { /* Copy subject as it would be freed further */ if (*subject != NULL) { free (*subject); } *subject = strdup (cur->subject); } } /* Write symbols */ cur_symbol = TAILQ_FIRST(&cur->symbols); if (cur_symbol == NULL) { r += snprintf (rbuf + r, sizeof (rbuf) - r, "no symbols"); } else { while (cur_symbol) { if (cur_symbol->symbol) { if (TAILQ_NEXT (cur_symbol, entry)) { r += snprintf (rbuf + r, sizeof (rbuf) - r, "%s, ", cur_symbol->symbol); } else { r += snprintf (rbuf + r, sizeof (rbuf) - r, "%s", cur_symbol->symbol); } if (cfg->trace_symbol) { c = strchr (cur_symbol->symbol, '('); if (c != NULL) { *c = '\0'; } if ( !strcmp (cfg->trace_symbol, cur_symbol->symbol)) { to_trace ++; } } if (cfg->extended_spam_headers) { if (TAILQ_NEXT (cur_symbol, entry)) { hr += snprintf (hdrbuf + hr, sizeof (hdrbuf) - hr, " %s\n", cur_symbol->symbol); } else { hr += snprintf (hdrbuf + hr, sizeof (hdrbuf) - hr, " %s", cur_symbol->symbol); } } free (cur_symbol->symbol); } tmp_symbol = cur_symbol; cur_symbol = TAILQ_NEXT(cur_symbol, entry); free (tmp_symbol); } } msg_info ("%s", rbuf); if (cur->subject != NULL) { free (cur->subject); } tmp = cur; cur = TAILQ_NEXT(cur, entry); free (tmp); if (cfg->extended_spam_headers) { if (extra) { smfi_addheader (ctx, "X-Spamd-Extra-Result", hdrbuf); } else { smfi_addheader (ctx, "X-Spamd-Result", hdrbuf); } } } /* All other statistic headers */ if (cfg->extended_spam_headers) { if (extra) { smfi_addheader (ctx, "X-Spamd-Extra-Server", selected->name); snprintf (hdrbuf, sizeof (hdrbuf), "%.2f", tf - ts); smfi_addheader (ctx, "X-Spamd-Extra-Scan-Time", hdrbuf); } else { smfi_addheader (ctx, "X-Spamd-Server", selected->name); snprintf (hdrbuf, sizeof (hdrbuf), "%.2f", tf - ts); smfi_addheader (ctx, "X-Spamd-Scan-Time", hdrbuf); smfi_addheader (ctx, "X-Spamd-Queue-ID", priv->mlfi_id); } } /* Trace spam messages to specific addr */ if (!extra && to_trace && cfg->trace_addr) { smfi_addrcpt (ctx, cfg->trace_addr); smfi_setpriv (ctx, priv); } return (r > 0 ? res_action : r); }
static sfsistat cb_eom(SMFICTX *ctx) { struct context *context; sfsistat action = SMFIS_CONTINUE; char buf[2048]; int pos = 0, retry = 0; 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 (context->fd < 0) goto done; context->symbols[0] = 0; /* no more writing data to spamd, want to read result now */ if (shutdown(context->fd, SHUT_WR)) { msg(LOG_ERR, context, "cb_eom: shutdown: %s", strerror(errno)); goto done; } if (fcntl(context->fd, F_SETFL, fcntl(context->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->fd, &fds); tv.tv_sec = 10; tv.tv_usec = 0; r = select(context->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->fd, &fds)) { retry++; msg(LOG_DEBUG, context, "cb_eom: waiting for " "spamd reply (retry %d)", retry); continue; } r = read(context->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->fd >= 0) { close(context->fd); context->fd = -1; } /* either way, we don't want to continue */ if (action == SMFIS_CONTINUE) action = (context->spam && context->score > REJECT_SPAM_LEVEL) ? SMFIS_REJECT : SMFIS_ACCEPT; 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->spam ? "SPAM" : "ham"), context->score, context->threshold, (context->symbols[0] ? " " : ""), context->symbols, context->hdr_from, context->hdr_to, context->hdr_subject); if (action == SMFIS_REJECT) { char m[1024]; snprintf(m, sizeof(m), "Spam (score %.1f)", context->score); if (smfi_setreply(ctx, RCODE_REJECT, XCODE_REJECT, m) != MI_SUCCESS) msg(LOG_ERR, context, "smfi_setreply"); } else { char m[1024]; int j=0; int starcnt = (int)context->score; const char *star = "*"; char stars[1024]; stars[0]='\0'; snprintf(m, sizeof(m), "%s, %s%.1f %s%.1f%s%s", (context->spam ? "Yes" : "No"),"score=", context->score, "required=",context->threshold, (context->symbols[0] ? " " : ""), context->symbols); if (smfi_addheader(ctx, "X-Spam-Flag", context->spam ? "YES" : "NO") != MI_SUCCESS) { msg(LOG_ERR, context, "smfi_addheader"); } if (smfi_addheader(ctx, "X-Spam-Status", m) != MI_SUCCESS) { msg(LOG_ERR, context, "smfi_addheader"); } for (j = 0; j < starcnt; j++) { strlcat(stars, star, sizeof (stars)); } if (smfi_addheader(ctx, "X-Spam-Level", stars) != MI_SUCCESS) { msg(LOG_ERR, context, "smfi_addheader"); } } context->pos = context->hdr_from[0] = context->hdr_to[0] = context->hdr_subject[0] = context->state = context->spam = context->symbols[0] = 0; context->score = context->threshold = 0.0; return (action); }