clamav-milter/clamav-milter.c
b151ef55
 /*
  * clamav-milter.c
  *	.../clamav-milter/clamav-milter.c
  *
  *  Copyright (C) 2003 Nigel Horne <njh@bandsman.co.uk>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
  *
f02c3c82
  * Install into /usr/local/sbin/clamav-milter
b151ef55
  *
71f25024
  * For installation instructions see the file INSTALL that came with this file
b151ef55
  */
 
02e79d0c
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
e82a5185
 #define	CM_VERSION	VERSION
 
b151ef55
 #include "defaults.h"
c96f7461
 #include "cfgparser.h"
a87be00b
 #include "target.h"
244be94f
 #include "str.h"
64d9c7a1
 #include "../libclamav/others.h"
a87be00b
 #include "strrcpy.h"
64d9c7a1
 #include "clamav.h"
a87be00b
 #include "../libclamav/table.h"
b151ef55
 
 #ifndef	CL_DEBUG
 #define	NDEBUG
 #endif
 
 #include <stdio.h>
 #include <sysexits.h>
709d1342
 #include <sys/types.h>
 #include <sys/stat.h>
b151ef55
 #include <syslog.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/wait.h>
 #include <assert.h>
d7e0b4e6
 #include <sys/socket.h>
fce82877
 #include <netinet/in.h>
e5bb3b37
 #include <net/if.h>
b151ef55
 #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>
1f025849
 #include <regex.h>
 #include <fcntl.h>
bb9979e6
 #include <pwd.h>
434012b8
 #include <grp.h>
bbc9d684
 #include <netdb.h>
8c42185e
 #include <sys/param.h>
b151ef55
 
21e6b430
 #if HAVE_MMAP
 #if HAVE_SYS_MMAN_H
 #include <sys/mman.h>
 #else /* HAVE_SYS_MMAN_H */
 #undef HAVE_MMAP
 #endif
 #endif
 
4e136199
 #ifdef	C_LINUX
54ba1fe6
 #include <sys/sendfile.h>
4e136199
 #include <libintl.h>
d7e0b4e6
 #include <locale.h>
4e136199
 
 #define	gettext_noop(s)	s
 #define	_(s)	gettext(s)
 #define	N_(s)	gettext_noop(s)
 
 #else
 
 #define	_(s)	s
 #define	N_(s)	s
 
 #endif
 
b7f54bf2
 #ifdef	WITH_TCPWRAP
 #include <tcpd.h>
35d98cf9
 
 int	allow_severity = LOG_DEBUG;
e27af651
 int	deny_severity = LOG_NOTICE;
35d98cf9
 
b7f54bf2
 #endif
 
9c8806fb
 #ifndef	CL_DEBUG
 static	const	char	*logFile;
ac8dfb6a
 static	char	console[] = "/dev/console";
9c8806fb
 #endif
 
244be94f
 #if defined(CL_DEBUG) && defined(C_LINUX)
 #include <sys/resource.h>
 #endif
 
b151ef55
 #define _GNU_SOURCE
4e136199
 #include <getopt.h>
b151ef55
 
b696653a
 #ifndef	SENDMAIL_BIN
 #define	SENDMAIL_BIN	"/usr/lib/sendmail"
 #endif
 
b7f54bf2
 #ifndef HAVE_IN_PORT_T
 typedef	unsigned short	in_port_t;
 #endif
 
78a78fcc
 #ifndef	HAVE_IN_ADDR_T
 typedef	unsigned int	in_addr_t;
 #endif
 
09e761fb
 #define	VERSION_LENGTH	128
 
e1b478ab
 /*#define	SESSION	/*
f5d23f2e
 		 * Keep one command connection open to clamd, otherwise a new
 		 * command connection is created for each new email
7ca2a6cf
 		 *
 		 * FIXME: When SESSIONS are open, freshclam can hang when
 		 *	notfying clamd of an update. This is most likely to be a
 		 *	problem with the implementation of SESSIONS on clamd.
 		 *	The problem seems worst on BSD.
384942c2
 		 *
 		 * Note that clamd is buggy and can hang or even crash if you
 		 *	send SESSION command so be aware
f5d23f2e
 		 */
 
b151ef55
 /*
  * TODO: optional: xmessage on console when virus stopped (SNMP would be real nice!)
434012b8
  *	Having said that, with LogSysLog you can (on Linux) configure the system
  *	to get messages on the system console, see syslog.conf(5), also you
c857005e
  *	can use wall(1) in the VirusEvent entry in clamd.conf
b151ef55
  * TODO: build with libclamav.so rather than libclamav.a
02e79d0c
  * TODO: Decide action (bounce, discard, reject etc.) based on the virus
  *	found. Those with faked addresses, such as SCO.A want discarding,
  *	others could be bounced properly.
1392cf22
  * TODO: Encrypt mails sent to clamd to stop sniffers
063b6c33
  * TODO: Test with IPv6
ae17ae8e
  * TODO: Files can be scanned with "SCAN" not "STREAM" if clamd is on the same
ebb16454
  *	machine when talking via INET domain socket.
ae17ae8e
  * TODO: Load balancing, allow local machine to talk via UNIX domain socket.
1ae303c2
  * TODO: allow each line in the whitelist file to specify a quarantine email
  *	address
b151ef55
  */
 
d9b674b2
 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;
 
b151ef55
 /*
503b2956
  * Local addresses are those not scanned if --local is not set
  * 127.0.0.0 is not in this table since that's goverend by --outgoing
  * Andy Fiddaman <clam@fiddaman.net> added 69.254.0.0/16
  *	(Microsoft default DHCP)
  *
c857005e
  * TODO: read this table in from a file (clamd.conf?)
503b2956
  */
9a5dd76f
 #define PACKADDR(a, b, c, d) (((uint32_t)(a) << 24) | ((b) << 16) | ((c) << 8) | (d))
5a4966af
 #define MAKEMASK(bits)	((uint32_t)(0xffffffff << (bits)))
503b2956
 
 static const struct cidr_net {
 	uint32_t	base;
 	uint32_t	mask;
 } localNets[] = {
 	/*{ PACKADDR(127,   0,   0,   0), MAKEMASK(24) },	/*   127.0.0.0/24 */
 	{ PACKADDR(192, 168,   0,   0), MAKEMASK(16) },	/* 192.168.0.0/16 */
 	{ PACKADDR( 10,   0,   0,   0), MAKEMASK(24) },	/*    10.0.0.0/24 */
 	{ PACKADDR(172,  16,   0,   0), MAKEMASK(20) },	/*  172.16.0.0/20 */
 	{ PACKADDR(169,  254,  0,   0), MAKEMASK(16) },	/* 169.254.0.0/16 */
 	{ 0, 0 }
 };
 
 /*
c840fa41
  * Each libmilter thread has one of these
b151ef55
  */
 struct	privdata {
 	char	*from;	/* Who sent the message */
384942c2
 	char	*subject;	/* Original subject */
 	char	*sender;	/* Secretary - often used in mailing lists */
 	char	*helo;		/* The HELO string */
b151ef55
 	char	**to;	/* Who is the message going to */
 	int	numTo;	/* Number of people the message is going to */
f5d23f2e
 #ifndef	SESSION
b151ef55
 	int	cmdSocket;	/*
 				 * Socket to send/get commands e.g. PORT for
 				 * dataSocket
 				 */
f5d23f2e
 #endif
b151ef55
 	int	dataSocket;	/* Socket to send data to clamd */
3454ad43
 	char	*filename;	/* Where to store the message in quarantine */
3166c010
 	u_char	*body;		/* body of the message if Sflag is set */
 	size_t	bodyLen;	/* number of bytes in body */
d9b674b2
 	header_list_t headers;	/* Message headers */
5691e168
 	long	numBytes;	/* Number of bytes sent so far */
063b6c33
 	char	*received;	/* keep track of received from */
a121491a
 	const	char	*rejectCode;	/* 550 or 554? */
d69535ae
 	char	*messageID;	/* sendmailID */
a121491a
 	int	discard;	/*
 				 * looks like the remote end is playing ping
 				 * pong with us
 				 */
51cddddf
 	int	statusCount;	/* number of X-Virus-Status headers */
39378e7f
 	int	serverNumber;	/* Index into serverIPs */
c840fa41
 	struct  cl_node *root; /* database of viruses used to scan this one */
b151ef55
 };
 
85563fe5
 #ifdef	SESSION
9c8806fb
 static	int		createSession(unsigned int s);
85563fe5
 #else
244be94f
 static	int		pingServer(int serverNumber);
85563fe5
 #endif
244be94f
 static	int		findServer(void);
b151ef55
 static	sfsistat	clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr);
384942c2
 #ifdef	CL_DEBUG
 static	sfsistat	clamfi_helo(SMFICTX *ctx, char *helostring);
 #endif
b151ef55
 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);
64d5612d
 static	void		clamfi_free(struct privdata *privdata);
0cb97bcb
 static	int		clamfi_send(struct privdata *privdata, size_t len, const char *format, ...);
cdbe607d
 static	int		clamd_recv(int sock, char *buf, size_t len);
 static	off_t		updateSigFile(void);
d9b674b2
 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);
6489ba59
 static	int	connect2clamd(struct privdata *privdata);
384942c2
 static	int	sendToFrom(struct privdata *privdata);
062dddc9
 static	void	checkClamd(void);
0b79c1b7
 static	int	sendtemplate(SMFICTX *ctx, const char *filename, FILE *sendmail, const char *virusname);
dd63090a
 static	int	qfile(struct privdata *privdata, const char *sendmailId, const char *virusname);
54ba1fe6
 static	int	move(const char *oldfile, const char *newfile);
ced83bee
 static	void	setsubject(SMFICTX *ctx, const char *virusname);
6d113e37
 static	int	clamfi_gethostbyname(const char *hostname, struct hostent *hp, char *buf, size_t len);
503b2956
 static	int	isLocalAddr(in_addr_t addr);
85563fe5
 static	void	clamdIsDown(void);
 static	void	*watchdog(void *a);
d7e0b4e6
 static	int	logg_facility(const char *name);
78a78fcc
 static	void	quit(void);
 static	void	broadcast(const char *mess);
cf6c41b7
 static	int	loadDatabase(void);
b151ef55
 
09e761fb
 #ifdef	SESSION
c78ec4f3
 static	pthread_mutex_t	version_mutex = PTHREAD_MUTEX_INITIALIZER;
b1dc4b1b
 static	char	**clamav_versions;	/* max_children elements in the array */
09e761fb
 #define	clamav_version	(clamav_versions[0])
 #else
 static	char	clamav_version[VERSION_LENGTH + 1];
 #endif
b5d15e64
 static	int	fflag = 0;	/* force a scan, whatever */
b151ef55
 static	int	oflag = 0;	/* scan messages from our machine? */
 static	int	lflag = 0;	/* scan messages from our site? */
1ae303c2
 static	const	char	*progname;	/* our name - usually clamav-milter */
cf6c41b7
 
7ca2a6cf
 /* Variables for --external */
 static	int	external = 0;	/* scan messages ourself or use clamd? */
1ae303c2
 static	pthread_mutex_t	root_mutex = PTHREAD_MUTEX_INITIALIZER;
ccf2bd40
 static	struct	cl_node	*root = NULL;
 static	struct	cl_limits	limits;
 static	struct	cl_stat	dbstat;
1d4e7005
 static	int	options = CL_SCAN_STDOPT;
ccf2bd40
 
b151ef55
 static	int	bflag = 0;	/*
 				 * send a failure (bounce) message to the
 				 * sender. This probably isn't a good idea
 				 * since most reply addresses will be fake
9c8806fb
 				 *
 				 * TODO: Perhaps we can have an option to
 				 * bounce outgoing mail, but not incoming?
b151ef55
 				 */
e5bb3b37
 static	const	char	*iface;	/*
78a78fcc
 				 * Broadcast a message when a virus is found,
 				 * this allows remote network management
 				 */
e5bb3b37
 static	int	broadcastSock = -1;
1c3f1ce1
 static	int	pflag = 0;	/*
 				 * Send a warning to the postmaster only,
 				 * this means user's won't be told when someone
 				 * sent them a virus
 				 */
 static	int	qflag = 0;	/*
 				 * Send no warnings when a virus is found,
 				 * this means that the only log of viruses
 				 * found is the syslog, so it's best to
c857005e
 				 * enable LogSyslog in clamd.conf
1c3f1ce1
 				 */
e6bffccb
 static	int	Sflag = 0;	/*
 				 * Add a signature to each message that
 				 * has been scanned
 				 */
cdbe607d
 static	const	char	*sigFilename;	/*
 				 * File where the scanned message signature
 				 * can be found
 				 */
3454ad43
 static	char	*quarantine;	/*
bb9979e6
 				 * If a virus is found in an email redirect
 				 * it to this account
 				 */
3454ad43
 static	char	*quarantine_dir; /*
 				 * Path to store messages before scanning.
 				 * Infected ones will be left there.
 				 */
3613bd91
 static	int	nflag = 0;	/*
 				 * Don't add X-Virus-Scanned to header. Patch
 				 * from Dirk Meyer <dirk.meyer@dinoex.sub.org>
 				 */
a7164828
 static	int	rejectmail = 1;	/*
 				 * Send a 550 rejection when a virus is
 				 * found
 				 */
d9b674b2
 static	int	hflag = 0;	/*
 				 * Include original message headers in
 				 * report
 				 */
b0a42ec6
 static	int	cl_error = SMFIS_TEMPFAIL; /*
 				 * If an error occurs, return
 				 * this status. Allows messages
 				 * to be passed through
 				 * unscanned in the event of
 				 * an error. Patch from
 				 * Joe Talbott <josepht@cstone.net>
 				 */
cc511c7a
 static	int	readTimeout = CL_DEFAULT_SCANTIMEOUT; /*
d2efb3fa
 				 * number of seconds to wait for clamd to
c857005e
 				 * respond, see ReadTimeout in clamd.conf
d2efb3fa
 				 */
c857005e
 static	long	streamMaxLength = -1;	/* StreamMaxLength from clamd.conf */
3bc05064
 static	int	logClean = 1;	/*
 				 * Add clean items to the log file
 				 */
4e136199
 static	char	*signature = N_("-- \nScanned by ClamAv - http://www.clamav.net\n");
cdbe607d
 static	time_t	signatureStamp;
1392cf22
 static	char	*templatefile;	/* e-mail to be sent when virus detected */
9a5dd76f
 static	const char	*tmpdir;
b151ef55
 
 #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;
e483f51a
 static	volatile	unsigned	int	n_children = 0;
b151ef55
 static	unsigned	int	max_children = 0;
c840fa41
 static	int	child_timeout = 300;	/* number of seconds to wait for
ad31761c
 					 * a child to die. Set to 0 to
 					 * wait forever
 					 */
ced83bee
 static	int	dont_wait = 0;	/*
 				 * If 1 send retry later to the remote end
 				 * if max_chilren is exceeded, otherwise we
 				 * wait for the number to go down
 				 */
 static	int	advisory = 0;	/*
 				 * Run clamav-milter in advisory mode - viruses
 				 * are flagged rather than deleted. Incompatible
 				 * with quarantine options
 				 */
ad5eb067
 static	int	detect_forged_local_address;	/*
 				 * for incoming only mail servers, drop emails
 				 * claiming to be from us that must be false
 				 * Requires that -o, -l or -f are NOT given
 				 */
31d36bd3
 static	short	use_syslog = 0;
062dddc9
 static	const	char	*pidFile;
b151ef55
 static	int	logVerbose = 0;
 static	struct	cfgstruct	*copt;
ce14cc31
 static	const	char	*localSocket;	/* milter->clamd comms */
 static	in_port_t	tcpSocket;	/* milter->clamd comms */
 static	char	*port = NULL;	/* sendmail->milter comms */
f5d23f2e
 
244be94f
 static	const	char	*serverHostNames = "127.0.0.1";
 static	long	*serverIPs;	/* IPv4 only */
98dcfffc
 static	int	numServers;	/* number of elements in serverIPs array */
85563fe5
 
f5d23f2e
 #ifdef	SESSION
ebb16454
 static	struct	session {
 	int	sock;	/* fd */
 	enum	{ CMDSOCKET_FREE, CMDSOCKET_INUSE, CMDSOCKET_DOWN }	status;
98dcfffc
 } *sessions;	/* max_children elements in the array */
85563fe5
 static	pthread_mutex_t sstatus_mutex = PTHREAD_MUTEX_INITIALIZER;
 
f5d23f2e
 #endif	/*SESSION*/
 
83d6495b
 static	pthread_cond_t	watchdog_cond = PTHREAD_COND_INITIALIZER;
 
ae17ae8e
 #ifndef	SHUT_RD
 #define	SHUT_RD		0
 #endif
 #ifndef	SHUT_WR
 #define	SHUT_WR		1
 #endif
 #ifndef	INET_ADDRSTRLEN
 #define	INET_ADDRSTRLEN	16
 #endif
 
c6259ac5
 static	const	char	*postmaster = "postmaster";
063b6c33
 static	const	char	*from = "MAILER-DAEMON";
78a78fcc
 static	int	quitting;
b151ef55
 
a87be00b
 static	const	char	*whitelistFile;	/*
 					 * file containing destination email
 					 * addresses that we don't scan
 					 */
 static	const	char	*sendmailCF;	/* location of sendmail.cf to verify */
9f47e7ab
 static	const	char	*pidfile;
b14e9e77
 
3c98c93b
 #ifdef	CL_DEBUG
 #if __GLIBC__ == 2 && __GLIBC_MINOR__ >= 1
 #define HAVE_BACKTRACE
 #endif
 #endif
 
e483f51a
 static	void	sigsegv(int sig);
 
3c98c93b
 #ifdef HAVE_BACKTRACE
 #include <execinfo.h>
 
0a7e8089
 static	void	print_trace(void);
3c98c93b
 
 #define	BACKTRACE_SIZE	200
 
 #endif
 
21e6b430
 static	int	verifyIncomingSocketName(const char *sockName);
5aeb0d51
 static	int	isWhitelisted(const char *emailaddress);
9c8806fb
 static	void	logger(const char *mess);
21e6b430
 
1ae303c2
 short	logg_time, logg_lock, logok;
 int	logg_size;
 
b151ef55
 static void
 help(void)
 {
 	printf("\n\tclamav-milter version %s\n", CM_VERSION);
1392cf22
 	puts("\tCopyright (C) 2004 Nigel Horne <njh@despammed.com>\n");
b151ef55
 
4e136199
 	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."));
e5bb3b37
 	puts(_("\t--broadcast\t\t-B [IFACE]\tBroadcast to a network manager when a virus is found."));
4e136199
 	puts(_("\t--config-file=FILE\t-c FILE\tRead configuration from FILE."));
 	puts(_("\t--debug\t\t\t-D\tPrint debug messages."));
ad5eb067
 	puts(_("\t--detect-forged-local-address\t-L\tReject mails that claim to be from us."));
4e136199
 	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."));
7ca2a6cf
 	puts(_("\t--external\t\t-e\tUse an external scanner (usually clamd)."));
4e136199
 	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."));
58ae48c5
 	puts(_("\t--max-childen\t\t-m\tMaximum number of concurrent scans."));
4e136199
 	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)."));
a87be00b
 	puts(_("\t--sendmail-cf=FILE\t\tLocation of the sendmail.cf file to verify"));
4e136199
 	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."));
a87be00b
 	puts(_("\t--whitelist-file=FILE\t-W FILE\tLocation of the file of whitelisted addresses"));
4e136199
 	puts(_("\t--version\t\t-V\tPrint the version number of this software."));
b151ef55
 #ifdef	CL_DEBUG
4e136199
 	puts(_("\t--debug-level=n\t\t-x n\tSets the debug level to 'n'."));
b151ef55
 #endif
4e136199
 	puts(_("\nFor more information type \"man clamav-milter\"."));
 	puts(_("Report bugs to bugs@clamav.net."));
