mirror of https://github.com/Cisco-Talos/clamav
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
782 lines
26 KiB
782 lines
26 KiB
/*
|
|
* Copyright (C) 2013-2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
|
* Copyright (C) 2008-2013 Sourcefire, Inc.
|
|
*
|
|
* Author: aCaB <acab@clamav.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*/
|
|
|
|
#if HAVE_CONFIG_H
|
|
#include "clamav-config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
|
|
#include <libmilter/mfapi.h>
|
|
|
|
// libclamav
|
|
#include "clamav.h"
|
|
#include "others.h"
|
|
|
|
// common
|
|
#include "optparser.h"
|
|
#include "output.h"
|
|
|
|
#include "connpool.h"
|
|
#include "netcode.h"
|
|
#include "allow_list.h"
|
|
#include "clamfi.h"
|
|
|
|
#if __GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7)
|
|
#define _UNUSED_ __attribute__((__unused__))
|
|
#else
|
|
#define _UNUSED_
|
|
#endif
|
|
|
|
uint64_t maxfilesize;
|
|
|
|
static sfsistat FailAction;
|
|
static sfsistat (*CleanAction)(SMFICTX *ctx);
|
|
static sfsistat (*InfectedAction)(SMFICTX *ctx);
|
|
static char *rejectfmt = NULL;
|
|
|
|
int addxvirus = 0; /* 0 - don't add | 1 - replace | 2 - add */
|
|
char xvirushdr[300];
|
|
char *viraction = NULL;
|
|
int multircpt = 1;
|
|
|
|
#define LOGINF_NONE 0
|
|
#define LOGINF_BASIC 1
|
|
#define LOGINF_FULL 2
|
|
#define LOGCLN_BASIC 4
|
|
#define LOGCLN_FULL 8
|
|
int loginfected;
|
|
|
|
#define CLAMFIBUFSZ 1424
|
|
static const char *HDR_UNAVAIL = "UNKNOWN";
|
|
static pthread_mutex_t virusaction_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
struct CLAMFI {
|
|
const char *virusname;
|
|
char *msg_subj;
|
|
char *msg_date;
|
|
char *msg_id;
|
|
char **recipients;
|
|
int local;
|
|
int main;
|
|
int alt;
|
|
unsigned int totsz;
|
|
unsigned int bufsz;
|
|
unsigned int all_allowed;
|
|
unsigned int gotbody;
|
|
unsigned int scanned_count;
|
|
unsigned int status_count;
|
|
unsigned int nrecipients;
|
|
uint32_t sendme;
|
|
char buffer[CLAMFIBUFSZ];
|
|
};
|
|
|
|
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(LOGG_WARNING, "Failed to remove existing X-Virus-Scanned header\n");
|
|
while (status)
|
|
if (smfi_chgheader(ctx, (char *)"X-Virus-Status", status--, NULL) != MI_SUCCESS)
|
|
logg(LOGG_WARNING, "Failed to remove existing X-Virus-Status header\n");
|
|
if (smfi_addheader(ctx, (char *)"X-Virus-Scanned", xvirushdr) != MI_SUCCESS)
|
|
logg(LOGG_WARNING, "Failed to add X-Virus-Scanned header\n");
|
|
if (smfi_addheader(ctx, (char *)"X-Virus-Status", st) != MI_SUCCESS)
|
|
logg(LOGG_WARNING, "Failed to add X-Virus-Status header\n");
|
|
} else { /* Add */
|
|
if (smfi_insheader(ctx, 1, (char *)"X-Virus-Scanned", xvirushdr) != MI_SUCCESS)
|
|
logg(LOGG_WARNING, "Failed to insert X-Virus-Scanned header\n");
|
|
if (smfi_insheader(ctx, 1, (char *)"X-Virus-Status", st) != MI_SUCCESS)
|
|
logg(LOGG_WARNING, "Failed to insert X-Virus-Status header\n");
|
|
}
|
|
}
|
|
|
|
enum CFWHAT {
|
|
CF_NONE, /* 0 */
|
|
CF_MAIN, /* 1 */
|
|
CF_ALT, /* 2 */
|
|
CF_BOTH, /* 3 */
|
|
CF_ANY /* 4 */
|
|
};
|
|
|
|
static const char *makesanehdr(char *hdr)
|
|
{
|
|
char *ret = hdr;
|
|
if (!hdr) return HDR_UNAVAIL;
|
|
while (*hdr) {
|
|
if (*hdr == '\'' || *hdr == '\t' || *hdr == '\r' || *hdr == '\n' || !isprint(*hdr))
|
|
*hdr = ' ';
|
|
hdr++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void nullify(SMFICTX *ctx, struct CLAMFI *cf, enum CFWHAT closewhat)
|
|
{
|
|
if (closewhat & CF_MAIN || ((closewhat & CF_ANY) && cf->main >= 0))
|
|
close(cf->main);
|
|
if (closewhat & CF_ALT || ((closewhat & CF_ANY) && cf->alt >= 0))
|
|
close(cf->alt);
|
|
if (cf->msg_subj) free(cf->msg_subj);
|
|
if (cf->msg_date) free(cf->msg_date);
|
|
if (cf->msg_id) free(cf->msg_id);
|
|
if (multircpt && cf->nrecipients) {
|
|
while (cf->nrecipients) {
|
|
cf->nrecipients--;
|
|
free(cf->recipients[cf->nrecipients]);
|
|
}
|
|
free(cf->recipients);
|
|
}
|
|
smfi_setpriv(ctx, NULL);
|
|
}
|
|
|
|
static sfsistat sendchunk(struct CLAMFI *cf, unsigned char *bodyp, size_t len, SMFICTX *ctx)
|
|
{
|
|
if (cf->totsz >= maxfilesize || len == 0)
|
|
return SMFIS_CONTINUE;
|
|
|
|
if (!cf->totsz) {
|
|
sfsistat ret;
|
|
if (nc_connect_rand(&cf->main, &cf->alt, &cf->local)) {
|
|
logg(LOGG_ERROR, "Failed to initiate streaming/fdpassing\n");
|
|
nullify(ctx, cf, CF_NONE);
|
|
return FailAction;
|
|
}
|
|
cf->totsz = 1; /* do not infloop */
|
|
if ((ret = sendchunk(cf, (unsigned char *)"From clamav-milter\n", 19, ctx)) != SMFIS_CONTINUE)
|
|
return ret;
|
|
cf->totsz -= 1;
|
|
}
|
|
|
|
if (cf->totsz + len > maxfilesize)
|
|
len = maxfilesize - cf->totsz;
|
|
|
|
cf->totsz += len;
|
|
if (cf->local) {
|
|
while (len) {
|
|
int n = write(cf->alt, bodyp, len);
|
|
|
|
if (n == -1) {
|
|
logg(LOGG_ERROR, "Failed to write temporary file\n");
|
|
nullify(ctx, cf, CF_BOTH);
|
|
return FailAction;
|
|
}
|
|
len -= n;
|
|
bodyp += n;
|
|
}
|
|
} else {
|
|
int sendfailed = 0;
|
|
|
|
if (len < CLAMFIBUFSZ - cf->bufsz) {
|
|
memcpy(&cf->buffer[cf->bufsz], bodyp, len);
|
|
cf->bufsz += len;
|
|
} else if (len < CLAMFIBUFSZ) {
|
|
memcpy(&cf->buffer[cf->bufsz], bodyp, CLAMFIBUFSZ - cf->bufsz);
|
|
cf->sendme = htonl(CLAMFIBUFSZ);
|
|
sendfailed = nc_send(cf->main, &cf->sendme, CLAMFIBUFSZ + 4);
|
|
len -= (CLAMFIBUFSZ - cf->bufsz);
|
|
memcpy(cf->buffer, &bodyp[CLAMFIBUFSZ - cf->bufsz], len);
|
|
cf->bufsz = len;
|
|
} else {
|
|
uint32_t sendmetoo = htonl(len);
|
|
cf->sendme = htonl(cf->bufsz);
|
|
if ((cf->bufsz && nc_send(cf->main, &cf->sendme, cf->bufsz + 4)) || nc_send(cf->main, &sendmetoo, 4) || nc_send(cf->main, bodyp, len))
|
|
sendfailed = 1;
|
|
cf->bufsz = 0;
|
|
}
|
|
if (sendfailed) {
|
|
logg(LOGG_ERROR, "Streaming failed\n");
|
|
nullify(ctx, cf, CF_NONE);
|
|
return FailAction;
|
|
}
|
|
}
|
|
return SMFIS_CONTINUE;
|
|
}
|
|
|
|
sfsistat clamfi_header(SMFICTX *ctx, char *headerf, char *headerv)
|
|
{
|
|
struct CLAMFI *cf;
|
|
sfsistat ret;
|
|
|
|
if (!(cf = (struct CLAMFI *)smfi_getpriv(ctx)))
|
|
return SMFIS_CONTINUE; /* whatever */
|
|
|
|
if (!cf->totsz && cf->all_allowed) {
|
|
logg(LOGG_DEBUG, "Skipping scan (all destinations allowed)\n");
|
|
nullify(ctx, cf, CF_NONE);
|
|
free(cf);
|
|
return SMFIS_ACCEPT;
|
|
}
|
|
|
|
if (!headerf) return SMFIS_CONTINUE; /* just in case */
|
|
|
|
if ((loginfected & (LOGINF_FULL | LOGCLN_FULL)) || viraction) {
|
|
if (!cf->msg_subj && !strcasecmp(headerf, "Subject"))
|
|
cf->msg_subj = strdup(headerv ? headerv : "");
|
|
if (!cf->msg_date && !strcasecmp(headerf, "Date"))
|
|
cf->msg_date = strdup(headerv ? headerv : "");
|
|
if (!cf->msg_id && !strcasecmp(headerf, "Message-ID"))
|
|
cf->msg_id = strdup(headerv ? headerv : "");
|
|
}
|
|
|
|
if (addxvirus == 1) {
|
|
if (!strcasecmp(headerf, "X-Virus-Scanned")) cf->scanned_count++;
|
|
if (!strcasecmp(headerf, "X-Virus-Status")) cf->status_count++;
|
|
}
|
|
|
|
if ((ret = sendchunk(cf, (unsigned char *)headerf, strlen(headerf), ctx)) != SMFIS_CONTINUE) {
|
|
free(cf);
|
|
return ret;
|
|
}
|
|
if ((ret = sendchunk(cf, (unsigned char *)": ", 2, ctx)) != SMFIS_CONTINUE) {
|
|
free(cf);
|
|
return ret;
|
|
}
|
|
if (headerv && (ret = sendchunk(cf, (unsigned char *)headerv, strlen(headerv), ctx)) != SMFIS_CONTINUE) {
|
|
free(cf);
|
|
return ret;
|
|
}
|
|
ret = sendchunk(cf, (unsigned char *)"\r\n", 2, ctx);
|
|
if (ret != SMFIS_CONTINUE)
|
|
free(cf);
|
|
return ret;
|
|
}
|
|
|
|
sfsistat clamfi_body(SMFICTX *ctx, unsigned char *bodyp, size_t len)
|
|
{
|
|
struct CLAMFI *cf;
|
|
sfsistat ret;
|
|
|
|
if (!(cf = (struct CLAMFI *)smfi_getpriv(ctx)))
|
|
return SMFIS_CONTINUE; /* whatever */
|
|
|
|
if (!cf->gotbody) {
|
|
ret = sendchunk(cf, (unsigned char *)"\r\n", 2, ctx);
|
|
if (ret != SMFIS_CONTINUE) {
|
|
free(cf);
|
|
return ret;
|
|
}
|
|
cf->gotbody = 1;
|
|
}
|
|
|
|
ret = sendchunk(cf, bodyp, len, ctx);
|
|
if (ret != SMFIS_CONTINUE)
|
|
free(cf);
|
|
return ret;
|
|
}
|
|
|
|
sfsistat clamfi_abort(SMFICTX *ctx)
|
|
{
|
|
struct CLAMFI *cf;
|
|
|
|
if ((cf = (struct CLAMFI *)smfi_getpriv(ctx))) {
|
|
nullify(ctx, cf, CF_ANY);
|
|
free(cf);
|
|
}
|
|
return SMFIS_CONTINUE;
|
|
}
|
|
|
|
sfsistat clamfi_eom(SMFICTX *ctx)
|
|
{
|
|
struct CLAMFI *cf;
|
|
char *reply;
|
|
int len, ret;
|
|
unsigned int crcpt;
|
|
|
|
if (!(cf = (struct CLAMFI *)smfi_getpriv(ctx)))
|
|
return SMFIS_CONTINUE; /* whatever */
|
|
|
|
if (!cf->totsz) {
|
|
/* got no headers and no body */
|
|
logg(LOGG_DEBUG, "Not scanning an empty message\n");
|
|
ret = CleanAction(ctx);
|
|
nullify(ctx, cf, CF_NONE);
|
|
free(cf);
|
|
return ret;
|
|
}
|
|
|
|
if (cf->local) {
|
|
lseek(cf->alt, 0, SEEK_SET);
|
|
|
|
if (nc_sendmsg(cf->main, cf->alt) == -1) {
|
|
logg(LOGG_ERROR, "FD send failed\n");
|
|
nullify(ctx, cf, CF_ALT);
|
|
free(cf);
|
|
return FailAction;
|
|
}
|
|
} else {
|
|
uint32_t sendmetoo = 0;
|
|
cf->sendme = htonl(cf->bufsz);
|
|
if ((cf->bufsz && nc_send(cf->main, &cf->sendme, cf->bufsz + 4)) || nc_send(cf->main, &sendmetoo, 4)) {
|
|
logg(LOGG_ERROR, "Failed to flush STREAM\n");
|
|
nullify(ctx, cf, CF_NONE);
|
|
free(cf);
|
|
return FailAction;
|
|
}
|
|
}
|
|
|
|
reply = nc_recv(cf->main);
|
|
|
|
if (cf->local)
|
|
close(cf->alt);
|
|
|
|
cf->alt = -1;
|
|
|
|
if (!reply) {
|
|
logg(LOGG_ERROR, "No reply from clamd\n");
|
|
nullify(ctx, cf, CF_NONE);
|
|
free(cf);
|
|
return FailAction;
|
|
}
|
|
|
|
len = strlen(reply);
|
|
if (len > 5 && !strcmp(reply + len - 5, ": OK\n")) {
|
|
if (addxvirus) add_x_header(ctx, "Clean", cf->scanned_count, cf->status_count);
|
|
if (loginfected & LOGCLN_FULL) {
|
|
const char *id = smfi_getsymval(ctx, "{i}");
|
|
const char *from = smfi_getsymval(ctx, "{mail_addr}");
|
|
const char *msg_subj = makesanehdr(cf->msg_subj);
|
|
const char *msg_date = makesanehdr(cf->msg_date);
|
|
const char *msg_id = makesanehdr(cf->msg_id);
|
|
if (multircpt && cf->nrecipients) {
|
|
for (crcpt = 0; crcpt < cf->nrecipients; crcpt++)
|
|
logg(LOGG_INFO, "Clean message %s from <%s> to <%s> with subject '%s' message-id '%s' date '%s'\n", id, from, cf->recipients[crcpt], msg_subj, msg_id, msg_date);
|
|
} else {
|
|
const char *to = smfi_getsymval(ctx, "{rcpt_addr}");
|
|
logg(LOGG_INFO, "Clean message %s from <%s> to <%s> with subject '%s' message-id '%s' date '%s'\n", id, from, to ? to : HDR_UNAVAIL, msg_subj, msg_id, msg_date);
|
|
}
|
|
} else if (loginfected & LOGCLN_BASIC) {
|
|
const char *from = smfi_getsymval(ctx, "{mail_addr}");
|
|
if (multircpt && cf->nrecipients) {
|
|
for (crcpt = 0; crcpt < cf->nrecipients; crcpt++)
|
|
logg(LOGG_INFO, "Clean message from <%s> to <%s>\n", from, cf->recipients[crcpt]);
|
|
} else {
|
|
const char *to = smfi_getsymval(ctx, "{rcpt_addr}");
|
|
logg(LOGG_INFO, "Clean message from <%s> to <%s>\n", from, to ? to : HDR_UNAVAIL);
|
|
}
|
|
}
|
|
ret = CleanAction(ctx);
|
|
} else if (len > 7 && !strcmp(reply + len - 7, " FOUND\n")) {
|
|
cf->virusname = NULL;
|
|
if ((loginfected & (LOGINF_BASIC | LOGINF_FULL)) || addxvirus || rejectfmt || viraction) {
|
|
char *vir;
|
|
|
|
reply[len - 7] = '\0';
|
|
vir = strrchr(reply, ' ');
|
|
if (vir) {
|
|
unsigned int have_multi = (multircpt != 0 && cf->nrecipients);
|
|
unsigned int lst_rcpt = (have_multi * (cf->nrecipients - 1)) + 1;
|
|
vir++;
|
|
|
|
if (rejectfmt)
|
|
cf->virusname = vir;
|
|
|
|
if (addxvirus) {
|
|
char msg[255];
|
|
snprintf(msg, sizeof(msg), "Infected (%s)", vir);
|
|
msg[sizeof(msg) - 1] = '\0';
|
|
add_x_header(ctx, msg, cf->scanned_count, cf->status_count);
|
|
}
|
|
|
|
for (crcpt = 0; crcpt < lst_rcpt; crcpt++) {
|
|
if (loginfected || viraction) {
|
|
const char *from = smfi_getsymval(ctx, "{mail_addr}");
|
|
const char *to = have_multi ? cf->recipients[crcpt] : smfi_getsymval(ctx, "{rcpt_addr}");
|
|
|
|
if (!from) from = HDR_UNAVAIL;
|
|
if (!to) to = HDR_UNAVAIL;
|
|
if ((loginfected & LOGINF_FULL) || viraction) {
|
|
const char *id = smfi_getsymval(ctx, "{i}");
|
|
const char *msg_subj = makesanehdr(cf->msg_subj);
|
|
const char *msg_date = makesanehdr(cf->msg_date);
|
|
const char *msg_id = makesanehdr(cf->msg_id);
|
|
|
|
if (!id) id = HDR_UNAVAIL;
|
|
|
|
if (loginfected & LOGINF_FULL)
|
|
logg(LOGG_INFO, "Message %s from <%s> to <%s> with subject '%s' message-id '%s' date '%s' infected by %s\n", id, from, to, msg_subj, msg_id, msg_date, vir);
|
|
|
|
if (viraction) {
|
|
char er[256];
|
|
char *e_id = strdup(id);
|
|
char *e_from = strdup(from);
|
|
char *e_to = strdup(to);
|
|
char *e_msg_subj = strdup(msg_subj);
|
|
char *e_msg_date = strdup(msg_date);
|
|
char *e_msg_id = strdup(msg_id);
|
|
pid_t pid;
|
|
|
|
logg(LOGG_DEBUG, "VirusEvent: about to execute '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s'\n", viraction, vir, e_id, e_from, e_to, e_msg_subj, e_msg_id, e_msg_date);
|
|
|
|
pthread_mutex_lock(&virusaction_lock);
|
|
pid = fork();
|
|
if (!pid) {
|
|
char *args[9]; /* avoid element is not computable at load time warns */
|
|
args[0] = viraction;
|
|
args[1] = vir;
|
|
args[2] = e_id;
|
|
args[3] = e_from;
|
|
args[4] = e_to;
|
|
args[5] = e_msg_subj;
|
|
args[6] = e_msg_id;
|
|
args[7] = e_msg_date;
|
|
args[8] = NULL;
|
|
exit(execvp(viraction, args));
|
|
} else if (pid > 0) {
|
|
int wret;
|
|
pthread_mutex_unlock(&virusaction_lock);
|
|
while ((wret = waitpid(pid, &ret, 0)) == -1 && errno == EINTR) continue;
|
|
if (wret < 0)
|
|
logg(LOGG_ERROR, "VirusEvent: waitpid() failed: %s\n", cli_strerror(errno, er, sizeof(er)));
|
|
else {
|
|
if (WIFEXITED(ret))
|
|
logg(LOGG_DEBUG, "VirusEvent: child exited with code %d\n", WEXITSTATUS(ret));
|
|
else if (WIFSIGNALED(ret))
|
|
logg(LOGG_DEBUG, "VirusEvent: child killed by signal %d\n", WTERMSIG(ret));
|
|
else
|
|
logg(LOGG_DEBUG, "VirusEvent: child lost\n");
|
|
}
|
|
} else {
|
|
logg(LOGG_ERROR, "VirusEvent: fork failed: %s\n", cli_strerror(errno, er, sizeof(er)));
|
|
}
|
|
free(e_id);
|
|
free(e_from);
|
|
free(e_to);
|
|
free(e_msg_subj);
|
|
free(e_msg_date);
|
|
free(e_msg_id);
|
|
}
|
|
}
|
|
if (loginfected & LOGINF_BASIC)
|
|
logg(LOGG_INFO, "Message from <%s> to <%s> infected by %s\n", from, to, vir);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ret = InfectedAction(ctx);
|
|
} else {
|
|
logg(LOGG_ERROR, "Unknown reply from clamd\n");
|
|
ret = FailAction;
|
|
}
|
|
|
|
nullify(ctx, cf, CF_MAIN);
|
|
free(cf);
|
|
free(reply);
|
|
return ret;
|
|
}
|
|
|
|
sfsistat clamfi_connect(_UNUSED_ SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
|
|
{
|
|
while (1) {
|
|
/* Postfix doesn't seem to honor passing a NULL hostaddr and hostname
|
|
set to "localhost" for non-smtp messages (they still appear as SMTP
|
|
messages from 127.0.0.1). Here's a small workaround. */
|
|
if (hostaddr) {
|
|
if (islocalnet_sock(hostaddr)) {
|
|
logg(LOGG_DEBUG, "Skipping scan for %s (in LocalNet)\n", hostname);
|
|
return SMFIS_ACCEPT;
|
|
}
|
|
break;
|
|
}
|
|
if (!strcasecmp(hostname, "localhost"))
|
|
hostname = NULL;
|
|
if (islocalnet_name(hostname)) {
|
|
logg(LOGG_DEBUG, "Skipping scan for %s (in LocalNet)\n", hostname ? hostname : "local");
|
|
return SMFIS_ACCEPT;
|
|
}
|
|
break;
|
|
}
|
|
return SMFIS_CONTINUE;
|
|
}
|
|
|
|
static int parse_action(char *action)
|
|
{
|
|
if (!strcasecmp(action, "Accept"))
|
|
return 0;
|
|
if (!strcasecmp(action, "Defer"))
|
|
return 1;
|
|
if (!strcasecmp(action, "Reject"))
|
|
return 2;
|
|
if (!strcasecmp(action, "Blackhole"))
|
|
return 3;
|
|
if (!strcasecmp(action, "Quarantine"))
|
|
return 4;
|
|
logg(LOGG_ERROR, "Unknown action %s\n", action);
|
|
return -1;
|
|
}
|
|
|
|
static sfsistat action_accept(_UNUSED_ SMFICTX *ctx)
|
|
{
|
|
return SMFIS_ACCEPT;
|
|
}
|
|
static sfsistat action_defer(_UNUSED_ SMFICTX *ctx)
|
|
{
|
|
return SMFIS_TEMPFAIL;
|
|
}
|
|
static sfsistat action_reject(_UNUSED_ SMFICTX *ctx)
|
|
{
|
|
return SMFIS_REJECT;
|
|
}
|
|
static sfsistat action_blackhole(_UNUSED_ SMFICTX *ctx)
|
|
{
|
|
return SMFIS_DISCARD;
|
|
}
|
|
static sfsistat action_quarantine(SMFICTX *ctx)
|
|
{
|
|
if (smfi_quarantine(ctx, "quarantined by clamav-milter") != MI_SUCCESS) {
|
|
logg(LOGG_WARNING, "Failed to quarantine message\n");
|
|
return SMFIS_TEMPFAIL;
|
|
}
|
|
return SMFIS_ACCEPT;
|
|
}
|
|
|
|
static sfsistat action_reject_msg(SMFICTX *ctx)
|
|
{
|
|
struct CLAMFI *cf;
|
|
char buf[1024];
|
|
|
|
if (!rejectfmt || !(cf = (struct CLAMFI *)smfi_getpriv(ctx)))
|
|
return SMFIS_REJECT;
|
|
|
|
snprintf(buf, sizeof(buf), rejectfmt, cf->virusname);
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
smfi_setreply(ctx, "550", "5.7.1", buf);
|
|
return SMFIS_REJECT;
|
|
}
|
|
|
|
int init_actions(struct optstruct *opts)
|
|
{
|
|
const struct optstruct *opt;
|
|
|
|
if (!(opt = optget(opts, "LogInfected"))->enabled || !strcasecmp(opt->strarg, "Off"))
|
|
loginfected = LOGINF_NONE;
|
|
else if (!strcasecmp(opt->strarg, "Basic"))
|
|
loginfected = LOGINF_BASIC;
|
|
else if (!strcasecmp(opt->strarg, "Full"))
|
|
loginfected = LOGINF_FULL;
|
|
else {
|
|
logg(LOGG_ERROR, "Invalid setting %s for option LogInfected\n", opt->strarg);
|
|
return 1;
|
|
}
|
|
|
|
if ((opt = optget(opts, "LogClean"))->enabled) {
|
|
if (!strcasecmp(opt->strarg, "Basic"))
|
|
loginfected |= LOGCLN_BASIC;
|
|
else if (!strcasecmp(opt->strarg, "Full"))
|
|
loginfected |= LOGCLN_FULL;
|
|
else if (strcasecmp(opt->strarg, "Off")) {
|
|
logg(LOGG_ERROR, "Invalid setting %s for option LogClean\n", opt->strarg);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if ((opt = optget(opts, "VirusAction"))->enabled)
|
|
viraction = strdup(opt->strarg);
|
|
|
|
if ((opt = optget(opts, "OnFail"))->enabled) {
|
|
switch (parse_action(opt->strarg)) {
|
|
case 0:
|
|
FailAction = SMFIS_ACCEPT;
|
|
break;
|
|
case 1:
|
|
FailAction = SMFIS_TEMPFAIL;
|
|
break;
|
|
case 2:
|
|
FailAction = SMFIS_REJECT;
|
|
break;
|
|
default:
|
|
logg(LOGG_ERROR, "Invalid action %s for option OnFail\n", opt->strarg);
|
|
return 1;
|
|
}
|
|
} else
|
|
FailAction = SMFIS_TEMPFAIL;
|
|
|
|
if ((opt = optget(opts, "OnClean"))->enabled) {
|
|
switch (parse_action(opt->strarg)) {
|
|
case 0:
|
|
CleanAction = action_accept;
|
|
break;
|
|
case 1:
|
|
CleanAction = action_defer;
|
|
break;
|
|
case 2:
|
|
CleanAction = action_reject;
|
|
break;
|
|
case 3:
|
|
CleanAction = action_blackhole;
|
|
break;
|
|
case 4:
|
|
CleanAction = action_quarantine;
|
|
break;
|
|
default:
|
|
logg(LOGG_ERROR, "Invalid action %s for option OnClean\n", opt->strarg);
|
|
return 1;
|
|
}
|
|
} else
|
|
CleanAction = action_accept;
|
|
|
|
if ((opt = optget(opts, "OnInfected"))->enabled) {
|
|
switch (parse_action(opt->strarg)) {
|
|
case 0:
|
|
InfectedAction = action_accept;
|
|
break;
|
|
case 1:
|
|
InfectedAction = action_defer;
|
|
break;
|
|
case 3:
|
|
InfectedAction = action_blackhole;
|
|
break;
|
|
case 4:
|
|
InfectedAction = action_quarantine;
|
|
break;
|
|
case 2:
|
|
InfectedAction = action_reject_msg;
|
|
if ((opt = optget(opts, "RejectMsg"))->enabled) {
|
|
const char *src = opt->strarg;
|
|
char *dst, c;
|
|
int gotpctv = 0;
|
|
|
|
rejectfmt = dst = malloc(strlen(src) * 4 + 1);
|
|
if (!dst) {
|
|
logg(LOGG_ERROR, "Failed to allocate memory for RejectMsg\n");
|
|
return 1;
|
|
}
|
|
while ((c = *src++)) {
|
|
if (!isprint(c)) {
|
|
logg(LOGG_ERROR, "RejectMsg contains non printable characters\n");
|
|
free(rejectfmt);
|
|
return 1;
|
|
}
|
|
*dst++ = c;
|
|
if (c == '%') {
|
|
if (*src == 'v') {
|
|
if (gotpctv) {
|
|
logg(LOGG_ERROR, "%%v may appear at most once in RejectMsg\n");
|
|
free(rejectfmt);
|
|
return 1;
|
|
}
|
|
gotpctv |= 1;
|
|
src++;
|
|
*dst++ = 's';
|
|
} else {
|
|
dst[0] = dst[1] = dst[2] = '%';
|
|
dst += 3;
|
|
}
|
|
}
|
|
}
|
|
*dst = '\0';
|
|
}
|
|
break;
|
|
default:
|
|
logg(LOGG_ERROR, "Invalid action %s for option OnInfected\n", opt->strarg);
|
|
return 1;
|
|
}
|
|
} else
|
|
InfectedAction = action_quarantine;
|
|
return 0;
|
|
}
|
|
|
|
sfsistat clamfi_envfrom(SMFICTX *ctx, char **argv)
|
|
{
|
|
struct CLAMFI *cf;
|
|
const char *login = smfi_getsymval(ctx, "{auth_authen}");
|
|
|
|
if (login && smtpauthed(login)) {
|
|
logg(LOGG_DEBUG, "Skipping scan for authenticated user %s\n", login);
|
|
return SMFIS_ACCEPT;
|
|
}
|
|
|
|
if (allowed(argv[0], 1)) {
|
|
logg(LOGG_DEBUG, "Skipping scan for %s (allowed from)\n", argv[0]);
|
|
return SMFIS_ACCEPT;
|
|
}
|
|
|
|
if (!(cf = (struct CLAMFI *)malloc(sizeof(*cf)))) {
|
|
logg(LOGG_ERROR, "Failed to allocate CLAMFI struct\n");
|
|
return FailAction;
|
|
}
|
|
cf->totsz = 0;
|
|
cf->bufsz = 0;
|
|
cf->main = cf->alt = -1;
|
|
cf->all_allowed = 1;
|
|
cf->gotbody = 0;
|
|
cf->msg_subj = cf->msg_date = cf->msg_id = NULL;
|
|
if (multircpt) {
|
|
cf->recipients = NULL;
|
|
cf->nrecipients = 0;
|
|
}
|
|
if (addxvirus == 1) {
|
|
cf->scanned_count = 0;
|
|
cf->status_count = 0;
|
|
}
|
|
smfi_setpriv(ctx, (void *)cf);
|
|
|
|
return SMFIS_CONTINUE;
|
|
}
|
|
|
|
sfsistat clamfi_envrcpt(SMFICTX *ctx, char **argv)
|
|
{
|
|
struct CLAMFI *cf;
|
|
|
|
if (!(cf = (struct CLAMFI *)smfi_getpriv(ctx)))
|
|
return SMFIS_CONTINUE; /* whatever */
|
|
|
|
if (cf->all_allowed)
|
|
cf->all_allowed &= allowed(argv[0], 0);
|
|
|
|
if (multircpt) {
|
|
void *new_rcpt = realloc(cf->recipients, (cf->nrecipients + 1) * sizeof(*(cf->recipients)));
|
|
unsigned int rcpt_cnt;
|
|
if (!new_rcpt) {
|
|
logg(LOGG_ERROR, "Failed to allocate array for new recipient\n");
|
|
nullify(ctx, cf, CF_ANY);
|
|
free(cf);
|
|
return FailAction;
|
|
}
|
|
cf->recipients = new_rcpt;
|
|
rcpt_cnt = cf->nrecipients++;
|
|
if (!(cf->recipients[rcpt_cnt] = strdup(argv[0]))) {
|
|
logg(LOGG_ERROR, "Failed to allocate space for new recipient\n");
|
|
nullify(ctx, cf, CF_ANY);
|
|
free(cf);
|
|
return FailAction;
|
|
}
|
|
}
|
|
|
|
return SMFIS_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* Local Variables:
|
|
* mode: c
|
|
* c-basic-offset: 4
|
|
* tab-width: 8
|
|
* End:
|
|
* vim: set cindent smartindent autoindent softtabstop=4 shiftwidth=4 tabstop=8:
|
|
*/
|
|
|