static char const rcsid[] = "$Id: clamav-milter.c,v 1.4 2004/11/30 17:29:59 dasenbro Exp $";
#define CM_VERSION "0.80j"
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#include "defaults.h"
#include "cfgparser.h"
#include "../target.h"
#include "str.h"
#include "../libclamav/others.h"
#include "../libclamav/strrcpy.h"
#include "clamav.h"
#ifndef CL_DEBUG
#define NDEBUG
#endif
#include <stdio.h>
#include <sysexits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <stdarg.h>
#include <errno.h>
#include <libmilter/mfapi.h>
#include <pthread.h>
#include <sys/time.h>
#include <signal.h>
#include <regex.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <netdb.h>
#ifdef C_LINUX
#include <libintl.h>
#include <locale.h>
#define gettext_noop(s) s
#define _(s) gettext(s)
#define N_(s) gettext_noop(s)
#else
#define _(s) s
#define N_(s) s
#endif
#ifdef WITH_TCPWRAP
#include <tcpd.h>
int allow_severity = LOG_DEBUG;
int deny_severity = LOG_NOTICE;
#endif
#if defined(CL_DEBUG) && defined(C_LINUX)
#include <sys/resource.h>
#endif
#define _GNU_SOURCE
#include <getopt.h>
#ifndef SENDMAIL_BIN
#define SENDMAIL_BIN "/usr/lib/sendmail"
#endif
#ifndef HAVE_IN_PORT_T
typedef unsigned short in_port_t;
#endif
#ifndef HAVE_IN_ADDR_T
typedef unsigned int in_addr_t;
#endif
struct header_node_t {
char *header;
struct header_node_t *next;
};
struct header_list_struct {
struct header_node_t *first;
struct header_node_t *last;
};
typedef struct header_list_struct *header_list_t;
#define PACKADDR(a, b, c, d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
#define MAKEMASK(bits) ((uint32_t)(0xffffffff << (bits)))
static const struct cidr_net {
uint32_t base;
uint32_t mask;
} localNets[] = {
{ PACKADDR(192, 168, 0, 0), MAKEMASK(16) },
{ PACKADDR( 10, 0, 0, 0), MAKEMASK(24) },
{ PACKADDR(172, 16, 0, 0), MAKEMASK(20) },
{ PACKADDR(169, 254, 0, 0), MAKEMASK(16) },
{ 0, 0 }
};
struct privdata {
char *from;
char **to;
int numTo;
#ifndef SESSION
int cmdSocket;
#endif
int dataSocket;
char *filename;
u_char *body;
size_t bodyLen;
header_list_t headers;
long numBytes;
char *received;
const char *rejectCode;
char *messageID;
int discard;
int serverNumber;
};
#ifdef SESSION
static int createSession(int session);
#else
static int pingServer(int serverNumber);
#endif
static int findServer(void);
static sfsistat clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr);
static sfsistat clamfi_envfrom(SMFICTX *ctx, char **argv);
static sfsistat clamfi_envrcpt(SMFICTX *ctx, char **argv);
static sfsistat clamfi_header(SMFICTX *ctx, char *headerf, char *headerv);
static sfsistat clamfi_eoh(SMFICTX *ctx);
static sfsistat clamfi_body(SMFICTX *ctx, u_char *bodyp, size_t len);
static sfsistat clamfi_eom(SMFICTX *ctx);
static sfsistat clamfi_abort(SMFICTX *ctx);
static sfsistat clamfi_close(SMFICTX *ctx);
static void clamfi_cleanup(SMFICTX *ctx);
static void clamfi_free(struct privdata *privdata);
static int clamfi_send(struct privdata *privdata, size_t len, const char *format, ...);
static int clamd_recv(int sock, char *buf, size_t len);
static off_t updateSigFile(void);
static header_list_t header_list_new(void);
static void header_list_free(header_list_t list);
static void header_list_add(header_list_t list, const char *headerf, const char *headerv);
static void header_list_print(header_list_t list, FILE *fp);
static int connect2clamd(struct privdata *privdata);
static void checkClamd(void);
static int sendtemplate(SMFICTX *ctx, const char *filename, FILE *sendmail, const char *virusname);
static int qfile(struct privdata *privdata, const char *virusname);
static void setsubject(SMFICTX *ctx, const char *virusname);
static int clamfi_gethostbyname(const char *hostname, struct hostent *hp, char *buf, size_t len);
static int isLocalAddr(in_addr_t addr);
static void clamdIsDown(void);
#ifdef SESSION
static void *watchdog(void *a);
#endif
static int logg_facility(const char *name);
static void quit(void);
static void broadcast(const char *mess);
static char clamav_version[128];
static int fflag = 0;
static int oflag = 0;
static int lflag = 0;
static int bflag = 0;
static const char *iface;
static int broadcastSock = -1;
static int pflag = 0;
static int qflag = 0;
static int Sflag = 0;
static const char *sigFilename;
static char *quarantine;
static char *quarantine_dir;
static int nflag = 0;
static int rejectmail = 1;
static int hflag = 0;
static int cl_error = SMFIS_TEMPFAIL;
static int readTimeout = CL_DEFAULT_SCANTIMEOUT;
static long streamMaxLength = -1;
static int logClean = 1;
static char *signature = N_("-- \nScanned by ClamAv - http://www.clamav.net\n");
static time_t signatureStamp;
static char *templatefile;
#ifdef CL_DEBUG
static int debug_level = 0;
#endif
static pthread_mutex_t n_children_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t n_children_cond = PTHREAD_COND_INITIALIZER;
static unsigned int n_children = 0;
static unsigned int max_children = 0;
static int child_timeout = 60;
static int dont_wait = 0;
static int advisory = 0;
static short use_syslog = 0;
static const char *pidFile;
static int logVerbose = 0;
static struct cfgstruct *copt;
static const char *localSocket;
static in_port_t tcpSocket;
static char *port = NULL;
static const char *serverHostNames = "127.0.0.1";
static long *serverIPs;
static int numServers;
#ifdef SESSION
static int *cmdSockets;
static int *cmdSocketsStatus;
static pthread_mutex_t sstatus_mutex = PTHREAD_MUTEX_INITIALIZER;
#define CMDSOCKET_FREE 0
#define CMDSOCKET_INUSE 1
#define CMDSOCKET_DOWN 2
static pthread_cond_t watchdog_cond = PTHREAD_COND_INITIALIZER;
#endif
static const char *postmaster = "postmaster";
static const char *from = "MAILER-DAEMON";
static int quitting;
static const char *ignoredEmailAddresses[] = {
NULL
};
static void
help(void)
{
printf("\n\tclamav-milter version %s\n", CM_VERSION);
puts("\tCopyright (C) 2004 Nigel Horne <njh@despammed.com>\n");
puts(_("\t--advisory\t\t-A\tFlag viruses rather than deleting them."));
puts(_("\t--bounce\t\t-b\tSend a failure message to the sender."));
puts(_("\t--broadcast\t\t-B [IFACE]\tBroadcast to a network manager when a virus is found."));
puts(_("\t--config-file=FILE\t-c FILE\tRead configuration from FILE."));
puts(_("\t--debug\t\t\t-D\tPrint debug messages."));
puts(_("\t--dont-log-clean\t-C\tDon't add an entry to syslog that a mail is clean."));
puts(_("\t--dont-scan-on-error\t-d\tPass e-mails through unscanned if a system error occurs."));
puts(_("\t--dont-wait\t\t\tAsk remote end to resend if max-children exceeded."));
puts(_("\t--from=EMAIL\t\t-a EMAIL\tError messages come from here."));
puts(_("\t--force-scan\t\t-f\tForce scan all messages (overrides (-o and -l)."));
puts(_("\t--help\t\t\t-h\tThis message."));
puts(_("\t--headers\t\t-H\tInclude original message headers in the report."));
puts(_("\t--local\t\t\t-l\tScan messages sent from machines on our LAN."));
puts(_("\t--max-childen\t\t-m\tMaximum number of concurrent scans."));
puts(_("\t--outgoing\t\t-o\tScan outgoing messages from this machine."));
puts(_("\t--noreject\t\t-N\tDon't reject viruses, silently throw them away."));
puts(_("\t--noxheader\t\t-n\tSuppress X-Virus-Scanned/X-Virus-Status headers."));
puts(_("\t--pidfile=FILE\t\t-i FILE\tLocation of pidfile."));
puts(_("\t--postmaster\t\t-p EMAIL\tPostmaster address [default=postmaster]."));
puts(_("\t--postmaster-only\t-P\tSend warnings only to the postmaster."));
puts(_("\t--quiet\t\t\t-q\tDon't send e-mail notifications of interceptions."));
puts(_("\t--quarantine=USER\t-Q EMAIL\tQuanrantine e-mail account."));
puts(_("\t--quarantine-dir=DIR\t-U DIR\tDirectory to store infected emails."));
puts(_("\t--server=SERVER\t\t-s SERVER\tHostname/IP address of server(s) running clamd (when using TCPsocket)."));
puts(_("\t--sign\t\t\t-S\tAdd a hard-coded signature to each scanned message."));
puts(_("\t--signature-file=FILE\t-F FILE\tLocation of signature file."));
puts(_("\t--template-file=FILE\t-t FILE\tLocation of e-mail template file."));
puts(_("\t--timeout=SECS\t\t-T SECS\tTimeout waiting to childen to die."));
puts(_("\t--version\t\t-V\tPrint the version number of this software."));
#ifdef CL_DEBUG
puts(_("\t--debug-level=n\t\t-x n\tSets the debug level to 'n'."));
#endif
puts(_("\nFor more information type \"man clamav-milter\"."));
puts(_("Report bugs to bugs@clamav.net."));
}
int
main(int argc, char **argv)
{
extern char *optarg;
int i, Bflag = 0;
const char *cfgfile = CL_DEFAULT_CFG;
struct cfgstruct *cpt;
struct passwd *user;
const char *pidfile = NULL;
#ifdef SESSION
pthread_t tid;
#endif
struct smfiDesc smfilter = {
"ClamAv",
SMFI_VERSION,
SMFIF_ADDHDRS,
clamfi_connect,
NULL,
clamfi_envfrom,
clamfi_envrcpt,
clamfi_header,
clamfi_eoh,
clamfi_body,
clamfi_eom,
clamfi_abort,
clamfi_close,
};
#if defined(CL_DEBUG) && defined(C_LINUX)
struct rlimit rlim;
rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
if(setrlimit(RLIMIT_CORE, &rlim) < 0)
perror("setrlimit");
#endif
snprintf(clamav_version, sizeof(clamav_version),
"ClamAV version %s, clamav-milter version %s",
VERSION, CM_VERSION);
#ifdef C_LINUX
setlocale(LC_ALL, "");
bindtextdomain("clamav-milter", DATADIR"/clamav-milter/locale");
textdomain("clamav-milter");
#endif
for(;;) {
int opt_index = 0;
#ifdef CL_DEBUG
const char *args = "a:AbB:c:CDfF:lm:nNop:PqQ:dhHs:St:T:U:Vx:";
#else
const char *args = "a:AbB:c:CDfF:lm:nNop:PqQ:dhHs:St:T:U:V";
#endif
static struct option long_options[] = {
{
"from", 2, NULL, 'a'
},
{
"advisory", 0, NULL, 'A'
},
{
"bounce", 0, NULL, 'b'
},
{
"broadcast", 2, NULL, 'B'
},
{
"config-file", 1, NULL, 'c'
},
{
"dont-log-clean", 0, NULL, 'C'
},
{
"dont-scan-on-error", 0, NULL, 'd'
},
{
"dont-wait", 0, NULL, 'w'
},
{
"debug", 0, NULL, 'D'
},
{
"force-scan", 0, NULL, 'f'
},
{
"headers", 0, NULL, 'H'
},
{
"help", 0, NULL, 'h'
},
{
"pidfile", 1, NULL, 'i'
},
{
"local", 0, NULL, 'l'
},
{
"noreject", 0, NULL, 'N'
},
{
"noxheader", 0, NULL, 'n'
},
{
"outgoing", 0, NULL, 'o'
},
{
"postmaster", 1, NULL, 'p'
},
{
"postmaster-only", 0, NULL, 'P',
},
{
"quiet", 0, NULL, 'q'
},
{
"quarantine", 1, NULL, 'Q',
},
{
"quarantine-dir", 1, NULL, 'U',
},
{
"max-children", 1, NULL, 'm'
},
{
"server", 1, NULL, 's'
},
{
"sign", 0, NULL, 'S'
},
{
"signature-file", 1, NULL, 'F'
},
{
"template-file", 1, NULL, 't'
},
{
"timeout", 1, NULL, 'T'
},
{
"version", 0, NULL, 'V'
},
#ifdef CL_DEBUG
{
"debug-level", 1, NULL, 'x'
},
#endif
{
NULL, 0, NULL, '\0'
}
};
int ret = getopt_long(argc, argv, args, long_options, &opt_index);
if(ret == -1)
break;
else if(ret == 0)
ret = long_options[opt_index].val;
switch(ret) {
case 'a':
from = optarg;
break;
case 'A':
advisory++;
smfilter.xxfi_flags |= SMFIF_CHGHDRS;
break;
case 'b':
bflag++;
break;
case 'B':
Bflag++;
if(optarg)
iface = optarg;
break;
case 'c':
cfgfile = optarg;
break;
case 'C':
logClean = 0;
break;
case 'd':
cl_error = SMFIS_ACCEPT;
break;
case 'D':
cl_debug();
break;
case 'f':
fflag++;
break;
case 'h':
help();
return EX_OK;
case 'H':
hflag++;
break;
case 'i':
pidfile = optarg;
break;
case 'l':
lflag++;
break;
case 'm':
max_children = atoi(optarg);
break;
case 'n':
nflag++;
smfilter.xxfi_flags &= ~SMFIF_ADDHDRS;
break;
case 'N':
rejectmail = 0;
break;
case 'o':
oflag++;
break;
case 'p':
postmaster = optarg;
break;
case 'P':
pflag++;
break;
case 'q':
qflag++;
break;
case 'Q':
quarantine = optarg;
smfilter.xxfi_flags |= SMFIF_CHGHDRS|SMFIF_ADDRCPT|SMFIF_DELRCPT;
break;
case 's':
serverHostNames = optarg;
break;
case 'F':
sigFilename = optarg;
signature = NULL;
case 'S':
smfilter.xxfi_flags |= SMFIF_CHGBODY;
Sflag++;
break;
case 't':
templatefile = optarg;
break;
case 'T':
child_timeout = atoi(optarg);
break;
case 'U':
quarantine_dir = optarg;
break;
case 'V':
puts(clamav_version);
return EX_OK;
case 'w':
dont_wait++;
break;
#ifdef CL_DEBUG
case 'x':
debug_level = atoi(optarg);
break;
#endif
default:
#ifdef CL_DEBUG
fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-s SERVER] [-S] [-x#] [-U PATH] socket-addr\n", argv[0]);
#else
fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-s SERVER] [-S] [-U PATH] socket-addr\n", argv[0]);
#endif
return EX_USAGE;
}
}
if (optind == argc) {
fprintf(stderr, _("%s: No socket-addr given\n"), argv[0]);
return EX_USAGE;
}
port = argv[optind];
if((copt = parsecfg(cfgfile, 1)) == NULL) {
fprintf(stderr, _("%s: Can't parse the config file %s\n"),
argv[0], cfgfile);
return EX_CONFIG;
}
if(Bflag) {
int on;
broadcastSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
on = 1;
if(setsockopt(broadcastSock, SOL_SOCKET, SO_BROADCAST, (int *)&on, sizeof(on)) < 0) {
perror("setsockopt");
return EX_UNAVAILABLE;
}
shutdown(broadcastSock, SHUT_RD);
}
if(getuid() == 0) {
if(iface) {
#ifdef SO_BINDTODEVICE
struct ifreq ifr;
memset(&ifr, '\0', sizeof(struct ifreq));
strncpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name) - 1);
if(setsockopt(broadcastSock, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0) {
perror(iface);
return EX_CONFIG;
}
#else
fprintf(stderr, _("%s: The iface option to --broadcast is not supported on your operating system\n"), argv[0]);
return EX_CONFIG;
#endif
}
if((cpt = cfgopt(copt, "User")) != NULL) {
if((user = getpwnam(cpt->strarg)) == NULL) {
fprintf(stderr, _("%s: Can't get information about user %s\n"), argv[0], cpt->strarg);
return EX_CONFIG;
}
if(cfgopt(copt, "AllowSupplementaryGroups")) {
#ifdef HAVE_INITGROUPS
if(initgroups(cpt->strarg, user->pw_gid) < 0) {
perror(cpt->strarg);
return EX_CONFIG;
}
#else
fprintf(stderr, _("%s: AllowSupplementaryGroups: initgroups not supported.\n"),
argv[0]);
return EX_CONFIG;
#endif
} else {
#ifdef HAVE_SETGROUPS
if(setgroups(1, &user->pw_gid) < 0) {
perror(cpt->strarg);
return EX_CONFIG;
}
#endif
}
setgid(user->pw_gid);
if(setuid(user->pw_uid) < 0)
perror(cpt->strarg);
else
cli_dbgmsg(_("Running as user %s (UID %d, GID %d)\n"),
cpt->strarg, user->pw_uid, user->pw_gid);
} else
fprintf(stderr, _("%s: running as root is not recommended (check \"User\" in clamd.conf)\n"), argv[0]);
} else if(iface) {
fprintf(stderr, _("%s: Only root can set an interface for --broadcast\n"), argv[0]);
return EX_USAGE;
}
if(advisory && quarantine) {
fprintf(stderr, _("%s: Advisory mode doesn't work with quarantine mode\n"), argv[0]);
return EX_USAGE;
}
if(quarantine_dir) {
struct stat statb;
if(advisory) {
fprintf(stderr, _("%s: Advisory mode doesn't work with quarantine directories\n"), argv[0]);
return EX_USAGE;
}
if(access(quarantine_dir, W_OK) < 0) {
perror(quarantine_dir);
return EX_USAGE;
}
if(stat(quarantine_dir, &statb) < 0) {
perror(quarantine_dir);
return EX_USAGE;
}
if(statb.st_mode & 077) {
fprintf(stderr, _("%s: insecure quarantine directory %s (mode 0%o)\n"),
argv[0], quarantine_dir, statb.st_mode & 0777);
return EX_CONFIG;
}
}
if(sigFilename && !updateSigFile())
return EX_USAGE;
if(templatefile && (access(templatefile, R_OK) < 0)) {
perror(templatefile);
return EX_CONFIG;
}
if((max_children == 0) && ((cpt = cfgopt(copt, "MaxThreads")) != NULL))
max_children = cpt->numarg;
if((cpt = cfgopt(copt, "ReadTimeout")) != NULL) {
readTimeout = cpt->numarg;
if(readTimeout < 0) {
fprintf(stderr, _("%s: ReadTimeout must not be negative in %s\n"),
argv[0], cfgfile);
return EX_CONFIG;
}
}
if((cpt = cfgopt(copt, "StreamMaxLength")) != NULL) {
if(cpt->numarg < 0) {
fprintf(stderr, _("%s: StreamMaxLength must not be negative in %s\n"),
argv[0], cfgfile);
return EX_CONFIG;
}
streamMaxLength = (long)cpt->numarg;
}
if((cpt = cfgopt(copt, "LocalSocket")) != NULL) {
#ifdef SESSION
struct sockaddr_un server;
#endif
if(cfgopt(copt, "TCPSocket") != NULL) {
fprintf(stderr, _("%s: You can select one server type only (local/TCP) in %s\n"),
argv[0], cfgfile);
return EX_CONFIG;
}
localSocket = cpt->strarg;
#ifndef SESSION
if(!pingServer(-1)) {
fprintf(stderr, _("Can't talk to clamd server via %s\n"),
localSocket);
fprintf(stderr, _("Check your entry for LocalSocket in %s\n"),
cfgfile);
return EX_CONFIG;
}
#endif
umask(077);
serverIPs = (long *)cli_malloc(sizeof(long));
serverIPs[0] = inet_addr("127.0.0.1");
#ifdef SESSION
memset((char *)&server, 0, sizeof(struct sockaddr_un));
server.sun_family = AF_UNIX;
strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
cmdSockets = (int *)cli_malloc(sizeof(int));
if((cmdSockets[0] = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror(localSocket);
fprintf(stderr, _("Can't talk to clamd server via %s\n"),
localSocket);
fprintf(stderr, _("Check your entry for LocalSocket in %s\n"),
cfgfile);
return EX_CONFIG;
}
if(connect(cmdSockets[0], (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
perror(localSocket);
return EX_UNAVAILABLE;
}
if(send(cmdSockets[0], "SESSION\n", 7, 0) < 7) {
perror("send");
if(use_syslog)
syslog(LOG_ERR, _("Can't create a clamd session"));
return EX_UNAVAILABLE;
}
#endif
numServers = 1;
} else if((cpt = cfgopt(copt, "TCPSocket")) != NULL) {
int activeServers;
if(quarantine_dir) {
fprintf(stderr, _("%s: --quarantine-dir not supported for remote scanning - use --quarantine\n"), argv[0]);
return EX_CONFIG;
}
tcpSocket = (in_port_t)cpt->numarg;
for(;;) {
char *hostname = cli_strtok(serverHostNames, numServers, ":");
if(hostname == NULL)
break;
numServers++;
free(hostname);
}
cli_dbgmsg("numServers: %d\n", numServers);
serverIPs = (long *)cli_malloc(numServers * sizeof(long));
activeServers = 0;
#ifdef SESSION
if(max_children == 0) {
fprintf(stderr, _("%s: Sessions does not multiplex\n"), argv[0]);
return EX_CONFIG;
}
#endif
for(i = 0; i < numServers; i++) {
char *hostname = cli_strtok(serverHostNames, i, ":");
serverIPs[i] = inet_addr(hostname);
if(serverIPs[i] == -1L) {
const struct hostent *h = gethostbyname(hostname);
if(h == NULL) {
fprintf(stderr, _("%s: Unknown host %s\n"),
argv[0], hostname);
return EX_USAGE;
}
memcpy((char *)&serverIPs[i], h->h_addr, sizeof(serverIPs[i]));
}
#ifndef SESSION
if(pingServer(i))
activeServers++;
else {
cli_warnmsg(_("Can't talk to clamd server %s on port %d\n"),
hostname, tcpSocket);
}
#endif
free(hostname);
}
#ifdef SESSION
activeServers = numServers;
cmdSockets = (int *)cli_malloc(max_children * sizeof(int));
cmdSocketsStatus = (int *)cli_calloc(max_children, sizeof(int));
for(i = 0; i < max_children; i++)
if(createSession(i) < 0)
return EX_UNAVAILABLE;
#else
if(activeServers == 0) {
cli_errmsg(_("Can't find any clamd servers\n"));
cli_errmsg(_("Check your entry for TCPSocket in %s\n"),
cfgfile);
return EX_CONFIG;
}
#endif
} else {
fprintf(stderr, _("%s: You must select server type (local/TCP) in %s\n"),
argv[0], cfgfile);
return EX_CONFIG;
}
if(!cfgopt(copt, "Foreground")) {
#ifdef CL_DEBUG
printf(_("When debugging it is recommended that you use Foreground mode in %s\n"), cfgfile);
puts(_("So that you can see all of the messages"));
#endif
switch(fork()) {
case -1:
perror("fork");
return EX_TEMPFAIL;
case 0:
break;
default:
return EX_OK;
}
close(0);
open("/dev/null", O_RDONLY);
#ifndef CL_DEBUG
close(1);
close(2);
if((open("/dev/console", O_WRONLY) == 1) ||
(open("/dev/null", O_WRONLY) == 1))
dup(1);
#endif
#ifdef HAVE_SETPGRP
#ifdef SETPGRP_VOID
setpgrp();
#else
setpgrp(0,0);
#endif
#else
#ifdef HAVE_SETSID
setsid();
#endif
#endif
}
atexit(quit);
#ifdef SESSION
pthread_create(&tid, NULL, watchdog, NULL);
#endif
if((cpt = cfgopt(copt, "PidFile")) != NULL)
pidFile = cpt->strarg;
if(cfgopt(copt, "LogSyslog")) {
int fac = LOG_LOCAL6;
if(cfgopt(copt, "LogVerbose"))
logVerbose = 1;
use_syslog = 1;
if((cpt = cfgopt(copt, "LogFacility")) != NULL)
if((fac = logg_facility(cpt->strarg)) == -1) {
fprintf(stderr, "%s: LogFacility: %s: No such facility\n",
argv[0], cpt->strarg);
return EX_CONFIG;
}
openlog("clamav-milter", LOG_CONS|LOG_PID, fac);
if(logVerbose)
syslog(LOG_INFO, _("Starting: %s"), clamav_version);
else
syslog(LOG_INFO, "%s", clamav_version);
#ifdef CL_DEBUG
if(debug_level > 0)
syslog(LOG_DEBUG, _("Debugging is on"));
#endif
} else {
if(qflag)
fprintf(stderr, _("%s: (-q && !LogSyslog): warning - all interception message methods are off\n"),
argv[0]);
use_syslog = 0;
}
broadcast(_("Starting clamav-milter"));
if(pidfile) {
FILE *fd;
const mode_t old_umask = umask(0006);
if((fd = fopen(pidfile, "w")) == NULL) {
if(use_syslog)
syslog(LOG_WARNING, _("Can't save PID in file %s"),
pidfile);
cli_warnmsg(_("Can't save PID in file %s\n"), pidfile);
} else {
fprintf(fd, "%d\n", (int)getpid());
fclose(fd);
}
umask(old_umask);
}
if(cfgopt(copt, "FixStaleSocket")) {
if(strncasecmp(port, "unix:", 5) == 0) {
if(unlink(&port[5]) < 0)
if(errno != ENOENT)
perror(&port[5]);
} else if(strncasecmp(port, "local:", 6) == 0) {
if(unlink(&port[6]) < 0)
if(errno != ENOENT)
perror(&port[6]);
}
}
if(smfi_setconn(port) == MI_FAILURE) {
fprintf(stderr, _("%s: smfi_setconn failed\n"),
argv[0]);
return EX_SOFTWARE;
}
if(smfi_register(smfilter) == MI_FAILURE) {
cli_errmsg("smfi_register failure\n");
return EX_UNAVAILABLE;
}
signal(SIGPIPE, SIG_IGN);
if(logVerbose)
syslog(LOG_INFO, _("Started: %s"), clamav_version);
return smfi_main();
}
#ifdef SESSION
static int
createSession(int session)
{
int ret = 0;
struct sockaddr_in server;
const int serverNumber = session % numServers;
memset((char *)&server, 0, sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = (in_port_t)htons(tcpSocket);
server.sin_addr.s_addr = serverIPs[serverNumber];
if((cmdSockets[session] = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
ret = -1;
} else if(connect(cmdSockets[session], (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
perror("connect");
ret = 1;
} else if(send(cmdSockets[session], "SESSION\n", 7, 0) < 7) {
perror("send");
ret = 1;
}
if(ret != 0) {
char *hostname = cli_strtok(serverHostNames, serverNumber, ":");
cli_warnmsg(_("Check clamd server %s - it may be down\n"), hostname);
free(hostname);
broadcast(_("Check clamd server - it may be down\n"));
cmdSocketsStatus[session] = CMDSOCKET_DOWN;
}
cli_dbgmsg("cmdSockets[%d] = %d\n", session, cmdSockets[session]);
return ret;
}
#else
static int
pingServer(int serverNumber)
{
char *ptr;
int sock, nbytes;
char buf[128];
if(localSocket) {
struct sockaddr_un server;
memset((char *)&server, 0, sizeof(struct sockaddr_un));
server.sun_family = AF_UNIX;
strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
if((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror(localSocket);
return 0;
}
checkClamd();
if(connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
perror(localSocket);
close(sock);
return 0;
}
} else {
struct sockaddr_in server;
memset((char *)&server, 0, sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = (in_port_t)htons(tcpSocket);
assert(serverIPs != NULL);
assert(serverIPs[0] != -1L);
server.sin_addr.s_addr = serverIPs[serverNumber];
if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
return 0;
}
if(connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
perror("connect");
close(sock);
return 0;
}
}
cli_dbgmsg("pingServer%d: sending VERSION\n", serverNumber);
if(send(sock, "VERSION\n", 8, 0) < 8) {
perror("send");
close(sock);
return 0;
}
shutdown(sock, SHUT_WR);
nbytes = clamd_recv(sock, buf, sizeof(buf));
close(sock);
if(nbytes < 0) {
perror("recv");
return 0;
}
if(nbytes == 0)
return 0;
buf[nbytes] = '\0';
if((ptr = strchr(buf, '\n')) != NULL)
*ptr = '\0';
snprintf(clamav_version, sizeof(clamav_version),
"%s\n\tclamav-milter version %s",
buf, CM_VERSION);
return 1;
}
#endif
#ifdef SESSION
static int
findServer(void)
{
int i;
pthread_mutex_lock(&n_children_mutex);
assert(n_children > 0);
assert(n_children <= max_children);
i = n_children - 1;
pthread_mutex_unlock(&n_children_mutex);
pthread_mutex_lock(&sstatus_mutex);
for(; i < max_children; i++)
if(cmdSocketsStatus[i] == CMDSOCKET_FREE) {
cmdSocketsStatus[i] = CMDSOCKET_INUSE;
pthread_mutex_unlock(&sstatus_mutex);
return i;
}
pthread_mutex_unlock(&sstatus_mutex);
if(pthread_cond_broadcast(&watchdog_cond) < 0)
perror("pthread_cond_broadcast");
pthread_mutex_lock(&sstatus_mutex);
for(; i < max_children; i++)
if(cmdSocketsStatus[i] == CMDSOCKET_FREE) {
cmdSocketsStatus[i] = CMDSOCKET_INUSE;
pthread_mutex_unlock(&sstatus_mutex);
return i;
}
pthread_mutex_unlock(&sstatus_mutex);
cli_warnmsg(_("No free clamd sessions\n"));
return -1;
}
#else
static int
findServer(void)
{
struct sockaddr_in *servers, *server;
int *socks, maxsock = 0, i, j;
fd_set rfds;
struct timeval tv;
int retval;
assert(tcpSocket != 0);
assert(numServers > 0);
if(numServers == 1)
return 0;
servers = (struct sockaddr_in *)cli_calloc(numServers, sizeof(struct sockaddr_in));
socks = (int *)cli_malloc(numServers * sizeof(int));
FD_ZERO(&rfds);
if(max_children > 0) {
assert(n_children > 0);
assert(n_children <= max_children);
j = n_children - 1;
} else
j = cli_rndnum(numServers);
for(i = 0, server = servers; i < numServers; i++, server++) {
int sock;
server->sin_family = AF_INET;
server->sin_port = (in_port_t)htons(tcpSocket);
server->sin_addr.s_addr = serverIPs[(i + j) % numServers];
cli_dbgmsg("findServer: try server %d\n",
(i + j) % numServers);
sock = socks[i] = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0) {
perror("socket");
do
if(socks[i] >= 0)
close(socks[i]);
while(--i >= 0);
free(socks);
free(servers);
return 0;
}
if((connect(sock, (struct sockaddr *)server, sizeof(struct sockaddr)) < 0) ||
(send(sock, "PING\n", 5, 0) < 5)) {
char *hostname = cli_strtok(serverHostNames, i, ":");
cli_warnmsg(_("Check clamd server %s - it may be down\n"), hostname);
if(use_syslog)
syslog(LOG_WARNING,
_("Check clamd server %s - it may be down"),
hostname);
close(sock);
free(hostname);
broadcast(_("Check clamd server - it may be down\n"));
socks[i] = -1;
continue;
}
shutdown(sock, SHUT_WR);
FD_SET(sock, &rfds);
if(sock > maxsock)
maxsock = sock;
}
free(servers);
tv.tv_sec = readTimeout;
tv.tv_usec = 0;
if(maxsock == 0)
retval = 0;
else
retval = select(maxsock + 1, &rfds, NULL, NULL, &tv);
if(retval < 0)
perror("select");
for(i = 0; i < numServers; i++)
if(socks[i] >= 0)
close(socks[i]);
if(retval == 0) {
free(socks);
clamdIsDown();
return 0;
} else if(retval < 0) {
free(socks);
if(use_syslog)
syslog(LOG_ERR, _("findServer: select failed"));
return 0;
}
for(i = 0; i < numServers; i++)
if((socks[i] >= 0) && (FD_ISSET(socks[i], &rfds))) {
const int s = (i + j) % numServers;
free(socks);
cli_dbgmsg(_("findServer: using server %d\n"), s);
return s;
}
free(socks);
cli_dbgmsg(_("findServer: No response from any server\n"));
if(use_syslog)
syslog(LOG_WARNING, _("findServer: No response from any server"));
return 0;
}
#endif
static sfsistat
clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
{
#if defined(HAVE_INET_NTOP) || defined(WITH_TCPWRAP)
char ip[INET_ADDRSTRLEN];
#endif
const char *remoteIP;
if(quitting)
return cl_error;
if(ctx == NULL) {
if(use_syslog)
syslog(LOG_ERR, _("clamfi_connect: ctx is null"));
return cl_error;
}
if(hostname == NULL) {
if(use_syslog)
syslog(LOG_ERR, _("clamfi_connect: hostname is null"));
return cl_error;
}
if((hostaddr == NULL) || (&(((struct sockaddr_in *)(hostaddr))->sin_addr) == NULL))
remoteIP = "127.0.0.1";
else {
#ifdef HAVE_INET_NTOP
remoteIP = (char *)inet_ntop(AF_INET, &((struct sockaddr_in *)(hostaddr))->sin_addr, ip, sizeof(ip));
#else
remoteIP = inet_ntoa(((struct sockaddr_in *)(hostaddr))->sin_addr);
#endif
if(remoteIP == NULL) {
if(use_syslog)
syslog(LOG_ERR, _("clamfi_connect: remoteIP is null"));
return cl_error;
}
}
#ifdef CL_DEBUG
if(debug_level >= 4) {
if(use_syslog)
syslog(LOG_NOTICE, _("clamfi_connect: connection from %s [%s]"), hostname, remoteIP);
cli_dbgmsg(_("clamfi_connect: connection from %s [%s]\n"), hostname, remoteIP);
}
#endif
#ifdef WITH_TCPWRAP
if(strncasecmp(port, "inet:", 5) == 0) {
const char *hostmail;
struct hostent hostent;
char buf[BUFSIZ];
static pthread_mutex_t wrap_mutex = PTHREAD_MUTEX_INITIALIZER;
if((hostmail = smfi_getsymval(ctx, "{if_name}")) == NULL) {
if(use_syslog)
syslog(LOG_ERR, _("Can't get sendmail hostname"));
return cl_error;
}
if(clamfi_gethostbyname(hostmail, &hostent, buf, sizeof(buf)) != 0) {
if(use_syslog)
syslog(LOG_WARNING, _("Access Denied: Host Unknown (%s)"), hostname);
return cl_error;
}
#ifdef HAVE_INET_NTOP
if(hostent.h_addr &&
(inet_ntop(AF_INET, (struct in_addr *)hostent.h_addr, ip, sizeof(ip)) == NULL)) {
perror(hostent.h_name);
if(use_syslog)
syslog(LOG_WARNING, _("Access Denied: Can't get IP address for (%s)"), hostent.h_name);
return cl_error;
}
#else
strncpy(ip, (char *)inet_ntoa(*(struct in_addr *)hostent.h_addr), sizeof(ip));
#endif
pthread_mutex_lock(&wrap_mutex);
if(!hosts_ctl("clamav-milter", hostent.h_name, ip, STRING_UNKNOWN)) {
pthread_mutex_unlock(&wrap_mutex);
if(use_syslog)
syslog(LOG_WARNING, _("Access Denied for %s[%s]"), hostent.h_name, ip);
return SMFIS_TEMPFAIL;
}
pthread_mutex_unlock(&wrap_mutex);
}
#endif
if(fflag)
return SMFIS_CONTINUE;
if(!oflag)
if(strcmp(remoteIP, "127.0.0.1") == 0) {
#ifdef CL_DEBUG
if(use_syslog)
syslog(LOG_DEBUG, _("clamfi_connect: not scanning outgoing messages"));
cli_dbgmsg(_("clamfi_connect: not scanning outgoing messages\n"));
#endif
return SMFIS_ACCEPT;
}
if((!lflag) && isLocalAddr(inet_addr(remoteIP))) {
#ifdef CL_DEBUG
if(use_syslog)
syslog(LOG_DEBUG, _("clamfi_connect: not scanning local messages"));
cli_dbgmsg(_("clamfi_connect: not scanning local messages\n"));
#endif
return SMFIS_ACCEPT;
}
return SMFIS_CONTINUE;
}
static sfsistat
clamfi_envfrom(SMFICTX *ctx, char **argv)
{
struct privdata *privdata;
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_envfrom: %s", argv[0]);
cli_dbgmsg("clamfi_envfrom: %s\n", argv[0]);
if(max_children > 0) {
int rc = 0;
pthread_mutex_lock(&n_children_mutex);
if(n_children >= max_children) {
struct timeval now;
struct timespec timeout;
struct timezone tz;
cli_dbgmsg((dont_wait) ?
_("hit max-children limit (%u >= %u)\n") :
_("hit max-children limit (%u >= %u): waiting for some to exit\n"),
n_children, max_children);
if(use_syslog)
syslog(LOG_NOTICE,
(dont_wait) ?
_("hit max-children limit (%u >= %u)") :
_("hit max-children limit (%u >= %u): waiting for some to exit"),
n_children, max_children);
if(dont_wait) {
pthread_mutex_unlock(&n_children_mutex);
smfi_setreply(ctx, "451", "4.3.2", _("AV system temporarily overloaded - please try later"));
return SMFIS_TEMPFAIL;
}
if(child_timeout) {
gettimeofday(&now, &tz);
timeout.tv_sec = now.tv_sec + child_timeout;
timeout.tv_nsec = 0;
}
do
if(child_timeout == 0)
rc = pthread_cond_wait(&n_children_cond, &n_children_mutex);
else
rc = pthread_cond_timedwait(&n_children_cond, &n_children_mutex, &timeout);
while((n_children >= max_children) && (rc != ETIMEDOUT));
}
n_children++;
cli_dbgmsg(_(">n_children = %d\n"), n_children);
pthread_mutex_unlock(&n_children_mutex);
if(child_timeout && (rc == ETIMEDOUT)) {
#ifdef CL_DEBUG
if(use_syslog)
syslog(LOG_NOTICE, _("Timeout waiting for a child to die"));
#endif
cli_dbgmsg(_("Timeout waiting for a child to die\n"));
}
}
privdata = (struct privdata *)cli_calloc(1, sizeof(struct privdata));
if(privdata == NULL)
return cl_error;
privdata->dataSocket = -1;
#ifndef SESSION
privdata->cmdSocket = -1;
#endif
privdata->rejectCode = "550";
privdata->from = strdup(argv[0]);
if(hflag)
privdata->headers = header_list_new();
if(smfi_setpriv(ctx, privdata) == MI_SUCCESS)
return SMFIS_CONTINUE;
clamfi_free(privdata);
return cl_error;
}
static sfsistat
clamfi_envrcpt(SMFICTX *ctx, char **argv)
{
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_envrcpt: %s", argv[0]);
cli_dbgmsg("clamfi_envrcpt: %s\n", argv[0]);
if(privdata->to == NULL) {
privdata->to = cli_malloc(sizeof(char *) * 2);
assert(privdata->numTo == 0);
} else
privdata->to = cli_realloc(privdata->to, sizeof(char *) * (privdata->numTo + 2));
if(privdata->to == NULL)
return cl_error;
privdata->to[privdata->numTo] = strdup(argv[0]);
privdata->to[++privdata->numTo] = NULL;
return SMFIS_CONTINUE;
}
static sfsistat
clamfi_header(SMFICTX *ctx, char *headerf, char *headerv)
{
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_header: %s: %s", headerf, headerv);
#ifdef CL_DEBUG
if(debug_level >= 9)
cli_dbgmsg("clamfi_header: %s: %s\n", headerf, headerv);
else
cli_dbgmsg("clamfi_header\n");
#endif
privdata->rejectCode = "554";
if(privdata->dataSocket == -1)
if(!connect2clamd(privdata)) {
clamfi_cleanup(ctx);
return cl_error;
}
if(clamfi_send(privdata, 0, "%s: %s\n", headerf, headerv) <= 0) {
clamfi_cleanup(ctx);
return cl_error;
}
if(hflag)
header_list_add(privdata->headers, headerf, headerv);
else if((strcasecmp(headerf, "Received") == 0) &&
(strncasecmp(headerv, "from ", 5) == 0) &&
(strstr(headerv, "localhost") != 0)) {
if(privdata->received)
free(privdata->received);
privdata->received = strdup(headerv);
}
if((strcasecmp(headerf, "Message-ID") == 0) &&
(strncasecmp(headerv, "<MDAEMON", 8) == 0))
privdata->discard = 1;
return SMFIS_CONTINUE;
}
static sfsistat
clamfi_eoh(SMFICTX *ctx)
{
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
char **to;
if(logVerbose)
syslog(LOG_DEBUG, _("clamfi_eoh"));
#ifdef CL_DEBUG
if(debug_level >= 4)
cli_dbgmsg(_("clamfi_eoh\n"));
#endif
privdata->rejectCode = "554";
if(privdata->dataSocket == -1)
if(!connect2clamd(privdata)) {
clamfi_cleanup(ctx);
return cl_error;
}
if(clamfi_send(privdata, 1, "\n") != 1) {
clamfi_cleanup(ctx);
return cl_error;
}
for(to = privdata->to; *to; to++) {
const char **s;
for(s = ignoredEmailAddresses; *s; s++)
if(strcasecmp(*s, *to) == 0)
break;
if(*s == NULL)
return SMFIS_CONTINUE;
}
if(use_syslog)
syslog(LOG_NOTICE, _("clamfi_eoh: ignoring whitelisted message"));
#ifdef CL_DEBUG
cli_dbgmsg(_("clamfi_eoh: not scanning outgoing messages\n"));
#endif
clamfi_cleanup(ctx);
return SMFIS_ACCEPT;
}
static sfsistat
clamfi_body(SMFICTX *ctx, u_char *bodyp, size_t len)
{
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
int nbytes;
if(logVerbose)
syslog(LOG_DEBUG, _("clamfi_envbody: %u bytes"), len);
#ifdef CL_DEBUG
cli_dbgmsg(_("clamfi_envbody: %u bytes\n"), len);
#endif
nbytes = clamfi_send(privdata, len, (char *)bodyp);
if(streamMaxLength > 0L) {
if(privdata->numBytes > streamMaxLength) {
if(use_syslog) {
const char *sendmailId = smfi_getsymval(ctx, "i");
if(sendmailId == NULL)
sendmailId = "Unknown";
syslog(LOG_NOTICE, _("%s: Message more than StreamMaxLength (%ld) bytes - not scanned"),
sendmailId, streamMaxLength);
}
if(!nflag)
smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned - StreamMaxLength exceeded"));
return SMFIS_ACCEPT;
}
}
if(nbytes < len) {
clamfi_cleanup(ctx);
return cl_error;
}
if(Sflag) {
if(privdata->body) {
assert(privdata->bodyLen > 0);
privdata->body = cli_realloc(privdata->body, privdata->bodyLen + len);
memcpy(&privdata->body[privdata->bodyLen], bodyp, len);
privdata->bodyLen += len;
} else {
assert(privdata->bodyLen == 0);
privdata->body = cli_malloc(len);
memcpy(privdata->body, bodyp, len);
privdata->bodyLen = len;
}
}
return SMFIS_CONTINUE;
}
static sfsistat
clamfi_eom(SMFICTX *ctx)
{
int rc = SMFIS_CONTINUE;
char *ptr;
const char *sendmailId;
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
char mess[128];
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_eom");
#ifdef CL_DEBUG
cli_dbgmsg("clamfi_eom\n");
assert(privdata != NULL);
#ifndef SESSION
assert((privdata->cmdSocket >= 0) || (privdata->filename != NULL));
assert(!((privdata->cmdSocket >= 0) && (privdata->filename != NULL)));
#endif
assert(privdata->dataSocket >= 0);
#endif
close(privdata->dataSocket);
privdata->dataSocket = -1;
if(quarantine_dir != NULL) {
char cmdbuf[1024];
struct sockaddr_un server;
int nbytes;
assert(localSocket != NULL);
memset((char *)&server, 0, sizeof(struct sockaddr_un));
server.sun_family = AF_UNIX;
strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
snprintf(cmdbuf, sizeof(cmdbuf) - 1, "SCAN %s", privdata->filename);
nbytes = (int)strlen(cmdbuf);
#ifdef SESSION
if(send(cmdSockets[0], cmdbuf, nbytes, 0) < nbytes) {
perror("send");
clamfi_cleanup(ctx);
if(use_syslog)
syslog(LOG_ERR, _("send failed to clamd"));
return cl_error;
}
#else
if((privdata->cmdSocket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("socket");
clamfi_cleanup(ctx);
return cl_error;
}
if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
perror(localSocket);
clamfi_cleanup(ctx);
return cl_error;
}
if(send(privdata->cmdSocket, cmdbuf, nbytes, 0) < nbytes) {
perror("send");
clamfi_cleanup(ctx);
if(use_syslog)
syslog(LOG_ERR, _("send failed to clamd"));
return cl_error;
}
shutdown(privdata->cmdSocket, SHUT_WR);
#endif
}
#ifdef SESSION
if(clamd_recv(cmdSockets[privdata->serverNumber], mess, sizeof(mess)) > 0) {
#else
if(clamd_recv(privdata->cmdSocket, mess, sizeof(mess)) > 0) {
#endif
if((ptr = strchr(mess, '\n')) != NULL)
*ptr = '\0';
if(logVerbose)
syslog(LOG_DEBUG, _("clamfi_eom: read %s"), mess);
cli_dbgmsg(_("clamfi_eom: read %s\n"), mess);
} else {
clamfi_cleanup(ctx);
syslog(LOG_NOTICE, _("clamfi_eom: read nothing from clamd"));
#ifdef CL_DEBUG
cli_dbgmsg(_("clamfi_eom: read nothing from clamd\n"));
#endif
return cl_error;
}
#ifdef SESSION
pthread_mutex_lock(&sstatus_mutex);
if(cmdSocketsStatus[privdata->serverNumber] == CMDSOCKET_INUSE)
cmdSocketsStatus[privdata->serverNumber] = CMDSOCKET_FREE;
pthread_mutex_unlock(&sstatus_mutex);
#else
close(privdata->cmdSocket);
privdata->cmdSocket = -1;
#endif
sendmailId = smfi_getsymval(ctx, "i");
if(sendmailId == NULL)
sendmailId = "Unknown";
if(!nflag) {
char buf[1024];
if(localSocket) {
char hostname[32];
if(gethostname(hostname, sizeof(hostname)) < 0) {
const char *j = smfi_getsymval(ctx, "{j}");
if(j)
strncpy(hostname, j,
sizeof(hostname) - 1);
else
strcpy(buf, _("Error determining host"));
} else if(strchr(hostname, '.') == NULL) {
struct hostent hostent;
if(clamfi_gethostbyname(hostname, &hostent, buf, sizeof(buf)) == 0)
strncpy(hostname, hostent.h_name, sizeof(hostname));
}
snprintf(buf, sizeof(buf) - 1, "%s\n\ton %s",
clamav_version, hostname);
} else {
char *hostname = cli_strtok(serverHostNames, privdata->serverNumber, ":");
if(hostname) {
snprintf(buf, sizeof(buf) - 1, "%s\n\ton %s",
clamav_version,
hostname);
free(hostname);
} else
strcpy(buf, _("Error determining host"));
}
smfi_addheader(ctx, "X-Virus-Scanned", buf);
}
if(strstr(mess, "ERROR") != NULL) {
if(strstr(mess, "Size limit reached") != NULL) {
if(use_syslog)
syslog(LOG_NOTICE, _("%s: Message more than StreamMaxLength (%ld) bytes - not scanned"),
sendmailId, streamMaxLength);
if(!nflag)
smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned - StreamMaxLength exceeded"));
clamfi_cleanup(ctx);
return SMFIS_ACCEPT;
}
if(!nflag)
smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned"));
cli_warnmsg("%s: %s\n", sendmailId, mess);
if(use_syslog)
syslog(LOG_ERR, "%s: %s\n", sendmailId, mess);
clamfi_cleanup(ctx);
return cl_error;
}
if((ptr = strstr(mess, "FOUND")) == NULL) {
if(!nflag)
smfi_addheader(ctx, "X-Virus-Status", _("Clean"));
if(use_syslog && logClean)
syslog(LOG_NOTICE, _("%s: clean message from %s"),
sendmailId,
(privdata->from) ? privdata->from : _("an unknown sender"));
if(privdata->body) {
off_t len = updateSigFile();
if(len) {
assert(Sflag != 0);
privdata->body = cli_realloc(privdata->body, privdata->bodyLen + len);
if(privdata->body) {
memcpy(&privdata->body[privdata->bodyLen], signature, len);
smfi_replacebody(ctx, privdata->body, privdata->bodyLen + len);
}
}
}
} else {
char reject[1024];
char **to, *virusname;
*--ptr = '\0';
if((virusname = strchr(mess, ':')) != NULL)
virusname = &virusname[2];
else
virusname = mess;
if(!nflag)
smfi_addheader(ctx, "X-Virus-Status", _("Infected"));
if(use_syslog) {
char *err = (char *)cli_malloc(1024);
int i;
if(err == NULL) {
clamfi_cleanup(ctx);
return cl_error;
}
snprintf(err, 1023, _("Intercepted virus from %s to"),
privdata->from);
ptr = strchr(err, '\0');
i = 1024;
for(to = privdata->to; *to; to++) {
if(&ptr[strlen(*to) + 2] >= &err[i]) {
i += 1024;
err = cli_realloc(err, i);
if(err == NULL) {
clamfi_cleanup(ctx);
return cl_error;
}
ptr = strchr(err, '\0');
}
ptr = strrcpy(ptr, " ");
ptr = strrcpy(ptr, *to);
}
(void)strcpy(ptr, "\n");
syslog(LOG_NOTICE, "%s: %s %s", sendmailId, mess, err);
#ifdef CL_DEBUG
cli_dbgmsg("%s", err);
#endif
free(err);
}
if(!qflag) {
char cmd[128];
FILE *sendmail;
snprintf(cmd, sizeof(cmd) - 1,
(oflag || fflag) ? "%s -t -odq" : "%s -t",
SENDMAIL_BIN);
sendmail = popen(cmd, "w");
if(sendmail) {
if(from && from[0])
fprintf(sendmail, "From: %s\n", from);
else
fprintf(sendmail, "From: %s\n", privdata->from);
if(bflag) {
fprintf(sendmail, "To: %s\n",
(privdata->from) ?
privdata->from :
smfi_getsymval(ctx, "{mail_addr}"));
fprintf(sendmail, "Cc: %s\n", postmaster);
} else
fprintf(sendmail, "To: %s\n", postmaster);
if(!pflag)
for(to = privdata->to; *to; to++)
fprintf(sendmail, "Cc: %s\n", *to);
fputs("Auto-Submitted: auto-submitted (antivirus notify)\n", sendmail);
if((ptr = smfi_getsymval(ctx, "{_}")) != NULL)
fprintf(sendmail,
"X-Infected-Received-From: %s\n",
ptr);
fputs(_("Subject: Virus intercepted\n\n"), sendmail);
if((templatefile == NULL) ||
(sendtemplate(ctx, templatefile, sendmail, virusname) < 0)) {
const char *sender;
if(privdata->from)
sender = (strcmp(privdata->from, "<>") == 0) ?
smfi_getsymval(ctx, "_") :
privdata->from;
else
sender = smfi_getsymval(ctx, "_");
if(bflag)
fputs(_("A message you sent to\n"), sendmail);
else if(pflag)
fprintf(sendmail, _("The message %1$s sent from %2$s to\n"),
sendmailId, sender);
else
fprintf(sendmail, _("A message sent from %s to\n"),
sender);
for(to = privdata->to; *to; to++)
fprintf(sendmail, "\t%s\n", *to);
fprintf(sendmail, _("contained %s and has not been delivered.\n"), virusname);
if(privdata->filename != NULL)
if(qfile(privdata, virusname) == 0)
fprintf(sendmail, _("\nThe message in question has been quarantined as %s\n"), privdata->filename);
if(hflag) {
fprintf(sendmail, _("\nThe message was received by %1$s from %2$s via %3$s\n\n"),
smfi_getsymval(ctx, "j"), sender,
smfi_getsymval(ctx, "_"));
fputs(_("For your information, the original message headers were:\n\n"), sendmail);
header_list_print(privdata->headers, sendmail);
} else if(privdata->received)
fprintf(sendmail, _("\nThe infected machine is likely to be here:\n%s\t\n"),
privdata->received);
}
pclose(sendmail);
}
}
if(privdata->filename) {
assert(quarantine_dir != NULL);
if(use_syslog)
syslog(LOG_NOTICE, _("Quarantined infected mail as %s"), privdata->filename);
free(privdata->filename);
privdata->filename = NULL;
}
if(quarantine) {
for(to = privdata->to; *to; to++) {
smfi_delrcpt(ctx, *to);
smfi_addheader(ctx, "X-Original-To", *to);
free(*to);
}
free(privdata->to);
privdata->to = NULL;
if(smfi_addrcpt(ctx, quarantine) == MI_FAILURE) {
if(use_syslog)
syslog(LOG_DEBUG, _("Can't set quarantine user %s"), quarantine);
else
cli_warnmsg(_("Can't set quarantine user %s\n"), quarantine);
} else
setsubject(ctx, virusname);
} else if(advisory)
setsubject(ctx, virusname);
else if(rejectmail) {
if(privdata->discard)
rc = SMFIS_DISCARD;
else
rc = SMFIS_REJECT;
} else
rc = SMFIS_DISCARD;
snprintf(reject, sizeof(reject) - 1, _("virus %s detected by ClamAV - http://www.clamav.net"), virusname);
smfi_setreply(ctx, (char *)privdata->rejectCode, "5.7.1", reject);
broadcast(mess);
}
clamfi_cleanup(ctx);
return rc;
}
static sfsistat
clamfi_abort(SMFICTX *ctx)
{
#ifdef CL_DEBUG
if(use_syslog)
syslog(LOG_DEBUG, "clamfi_abort");
#endif
cli_dbgmsg("clamfi_abort\n");
if((max_children > 0) && (n_children >= max_children)) {
(void)pthread_mutex_trylock(&n_children_mutex);
(void)pthread_mutex_unlock(&n_children_mutex);
}
clamfi_cleanup(ctx);
return cl_error;
}
static sfsistat
clamfi_close(SMFICTX *ctx)
{
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
cli_dbgmsg("clamfi_close\n");
if(privdata != NULL)
clamfi_cleanup(ctx);
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_close");
return SMFIS_CONTINUE;
}
static void
clamfi_cleanup(SMFICTX *ctx)
{
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
if(privdata) {
clamfi_free(privdata);
smfi_setpriv(ctx, NULL);
}
}
static void
clamfi_free(struct privdata *privdata)
{
cli_dbgmsg("clamfi_free\n");
if(privdata) {
if(privdata->body)
free(privdata->body);
if(privdata->dataSocket >= 0) {
close(privdata->dataSocket);
privdata->dataSocket = -1;
}
if(privdata->filename != NULL) {
if((unlink(privdata->filename) < 0) && (errno != ENOENT)) {
perror(privdata->filename);
if(use_syslog)
syslog(LOG_ERR,
_("Can't remove clean file %s"),
privdata->filename);
}
free(privdata->filename);
privdata->filename = NULL;
}
if(privdata->from) {
#ifdef CL_DEBUG
if(debug_level >= 9)
cli_dbgmsg("Free privdata->from\n");
#endif
free(privdata->from);
privdata->from = NULL;
}
if(privdata->to) {
char **to;
for(to = privdata->to; *to; to++) {
#ifdef CL_DEBUG
if(debug_level >= 9)
cli_dbgmsg("Free *privdata->to\n");
#endif
free(*to);
}
#ifdef CL_DEBUG
if(debug_level >= 9)
cli_dbgmsg("Free privdata->to\n");
#endif
free(privdata->to);
privdata->to = NULL;
}
#ifdef SESSION
pthread_mutex_lock(&sstatus_mutex);
if(cmdSocketsStatus[privdata->serverNumber] == CMDSOCKET_INUSE) {
#if 0
pthread_mutex_unlock(&sstatus_mutex);
if(readTimeout) {
char buf[64];
const int fd = cmdSockets[privdata->serverNumber];
cli_dbgmsg("clamfi_free: flush server %d fd %d\n",
privdata->serverNumber, fd);
while(clamd_recv(fd, buf, sizeof(buf)) > 0)
;
}
pthread_mutex_lock(&sstatus_mutex);
#endif
cmdSocketsStatus[privdata->serverNumber] = CMDSOCKET_FREE;
}
pthread_mutex_unlock(&sstatus_mutex);
#else
if(privdata->cmdSocket >= 0) {
char buf[64];
if(readTimeout)
while(clamd_recv(privdata->cmdSocket, buf, sizeof(buf)) > 0)
;
close(privdata->cmdSocket);
privdata->cmdSocket = -1;
}
#endif
if(privdata->headers)
header_list_free(privdata->headers);
#ifdef CL_DEBUG
if(debug_level >= 9)
cli_dbgmsg("Free privdata\n");
#endif
if(privdata->received)
free(privdata->received);
free(privdata);
}
if(max_children > 0) {
pthread_mutex_lock(&n_children_mutex);
cli_dbgmsg("clamfi_free: n_children = %d\n", n_children);
if(n_children > 0) {
--n_children;
#ifdef SESSION
if(n_children == 0)
if(pthread_cond_broadcast(&watchdog_cond) < 0)
perror("pthread_cond_broadcast");
#endif
}
#ifdef CL_DEBUG
cli_dbgmsg("pthread_cond_broadcast\n");
#endif
if(pthread_cond_broadcast(&n_children_cond) < 0)
perror("pthread_cond_broadcast");
cli_dbgmsg("<n_children = %d\n", n_children);
pthread_mutex_unlock(&n_children_mutex);
}
}
static int
clamfi_send(struct privdata *privdata, size_t len, const char *format, ...)
{
char output[BUFSIZ];
const char *ptr;
int ret = 0;
assert(format != NULL);
if(len > 0)
ptr = format;
else {
va_list argp;
va_start(argp, format);
vsnprintf(output, sizeof(output) - 1, format, argp);
va_end(argp);
len = strlen(output);
ptr = output;
}
#ifdef CL_DEBUG
if(debug_level >= 9) {
time_t t;
const struct tm *tm;
time(&t);
tm = localtime(&t);
cli_dbgmsg("%d:%d:%d clamfi_send: len=%u bufsiz=%u, fd=%d\n",
tm->tm_hour, tm->tm_min, tm->tm_sec, len,
sizeof(output), privdata->dataSocket);
}
#endif
while(len > 0) {
const int nbytes = (quarantine_dir) ?
write(privdata->dataSocket, ptr, len) :
send(privdata->dataSocket, ptr, len, 0);
assert(privdata->dataSocket >= 0);
if(nbytes == -1) {
if(quarantine_dir) {
perror(privdata->filename);
if(use_syslog) {
#ifdef HAVE_STRERROR_R
char buf[32];
strerror_r(errno, buf, sizeof(buf));
syslog(LOG_ERR,
_("write failure (%u bytes) to %s: %s"),
len, privdata->filename, buf);
#else
syslog(LOG_ERR, _("write failure (%u bytes) to %s: %s"),
len, privdata->filename,
strerror(errno));
#endif
}
} else {
if(errno == EINTR)
continue;
perror("send");
if(use_syslog) {
#ifdef HAVE_STRERROR_R
char buf[32];
strerror_r(errno, buf, sizeof(buf));
syslog(LOG_ERR,
_("write failure (%u bytes) to clamd: %s"),
len, buf);
#else
syslog(LOG_ERR, _("write failure (%u bytes) to clamd: %s"), len, strerror(errno));
#endif
}
checkClamd();
}
return -1;
}
ret += nbytes;
len -= nbytes;
ptr = &ptr[nbytes];
if(streamMaxLength > 0L) {
privdata->numBytes += nbytes;
if(privdata->numBytes >= streamMaxLength)
break;
}
}
return ret;
}
#if 0
static char *
strrcpy(char *dest, const char *source)
{
assert(dest != NULL);
assert(source != NULL);
assert(dest != source);
while((*dest++ = *source++) != '\0')
;
return(--dest);
}
#endif
static int
clamd_recv(int sock, char *buf, size_t len)
{
fd_set rfds;
struct timeval tv;
assert(sock >= 0);
if(readTimeout == 0)
return recv(sock, buf, len, 0);
FD_ZERO(&rfds);
FD_SET(sock, &rfds);
tv.tv_sec = readTimeout;
tv.tv_usec = 0;
switch(select(sock + 1, &rfds, NULL, NULL, &tv)) {
case -1:
perror("select");
return -1;
case 0:
if(use_syslog)
syslog(LOG_ERR, _("No data received from clamd in %d seconds\n"), readTimeout);
return 0;
}
return recv(sock, buf, len, 0);
}
static off_t
updateSigFile(void)
{
struct stat statb;
int fd;
if(sigFilename == NULL)
return 0;
if(stat(sigFilename, &statb) < 0) {
perror(sigFilename);
if(use_syslog)
syslog(LOG_ERR, _("Can't stat %s"), sigFilename);
return 0;
}
if(statb.st_mtime <= signatureStamp)
return statb.st_size;
fd = open(sigFilename, O_RDONLY);
if(fd < 0) {
perror(sigFilename);
if(use_syslog)
syslog(LOG_ERR, _("Can't open %s"), sigFilename);
return 0;
}
signatureStamp = statb.st_mtime;
signature = cli_realloc(signature, statb.st_size);
if(signature)
read(fd, signature, statb.st_size);
close(fd);
return statb.st_size;
}
static header_list_t
header_list_new(void)
{
header_list_t ret;
ret = (header_list_t)cli_malloc(sizeof(struct header_list_struct));
if(ret) {
ret->first = NULL;
ret->last = NULL;
}
return ret;
}
static void
header_list_free(header_list_t list)
{
struct header_node_t *iter;
iter = list->first;
while (iter) {
struct header_node_t *iter2 = iter->next;
free(iter->header);
free(iter);
iter = iter2;
}
free(list);
}
static void
header_list_add(header_list_t list, const char *headerf, const char *headerv)
{
char *header;
size_t len;
struct header_node_t *new_node;
len = (size_t)(strlen(headerf) + strlen(headerv) + 3);
header = (char *)cli_malloc(len);
if(header == NULL)
return;
sprintf(header, "%s: %s", headerf, headerv);
new_node = (struct header_node_t *)cli_malloc(sizeof(struct header_node_t));
if(new_node == NULL) {
free(header);
return;
}
new_node->header = header;
new_node->next = NULL;
if(!list->first)
list->first = new_node;
if(list->last)
list->last->next = new_node;
list->last = new_node;
}
static void
header_list_print(const header_list_t list, FILE *fp)
{
const struct header_node_t *iter;
if(list == NULL)
return;
for(iter = list->first; iter; iter = iter->next) {
if(strncmp(iter->header, "From ", 5) == 0)
putc('>', fp);
fprintf(fp, "%s\n", iter->header);
}
}
static int
connect2clamd(struct privdata *privdata)
{
char **to;
char *msg;
int length;
assert(privdata != NULL);
assert(privdata->dataSocket == -1);
assert(privdata->from != NULL);
assert(privdata->to != NULL);
#ifdef CL_DEBUG
if((debug_level > 0) && use_syslog)
syslog(LOG_DEBUG, "connect2clamd");
if(debug_level >= 4)
cli_dbgmsg("connect2clamd\n");
#endif
if(quarantine_dir) {
int ntries = 5;
time_t t;
int MM, YY, DD;
const struct tm *tm;
t = time((time_t *)0);
tm = localtime(&t);
MM = tm->tm_mon + 1;
YY = tm->tm_year - 100;
DD = tm->tm_mday;
privdata->filename = (char *)cli_malloc(strlen(quarantine_dir) + 19);
sprintf(privdata->filename, "%s/%02d%02d%02d", quarantine_dir,
YY, MM, DD);
if((mkdir(privdata->filename, 0700) < 0) && (errno != EEXIST)) {
perror(privdata->filename);
if(use_syslog)
syslog(LOG_ERR, _("mkdir %s failed"), privdata->filename);
return 0;
}
do {
sprintf(privdata->filename,
"%s/%02d%02d%02d/msg.XXXXXX",
quarantine_dir, YY, MM, DD);
#if defined(C_LINUX) || defined(C_BSD) || defined(HAVE_MKSTEMP) || defined(C_SOLARIS)
privdata->dataSocket = mkstemp(privdata->filename);
#else
if(mktemp(privdata->filename) == NULL) {
if(use_syslog)
syslog(LOG_ERR, _("mktemp %s failed"), privdata->filename);
return 0;
}
privdata->dataSocket = open(privdata->filename, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC, 0600);
#endif
} while((--ntries > 0) && (privdata->dataSocket < 0));
if(privdata->dataSocket < 0) {
perror(privdata->filename);
if(use_syslog)
syslog(LOG_ERR, _("Temporary quarantine file %s creation failed"), privdata->filename);
return 0;
}
privdata->serverNumber = 0;
} else {
int freeServer, nbytes;
struct sockaddr_in reply;
unsigned short p;
char buf[64];
#ifndef SESSION
assert(privdata->cmdSocket == -1);
#endif
if(localSocket) {
#ifndef SESSION
struct sockaddr_un server;
memset((char *)&server, 0, sizeof(struct sockaddr_un));
server.sun_family = AF_UNIX;
strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
if((privdata->cmdSocket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("socket");
return 0;
}
if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
perror(localSocket);
return 0;
}
privdata->serverNumber = 0;
#endif
freeServer = 0;
} else {
#ifdef SESSION
freeServer = findServer();
if(freeServer < 0)
return 0;
#else
struct sockaddr_in server;
memset((char *)&server, 0, sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = (in_port_t)htons(tcpSocket);
assert(serverIPs != NULL);
freeServer = findServer();
if(freeServer < 0)
return 0;
server.sin_addr.s_addr = serverIPs[freeServer];
if((privdata->cmdSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
return 0;
}
if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
perror("connect");
return 0;
}
#endif
privdata->serverNumber = freeServer;
}
#ifdef SESSION
if(send(cmdSockets[freeServer], "STREAM\n", 7, 0) < 7) {
perror("send");
pthread_mutex_lock(&sstatus_mutex);
cmdSocketsStatus[privdata->serverNumber] = CMDSOCKET_DOWN;
pthread_mutex_unlock(&sstatus_mutex);
cli_warnmsg("Failed sending stream to server %d (fd %d) errno %d\n",
freeServer, cmdSockets[freeServer], errno);
if(use_syslog)
syslog(LOG_ERR, _("send failed to clamd"));
return 0;
}
#else
if(send(privdata->cmdSocket, "STREAM\n", 7, 0) < 7) {
perror("send");
if(use_syslog)
syslog(LOG_ERR, _("send failed to clamd"));
return 0;
}
shutdown(privdata->cmdSocket, SHUT_WR);
#endif
if((privdata->dataSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
if(use_syslog)
syslog(LOG_ERR, _("failed to create TCPSocket to talk to clamd"));
return 0;
}
shutdown(privdata->dataSocket, SHUT_RD);
#ifdef SESSION
nbytes = clamd_recv(cmdSockets[privdata->serverNumber], buf, sizeof(buf));
#else
nbytes = clamd_recv(privdata->cmdSocket, buf, sizeof(buf));
#endif
if(nbytes < 0) {
perror("recv");
if(use_syslog)
syslog(LOG_ERR, _("recv failed from clamd getting PORT"));
return 0;
}
buf[nbytes] = '\0';
#ifdef CL_DEBUG
if(debug_level >= 4)
cli_dbgmsg("Received: %s", buf);
#endif
if(sscanf(buf, "PORT %hu\n", &p) != 1) {
if(use_syslog)
syslog(LOG_ERR, _("Expected port information from clamd, got '%s'"),
buf);
else
cli_warnmsg(_("Expected port information from clamd, got '%s'\n"),
buf);
#ifdef SESSION
pthread_mutex_lock(&sstatus_mutex);
cmdSocketsStatus[privdata->serverNumber] = CMDSOCKET_DOWN;
pthread_mutex_unlock(&sstatus_mutex);
#endif
return 0;
}
memset((char *)&reply, 0, sizeof(struct sockaddr_in));
reply.sin_family = AF_INET;
reply.sin_port = (in_port_t)htons(p);
assert(serverIPs != NULL);
reply.sin_addr.s_addr = serverIPs[freeServer];
#ifdef CL_DEBUG
if(debug_level >= 4)
cli_dbgmsg(_("Connecting to local port %d\n"), p);
#endif
if(connect(privdata->dataSocket, (struct sockaddr *)&reply, sizeof(struct sockaddr_in)) < 0) {
perror("connect");
if(use_syslog) {
#ifdef HAVE_STRERROR_R
strerror_r(errno, buf, sizeof(buf));
syslog(LOG_ERR,
_("Failed to connect to port %d given by clamd: %s"),
p, buf);
#else
syslog(LOG_ERR, _("Failed to connect to port %d given by clamd: %s"), p, strerror(errno));
#endif
}
return 0;
}
}
length = strlen(privdata->from) + 34;
for(to = privdata->to; *to; to++)
length += strlen(*to) + 5;
msg = cli_malloc(length + 1);
if(msg) {
sprintf(msg, "Received: by clamav-milter\nFrom: %s\n",
privdata->from);
for(to = privdata->to; *to; to++) {
char *eom = strchr(msg, '\0');
sprintf(eom, "To: %s\n", *to);
}
if(clamfi_send(privdata, length, msg) != length) {
free(msg);
return 0;
}
free(msg);
} else {
clamfi_send(privdata, 0,
"Received: by clamav-milter\nFrom: %s\n",
privdata->from);
for(to = privdata->to; *to; to++)
if(clamfi_send(privdata, 0, "To: %s\n", *to) <= 0)
return 0;
}
cli_dbgmsg("connect2clamd: serverNumber = %d\n", privdata->serverNumber);
return 1;
}
static void
checkClamd(void)
{
pid_t pid;
int fd, nbytes;
char buf[9];
if(!localSocket)
return;
if(pidFile == NULL)
return;
fd = open(pidFile, O_RDONLY);
if(fd < 0) {
perror(pidFile);
if(use_syslog)
syslog(LOG_ERR, _("Can't open %s"), pidFile);
return;
}
nbytes = read(fd, buf, sizeof(buf) - 1);
if(nbytes < 0)
perror(pidFile);
else
buf[nbytes] = '\0';
close(fd);
pid = atoi(buf);
if((kill(pid, 0) < 0) && (errno == ESRCH)) {
if(use_syslog)
syslog(LOG_ERR, _("Clamd (pid %d) seems to have died"),
pid);
perror("clamd");
}
}
static int
sendtemplate(SMFICTX *ctx, const char *filename, FILE *sendmail, const char *virusname)
{
FILE *fin = fopen(filename, "r");
struct stat statb;
char *buf, *ptr ;
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
if(fin == NULL) {
perror(filename);
if(use_syslog)
syslog(LOG_ERR, _("Can't open e-mail template file %s"),
filename);
return -1;
}
if(fstat(fileno(fin), &statb) < 0) {
perror(filename);
if(use_syslog)
syslog(LOG_ERR, _("Can't stat e-mail template file %s"),
filename);
fclose(fin);
return -1;
}
buf = cli_malloc(statb.st_size + 1);
if(buf == NULL) {
fclose(fin);
if(use_syslog)
syslog(LOG_ERR, _("Out of memory"));
return -1;
}
fread(buf, sizeof(char), statb.st_size, fin);
fclose(fin);
buf[statb.st_size] = '\0';
for(ptr = buf; *ptr; ptr++)
switch(*ptr) {
case '%':
switch(*++ptr) {
case 'v':
fputs(virusname, sendmail);
break;
case '%':
putc('%', sendmail);
break;
case 'h':
if(privdata)
header_list_print(privdata->headers, sendmail);
break;
case '\0':
putc('%', sendmail);
--ptr;
continue;
default:
syslog(LOG_ERR,
_("%s: Unknown clamAV variable \"%c\"\n"),
filename, *ptr);
break;
}
break;
case '$': {
const char *val;
char *end = strchr(++ptr, '$');
if(end == NULL) {
syslog(LOG_ERR,
_("%s: Unterminated sendmail variable \"%s\"\n"),
filename, ptr);
continue;
}
*end = '\0';
val = smfi_getsymval(ctx, ptr);
if(val == NULL) {
fputs(ptr, sendmail);
if(use_syslog)
syslog(LOG_ERR,
_("%s: Unknown sendmail variable \"%s\"\n"),
filename, ptr);
} else
fputs(val, sendmail);
ptr = end;
break;
}
case '\\':
if(*++ptr == '\0') {
--ptr;
continue;
}
putc(*ptr, sendmail);
break;
default:
putc(*ptr, sendmail);
}
free(buf);
return 0;
}
static int
qfile(struct privdata *privdata, const char *virusname)
{
char *newname, *ptr;
size_t len;
assert(privdata != NULL);
if((privdata->filename == NULL) || (virusname == NULL))
return -1;
len = strlen(privdata->filename);
newname = cli_malloc(len + strlen(virusname) + 2);
if(newname == NULL)
return -1;
sprintf(newname, "%s.%s", privdata->filename, virusname);
for(ptr = &newname[len]; *ptr; ptr++) {
#ifdef C_DARWIN
*ptr &= '\177';
#endif
#if defined(MSDOS) || defined(C_CYGWIN) || defined(WIN32)
if(strchr("/*?<>|\"+=,;: ", *ptr))
#else
if(*ptr == '/')
#endif
*ptr = '_';
}
if(link(privdata->filename, newname) < 0) {
perror(newname);
if(use_syslog)
syslog(LOG_WARNING, _("Can't rename %1$s to %2$s"),
privdata->filename, newname);
free(newname);
return -1;
}
unlink(privdata->filename);
free(privdata->filename);
privdata->filename = newname;
return 0;
}
static void
setsubject(SMFICTX *ctx, const char *virusname)
{
char subject[128];
snprintf(subject, sizeof(subject) - 1, _("[Virus] %s"), virusname);
smfi_chgheader(ctx, "Subject", 1, subject);
}
static int
clamfi_gethostbyname(const char *hostname, struct hostent *hp, char *buf, size_t len)
{
#if defined(HAVE_GETHOSTBYNAME_R_6)
struct hostent *hp2;
int ret;
if((hostname == NULL) || (hp == NULL))
return -1;
if(gethostbyname_r(hostname, hp, buf, len, &hp2, &ret) < 0)
return errno;
#elif defined(HAVE_GETHOSTBYNAME_R_5)
int ret;
if((hostname == NULL) || (hp == NULL))
return -1;
if(gethostbyname_r(hostname, hp, buf, len, &ret) == NULL)
return errno;
#elif defined(HAVE_GETHOSTBYNAME_R_3)
if((hostname == NULL) || (hp == NULL))
return -1;
if(gethostbyname_r(hostname, &hp, (struct hostent_data *)buf) < 0)
return errno;
#else
struct hostent *hp2;
static pthread_mutex_t hostent_mutex = PTHREAD_MUTEX_INITIALIZER;
if((hostname == NULL) || (hp == NULL))
return -1;
pthread_mutex_lock(&hostent_mutex);
if((hp2 = gethostbyname(hostname)) == NULL) {
pthread_mutex_unlock(&hostent_mutex);
return errno;
}
memcpy(hp, hp2, sizeof(struct hostent));
pthread_mutex_unlock(&hostent_mutex);
#endif
return 0;
}
static int
isLocalAddr(in_addr_t addr)
{
const struct cidr_net *net;
for(net = localNets; net->base; net++)
if(htonl(net->base & net->mask) == (addr & htonl(net->mask)))
return 1;
return 0;
}
static void
clamdIsDown(void)
{
static time_t lasttime;
time_t thistime, diff;
static pthread_mutex_t time_mutex = PTHREAD_MUTEX_INITIALIZER;
cli_errmsg(_("No response from any clamd server - your AV system is not scanning emails\n"));
if(use_syslog)
syslog(LOG_ERR, _("No response from any clamd server - your AV system is not scanning emails"));
time(&thistime);
pthread_mutex_lock(&time_mutex);
diff = thistime - lasttime;
pthread_mutex_unlock(&time_mutex);
if(diff >= (time_t)(15 * 60)) {
char cmd[128];
FILE *sendmail;
snprintf(cmd, sizeof(cmd) - 1, "%s -t", SENDMAIL_BIN);
sendmail = popen(cmd, "w");
if(sendmail) {
fprintf(sendmail, "To: %s\n", postmaster);
fprintf(sendmail, "From: %s\n", postmaster);
fputs(_("Subject: ClamAV Down\n"), sendmail);
fputs("Priority: High\n\n", sendmail);
fputs(_("This is an automatic message\n\n"), sendmail);
if(numServers == 1)
fputs(_("The clamd program cannot be contacted.\n"), sendmail);
else
fputs(_("No clamd server can be contacted.\n"), sendmail);
fputs(_("Emails may not be being scanned, please check your servers.\n"), sendmail);
if(pclose(sendmail) == 0) {
pthread_mutex_lock(&time_mutex);
time(&lasttime);
pthread_mutex_unlock(&time_mutex);
}
}
}
}
#ifdef SESSION
static void *
watchdog(void *a)
{
static pthread_mutex_t watchdog_mutex = PTHREAD_MUTEX_INITIALIZER;
assert(cmdSockets != NULL);
while(!quitting) {
int i;
struct timespec ts;
struct timeval tp;
gettimeofday(&tp, NULL);
ts.tv_sec = tp.tv_sec + readTimeout - 1;
ts.tv_nsec = tp.tv_usec * 1000;
cli_dbgmsg("watchdog sleeps\n");
pthread_mutex_lock(&watchdog_mutex);
switch(pthread_cond_timedwait(&watchdog_cond, &watchdog_mutex, &ts)) {
case ETIMEDOUT:
case 0:
break;
default:
perror("pthread_cond_timedwait");
}
cli_dbgmsg("watchdog wakes\n");
pthread_mutex_unlock(&watchdog_mutex);
pthread_mutex_lock(&sstatus_mutex);
for(i = 0; i < max_children; i++) {
const int sock = cmdSockets[i];
cli_dbgmsg("watchdog: check server %d\n", i);
if((n_children == 0) && (cmdSocketsStatus[i] == CMDSOCKET_FREE)) {
if(send(sock, "PING\n", 5, 0) == 5) {
char buf[6];
buf[5] = '\0';
if(clamd_recv(sock, buf, 5) != 5)
cmdSocketsStatus[i] = CMDSOCKET_DOWN;
else if(strcmp(buf, "PONG\n") != 0) {
cli_dbgmsg("watchdog: expected \"PONG\", got \"%s\"\n", buf);
cmdSocketsStatus[i] = CMDSOCKET_DOWN;
}
} else {
perror("send");
cmdSocketsStatus[i] = CMDSOCKET_DOWN;
}
if(cmdSocketsStatus[i] == CMDSOCKET_DOWN)
cli_warnmsg("Session %d has gone down\n", i);
}
if(cmdSocketsStatus[i] == CMDSOCKET_DOWN) {
send(sock, "END\n", 4, 0);
close(sock);
cli_dbgmsg("Trying to restart session %d\n", i);
if(createSession(i) == 0) {
cmdSocketsStatus[i] = CMDSOCKET_FREE;
cli_warnmsg("Session %d restarted OK\n", i);
}
}
}
for(i = 0; i < max_children; i++)
if(cmdSocketsStatus[i] != CMDSOCKET_DOWN)
break;
if(i == max_children)
clamdIsDown();
pthread_mutex_unlock(&sstatus_mutex);
}
cli_errmsg("watchdog quits\n");
return NULL;
}
#endif
static const struct {
const char *name;
int code;
} facilitymap[] = {
#ifdef LOG_AUTH
{ "LOG_AUTH", LOG_AUTH },
#endif
#ifdef LOG_AUTHPRIV
{ "LOG_AUTHPRIV", LOG_AUTHPRIV },
#endif
#ifdef LOG_CRON
{ "LOG_CRON", LOG_CRON },
#endif
#ifdef LOG_DAEMON
{ "LOG_DAEMON", LOG_DAEMON },
#endif
#ifdef LOG_FTP
{ "LOG_FTP", LOG_FTP },
#endif
#ifdef LOG_KERN
{ "LOG_KERN", LOG_KERN },
#endif
#ifdef LOG_LPR
{ "LOG_LPR", LOG_LPR },
#endif
#ifdef LOG_MAIL
{ "LOG_MAIL", LOG_MAIL },
#endif
#ifdef LOG_NEWS
{ "LOG_NEWS", LOG_NEWS },
#endif
#ifdef LOG_AUTH
{ "LOG_AUTH", LOG_AUTH },
#endif
#ifdef LOG_SYSLOG
{ "LOG_SYSLOG", LOG_SYSLOG },
#endif
#ifdef LOG_USER
{ "LOG_USER", LOG_USER },
#endif
#ifdef LOG_UUCP
{ "LOG_UUCP", LOG_UUCP },
#endif
#ifdef LOG_LOCAL0
{ "LOG_LOCAL0", LOG_LOCAL0 },
#endif
#ifdef LOG_LOCAL1
{ "LOG_LOCAL1", LOG_LOCAL1 },
#endif
#ifdef LOG_LOCAL2
{ "LOG_LOCAL2", LOG_LOCAL2 },
#endif
#ifdef LOG_LOCAL3
{ "LOG_LOCAL3", LOG_LOCAL3 },
#endif
#ifdef LOG_LOCAL4
{ "LOG_LOCAL4", LOG_LOCAL4 },
#endif
#ifdef LOG_LOCAL5
{ "LOG_LOCAL5", LOG_LOCAL5 },
#endif
#ifdef LOG_LOCAL6
{ "LOG_LOCAL6", LOG_LOCAL6 },
#endif
#ifdef LOG_LOCAL7
{ "LOG_LOCAL7", LOG_LOCAL7 },
#endif
{ NULL, -1 }
};
static int
logg_facility(const char *name)
{
int i;
for(i = 0; facilitymap[i].name; i++)
if(strcasecmp(facilitymap[i].name, name) == 0)
return facilitymap[i].code;
return -1;
}
static void
quit(void)
{
#ifdef SESSION
int i;
quitting++;
if(use_syslog)
syslog(LOG_INFO, _("Stopping %s"), clamav_version);
pthread_mutex_lock(&sstatus_mutex);
for(i = 0; i < max_children; i++) {
const int sock = cmdSockets[i];
cli_dbgmsg("quit: close server %d\n", i);
if(cmdSocketsStatus[i] == CMDSOCKET_FREE) {
send(sock, "END\n", 4, 0);
shutdown(sock, SHUT_WR);
cmdSocketsStatus[i] = CMDSOCKET_DOWN;
pthread_mutex_unlock(&sstatus_mutex);
close(sock);
pthread_mutex_lock(&sstatus_mutex);
}
}
pthread_mutex_unlock(&sstatus_mutex);
#else
quitting++;
if(use_syslog)
syslog(LOG_INFO, _("Stopping %s"), clamav_version);
#endif
broadcast(_("Stopping clamav-milter"));
}
static void
broadcast(const char *mess)
{
struct sockaddr_in s;
if(broadcastSock < 0)
return;
memset(&s, '\0', sizeof(struct sockaddr_in));
s.sin_family = AF_INET;
s.sin_port = (in_port_t)htons(tcpSocket);
s.sin_addr.s_addr = htonl(INADDR_BROADCAST);
if(sendto(broadcastSock, mess, strlen(mess), 0, (struct sockaddr *)&s, sizeof(struct sockaddr_in)) < 0)
perror("sendto");
}