b151ef55
 }
 
 int
 main(int argc, char **argv)
 {
 	extern char *optarg;
e5bb3b37
 	int i, Bflag = 0;
b151ef55
 	const char *cfgfile = CL_DEFAULT_CFG;
1d4e7005
 	const struct cfgstruct *cpt;
09e761fb
 	char version[VERSION_LENGTH + 1];
85563fe5
 	pthread_t tid;
ac8dfb6a
 #ifndef	CL_DEBUG
 	int consolefd;
 #endif
b151ef55
 	struct smfiDesc smfilter = {
 		"ClamAv", /* filter name */
 		SMFI_VERSION,	/* version code -- leave untouched */
a3a024c5
 		SMFIF_ADDHDRS|SMFIF_CHGHDRS,	/* flags - we add and deleted headers */
b151ef55
 		clamfi_connect, /* connection callback */
384942c2
 #ifdef	CL_DEBUG
 		clamfi_helo,	/* HELO filter callback */
 #else
 		NULL,
 #endif
b151ef55
 		clamfi_envfrom, /* envelope sender filter callback */
 		clamfi_envrcpt, /* envelope recipient filter callback */
 		clamfi_header, /* header filter callback */
 		clamfi_eoh, /* end of header callback */
 		clamfi_body, /* body filter callback */
 		clamfi_eom, /* end of message callback */
 		clamfi_abort, /* message aborted callback */
 		clamfi_close, /* connection cleanup callback */
 	};
 
244be94f
 #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
f0d6f5a1
 	/*
09e761fb
 	 * Temporarily enter guessed value into version, will
f0d6f5a1
 	 * be overwritten later by the value returned by clamd
 	 */
09e761fb
 	snprintf(version, sizeof(version) - 1,
dd0d5a8c
 		"ClamAV version %s, clamav-milter version %s",
 		VERSION, CM_VERSION);
 
1ae303c2
 	progname = strrchr(argv[0], '/');
 	if(progname)
 		progname++;
 	else
 		progname = "clamav-milter";
 
4e136199
 #ifdef	C_LINUX
 	setlocale(LC_ALL, "");
1ae303c2
 	bindtextdomain(progname, DATADIR"/clamav-milter/locale");
 	textdomain(progname);
4e136199
 #endif
 
b151ef55
 	for(;;) {
 		int opt_index = 0;
 #ifdef	CL_DEBUG
a87be00b
 		const char *args = "a:AbB:c:CdDefF:lLm:nNop:PqQ:hHs:St:T:U:VwW:x:0:";
b151ef55
 #else
a87be00b
 		const char *args = "a:AbB:c:CdDefF:lLm:nNop:PqQ:hHs:St:T:U:VwW:0:";
b151ef55
 #endif
bb9979e6
 
b151ef55
 		static struct option long_options[] = {
 			{
b61a0781
 				"from", 2, NULL, 'a'
063b6c33
 			},
 			{
ced83bee
 				"advisory", 0, NULL, 'A'
 			},
 			{
b151ef55
 				"bounce", 0, NULL, 'b'
 			},
 			{
e5bb3b37
 				"broadcast", 2, NULL, 'B'
78a78fcc
 			},
 			{
b151ef55
 				"config-file", 1, NULL, 'c'
 			},
 			{
ad5eb067
 				"detect-forged-local-address", 0, NULL, 'L'
 			},
 			{
3bc05064
 				"dont-log-clean", 0, NULL, 'C'
 			},
 			{
b0a42ec6
 				"dont-scan-on-error", 0, NULL, 'd'
 			},
 			{
ced83bee
 				"dont-wait", 0, NULL, 'w'
 			},
 			{
244be94f
 				"debug", 0, NULL, 'D'
 			},
 			{
7ca2a6cf
 				"external", 0, NULL, 'e'
 			},
 			{
3454ad43
 				"force-scan", 0, NULL, 'f'
b5d15e64
 			},
 			{
cbd7686b
 				"headers", 0, NULL, 'H'
d9b674b2
 			},
 			{
b151ef55
 				"help", 0, NULL, 'h'
 			},
 			{
679ff9e7
 				"pidfile", 1, NULL, 'i'
 			},
 			{
b151ef55
 				"local", 0, NULL, 'l'
 			},
 			{
a7164828
 				"noreject", 0, NULL, 'N'
 			},
 			{
3613bd91
 				"noxheader", 0, NULL, 'n'
 			},
 			{
b151ef55
 				"outgoing", 0, NULL, 'o'
 			},
 			{
434012b8
 				"postmaster", 1, NULL, 'p'
c6259ac5
 			},
 			{
1c3f1ce1
 				"postmaster-only", 0, NULL, 'P',
 			},
 			{
 				"quiet", 0, NULL, 'q'
 			},
 			{
bb9979e6
 				"quarantine", 1, NULL, 'Q',
 			},
 			{
3454ad43
 				"quarantine-dir", 1, NULL, 'U',
 			},
 			{
b151ef55
 				"max-children", 1, NULL, 'm'
 			},
 			{
 				"server", 1, NULL, 's'
 			},
 			{
cdbe607d
 				"sign", 0, NULL, 'S'
 			},
 			{
 				"signature-file", 1, NULL, 'F'
e6bffccb
 			},
 			{
1392cf22
 				"template-file", 1, NULL, 't'
 			},
 			{
ad31761c
 				"timeout", 1, NULL, 'T'
 			},
 			{
a87be00b
 				"whitelist-file", 1, NULL, 'W'
 			},
 			{
b151ef55
 				"version", 0, NULL, 'V'
 			},
a87be00b
 			{
 				"sendmail-cf", 1, NULL, '0'
 			},
b151ef55
 #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) {
063b6c33
 			case 'a':	/* e-mail errors from here */
b61a0781
 				/*
 				 * optarg is optional - if you give --from
 				 * then the --from is set to the orginal,
 				 * probably forged, email address
 				 */
063b6c33
 				from = optarg;
 				break;
ced83bee
 			case 'A':
 				advisory++;
 				break;
b151ef55
 			case 'b':	/* bounce worms/viruses */
 				bflag++;
 				break;
78a78fcc
 			case 'B':	/* broadcast */
 				Bflag++;
e5bb3b37
 				if(optarg)
 					iface = optarg;
78a78fcc
 				break;
c857005e
 			case 'c':	/* where is clamd.conf? */
b151ef55
 				cfgfile = optarg;
 				break;
3bc05064
 			case 'C':	/* dont log clean */
 				logClean = 0;
 				break;
b0a42ec6
 			case 'd':	/* don't scan on error */
 				cl_error = SMFIS_ACCEPT;
 				break;
244be94f
 			case 'D':	/* enable debug messages */
 				cl_debug();
 				break;
7ca2a6cf
 			case 'e':	/* use clamd */
 				external++;
 				break;
b5d15e64
 			case 'f':	/* force the scan */
 				fflag++;
 				break;
b151ef55
 			case 'h':
 				help();
 				return EX_OK;
d9b674b2
 			case 'H':
 				hflag++;
 				break;
679ff9e7
 			case 'i':	/* pidfile */
 				pidfile = optarg;
 				break;
b151ef55
 			case 'l':	/* scan mail from the lan */
 				lflag++;
 				break;
ad5eb067
 			case 'L':	/* detect forged local addresses */
 				detect_forged_local_address++;
 				break;
c6259ac5
 			case 'm':	/* maximum number of children */
 				max_children = atoi(optarg);
 				break;
3613bd91
 			case 'n':	/* don't add X-Virus-Scanned */
 				nflag++;
a3a024c5
 				smfilter.xxfi_flags &= ~(SMFIF_ADDHDRS|SMFIF_CHGHDRS);
3613bd91
 				break;
a7164828
 			case 'N':	/* Do we reject mail or silently drop it */
 				rejectmail = 0;
 				break;
b151ef55
 			case 'o':	/* scan outgoing mail */
 				oflag++;
 				break;
c6259ac5
 			case 'p':	/* postmaster e-mail address */
 				postmaster = optarg;
b151ef55
 				break;
1c3f1ce1
 			case 'P':	/* postmaster only */
 				pflag++;
 				break;
 			case 'q':	/* send NO notification email */
 				qflag++;
 				break;
bb9979e6
 			case 'Q':	/* quarantine e-mail address */
 				quarantine = optarg;
 				smfilter.xxfi_flags |= SMFIF_CHGHDRS|SMFIF_ADDRCPT|SMFIF_DELRCPT;
 				break;
b151ef55
 			case 's':	/* server running clamd */
7ca2a6cf
 #ifdef	notdef	/* don't define - forces --external to be listed first :-( */
 				if(!external) {
 					fputs("--server can only be used with --external\n", stderr);
ccf2bd40
 					return EX_USAGE;
 				}
7ca2a6cf
 #endif
244be94f
 				serverHostNames = optarg;
b151ef55
 				break;
cdbe607d
 			case 'F':	/* signature file */
 				sigFilename = optarg;
 				signature = NULL;
 				/* fall through */
e6bffccb
 			case 'S':	/* sign */
 				smfilter.xxfi_flags |= SMFIF_CHGBODY;
 				Sflag++;
 				break;
1392cf22
 			case 't':	/* e-mail template file */
 				templatefile = optarg;
 				break;
ad31761c
 			case 'T':	/* time to wait for child to die */
 				child_timeout = atoi(optarg);
 				break;
3454ad43
 			case 'U':	/* quarantine path */
 				quarantine_dir = optarg;
 				break;
b151ef55
 			case 'V':
09e761fb
 				puts(version);
b151ef55
 				return EX_OK;
ced83bee
 			case 'w':
 				dont_wait++;
 				break;
a87be00b
 			case 'W':
 				whitelistFile = optarg;
 				break;
 			case '0':
 				sendmailCF = optarg;
 				break;
b151ef55
 #ifdef	CL_DEBUG
 			case 'x':
 				debug_level = atoi(optarg);
 				break;
 #endif
 			default:
 #ifdef	CL_DEBUG
7ca2a6cf
 				fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-e] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-s SERVER] [-S] [-x#] [-U PATH] socket-addr\n", argv[0]);
b151ef55
 #else
7ca2a6cf
 				fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-e] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-s SERVER] [-S] [-U PATH] socket-addr\n", argv[0]);
b151ef55
 #endif
 				return EX_USAGE;
 		}
 	}
 
7ca2a6cf
 	/* FIXME: error if --servers and --external is not given */
 	/* TODO: support freshclam's daemon notify if --external is not given */
 
48e8856b
 	if(optind == argc) {
4e136199
 		fprintf(stderr, _("%s: No socket-addr given\n"), argv[0]);
b151ef55
 		return EX_USAGE;
 	}
 	port = argv[optind];
 
21e6b430
 	if(verifyIncomingSocketName(port) < 0) {
 		fprintf(stderr, _("%s: socket-addr (%s) doesn't agree with sendmail.cf\n"), argv[0], port);
 		return EX_CONFIG;
 	}
fa7e490e
 	if(strncasecmp(port, "inet:", 5) == 0)
 		if(!lflag) {
 			/*
 			 * Barmy but true. It seems that clamfi_connect will,
 			 * in this case, get the IP address of the machine
 			 * running sendmail, not of the machine sending the
 			 * mail, so the remote end will be a local address so
 			 * we must scan by enabling --local
c6a870ad
 			 *
 			 * TODO: this is probably not needed if the remote
 			 * machine is localhost, need to check though
fa7e490e
 			 */
 			fprintf(stderr, _("%s: when using inet: connection to sendmail you must enable --local\n"), argv[0]);
 			return EX_USAGE;
 		}
21e6b430
 
b151ef55
 	/*
 	 * Sanity checks on the clamav configuration file
 	 */
819c7c41
 	if((copt = parsecfg(cfgfile, 1)) == NULL) {
4e136199
 		fprintf(stderr, _("%s: Can't parse the config file %s\n"),
b151ef55
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 
ad5eb067
 	if(detect_forged_local_address) {
 		if(oflag) {
 			fprintf(stderr, _("%s: --detect-forged-local-addresses is not compatible with --outgoing\n"), argv[0]);
 			return EX_CONFIG;
 		}
 		if(lflag) {
 			fprintf(stderr, _("%s: --detect-forged-local-addresses is not compatible with --local\n"), argv[0]);
 			return EX_CONFIG;
 		}
 		if(fflag) {
 			fprintf(stderr, _("%s: --detect-forged-local-addresses is not compatible with --force\n"), argv[0]);
 			return EX_CONFIG;
 		}
 	}
 
e5bb3b37
 	if(Bflag) {
 		int on;
 
 		broadcastSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
 		/*
 		 * SO_BROADCAST doesn't sent to all NICs on Linux, it only
 		 * broadcasts on eth0, which is why there's an optional argument
 		 * to --broadcast to say which NIC to broadcast on. You can use
 		 * SO_BINDTODEVICE to get around that, but you need to have
 		 * uid == 0 for that
 		 */
 		on = 1;
 		if(setsockopt(broadcastSock, SOL_SOCKET, SO_BROADCAST, (int *)&on, sizeof(on)) < 0) {
 			perror("setsockopt");
 			return EX_UNAVAILABLE;
 		}
 		shutdown(broadcastSock, SHUT_RD);
 	}
 
434012b8
 	/*
 	 * Drop privileges
 	 */
ac8dfb6a
 #ifndef	CL_DEBUG
 	/* Save the fd for later, open while we can */
 	consolefd = open(console, O_WRONLY);
 #endif
 
434012b8
 	if(getuid() == 0) {
e5bb3b37
 		if(iface) {
63e6694b
 #ifdef	SO_BINDTODEVICE
e5bb3b37
 			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);
63e6694b
 				return EX_CONFIG;
e5bb3b37
 			}
3eacc10d
 #else
63e6694b
 			fprintf(stderr, _("%s: The iface option to --broadcast is not supported on your operating system\n"), argv[0]);
 			return EX_CONFIG;
3eacc10d
 #endif
63e6694b
 		}
1d4e7005
 
434012b8
 		if((cpt = cfgopt(copt, "User")) != NULL) {
ccf2bd40
 			const struct passwd *user;
 
434012b8
 			if((user = getpwnam(cpt->strarg)) == NULL) {
4e136199
 				fprintf(stderr, _("%s: Can't get information about user %s\n"), argv[0], cpt->strarg);
434012b8
 				return EX_CONFIG;
 			}
bb9979e6
 
a0a82124
 			if(cfgopt(copt, "AllowSupplementaryGroups")) {
 #ifdef HAVE_INITGROUPS
 				if(initgroups(cpt->strarg, user->pw_gid) < 0) {
 					perror(cpt->strarg);
 					return EX_CONFIG;
 				}
 #else
4e136199
 				fprintf(stderr, _("%s: AllowSupplementaryGroups: initgroups not supported.\n"),
a0a82124
 					argv[0]);
 				return EX_CONFIG;
 #endif
 			} else {
 #ifdef	HAVE_SETGROUPS
 				if(setgroups(1, &user->pw_gid) < 0) {
 					perror(cpt->strarg);
 					return EX_CONFIG;
 				}
 #endif
 			}
434012b8
 
 			setgid(user->pw_gid);
d69535ae
 			if(setuid(user->pw_uid) < 0)
 				perror(cpt->strarg);
 			else
4e136199
 				cli_dbgmsg(_("Running as user %s (UID %d, GID %d)\n"),
d69535ae
 					cpt->strarg, user->pw_uid, user->pw_gid);
434012b8
 		} else
1ae303c2
 			fprintf(stderr, _("%s: running as root is not recommended (check \"User\" in %s)\n"), argv[0], cfgfile);
e5bb3b37
 	} else if(iface) {
 		fprintf(stderr, _("%s: Only root can set an interface for --broadcast\n"), argv[0]);
 		return EX_USAGE;
434012b8
 	}
e5bb3b37
 
ced83bee
 	if(advisory && quarantine) {
4e136199
 		fprintf(stderr, _("%s: Advisory mode doesn't work with quarantine mode\n"), argv[0]);
ced83bee
 		return EX_USAGE;
 	}
79f846a3
 	if(quarantine_dir) {
 		struct stat statb;
 
ced83bee
 		if(advisory) {
5a4966af
 			fprintf(stderr,
 				_("%s: Advisory mode doesn't work with quarantine directories\n"),
 				argv[0]);
 			return EX_USAGE;
 		}
 		if(strstr(quarantine_dir, "ERROR") != NULL) {
 			fprintf(stderr,
 				_("%s: the quarantine directory must not contain the string 'ERROR'\n"),
 				argv[0]);
 			return EX_USAGE;
 		}
 		if(strstr(quarantine_dir, "FOUND") != NULL) {
 			fprintf(stderr,
 				_("%s: the quarantine directory must not contain the string 'FOUND'\n"),
 				argv[0]);
 			return EX_USAGE;
 		}
 		if(strstr(quarantine_dir, "OK") != NULL) {
 			fprintf(stderr,
 				_("%s: the quarantine directory must not contain the string 'OK'\n"),
 				argv[0]);
ced83bee
 			return EX_USAGE;
 		}
79f846a3
 		if(access(quarantine_dir, W_OK) < 0) {
 			perror(quarantine_dir);
ced83bee
 			return EX_USAGE;
79f846a3
 		}
 		if(stat(quarantine_dir, &statb) < 0) {
 			perror(quarantine_dir);
ced83bee
 			return EX_USAGE;
79f846a3
 		}
 		/*
 		 * Quit if the quarantine directory is publically readable
 		 * or writeable
 		 */
 		if(statb.st_mode & 077) {
4e136199
 			fprintf(stderr, _("%s: insecure quarantine directory %s (mode 0%o)\n"),
 				argv[0], quarantine_dir, statb.st_mode & 0777);
79f846a3
 			return EX_CONFIG;
 		}
3454ad43
 	}
bb9979e6
 
cdbe607d
 	if(sigFilename && !updateSigFile())
 		return EX_USAGE;
 
b90c8762
 	if(templatefile && (access(templatefile, R_OK) < 0)) {
 		perror(templatefile);
 		return EX_CONFIG;
1392cf22
 	}
 
1ae303c2
 	if(whitelistFile && (access(whitelistFile, R_OK) < 0)) {  
 	    perror(templatefile);	
 	    return EX_CONFIG;	
 	}
 
b151ef55
 	/*
b5d15e64
 	 * patch from "Richard G. Roberto" <rgr@dedlegend.com>
 	 * If the --max-children flag isn't set, see if MaxThreads
 	 * is set in the config file
 	 */
c15d1d2c
 	if((max_children == 0) && ((cpt = cfgopt(copt, "MaxThreads")) != NULL))
31268a5c
 		max_children = cpt->numarg;
b5d15e64
 
9873b3c1
 	if((cpt = cfgopt(copt, "ReadTimeout")) != NULL) {
cc511c7a
 		readTimeout = cpt->numarg;
d2efb3fa
 
cc511c7a
 		if(readTimeout < 0) {
4e136199
 			fprintf(stderr, _("%s: ReadTimeout must not be negative in %s\n"),
d2efb3fa
 				argv[0], cfgfile);
cc511c7a
 			return EX_CONFIG;
d2efb3fa
 		}
 	}
1d4e7005
 
cc511c7a
 	if((cpt = cfgopt(copt, "StreamMaxLength")) != NULL) {
 		if(cpt->numarg < 0) {
4e136199
 			fprintf(stderr, _("%s: StreamMaxLength must not be negative in %s\n"),
cc511c7a
 				argv[0], cfgfile);
 			return EX_CONFIG;
 		}
5691e168
 		streamMaxLength = (long)cpt->numarg;
cc511c7a
 	}
ccf2bd40
 
 	if(cfgopt(copt, "LogSyslog")) {
 		int fac = LOG_LOCAL6;
 
e483f51a
 		if(cfgopt(copt, "LogVerbose")) {
ccf2bd40
 			logVerbose = 1;
1ae303c2
 #if	((SENDMAIL_VERSION_A > 8) || ((SENDMAIL_VERSION_A == 8) && (SENDMAIL_VERSION_B >= 13)))
e483f51a
 			smfi_setdbg(6);
 #endif
 		}
ccf2bd40
 		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;
 			}
1ae303c2
 		openlog(progname, LOG_CONS|LOG_PID, fac);
ccf2bd40
 	} else {
 		if(qflag)
 			fprintf(stderr, _("%s: (-q && !LogSyslog): warning - all interception message methods are off\n"),
 				argv[0]);
 		use_syslog = 0;
 	}
b5d15e64
 	/*
ccf2bd40
 	 * Get the outgoing socket details - the way to talk to clamd, unless
 	 * we're doing the scanning internally
b151ef55
 	 */
