rmilter/src/rmilter.c
Vsevolod Stakhov 810b1cfa9e Do not use %m
2015-10-22 19:01:18 +01:00

1622 lines
42 KiB
C

/*
* Copyright (c) 2007-2012, Vsevolod Stakhov
* 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. Neither the name of the author nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
* 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 HOLDER 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.
*/
#include "config.h"
#ifdef WITH_SPF
#include "spf2/spf.h"
#include "spf.h"
#endif
#include "libclamc.h"
#include "libspamd.h"
#include "cfg_file.h"
#include "rmilter.h"
#include "regexp.h"
#ifdef HAVE_DCC
#include "dccif.h"
#endif
#include "ratelimit.h"
#include "greylist.h"
#include "blake2.h"
#ifndef HAVE_STDBOOL_H
# ifndef bool
# ifndef __bool_true_false_are_defined
typedef int bool;
# define __bool_true_false_are_defined 1
# endif /* ! __bool_true_false_are_defined */
# endif /* bool */
# ifndef true
#define false 0
#define true 1
# endif /* ! true */
#endif
#define SPAM_SUBJECT "***SPAM***"
static sfsistat mlfi_connect(SMFICTX *, char *, _SOCK_ADDR *);
static sfsistat mlfi_helo(SMFICTX *, char *);
static sfsistat mlfi_envfrom(SMFICTX *, char **);
static sfsistat mlfi_envrcpt(SMFICTX *, char **);
static sfsistat mlfi_data(SMFICTX *);
static sfsistat mlfi_header(SMFICTX * , char *, char *);
static sfsistat mlfi_eoh(SMFICTX *);
static sfsistat mlfi_body(SMFICTX *, u_char *, size_t);
static sfsistat mlfi_eom(SMFICTX *);
static sfsistat mlfi_close(SMFICTX *);
static sfsistat mlfi_abort(SMFICTX *);
static sfsistat mlfi_cleanup(SMFICTX *, bool);
static int check_clamscan(const char *, char *, size_t);
static void send_beanstalk (const struct mlfi_priv *);
#ifdef HAVE_DCC
static int check_dcc(const struct mlfi_priv *);
#endif
struct smfiDesc smfilter =
{
"rmilter", /* filter name */
SMFI_VERSION, /* version code -- do not change */
SMFIF_ADDHDRS | SMFIF_CHGHDRS | SMFIF_ADDRCPT | SMFIF_DELRCPT, /* flags */
mlfi_connect, /* connection info filter */
mlfi_helo, /* SMTP HELO command filter */
mlfi_envfrom, /* envelope sender filter */
mlfi_envrcpt, /* 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, /* connection cleanup */
#if (SMFI_PROT_VERSION >= 4)
NULL, /* unknown situation */
mlfi_data, /* SMTP DATA callback */
NULL /* Negotiation callback */
#endif
};
extern struct config_file *cfg;
/* Milter mutexes */
pthread_mutex_t mkstemp_mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t regexp_mtx = PTHREAD_MUTEX_INITIALIZER;
static sfsistat
set_reply (SMFICTX *ctx, const struct action *act)
{
int result = SMFIS_CONTINUE;
switch (act->type) {
case ACTION_ACCEPT:
result = SMFIS_ACCEPT;
break;
case ACTION_REJECT:
result = SMFIS_REJECT;
break;
case ACTION_TEMPFAIL:
result = SMFIS_TEMPFAIL;
break;
case ACTION_QUARANTINE:
result = SMFIS_DISCARD;
break;
case ACTION_DISCARD:
result = SMFIS_DISCARD;
break;
}
if (act->type == ACTION_REJECT &&
smfi_setreply(ctx, RCODE_REJECT, XCODE_REJECT,
(char *)act->message) != MI_SUCCESS) {
msg_err("smfi_setreply");
}
if (act->type == ACTION_TEMPFAIL &&
smfi_setreply(ctx, RCODE_TEMPFAIL, XCODE_TEMPFAIL,
(char *)act->message) != MI_SUCCESS) {
msg_err("smfi_setreply");
}
return result;
}
/*
* Strip angle braces if needed
*/
static void
normalize_email_addr (const char *src, char *dest, size_t destlen)
{
const char *c;
char *d;
c = src;
d = dest;
if (*c == '<') {
c++;
}
while (--destlen != 0) {
if ((*d++ = tolower (*c++)) == '\0') {
break;
}
}
if (d != dest + 1 && *(d - 1) == '>') {
*(d - 1) = '\0';
}
if (destlen == 0) {
*d = '\0';
}
}
static inline int
create_temp_file (struct mlfi_priv *priv)
{
#ifdef HAVE_PATH_MAX
char buf[PATH_MAX];
#elif defined(HAVE_MAXPATHLEN)
char buf[MAXPATHLEN];
#else
#error "neither PATH_MAX nor MAXPATHEN defined"
#endif
int fd;
snprintf (buf, sizeof (buf), "%s/msg.XXXXXXXX", cfg->temp_dir);
rmilter_strlcpy (priv->file, buf, sizeof (priv->file));
/* mkstemp is based on arc4random (3) and is not reentrable
* so acquire mutex for it
*/
pthread_mutex_lock (&mkstemp_mtx);
fd = mkstemp (priv->file);
pthread_mutex_unlock (&mkstemp_mtx);
if (fd == -1) {
msg_warn ("create_temp_file: %s: mkstemp failed: %s",
priv->mlfi_id, strerror (errno));
return -1;
}
priv->fileh = fdopen(fd, "w");
if (!priv->fileh) {
msg_warn ("create_temp_file: %s: can't open tempfile: %s",
priv->mlfi_id, strerror (errno));
return -1;
}
fprintf (priv->fileh, "Received: from %s (%s [%s]) by localhost (Postfix) with ESMTP id 0000000;\r\n",
priv->priv_helo, priv->priv_hostname, priv->priv_ip);
return 0;
}
static char *
dkim_stripcr (char *str)
{
char *t, *h;
for (t = str, h = str; *t != '\0'; t++) {
if (*t == '\r') {
continue;
}
if (t != h) {
*h = *t;
}
h++;
}
if (h != t) {
*h = *t;
}
return str;
}
static void
check_message_id (struct mlfi_priv *priv, char *header)
{
blake2b_state mdctx;
u_char final[BLAKE2B_OUTBYTES], param = '0';
char md5_out[BLAKE2B_OUTBYTES * 2 + 1], *c, ipout[INET_ADDRSTRLEN + 1];
struct memcached_server *selected;
memcached_ctx_t mctx;
memcached_param_t cur_param;
int r;
size_t s = strlen (header);
/* First of all do regexp check of message to determine special message id */
if (cfg->special_mid_re) {
if ((r = pcre_exec (cfg->special_mid_re, NULL, header, s, 0, 0, NULL, 0)) >= 0) {
priv->complete_to_beanstalk = 1;
}
}
if (cfg->memcached_servers_id_num == 0) {
return;
}
bzero (&cur_param, sizeof (cur_param));
cur_param.buf = &param;
cur_param.bufsize = sizeof (param);
blake2b_init (&mdctx, BLAKE2B_OUTBYTES);
/* Check reply message id in memcached */
/* Make hash from message id */
blake2b_update (&mdctx, (const u_char *)header, s);
blake2b_final (&mdctx, final, BLAKE2B_OUTBYTES);
/* Format md5 output */
s = sizeof (md5_out);
for (r = 0; r < BLAKE2B_OUTBYTES; r ++){
s -= snprintf (md5_out + r * 2, s, "%02x", final[r]);
}
c = cur_param.key;
s = sizeof (cur_param.key);
if (cfg->id_prefix) {
s = rmilter_strlcpy (c, cfg->id_prefix, s);
c += s;
}
if (sizeof (cur_param.key) - s > sizeof (md5_out)) {
memcpy (c, md5_out, sizeof (md5_out));
}
else {
msg_warn ("check_id: id_prefix(%s) too long for memcached key, error in configure", cfg->id_prefix);
memcpy (c, md5_out, sizeof (cur_param.key) - s);
}
selected = (struct memcached_server *) get_upstream_by_hash ((void *)cfg->memcached_servers_id,
cfg->memcached_servers_id_num, sizeof (struct memcached_server),
(time_t)priv->conn_tm.tv_sec, cfg->memcached_error_time,
cfg->memcached_dead_time, cfg->memcached_maxerrors,
(char *)cur_param.key, strlen (cur_param.key));
if (selected == NULL) {
msg_err ("mlfi_data: cannot get memcached upstream for storing message id");
return;
}
mctx.protocol = cfg->memcached_protocol;
memcpy(&mctx.addr, &selected->addr[0], sizeof (struct in_addr));
mctx.port = selected->port[0];
mctx.timeout = cfg->memcached_connect_timeout;
mctx.alive = selected->alive[0];
#ifdef WITH_DEBUG
mctx.options = MEMC_OPT_DEBUG;
#else
mctx.options = 0;
#endif
r = memc_init_ctx(&mctx);
if (r == -1) {
msg_warn ("mlfi_data: cannot connect to memcached upstream: %s",
inet_ntop (AF_INET, &selected->addr[0], ipout, sizeof (ipout)));
upstream_fail (&selected->up, priv->conn_tm.tv_sec);
return;
}
r = OK;
r = memc_get (&mctx, &cur_param, &s);
if (r == OK) {
/* Turn off strict checks if message id is found */
memc_close_ctx (&mctx);
upstream_ok (&selected->up, priv->conn_tm.tv_sec);
priv->strict = 0;
rmilter_strlcpy (priv->reply_id, header, sizeof (priv->reply_id));
msg_info ("check_message_id: %s: from %s[%s] from=<%s> to=<%s> is reply to our message %s; "
"skip dcc and spamd checks",
priv->mlfi_id,
priv->priv_hostname,
priv->priv_ip,
priv->priv_from,
priv->rcpts.lh_first->r_addr,
priv->reply_id);
return;
}
else if (r != NOT_EXISTS) {
msg_err ("mlfi_data: cannot read data from memcached: %s", memc_strerror (r));
upstream_fail (&selected->up, priv->conn_tm.tv_sec);
memc_close_ctx (&mctx);
return;
}
memc_close_ctx (&mctx);
}
static sfsistat
check_greylisting_ctx(SMFICTX *ctx, struct mlfi_priv *priv)
{
int r;
void *ptr;
CFG_RLOCK();
if (priv->priv_ip[0] != '\0' && cfg->memcached_servers_grey_num > 0 &&
cfg->greylisting_timeout > 0 && cfg->greylisting_expire > 0 && priv->strict != 0) {
msg_debug ("check_greylisting_ctx: %s: checking greylisting", priv->mlfi_id);
ptr = priv->priv_addr.family == AF_INET6 ? (void *)&priv->priv_addr.addr.sa6.sin6_addr :
(void *)&priv->priv_addr.addr.sa4.sin_addr;
r = check_greylisting (cfg, ptr, priv->priv_addr.family, &priv->conn_tm,
priv->priv_from, priv->rcpts.lh_first->r_addr);
switch (r) {
case GREY_GREYLISTED:
if (smfi_setreply (ctx, RCODE_LATER, XCODE_TEMPFAIL, cfg->greylisted_message) != MI_SUCCESS) {
msg_err("check_greylisting_ctx: %s: smfi_setreply failed", priv->mlfi_id);
}
CFG_UNLOCK();
return SMFIS_TEMPFAIL;
break;
case GREY_ERROR:
if (smfi_setreply (ctx, RCODE_TEMPFAIL, XCODE_TEMPFAIL, (char *)"Service unavailable") != MI_SUCCESS) {
msg_err("check_greylisting_ctx: %s: smfi_setreply failed", priv->mlfi_id);
}
CFG_UNLOCK();
return SMFIS_TEMPFAIL;
break;
case GREY_WHITELISTED:
default:
break;
}
}
CFG_UNLOCK();
return SMFIS_CONTINUE;
}
/*
* Send copy of message to beanstalk
* XXX: too many copy&paste
*/
static void
send_beanstalk_copy (const struct mlfi_priv *priv, struct beanstalk_server *srv)
{
beanstalk_ctx_t bctx;
beanstalk_param_t bp;
size_t s;
int r, fd;
void *map;
struct stat st;
char ipout[INET_ADDRSTRLEN + 1];
/* Open and mmap file */
if (!*priv->file) {
return;
}
if (stat (priv->file, &st) == -1) {
msg_warn ("send_beanstalk_copy: %s: data file stat(): %s", priv->mlfi_id,
strerror (errno));
return;
}
fd = open (priv->file, O_RDONLY);
if (fd == -1) {
msg_warn ("send_beanstalk_copy: %s: data file open(): %s", priv->mlfi_id,
strerror (errno));
return;
}
if ((map = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
msg_err ("send_beanstalk_copy: cannot mmap file %s, %s", priv->file,
strerror (errno));
close (fd);
return;
}
close (fd);
bctx.protocol = cfg->beanstalk_protocol;
memcpy (&bctx.addr, &srv->addr, sizeof (struct in_addr));
bctx.port = srv->port;
bctx.timeout = cfg->beanstalk_connect_timeout;
r = bean_init_ctx (&bctx);
if (r == -1) {
munmap (map, st.st_size);
msg_warn ("send_beanstalk_copy: cannot connect to beanstalk upstream: %s",
inet_ntop (AF_INET, &srv->addr, ipout, sizeof (ipout)));
upstream_fail (&srv->up, priv->conn_tm.tv_sec);
return;
}
bp.buf = (u_char *)map;
bp.bufsize = st.st_size;
bp.len = bp.bufsize;
bp.priority = 1025;
s = 1;
r = bean_put (&bctx, &bp, &s, cfg->beanstalk_lifetime, 0);
munmap (map, st.st_size);
if (r == BEANSTALK_OK) {
bean_close_ctx (&bctx);
upstream_ok (&srv->up, priv->conn_tm.tv_sec);
return;
}
else {
msg_err ("send_beanstalk_copy: cannot put data to beanstalk: %s", bean_strerror (r));
upstream_fail (&srv->up, priv->conn_tm.tv_sec);
bean_close_ctx (&bctx);
return;
}
bean_close_ctx (&bctx);
}
static void
send_beanstalk (const struct mlfi_priv *priv)
{
struct beanstalk_server *selected;
beanstalk_ctx_t bctx;
beanstalk_param_t bp;
size_t s;
int r, fd;
void *map;
char ipout[INET_ADDRSTRLEN + 1];
selected = (struct beanstalk_server *) get_random_upstream ((void *)cfg->beanstalk_servers,
cfg->beanstalk_servers_num, sizeof (struct beanstalk_server),
priv->conn_tm.tv_sec, cfg->beanstalk_error_time,
cfg->beanstalk_dead_time, cfg->beanstalk_maxerrors);
if (selected == NULL) {
msg_err ("send_beanstalk: upstream get error, %s", priv->file);
return;
}
/* Open and mmap file */
if (!*priv->file || priv->eoh_pos == 0) {
return;
}
fd = open (priv->file, O_RDONLY);
if (fd == -1) {
msg_warn ("send_beanstalk: %s: data file open(): %s",
priv->mlfi_id, strerror (errno));
return;
}
if ((map = mmap (NULL, priv->eoh_pos, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
msg_err ("send_beanstalk: %s: cannot mmap file %s: %s", priv->mlfi_id,
priv->file, strerror (errno));
close (fd);
return;
}
close (fd);
bctx.protocol = cfg->beanstalk_protocol;
memcpy(&bctx.addr, &selected->addr, sizeof (struct in_addr));
bctx.port = selected->port;
bctx.timeout = cfg->beanstalk_connect_timeout;
r = bean_init_ctx (&bctx);
if (r == -1) {
msg_warn ("send_beanstalk: %s: cannot connect to beanstalk upstream: %s",
priv->mlfi_id,
inet_ntop (AF_INET, &selected->addr, ipout, sizeof (ipout)));
upstream_fail (&selected->up, priv->conn_tm.tv_sec);
munmap (map, priv->eoh_pos);
return;
}
bp.buf = (u_char *)map;
bp.bufsize = priv->eoh_pos;
bp.len = bp.bufsize;
bp.priority = 1025;
s = 1;
r = bean_put (&bctx, &bp, &s, cfg->beanstalk_lifetime, 0);
munmap (map, priv->eoh_pos);
if (r == BEANSTALK_OK) {
bean_close_ctx (&bctx);
upstream_ok (&selected->up, priv->conn_tm.tv_sec);
return;
}
else {
msg_err ("send_beanstalk: cannot put data to beanstalk: %s", bean_strerror (r));
upstream_fail (&selected->up, priv->conn_tm.tv_sec);
bean_close_ctx (&bctx);
return;
}
bean_close_ctx (&bctx);
}
static void
format_spamd_reply (char *result, size_t len, char *format, char *symbols)
{
char *pos = result, *s = format;
while (pos - result < len && *s != '\0') {
if (*s != '%') {
/* Copy next symbol */
*pos ++ = *s ++;
}
else if (*(s + 1) == 's') {
/* Paste symbols */
if (symbols != NULL) {
pos += rmilter_strlcpy (pos, symbols, len - (pos - result));
}
else {
pos += rmilter_strlcpy (pos, "no symbols", len - (pos - result));
}
}
}
*pos = '\0';
}
/* Milter callbacks */
static sfsistat
mlfi_connect(SMFICTX * ctx, char *hostname, _SOCK_ADDR * addr)
{
struct mlfi_priv *priv;
union sockaddr_un {
struct sockaddr_in sa4;
struct sockaddr_in6 sa6;
struct sockaddr sa;
} *addr_storage;
priv = malloc(sizeof (struct mlfi_priv));
if (priv == NULL) {
return SMFIS_TEMPFAIL;
}
memset(priv, '\0', sizeof (struct mlfi_priv));
LIST_INIT (&priv->rcpts);
priv->strict = 1;
priv->serial = cfg->serial;
priv->priv_addr.family = AF_UNSPEC;
priv->priv_rcptcount = 0;
if (gettimeofday (&priv->conn_tm, NULL) == -1) {
msg_err ("Internal error: gettimeofday failed %s", strerror (errno));
return SMFIS_TEMPFAIL;
}
if (addr != NULL) {
addr_storage = (union sockaddr_un *)addr;
priv->priv_addr.family = addr->sa_family;
switch (addr->sa_family) {
case AF_INET:
inet_ntop (AF_INET, &addr_storage->sa4.sin_addr, priv->priv_ip, sizeof (priv->priv_ip));
memcpy (&priv->priv_addr.addr.sa4, &addr_storage->sa4, sizeof (struct sockaddr_in));
break;
case AF_INET6:
inet_ntop (AF_INET6, &addr_storage->sa6.sin6_addr, priv->priv_ip, sizeof (priv->priv_ip));
memcpy (&priv->priv_addr.addr.sa6, &addr_storage->sa6, sizeof (struct sockaddr_in6));
break;
default:
rmilter_strlcpy (priv->priv_ip, "NULL", sizeof(priv->priv_ip));
memcpy (&priv->priv_addr.addr.sa, &addr_storage->sa, sizeof (struct sockaddr));
break;
}
}
if (hostname != NULL) {
rmilter_strlcpy (priv->priv_hostname, hostname, sizeof (priv->priv_hostname));
}
else {
priv->priv_hostname[0] = '\0';
}
smfi_setpriv(ctx, priv);
/* Cannot set reply here, so delay processing of connect stage */
return SMFIS_CONTINUE;
}
static sfsistat
mlfi_helo(SMFICTX *ctx, char *helostr)
{
struct mlfi_priv *priv;
priv = (struct mlfi_priv *) smfi_getpriv (ctx);
rmilter_strlcpy (priv->priv_helo, helostr, ADDRLEN);
msg_debug ("mlfi_helo: got helo value: %s", priv->priv_helo);
return SMFIS_CONTINUE;
}
#ifdef WITH_DKIM
static DKIM*
try_wildcard_dkim (const char *domain, struct mlfi_priv *priv)
{
DKIM_STAT statp;
struct dkim_domain_entry *dkim_domain;
DKIM *d;
int fd;
struct stat st;
void *keymap;
#ifdef HAVE_PATH_MAX
char fname[PATH_MAX + 10];
#elif defined(HAVE_MAXPATHLEN)
char fname[MAXPATHLEN + 10];
#else
#error "neither PATH_MAX nor MAXPATHEN defined"
#endif
for(dkim_domain = cfg->dkim_domains; dkim_domain != NULL; dkim_domain = dkim_domain->hh.next) {
if (dkim_domain->is_wildcard) {
/* Check for domain */
if (strcmp (dkim_domain->domain, "*") != 0 && strcasestr (domain, dkim_domain->domain) == NULL) {
/* Not our domain */
continue;
}
/* Check for directory */
if (dkim_domain->keyfile) {
if (stat (dkim_domain->keyfile, &st) != -1 && S_ISDIR (st.st_mode)) {
/* Print keyfilename in format <dkim_domain>/<domain>.<selector>.key */
snprintf (fname, sizeof (fname), "%s/%s.%s.key", dkim_domain->keyfile, domain, dkim_domain->selector);
if (stat (fname, &st) != -1 && S_ISREG (st.st_mode)) {
fd = open (fname, O_RDONLY);
if (fd != -1) {
/* Mmap key */
keymap = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
close (fd);
if (keymap != MAP_FAILED) {
d = dkim_sign (cfg->dkim_lib, (u_char *)"rmilter", NULL,
(u_char *)keymap, (u_char *)dkim_domain->selector,
(u_char *)domain,
cfg->dkim_relaxed_header ? DKIM_CANON_RELAXED : DKIM_CANON_SIMPLE,
cfg->dkim_relaxed_body ? DKIM_CANON_RELAXED : DKIM_CANON_SIMPLE,
cfg->dkim_sign_sha256 ? DKIM_SIGN_RSASHA256 : DKIM_SIGN_RSASHA1, -1, &statp);
/* It is safe to unmap memory here */
munmap (keymap, st.st_size);
if (statp != DKIM_STAT_OK) {
msg_info ("dkim sign failed: %s", dkim_geterror (d));
if (d) {
dkim_free (d);
}
return NULL;
}
else {
priv->dkim_domain = dkim_domain;
return d;
}
}
}
}
}
}
}
}
return NULL;
}
#endif
static sfsistat
mlfi_envfrom(SMFICTX *ctx, char **envfrom)
{
char *tmpfrom;
struct mlfi_priv *priv;
struct rule *act;
unsigned int i;
if ((priv = (struct mlfi_priv *) smfi_getpriv (ctx)) == NULL) {
msg_err ("Internal error: smfi_getpriv() returns NULL");
return SMFIS_TEMPFAIL;
}
/*
* Get mail from addr
*/
tmpfrom = smfi_getsymval(ctx, "{mail_addr}");
if (tmpfrom == NULL || *tmpfrom == '\0') {
tmpfrom = "";
}
else if (strchr (tmpfrom, '@') == NULL) {
/* Special case for sendmail */
tmpfrom = envfrom[0];
if (tmpfrom == NULL || *tmpfrom == '\0') {
tmpfrom = "";
}
}
normalize_email_addr (tmpfrom, priv->priv_from, sizeof (priv->priv_from));
msg_debug ("mlfi_envfrom: got from value: %s", priv->priv_from);
if (priv->priv_hostname[0] == '\0') {
tmpfrom = smfi_getsymval(ctx, "{client_name}");
if (tmpfrom != NULL) {
rmilter_strlcpy (priv->priv_hostname, tmpfrom, sizeof (priv->priv_hostname));
msg_debug ("mlfi_envfrom: got host value: %s", priv->priv_hostname);
}
else {
rmilter_strlcpy (priv->priv_hostname, "unknown", sizeof (priv->priv_hostname));
}
}
tmpfrom = smfi_getsymval(ctx, "{auth_authen}");
if (tmpfrom != NULL) {
priv->authenticated = 1;
rmilter_strlcpy (priv->priv_user, tmpfrom, sizeof (priv->priv_user));
}
/* Check whether we need to sign this message */
#ifdef WITH_DKIM
CFG_RLOCK();
DKIM_STAT statp;
struct dkim_domain_entry *dkim_domain;
char *domain_pos;
domain_pos = strchr (priv->priv_from, '@');
if (domain_pos) {
HASH_FIND_STR (cfg->dkim_domains, domain_pos + 1, dkim_domain, strncasecmp);
if (!cfg->dkim_auth_only || priv->authenticated) {
if (dkim_domain && dkim_domain->is_loaded) {
priv->dkim = dkim_sign (cfg->dkim_lib, (u_char *)"rmilter", NULL,
(u_char *)dkim_domain->key, (u_char *)dkim_domain->selector,
(u_char *)dkim_domain->domain,
cfg->dkim_relaxed_header ? DKIM_CANON_RELAXED : DKIM_CANON_SIMPLE,
cfg->dkim_relaxed_body ? DKIM_CANON_RELAXED : DKIM_CANON_SIMPLE,
cfg->dkim_sign_sha256 ? DKIM_SIGN_RSASHA256 : DKIM_SIGN_RSASHA1, -1, &statp);
if (statp != DKIM_STAT_OK) {
msg_info ("dkim sign failed: %s", dkim_geterror (priv->dkim));
if (priv->dkim) {
dkim_free (priv->dkim);
}
priv->dkim = NULL;
}
else {
msg_debug ("try to add signature for %s domain", dkim_domain->domain);
priv->dkim_domain = dkim_domain;
}
}
else {
priv->dkim = try_wildcard_dkim (domain_pos + 1, priv);
if (priv->dkim) {
msg_debug ("try to add signature for %s domain", domain_pos + 1);
}
}
}
else {
priv->dkim = NULL;
msg_debug ("do not add dkim signature for unauthorized user");
}
}
CFG_UNLOCK();
#endif
CFG_RLOCK();
/* Check connect */
act = regexp_check (cfg, priv, STAGE_CONNECT);
if (act != NULL) {
priv->matched_rules[STAGE_CONNECT] = act;
}
/* Check helo */
act = regexp_check (cfg, priv, STAGE_HELO);
if (act != NULL) {
priv->matched_rules[STAGE_HELO] = act;
}
/* Check envfrom */
act = regexp_check (cfg, priv, STAGE_ENVFROM);
if (act != NULL) {
priv->matched_rules[STAGE_ENVFROM] = act;
}
CFG_UNLOCK();
return SMFIS_CONTINUE;
}
static sfsistat
mlfi_envrcpt(SMFICTX *ctx, char **envrcpt)
{
struct mlfi_priv *priv;
struct rule *act;
struct rcpt *newrcpt;
char *tmprcpt;
if ((priv = (struct mlfi_priv *) smfi_getpriv (ctx)) == NULL) {
msg_err ("Internal error: smfi_getpriv() returns NULL");
return SMFIS_TEMPFAIL;
}
/*
* Get recipient address
*/
tmprcpt = *envrcpt;
if (tmprcpt == NULL || *tmprcpt == '\0') {
tmprcpt = "<>";
}
newrcpt = malloc (sizeof (struct rcpt));
if (newrcpt == NULL) {
msg_err ("malloc failed: %s", strerror (errno));
return SMFIS_TEMPFAIL;
}
rmilter_strlcpy (newrcpt->r_addr, tmprcpt, sizeof (newrcpt->r_addr));
CFG_RLOCK();
newrcpt->is_whitelisted = is_whitelisted_rcpt (cfg, newrcpt->r_addr, 1);
if (!newrcpt->is_whitelisted && priv->has_whitelisted) {
priv->has_whitelisted = 0;
}
else if (newrcpt->is_whitelisted && !priv->has_whitelisted) {
priv->has_whitelisted = 1;
}
/* Check ratelimit */
if (rate_check (priv, cfg, newrcpt->r_addr, 0) == 0) {
/* Rate is more than limit */
if (smfi_setreply (ctx, RCODE_TEMPFAIL, XCODE_TEMPFAIL, (char *)"Rate limit exceeded") != MI_SUCCESS) {
msg_err("smfi_setreply");
}
CFG_UNLOCK();
free (newrcpt);
return SMFIS_TEMPFAIL;
}
LIST_INSERT_HEAD (&priv->rcpts, newrcpt, r_list);
/* Check recipient */
act = regexp_check (cfg, priv, STAGE_ENVRCPT);
if (act != NULL) {
priv->matched_rules[STAGE_ENVRCPT] = act;
}
CFG_UNLOCK();
return SMFIS_CONTINUE;
}
static sfsistat
mlfi_data(SMFICTX *ctx)
{
struct mlfi_priv *priv;
char *id;
int r;
if ((priv = (struct mlfi_priv *) smfi_getpriv (ctx)) == NULL) {
msg_err ("Internal error: smfi_getpriv() returns NULL");
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;
}
/* set queue id */
id = smfi_getsymval(ctx, "i");
CFG_RLOCK();
if (id) {
rmilter_strlcpy (priv->mlfi_id, id, sizeof(priv->mlfi_id));
}
else {
rmilter_strlcpy (priv->mlfi_id, "NOQUEUE", sizeof (priv->mlfi_id));
msg_err ("mlfi_data: cannot get queue id, set to 'NOQUEUE'");
}
CFG_UNLOCK();
if (priv->authenticated && !cfg->strict_auth) {
msg_info ("mlfi_envfrom: %s: turn off strict checks for authenticated sender: %s",
priv->mlfi_id, priv->priv_user);
priv->strict = 0;
}
if (!cfg->spamd_greylist) {
if (!priv->authenticated &&
(r = check_greylisting_ctx (ctx, priv)) != SMFIS_CONTINUE) {
msg_info ("mlfi_eom: %s: greylisting message", priv->mlfi_id);
mlfi_cleanup (ctx, false);
return r;
}
}
return SMFIS_CONTINUE;
}
static sfsistat
mlfi_header(SMFICTX * ctx, char *headerf, char *headerv)
{
struct mlfi_priv *priv;
struct rule *act;
int len;
char *p, *c, t;
if ((priv = (struct mlfi_priv *) smfi_getpriv (ctx)) == NULL) {
msg_err ("Internal error: smfi_getpriv() returns NULL");
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;
}
if (headerv && strncasecmp (headerf, "In-Reply-To", sizeof ("In-Reply-To") - 1) == 0) {
check_message_id (priv, headerv);
}
else if (headerv && strncasecmp (headerf, "References", sizeof ("References") - 1) == 0) {
/* Break references into individual message-id */
c = headerv;
p = c;
while (*p) {
if (isspace (*p)) {
t = *p;
*p = '\0';
check_message_id (priv, c);
*p = t;
while (isspace (*p) && *p) {
p ++;
}
c = p;
}
p ++;
}
}
else if (strncasecmp (headerf, "Return-Path", sizeof ("Return-Path") - 1) == 0) {
priv->has_return_path = 1;
}
/*
* Create temporary file, if this is first call of mlfi_header(), and it
* not yet created
*/
CFG_RLOCK();
if (!priv->fileh) {
if (create_temp_file (priv) == -1) {
msg_err ("mlfi_eoh: cannot create temp file");
CFG_UNLOCK();
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;
}
}
#ifdef WITH_DKIM
struct dkim_hash_entry *e;
u_char *tmp;
int tmplen, r;
if (priv->dkim) {
HASH_FIND_STR (cfg->headers, headerf, e, strncasecmp);
if (e) {
tmplen = strlen (headerf) + strlen (headerv) + sizeof (": ");
tmp = malloc (tmplen);
if (tmp != NULL) {
snprintf ((char *)tmp, tmplen, "%s: %s", headerf, headerv);
r = dkim_header (priv->dkim, tmp, tmplen - 1);
if (r != DKIM_STAT_OK) {
msg_info ("dkim_header failed: %s", dkim_geterror (priv->dkim));
}
free (tmp);
}
}
}
#endif
/*
* Write header line to temporary file.
*/
fprintf (priv->fileh, "%s: %s\n", headerf, headerv);
/* Check header with regexp */
priv->priv_cur_header.header_name = headerf;
priv->priv_cur_header.header_value = headerv;
if (strcasecmp (headerf, "Subject") == 0) {
len = sizeof (SPAM_SUBJECT) + strlen (headerv);
priv->priv_subject = malloc (len);
if (priv->priv_subject) {
snprintf (priv->priv_subject, len, SPAM_SUBJECT " %s", headerv);
}
}
act = regexp_check (cfg, priv, STAGE_HEADER);
if (act != NULL) {
priv->matched_rules[STAGE_HEADER] = act;
}
CFG_UNLOCK();
return SMFIS_CONTINUE;
}
static sfsistat
mlfi_eoh(SMFICTX * ctx)
{
struct mlfi_priv *priv;
struct rcpt *rcpt;
if ((priv = (struct mlfi_priv *) smfi_getpriv (ctx)) == NULL) {
msg_err ("Internal error: smfi_getpriv() returns NULL");
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;
}
if (!priv->fileh) {
if (create_temp_file (priv) == -1) {
msg_err ("mlfi_eoh: %s: cannot create temp file", priv->mlfi_id);
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;
}
}
if (!priv->has_return_path && priv->fileh) {
fprintf (priv->fileh, "Return-Path: <%s>\r\n", priv->priv_from);
}
if (priv->fileh) {
LIST_FOREACH (rcpt, &priv->rcpts, r_list) {
fprintf (priv->fileh, "X-Rcpt-To: %s\r\n", rcpt->r_addr);
}
fprintf (priv->fileh, "\r\n");
priv->eoh_pos = ftell (priv->fileh);
}
#ifdef WITH_DKIM
int r;
if (priv->dkim) {
r = dkim_eoh (priv->dkim);
if (r != DKIM_STAT_OK) {
msg_info ("mlfi_eoh: %s: dkim_eoh failed: %s", priv->mlfi_id, dkim_geterror (priv->dkim));
}
}
#endif
return SMFIS_CONTINUE;
}
static sfsistat
mlfi_eom(SMFICTX * ctx)
{
struct mlfi_priv *priv;
int r, er;
#ifdef HAVE_PATH_MAX
char strres[PATH_MAX], buf[PATH_MAX];
#elif defined(HAVE_MAXPATHLEN)
char strres[MAXPATHLEN], buf[MAXPATHLEN ];
#else
#error "neither PATH_MAX nor MAXPATHEN defined"
#endif
char *id, *subject = NULL;
int prob_max;
double prob_cur;
struct stat sb;
struct action *act;
struct rcpt *rcpt;
bool ip_whitelisted = false;
if ((priv = (struct mlfi_priv *) smfi_getpriv (ctx)) == NULL) {
msg_err ("Internal error: smfi_getpriv() returns NULL");
return SMFIS_TEMPFAIL;
}
/* set queue id */
id = smfi_getsymval(ctx, "i");
if (id) {
rmilter_strlcpy (priv->mlfi_id, id, sizeof(priv->mlfi_id));
}
else {
rmilter_strlcpy (priv->mlfi_id, "NOQUEUE", sizeof (priv->mlfi_id));
msg_err ("mlfi_eom: cannot get queue id, set to 'NOQUEUE'");
}
#if (SMFI_PROT_VERSION < 4)
/* Do greylisting here if DATA callback is not available */
if (!cfg->spamd_greylist) {
if ((r = check_greylisting_ctx (ctx, priv)) != SMFIS_CONTINUE) {
msg_info ("mlfi_eom: %s: greylisting message", priv->mlfi_id);
mlfi_cleanup (ctx, false);
return r;
}
}
#endif
CFG_RLOCK();
if (cfg->serial == priv->serial) {
msg_debug ("mlfi_eom: %s: checking regexp rules", priv->mlfi_id);
act = rules_check (priv->matched_rules);
if (act != NULL && act->type != ACTION_ACCEPT) {
CFG_UNLOCK ();
return set_reply (ctx, act);
}
}
else {
msg_warn ("mlfi_eom: %s: config was reloaded, not checking rules", priv->mlfi_id);
}
#ifdef WITH_SPF
/*
* Is the sender address SPF-compliant?
*/
if (cfg->spf_domains_num > 0) {
msg_debug ("mlfi_eom: %s: check spf", priv->mlfi_id);
r = spf_check (priv, cfg);
switch (r) {
case SPF_RESULT_PASS:
case SPF_RESULT_SOFTFAIL:
case SPF_RESULT_NEUTRAL:
case SPF_RESULT_NONE:
break;
case SPF_RESULT_FAIL:
if (!priv->has_whitelisted) {
snprintf (buf, sizeof (buf) - 1, "SPF policy violation. Host %s[%s] is not allowed to send mail as %s.",
(*priv->priv_hostname != '\0') ? priv->priv_hostname : "unresolved",
priv->priv_ip, priv->priv_from);
smfi_setreply (ctx, RCODE_REJECT, XCODE_REJECT, buf);
CFG_UNLOCK();
mlfi_cleanup (ctx, false);
return SMFIS_REJECT;
}
break;
}
}
#endif
if (priv->complete_to_beanstalk) {
/* Set actual pos to send all message to beanstalk */
priv->eoh_pos = ftell (priv->fileh);
}
fflush (priv->fileh);
/* check file size */
if (stat (priv->file, &sb) == -1) {
msg_warn ("mlfi_eom: %s: stat failed: %s", priv->mlfi_id,
strerror (errno));
CFG_UNLOCK();
return mlfi_cleanup (ctx, true);
}
else if (cfg->sizelimit != 0 && sb.st_size > (off_t)cfg->sizelimit) {
#ifndef FREEBSD_LEGACY
msg_warn ("mlfi_eom: %s: message size(%zd) exceeds limit(%zd), not scanned, %s",
priv->mlfi_id, (size_t)sb.st_size, cfg->sizelimit, priv->file);
#else
msg_warn ("mlfi_eom: %s: message size(%ld) exceeds limit(%ld), not scanned, %s",
priv->mlfi_id, (long int)sb.st_size, (long int)cfg->sizelimit, priv->file);
#endif
CFG_UNLOCK();
return mlfi_cleanup (ctx, true);
}
msg_info ("mlfi_eom: %s: tempfile=%s, size=%lu",
priv->mlfi_id, priv->file, (unsigned long int)sb.st_size);
if (!priv->strict) {
}
#ifdef HAVE_DCC
/* Check dcc */
if (cfg->use_dcc == 1 && !priv->has_whitelisted && priv->strict &&
(cfg->strict_auth && *priv->priv_user != '\0')) {
msg_debug ("mlfi_eom: %s: check dcc", priv->mlfi_id);
r = check_dcc (priv);
switch (r) {
case 'A':
break;
case 'G':
msg_warn ("mlfi_eom: %s: greylisting by dcc", priv->mlfi_id);
smfi_setreply (ctx, RCODE_LATER, XCODE_TEMPFAIL, "Try again later");
CFG_UNLOCK();
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;
case 'R':
msg_warn ("mlfi_eom: %s: rejected by dcc", priv->mlfi_id);
smfi_setreply (ctx, "550", XCODE_REJECT, "Message content rejected");
CFG_UNLOCK();
mlfi_cleanup (ctx, false);
return SMFIS_REJECT;
case 'S': /* XXX - dcc selective reject - not implemented yet */
case 'T': /* Temp failure by dcc */
default:
break;
}
}
#endif
if (priv->priv_addr.family == AF_INET) {
if (radix32tree_find (cfg->spamd_whitelist,
ntohl((uint32_t)priv->priv_addr.addr.sa4.sin_addr.s_addr)) != RADIX_NO_VALUE) {
ip_whitelisted = true;
}
}
/* Check spamd */
if (cfg->spamd_servers_num != 0 && !priv->has_whitelisted && priv->strict
&& !ip_whitelisted &&
(cfg->strict_auth || *priv->priv_user == '\0')) {
msg_debug ("mlfi_eom: %s: check spamd", priv->mlfi_id);
r = spamdscan (ctx, priv, cfg, &subject, 0);
/* Check on extra servers */
if (cfg->extra_spamd_servers_num != 0) {
msg_debug ("mlfi_eom: %s: check spamd", priv->mlfi_id);
er = spamdscan (ctx, priv, cfg, &subject, 1);
if (er < 0) {
msg_warn ("mlfi_eom: %s: extra_spamdscan() failed, %d", priv->mlfi_id, r);
}
else if (r != er) {
msg_warn ("mlfi_eom: spamd_extra_scan returned %d and normal scan returned %d", er, r);
if (cfg->spam_server && cfg->send_beanstalk_extra_diff) {
send_beanstalk_copy (priv, cfg->spam_server);
}
}
}
if (r < 0) {
msg_warn ("mlfi_eom: %s: spamdscan() failed, %d", priv->mlfi_id, r);
if (cfg->spamd_temp_fail) {
smfi_setreply (ctx, RCODE_LATER, XCODE_TEMPFAIL, "Temporary service failure.");
CFG_UNLOCK();
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;
}
}
else if (r != METRIC_ACTION_NOACTION) {
if (cfg->spam_server && cfg->send_beanstalk_spam) {
send_beanstalk_copy (priv, cfg->spam_server);
}
if (! cfg->spamd_soft_fail || r == METRIC_ACTION_REJECT) {
msg_info ("mlfi_eom: %s: rejecting spam", priv->mlfi_id);
format_spamd_reply (strres, sizeof (strres), cfg->spamd_reject_message, NULL);
smfi_setreply (ctx, RCODE_REJECT, XCODE_REJECT, strres);
CFG_UNLOCK();
mlfi_cleanup (ctx, false);
return SMFIS_REJECT;
}
else {
format_spamd_reply (strres, sizeof (strres), cfg->spamd_reject_message, NULL);
if (r >= METRIC_ACTION_GREYLIST && cfg->spamd_greylist) {
/* Perform greylisting */
CFG_UNLOCK();
if (!priv->authenticated && check_greylisting_ctx (ctx, priv) != SMFIS_CONTINUE) {
msg_info ("mlfi_eom: %s: greylisting message according to spamd action", priv->mlfi_id);
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;
}
CFG_RLOCK();
}
if (r == METRIC_ACTION_ADD_HEADER) {
msg_info ("mlfi_eom: %s: add spam header to message according to spamd action", priv->mlfi_id);
smfi_chgheader (ctx, cfg->spam_header, 1, cfg->spam_header_value);
}
else if (r == METRIC_ACTION_REWRITE_SUBJECT) {
msg_info ("mlfi_eom: %s: rewriting spam subject and adding spam header", priv->mlfi_id);
smfi_chgheader (ctx, cfg->spam_header, 1, cfg->spam_header_value);
if (subject == NULL) {
/* Use own settings */
if (priv->priv_subject) {
smfi_chgheader (ctx, "Subject", 1, priv->priv_subject);
}
else {
smfi_chgheader (ctx, "Subject", 1, SPAM_SUBJECT);
}
}
else {
smfi_chgheader (ctx, "Subject", 1, subject);
free (subject);
}
}
}
}
}
/* Write message to beanstalk */
if (cfg->beanstalk_servers_num > 0 && cfg->send_beanstalk_headers) {
send_beanstalk (priv);
}
/* Maybe write its copy */
if (cfg->copy_server && cfg->send_beanstalk_copy) {
prob_cur = cfg->beanstalk_copy_prob;
/* Normalize */
prob_max = 100;
while (prob_cur < 1.0) {
prob_max *= 10;
prob_cur *= 10;
}
if (rand () % prob_max <= prob_cur) {
send_beanstalk_copy (priv, cfg->copy_server);
}
}
/* Check clamav */
if (cfg->clamav_servers_num != 0) {
msg_debug ("mlfi_eom: %s: check clamav", priv->mlfi_id);
r = check_clamscan (priv->file, strres, sizeof (strres));
if (r < 0) {
msg_warn ("mlfi_eom: %s: check_clamscan() failed, %d", priv->mlfi_id, r);
CFG_UNLOCK();
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;
}
if (*strres) {
msg_warn ("mlfi_eom: %s: rejecting virus %s", priv->mlfi_id, strres);
snprintf (buf, sizeof (buf), "Infected: %s", strres);
smfi_setreply (ctx, RCODE_REJECT, XCODE_REJECT, buf);
CFG_UNLOCK();
mlfi_cleanup (ctx, false);
return SMFIS_REJECT;
}
}
/* Update rate limits for message */
msg_debug ("mlfi_eom: %s: updating rate limits", priv->mlfi_id);
#if 0
char rcptbuf[8192];
int rr = 0;
for (rcpt = priv->rcpts.lh_first; rcpt != NULL; rcpt = rcpt->r_list.le_next) {
rate_check (priv, cfg, rcpt->r_addr, 1);
if (rcpt->r_list.le_next) {
rr += snprintf (rcptbuf + rr, sizeof (rcptbuf) - rr, "%s, ", rcpt->r_addr);
}
else {
rr += snprintf (rcptbuf + rr, sizeof (rcptbuf) - rr, "%s", rcpt->r_addr);
}
}
smfi_addheader (ctx, "X-Rcpt-To", rcptbuf);
#else
for (rcpt = priv->rcpts.lh_first; rcpt != NULL; rcpt = rcpt->r_list.le_next) {
rate_check (priv, cfg, rcpt->r_addr, 1);
}
#endif
#ifdef WITH_DKIM
/* Add dkim signature */
char *hdr;
size_t len;
if (priv->dkim) {
if (!cfg->dkim_fold_header) {
/* Disable header folding */
dkim_set_margin (priv->dkim, 0);
}
r = dkim_eom (priv->dkim, NULL);
if (r == DKIM_STAT_OK) {
r = dkim_getsighdr_d (priv->dkim, 0, (u_char **)&hdr, &len);
if (r == DKIM_STAT_OK) {
msg_info ("mlfi_eom: %s: d=%s, s=%s, added DKIM signature",
priv->mlfi_id,
dkim_getdomain (priv->dkim),
priv->dkim_domain->selector);
smfi_addheader (ctx, DKIM_SIGNHEADER, dkim_stripcr (hdr));
}
else {
msg_info ("mlfi_eom: %s: d=%s, s=%s, sign failed: %s",
priv->mlfi_id,
dkim_getdomain (priv->dkim),
priv->dkim_domain->selector,
dkim_geterror (priv->dkim));
}
}
else {
msg_info ("mlfi_eom: %s: d=%s, s=%s, dkim_eom failed: %s",
priv->mlfi_id,
dkim_getdomain (priv->dkim),
priv->dkim_domain->selector,
dkim_geterror (priv->dkim));
}
}
#endif
CFG_UNLOCK();
return mlfi_cleanup (ctx, true);
}
static sfsistat
mlfi_close(SMFICTX * ctx)
{
struct mlfi_priv *priv;
if ((priv = (struct mlfi_priv *) smfi_getpriv (ctx)) == NULL) {
msg_err ("Internal error: smfi_getpriv() returns NULL");
return SMFIS_TEMPFAIL;
}
msg_debug ("mlfi_close: cleanup");
mlfi_cleanup (ctx, true);
free(priv);
smfi_setpriv(ctx, NULL);
return SMFIS_ACCEPT;
}
static sfsistat
mlfi_abort(SMFICTX * ctx)
{
return mlfi_cleanup(ctx, false);
}
static sfsistat
mlfi_cleanup(SMFICTX * ctx, bool ok)
{
sfsistat rstat = SMFIS_CONTINUE;
struct mlfi_priv *priv;
struct rcpt *rcpt, *next;
if ((priv = (struct mlfi_priv *) smfi_getpriv (ctx)) == NULL) {
msg_err ("Internal error: smfi_getpriv() returns NULL");
return SMFIS_TEMPFAIL;
}
msg_debug ("mlfi_cleanup: cleanup");
if (priv->fileh) {
if (fclose (priv->fileh) != 0) {
msg_err ("mlfi_close: %s: close failed: %s", priv->mlfi_id,
strerror (errno));
}
priv->fileh = NULL;
}
if (*priv->file) {
unlink (priv->file);
priv->file[0] = '\0';
}
/* clean message specific data */
priv->strict = 1;
priv->mlfi_id[0] = '\0';
priv->reply_id[0] = '\0';
#ifdef WITH_DKIM
if (priv->dkim) {
dkim_free (priv->dkim);
}
priv->dkim = NULL;
#endif
if (priv->priv_subject != NULL) {
free (priv->priv_subject);
priv->priv_subject = NULL;
}
if (ok) {
/* If ok is not true do not clean SMTP data, just reject message */
priv->priv_from[0] = '\0';
priv->priv_user[0] = '\0';
priv->priv_rcptcount = 0;
rcpt = priv->rcpts.lh_first;
while (rcpt) {
next = rcpt->r_list.le_next;
free (rcpt);
rcpt = next;
}
LIST_INIT (&priv->rcpts);
}
/* return status */
return rstat;
}
static sfsistat
mlfi_body(SMFICTX * ctx, u_char * bodyp, size_t bodylen)
{
struct mlfi_priv *priv;
struct rule *act;
if ((priv = (struct mlfi_priv *) smfi_getpriv (ctx)) == NULL) {
msg_err ("Internal error: smfi_getpriv() returns NULL");
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;
}
if (!priv->fileh) {
if (create_temp_file (priv) == -1) {
msg_err ("mlfi_body: %s: cannot create temp file", priv->mlfi_id);
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;
}
}
if (fwrite (bodyp, bodylen, 1, priv->fileh) != 1) {
msg_warn ("mlfi_body: %s: file write error: %s", priv->mlfi_id,
strerror (errno));
mlfi_cleanup (ctx, false);
return SMFIS_TEMPFAIL;;
}
/* Check body with regexp */
priv->priv_cur_body.value = (char *)bodyp;
priv->priv_cur_body.len = bodylen;
CFG_RLOCK();
act = regexp_check (cfg, priv, STAGE_BODY);
if (act != NULL) {
priv->matched_rules[STAGE_BODY] = act;
}
/* continue processing */
#ifdef WITH_DKIM
int r;
if (priv->dkim) {
r = dkim_body (priv->dkim, bodyp, bodylen);
if (r != DKIM_STAT_OK) {
msg_info ("mlfi_body: %s: dkim_body failed: %s", priv->mlfi_id,
dkim_geterror (priv->dkim));
}
}
#endif
CFG_UNLOCK();
return SMFIS_CONTINUE;
}
/*****************************************************************************/
/*
* check_clamscan() return values: 0 - scanned (or not scanned due to
* filesize limit) -1 - retry limit exceeded -2 - unexpected error,
* e.g. unexpected reply from server (suppose scanned message killed
* clamd...)
*/
static int
check_clamscan(const char *file, char *strres, size_t strres_len)
{
int r = -2;
*strres = '\0';
/* scan using libclamc clamscan() */
r = clamscan (file, cfg, strres, strres_len);
/* reset virusname for non-viruses */
if (*strres && (!strcmp (strres, "Suspected.Zip") || !strcmp (strres, "Oversized.Zip"))) {
*strres = '\0';
}
return r;
}
#ifdef HAVE_DCC
static int
check_dcc (const struct mlfi_priv *priv)
{
DCC_EMSG emsg;
char *homedir = 0;
char opts[] = "";
DCC_SOCKU sup;
DCCIF_RCPT *rcpts = NULL, rcpt;
int dccres;
int dccfd, dccofd = -1;
if (!*priv->file) {
return 0;
}
dccfd = open (priv->file, O_RDONLY);
if (dccfd == -1) {
msg_warn ("check_dcc: %s: dcc data file open(): %s", priv->mlfi_id, strerror (errno));
return 0;
}
dcc_mk_su (&sup, priv->priv_addr.family, &priv->priv_addr.addr.sa, 0);
rcpt.next = rcpts;
rcpt.addr = priv->rcpts.lh_first->r_addr;
rcpt.user = "";
rcpt.ok = '?';
rcpts = &rcpt;
dccres = dccif (emsg, /*out body fd*/dccofd, /*out_body*/0,
opts, &sup, priv->priv_hostname, priv->priv_helo,
(priv->priv_from[0] == 0) ? "<>" : priv->priv_from,
rcpts, dccfd, /*in_body*/0, homedir);
return dccres;
}
#endif
/*
* vi:ts=4
*/