7ca2a6cf
 	if(!external) {
cf6c41b7
 		if(max_children == 0) {
654ab4b3
 			fprintf(stderr, _("%s: --max-children must be given if --external is not given\n"), argv[0]);
ccf2bd40
 			return EX_CONFIG;
 		}
d18eac06
 #if     0
cf6c41b7
 		if(child_timeout) {
654ab4b3
 			fprintf(stderr, _("%s: --timeout must not be given if --external is not given\n"), argv[0]);
ccf2bd40
 			return EX_CONFIG;
 		}
d18eac06
 #endif
cf6c41b7
 		if(loadDatabase() != 0)
ccf2bd40
 			return EX_CONFIG;
98dcfffc
 		numServers = 1;
ccf2bd40
 	} else if((cpt = cfgopt(copt, "LocalSocket")) != NULL) {
85563fe5
 #ifdef	SESSION
 		struct sockaddr_un server;
 #endif
8c42185e
 		char *sockname = NULL;
85563fe5
 
f0d6f5a1
 		if(cfgopt(copt, "TCPSocket") != NULL) {
4e136199
 			fprintf(stderr, _("%s: You can select one server type only (local/TCP) in %s\n"),
f0d6f5a1
 				argv[0], cfgfile);
 			return EX_CONFIG;
 		}
8c42185e
 		if(strncasecmp(port, "unix:", 5) == 0)
 			sockname = &port[5];
 		else if(strncasecmp(port, "local:", 6) == 0)
 			sockname = &port[6];
 
 		if(sockname && (strcmp(sockname, cpt->strarg) == 0)) {
09e761fb
 			fprintf(stderr, _("The connection from sendmail to %s (%s) must not\n"),
8c42185e
 				argv[0], sockname);
09e761fb
 			fprintf(stderr, _("be the same as the connection to clamd (%s) in %s\n"),
8c42185e
 				cpt->strarg, cfgfile);
 			return EX_CONFIG;
 		}
b151ef55
 		/*
 		 * TODO: check --server hasn't been set
 		 */
 		localSocket = cpt->strarg;
85563fe5
 #ifndef	SESSION
244be94f
 		if(!pingServer(-1)) {
4e136199
 			fprintf(stderr, _("Can't talk to clamd server via %s\n"),
f0d6f5a1
 				localSocket);
4e136199
 			fprintf(stderr, _("Check your entry for LocalSocket in %s\n"),
f0d6f5a1
 				cfgfile);
 			return EX_CONFIG;
 		}
85563fe5
 #endif
f7925922
 		/*if(quarantine_dir == NULL)
4e136199
 			fprintf(stderr, _("When using Localsocket in %s\nyou may improve performance if you use the --quarantine-dir option\n"), cfgfile);*/
9873b3c1
 
e27af651
 		umask(077);
244be94f
 
 		serverIPs = (long *)cli_malloc(sizeof(long));
 		serverIPs[0] = inet_addr("127.0.0.1");
85563fe5
 
 #ifdef	SESSION
 		memset((char *)&server, 0, sizeof(struct sockaddr_un));
 		server.sun_family = AF_UNIX;
 		strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
 
ebb16454
 		sessions = (struct session *)cli_malloc(sizeof(struct session));
 		if((sessions[0].sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
85563fe5
 			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;
 		}
ebb16454
 		if(connect(sessions[0].sock, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
85563fe5
 			perror(localSocket);
 			return EX_UNAVAILABLE;
 		}
ebb16454
 		if(send(sessions[0].sock, "SESSION\n", 7, 0) < 7) {
85563fe5
 			perror("send");
 			if(use_syslog)
 				syslog(LOG_ERR, _("Can't create a clamd session"));
 			return EX_UNAVAILABLE;
 		}
ebb16454
 		sessions[0].status = CMDSOCKET_FREE;
85563fe5
 #endif
8c42185e
 		/*
 		 * FIXME: Allow connection to remote servers by TCP/IP whilst
 		 * connecting to the localserver via a UNIX domain socket
 		 */
85563fe5
 		numServers = 1;
f0d6f5a1
 	} else if((cpt = cfgopt(copt, "TCPSocket")) != NULL) {
f5d23f2e
 		int activeServers;
244be94f
 
b151ef55
 		/*
 		 * TCPSocket is in fact a port number not a full socket
 		 */
3454ad43
 		if(quarantine_dir) {
5aeb0d51
 			fprintf(stderr, _("%s: --quarantine-dir not supported for TCPSocket - use --quarantine\n"), argv[0]);
3454ad43
 			return EX_CONFIG;
 		}
 
cbd7686b
 		tcpSocket = (in_port_t)cpt->numarg;
244be94f
 
bbc9d684
 		/*
244be94f
 		 * cli_strtok's fieldno counts from 0
bbc9d684
 		 */
244be94f
 		for(;;) {
71f25024
 			char *hostname = cli_strtok(serverHostNames, numServers, ":");
244be94f
 			if(hostname == NULL)
 				break;
8c42185e
 #ifdef	MAXHOSTNAMELEN
 			if(strlen(hostname) > MAXHOSTNAMELEN) {
 				fprintf(stderr, _("%s: hostname %s is longer than %d characters\n"),
 					argv[0], hostname, MAXHOSTNAMELEN);
 				return EX_CONFIG;
 			}
 #endif
244be94f
 			numServers++;
 			free(hostname);
bbc9d684
 		}
 
244be94f
 		cli_dbgmsg("numServers: %d\n", numServers);
3454ad43
 
244be94f
 		serverIPs = (long *)cli_malloc(numServers * sizeof(long));
97727aa0
 		activeServers = 0;
244be94f
 
85563fe5
 #ifdef	SESSION
 		/*
 		 * We need to know how many connections to establish to clamd
 		 */
 		if(max_children == 0) {
6e8e00b6
 			fprintf(stderr, _("%s: --max-children must be given in sessions mode\n"), argv[0]);
85563fe5
 			return EX_CONFIG;
 		}
 #endif
 
244be94f
 		for(i = 0; i < numServers; i++) {
8c42185e
 #ifdef	MAXHOSTNAMELEN
 			char hostname[MAXHOSTNAMELEN + 1];
 
 			if(cli_strtokbuf(serverHostNames, i, ":", hostname) == NULL)
 				break;
 #else
71f25024
 			char *hostname = cli_strtok(serverHostNames, i, ":");
8c42185e
 #endif
244be94f
 
 			/*
 			 * Translate server's name to IP address
 			 */
 			serverIPs[i] = inet_addr(hostname);
 			if(serverIPs[i] == -1L) {
 				const struct hostent *h = gethostbyname(hostname);
 
 				if(h == NULL) {
4e136199
 					fprintf(stderr, _("%s: Unknown host %s\n"),
244be94f
 						argv[0], hostname);
 					return EX_USAGE;
 				}
 
 				memcpy((char *)&serverIPs[i], h->h_addr, sizeof(serverIPs[i]));
 			}
 
85563fe5
 #ifndef	SESSION
97727aa0
 			if(pingServer(i))
 				activeServers++;
 			else {
4e136199
 				cli_warnmsg(_("Can't talk to clamd server %s on port %d\n"),
244be94f
 					hostname, tcpSocket);
1ae303c2
 				if(serverIPs[i] == (int)inet_addr("127.0.0.1")) {
 					if(cfgopt(copt, "TCPAddr") != NULL)
 						cli_warnmsg(_("Check the value for TCPAddr in %s\n"), cfgfile);
 				} else
 					cli_warnmsg(_("Check the value for TCPAddr in clamd.conf on %s\n"), hostname);
244be94f
 			}
85563fe5
 #endif
8c42185e
 
 #ifndef	MAXHOSTNAMELEN
244be94f
 			free(hostname);
8c42185e
 #endif
b151ef55
 		}
85563fe5
 #ifdef	SESSION
 		activeServers = numServers;
09e761fb
 
ebb16454
 		sessions = (struct session *)cli_calloc(max_children, sizeof(struct session));
9c8806fb
 		for(i = 0; i < (int)max_children; i++)
85563fe5
 			if(createSession(i) < 0)
 				return EX_UNAVAILABLE;
3d1b1f07
 		if(activeServers == 0) {
 			cli_warnmsg(_("Can't find any active clamd servers\n"));
 			cli_warnmsg(_("Check your entry for TCPSocket in %s\n"),
 				cfgfile);
 		}
85563fe5
 #else
97727aa0
 		if(activeServers == 0) {
4e136199
 			cli_errmsg(_("Can't find any clamd servers\n"));
 			cli_errmsg(_("Check your entry for TCPSocket in %s\n"),
97727aa0
 				cfgfile);
 			return EX_CONFIG;
 		}
85563fe5
 #endif
b151ef55
 	} else {
4e136199
 		fprintf(stderr, _("%s: You must select server type (local/TCP) in %s\n"),
b151ef55
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 
5a4966af
 #ifdef	SESSION
7ca2a6cf
 	if(!external) {
cf6c41b7
 		if(clamav_versions == NULL) {
 			clamav_versions = (char **)cli_malloc(sizeof(char *));
 			if(clamav_versions == NULL)
 				return EX_TEMPFAIL;
 			clamav_version = strdup(version);
 		}
ccf2bd40
 	} else {
9c8806fb
 		unsigned int session;
 
a685d081
 		/*
 		 * We need to know how many connections to establish to clamd
 		 */
 		if(max_children == 0) {
 			fprintf(stderr, _("%s: --max-children must be given in sessions mode\n"), argv[0]);
 			return EX_CONFIG;
 		}
 
b1dc4b1b
 		clamav_versions = (char **)cli_malloc(max_children * sizeof(char *));
ccf2bd40
 		if(clamav_versions == NULL)
fe4d912a
 			return EX_TEMPFAIL;
ccf2bd40
 
9c8806fb
 		for(session = 0; session < max_children; session++) {
 			clamav_versions[session] = strdup(version);
 			if(clamav_versions[session] == NULL)
ccf2bd40
 				return EX_TEMPFAIL;
 		}
 	}
83ae9a24
 #else
 	strcpy(clamav_version, version);
 #endif
fe4d912a
 
a87be00b
 	if(((quarantine_dir == NULL) && localSocket) || !external) {
ae17ae8e
 		/* set the temporary dir */
dd63090a
 		if((cpt = cfgopt(copt, "TemporaryDirectory"))) {
ae17ae8e
 			tmpdir = cpt->strarg;
baff8875
 			cl_settempdir(tmpdir, (short)(cfgopt(copt, "LeaveTemporaryFiles") != NULL));
dd63090a
 		} else if((tmpdir = getenv("TMPDIR")) == (char *)NULL)
ae17ae8e
 			if((tmpdir = getenv("TMP")) == (char *)NULL)
 				if((tmpdir = getenv("TEMP")) == (char *)NULL)
 #ifdef	P_tmpdir
 					tmpdir = P_tmpdir;
 #else
 					tmpdir = "/tmp";
 #endif
 
9f47e7ab
 		/*
 		 * TODO: investigate mkdtemp on LINUX and possibly others
 		 */
dd63090a
 		tmpdir = cli_gentemp(NULL);
ae17ae8e
 
a87be00b
 		cli_dbgmsg("Making %s\n", tmpdir);
 
ae17ae8e
 		if(mkdir(tmpdir, 0700)) {
 			perror(tmpdir);
 			return EX_CANTCREAT;
 		}
 	} else
 		tmpdir = NULL;
 
f0d6f5a1
 	if(!cfgopt(copt, "Foreground")) {
cdbe607d
 #ifdef	CL_DEBUG
4e136199
 		printf(_("When debugging it is recommended that you use Foreground mode in %s\n"), cfgfile);
ccf2bd40
 		puts(_("\tso that you can see all of the messages"));
cdbe607d
 #endif
 
b151ef55
 		switch(fork()) {
 			case -1:
 				perror("fork");
09e761fb
 				return EX_OSERR;
b151ef55
 			case 0:	/* child */
 				break;
 			default:	/* parent */
 				return EX_OK;
 		}
f0d6f5a1
 		close(0);
a121491a
 		open("/dev/null", O_RDONLY);
 
 #ifndef	CL_DEBUG
f0d6f5a1
 		close(1);
21e6b430
 
 		if((cpt = cfgopt(copt, "LogFile"))) {
 			logFile = cpt->strarg;
 
 #if	defined(MSDOS) || defined(C_CYGWIN) || defined(WIN32)
 			if((strlen(logFile) < 2) || ((logFile[0] != '/') && (logFile[0] != '\\') && (logFile[1] != ':'))) {
 #else
 			if((strlen(logFile) < 2) || (logFile[0] != '/')) {
 #endif
 				fprintf(stderr, "%s: LogFile requires full path\n", argv[0]);
 				return EX_CONFIG;
 			}
e483f51a
 			if(open(logFile, O_WRONLY|O_APPEND) < 0) {
1ae303c2
 				if(errno == ENOENT) {
 					/*
 					 * There is low risk race condition here
 					 */
 					if(open(logFile, O_WRONLY|O_CREAT, 0644) < 0) {
 						perror(logFile);
 						return EX_CANTCREAT;
 					}
 				} else {
 					perror(logFile);
 					return EX_CANTCREAT;
 				}
e483f51a
 			}
 		} else {
ac8dfb6a
 			logFile = console;
 			if(consolefd < 0) {
 				perror(console);
e483f51a
 				return EX_OSFILE;
 			}
ac8dfb6a
 			dup(consolefd);
e483f51a
 		}
 		close(2);
 		dup(1);
ac8dfb6a
 		if(consolefd >= 0)
 			close(consolefd);
 
9c8806fb
 #endif	/*!CL_DEBUG*/
a121491a
 
1ae303c2
 		if(cfgopt(copt, "LogTime"))
 			logg_time = 1;
 		if(cfgopt(copt, "LogFileUnlock"))
 			logg_lock = 0;
 		if(cfgopt(copt, "LogClean"))
 			logok = 1;
 		if((cpt = cfgopt(copt, "LogFileMaxSize")))
 			logg_size = cpt->numarg;
 		else
 			logg_size = CL_DEFAULT_LOGSIZE;
 
 
65a8d561
 #ifdef HAVE_SETPGRP
 #ifdef SETPGRP_VOID
0b6bce84
 		setpgrp();
65a8d561
 #else
 		setpgrp(0,0);
 #endif
 #else
 #ifdef HAVE_SETSID
9c8806fb
 		setsid();
65a8d561
 #endif
 #endif
f0d6f5a1
 	}
b151ef55
 
78a78fcc
 	atexit(quit);
 
7ca2a6cf
 	if(!external) {
1d4e7005
 		/* TODO: read the limits from clamd.conf */
 
654ab4b3
 		if(cfgopt(copt, "DisableDefaultScanOptions")) {
1d4e7005
 			options &= ~CL_SCAN_STDOPT;
654ab4b3
 			if(!cfgopt(copt, "ScanMail"))
 				printf(_("%s: ScanMail not defined in %s (needed without --external), enabling\n"),
 					argv[0], cfgfile);
 		}
1d4e7005
 		options |= CL_SCAN_MAIL;	/* no choice */
 		if(!cfgopt(copt, "ScanRAR"))
 			options |= CL_SCAN_DISABLERAR;
 		if(cfgopt(copt, "ArchiveBlockEncrypted"))
 			options |= CL_SCAN_BLOCKENCRYPTED;
 		if(cfgopt(copt, "ArchiveBlockMax"))
 			options |= CL_SCAN_BLOCKMAX;
 		if(cfgopt(copt, "ScanPE"))
 			options |= CL_SCAN_PE;
 		if(cfgopt(copt, "DetectBrokenExecutables"))
 			options |= CL_SCAN_BLOCKBROKEN;
 		if(cfgopt(copt, "MailFollowURLs"))
 			options |= CL_SCAN_MAILURL;
 		if(cfgopt(copt, "ScanOLE2"))
 			options |= CL_SCAN_OLE2;
 		if(cfgopt(copt, "ScanHTML"))
 			options |= CL_SCAN_HTML;
 
 		memset(&limits, '\0', sizeof(struct cl_limits));
 
 		if(cfgopt(copt, "ScanArchive")) {
 			options |= CL_SCAN_ARCHIVE;
 			if((cpt = cfgopt(copt, "ArchiveMaxFileSize")) != NULL)
 				limits.maxfilesize = cpt->numarg;
 			else
 				limits.maxfilesize = 10485760;
 
 			if((cpt = cfgopt(copt, "ArchiveMaxRecursion")) != NULL)
 				limits.maxreclevel = cpt->numarg;
 			else
 				limits.maxreclevel = 8;
 
 			if((cpt = cfgopt(copt, "ArchiveMaxFiles")) != NULL)
 				limits.maxfiles = cpt->numarg;
 			else
 				limits.maxfiles = 1000;
 
 			if((cpt = cfgopt(copt, "ArchiveMaxCompressionRatio")) != NULL)
 				limits.maxratio = cpt->numarg;
 			else
 				limits.maxratio = 250;
 
 			if(cfgopt(copt, "ArchiveLimitMemoryUsage") != NULL)
 				limits.archivememlim = 1;
 			else
 				limits.archivememlim = 0;
 		}
 	}
 
41190088
 #ifdef	SESSION
677565d4
 	/* FIXME: add localSocket support to watchdog */
e483f51a
 	if((localSocket == NULL) || external)
41190088
 #endif
83d6495b
 		pthread_create(&tid, NULL, watchdog, NULL);
41190088
 
062dddc9
 	if((cpt = cfgopt(copt, "PidFile")) != NULL)
 		pidFile = cpt->strarg;
 
3eacc10d
 	broadcast(_("Starting clamav-milter"));
b151ef55
 
679ff9e7
 	if(pidfile) {
 		/* save the PID */
e483f51a
 		char *p, *q;
679ff9e7
 		FILE *fd;
 		const mode_t old_umask = umask(0006);
 
e483f51a
 		if(pidfile[0] != '/') {
 			if(use_syslog)
 				syslog(LOG_ERR, _("pidfile: '%s' must be a full pathname"),
 					pidfile);
 			cli_errmsg(_("pidfile '%s' must be a full pathname\n"), pidfile);
 
 			return EX_CONFIG;
 		}
 		p = strdup(pidfile);
 		q = strrchr(p, '/');
 		*q = '\0';
 
 		if(chdir(p) < 0)	/* safety */
 			perror(p);
 		free(p);
 
679ff9e7
 		if((fd = fopen(pidfile, "w")) == NULL) {
 			if(use_syslog)
fa7e490e
 				syslog(LOG_ERR, _("Can't save PID in file %s"),
679ff9e7
 					pidfile);
fa7e490e
 			cli_errmsg(_("Can't save PID in file %s\n"), pidfile);
 			return EX_CONFIG;
e483f51a
 		}
54ba1fe6
 #ifdef	C_LINUX
e483f51a
 		/* Ensure that all threads are kill()ed */
 		fprintf(fd, "-%d\n", (int)getpgrp());
54ba1fe6
 #else
e483f51a
 		fprintf(fd, "%d\n", (int)getpid());
54ba1fe6
 #endif
e483f51a
 		fclose(fd);
679ff9e7
 		umask(old_umask);
e483f51a
 	} else if(tmpdir)
 		chdir(tmpdir);	/* safety */
 	else
 #ifdef	P_tmpdir
 		chdir(P_tmpdir);
 #else
 		chdir("/tmp");
 #endif
679ff9e7
 
3a0b4e5b
 	if(cfgopt(copt, "FixStaleSocket")) {
 		/*
 		 * Get the incoming socket details - the way sendmail talks to
 		 * us
 		 *
434012b8
 		 * TODO: There's a security problem here that'll need fixing if
c857005e
 		 * the User entry of clamd.conf is not used
3a0b4e5b
 		 */
 		if(strncasecmp(port, "unix:", 5) == 0) {
 			if(unlink(&port[5]) < 0)
462b5251
 				if(errno != ENOENT)
 					perror(&port[5]);
3a0b4e5b
 		} else if(strncasecmp(port, "local:", 6) == 0) {
 			if(unlink(&port[6]) < 0)
462b5251
 				if(errno != ENOENT)
 					perror(&port[6]);
3a0b4e5b
 		}
 	}
b151ef55
 
e27af651
 	if(smfi_setconn(port) == MI_FAILURE) {
9c8806fb
 		cli_errmsg("smfi_setconn failure\n");
e27af651
 		return EX_SOFTWARE;
 	}
 
b151ef55
 	if(smfi_register(smfilter) == MI_FAILURE) {
35d98cf9
 		cli_errmsg("smfi_register failure\n");
b151ef55
 		return EX_UNAVAILABLE;
 	}
 
1ae303c2
 #if	((SENDMAIL_VERSION_A > 8) || ((SENDMAIL_VERSION_A == 8) && (SENDMAIL_VERSION_B >= 13)))
9c8806fb
 	if(smfi_opensocket(1) == MI_FAILURE) {
1ae303c2
 		cli_errmsg("Can't open/create %s\n", port);
9c8806fb
 		return EX_CONFIG;
 	}
 #endif
 
e483f51a
 	signal(SIGPIPE, SIG_IGN);	/* libmilter probably does this as well */
b151ef55
 
83ae9a24
 #ifdef	SESSION
c78ec4f3
 	pthread_mutex_lock(&version_mutex);
83ae9a24
 #endif
ccf2bd40
 	if(use_syslog) {
98dcfffc
 		syslog(LOG_INFO, _("Starting %s"), clamav_version);
ccf2bd40
 #ifdef	CL_DEBUG
 		if(debug_level > 0)
 			syslog(LOG_DEBUG, _("Debugging is on"));
 #endif
 	}
 
 	cli_dbgmsg("Started: %s\n", clamav_version);
83ae9a24
 #ifdef	SESSION
c78ec4f3
 	pthread_mutex_unlock(&version_mutex);
83ae9a24
 #endif
d69535ae
 
3c98c93b
 	(void)signal(SIGSEGV, sigsegv);
 
b151ef55
 	return smfi_main();
 }
 
85563fe5
 #ifdef	SESSION
 /*
  * Use the SESSION command of clamd.
  * Returns -1 for terminal failure, 0 for OK, 1 for nonterminal failure
ebb16454
  * The caller must take care of locking the sessions array
85563fe5
  */
 static int
9c8806fb
 createSession(unsigned int s)
85563fe5
 {
80a6c423
 	int ret = 0, fd;
85563fe5
 	struct sockaddr_in server;
ebb16454
 	const int serverNumber = s % numServers;
 	struct session *session = &sessions[s];
85563fe5
 
3c98c93b
 	cli_dbgmsg("createSession session %d, server %d\n", s, serverNumber);
98dcfffc
 	assert(s < max_children);
 
85563fe5
 	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];
 
80a6c423
 	session->sock = -1;
 	if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
85563fe5
 		perror("socket");
7e8d6a11
 		ret = -1;
80a6c423
 	} else if(connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
85563fe5
 		perror("connect");
7e8d6a11
 		ret = 1;
80a6c423
 	} else if(send(fd, "SESSION\n", 7, 0) < 7) {
7e8d6a11
 		perror("send");
 		ret = 1;
85563fe5
 	}
7e8d6a11
 
 	if(ret != 0) {
8c42185e
 #ifdef	MAXHOSTNAMELEN
 		char hostname[MAXHOSTNAMELEN + 1];
 
 		cli_strtokbuf(serverHostNames, serverNumber, ":", hostname);
48e8856b
 		if(strcmp(hostname, "127.0.0.1") == 0)
 			gethostname(hostname, sizeof(hostname));
8c42185e
 #else
85563fe5
 		char *hostname = cli_strtok(serverHostNames, serverNumber, ":");
8c42185e
 #endif
 
80a6c423
 		session->status = CMDSOCKET_DOWN;
 
 		if(fd >= 0)
 			close(fd);
09e761fb
 
85563fe5
 		cli_warnmsg(_("Check clamd server %s - it may be down\n"), hostname);
8c42185e
 #ifndef	MAXHOSTNAMELEN
85563fe5
 		free(hostname);
8c42185e
 #endif
85563fe5
 
3c98c93b
 		broadcast(_("Check clamd server - it may be down"));
 	} else
 		session->sock = fd;
09e761fb
 
7e8d6a11
 	return ret;
85563fe5
 }
 
 #else
 
b151ef55
 /*
  * Verify that the server is where we think it is
  * Returns true or false
244be94f
  *
97727aa0
  * serverNumber counts from 0, but is only used for TCPSocket
b151ef55
  */
 static int
244be94f
 pingServer(int serverNumber)
b151ef55
 {
f0d6f5a1
 	char *ptr;
b151ef55
 	int sock, nbytes;
f0d6f5a1
 	char buf[128];
b151ef55
 
f0d6f5a1
 	if(localSocket) {
 		struct sockaddr_un server;
b151ef55
 
f0d6f5a1
 		memset((char *)&server, 0, sizeof(struct sockaddr_un));
 		server.sun_family = AF_UNIX;
7cc3891c
 		strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
f0d6f5a1
 
 		if((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
f5d23f2e
 			perror(localSocket);
f0d6f5a1
 			return 0;
 		}
6e945c41
 		checkClamd();
f0d6f5a1
 		if(connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
 			perror(localSocket);
6489ba59
 			close(sock);
f0d6f5a1
 			return 0;
 		}
 	} else {
 		struct sockaddr_in server;
 
 		memset((char *)&server, 0, sizeof(struct sockaddr_in));
 		server.sin_family = AF_INET;
cbd7686b
 		server.sin_port = (in_port_t)htons(tcpSocket);
 
ca59d8d0
 		assert(serverIPs != NULL);
244be94f
 		assert(serverIPs[0] != -1L);
bbc9d684
 
244be94f
 		server.sin_addr.s_addr = serverIPs[serverNumber];
f0d6f5a1
 
 		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");
9873b3c1
 			close(sock);
f0d6f5a1
 			return 0;
 		}
b151ef55
 	}
f0d6f5a1
 
 	/*
 	 * It would be better to use PING, check for PONG then issue the
 	 * VERSION command, since that would better validate that we're
 	 * talking to clamd, however clamd closes the session after
 	 * sending PONG :-(
 	 * So this code does not really validate that we're talking to clamd
 	 * Needs a fix to clamd
 	 * Also version command is verbose: says "clamd / ClamAV version"
 	 * instead of "clamAV version"
 	 */
78a78fcc
 	cli_dbgmsg("pingServer%d: sending VERSION\n", serverNumber);
f0d6f5a1
 	if(send(sock, "VERSION\n", 8, 0) < 8) {
b151ef55
 		perror("send");
 		close(sock);
 		return 0;
 	}
 
 	shutdown(sock, SHUT_WR);
 
d2efb3fa
 	nbytes = clamd_recv(sock, buf, sizeof(buf));
b151ef55
 
 	close(sock);
 
 	if(nbytes < 0) {
 		perror("recv");
 		return 0;
 	}
d2efb3fa
 	if(nbytes == 0)
 		return 0;
 
b151ef55
 	buf[nbytes] = '\0';
 
f0d6f5a1
 	/* Remove the trailing new line from the reply */
 	if((ptr = strchr(buf, '\n')) != NULL)
 		*ptr = '\0';
 
 	/*
 	 * No real validation is done here
244be94f
 	 *
 	 * TODO: When connecting to more than one server, give a warning
 	 *	if they're running different versions, or if the virus DBs
0ac43d02
 	 *	are out of date (say more than a month old)
f0d6f5a1
 	 */
09e761fb
 	snprintf(clamav_version, sizeof(clamav_version) - 1,
f5d23f2e
 		"%s\n\tclamav-milter version %s",
f0d6f5a1
 		buf, CM_VERSION);
bb9979e6
 
f0d6f5a1
 	return 1;
b151ef55
 }
85563fe5
 #endif
b151ef55
 
244be94f
 /*
97727aa0
  * Find the best server to connect to. No intelligence to this.
  * It is best to weight the order of the servers from most wanted to least
  * wanted
244be94f
  *
5a4966af
  * Return value is from 0 - index into sessions array
97727aa0
  *
  * If the load balancing fails return the first server in the list, not
  * an error, to be on the safe side
244be94f
  */
85563fe5
 #ifdef	SESSION
 static int
 findServer(void)
 {
9c8806fb
 	unsigned int i, j;
ebb16454
 	struct session *session;
85563fe5
 
 	/*
 	 * FIXME: Sessions code isn't flexible at handling servers
ebb16454
 	 *	appearing and disappearing, e.g. sessions[n_children].sock == -1
85563fe5
 	 */
9c8806fb
 	i = 0;
85563fe5
 	pthread_mutex_lock(&n_children_mutex);
 	assert(n_children > 0);
 	assert(n_children <= max_children);
98dcfffc
 	j = n_children - 1;
85563fe5
 	pthread_mutex_unlock(&n_children_mutex);
 
 	pthread_mutex_lock(&sstatus_mutex);
98dcfffc
 	for(; i < max_children; i++) {
80a6c423
 		const int sess = (j + i) % max_children;
 
 		session = &sessions[sess];
 		cli_dbgmsg("findServer: try server %d\n", sess);
ebb16454
 		if(session->status == CMDSOCKET_FREE) {
 			session->status = CMDSOCKET_INUSE;
85563fe5
 			pthread_mutex_unlock(&sstatus_mutex);
80a6c423
 			return sess;
85563fe5
 		}
98dcfffc
 	}
85563fe5
 	pthread_mutex_unlock(&sstatus_mutex);
 
5a4966af
 	/*
 	 * No session free - wait until one comes available. Only
 	 * retries once.
 	 */
41190088
 	if(pthread_cond_broadcast(&watchdog_cond) < 0)
 		perror("pthread_cond_broadcast");
85563fe5
 
3d1b1f07
 	i = 0;
ebb16454
 	session = sessions;
85563fe5
 	pthread_mutex_lock(&sstatus_mutex);
98dcfffc
 	for(; i < max_children; i++, session++) {
 		cli_dbgmsg("findServer: try server %d\n", i);
ebb16454
 		if(session->status == CMDSOCKET_FREE) {
 			session->status = CMDSOCKET_INUSE;
85563fe5
 			pthread_mutex_unlock(&sstatus_mutex);
 			return i;
 		}
98dcfffc
 	}
85563fe5
 	pthread_mutex_unlock(&sstatus_mutex);
 
 	cli_warnmsg(_("No free clamd sessions\n"));
 
 	return -1;	/* none available - must fail */
 }
 #else
5a4966af
 /*
  * Return value is from 0 - index into serverIPs
  */
244be94f
 static int
 findServer(void)
 {
 	struct sockaddr_in *servers, *server;
fce82877
 	int *socks, maxsock = 0, i, j;
244be94f
 	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);
 
85563fe5
 	if(max_children > 0) {
 		assert(n_children > 0);
 		assert(n_children <= max_children);
 
 		/*
 		 * Don't worry about no lock - it's doesn't matter if it's
 		 * not really accurate
 		 */
 		j = n_children - 1;
 	} else
7d7092de
 		/*
 		 * cli_rndnum returns 0..(max-1) - the max argument is not
 		 * the maximum number you want it to return, it is in fact
 		 * one *more* than the maximum number you want it to return
 		 */
 		j = cli_rndnum(numServers);
fce82877
 
244be94f
 	for(i = 0, server = servers; i < numServers; i++, server++) {
 		int sock;
 
 		server->sin_family = AF_INET;
cbd7686b
 		server->sin_port = (in_port_t)htons(tcpSocket);
fce82877
 		server->sin_addr.s_addr = serverIPs[(i + j) % numServers];
 
 		cli_dbgmsg("findServer: try server %d\n",
 			(i + j) % numServers);
244be94f
 
 		sock = socks[i] = socket(AF_INET, SOCK_STREAM, 0);
85563fe5
 
244be94f
 		if(sock < 0) {
 			perror("socket");
 			do
 				if(socks[i] >= 0)
 					close(socks[i]);
 			while(--i >= 0);
 			free(socks);
 			free(servers);
 			return 0;	/* Use the first server on failure */
 		}
 
 		if((connect(sock, (struct sockaddr *)server, sizeof(struct sockaddr)) < 0) ||
 		   (send(sock, "PING\n", 5, 0) < 5)) {
8c42185e
 #ifdef	MAXHOSTNAMELEN
 			char hostname[MAXHOSTNAMELEN + 1];
 
 			cli_strtokbuf(serverHostNames, i, ":", hostname);
48e8856b
 			if(strcmp(hostname, "127.0.0.1") == 0)
 				gethostname(hostname, sizeof(hostname));
8c42185e
 #else
fce82877
 			char *hostname = cli_strtok(serverHostNames, i, ":");
8c42185e
 #endif
4e136199
 			cli_warnmsg(_("Check clamd server %s - it may be down\n"), hostname);
244be94f
 			if(use_syslog)
97727aa0
 				syslog(LOG_WARNING,
4e136199
 					_("Check clamd server %s - it may be down"),
97727aa0
 					hostname);
6489ba59
 			close(sock);
8c42185e
 #ifndef	MAXHOSTNAMELEN
fce82877
 			free(hostname);
8c42185e
 #endif
3eacc10d
 			broadcast(_("Check clamd server - it may be down\n"));
fce82877
 			socks[i] = -1;
244be94f
 			continue;
 		}
 
 		shutdown(sock, SHUT_WR);
 
 		FD_SET(sock, &rfds);
 		if(sock > maxsock)
 			maxsock = sock;
 	}
 
 	free(servers);
 
cc511c7a
 	tv.tv_sec = readTimeout;
244be94f
 	tv.tv_usec = 0;
cbd7686b
 
95ec27d0
 	if(maxsock == 0)
 		retval = 0;
 	else
 		retval = select(maxsock + 1, &rfds, NULL, NULL, &tv);
 
244be94f
 	if(retval < 0)
 		perror("select");
 
 	for(i = 0; i < numServers; i++)
 		if(socks[i] >= 0)
 			close(socks[i]);
 
 	if(retval == 0) {
 		free(socks);
85563fe5
 		clamdIsDown();
244be94f
 		return 0;
 	} else if(retval < 0) {
 		free(socks);
 		if(use_syslog)
4e136199
 			syslog(LOG_ERR, _("findServer: select failed"));
244be94f
 		return 0;
 	}
 
 	for(i = 0; i < numServers; i++)
fce82877
 		if((socks[i] >= 0) && (FD_ISSET(socks[i], &rfds))) {
8eb408e5
 			const int s = (i + j) % numServers;
fce82877
 
244be94f
 			free(socks);
4e136199
 			cli_dbgmsg(_("findServer: using server %d\n"), s);
8eb408e5
 			return s;
244be94f
 		}
 
 	free(socks);
4e136199
 	cli_dbgmsg(_("findServer: No response from any server\n"));
244be94f
 	if(use_syslog)
4e136199
 		syslog(LOG_WARNING, _("findServer: No response from any server"));
244be94f
 	return 0;
 }
85563fe5
 #endif
244be94f
 
6489ba59
 /*
  * Sendmail wants to establish a connection to us
  */
b151ef55
 static sfsistat
 clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
6489ba59
 {
0a9dafb1
 #if	defined(HAVE_INET_NTOP) || defined(WITH_TCPWRAP)
35d98cf9
 	char ip[INET_ADDRSTRLEN];	/* IPv4 only */
0a9dafb1
 #endif
4e136199
 	const char *remoteIP;
b151ef55
 
78a78fcc
 	if(quitting)
 		return cl_error;
 
18f9be43
 	if(ctx == NULL) {
 		if(use_syslog)
4e136199
 			syslog(LOG_ERR, _("clamfi_connect: ctx is null"));
18f9be43
 		return cl_error;
 	}
2a1ff3e4
 	if(hostname == NULL) {
 		if(use_syslog)
4e136199
 			syslog(LOG_ERR, _("clamfi_connect: hostname is null"));
2a1ff3e4
 		return cl_error;
 	}
384e5f09
 	if((hostaddr == NULL) || (&(((struct sockaddr_in *)(hostaddr))->sin_addr) == NULL))
40c9a608
 		/*
 		 * According to the sendmail API hostaddr is NULL if
 		 * "the type is not supported in the current version". What
18f9be43
 		 * the documentation doesn't say is the type of what.
40c9a608
 		 *
d69535ae
 		 * Possibly the input is not a TCP/IP socket e.g. stdin?
40c9a608
 		 */
 		remoteIP = "127.0.0.1";
 	else {
0a9dafb1
 #ifdef HAVE_INET_NTOP
40c9a608
 		remoteIP = (char *)inet_ntop(AF_INET, &((struct sockaddr_in *)(hostaddr))->sin_addr, ip, sizeof(ip));
0a9dafb1
 #else
40c9a608
 		remoteIP = inet_ntoa(((struct sockaddr_in *)(hostaddr))->sin_addr);
0a9dafb1
 #endif
2a1ff3e4
 
40c9a608
 		if(remoteIP == NULL) {
 			if(use_syslog)
4e136199
 				syslog(LOG_ERR, _("clamfi_connect: remoteIP is null"));
40c9a608
 			return cl_error;
 		}
2a1ff3e4
 	}
80a72b0d
 
1f025849
 #ifdef	CL_DEBUG
e2bb746e
 	if(debug_level >= 4) {
98dcfffc
 		if(hostname[0] == '[') {
 			if(use_syslog)
 				syslog(LOG_NOTICE, _("clamfi_connect: connection from %s"), remoteIP);
 			cli_dbgmsg(_("clamfi_connect: connection from %s\n"), remoteIP);
 		} else {
 			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);
 		}
e2bb746e
 	}
1f025849
 #endif
b151ef55
 
f8a7079c
 #ifdef	WITH_TCPWRAP
 	/*
 	 * Support /etc/hosts.allow and /etc/hosts.deny
 	 */
8f1f6fb8
 	if(strncasecmp(port, "inet:", 5) == 0) {
ce14cc31
 		const char *hostmail;
88ad6ea4
 		struct hostent hostent;
6d113e37
 		char buf[BUFSIZ];
8eb408e5
 		static pthread_mutex_t wrap_mutex = PTHREAD_MUTEX_INITIALIZER;
35d98cf9
 
cbd7686b
 		/*
ce14cc31
 		 * Using TCP/IP for the sendmail->clamav-milter connection
 		 */
 		if((hostmail = smfi_getsymval(ctx, "{if_name}")) == NULL) {
 			if(use_syslog)
4e136199
 				syslog(LOG_ERR, _("Can't get sendmail hostname"));
62f5a55b
 			return cl_error;
ce14cc31
 		}
0a7e8089
 		/*
 		 * Use hostmail for error statements, not hostname, suggestion
 		 * by Yar Tikhiy <yar@comp.chem.msu.su>
 		 */
6d113e37
 		if(clamfi_gethostbyname(hostmail, &hostent, buf, sizeof(buf)) != 0) {
88ad6ea4
 			if(use_syslog)
0a7e8089
 				syslog(LOG_WARNING, _("Access Denied: Host Unknown (%s)"), hostmail);
 			if(hostmail[0] == '[')
c78ec4f3
 				/*
 				 * A case could be made that it's not clamAV's
 				 * job to check a system's DNS configuration
 				 * and let this message through. However I am
 				 * just too worried about any knock on effects
 				 * to do that...
 				 */
 				cli_warnmsg(_("Can't find entry for IP address %s in DNS - check your DNS setting\n"),
0a7e8089
 					hostmail);
88ad6ea4
 			return cl_error;
 		}
35d98cf9
 
62f5a55b
 #ifdef HAVE_INET_NTOP
6d113e37
 		if(hostent.h_addr &&
 		   (inet_ntop(AF_INET, (struct in_addr *)hostent.h_addr, ip, sizeof(ip)) == NULL)) {
 			perror(hostent.h_name);
8eb408e5
 			/*strcpy(ip, (char *)inet_ntoa(*(struct in_addr *)hostent.h_addr));*/
d69535ae
 			if(use_syslog)
4e136199
 				syslog(LOG_WARNING, _("Access Denied: Can't get IP address for (%s)"), hostent.h_name);
d69535ae
 			return cl_error;
 		}
62f5a55b
 #else
6d113e37
 		strncpy(ip, (char *)inet_ntoa(*(struct in_addr *)hostent.h_addr), sizeof(ip));
62f5a55b
 #endif
ce14cc31
 
 		/*
 		 * Ask is this is a allowed name or IP number
8eb408e5
 		 *
 		 * hosts_ctl uses strtok so it is not thread safe, see
 		 * hosts_access(3)
ce14cc31
 		 */
8eb408e5
 		pthread_mutex_lock(&wrap_mutex);
1ae303c2
 		if(!hosts_ctl(progname, hostent.h_name, ip, STRING_UNKNOWN)) {
8eb408e5
 			pthread_mutex_unlock(&wrap_mutex);
ce14cc31
 			if(use_syslog)
4e136199
 				syslog(LOG_WARNING, _("Access Denied for %s[%s]"), hostent.h_name, ip);
ce14cc31
 			return SMFIS_TEMPFAIL;
 		}
8eb408e5
 		pthread_mutex_unlock(&wrap_mutex);
f8a7079c
 	}
 #endif
 
b5d15e64
 	if(fflag)
 		/*
 		 * Patch from "Richard G. Roberto" <rgr@dedlegend.com>
 		 * Always scan whereever the message is from
 		 */
 		return SMFIS_CONTINUE;
 
b151ef55
 	if(!oflag)
 		if(strcmp(remoteIP, "127.0.0.1") == 0) {
 #ifdef	CL_DEBUG
 			if(use_syslog)
4e136199
 				syslog(LOG_DEBUG, _("clamfi_connect: not scanning outgoing messages"));
 			cli_dbgmsg(_("clamfi_connect: not scanning outgoing messages\n"));
b151ef55
 #endif
 			return SMFIS_ACCEPT;
 		}
 
503b2956
 	if((!lflag) && isLocalAddr(inet_addr(remoteIP))) {
b151ef55
 #ifdef	CL_DEBUG
503b2956
 		if(use_syslog)
4e136199
 			syslog(LOG_DEBUG, _("clamfi_connect: not scanning local messages"));
85563fe5
 		cli_dbgmsg(_("clamfi_connect: not scanning local messages\n"));
b151ef55
 #endif
503b2956
 		return SMFIS_ACCEPT;
b151ef55
 	}
b7f54bf2
 
5aeb0d51
 #if	defined(HAVE_INET_NTOP) || defined(WITH_TCPWRAP)
 	if(detect_forged_local_address && !isLocalAddr(inet_addr(ip))) {
 #else
 	if(detect_forged_local_address && !isLocalAddr(inet_addr(remoteIP))) {
 #endif
ad5eb067
 		char me[MAXHOSTNAMELEN + 1];
 
 		if(gethostname(me, sizeof(me) - 1) < 0) {
 			if(use_syslog)
 				syslog(LOG_WARNING, _("clamfi_connect: gethostname failed"));
 			return SMFIS_CONTINUE;
 		}
 		if(strcasecmp(hostname, me) == 0) {
 			if(use_syslog)
 				syslog(LOG_NOTICE, _("Rejected email falsely claiming to be from here"));
 			smfi_setreply(ctx, "550", "5.7.1", _("You have claimed to be me, but you are not"));
 			broadcast(_("Forged local address detected"));
 			return SMFIS_REJECT;
 		}
 	}
b151ef55
 	return SMFIS_CONTINUE;
 }
 
 static sfsistat
 clamfi_envfrom(SMFICTX *ctx, char **argv)
 {
 	struct privdata *privdata;
384942c2
 	const char *mailaddr = argv[0];
b151ef55
 
 	if(logVerbose)
 		syslog(LOG_DEBUG, "clamfi_envfrom: %s", argv[0]);
 
6489ba59
 	cli_dbgmsg("clamfi_envfrom: %s\n", argv[0]);
b151ef55
 
384942c2
 	if(strcmp(argv[0], "<>") == 0) {
 		mailaddr = smfi_getsymval(ctx, "{mail_addr}");
c6a870ad
 		if(mailaddr == NULL)
 			mailaddr = smfi_getsymval(ctx, "_");
 
 		if(mailaddr && *mailaddr)
 			cli_dbgmsg("Message from \"%s\" has no from field\n", mailaddr);
384942c2
 		else {
 #if	0
 			if(use_syslog)
 				syslog(LOG_NOTICE, _("Rejected email with empty from field"));
 			smfi_setreply(ctx, "554", "5.7.1", _("You have not said who the email is from"));
 			broadcast(_("Reject email with empty from field"));
 			clamfi_cleanup(ctx);
 			return SMFIS_REJECT;
 #endif
c6a870ad
 			mailaddr = "<>";
384942c2
 		}
 	}
 
b151ef55
 	if(max_children > 0) {
6489ba59
 		int rc = 0;
b151ef55
 
 		pthread_mutex_lock(&n_children_mutex);
 
3a805bfd
 		/*
98dcfffc
 		 * Wait a while since sendmail doesn't like it if we
3a805bfd
 		 * take too long replying. Effectively this means that
 		 * max_children is more of a hint than a rule
 		 */
 		if(n_children >= max_children) {
b151ef55
 			struct timespec timeout;
 
85563fe5
 			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);
 
d69535ae
 			if(use_syslog)
 				syslog(LOG_NOTICE,
85563fe5
 					(dont_wait) ?
4e136199
 						_("hit max-children limit (%u >= %u)") :
85563fe5
 						_("hit max-children limit (%u >= %u): waiting for some to exit"),
d69535ae
 					n_children, max_children);
 
ced83bee
 			if(dont_wait) {
 				pthread_mutex_unlock(&n_children_mutex);
4e136199
 				smfi_setreply(ctx, "451", "4.3.2", _("AV system temporarily overloaded - please try later"));
ced83bee
 				return SMFIS_TEMPFAIL;
 			}
b151ef55
 			/*
d18eac06
 			 * Wait for an amount of time for a child to go
ad31761c
 			 *
b151ef55
 			 * Use pthread_cond_timedwait rather than
 			 * pthread_cond_wait since the sendmail which calls
ad31761c
 			 * us will have a timeout that we don't want to exceed,
 			 * stops sendmail getting fidgety.
d69535ae
 			 *
 			 * Patch from Damian Menscher <menscher@uiuc.edu> to
 			 * ensure it wakes up when a child goes away
b151ef55
 			 */
d18eac06
 			do
 				if(child_timeout == 0) {
 					pthread_cond_wait(&n_children_cond, &n_children_mutex);
 					rc = 0;
 				} else {
 					struct timeval now;
 					struct timezone tz;
98dcfffc
 
d18eac06
 					gettimeofday(&now, &tz);
 					timeout.tv_sec = now.tv_sec + child_timeout;
 					timeout.tv_nsec = 0;
b151ef55
 
ad31761c
 					rc = pthread_cond_timedwait(&n_children_cond, &n_children_mutex, &timeout);
d18eac06
 				}
d69535ae
 			while((n_children >= max_children) && (rc != ETIMEDOUT));
b151ef55
 		}
 		n_children++;
 
4e136199
 		cli_dbgmsg(_(">n_children = %d\n"), n_children);
b151ef55
 		pthread_mutex_unlock(&n_children_mutex);
 
ad31761c
 		if(child_timeout && (rc == ETIMEDOUT)) {
b151ef55
 #ifdef	CL_DEBUG
 			if(use_syslog)
4e136199
 				syslog(LOG_NOTICE, _("Timeout waiting for a child to die"));
b151ef55
 #endif
4e136199
 			cli_dbgmsg(_("Timeout waiting for a child to die\n"));
b151ef55
 		}
 	}
 
64d5612d
 	privdata = (struct privdata *)cli_calloc(1, sizeof(struct privdata));
063b6c33
 	if(privdata == NULL)
6ad582fe
 		return cl_error;
063b6c33
 
b151ef55
 	privdata->dataSocket = -1;	/* 0.4 */
f5d23f2e
 #ifndef	SESSION
b151ef55
 	privdata->cmdSocket = -1;	/* 0.4 */
f5d23f2e
 #endif
b151ef55
 
a121491a
 	/*
 	 * Rejection is via 550 until DATA is received. We know that
 	 * DATA has been sent when either we get a header or the end of
 	 * header statement
 	 */
 	privdata->rejectCode = "550";
 
384942c2
 	privdata->from = strdup(mailaddr);
b151ef55
 
cc511c7a
 	if(hflag)
 		privdata->headers = header_list_new();
64d5612d
 
 	if(smfi_setpriv(ctx, privdata) == MI_SUCCESS)
 		return SMFIS_CONTINUE;
 
 	clamfi_free(privdata);
d9b674b2
 
64d5612d
 	return cl_error;
b151ef55
 }
 
384942c2
 #ifdef	CL_DEBUG
 static sfsistat
 clamfi_helo(SMFICTX *ctx, char *helostring)
 {
 	cli_dbgmsg("HELO '%s'\n", helostring);
 
 	return SMFIS_CONTINUE;
 }
 #endif
 
b151ef55
 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]);
 
6489ba59
 	cli_dbgmsg("clamfi_envrcpt: %s\n", argv[0]);
b151ef55
 
 	if(privdata->to == NULL) {
cbd7686b
 		privdata->to = cli_malloc(sizeof(char *) * 2);
b151ef55
 
 		assert(privdata->numTo == 0);
 	} else
ea815cbc
 		privdata->to = cli_realloc(privdata->to, sizeof(char *) * (privdata->numTo + 2));
b151ef55
 
063b6c33
 	if(privdata->to == NULL)
6ad582fe
 		return cl_error;
063b6c33
 
b151ef55
 	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)
6489ba59
 		cli_dbgmsg("clamfi_header: %s: %s\n", headerf, headerv);
b151ef55
 	else
cbd7686b
 		cli_dbgmsg("clamfi_header\n");
b151ef55
 #endif
 
a121491a
 	/*
 	 * The DATA instruction from SMTP (RFC2821) must have been sent
 	 */
 	privdata->rejectCode = "554";
 
6489ba59
 	if(privdata->dataSocket == -1)
 		/*
 		 * First header - make connection with clamd
 		 */
 		if(!connect2clamd(privdata)) {
 			clamfi_cleanup(ctx);
 			return cl_error;
 		}
 
0cb97bcb
 	if(clamfi_send(privdata, 0, "%s: %s\n", headerf, headerv) <= 0) {
b151ef55
 		clamfi_cleanup(ctx);
b0a42ec6
 		return cl_error;
b151ef55
 	}
d9b674b2
 
 	if(hflag)
cbd7686b
 		header_list_add(privdata->headers, headerf, headerv);
97727aa0
 	else if((strcasecmp(headerf, "Received") == 0) &&
2285693f
 		(strncasecmp(headerv, "from ", 5) == 0) &&
 		(strstr(headerv, "localhost") != 0)) {
97727aa0
 		if(privdata->received)
063b6c33
 			free(privdata->received);
 		privdata->received = strdup(headerv);
 	}
 
a121491a
 	if((strcasecmp(headerf, "Message-ID") == 0) &&
 	   (strncasecmp(headerv, "<MDAEMON", 8) == 0))
 		privdata->discard = 1;
51cddddf
 	else if(strcasecmp(headerf, "Subject") == 0) {
3d1b1f07
 		if(privdata->subject)
 			free(privdata->subject);
 		if(headerv)
 			privdata->subject = strdup(headerv);
51cddddf
 	} else if(strcasecmp(headerf, "X-Virus-Status") == 0)
 		privdata->statusCount++;
384942c2
 	else if(strcasecmp(headerf, "Sender") == 0) {
 		if(privdata->sender)
 			free(privdata->sender);
 		if(headerv)
 			privdata->sender = strdup(headerv);
 	}
c6a870ad
 
b151ef55
 	return SMFIS_CONTINUE;
 }
 
a121491a
 /*
  * At this point DATA will have been received, so we really ought to
  * send 554 back not 550
  */
b151ef55
 static sfsistat
 clamfi_eoh(SMFICTX *ctx)
 {
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
b14e9e77
 	char **to;
b151ef55
 
 	if(logVerbose)
4e136199
 		syslog(LOG_DEBUG, _("clamfi_eoh"));
b151ef55
 #ifdef	CL_DEBUG
6489ba59
 	if(debug_level >= 4)
4e136199
 		cli_dbgmsg(_("clamfi_eoh\n"));
b151ef55
 #endif
 
a121491a
 	/*
 	 * The DATA instruction from SMTP (RFC2821) must have been sent
 	 */
 	privdata->rejectCode = "554";
 
6489ba59
 	if(privdata->dataSocket == -1)
 		/*
 		 * No headers - make connection with clamd
 		 */
 		if(!connect2clamd(privdata)) {
 			clamfi_cleanup(ctx);
 			return cl_error;
 		}
 
384942c2
 	if(detect_forged_local_address && privdata->from &&
 	   (!privdata->sender) && !isWhitelisted(privdata->from)) {
 		char me[MAXHOSTNAMELEN + 1];
 		const char *ptr;
 
 		if(gethostname(me, sizeof(me) - 1) < 0) {
 			if(use_syslog)
 				syslog(LOG_WARNING, _("clamfi_eoh: gethostname failed"));
 			return SMFIS_CONTINUE;
 		}
 		ptr = strstr(privdata->from, me);
 		if(ptr && (ptr != privdata->from) && (*--ptr == '@')) {
 			if(use_syslog)
 				syslog(LOG_NOTICE, _("Rejected email falsely claiming to be from %s"), privdata->from);
 			smfi_setreply(ctx, "554", "5.7.1", _("You have claimed to be from me, but you are not"));
 			broadcast(_("Forged local address detected"));
 			clamfi_cleanup(ctx);
 			return SMFIS_REJECT;
 		}
 	}
 
0cb97bcb
 	if(clamfi_send(privdata, 1, "\n") != 1) {
b151ef55
 		clamfi_cleanup(ctx);
b0a42ec6
 		return cl_error;
b151ef55
 	}
 
b14e9e77
 	/*
 	 * See if the e-mail is only going to members of the list
 	 * of users we don't scan for. If it is, don't scan, otherwise
 	 * scan
 	 *
 	 * scan = false
 	 * FORALL recipients
 	 *	IF receipient NOT MEMBER OF white address list
 	 *	THEN
 	 *		scan = true
 	 *	FI
 	 * ENDFOR
 	 */
5aeb0d51
 	for(to = privdata->to; *to; to++)
 		if(!isWhitelisted(*to))
b14e9e77
 			/*
 			 * This recipient is not on the whitelist,
 			 * no need to check any further
 			 */
 			return SMFIS_CONTINUE;
 	/*
 	 * Didn't find a recipient who is not on the white list, so all
 	 * must be on the white list, so just accept the e-mail
 	 */
 	if(use_syslog)
4e136199
 		syslog(LOG_NOTICE, _("clamfi_eoh: ignoring whitelisted message"));
b14e9e77
 #ifdef	CL_DEBUG
dd63090a
 	cli_dbgmsg(_("clamfi_eoh: ignoring whitelisted message\n"));
b14e9e77
 #endif
 	clamfi_cleanup(ctx);
 
 	return SMFIS_ACCEPT;
b151ef55
 }
 
 static sfsistat
 clamfi_body(SMFICTX *ctx, u_char *bodyp, size_t len)
 {
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
0cb97bcb
 	int nbytes;
b151ef55
 
 	if(logVerbose)
4e136199
 		syslog(LOG_DEBUG, _("clamfi_envbody: %u bytes"), len);
b151ef55
 #ifdef	CL_DEBUG
4e136199
 	cli_dbgmsg(_("clamfi_envbody: %u bytes\n"), len);
b151ef55
 #endif
 
e483f51a
 	if(len == 0)	/* unlikely */
 		return SMFIS_CONTINUE;
 
0cb97bcb
 	nbytes = clamfi_send(privdata, len, (char *)bodyp);
5691e168
 	if(streamMaxLength > 0L) {
cc511c7a
 		if(privdata->numBytes > streamMaxLength) {
6ad582fe
 			if(use_syslog) {
 				const char *sendmailId = smfi_getsymval(ctx, "i");
 				if(sendmailId == NULL)
 					sendmailId = "Unknown";
4e136199
 				syslog(LOG_NOTICE, _("%s: Message more than StreamMaxLength (%ld) bytes - not scanned"),
6ad582fe
 					sendmailId, streamMaxLength);
 			}
0cb97bcb
 			if(!nflag)
4e136199
 				smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned - StreamMaxLength exceeded"));
0cb97bcb
 
 			return SMFIS_ACCEPT;	/* clamfi_close will be called */
cc511c7a
 		}
 	}
c6a870ad
 	if((size_t)nbytes < len) {
cc511c7a
 		clamfi_cleanup(ctx);	/* not needed, but just to be safe */
b0a42ec6
 		return cl_error;
b151ef55
 	}
e6bffccb
 	if(Sflag) {
3166c010
 		if(privdata->body) {
 			assert(privdata->bodyLen > 0);
a121491a
 			privdata->body = cli_realloc(privdata->body, privdata->bodyLen + len);
3166c010
 			memcpy(&privdata->body[privdata->bodyLen], bodyp, len);
 			privdata->bodyLen += len;
e6bffccb
 		} else {
3166c010
 			assert(privdata->bodyLen == 0);
cbd7686b
 			privdata->body = cli_malloc(len);
3166c010
 			memcpy(privdata->body, bodyp, len);
 			privdata->bodyLen = len;
e6bffccb
 		}
 	}
b151ef55
 	return SMFIS_CONTINUE;
 }
 
 static sfsistat
 clamfi_eom(SMFICTX *ctx)
 {
 	int rc = SMFIS_CONTINUE;
 	char *ptr;
c96f7461
 	const char *sendmailId;
b151ef55
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
 	char mess[128];
ebb16454
 #ifdef	SESSION
 	struct session *session;
 #endif
b151ef55
 
 	if(logVerbose)
 		syslog(LOG_DEBUG, "clamfi_eom");
ebb16454
 
cbd7686b
 	cli_dbgmsg("clamfi_eom\n");
ebb16454
 
baff8875
 	if(!nflag) {
 		/*
 		 * remove any existing claims that it's virus free so that
 		 * downstream checkers aren't fooled by a carefully crafted
 		 * virus.
 		 */
51cddddf
 		int i;
 
baff8875
 		for(i = privdata->statusCount; i > 0; --i)
51cddddf
 			if(smfi_chgheader(ctx, "X-Virus-Status", i, NULL) == MI_FAILURE)
 				if(use_syslog)
 					syslog(LOG_WARNING, _("Failed to delete X-Virus-Status header %d"), i);
 	}
 
ebb16454
 #ifdef	CL_DEBUG
b151ef55
 	assert(privdata != NULL);
f5d23f2e
 #ifndef	SESSION
3454ad43
 	assert((privdata->cmdSocket >= 0) || (privdata->filename != NULL));
 	assert(!((privdata->cmdSocket >= 0) && (privdata->filename != NULL)));
f5d23f2e
 #endif
b151ef55
 	assert(privdata->dataSocket >= 0);
 #endif
 
7ca2a6cf
 	if(external) {
ccf2bd40
 		close(privdata->dataSocket);
 		privdata->dataSocket = -1;
 	}
 
7ca2a6cf
 	if(!external) {
ccf2bd40
 		const char *virname;
 		unsigned long int scanned = 0L;
 
 		/*
 		 * TODO: consider using cl_scandesc and not using a temporary
 		 *	file from the mail being read in
 		 */
1ae303c2
 		pthread_mutex_lock(&root_mutex);
c840fa41
 		privdata->root = cl_dup(root);
1ae303c2
 		pthread_mutex_unlock(&root_mutex);
c840fa41
 		if(privdata->root == NULL) {
 			cli_errmsg("privdata->root == NULL\n");
1ae303c2
 			clamfi_cleanup(ctx);
 			return cl_error;
 		}
c840fa41
 		switch(cl_scanfile(privdata->filename, &virname, &scanned, privdata->root, &limits, options)) {
21e6b430
 			case CL_CLEAN:
c840fa41
 				if(logClean) {
 					snprintf(mess, sizeof(mess), "%s: OK", privdata->filename);
 					logger(mess);
 				}
21e6b430
 				strcpy(mess, "OK");
 				break;
 			case CL_VIRUS:
c6a870ad
 				snprintf(mess, sizeof(mess), "%s: %s FOUND", privdata->filename, virname);
9c8806fb
 				logger(mess);
21e6b430
 				break;
 			default:
c6a870ad
 				snprintf(mess, sizeof(mess), "%s: %s ERROR", privdata->filename, cl_strerror(rc));
9c8806fb
 				logger(mess);
21e6b430
 				break;
 		}
c840fa41
 		cl_free(privdata->root);
 		privdata->root = NULL;
b151ef55
 
ccf2bd40
 #ifdef	SESSION
 		session = NULL;
 #endif
 	} else if(privdata->filename) {
3454ad43
 		char cmdbuf[1024];
 		/*
 		 * Create socket to talk to clamd.
 		 */
ccf2bd40
 #ifndef	SESSION
3454ad43
 		struct sockaddr_un server;
ebb16454
 #endif
3454ad43
 		int nbytes;
 
f5d23f2e
 		snprintf(cmdbuf, sizeof(cmdbuf) - 1, "SCAN %s", privdata->filename);
ae17ae8e
 		cli_dbgmsg("clamfi_eom: SCAN %s\n", privdata->filename);
f5d23f2e
 
 		nbytes = (int)strlen(cmdbuf);
 
 #ifdef	SESSION
ebb16454
 		session = sessions;
 		if(send(session->sock, cmdbuf, nbytes, 0) < nbytes) {
f5d23f2e
 			perror("send");
 			clamfi_cleanup(ctx);
 			if(use_syslog)
1d4e7005
 				syslog(LOG_ERR, _("failed to send SCAN %s command to clamd"), privdata->filename);
f5d23f2e
 			return cl_error;
 		}
 #else
3454ad43
 		if((privdata->cmdSocket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
 			perror("socket");
85c1dbfd
 			clamfi_cleanup(ctx);
3454ad43
 			return cl_error;
 		}
ebb16454
 		memset((char *)&server, 0, sizeof(struct sockaddr_un));
 		server.sun_family = AF_UNIX;
 		strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
 
3454ad43
 		if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
 			perror(localSocket);
85c1dbfd
 			clamfi_cleanup(ctx);
3454ad43
 			return cl_error;
 		}
 		if(send(privdata->cmdSocket, cmdbuf, nbytes, 0) < nbytes) {
 			perror("send");
 			clamfi_cleanup(ctx);
 			if(use_syslog)
1d4e7005
 				syslog(LOG_ERR, _("failed to send SCAN command to clamd"));
3454ad43
 			return cl_error;
 		}
 
 		shutdown(privdata->cmdSocket, SHUT_WR);
f5d23f2e
 #endif
3454ad43
 	}
ebb16454
 #ifdef	SESSION
 	else
 		session = &sessions[privdata->serverNumber];
 #endif
3454ad43
 
7ca2a6cf
 	if(external) {
ad5eb067
 		int nbytes;
f5d23f2e
 #ifdef	SESSION
ccf2bd40
 #ifdef	CL_DEBUG
 		if(debug_level >= 4)
c6a870ad
 			cli_dbgmsg(_("Waiting to read status from fd %d\n"),
ccf2bd40
 				session->sock);
 #endif
ad5eb067
 		nbytes = clamd_recv(session->sock, mess, sizeof(mess) - 1);
f5d23f2e
 #else
ad5eb067
 		nbytes = clamd_recv(privdata->cmdSocket, mess, sizeof(mess) - 1);
f5d23f2e
 #endif
ad5eb067
 		if(nbytes > 0) {
 			mess[nbytes] = '\0';
ccf2bd40
 			if((ptr = strchr(mess, '\n')) != NULL)
 				*ptr = '\0';
b151ef55
 
ccf2bd40
 			if(logVerbose)
 				syslog(LOG_DEBUG, _("clamfi_eom: read %s"), mess);
 			cli_dbgmsg(_("clamfi_eom: read %s\n"), mess);
 		} else {
48e8856b
 #ifdef	MAXHOSTNAMELEN
 			char hostname[MAXHOSTNAMELEN + 1];
 
 			cli_strtokbuf(serverHostNames, privdata->serverNumber, ":", hostname);
 			if(strcmp(hostname, "127.0.0.1") == 0)
 				gethostname(hostname, sizeof(hostname));
 #else
 			char *hostname = cli_strtok(serverHostNames, privdata->serverNumber, ":");
 #endif
ccf2bd40
 			/*
 			 * TODO: if more than one host has been specified, try
e483f51a
 			 * another one - setting cl_error to SMFIS_TEMPFAIL
 			 * helps by forcing a retry
ccf2bd40
 			 */
 			clamfi_cleanup(ctx);
48e8856b
 			syslog(LOG_NOTICE, _("clamfi_eom: read nothing from clamd on %s"), hostname);
b151ef55
 #ifdef	CL_DEBUG
48e8856b
 			cli_dbgmsg(_("clamfi_eom: read nothing from clamd on %s\n"), hostname);
b151ef55
 #endif
a3a024c5
 #ifdef	SESSION
ccf2bd40
 			pthread_mutex_lock(&sstatus_mutex);
 			session->status = CMDSOCKET_DOWN;
 			pthread_mutex_unlock(&sstatus_mutex);
a3a024c5
 #endif
ccf2bd40
 			return cl_error;
 		}
b151ef55
 
85563fe5
 #ifdef	SESSION
ccf2bd40
 		pthread_mutex_lock(&sstatus_mutex);
 		if(session->status == CMDSOCKET_INUSE)
 			session->status = CMDSOCKET_FREE;
 		pthread_mutex_unlock(&sstatus_mutex);
85563fe5
 #else
ccf2bd40
 		close(privdata->cmdSocket);
 		privdata->cmdSocket = -1;
f5d23f2e
 #endif
ccf2bd40
 	}
0b6bce84
 
c96f7461
 	sendmailId = smfi_getsymval(ctx, "i");
6ad582fe
 	if(sendmailId == NULL)
 		sendmailId = "Unknown";
6489ba59
 
71f25024
 	if(!nflag) {
 		char buf[1024];
 
 		/*
73ffa07e
 		 * Include the hostname where the scan took place
71f25024
 		 */
7ca2a6cf
 		if(localSocket || !external) {
09e761fb
 #ifdef	MAXHOSTNAMELEN
 			char hostname[MAXHOSTNAMELEN + 1];
 #else
 			char hostname[65];
 #endif
73ffa07e
 
6d113e37
 			if(gethostname(hostname, sizeof(hostname)) < 0) {
8eb408e5
 				const char *j = smfi_getsymval(ctx, "{j}");
6d113e37
 
8eb408e5
 				if(j)
 					strncpy(hostname, j,
6d113e37
 						sizeof(hostname) - 1);
 				else
83ae9a24
 					strcpy(hostname, _("Error determining host"));
6d113e37
 			} else if(strchr(hostname, '.') == NULL) {
 				/*
 				 * Determine fully qualified name
 				 */
 				struct hostent hostent;
 
 				if(clamfi_gethostbyname(hostname, &hostent, buf, sizeof(buf)) == 0)
 					strncpy(hostname, hostent.h_name, sizeof(hostname));
 			}
73ffa07e
 
09e761fb
 #ifdef	SESSION
c78ec4f3
 			pthread_mutex_lock(&version_mutex);
09e761fb
 			snprintf(buf, sizeof(buf) - 1, "%s on %s",
 				clamav_versions[privdata->serverNumber], hostname);
c78ec4f3
 			pthread_mutex_unlock(&version_mutex);
09e761fb
 #else
 			snprintf(buf, sizeof(buf) - 1, "%s on %s",
73ffa07e
 				clamav_version, hostname);
09e761fb
 #endif
73ffa07e
 		} else {
8c42185e
 #ifdef	MAXHOSTNAMELEN
 			char hostname[MAXHOSTNAMELEN + 1];
39378e7f
 
8c42185e
 			if(cli_strtokbuf(serverHostNames, privdata->serverNumber, ":", hostname)) {
48e8856b
 				if(strcmp(hostname, "127.0.0.1") == 0)
 					gethostname(hostname, sizeof(hostname));
8c42185e
 #else
 			char *hostname = cli_strtok(serverHostNames, privdata->serverNumber, ":");
39378e7f
 			if(hostname) {
8c42185e
 #endif
 
09e761fb
 #ifdef	SESSION
c78ec4f3
 				pthread_mutex_lock(&version_mutex);
09e761fb
 				snprintf(buf, sizeof(buf) - 1, "%s on %s",
 					clamav_versions[privdata->serverNumber], hostname);
c78ec4f3
 				pthread_mutex_unlock(&version_mutex);
09e761fb
 #else
 				snprintf(buf, sizeof(buf) - 1, "%s on %s",
 					clamav_version, hostname);
 #endif
8c42185e
 #ifndef	MAXHOSTNAMELEN
39378e7f
 				free(hostname);
8c42185e
 #endif
39378e7f
 			} else
 				/* sanity check failed - should issue warning */
4e136199
 				strcpy(buf, _("Error determining host"));
39378e7f
 		}
71f25024
 		smfi_addheader(ctx, "X-Virus-Scanned", buf);
 	}
40c9a608
 
c96f7461
 	if(strstr(mess, "ERROR") != NULL) {
de23431a
 		if(strstr(mess, "Size limit reached") != NULL) {
0a867b46
 			/*
 			 * Clamd has stopped on StreamMaxLength before us
 			 */
 			if(use_syslog)
4e136199
 				syslog(LOG_NOTICE, _("%s: Message more than StreamMaxLength (%ld) bytes - not scanned"),
0a867b46
 					sendmailId, streamMaxLength);
0cb97bcb
 			if(!nflag)
4e136199
 				smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned - StreamMaxLength exceeded"));
39378e7f
 			clamfi_cleanup(ctx);	/* not needed, but just to be safe */
0a867b46
 			return SMFIS_ACCEPT;
 		}
0cb97bcb
 		if(!nflag)
4e136199
 			smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned"));
6489ba59
 
c96f7461
 		cli_warnmsg("%s: %s\n", sendmailId, mess);
e27af651
 		if(use_syslog)
c96f7461
 			syslog(LOG_ERR, "%s: %s\n", sendmailId, mess);
ebb16454
 		rc = cl_error;
dd63090a
 	} else if((ptr = strstr(mess, "FOUND")) != NULL) {
5a4966af
 		/*
 		 * Fixme: This will give false positives if the
 		 *	word "FOUND" is in the email, e.g. the
 		 *	quarantine directory is /tmp/VIRUSES-FOUND
 		 */
dd68be92
 		char reject[1024];
97727aa0
 		char **to, *virusname;
62f5a55b
 
6d113e37
 		/*
 		 Remove the "FOUND" word, and the space before it
 		 */
 		*--ptr = '\0';
b151ef55
 
97727aa0
 		/* skip over 'stream/filename: ' at the start */
 		if((virusname = strchr(mess, ':')) != NULL)
 			virusname = &virusname[2];
 		else
 			virusname = mess;
 
51cddddf
 		if(!nflag) {
 			char buf[129];
 
 			snprintf(buf, sizeof(buf) - 1, "%s %s", _("Infected with"), virusname);
 			smfi_addheader(ctx, "X-Virus-Status", buf);
 		}
40c9a608
 
62f5a55b
 		if(use_syslog) {
 			/*
 			 * Setup err as a list of recipients
 			 */
 			char *err = (char *)cli_malloc(1024);
 			int i;
b151ef55
 
62f5a55b
 			if(err == NULL) {
 				clamfi_cleanup(ctx);
 				return cl_error;
 			}
063b6c33
 
62f5a55b
 			/*
3c98c93b
 			 * Use snprintf rather than printf since we don't know
 			 * the length of privdata->from and may get a buffer
 			 * overrun
62f5a55b
 			 */
4e136199
 			snprintf(err, 1023, _("Intercepted virus from %s to"),
62f5a55b
 				privdata->from);
b151ef55
 
62f5a55b
 			ptr = strchr(err, '\0');
b151ef55
 
62f5a55b
 			i = 1024;
c6259ac5
 
62f5a55b
 			for(to = privdata->to; *to; to++) {
 				/*
 				 * Re-alloc if we are about run out of buffer space
 				 */
 				if(&ptr[strlen(*to) + 2] >= &err[i]) {
 					i += 1024;
a121491a
 					err = cli_realloc(err, i);
 					if(err == NULL) {
 						clamfi_cleanup(ctx);
 						return cl_error;
 					}
62f5a55b
 					ptr = strchr(err, '\0');
 				}
 				ptr = strrcpy(ptr, " ");
 				ptr = strrcpy(ptr, *to);
b151ef55
 			}
62f5a55b
 			(void)strcpy(ptr, "\n");
b151ef55
 
e2bb746e
 			/* Include the sendmail queue ID in the log */
503b2956
 			syslog(LOG_NOTICE, "%s: %s %s", sendmailId, mess, err);
b151ef55
 #ifdef	CL_DEBUG
85563fe5
 			cli_dbgmsg("%s", err);
b151ef55
 #endif
62f5a55b
 			free(err);
 		}
b151ef55
 
1c3f1ce1
 		if(!qflag) {
b696653a
 			char cmd[128];
64d9c7a1
 			FILE *sendmail;
b696653a
 
97727aa0
 			/*
 			 * If the oflag is given this sendmail command
 			 * will cause the mail being generated here to be
 			 * scanned. So if oflag is given we just put the
 			 * item in the queue so there's no scanning of two
 			 * messages at once. It'll still be scanned, but
 			 * not at the same time as the incoming message
fce82877
 			 *
0ac43d02
 			 * FIXME: there is a race condition here when sendmail
 			 * and clamav-milter run on the same machine. If the
fce82877
 			 * system is very overloaded this sendmail can
 			 * take a long time to start - and may even fail
 			 * is the LA is > REFUSE_LA. In all the time we're
 			 * taking to start this sendmail, the sendmail that's
 			 * started us may timeout waiting for a response and
 			 * let the virus through (albeit tagged with
 			 * X-Virus-Status: Infected) because we haven't
 			 * sent SMFIS_DISCARD or SMFIS_REJECT
f74d7f94
 			 *
 			 * -i flag, suggested by Michal Jaegermann
 			 *	<michal@harddata.com>
97727aa0
 			 */
 			snprintf(cmd, sizeof(cmd) - 1,
f74d7f94
 				(oflag || fflag) ? "%s -t -i -odq" : "%s -t -i",
c51c112c
 				SENDMAIL_BIN);
b696653a
 
5aeb0d51
 			cli_dbgmsg("Calling %s\n", cmd);
b696653a
 			sendmail = popen(cmd, "w");
 
1c3f1ce1
 			if(sendmail) {
b61a0781
 				if(from && from[0])
 					fprintf(sendmail, "From: %s\n", from);
 				else
 					fprintf(sendmail, "From: %s\n", privdata->from);
c6a870ad
 				if(bflag && privdata->from) {
 					fprintf(sendmail, "To: %s\n", privdata->from);
1c3f1ce1
 					fprintf(sendmail, "Cc: %s\n", postmaster);
 				} else
 					fprintf(sendmail, "To: %s\n", postmaster);
 
c6a870ad
 				if((!pflag) && privdata->to)
1c3f1ce1
 					for(to = privdata->to; *to; to++)
 						fprintf(sendmail, "Cc: %s\n", *to);
9873b3c1
 				/*
d69535ae
 				 * Auto-submitted is still a draft, keep an
9873b3c1
 				 * eye on its format
 				 */
 				fputs("Auto-Submitted: auto-submitted (antivirus notify)\n", sendmail);
d69535ae
 				/* "Sergey Y. Afonin" <asy@kraft-s.ru> */
 				if((ptr = smfi_getsymval(ctx, "{_}")) != NULL)
 					fprintf(sendmail,
 						"X-Infected-Received-From: %s\n",
 						ptr);
4e136199
 				fputs(_("Subject: Virus intercepted\n\n"), sendmail);
1c3f1ce1
 
1392cf22
 				if((templatefile == NULL) ||
0b79c1b7
 				   (sendtemplate(ctx, templatefile, sendmail, virusname) < 0)) {
95ec27d0
 					/*
31d36bd3
 					 * Use our own hardcoded template
 					 */
1392cf22
 					if(bflag)
4e136199
 						fputs(_("A message you sent to\n"), sendmail);
1392cf22
 					else if(pflag)
 						/*
31d36bd3
 						 * The message is only going to
 						 * the postmaster, so include
 						 * some useful information
1392cf22
 						 */
4e136199
 						fprintf(sendmail, _("The message %1$s sent from %2$s to\n"),
c6a870ad
 							sendmailId, privdata->from);
1392cf22
 					else
4e136199
 						fprintf(sendmail, _("A message sent from %s to\n"),
c6a870ad
 							privdata->from);
1392cf22
 
 					for(to = privdata->to; *to; to++)
62f5a55b
 						fprintf(sendmail, "\t%s\n", *to);
4e136199
 					fprintf(sendmail, _("contained %s and has not been delivered.\n"), virusname);
1392cf22
 
ae17ae8e
 					if(quarantine_dir != NULL)
5a4966af
 						fprintf(sendmail, _("\nThe message in question has been quarantined as %s\n"), privdata->filename);
1392cf22
 
97727aa0
 					if(hflag) {
4e136199
 						fprintf(sendmail, _("\nThe message was received by %1$s from %2$s via %3$s\n\n"),
c6a870ad
 							smfi_getsymval(ctx, "j"), privdata->from,
97727aa0
 							smfi_getsymval(ctx, "_"));
4e136199
 						fputs(_("For your information, the original message headers were:\n\n"), sendmail);
97727aa0
 						header_list_print(privdata->headers, sendmail);
 					} else if(privdata->received)
063b6c33
 						/*
 						 * TODO: parse this to find
 						 * real infected machine.
 						 * Need to decide how to find
 						 * if it's a dynamic IP from a
 						 * dial up account in which
 						 * case there may not be much
 						 * we can do if that DHCP has
 						 * set the hostname...
 						 */
4e136199
 						fprintf(sendmail, _("\nThe infected machine is likely to be here:\n%s\t\n"),
063b6c33
 							privdata->received);
 
d9b674b2
 				}
3454ad43
 
5aeb0d51
 				cli_dbgmsg("Waiting for %s to finish\n", cmd);
1ae303c2
 				if(pclose(sendmail) != 0)
 					if(use_syslog)
 						syslog(LOG_ERR, "%s: Failed to notify clamAV interception - see dead.letter", sendmailId);
9f47e7ab
 			} else if(use_syslog)
 				syslog(LOG_WARNING, _("Can't execute '%s' to send virus notice"), cmd);
b151ef55
 		}
3454ad43
 
ebb16454
 		if(quarantine_dir) {
c840fa41
 			qfile(privdata, sendmailId, virusname);
 
3454ad43
 			/*
0cb97bcb
 			 * Cleanup filename here otherwise clamfi_free() will
 			 * delete the file that we wish to keep because it
 			 * is infected
3454ad43
 			 */
 			free(privdata->filename);
 			privdata->filename = NULL;
 		}
 
bb9979e6
 		if(quarantine) {
 			for(to = privdata->to; *to; to++) {
 				smfi_delrcpt(ctx, *to);
3454ad43
 				smfi_addheader(ctx, "X-Original-To", *to);
bb9979e6
 				free(*to);
 			}
 			free(privdata->to);
 			privdata->to = NULL;
c51c112c
 			/*
 			 * NOTE: on a closed relay this will not work
 			 * if the recipient is a remote address
 			 */
bb9979e6
 			if(smfi_addrcpt(ctx, quarantine) == MI_FAILURE) {
 				if(use_syslog)
21e6b430
 					syslog(LOG_ERR, _("Can't set quarantine user %s"), quarantine);
bb9979e6
 				else
4e136199
 					cli_warnmsg(_("Can't set quarantine user %s\n"), quarantine);
e483f51a
 				rc = (privdata->discard) ? SMFIS_DISCARD : SMFIS_REJECT;
21e6b430
 			} else {
 				if(use_syslog)
 					syslog(LOG_DEBUG, "Redirected virus to %s", quarantine);
 				cli_dbgmsg("Redirected virus to %s\n", quarantine);
ced83bee
 				setsubject(ctx, virusname);
21e6b430
 			}
ced83bee
 		} else if(advisory)
 			setsubject(ctx, virusname);
 		else if(rejectmail) {
a121491a
 			if(privdata->discard)
 				rc = SMFIS_DISCARD;
 			else
 				rc = SMFIS_REJECT;	/* Delete the e-mail */
 		} else
a7164828
 			rc = SMFIS_DISCARD;
b151ef55
 
21e6b430
 		/*
 		 * Don't drop the message if it's been forwarded to a
 		 * quarantine email
 		 */
da7f01e3
 		snprintf(reject, sizeof(reject) - 1, _("virus %s detected by ClamAV - http://www.clamav.net"), virusname);
a121491a
 		smfi_setreply(ctx, (char *)privdata->rejectCode, "5.7.1", reject);
3eacc10d
 		broadcast(mess);
ad5eb067
 	} else if(strstr(mess, "OK") == NULL) {
dd63090a
 		if(!nflag)
 			smfi_addheader(ctx, "X-Virus-Status", _("Unknown"));
 		if(use_syslog)
 			syslog(LOG_ERR, _("%s: incorrect message \"%s\" from clamd"),
 				sendmailId,
 				mess);
 		rc = cl_error;
 	} else {
 		if(!nflag)
 			smfi_addheader(ctx, "X-Virus-Status", _("Clean"));
 
 		if(use_syslog && logClean)
 			/* Include the sendmail queue ID in the log */
 			syslog(LOG_NOTICE, _("%s: clean message from %s"),
 				sendmailId,
 				(privdata->from) ? privdata->from : _("an unknown sender"));
 
 		if(privdata->body) {
 			/*
 			 * Add a signature that all has been scanned OK
 			 */
 			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);
 				}
 			}
 		}
b151ef55
 	}
 	clamfi_cleanup(ctx);
 
 	return rc;
 }
 
 static sfsistat
 clamfi_abort(SMFICTX *ctx)
 {
 #ifdef	CL_DEBUG
ccf2bd40
 	if(logVerbose)
b151ef55
 		syslog(LOG_DEBUG, "clamfi_abort");
 #endif
 
74e6c404
 	cli_dbgmsg("clamfi_abort\n");
b151ef55
 
 	clamfi_cleanup(ctx);
 
3c98c93b
 	cli_dbgmsg("clamfi_abort returns\n");
 
b0a42ec6
 	return cl_error;
b151ef55
 }
 
 static sfsistat
 clamfi_close(SMFICTX *ctx)
 {
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
 
cbd7686b
 	cli_dbgmsg("clamfi_close\n");
0cb97bcb
 	if(privdata != NULL)
 		clamfi_cleanup(ctx);
b151ef55
 
 	if(logVerbose)
 		syslog(LOG_DEBUG, "clamfi_close");
 
 	return SMFIS_CONTINUE;
 }
 
 static void
 clamfi_cleanup(SMFICTX *ctx)
 {
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
 
3c98c93b
 	cli_dbgmsg("clamfi_cleanup\n");
 
3613bd91
 	if(privdata) {
64d5612d
 		clamfi_free(privdata);
 		smfi_setpriv(ctx, NULL);
 	}
 }
 
 static void
 clamfi_free(struct privdata *privdata)
 {
85563fe5
 	cli_dbgmsg("clamfi_free\n");
5a4966af
 
64d5612d
 	if(privdata) {
ebb16454
 #ifdef	SESSION
 		struct session *session;
 #endif
3166c010
 		if(privdata->body)
 			free(privdata->body);
 
3613bd91
 		if(privdata->dataSocket >= 0) {
 			close(privdata->dataSocket);
 			privdata->dataSocket = -1;
 		}
b151ef55
 
3454ad43
 		if(privdata->filename != NULL) {
73ffa07e
 			/*
3c98c93b
 			 * Don't print an error if the file hasn't been
 			 * created yet
73ffa07e
 			 */
 			if((unlink(privdata->filename) < 0) && (errno != ENOENT)) {
3454ad43
 				perror(privdata->filename);
0cb97bcb
 				if(use_syslog)
 					syslog(LOG_ERR,
4e136199
 						_("Can't remove clean file %s"),
0cb97bcb
 						privdata->filename);
 			}
3454ad43
 			free(privdata->filename);
 			privdata->filename = NULL;
 		}
 
3613bd91
 		if(privdata->from) {
b151ef55
 #ifdef	CL_DEBUG
3613bd91
 			if(debug_level >= 9)
cbd7686b
 				cli_dbgmsg("Free privdata->from\n");
b151ef55
 #endif
3613bd91
 			free(privdata->from);
 			privdata->from = NULL;
 		}
b151ef55
 
3d1b1f07
 		if(privdata->subject) {
 			free(privdata->subject);
 			privdata->subject = NULL;
 		}
384942c2
 		if(privdata->sender) {
 			free(privdata->sender);
 			privdata->sender = NULL;
 		}
3d1b1f07
 
3613bd91
 		if(privdata->to) {
 			char **to;
b151ef55
 
3613bd91
 			for(to = privdata->to; *to; to++) {
b151ef55
 #ifdef	CL_DEBUG
3613bd91
 				if(debug_level >= 9)
cbd7686b
 					cli_dbgmsg("Free *privdata->to\n");
b151ef55
 #endif
3613bd91
 				free(*to);
 			}
b151ef55
 #ifdef	CL_DEBUG
3613bd91
 			if(debug_level >= 9)
cbd7686b
 				cli_dbgmsg("Free privdata->to\n");
b151ef55
 #endif
3613bd91
 			free(privdata->to);
 			privdata->to = NULL;
 		}
b151ef55
 
7ca2a6cf
 		if(external) {
5a4966af
 #ifdef	SESSION
ccf2bd40
 			session = &sessions[privdata->serverNumber];
 			pthread_mutex_lock(&sstatus_mutex);
 			if(session->status == CMDSOCKET_INUSE) {
 				/*
 				 * Probably we've got here because
83ae9a24
 				 * StreamMaxLength has been reached
ccf2bd40
 				 */
d7e0b4e6
 #if	0
ccf2bd40
 				pthread_mutex_unlock(&sstatus_mutex);
 				if(readTimeout) {
 					char buf[64];
 					const int fd = session->sock;
 
 					cli_dbgmsg("clamfi_free: flush server %d fd %d\n",
 						privdata->serverNumber, fd);
 
 					/*
 					 * FIXME: whenever this code gets
 					 *	executed, all of the PINGs fail
 					 *	in the next watchdog cycle
 					 */
 					while(clamd_recv(fd, buf, sizeof(buf)) > 0)
 						;
 				}
 				pthread_mutex_lock(&sstatus_mutex);
 #endif
 				/* Force a reset */
 				session->status = CMDSOCKET_DOWN;
 			}
85563fe5
 			pthread_mutex_unlock(&sstatus_mutex);
ccf2bd40
 #else
 			if(privdata->cmdSocket >= 0) {
85563fe5
 				char buf[64];
f5d23f2e
 
41190088
 				/*
ccf2bd40
 				 * Flush the remote end so that clamd doesn't
 				 * get a SIGPIPE
41190088
 				 */
ccf2bd40
 				if(readTimeout)
 					while(clamd_recv(privdata->cmdSocket, buf, sizeof(buf)) > 0)
 						;
 				close(privdata->cmdSocket);
 				privdata->cmdSocket = -1;
85563fe5
 			}
d7e0b4e6
 #endif
c840fa41
 		} else if(privdata->root)
 			/*
 			 * Since only one of clamfi_abort() and clamfi_eom()
 			 * can ever be called, and the only cl_dup is in
 			 * clamfi_eom() which calls cl_free soon after, this
 			 * should be overkill, since this can "never happen"
 			 */
 			cl_free(privdata->root);
 
d9b674b2
 		if(privdata->headers)
 			header_list_free(privdata->headers);
b151ef55
 
 #ifdef	CL_DEBUG
3613bd91
 		if(debug_level >= 9)
cbd7686b
 			cli_dbgmsg("Free privdata\n");
b151ef55
 #endif
97727aa0
 		if(privdata->received)
063b6c33
 			free(privdata->received);
3613bd91
 		free(privdata);
 	}
b151ef55
 
 	if(max_children > 0) {
 		pthread_mutex_lock(&n_children_mutex);
85563fe5
 		cli_dbgmsg("clamfi_free: n_children = %d\n", n_children);
b151ef55
 		/*
 		 * Deliberately errs on the side of broadcasting too many times
 		 */
5aeb0d51
 		if(n_children > 0)
 			if(--n_children == 0) {
1ae303c2
 				cli_dbgmsg("%s is idle\n", progname);
41190088
 				if(pthread_cond_broadcast(&watchdog_cond) < 0)
 					perror("pthread_cond_broadcast");
5aeb0d51
 			}
b151ef55
 #ifdef	CL_DEBUG
cbd7686b
 		cli_dbgmsg("pthread_cond_broadcast\n");
b151ef55
 #endif
41190088
 		if(pthread_cond_broadcast(&n_children_cond) < 0)
 			perror("pthread_cond_broadcast");
35d98cf9
 		cli_dbgmsg("<n_children = %d\n", n_children);
b151ef55
 		pthread_mutex_unlock(&n_children_mutex);
 	}
3c98c93b
 	cli_dbgmsg("clamfi_free returns\n");
b151ef55
 }
 
6489ba59
 /*
0cb97bcb
  * Returns < 0 for failure, otherwise the number of bytes sent
6489ba59
  */
b151ef55
 static int
0cb97bcb
 clamfi_send(struct privdata *privdata, size_t len, const char *format, ...)
b151ef55
 {
 	char output[BUFSIZ];
 	const char *ptr;
0cb97bcb
 	int ret = 0;
b151ef55
 
 	assert(format != NULL);
 
 	if(len > 0)
 		/*
 		 * It isn't a NUL terminated string. We have a set number of
 		 * bytes to output.
 		 */
 		ptr = format;
 	else {
 		va_list argp;
 
 		va_start(argp, format);
b90c8762
 		vsnprintf(output, sizeof(output) - 1, format, argp);
b151ef55
 		va_end(argp);
 
 		len = strlen(output);
 		ptr = output;
 	}
 #ifdef	CL_DEBUG
6489ba59
 	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);
 	}
b151ef55
 #endif
 
 	while(len > 0) {
ebb16454
 		const int nbytes = (privdata->filename) ?
3454ad43
 			write(privdata->dataSocket, ptr, len) :
 			send(privdata->dataSocket, ptr, len, 0);
b151ef55
 
ea815cbc
 		assert(privdata->dataSocket >= 0);
 
b151ef55
 		if(nbytes == -1) {
ebb16454
 			if(privdata->filename) {
f7925922
 				perror(privdata->filename);
 				if(use_syslog) {
 #ifdef HAVE_STRERROR_R
 					char buf[32];
 					strerror_r(errno, buf, sizeof(buf));
 					syslog(LOG_ERR,
4e136199
 						_("write failure (%u bytes) to %s: %s"),
f7925922
 						len, privdata->filename, buf);
 #else
4e136199
 					syslog(LOG_ERR, _("write failure (%u bytes) to %s: %s"),
f7925922
 						len, privdata->filename,
 						strerror(errno));
 #endif
 				}
 			} else {
 				if(errno == EINTR)
 					continue;
 				perror("send");
 				if(use_syslog) {
ea815cbc
 #ifdef HAVE_STRERROR_R
f7925922
 					char buf[32];
 					strerror_r(errno, buf, sizeof(buf));
 					syslog(LOG_ERR,
4e136199
 						_("write failure (%u bytes) to clamd: %s"),
f7925922
 						len, buf);
ea815cbc
 #else
4e136199
 					syslog(LOG_ERR, _("write failure (%u bytes) to clamd: %s"), len, strerror(errno));
ea815cbc
 #endif
f7925922
 				}
 				checkClamd();
ea815cbc
 			}
b151ef55
 
 			return -1;
 		}
0cb97bcb
 		ret += nbytes;
b151ef55
 		len -= nbytes;
 		ptr = &ptr[nbytes];
0cb97bcb
 
 		if(streamMaxLength > 0L) {
 			privdata->numBytes += nbytes;
 			if(privdata->numBytes >= streamMaxLength)
 				break;
 		}
b151ef55
 	}
0cb97bcb
 	return ret;
b151ef55
 }
 
 /*
  * Like strcpy, but return the END of the destination, allowing a quicker
  * means of adding to the end of a string than strcat
  */
e6bda975
 #if	0
b151ef55
 static char *
 strrcpy(char *dest, const char *source)
 {
 	/* Pre assertions */
 	assert(dest != NULL);
 	assert(source != NULL);
 	assert(dest != source);
 
 	while((*dest++ = *source++) != '\0')
 		;
 	return(--dest);
 }
e6bda975
 #endif
d2efb3fa
 
 /*
  * Read from clamav - timeout if necessary
  */
 static int
 clamd_recv(int sock, char *buf, size_t len)
 {
 	fd_set rfds;
 	struct timeval tv;
55b1a5f8
 	int ret;
d2efb3fa
 
f5d23f2e
 	assert(sock >= 0);
d18eac06
 
 	if(readTimeout == 0) {
 		do
 			ret = recv(sock, buf, len, 0);
 		while((ret < 0) && (errno == EINTR));
f5d23f2e
 
d18eac06
 		return ret;
 	}
d2efb3fa
 
 	FD_ZERO(&rfds);
 	FD_SET(sock, &rfds);
 
cc511c7a
 	tv.tv_sec = readTimeout;
d2efb3fa
 	tv.tv_usec = 0;
 
d18eac06
 	for(;;) {
 		switch(select(sock + 1, &rfds, NULL, NULL, &tv)) {
 			case -1:
 				if(errno == EINTR)
 					/* FIXME: work out time left */
 					continue;
 				perror("select");
 				return -1;
 			case 0:
 				if(use_syslog)
 					syslog(LOG_ERR, _("No data received from clamd in %d seconds\n"), readTimeout);
 				return 0;
 		}
 		break;
d2efb3fa
 	}
d18eac06
 
 	do
 		ret = recv(sock, buf, len, 0);
 	while((ret < 0) && (errno == EINTR));
 
 	return ret;
d2efb3fa
 }
cdbe607d
 
 /*
  * Read in the signature file
  */
 static off_t
 updateSigFile(void)
 {
 	struct stat statb;
 	int fd;
 
 	if(sigFilename == NULL)
 		/* nothing to read */
a121491a
 		return 0;
cdbe607d
 
 	if(stat(sigFilename, &statb) < 0) {
 		perror(sigFilename);
 		if(use_syslog)
4e136199
 			syslog(LOG_ERR, _("Can't stat %s"), sigFilename);
cdbe607d
 		return 0;
 	}
 
 	if(statb.st_mtime <= signatureStamp)
 		return statb.st_size;	/* not changed */
 
 	fd = open(sigFilename, O_RDONLY);
 	if(fd < 0) {
 		perror(sigFilename);
 		if(use_syslog)
4e136199
 			syslog(LOG_ERR, _("Can't open %s"), sigFilename);
cdbe607d
 		return 0;
 	}
 
 	signatureStamp = statb.st_mtime;
 
c63633c4
 	signature = cli_realloc(signature, statb.st_size);
73ffa07e
 	if(signature)
c840fa41
 		cli_readn(fd, signature, statb.st_size);
cdbe607d
 	close(fd);
 
 	return statb.st_size;
 }
d9b674b2
 
 static header_list_t
8f1f6fb8
 header_list_new(void)
d9b674b2
 {
 	header_list_t ret;
 
 	ret = (header_list_t)cli_malloc(sizeof(struct header_list_struct));
063b6c33
 	if(ret) {
 		ret->first = NULL;
 		ret->last = NULL;
 	}
d9b674b2
 	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;
 
71f25024
 	len = (size_t)(strlen(headerf) + strlen(headerv) + 3);
d9b674b2
 
 	header = (char *)cli_malloc(len);
063b6c33
 	if(header == NULL)
 		return;
 
0a867b46
 	sprintf(header, "%s: %s", headerf, headerv);
cbd7686b
 	new_node = (struct header_node_t *)cli_malloc(sizeof(struct header_node_t));
063b6c33
 	if(new_node == NULL) {
 		free(header);
 		return;
 	}
d9b674b2
 	new_node->header = header;
 	new_node->next = NULL;
ccf2bd40
 	if(list->first == NULL)
d9b674b2
 		list->first = new_node;
 	if(list->last)
 		list->last->next = new_node;
 
 	list->last = new_node;
 }
 
 static void
1941819d
 header_list_print(const header_list_t list, FILE *fp)
d9b674b2
 {
 	const struct header_node_t *iter;
 
1941819d
 	if(list == NULL)
 		return;
 
97727aa0
 	for(iter = list->first; iter; iter = iter->next) {
c51c112c
 		if(strncmp(iter->header, "From ", 5) == 0)
97727aa0
 			putc('>', fp);
d9b674b2
 		fprintf(fp, "%s\n", iter->header);
97727aa0
 	}
d9b674b2
 }
1392cf22
 
6489ba59
 /*
  * Establish a connection to clamd
  *	Returns success (1) or failure (0)
  */
 static int
 connect2clamd(struct privdata *privdata)
 {
a121491a
 	assert(privdata != NULL);
6489ba59
 	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
 
dd63090a
 	if(quarantine_dir || tmpdir) {	/* store message in a temporary file */
6489ba59
 		int ntries = 5;
ae17ae8e
 		const char *dir = (tmpdir) ? tmpdir : quarantine_dir;
6489ba59
 
9f47e7ab
 		/*
 		 * TODO: investigate mkdtemp on LINUX and possibly others
 		 */
9c8806fb
 #ifdef	C_AIX
 		/*
 		 * Patch by Andy Feldt <feldt@nhn.ou.edu>, AIX 5.2 sets errno
 		 * to ENOENT often and sometimes sets errno to 0 (after a
 		 * database reload) for the mkdir call
 		 */
 		if((mkdir(dir, 0700) < 0) && (errno != EEXIST) && (errno > 0) &&
 		    (errno != ENOENT)) {
 #else
5a4966af
 		if((mkdir(dir, 0700) < 0) && (errno != EEXIST)) {
9c8806fb
 #endif
5a4966af
 			perror(dir);
73ffa07e
 			if(use_syslog)
5a4966af
 				syslog(LOG_ERR, _("mkdir %s failed"), dir);
73ffa07e
 			return 0;
 		}
5a4966af
 		privdata->filename = (char *)cli_malloc(strlen(dir) + 12);
6489ba59
 
 		do {
5a4966af
 			sprintf(privdata->filename, "%s/msg.XXXXXX", dir);
6489ba59
 #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)
4e136199
 					syslog(LOG_ERR, _("mktemp %s failed"), privdata->filename);
6489ba59
 				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) {
a121491a
 			perror(privdata->filename);
6489ba59
 			if(use_syslog)
4e136199
 				syslog(LOG_ERR, _("Temporary quarantine file %s creation failed"), privdata->filename);
6489ba59
 			return 0;
 		}
f5d23f2e
 		privdata->serverNumber = 0;
dd63090a
 		cli_dbgmsg("Saving message to %s to scan later\n", privdata->filename);
 	} else {	/* communicate to clamd */
6489ba59
 		int freeServer, nbytes;
 		struct sockaddr_in reply;
8eb408e5
 		unsigned short p;
6489ba59
 		char buf[64];
 
5a4966af
 #ifdef	SESSION
 		struct session *session;
 #else
6489ba59
 		assert(privdata->cmdSocket == -1);
f5d23f2e
 #endif
6489ba59
 
 		/*
 		 * Create socket to talk to clamd. It will tell us the port to
 		 * use to send the data. That will require another socket.
 		 */
 		if(localSocket) {
f5d23f2e
 #ifndef	SESSION
6489ba59
 			struct sockaddr_un server;
 
 			memset((char *)&server, 0, sizeof(struct sockaddr_un));
 			server.sun_family = AF_UNIX;
7cc3891c
 			strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
6489ba59
 
 			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;
 			}
f5d23f2e
 			privdata->serverNumber = 0;
 #endif
6489ba59
 			freeServer = 0;
dd63090a
 		} else {	/* TCP/IP */
f5d23f2e
 #ifdef	SESSION
 			freeServer = findServer();
 			if(freeServer < 0)
 				return 0;
04cf00fb
 			assert(freeServer < (int)max_children);
f5d23f2e
 #else
6489ba59
 			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;
04cf00fb
 			assert(freeServer < (int)max_children);
6489ba59
 
 			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;
 			}
f5d23f2e
 #endif
39378e7f
 			privdata->serverNumber = freeServer;
6489ba59
 		}
 
f5d23f2e
 #ifdef	SESSION
9c8806fb
 		if(serverIPs[freeServer] == (int)inet_addr("127.0.0.1")) {
dd63090a
 			privdata->filename = cli_gentemp(NULL);
ebb16454
 			if(privdata->filename) {
 				cli_dbgmsg("connect2clamd(%d): creating %s\n", freeServer, privdata->filename);
 #ifdef	O_TEXT
 				privdata->dataSocket = open(privdata->filename, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_TEXT, 0600);
 #else
 				privdata->dataSocket = open(privdata->filename, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0600);
 #endif
 				if(privdata->dataSocket < 0) {
 					perror(privdata->filename);
 					free(privdata->filename);
 					privdata->filename = NULL;
 				} else
384942c2
 					return sendToFrom(privdata);
ebb16454
 			}
 		}
3c98c93b
 		cli_dbgmsg("connect2clamd(%d): STREAM\n", freeServer);
ae17ae8e
 
ebb16454
 		session = &sessions[freeServer];
3c98c93b
 		if((session->sock < 0) || (send(session->sock, "STREAM\n", 7, 0) < 7)) {
f5d23f2e
 			perror("send");
85563fe5
 			pthread_mutex_lock(&sstatus_mutex);
ebb16454
 			session->status = CMDSOCKET_DOWN;
85563fe5
 			pthread_mutex_unlock(&sstatus_mutex);
 			cli_warnmsg("Failed sending stream to server %d (fd %d) errno %d\n",
ebb16454
 				freeServer, session->sock, errno);
6489ba59
 			if(use_syslog)
98dcfffc
 				syslog(LOG_ERR, _("failed to send STREAM command clamd server %d"),
 					freeServer);
 
6489ba59
 			return 0;
 		}
f5d23f2e
 #else
6489ba59
 		if(send(privdata->cmdSocket, "STREAM\n", 7, 0) < 7) {
 			perror("send");
 			if(use_syslog)
1d4e7005
 				syslog(LOG_ERR, _("failed to send STREAM command clamd"));
6489ba59
 			return 0;
 		}
 		shutdown(privdata->cmdSocket, SHUT_WR);
f5d23f2e
 #endif
 
 		/*
 		 * Create socket that we'll use to send the data to clamd
 		 */
 		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;
 		}
6489ba59
 
f5d23f2e
 		shutdown(privdata->dataSocket, SHUT_RD);
 
 #ifdef	SESSION
ebb16454
 		nbytes = clamd_recv(session->sock, buf, sizeof(buf));
5a4966af
 		if(nbytes <= 0) {
 			if(nbytes < 0) {
 				perror("recv");
 				if(use_syslog)
 					syslog(LOG_ERR, _("recv failed from clamd getting PORT"));
80a6c423
 				cli_warnmsg("Failed get PORT from server %d (fd %d) errno %d\n",
 					freeServer, session->sock, errno);
5a4966af
 			} else if(use_syslog)
 				syslog(LOG_ERR, _("EOF from clamd getting PORT"));
a3a024c5
 			pthread_mutex_lock(&sstatus_mutex);
ebb16454
 			session->status = CMDSOCKET_DOWN;
a3a024c5
 			pthread_mutex_unlock(&sstatus_mutex);
 			return 0;
 		}
f5d23f2e
 #else
6489ba59
 		nbytes = clamd_recv(privdata->cmdSocket, buf, sizeof(buf));
5a4966af
 		if(nbytes <= 0) {
 			if(nbytes < 0) {
 				perror("recv");
 				if(use_syslog)
 					syslog(LOG_ERR, _("recv failed from clamd getting PORT"));
 			} else if(use_syslog)
 				syslog(LOG_ERR, _("EOF from clamd getting PORT"));
6489ba59
 			return 0;
 		}
a3a024c5
 #endif
6489ba59
 		buf[nbytes] = '\0';
 #ifdef	CL_DEBUG
 		if(debug_level >= 4)
 			cli_dbgmsg("Received: %s", buf);
 #endif
8eb408e5
 		if(sscanf(buf, "PORT %hu\n", &p) != 1) {
6489ba59
 			if(use_syslog)
4e136199
 				syslog(LOG_ERR, _("Expected port information from clamd, got '%s'"),
6489ba59
 					buf);
 			else
4e136199
 				cli_warnmsg(_("Expected port information from clamd, got '%s'\n"),
6489ba59
 					buf);
63e6694b
 #ifdef	SESSION
ebb16454
 			session->status = CMDSOCKET_DOWN;
63e6694b
 			pthread_mutex_unlock(&sstatus_mutex);
 #endif
6489ba59
 			return 0;
 		}
 
 		memset((char *)&reply, 0, sizeof(struct sockaddr_in));
 		reply.sin_family = AF_INET;
8eb408e5
 		reply.sin_port = (in_port_t)htons(p);
6489ba59
 
 		assert(serverIPs != NULL);
 
 		reply.sin_addr.s_addr = serverIPs[freeServer];
 
 #ifdef	CL_DEBUG
 		if(debug_level >= 4)
ccf2bd40
 #ifdef	SESSION
 			cli_dbgmsg(_("Connecting to local port %d - data %d cmd %d\n"),
 				p, privdata->dataSocket, session->sock);
 #else
 			cli_dbgmsg(_("Connecting to local port %d - data %d cmd %d\n"),
 				p, privdata->dataSocket, privdata->cmdSocket);
 #endif
6489ba59
 #endif
 
 		if(connect(privdata->dataSocket, (struct sockaddr *)&reply, sizeof(struct sockaddr_in)) < 0) {
 			perror("connect");
 
3c98c93b
 			cli_dbgmsg("Failed to connect to port %d given by clamd",
 				p);
6489ba59
 			/* 0.4 - use better error message */
 			if(use_syslog) {
 #ifdef HAVE_STRERROR_R
 				strerror_r(errno, buf, sizeof(buf));
 				syslog(LOG_ERR,
4e136199
 					_("Failed to connect to port %d given by clamd: %s"),
8eb408e5
 					p, buf);
6489ba59
 #else
4e136199
 				syslog(LOG_ERR, _("Failed to connect to port %d given by clamd: %s"), p, strerror(errno));
6489ba59
 #endif
3471d92e
 			}
a3a024c5
 #ifdef	SESSION
3471d92e
 			pthread_mutex_lock(&sstatus_mutex);
ebb16454
 			session->status = CMDSOCKET_DOWN;
3471d92e
 			pthread_mutex_unlock(&sstatus_mutex);
a3a024c5
 #endif
6489ba59
 			return 0;
 		}
 	}
 
384942c2
 	if(!sendToFrom(privdata))
 		return 0;
 
 	cli_dbgmsg("connect2clamd: serverNumber = %d\n", privdata->serverNumber);
 
 	return 1;
 }
 
 /*
  * Combine the To and From into one clamfi_send to save bandwidth
  * when sending using TCP/IP to connect to a remote clamd, by band
  * width here I mean number of packets
  */
 static int
 sendToFrom(struct privdata *privdata)
 {
 	char **to;
 	char *msg;
 	int length;
 
0ac43d02
 	length = strlen(privdata->from) + 34;
6489ba59
 	for(to = privdata->to; *to; to++)
95ec27d0
 		length += strlen(*to) + 5;
0ac43d02
 
 	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);
6489ba59
 			return 0;
0ac43d02
 		}
 		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;
 	}
6489ba59
 
 	return 1;
 }
062dddc9
 
 /*
  * If possible, check if clamd has died, and report if it has
  */
 static void
 checkClamd(void)
 {
 	pid_t pid;
 	int fd, nbytes;
 	char buf[9];
 
 	if(!localSocket)
ea815cbc
 		return;	/* communicating via TCP */
062dddc9
 
 	if(pidFile == NULL)
c857005e
 		return;	/* PidFile directive missing from clamd.conf */
062dddc9
 
 	fd = open(pidFile, O_RDONLY);
 	if(fd < 0) {
 		perror(pidFile);
 		if(use_syslog)
4e136199
 			syslog(LOG_ERR, _("Can't open %s"), pidFile);
062dddc9
 		return;
 	}
 	nbytes = read(fd, buf, sizeof(buf) - 1);
b90c8762
 	if(nbytes < 0)
 		perror(pidFile);
 	else
 		buf[nbytes] = '\0';
062dddc9
 	close(fd);
 	pid = atoi(buf);
6e945c41
 	if((kill(pid, 0) < 0) && (errno == ESRCH)) {
 		if(use_syslog)
4e136199
 			syslog(LOG_ERR, _("Clamd (pid %d) seems to have died"),
6e945c41
 				pid);
 		perror("clamd");
062dddc9
 	}
 }
1392cf22
 
 /*
  * Send a templated message about an intercepted message. Very basic for
  * now, just to prove it works, will enhance the flexability later, only
6d113e37
  * supports %v and $sendmail_variables$ at present.
063b6c33
  *
  * TODO: more template features
  * TODO: allow filename to start with a '|' taken to mean the output of
  *	a program
1392cf22
  */
 static int
0b79c1b7
 sendtemplate(SMFICTX *ctx, const char *filename, FILE *sendmail, const char *virusname)
1392cf22
 {
 	FILE *fin = fopen(filename, "r");
 	struct stat statb;
6d113e37
 	char *buf, *ptr /* , *ptr2 */;
95ec27d0
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
1392cf22
 
 	if(fin == NULL) {
 		perror(filename);
 		if(use_syslog)
4e136199
 			syslog(LOG_ERR, _("Can't open e-mail template file %s"),
1392cf22
 				filename);
 		return -1;
 	}
 
 	if(fstat(fileno(fin), &statb) < 0) {
 		/* File disappeared in race condition? */
 		perror(filename);
 		if(use_syslog)
4e136199
 			syslog(LOG_ERR, _("Can't stat e-mail template file %s"),
1392cf22
 				filename);
 		fclose(fin);
 		return -1;
 	}
 	buf = cli_malloc(statb.st_size + 1);
 	if(buf == NULL) {
73ffa07e
 		fclose(fin);
1392cf22
 		if(use_syslog)
4e136199
 			syslog(LOG_ERR, _("Out of memory"));
1392cf22
 		return -1;
 	}
d18eac06
 	if(fread(buf, sizeof(char), statb.st_size, fin) != (size_t)statb.st_size) {
c840fa41
 		perror(filename);
 		if(use_syslog)
 			syslog(LOG_ERR, _("Error reading e-mail template file %s"),
 				filename);
 		fclose(fin);
 		return -1;
 	}
1392cf22
 	fclose(fin);
 	buf[statb.st_size] = '\0';
 
6d113e37
 	for(ptr = buf; *ptr; ptr++)
95ec27d0
 		switch(*ptr) {
 			case '%': /* clamAV variable */
 				switch(*++ptr) {
 					case 'v':	/* virus name */
 						fputs(virusname, sendmail);
 						break;
 					case '%':
 						putc('%', sendmail);
 						break;
 					case 'h':	/* headers */
 						if(privdata)
 							header_list_print(privdata->headers, sendmail);
 						break;
 					case '\0':
 						putc('%', sendmail);
 						--ptr;
 						continue;
 					default:
 						syslog(LOG_ERR,
4e136199
 							_("%s: Unknown clamAV variable \"%c\"\n"),
95ec27d0
 							filename, *ptr);
 						break;
 				}
74e42636
 				break;
95ec27d0
 			case '$': /* sendmail string */ {
 				const char *val;
 				char *end = strchr(++ptr, '$');
6d113e37
 
95ec27d0
 				if(end == NULL) {
6d113e37
 					syslog(LOG_ERR,
4e136199
 						_("%s: Unterminated sendmail variable \"%s\"\n"),
95ec27d0
 							filename, ptr);
 					continue;
 				}
 				*end = '\0';
 
 				val = smfi_getsymval(ctx, ptr);
 				if(val == NULL) {
 					fputs(ptr, sendmail);
 					if(use_syslog)
 						syslog(LOG_ERR,
4e136199
 							_("%s: Unknown sendmail variable \"%s\"\n"),
95ec27d0
 							filename, ptr);
 				} else
 					fputs(val, sendmail);
 				ptr = end;
74e42636
 				break;
95ec27d0
 			}
 			case '\\':
 				if(*++ptr == '\0') {
 					--ptr;
 					continue;
 				}
 				putc(*ptr, sendmail);
 				break;
 			default:
 				putc(*ptr, sendmail);
 		}
1392cf22
 
 	free(buf);
 
6d113e37
 	return 0;
1392cf22
 }
ced83bee
 
 /*
79fa8528
  * Keep the infected file in quarantine, return success (0) or failure
  */
 static int
dd63090a
 qfile(struct privdata *privdata, const char *sendmailId, const char *virusname)
79fa8528
 {
5a4966af
 	int MM, YY, DD;
 	time_t t;
d7e0b4e6
 	size_t len;
5a4966af
 	char *newname, *ptr;
 	const struct tm *tm;
79fa8528
 
 	assert(privdata != NULL);
 
 	if((privdata->filename == NULL) || (virusname == NULL))
 		return -1;
 
5a4966af
 	cli_dbgmsg("qfile filename '%s' sendmailId '%s' virusname '%s'\n", privdata->filename, sendmailId, virusname);
d7e0b4e6
 
5a4966af
 	len = strlen(quarantine_dir);
 
 	newname = cli_malloc(len + strlen(sendmailId) + strlen(virusname) + 10);
79fa8528
 
 	if(newname == NULL)
 		return -1;
 
5a4966af
 	t = time((time_t *)0);
 	tm = localtime(&t);
 	MM = tm->tm_mon + 1;
 	YY = tm->tm_year - 100;
 	DD = tm->tm_mday;
 
d52ee4e0
 	sprintf(newname, "%s/%02d%02d%02d", quarantine_dir, YY, MM, DD);
9c8806fb
 #ifdef	C_AIX
 	if((mkdir(newname, 0700) < 0) && (errno != EEXIST) && (errno > 0) &&
 	    (errno != ENOENT)) {
 #else
d52ee4e0
 	if((mkdir(newname, 0700) < 0) && (errno != EEXIST)) {
9c8806fb
 #endif
d52ee4e0
 		perror(newname);
 		if(use_syslog)
 			syslog(LOG_ERR, _("mkdir %s failed"), newname);
 		return -1;
 	}
5a4966af
 	sprintf(newname, "%s/%02d%02d%02d/%s.%s",
 		quarantine_dir, YY, MM, DD, sendmailId, virusname);
0ac43d02
 
 	/*
 	 * Strip out funnies that may be in the name of the virus, such as '/'
 	 * that would cause the quarantine to fail to save since the name
 	 * of the virus is included in the filename
 	 */
5a4966af
 	for(ptr = &newname[len + 8]; *ptr; ptr++) {
4e136199
 #ifdef	C_DARWIN
 		*ptr &= '\177';
 #endif
83d6495b
 #if	defined(MSDOS) || defined(C_CYGWIN) || defined(WIN32) || defined(C_OS2)
 		if(strchr("/*?<>|\\\"+=,;:\t ", *ptr))
0ac43d02
 #else
 		if(*ptr == '/')
 #endif
 			*ptr = '_';
 	}
5a4966af
 	cli_dbgmsg("qfile move '%s' to '%s'\n", privdata->filename, newname);
 
54ba1fe6
 	if(move(privdata->filename, newname) < 0) {
d52ee4e0
 		if(use_syslog)
 			syslog(LOG_WARNING, _("Can't rename %1$s to %2$s"),
 				privdata->filename, newname);
 		free(newname);
 		return -1;
 	}
79fa8528
 	free(privdata->filename);
 	privdata->filename = newname;
 
5a4966af
 	if(use_syslog)
c840fa41
 		syslog(LOG_INFO, _("Email quarantined as %s"), newname);
5a4966af
 
79fa8528
 	return 0;
 }
 
 /*
54ba1fe6
  * Move oldfile to newfile using the fastest possible method
  */
 static int
 move(const char *oldfile, const char *newfile)
 {
 	int ret;
 #ifdef	C_LINUX
 	struct stat statb;
e82a5185
 	int fin, fout, c;
54ba1fe6
 	off_t offset;
e82a5185
 	FILE *fsin, *fsout;
54ba1fe6
 #else
 	FILE *fin, *fout;
 	int c;
a87be00b
 #endif
54ba1fe6
 
a87be00b
 	ret = rename(oldfile, newfile);
baff8875
 	if(ret >= 0)
a87be00b
 		return 0;
54ba1fe6
 
 	if((ret < 0) && (errno != EXDEV)) {
 		perror(newfile);
 		return -1;
 	}
 
 #ifdef	C_LINUX	/* >= 2.2 */
 	fin = open(oldfile, O_RDONLY);
 	if(fin < 0) {
 		perror(oldfile);
 		return -1;
 	}
 
 	if(fstat(fin, &statb) < 0) {
 		perror(oldfile);
 		close(fin);
 		return -1;
 	}
 	fout = open(newfile, O_WRONLY|O_CREAT, 0600);
 	if(fout < 0) {
 		perror(newfile);
 		close(fin);
 		return -1;
 	}
 	offset = (off_t)0;
 	ret = sendfile(fout, fin, &offset, statb.st_size);
 	close(fin);
 	if(ret < 0) {
e82a5185
 		/* fall back if sendfile fails, which shouldn't happen */
54ba1fe6
 		perror(newfile);
 		close(fout);
 		unlink(newfile);
e82a5185
 
 		fsin = fopen(oldfile, "r");
 		if(fsin == NULL)
 			return -1;
 
 		fsout = fopen(newfile, "w");
 		if(fsout == NULL) {
 			fclose(fsin);
 			return -1;
 		}
 		while((c = getc(fsin)) != EOF)
 			putc(c, fsout);
 
 		fclose(fsin);
 		fclose(fsout);
 	} else
 		close(fout);
54ba1fe6
 #else
 	fin = fopen(oldfile, "r");
 	if(fin == NULL)
 		return -1;
 
 	fout = fopen(newfile, "w");
 	if(fout == NULL) {
 		fclose(fin);
 		return -1;
 	}
 	while((c = getc(fin)) != EOF)
 		putc(c, fout);
 
 	fclose(fin);
 	fclose(fout);
 #endif
 
ad5eb067
 	cli_dbgmsg("removing %s\n", oldfile);
 
54ba1fe6
 	return unlink(oldfile);
 }
 
 /*
ced83bee
  * Store the name of the virus in the subject of the e-mail
  */
 static void
 setsubject(SMFICTX *ctx, const char *virusname)
 {
3d1b1f07
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
ced83bee
 	char subject[128];
 
3d1b1f07
 	if(privdata->subject)
 		smfi_addheader(ctx, "X-Original-Subject", privdata->subject);
 
4e136199
 	snprintf(subject, sizeof(subject) - 1, _("[Virus] %s"), virusname);
3d1b1f07
 	if(privdata->subject)
 		smfi_chgheader(ctx, "Subject", 1, subject);
 	else
 		smfi_addheader(ctx, "Subject", subject);
ced83bee
 }
6d113e37
 
 /*
  * TODO: gethostbyname_r is non-standard so different operating
  * systems do it in different ways. Need more examples
54ba1fe6
  * Perhaps we could use res_search()?
6d113e37
  *
  * Returns 0 for success
  */
 static int
 clamfi_gethostbyname(const char *hostname, struct hostent *hp, char *buf, size_t len)
 {
 #if	defined(HAVE_GETHOSTBYNAME_R_6)
 	/* e.g. Linux */
 	struct hostent *hp2;
c78ec4f3
 	int ret = -1;
6d113e37
 
 	if((hostname == NULL) || (hp == NULL))
 		return -1;
 	if(gethostbyname_r(hostname, hp, buf, len, &hp2, &ret) < 0)
c78ec4f3
 		return ret;
6d113e37
 #elif	defined(HAVE_GETHOSTBYNAME_R_5)
 	/* e.g. BSD, Solaris, Cygwin */
c78ec4f3
 	int ret = -1;
6d113e37
 
 	if((hostname == NULL) || (hp == NULL))
 		return -1;
 	if(gethostbyname_r(hostname, hp, buf, len, &ret) == NULL)
c78ec4f3
 		return ret;
6d113e37
 #elif	defined(HAVE_GETHOSTBYNAME_R_3)
 	/* e.g. HP/UX, AIX */
 	if((hostname == NULL) || (hp == NULL))
 		return -1;
 	if(gethostbyname_r(hostname, &hp, (struct hostent_data *)buf) < 0)
c78ec4f3
 		return h_errno;
6d113e37
 #else
 	/* Single thread the code */
 	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);
c78ec4f3
 		return h_errno;
6d113e37
 	}
 	memcpy(hp, hp2, sizeof(struct hostent));
 	pthread_mutex_unlock(&hostent_mutex);
 #endif
 
 	return 0;
 }
503b2956
 
 /*
  * David Champion <dgc@uchicago.edu>
  *
  * Check whether addr is on network by applying netmasks.
  * addr must be a 32-bit integer-packed IPv4 address in network order.
  * For example:
d7e0b4e6
  *	struct in_addr IPAddress;
  *	isLocal = isLocalAddr(IPAddress.s_addr);
503b2956
  */
 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;	/* is non-local */
 }
85563fe5
 
 /*
  * Can't connect to any clamd server. This is serious, we need to inform
  * someone. In the absence of SNMP the best way is by e-mail. We
  * don't want to flood so there's a need to restrict to
  * no more than say one message every 15 minutes
  */
 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;
 
f74d7f94
 		snprintf(cmd, sizeof(cmd) - 1, "%s -t -i", SENDMAIL_BIN);
85563fe5
 
 		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
 /*
  * Thread to monitor the links to clamd sessions. Any marked as being in
  * an error state because of previous I/O errors are restarted, and a heartbeat
  * is sent the others
09e761fb
  *
  * It is woken up when the milter goes idle, when there are no free servers
  * available and once every readTimeout-1 seconds
677565d4
  *
a87be00b
  * TODO: reload the whiteList file if it's been changed
  *
677565d4
  * TODO: localSocket support
85563fe5
  */
 static void *
 watchdog(void *a)
 {
 	static pthread_mutex_t watchdog_mutex = PTHREAD_MUTEX_INITIALIZER;
 
7ca2a6cf
 	assert((!external) || (sessions != NULL));
41190088
 
78a78fcc
 	while(!quitting) {
9c8806fb
 		unsigned int i;
85563fe5
 		struct timespec ts;
 		struct timeval tp;
ebb16454
 		struct session *session;
85563fe5
 
 		gettimeofday(&tp, NULL);
09e761fb
 
41190088
 		ts.tv_sec = tp.tv_sec + readTimeout - 1;
85563fe5
 		ts.tv_nsec = tp.tv_usec * 1000;
 		cli_dbgmsg("watchdog sleeps\n");
41190088
 		pthread_mutex_lock(&watchdog_mutex);
 		/*
 		 * Sometimes this returns EPIPE which isn't listed as a
 		 * return value in the Linux man page for pthread_cond_timedwait
 		 * so I'm not sure why it happens
 		 */
 		switch(pthread_cond_timedwait(&watchdog_cond, &watchdog_mutex, &ts)) {
 			case ETIMEDOUT:
 			case 0:
 				break;
 			default:
 				perror("pthread_cond_timedwait");
 		}
85563fe5
 		cli_dbgmsg("watchdog wakes\n");
41190088
 		pthread_mutex_unlock(&watchdog_mutex);
85563fe5
 
7ca2a6cf
 		if(!external) {
ccf2bd40
 			/*
1ae303c2
 			 * Re-load the database if needed
ccf2bd40
 			 */
1ae303c2
 			switch(cl_statchkdir(&dbstat)) {
 				case 1:
 					cli_dbgmsg("Database has changed\n");
 					cl_statfree(&dbstat);
 					if(use_syslog)
 						syslog(LOG_WARNING, _("Loading new database"));
 					if(loadDatabase() != 0) {
 						smfi_stop();
 						cli_errmsg("Failed to load updated database\n");
 						return NULL;
 					}
 					break;
 				case 0:
 					cli_dbgmsg("Database has not changed\n");
 					break;
 				default:
48e8856b
 					smfi_stop();
1ae303c2
 					cli_errmsg("Database error - %s is stopping\n", progname);
48e8856b
 					return NULL;
cf6c41b7
 			}
ccf2bd40
 			continue;
 		}
ebb16454
 		i = 0;
 		session = sessions;
85563fe5
 		pthread_mutex_lock(&sstatus_mutex);
ebb16454
 		for(; i < max_children; i++, session++) {
 			const int sock = session->sock;
85563fe5
 
 			/*
 			 * Check all free sessions are still usable
 			 * This could take some time with many free
 			 * sessions to slow remote servers, so only do this
 			 * when the system is quiet (not 100% accurate when
 			 * determining this since n_children isn't locked but
 			 * that doesn't really matter)
 			 */
 			cli_dbgmsg("watchdog: check server %d\n", i);
b1dc4b1b
 			if((n_children == 0) &&
 			   (session->status == CMDSOCKET_FREE) &&
 			   (clamav_versions != NULL)) {
09e761fb
 				if(send(sock, "VERSION\n", 8, 0) == 8) {
 					char buf[81];
 					const int nbytes = clamd_recv(sock, buf, sizeof(buf) - 1);
85563fe5
 
09e761fb
 					if(nbytes <= 0)
ebb16454
 						session->status = CMDSOCKET_DOWN;
09e761fb
 					else {
 						buf[nbytes] = '\0';
 						if(strncmp(buf, "ClamAV ", 7) == 0) {
 							/* Remove the trailing new line from the reply */
 							char *ptr;
dd63090a
 
09e761fb
 							if((ptr = strchr(buf, '\n')) != NULL)
 								*ptr = '\0';
c78ec4f3
 							pthread_mutex_lock(&version_mutex);
dd63090a
 							if(clamav_versions[i] == NULL)
 								clamav_versions[i] = strdup(buf);
 							else if(strcmp(buf, clamav_versions[i]) != 0) {
09e761fb
 								if(use_syslog)
 									syslog(LOG_INFO, "New version received for server %d: '%s'\n", i, buf);
 								free(clamav_versions[i]);
 								clamav_versions[i] = strdup(buf);
 							}
c78ec4f3
 							pthread_mutex_unlock(&version_mutex);
09e761fb
 						} else {
 							cli_warnmsg("watchdog: expected \"ClamAV\", got \"%s\"\n", buf);
ebb16454
 							session->status = CMDSOCKET_DOWN;
09e761fb
 						}
41190088
 					}
 				} else {
 					perror("send");
ebb16454
 					session->status = CMDSOCKET_DOWN;
41190088
 				}
85563fe5
 
ebb16454
 				if(session->status == CMDSOCKET_DOWN)
85563fe5
 					cli_warnmsg("Session %d has gone down\n", i);
 			}
 			/*
 			 * Reset all all dead sessions
 			 */
ebb16454
 			if(session->status == CMDSOCKET_DOWN) {
85563fe5
 				/*
 				 * The END command probably won't get through,
 				 * but let's give it a go anyway
 				 */
3471d92e
 				if(sock >= 0) {
 					send(sock, "END\n", 4, 0);
 					close(sock);
 				}
85563fe5
 
 				cli_dbgmsg("Trying to restart session %d\n", i);
 				if(createSession(i) == 0) {
ebb16454
 					session->status = CMDSOCKET_FREE;
85563fe5
 					cli_warnmsg("Session %d restarted OK\n", i);
 				}
 			}
 		}
 		for(i = 0; i < max_children; i++)
ebb16454
 			if(sessions[i].status != CMDSOCKET_DOWN)
85563fe5
 				break;
 
 		if(i == max_children)
 			clamdIsDown();
 		pthread_mutex_unlock(&sstatus_mutex);
 	}
5a4966af
 	cli_dbgmsg("watchdog quits\n");
85563fe5
 	return NULL;
 }
83d6495b
 #else	/*!SESSION*/
 /*
  * Reload the database from time to time, when using the internal scanner
a87be00b
  *
  * TODO: reload the whiteList file if it's been changed
83d6495b
  */
c6a870ad
 /*ARGSUSED*/
83d6495b
 static void *
 watchdog(void *a)
 {
 	static pthread_mutex_t watchdog_mutex = PTHREAD_MUTEX_INITIALIZER;
 
 	if(external)
 		return NULL;
 
 	while(!quitting) {
 		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);
 		/*
 		 * Sometimes this returns EPIPE which isn't listed as a
 		 * return value in the Linux man page for pthread_cond_timedwait
 		 * so I'm not sure why it happens
 		 */
 		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);
 
 		/*
1ae303c2
 		 * Re-load the database.
c840fa41
  		 * TODO: sanity check that if n_children == 0, that
  		 * root->refcount == 0. Unfortunatly root->refcount isn't
  		 * thread-safe, since it's governed by a mutex that we can't
  		 * see, and there's no access to it via an approved method
  		 */
1ae303c2
 		switch(cl_statchkdir(&dbstat)) {
 			case 1:
 				cli_dbgmsg("Database has changed\n");
 				cl_statfree(&dbstat);
 				if(use_syslog)
 					syslog(LOG_WARNING, _("Loading new database"));
 				if(loadDatabase() != 0) {
 					smfi_stop();
 					cli_errmsg("Failed to load updated database\n");
 					return NULL;
 				}
 				break;
 			case 0:
 				cli_dbgmsg("Database has not changed\n");
 				break;
 			default:
48e8856b
 				smfi_stop();
1ae303c2
 				cli_errmsg("Database error - %s is stopping\n", progname);
48e8856b
 				return NULL;
83d6495b
 		}
 	}
 	cli_dbgmsg("watchdog quits\n");
 	return NULL;
 }
85563fe5
 #endif
d7e0b4e6
 
 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;
 }
78a78fcc
 
 static void
 quit(void)
 {
ae17ae8e
 	extern short cli_leavetemps_flag;
 
78a78fcc
 	quitting++;
 
83ae9a24
 #ifdef	SESSION
c78ec4f3
 	pthread_mutex_lock(&version_mutex);
83ae9a24
 #endif
78a78fcc
 	if(use_syslog)
3eacc10d
 		syslog(LOG_INFO, _("Stopping %s"), clamav_version);
83ae9a24
 #ifdef	SESSION
c78ec4f3
 	pthread_mutex_unlock(&version_mutex);
83ae9a24
 #endif
78a78fcc
 
7ca2a6cf
 	if(!external) {
1ae303c2
 		pthread_mutex_lock(&root_mutex);
ccf2bd40
 		if(root) {
 			cl_free(root);
 			root = NULL;
78a78fcc
 		}
1ae303c2
 		pthread_mutex_unlock(&root_mutex);
ccf2bd40
 	} else {
 #ifdef	SESSION
 		int i = 0;
 		struct session *session = sessions;
78a78fcc
 
ccf2bd40
 		pthread_mutex_lock(&sstatus_mutex);
9c8806fb
 		for(; i < ((localSocket != NULL) ? 1 : (int)max_children); i++) {
ccf2bd40
 			/*
 			 * Check all free sessions are still usable
 			 * This could take some time with many free
 			 * sessions to slow remote servers, so only do this
 			 * when the system is quiet (not 100% accurate when
 			 * determining this since n_children isn't locked but
 			 * that doesn't really matter)
 			 */
 			cli_dbgmsg("quit: close server %d\n", i);
 			if(session->status == CMDSOCKET_FREE) {
 				const int sock = session->sock;
 
 				send(sock, "END\n", 4, 0);
 				shutdown(sock, SHUT_WR);
 				session->status = CMDSOCKET_DOWN;
 				pthread_mutex_unlock(&sstatus_mutex);
 				close(sock);
 				pthread_mutex_lock(&sstatus_mutex);
 			}
a87be00b
 			session++;
ccf2bd40
 		}
 		pthread_mutex_unlock(&sstatus_mutex);
78a78fcc
 #endif
ccf2bd40
 	}
ae17ae8e
 
 	if(tmpdir && !cli_leavetemps_flag)
a87be00b
 		if(rmdir(tmpdir) < 0)
 			perror(tmpdir);
ae17ae8e
 
384942c2
 	broadcast(_("Stopping clamav-milter"));
 
9f47e7ab
 	if(pidfile)
 		if(unlink(pidfile) < 0)
 			perror(pidfile);
 
384942c2
 	if(use_syslog)
 		closelog();
78a78fcc
 }
 
 static void
 broadcast(const char *mess)
 {
 	struct sockaddr_in s;
 
3eacc10d
 	if(broadcastSock < 0)
 		return;
 
78a78fcc
 	memset(&s, '\0', sizeof(struct sockaddr_in));
 	s.sin_family = AF_INET;
5aeb0d51
 	s.sin_port = (in_port_t)htons(tcpSocket ? tcpSocket : 3310);
78a78fcc
 	s.sin_addr.s_addr = htonl(INADDR_BROADCAST);
 
3c98c93b
 	cli_dbgmsg("broadcast %s to %d\n", mess, broadcastSock);
e5bb3b37
 	if(sendto(broadcastSock, mess, strlen(mess), 0, (struct sockaddr *)&s, sizeof(struct sockaddr_in)) < 0)
78a78fcc
 		perror("sendto");
 }
cf6c41b7
 
 /*
1ae303c2
  * Load a new database into the internal scanner
cf6c41b7
  */
 static int
 loadDatabase(void)
 {
 	extern const char *cl_retdbdir(void);	/* FIXME: should be included */
54ba1fe6
 	int ret, v;
 	unsigned int signatures;
cf6c41b7
 	time_t t;
 	char *daily, *ptr;
 	struct cl_cvd *d;
 	const struct cfgstruct *cpt;
1ae303c2
 	struct cl_node *newroot, *oldroot;
cf6c41b7
 	static const char *dbdir;
 
7ca2a6cf
 	assert(!external);
cf6c41b7
 
b1dc4b1b
 	if(dbdir == NULL) {
1ae303c2
 		/*
 		 * First time through, find out in which directory the signature
 		 * databases are
 		 */
cf6c41b7
 		if((cpt = cfgopt(copt, "DatabaseDirectory")) || (cpt = cfgopt(copt, "DataDirectory")))
 			dbdir = cpt->strarg;
 		else
 			dbdir = cl_retdbdir();
 	}
 
 	daily = cli_malloc(strlen(dbdir) + 11);
 	sprintf(daily, "%s/daily.cvd", dbdir);
 
9f47e7ab
 	cli_dbgmsg("loadDatabase: check %s for updates\n", daily);
 
cf6c41b7
 	d = cl_cvdhead(daily);
 
 	if(d == NULL) {
 		cli_errmsg("Can't find %s\n", daily);
 		free(daily);
 		return -1;
 	}
 
 	t = d->stime;
 	v = d->version;
 
 	cl_cvdfree(d);
 	free(daily);
 
 #ifdef	SESSION
c78ec4f3
 	pthread_mutex_lock(&version_mutex);
cf6c41b7
 	if(clamav_versions == NULL) {
 		clamav_versions = (char **)cli_malloc(sizeof(char *));
c78ec4f3
 		if(clamav_versions == NULL) {
 			pthread_mutex_unlock(&version_mutex);
cf6c41b7
 			return -1;
c78ec4f3
 		}
cf6c41b7
 		clamav_version = cli_malloc(VERSION_LENGTH + 1);
 		if(clamav_version == NULL) {
 			free(clamav_versions);
 			clamav_versions = NULL;
c78ec4f3
 			pthread_mutex_unlock(&version_mutex);
cf6c41b7
 			return -1;
 		}
 	}
c78ec4f3
 	pthread_mutex_unlock(&version_mutex);
cf6c41b7
 #endif
 	snprintf(clamav_version, VERSION_LENGTH,
 		"ClamAV %s/%d/%s", VERSION, v, ctime(&t));
 	/* Remove ctime's trailing \n */
 	if((ptr = strchr(clamav_version, '\n')) != NULL)
 		*ptr = '\0';
 
 	signatures = 0;
1ae303c2
 	newroot = NULL;
 	ret = cl_loaddbdir(dbdir, &newroot, &signatures);
cf6c41b7
 	if(ret != 0) {
 		cli_errmsg("%s\n", cl_strerror(ret));
 		return -1;
 	}
1ae303c2
 	if(newroot == NULL) {
cf6c41b7
 		cli_errmsg("Can't initialize the virus database.\n");
 		return -1;
 	}
 
1ae303c2
 	ret = cl_build(newroot);
cf6c41b7
 	if(ret != 0) {
 		cli_errmsg("Database initialization error: %s\n", cl_strerror(ret));
1ae303c2
 		cl_free(newroot);
cf6c41b7
 		return -1;
 	}
1ae303c2
 	pthread_mutex_lock(&root_mutex);
 	oldroot = root;
 	root = newroot;
 	pthread_mutex_unlock(&root_mutex);
cf6c41b7
 
1ae303c2
 	if(use_syslog) {
83ae9a24
 #ifdef	SESSION
c78ec4f3
 		pthread_mutex_lock(&version_mutex);
83ae9a24
 #endif
3c98c93b
 		syslog(LOG_INFO, _("Loaded %s"), clamav_version);
83ae9a24
 #ifdef	SESSION
c78ec4f3
 		pthread_mutex_unlock(&version_mutex);
83ae9a24
 #endif
1ae303c2
 		syslog(LOG_INFO, _("ClamAV: Protecting against %u viruses"), signatures);
c78ec4f3
 	}
1ae303c2
 	if(oldroot) {
c840fa41
 		char mess[128];
 
1ae303c2
 		cl_free(oldroot);
d18eac06
 		sprintf(mess, "Database correctly reloaded (%u signatures)\n", signatures);
c840fa41
 		logger(mess);
 		cli_dbgmsg("Database updated\n");
1ae303c2
 	} else
 		cli_dbgmsg("Database loaded\n");
cf6c41b7
 
 	return cl_statinidir(dbdir, &dbstat);
 }
3c98c93b
 
 static void
 sigsegv(int sig)
 {
 	signal(SIGSEGV, SIG_DFL);
e483f51a
 
 #ifdef HAVE_BACKTRACE
0a7e8089
 	print_trace();
e483f51a
 #endif
 
3c98c93b
 	if(use_syslog)
1ae303c2
 		syslog(LOG_CRIT, "Segmentation fault :-( Bye..");
e483f51a
 	cli_errmsg("Segmentation fault :-( Bye..\n");
3c98c93b
 
48e8856b
 	smfi_stop();
3c98c93b
 }
 
e483f51a
 #ifdef HAVE_BACKTRACE
3c98c93b
 static void
0a7e8089
 print_trace(void)
3c98c93b
 {
 	void *array[BACKTRACE_SIZE];
 	size_t size, i;
 	char **strings;
 	pid_t pid = getpid();
 
 	size = backtrace(array, BACKTRACE_SIZE);
 	strings = backtrace_symbols(array, size);
 
 	cli_dbgmsg("Backtrace of pid %d:\n", pid);
 	if(use_syslog)
 		syslog(LOG_ERR, "Backtrace of pid %d:", pid);
 
 	for(i = 0; i < size; i++) {
 		if(use_syslog)
 			syslog(LOG_ERR, "bt[%u]: %s", i, strings[i]);
 		cli_dbgmsg("%s\n", strings[i]);
 	}
 
 	/* TODO: dump the current email */
 
 	free(strings);
 }
 #endif
21e6b430
 
 /*
  * Check that the correct port name has been given, i.e. that the
  * input socket to clamav-milter from sendmail, is the same that
  * sendmail has been configured to use as it's output socket
  * Return:	<0 invalid
  *		=0 valid
  *		>0 unknown
  */
 static int
 verifyIncomingSocketName(const char *sockName)
 {
 #if HAVE_MMAP
 	int fd, ret;
 	char *ptr;
 	size_t size;
 	struct stat statb;
 
9a5dd76f
 	if(strncmp(sockName, "inet:", 5) == 0)
 		/*
 		 * clamav-milter is running on a different machine from sendmail
 		 */
 		return 1;
 
a87be00b
 	if(sendmailCF)
 		fd = open(sendmailCF, O_RDONLY);
 	else {
 		fd = open("/etc/mail/sendmail.cf", O_RDONLY);
 		if(fd < 0)
 			fd = open("/etc/sendmail.cf", O_RDONLY);
 	}
21e6b430
 
 	if(fd < 0)
 		return 1;
 
 	if(fstat(fd, &statb) < 0) {
 		close(fd);
 		return 1;
 	}
 
 	size = statb.st_size;
 
 	if(size == 0) {
 		close(fd);
 		return -1;
 	}
 
 	ptr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
 	if(ptr == MAP_FAILED) {
 		perror("mmap");
 		close(fd);
 		return -1;
 	}
 
 	ret = (cli_memstr(ptr, size, sockName, strlen(sockName)) != NULL) ? 1 : -1;
 
 	munmap(ptr, size);
 	close(fd);
 
 	return ret;
 #else	/*!HAVE_MMAP*/
 	return 1;
 #endif
 }
5aeb0d51
 
 /*
c840fa41
  * If the given email address is whitelisted don't scan emails to them
a87be00b
  *
  * TODO: Allow regular expressions in the emails
  * TODO: Syntax check the contents of the files
9f47e7ab
  * TODO: Allow emails of the form "name <address>"
5aeb0d51
  */
 static int
 isWhitelisted(const char *emailaddress)
 {
a87be00b
 	static table_t *whitelist;
5aeb0d51
 
1ae303c2
 
 	cli_dbgmsg("isWhitelisted %s\n", emailaddress);   
         /*	
 	 * Don't scan messages to the quarantine email address 
 	 */
         if(quarantine && (strcasecmp(quarantine, emailaddress) == 0))
 		return 1;
 
a87be00b
 	if((whitelist == NULL) && whitelistFile) {
 		FILE *fin;
 		char buf[BUFSIZ + 1];
 
 		fin = fopen(whitelistFile, "r");
 
 		if(fin == NULL) {
 			perror(whitelistFile);
1ae303c2
 			if(use_syslog)  
 			    syslog(LOG_ERR, _("Can't open white-list file %s"),
 				    whitelistFile);
a87be00b
 			return 0;
 		}
 		whitelist = tableCreate();
 
 		while(fgets(buf, sizeof(buf), fin) != NULL) {
 			/* comment line? */
 			switch(buf[0]) {
 				case '#':
 				case '/':
 				case ':':
 					continue;
 			}
9f47e7ab
 			if(cli_chomp(buf) > 0)
 				(void)tableInsert(whitelist, buf, 1);
a87be00b
 		}
 		fclose(fin);
 	}
 	if(whitelist && (tableFind(whitelist, emailaddress) == 1))
 		/*
 		 * This recipient is on the whitelist
 		 */
 		return 1;
5aeb0d51
 
 	/*
 	 * Don't scan messages to the quarantine email address
 	 */
 	if(quarantine && (strcasecmp(quarantine, emailaddress) == 0))
 		return 1;
 
 	return 0;
 }
9c8806fb
 
 static void
 logger(const char *mess)
 {
 #ifdef	CL_DEBUG
 	puts(mess);
 #else
1ae303c2
 	FILE *fout;
 	
 	if(cfgopt(copt, "Foreground"))
 		fout = stderr;
 	else
 		fout = fopen(logFile, "a");
9c8806fb
 
 	if(fout == NULL)
 		return;
 
1ae303c2
 	if(logg_time) {
9c8806fb
 #ifdef HAVE_CTIME_R
 		time_t currtime = time((time_t)0);
 		char buf[27];
 
 #ifdef HAVE_CTIME_R_3
 		ctime_r(&currtime, buf, sizeof(buf));
 #else
 		ctime_r(&currtime, buf);
 #endif
e483f51a
 		fprintf(fout, "%.*s -> %s\n", (int)strlen(buf) - 1, buf, mess);
9c8806fb
 #else	/*!HAVE_CTIME_R*/
 		/* TODO */
 		fprintf(fout, "%s\n", mess);
 #endif
 	} else
 		fprintf(fout, "%s\n", mess);
1ae303c2
 	if(fout != stderr)
 		fclose(fout);
9c8806fb
 #endif
 }