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.
  *
  * Install into /usr/local/sbin/clamav-milter, mode 744
  *
  * See http://www.nmt.edu/~wcolburn/sendmail-8.12.5/libmilter/docs/sample.html
  *
  * Installations for RedHat Linux and it's derivatives such as YellowDog:
c6259ac5
  * 1) Ensure that you have the sendmail-devel RPM installed
  * 2) Add to /etc/mail/sendmail.mc:
b151ef55
  *	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav.sock, F=, T=S:4m;R:4m')dnl
  *	define(`confINPUT_MAIL_FILTERS', `clamav')
c6259ac5
  * 3) Check entry in /usr/local/etc/clamav.conf of the form:
b151ef55
  *	LocalSocket /var/run/clamd.sock
  *	StreamSaveToDisk
c6259ac5
  * 4) If you already have a filter (such as spamassassin-milter from
b151ef55
  * http://savannah.nongnu.org/projects/spamass-milt) add it thus:
  *	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav.sock, F=, T=S:4m;R:4m')dnl
  *	INPUT_MAIL_FILTER(`spamassassin', `S=local:/var/run/spamass.sock, F=, T=C:15m;S:4m;R:4m;E:10m')
  *	define(`confINPUT_MAIL_FILTERS', `spamassassin,clamav')dnl
c6259ac5
  * 5) You may find INPUT_MAIL_FILTERS is not needed on your machine, however it
  * is recommended by the Sendmail documentation and I suggest going along
b151ef55
  * with that.
c6259ac5
  * 6) I suggest putting SpamAssassin first since you're more likely to get spam
b151ef55
  * than a virus/worm sent to you.
c6259ac5
  * 7) Add to /etc/sysconfig/clamav-milter
b151ef55
  *	CLAMAV_FLAGS="--max-children=2 local:/var/run/clamav.sock"
  * or if clamd is on a different machine
  *	CLAMAV_FLAGS="--max-children=2 --server=192.168.1.9 local:/var/run/clamav.sock"
c6259ac5
  * 8) You should have received a script to put into /etc/init.d with this
  * software.
b151ef55
  *
  * Tested OK on Linux/x86 (RH8.0) with gcc3.2.
  *	cc -O3 -pedantic -Wuninitialized -Wall -pipe -mcpu=pentium -march=pentium -fomit-frame-pointer -ffast-math -finline-functions -funroll-loops clamav-milter.c -pthread -lmilter ../libclamav/.libs/libclamav.a ../clamd/cfgfile.o ../clamd/others.o
  * Compiles OK on Linux/x86 with tcc 0.9.16, but fails to link errors with 'atexit'
  *	tcc -g -b -lmilter -lpthread clamav-milter.c...
  * Fails to compile on Linux/x86 with icc6.0 (complains about stdio.h...)
  *	icc -O3 -tpp7 -xiMKW -ipo -parallel -i_dynamic -w2 clamav-milter.c...
  * Fails to build on Linux/x86 with icc7.1 with -ipo (fails on libclamav.a - keeps saying run ranlib). Otherwise it builds and runs OK.
  *	icc -O2 -tpp7 -xiMKW -parallel -i_dynamic -w2 -march=pentium4 -mcpu=pentium4 clamav-milter.c...
  * Tested with Electric Fence 2.2.2
  *
  * Compiles OK on Linux/ppc (YDL2.3) with gcc2.95.4. Needs -lsmutil to link.
  *	cc -O3 -pedantic -Wuninitialized -Wall -pipe -fomit-frame-pointer -ffast-math -finline-functions -funroll-loop -pthread -lmilter ../libclamav/.libs/libclamav.a ../clamd/cfgfile.o ../clamd/others.o -lsmutil
  * I haven't tested it further on this platform yet.
c6259ac5
  * YDL3.0 should compile out of the box
 	cc -O3 -pedantic -Wuninitialized -Wall -pipe -fomit-frame-pointer -ffast-math -finline-functions -funroll-loop -pthread -lmilter ../libclamav/.libs/libclamav.a ../clamd/cfgfile.o ../clamd/others.o -lsmutil
b151ef55
  *
  * Sendmail on MacOS/X (10.1) is provided without a development package so this
  * can't be run "out of the box"
  *
c6259ac5
  * Solaris 8 doesn't have milter support so clamav-milter won't work unless
  * you rebuild sendmail from source.
  * Solaris 9 has milter support in the supplied sendmail, but doesn't include
  * libmilter so you can't develop milter applications on it. Go to sendmail.org,
  * download the lastest sendmail, cd to libmilter and "make install" there.
  * Needs -lresolv
b151ef55
  *
  * FreeBSD4.7 use /usr/local/bin/gcc30. GCC3.0 is an optional extra on
  * FreeBSD. It comes with getopt.h which is handy. To link you need
  * -lgnugetopt
  *	gcc30 -O3 -DCONFDIR=\"/usr/local/etc\" -I. -I.. -I../clamd -I../libclamav -pedantic -Wuninitialized -Wall -pipe -mcpu=pentium -march=pentium -fomit-frame-pointer -ffast-math -finline-functions -funroll-loops clamav-milter.c -pthread -lmilter ../libclamav/.libs/libclamav.a ../clamd/cfgfile.o ../clamd/others.o -lgnugetopt
  *
c6259ac5
  * FreeBSD4.8: should compile out of the box
  * OpenBSD3.3: the supplied sendmail does not come with Milter support. You
  * will need to rebuild sendmail from source
  *
b151ef55
  * Changes
  *	0.2:	4/3/03	clamfi_abort() now always calls pthread_mutex_unlock
  *		5/3/03	Only send a bounce if -b is set
  *			Version now uses -v not -V
  *			--config-file couldn't be set by -c
  *	0.3	7/3/03	Enhanced the Solaris compile time comment
  *			No need to save the return result of LogSyslog
  *			Use LogVerbose
  *	0.4	9/3/03	Initialise dataSocket/cmdSocket correctly
  *		10/3/03	Say why we don't connect() to clamd
  *			Enhanced '-l' usage message
  *	0.5	18/3/03	Ported to FreeBSD 4.7
  *			Source no longer in support, so remove one .. from
  *			the build instructions
  *			Corrected the use of strerror_r
  *	0.51	20/3/03	Mention StreamSaveToDisk in the installation
  *			Added -s option which allows clamd to run on a
  *			different machine from the milter
  *	0.52	20/3/03	-b flag now only stops the bounce, sends warning
  *			to recipient and postmaster
  *	0.53	24/3/03	%d->%u in syslog call
  *		27/3/03	tcpSocket is now of type in_port_t
  *		27/3/03	Use PING/PONG
  *	0.54	23/5/03	Allow a range of IP addresses as outgoing ones
  *			that need not be checked
  *	0.55	24/5/03	Use inet_ntop() instead of inet_ntoa()
  *			Thanks to Krzysztof Olędzki <ole@ans.pl>
  *	0.60	11/7/03	Added suggestions by Nigel Kukard <nkukard@lbsd.net>
  *			Should stop a couple of remote chances of crashes
c6259ac5
  *	0.60a	22/7/03	Tidied up message when sender is unknown
  *	0.60b	17/8/03	Optionally set postmaster address. Usually one uses
  *			/etc/aliases, but not everyone want's to...
  *	0.60c	22/8/03	Another go at Solaris support
  *	0.60d	26/8/03	Removed superflous buffer and unneeded strerror call
  *			ETIMEDOUT isn't an error, but should give a warning
1c3f1ce1
  *	0.60e	09/9/03	Added -P and -q flags by "Nicholas M. Kirsch"
  *			<nick@kirsch.org>
1f025849
  *	0.60f	24/9/03	Changed fprintf to fputs where possible
  *			Redirect stdin from /dev/null, stdout&stderr to
  *			/dev/console
3613bd91
  *	0.60g	26/9/03	Handle sendmail calling abort after calling cleanup
  *			(Should never happen - but it does)
  *			Added -noxheader patch from dirk.meyer@dinoex.sub.org
b5d15e64
  *	0.60h	28/9/03	Support MaxThreads option in config file,
  *			overriden by --max-children.
  *			Patch from "Richard G. Roberto" <rgr@dedlegend.com>
ecb8e6b4
  *	0.60i	30/9/03	clamfi_envfrom() now correctly returns SMFIS_TEMPFAIL,
  *			in a few circumstances it used to return EX_TEMPFAIL
  *			Patch from Matt Sullivan <matt@sullivan.gen.nz>
b14e9e77
  *	0.60j	1/10/03	strerror_r doesn't work on Linux, attempting workaround
  *			Added support for hard-coded list of email addresses
  *			who's e-mail is not scanned
3a0b4e5b
  *	0.60k	5/10/03	Only remove old UNIX domain socket if FixStaleSocket
  *			is set
dd0d5a8c
  *	0.60l	11/10/03 port is now unsigned
  *			Removed remote possibility of crash if the target
  *			e-mail address is very long
  *			No longer calls clamdscan to get the version
ecb8e6b4
  *
b5d15e64
  * Change History:
  * $Log: clamav-milter.c,v $
dd0d5a8c
  * Revision 1.13  2003/10/11 15:42:15  nigelhorne
  * Don't call clamdscan
  *
3a0b4e5b
  * Revision 1.12  2003/10/05 17:30:04  nigelhorne
  * Only fix old socket when FixStaleSocket is set
  *
31268a5c
  * Revision 1.11  2003/10/05 13:57:47  nigelhorne
  * Fixed handling of MaxThreads
  *
b14e9e77
  * Revision 1.10  2003/10/03 11:54:53  nigelhorne
  * Added white list of recipients
  *
ecb8e6b4
  * Revision 1.9  2003/09/30 11:53:55  nigelhorne
  * clamfi_envfrom was returning EX_TEMPFAIL in some places rather than SMFIS_TEMPFAIL
  *
c15d1d2c
  * Revision 1.8  2003/09/29 06:20:17  nigelhorne
  * max_children now overrides MaxThreads
  *
80a72b0d
  * Revision 1.7  2003/09/29 06:07:49  nigelhorne
  * Ensure remoteIP is set before usage
  *
b5d15e64
  * Revision 1.6  2003/09/28 16:37:23  nigelhorne
  * Added -f flag use MaxThreads if --max-children not set
  *
b151ef55
  */
dd0d5a8c
 static	char	const	rcsid[] = "$Id: clamav-milter.c,v 1.13 2003/10/11 15:42:15 nigelhorne Exp $";
b151ef55
 
3a0b4e5b
 #define	CM_VERSION	"0.60k"
b151ef55
 
 /*#define	CONFDIR	"/usr/local/etc"*/
 
 #include "defaults.h"
 #include "cfgfile.h"
 #include "../target.h"
 
 #ifndef	CL_DEBUG
 #define	NDEBUG
 #endif
 
 #include <stdio.h>
 #include <sysexits.h>
 #ifndef TARGET_OS_FREEBSD
 #include <malloc.h>
 #endif
 #include <syslog.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/wait.h>
 #include <assert.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <stdarg.h>
 #include <errno.h>
 #include <libmilter/mfapi.h>
 #include <pthread.h>
 #include <sys/time.h>
 #include <netinet/in.h>
 #include <signal.h>
1f025849
 #include <regex.h>
 #include <fcntl.h>
b151ef55
 
 #define _GNU_SOURCE
c6259ac5
 #include "getopt.h"
b151ef55
 
 /*
  * TODO: optional: xmessage on console when virus stopped (SNMP would be real nice!)
  * TODO: allow -s server to use a name as well as an IP address
  * TODO: build with libclamav.so rather than libclamav.a
  * TODO: check security - which UID will this run under?
  * TODO: bounce message should optionally be read from a file
  * TODO: optionally add a signature that the message has been scanned with ClamAV
  * TODO: Support ThreadTimeout, LogTime and Logfile from the conf
  *	 file
  * TODO: Allow more than one clamdscan server to be given
c6259ac5
  * TODO: Optionally quanrantine infected e-mails
b151ef55
  */
 
 /*
  * Each thread has one of these
  */
 struct	privdata {
 	char	*from;	/* Who sent the message */
 	char	**to;	/* Who is the message going to */
 	int	numTo;	/* Number of people the message is going to */
 	int	cmdSocket;	/*
 				 * Socket to send/get commands e.g. PORT for
 				 * dataSocket
 				 */
 	int	dataSocket;	/* Socket to send data to clamd */
 };
 
 static	int	pingServer(void);
 static	sfsistat	clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr);
 static	sfsistat	clamfi_envfrom(SMFICTX *ctx, char **argv);
 static	sfsistat	clamfi_envrcpt(SMFICTX *ctx, char **argv);
 static	sfsistat	clamfi_header(SMFICTX *ctx, char *headerf, char *headerv);
 static	sfsistat	clamfi_eoh(SMFICTX *ctx);
 static	sfsistat	clamfi_body(SMFICTX *ctx, u_char *bodyp, size_t len);
 static	sfsistat	clamfi_eom(SMFICTX *ctx);
 static	sfsistat	clamfi_abort(SMFICTX *ctx);
 static	sfsistat	clamfi_close(SMFICTX *ctx);
 static	void		clamfi_cleanup(SMFICTX *ctx);
 static	int		clamfi_send(const struct privdata *privdata, size_t len, const char *format, ...);
 static	char		*strrcpy(char *dest, const char *source);
 
 static	char	clamav_version[64];
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? */
 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
 				 */
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
 				 * enable LogSyslog in clamav.conf
 				 */
3613bd91
 static	int	nflag = 0;	/*
 				 * Don't add X-Virus-Scanned to header. Patch
 				 * from Dirk Meyer <dirk.meyer@dinoex.sub.org>
 				 */
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;
 static	unsigned	int	n_children = 0;
 static	unsigned	int	max_children = 0;
 static	int	use_syslog = 0;
 static	int	logVerbose = 0;
 static	struct	cfgstruct	*copt;
 static	const	char	*localSocket;
 static	in_port_t	tcpSocket;
 static	const	char	*serverIP = "127.0.0.1";
c6259ac5
 static	const	char	*postmaster = "postmaster";
b151ef55
 
b14e9e77
 /* TODO: read in from a file */
 static	const	char	*ignoredEmailAddresses[] = {
 	/*"Mailer-Daemon@bandsman.co.uk",
 	"postmaster@bandsman.co.uk",*/
 	NULL
 };
 
b151ef55
 static void
 help(void)
 {
 	printf("\n\tclamav-milter version %s\n", CM_VERSION);
 	puts("\tCopyright (C) 2003 Nigel Horne <njh@despammed.com>\n");
 
 	puts("\t--bounce\t\t-b\tSend a failure message to the sender.");
 	puts("\t--config-file=FILE\t-c FILE\tRead configuration from FILE.");
b5d15e64
 	puts("\t--force-scan\tForce scan all messages (overrides (-o and -l).");
b151ef55
 	puts("\t--help\t\t\t-h\tThis message.");
 	puts("\t--local\t\t\t-l\tScan messages sent from machines on our LAN.");
 	puts("\t--outgoing\t\t-o\tScan outgoing messages from this machine.");
3613bd91
 	puts("\t--noxheader\t\t-o\tSuppress X-Virus-Scanned header.");
1c3f1ce1
 	puts("\t--postmaster\t\t-p\tPostmaster address [default=postmaster].");
 	puts("\t--postmaster-only\t\t-P\tSend warnings only to the postmaster.");
 	puts("\t--quiet\t\t\t-q\tDon't send e-mail notifications of interceptions.");
b151ef55
 	puts("\t--server=ADDRESS\t-s ADDRESS\tIP address of server running clamd (when using TCPsocket).");
 	puts("\t--version\t\t-V\tPrint the version number of this software.");
 #ifdef	CL_DEBUG
 	puts("\t--debug-level=n\t\t-x n\tSets the debug level to 'n'.");
 #endif
 }
 
 int
 main(int argc, char **argv)
 {
 	extern char *optarg;
dd0d5a8c
 	char *port = NULL;
b151ef55
 	const char *cfgfile = CL_DEFAULT_CFG;
 	struct cfgstruct *cpt;
 	struct smfiDesc smfilter = {
 		"ClamAv", /* filter name */
 		SMFI_VERSION,	/* version code -- leave untouched */
 		SMFIF_ADDHDRS,	/* flags - we add headers */
 		clamfi_connect, /* connection callback */
 		NULL, /* HELO filter callback */
 		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 */
 	};
 
dd0d5a8c
 	snprintf(clamav_version, sizeof(clamav_version),
 		"ClamAV version %s, clamav-milter version %s",
 		VERSION, CM_VERSION);
 
b151ef55
 	for(;;) {
 		int opt_index = 0;
 #ifdef	CL_DEBUG
b5d15e64
 		const char *args = "bc:flnopPqdhs:Vx:";
b151ef55
 #else
b5d15e64
 		const char *args = "bc:flnopPqdhs:V";
b151ef55
 #endif
 		static struct option long_options[] = {
 			{
 				"bounce", 0, NULL, 'b'
 			},
 			{
 				"config-file", 1, NULL, 'c'
 			},
 			{
b5d15e64
 				"force-scan", 1, NULL, 'f'
 			},
 			{
b151ef55
 				"help", 0, NULL, 'h'
 			},
 			{
 				"local", 0, NULL, 'l'
 			},
 			{
3613bd91
 				"noxheader", 0, NULL, 'n'
 			},
 			{
b151ef55
 				"outgoing", 0, NULL, 'o'
 			},
 			{
c6259ac5
 				"postmaster", 0, NULL, 'p'
 			},
 			{
1c3f1ce1
 				"postmaster-only", 0, NULL, 'P',
 			},
 			{
 				"quiet", 0, NULL, 'q'
 			},
 			{
b151ef55
 				"max-children", 1, NULL, 'm'
 			},
 			{
 				"server", 1, NULL, 's'
 			},
 			{
 				"version", 0, NULL, 'V'
 			},
 #ifdef	CL_DEBUG
 			{
 				"debug-level", 1, NULL, 'x'
 			},
 #endif
 			{
 				NULL, 0, NULL, '\0'
 			}
 		};
 
 		int ret = getopt_long(argc, argv, args, long_options, &opt_index);
 
 		if(ret == -1)
 			break;
 		else if(ret == 0)
 			ret = long_options[opt_index].val;
 
 		switch(ret) {
 			case 'b':	/* bounce worms/viruses */
 				bflag++;
 				break;
 			case 'c':	/* where is clamav.conf? */
 				cfgfile = optarg;
 				break;
b5d15e64
 			case 'f':	/* force the scan */
 				fflag++;
 				break;
b151ef55
 			case 'h':
 				help();
 				return EX_OK;
 			case 'l':	/* scan mail from the lan */
 				lflag++;
 				break;
c6259ac5
 			case 'm':	/* maximum number of children */
 				max_children = atoi(optarg);
 				break;
3613bd91
 			case 'n':	/* don't add X-Virus-Scanned */
 				nflag++;
 				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;
b151ef55
 			case 's':	/* server running clamd */
 				serverIP = optarg;
 				break;
 			case 'V':
dd0d5a8c
 				puts(clamav_version);
b151ef55
 				return EX_OK;
 #ifdef	CL_DEBUG
 			case 'x':
 				debug_level = atoi(optarg);
 				break;
 #endif
 			default:
 #ifdef	CL_DEBUG
1c3f1ce1
 				fprintf(stderr, "Usage: %s [-b] [-c=FILE] [--max-children=num] [-l] [-o] [-p=address] [-P] [-q] [-x#] socket-addr\n", argv[0]);
b151ef55
 #else
1c3f1ce1
 				fprintf(stderr, "Usage: %s [-b] [-c=FILE] [--max-children=num] [-l] [-o] [-p=address] [-P] [-q] socket-addr\n", argv[0]);
b151ef55
 #endif
 				return EX_USAGE;
 		}
 	}
 
 	if (optind == argc) {
 		fprintf(stderr, "%s: No socket-addr given\n", argv[0]);
 		return EX_USAGE;
 	}
 	port = argv[optind];
 
 	/*
 	 * Sanity checks on the clamav configuration file
 	 */
 	if((copt = parsecfg(cfgfile)) == NULL) {
 		fprintf(stderr, "%s: Can't parse the config file %s\n",
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 
 	if(!cfgopt(copt, "StreamSaveToDisk")) {
 		fprintf(stderr, "%s: StreamSavetoDisk not enabled in %s\n",
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 
 	if(!cfgopt(copt, "ScanMail")) {
 		fprintf(stderr, "%s: ScanMail not enabled in %s\n",
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 
 	/*
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
 
 	/*
b151ef55
 	 * Get the outgoing socket details - the way to talk to clamd
 	 * TODO: support TCP sockets
 	 */
 	if((cpt = cfgopt(copt, "LocalSocket")) != NULL)
 		/*
 		 * TODO: check --server hasn't been set
 		 */
 		localSocket = cpt->strarg;
 	else if((cpt = cfgopt(copt, "TCPSocket")) != NULL) {
 		/*
 		 * TCPSocket is in fact a port number not a full socket
 		 */
 		tcpSocket = (in_port_t)cpt->numarg;
 		if(!pingServer()) {
 			fprintf(stderr, "Can't talk to clamd server at %s on port %d\n",
 				serverIP, tcpSocket);
 			fprintf(stderr, "Check your entry for TCPSocket in %s\n",
 				cfgfile);
 			return EX_CONFIG;
 		}
 	} else {
 		fprintf(stderr, "%s: You must select server type (local/TCP) in %s\n",
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 	if(localSocket && tcpSocket) {
 		fprintf(stderr, "%s: You can select one server type only (local/TCP) in %s\n",
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 
 	if(!cfgopt(copt, "Foreground"))
 		switch(fork()) {
 			case -1:
 				perror("fork");
 				return EX_TEMPFAIL;
 			case 0:	/* child */
 				break;
 			default:	/* parent */
 				return EX_OK;
 		}
 
 	if(smfi_setconn(port) == MI_FAILURE) {
 		fprintf(stderr, "%s: smfi_setconn failed\n",
 			argv[0]);
 		return EX_SOFTWARE;
 	}
 
 	if(cfgopt(copt, "LogSyslog")) {
 		openlog("clamav-milter", LOG_CONS|LOG_PID, LOG_MAIL);
 		syslog(LOG_INFO, clamav_version);
 #ifdef	CL_DEBUG
 		if(debug_level > 0)
 			syslog(LOG_DEBUG, "Debugging is on");
 #endif
 		use_syslog = 1;
 
 		if(cfgopt(copt, "LogVerbose"))
 			logVerbose = 1;
1c3f1ce1
 	} else {
 		if(qflag)
 			fprintf(stderr, "%s: (-q && !LogSysLog): warning - all interception message methods are off\n",
 				argv[0]);
b151ef55
 		use_syslog = 0;
1c3f1ce1
 	}
b151ef55
 
3a0b4e5b
 	if(cfgopt(copt, "FixStaleSocket")) {
 		/*
 		 * Get the incoming socket details - the way sendmail talks to
 		 * us
 		 *
 		 * TODO: There's a security problem here that'll need fixing
 		 */
 		if(strncasecmp(port, "unix:", 5) == 0) {
 			if(unlink(&port[5]) < 0)
 				perror(&port[5]);
 		} else if(strncasecmp(port, "local:", 6) == 0) {
 			if(unlink(&port[6]) < 0)
 				perror(&port[6]);
 		}
 	}
b151ef55
 
 	if(smfi_register(smfilter) == MI_FAILURE) {
1f025849
 		fputs("smfi_register failure\n", stderr);
b151ef55
 		return EX_UNAVAILABLE;
 	}
 
 	signal(SIGPIPE, SIG_IGN);
 
1f025849
 	close(0);
 	close(1);
 	close(2);
 	open("/dev/null", O_RDONLY);
b14e9e77
 	if(open("/dev/console", O_WRONLY) == 1)
 		dup(1);
1f025849
 
b151ef55
 	return smfi_main();
 }
 
 /*
  * Verify that the server is where we think it is
  * Returns true or false
  */
 static int
 pingServer(void)
 {
 	struct sockaddr_in server;
 	int sock, nbytes;
 	char buf[6];
 
 	memset((char *)&server, 0, sizeof(struct sockaddr_in));
 	server.sin_family = AF_INET;
 	server.sin_port = htons(tcpSocket);
 	server.sin_addr.s_addr = inet_addr(serverIP);
 
 	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");
 		return 0;
 	}
 	if(send(sock, "PING\n", 5, 0) < 5) {
 		perror("send");
 		close(sock);
 		return 0;
 	}
 
 	shutdown(sock, SHUT_WR);
 
 	nbytes = recv(sock, buf, sizeof(buf), 0);
 
 	close(sock);
 
 	if(nbytes < 0) {
 		perror("recv");
 		return 0;
 	}
 	buf[nbytes] = '\0';
 
 	return strcmp(buf, "PONG\n") == 0;
 }
 
 static sfsistat
 clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
 {
 	char buf[INET_ADDRSTRLEN];	/* IPv4 only */
 	const char *remoteIP = inet_ntop(AF_INET, &((struct sockaddr_in *)(hostaddr))->sin_addr, buf, sizeof(buf));
 
80a72b0d
 #ifdef	CL_DEBUG
 	assert(remoteIP != NULL);
 #endif
 
b151ef55
 	if(use_syslog)
 		syslog(LOG_NOTICE, "clamfi_connect: connection from %s [%s]", hostname, remoteIP);
1f025849
 #ifdef	CL_DEBUG
 	if(debug_level >= 4)
 		printf("clamfi_connect: connection from %s [%s]\n", hostname, remoteIP);
 #endif
b151ef55
 
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)
 				syslog(LOG_DEBUG, "clamfi_connect: not scanning outgoing messages");
 			puts("clamfi_connect: not scanning outgoing messages");
 #endif
 			return SMFIS_ACCEPT;
 		}
 	if(!lflag) {
 		/*
 		 * Decide what constitutes a local IP address. Emails from
 		 * local machines are not scanned.
 		 *
 		 * TODO: read these from clamav.conf
 		 */
 		static const char *localAddresses[] = {
 			/*"^192\\.168\\.[0-9]+\\.[0-9]+$",*/
 			"^192\\.168\\.[0-9]*\\.[0-9]*$",
 			"^10\\.0\\.0\\.[0-9]*$",
 			"127.0.0.1",
 			NULL
 		};
 		const char **possible;
 
 		for(possible = localAddresses; *possible; possible++) {
 			int rc;
 			regex_t reg;
 
 			if(regcomp(&reg, *possible, 0) != 0) {
 				if(use_syslog)
 					syslog(LOG_ERR, "Couldn't parse local regexp");
 				return SMFIS_TEMPFAIL;
 			}
 
 			rc = (regexec(&reg, remoteIP, 0, NULL, 0) == REG_NOMATCH) ? 0 : 1;
 
 			regfree(&reg);
 
 			if(rc) {
 #ifdef	CL_DEBUG
 				if(use_syslog)
 					syslog(LOG_DEBUG, "clamfi_connect: not scanning local messages");
 				puts("clamfi_connect: not scanning outgoing messages");
 #endif
 				return SMFIS_ACCEPT;
 			}
 		}
 	}
 	return SMFIS_CONTINUE;
 }
 
 static sfsistat
 clamfi_envfrom(SMFICTX *ctx, char **argv)
 {
 	struct privdata *privdata;
 	struct sockaddr_in reply;
dd0d5a8c
 	unsigned short port;
b151ef55
 	int nbytes, rc;
 	char buf[64];
 
 	if(logVerbose)
 		syslog(LOG_DEBUG, "clamfi_envfrom: %s", argv[0]);
 
 #ifdef	CL_DEBUG
 	printf("clamfi_envfrom: %s\n", argv[0]);
 #endif
 
 	if(max_children > 0) {
 		rc = 0;
 
 		pthread_mutex_lock(&n_children_mutex);
 
 		while((n_children >= max_children) && (rc != ETIMEDOUT)) {
 			struct timeval now;
 			struct timespec timeout;
 			struct timezone tz;
 
 			/*
 			 * Use pthread_cond_timedwait rather than
 			 * pthread_cond_wait since the sendmail which calls
 			 * us will have a timeout that we don't want to exceed
 			 *
 			 * Wait for a maximum of 1 minute.
 			 *
 			 * TODO: this timeout should be configurable
 			 * It stops sendmail getting fidgety.
 			 */
 			gettimeofday(&now, &tz);
 			timeout.tv_sec = now.tv_sec + 60;
 			timeout.tv_nsec = 0;
 
 			if(use_syslog)
 				syslog(LOG_NOTICE,
 					"hit max-children limit (%u >= %u): waiting for some to exit",
 					n_children, max_children);
 			rc = pthread_cond_timedwait(&n_children_cond, &n_children_mutex, &timeout);
c6259ac5
 #ifdef	CL_DEBUG
b151ef55
 			if(rc != 0) {
c6259ac5
 #else
 			if((rc != 0) && use_syslog) {
 #endif
 				char message[64];
b151ef55
 
1f025849
 #ifdef TARGET_OS_SOLARIS	/* no strerror_r */
c6259ac5
 				snprintf(message, sizeof(message), "pthread_cond_timedwait: %s", strerror(rc));
 #else
b14e9e77
 				if(strerror_r(rc, buf, sizeof(buf)) == NULL)
 					switch(rc) {
 						case EINTR:
 							strcpy(buf, "Interrupted system call");
 							break;
 						case ETIMEDOUT:
 							strcpy(buf, "Timedout");
 							break;
 						default:
 							strcpy(buf, "Unknown error");
 							break;
 						}
 				snprintf(message, sizeof(message), "pthread_cond_timedwait: (rc = %d) %s", rc, buf);
c6259ac5
 #endif
 				if(use_syslog) {
 					if(rc == ETIMEDOUT)
 						syslog(LOG_NOTICE, message);
 					else
 						syslog(LOG_ERR, message);
 				}
b151ef55
 #ifdef	CL_DEBUG
 				puts(message);
 #endif
 			}
 		}
 		n_children++;
 
 #ifdef	CL_DEBUG
 		printf(">n_children = %d\n", n_children);
 #endif
 		pthread_mutex_unlock(&n_children_mutex);
 
 		if(rc == ETIMEDOUT) {
 #ifdef	CL_DEBUG
 			if(use_syslog)
 				syslog(LOG_NOTICE, "Timeout waiting for a child to die");
 			puts("Timeout waiting for a child to die");
 #endif
 		}
 	}
 
 	privdata = (struct privdata *)calloc(1, sizeof(struct privdata));
 	privdata->dataSocket = -1;	/* 0.4 */
 	privdata->cmdSocket = -1;	/* 0.4 */
 
 	/*
 	 * 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) {
 		struct sockaddr_un server;
 
 		memset((char *)&server, 0, sizeof(struct sockaddr_un));
 		server.sun_family = AF_UNIX;
 		strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
 
 		if((privdata->cmdSocket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
 			perror("socket");
ecb8e6b4
 			return SMFIS_TEMPFAIL;
b151ef55
 		}
 		if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
 			perror(localSocket);
ecb8e6b4
 			return SMFIS_TEMPFAIL;
b151ef55
 		}
 	} else {
 		struct sockaddr_in server;
 
 		memset((char *)&server, 0, sizeof(struct sockaddr_in));
 		server.sin_family = AF_INET;
 		server.sin_port = htons(tcpSocket);
 		server.sin_addr.s_addr = inet_addr(serverIP);
 
 		if((privdata->cmdSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
 			perror("socket");
ecb8e6b4
 			return SMFIS_TEMPFAIL;
b151ef55
 		}
 		if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
 			perror("connect");
ecb8e6b4
 			return SMFIS_TEMPFAIL;
b151ef55
 		}
 	}
 
 	/*
 	 * Create socket that we'll use to send the data to clamd
 	 */
 	if((privdata->dataSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
 		perror("socket");
 		close(privdata->cmdSocket);
 		free(privdata);
 		if(use_syslog)
 			syslog(LOG_ERR, "send failed to create socket");
 		return SMFIS_TEMPFAIL;
 	}
 
 	shutdown(privdata->dataSocket, SHUT_RD);
 
 	if(send(privdata->cmdSocket, "STREAM\n", 7, 0) < 7) {
 		perror("send");
 		close(privdata->dataSocket);
 		close(privdata->cmdSocket);
 		free(privdata);
 		if(use_syslog)
 			syslog(LOG_ERR, "send failed to clamd");
 		return SMFIS_TEMPFAIL;
 	}
 
 	shutdown(privdata->cmdSocket, SHUT_WR);
 
 	nbytes = recv(privdata->cmdSocket, buf, sizeof(buf), 0);
 	if(nbytes < 0) {
 		perror("recv");
 		close(privdata->dataSocket);
 		close(privdata->cmdSocket);
 		free(privdata);
 		if(use_syslog)
 			syslog(LOG_ERR, "recv failed from clamd getting PORT");
 		return SMFIS_TEMPFAIL;
 	}
 	buf[nbytes] = '\0';
 #ifdef	CL_DEBUG
 	if(debug_level >= 4)
 		printf("Received: %s", buf);
 #endif
dd0d5a8c
 	if(sscanf(buf, "PORT %hu\n", &port) != 1) {
b151ef55
 		close(privdata->dataSocket);
 		close(privdata->cmdSocket);
 		free(privdata);
 		if(use_syslog)
 			syslog(LOG_ERR, "Expected port information from clamd, got '%s'",
 				buf);
1f025849
 		else
 			fprintf(stderr, "Expected port information from clamd, got '%s'\n",
 				buf);
b151ef55
 		return SMFIS_TEMPFAIL;
 	}
 
 	memset((char *)&reply, 0, sizeof(struct sockaddr_in));
 	reply.sin_family = AF_INET;
 	reply.sin_port = ntohs(port);
 
 	reply.sin_addr.s_addr = inet_addr(serverIP);
 
 #ifdef	CL_DEBUG
 	if(debug_level >= 4)
 		printf("Connecting to local port %d\n", port);
 #endif
 
 	rc = connect(privdata->dataSocket, (struct sockaddr *)&reply, sizeof(struct sockaddr_in));
 
 	if(rc < 0) {
 		perror("connect");
 
 		close(privdata->dataSocket);
 		close(privdata->cmdSocket);
 		free(privdata);
 
 		/* 0.4 - use better error message */
 		if(use_syslog) {
1f025849
 #ifdef TARGET_OS_SOLARIS	/* no strerror_r */
c6259ac5
 			syslog(LOG_ERR, "Failed to connect to port %d given by clamd: %s", port, strerror(rc));
 #else
b151ef55
 			strerror_r(rc, buf, sizeof(buf));
 			syslog(LOG_ERR, "Failed to connect to port %d given by clamd: %s", port, buf);
c6259ac5
 #endif
b151ef55
 		}
 
 		return SMFIS_TEMPFAIL;
 	}
 
 	clamfi_send(privdata, 0, "From %s\n", argv[0]);
 	clamfi_send(privdata, 0, "From: %s\n", argv[0]);
 
 	privdata->from = strdup(argv[0]);
 	privdata->to = NULL;
 
 	return (smfi_setpriv(ctx, privdata) == MI_SUCCESS) ? SMFIS_CONTINUE : SMFIS_TEMPFAIL;
 }
 
 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]);
 
 #ifdef	CL_DEBUG
 	printf("clamfi_envrcpt: %s \n", argv[0]);
 #endif
 
 	clamfi_send(privdata, 0, "To: %s\n", argv[0]);
 
 	if(privdata->to == NULL) {
 		privdata->to = malloc(sizeof(char *) * 2);
 
 		assert(privdata->numTo == 0);
 	} else
 		privdata->to = realloc(privdata->to, sizeof(char *) * (privdata->numTo + 2));
 
 	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)
 		printf("clamfi_header: %s: %s\n", headerf, headerv);
 	else
 		puts("clamfi_header");
 #endif
 
 	if(clamfi_send(privdata, 0, "%s: %s\n", headerf, headerv) < 0) {
 		clamfi_cleanup(ctx);
 		return SMFIS_TEMPFAIL;
 	}
 	return SMFIS_CONTINUE;
 }
 
 static sfsistat
 clamfi_eoh(SMFICTX *ctx)
 {
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
b14e9e77
 	char **to;
b151ef55
 
 	if(logVerbose)
 		syslog(LOG_DEBUG, "clamfi_eoh");
 #ifdef	CL_DEBUG
 	puts("clamfi_eoh");
 #endif
 
 	if(clamfi_send(privdata, 1, "\n") < 0) {
 		clamfi_cleanup(ctx);
 		return SMFIS_TEMPFAIL;
 	}
 
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
 	 */
 	for(to = privdata->to; *to; to++) {
 		const char **s;
 
 		for(s = ignoredEmailAddresses; *s; s++)
 			if(strcasecmp(*s, *to) == 0)
 				/*
 				 * This recipient is on the whitelist
 				 */
 				break;
 
 		if(*s == NULL)
 			/*
 			 * 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)
 		syslog(LOG_NOTICE, "clamfi_connect: ignoring whitelisted message");
 #ifdef	CL_DEBUG
 	puts("clamfi_connect: not scanning outgoing messages");
 #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);
 
 	if(logVerbose)
 		syslog(LOG_DEBUG, "clamfi_envbody: %u bytes", len);
 #ifdef	CL_DEBUG
 	printf("clamfi_envbody: %u bytes\n", len);
 #endif
 
 	if(clamfi_send(privdata, len, (char *)bodyp) < 0) {
 		clamfi_cleanup(ctx);
 		return SMFIS_TEMPFAIL;
 	}
 	return SMFIS_CONTINUE;
 }
 
 static sfsistat
 clamfi_eom(SMFICTX *ctx)
 {
 	int rc = SMFIS_CONTINUE;
 	char *ptr;
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
 	char mess[128];
 
 	if(logVerbose)
 		syslog(LOG_DEBUG, "clamfi_eom");
 #ifdef	CL_DEBUG
 	puts("clamfi_eom");
 	assert(privdata != NULL);
 	assert(privdata->cmdSocket >= 0);
 	assert(privdata->dataSocket >= 0);
 #endif
 
 	close(privdata->dataSocket);
 	privdata->dataSocket = -1;
 
 	if(recv(privdata->cmdSocket, mess, sizeof(mess), 0) > 0) {
 		if((ptr = strchr(mess, '\n')) != NULL)
 			*ptr = '\0';
 
 		if(logVerbose)
 			syslog(LOG_DEBUG, "clamfi_eom: read %s", mess);
 #ifdef	CL_DEBUG
 		printf("clamfi_eom: read %s\n", mess);
 #endif
 	} else {
 		syslog(LOG_NOTICE, "clamfi_eom: read nothing from clamd");
 #ifdef	CL_DEBUG
 		puts("clamfi_eom: read nothing from clamd");
 #endif
 		mess[0] = '\0';
 	}
 
 	if(strstr(mess, "FOUND") == NULL) {
3613bd91
 		if(!nflag)
 			smfi_addheader(ctx, "X-Virus-Scanned", clamav_version);
b151ef55
 
 		/*
 		 * TODO: if privdata->from is NULL it's probably SPAM, and
 		 * me might consider bouncing it...
 		 */
 		if(use_syslog)
c6259ac5
 			syslog(LOG_NOTICE, "clean message from %s",
 				(privdata->from) ? privdata->from : "an unknown sender");
b151ef55
 	} else {
 		int i;
 		char **to, *err;
 		FILE *sendmail;
 
 		if(use_syslog)
 			syslog(LOG_NOTICE, mess);
 
 		/*
 		 * Setup err as a list of recipients
 		 */
c6259ac5
 		err = (char *)malloc(1024);
b151ef55
 
dd0d5a8c
 		/*
 		 * Use snprintf rather than printf since we don't know the
 		 * length of privdata->from and may get a buffre overrun
 		 * causing a crash
 		 */
 		snprintf(err, 1024, "Intercepted virus from %s to", privdata->from);
b151ef55
 
 		ptr = strchr(err, '\0');
 
c6259ac5
 		i = 1024;
 
b151ef55
 		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;
 				err = realloc(err, i);
 			}
 			ptr = strrcpy(ptr, " ");
 			ptr = strrcpy(ptr, *to);
 		}
c6259ac5
 		(void)strcpy(ptr, "\n");
b151ef55
 
 		if(use_syslog)
 			syslog(LOG_NOTICE, err);
 #ifdef	CL_DEBUG
 		puts(err);
 #endif
 
1c3f1ce1
 		if(!qflag) {
 			sendmail = popen("/usr/lib/sendmail -t", "w");
 			if(sendmail) {
dd0d5a8c
 				/*
 				 * TODO: Make this e-mail message customisable
 				 * perhaps by means of a template
 				 */
1c3f1ce1
 				fputs("From: MAILER-DAEMON\n", sendmail);
 				if(bflag) {
 					fprintf(sendmail, "To: %s\n", privdata->from);
 					fprintf(sendmail, "Cc: %s\n", postmaster);
 				} else
 					fprintf(sendmail, "To: %s\n", postmaster);
 
 				if(!pflag)
 					for(to = privdata->to; *to; to++)
 						fprintf(sendmail, "Cc: %s\n", *to);
 				fputs("Subject: Virus intercepted\n\n", sendmail);
 
 				if(bflag)
 					fputs("A message you sent to\n\t", sendmail);
 				else
 					fprintf(sendmail, "A message sent from %s to\n\t", privdata->from);
 
 				for(to = privdata->to; *to; to++)
 					fprintf(sendmail, "%s\n", *to);
 				fputs("contained a virus and has not been delivered.\n\t", sendmail);
 				fputs(mess, sendmail);
 
 				pclose(sendmail);
 			}
b151ef55
 		}
 
 		smfi_setreply(ctx, "550", "5.7.1", "Virus detected by ClamAV - http://clamav.elektrapro.com");
 		rc = SMFIS_REJECT;
 		free(err);
 	}
 	clamfi_cleanup(ctx);
 
 	return rc;
 }
 
 static sfsistat
 clamfi_abort(SMFICTX *ctx)
 {
 #ifdef	CL_DEBUG
 	if(use_syslog)
 		syslog(LOG_DEBUG, "clamfi_abort");
 	puts("clamfi_abort");
 #endif
 
 	/*
 	 * Unlock incase we're called during a cond_timedwait in envfrom
 	 *
 	 * TODO: There *must* be a tidier way of doing this!
 	 */
 	(void)pthread_mutex_unlock(&n_children_mutex);
 
 	clamfi_cleanup(ctx);
 
 	return SMFIS_TEMPFAIL;
 }
 
 static sfsistat
 clamfi_close(SMFICTX *ctx)
 {
 #ifdef	CL_DEBUG
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
 
 	puts("clamfi_close");
 	assert(privdata == NULL);
 #endif
 
 	if(logVerbose)
 		syslog(LOG_DEBUG, "clamfi_close");
 
 	return SMFIS_CONTINUE;
 }
 
 static void
 clamfi_cleanup(SMFICTX *ctx)
 {
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
 
3613bd91
 	if(privdata) {
 		if(privdata->dataSocket >= 0) {
 			close(privdata->dataSocket);
 			privdata->dataSocket = -1;
 		}
b151ef55
 
3613bd91
 		if(privdata->from) {
b151ef55
 #ifdef	CL_DEBUG
3613bd91
 			if(debug_level >= 9)
 				puts("Free privdata->from");
b151ef55
 #endif
3613bd91
 			free(privdata->from);
 			privdata->from = NULL;
 		}
b151ef55
 
3613bd91
 		if(privdata->to) {
 			char **to;
b151ef55
 
3613bd91
 			for(to = privdata->to; *to; to++) {
b151ef55
 #ifdef	CL_DEBUG
3613bd91
 				if(debug_level >= 9)
 					puts("Free *privdata->to");
b151ef55
 #endif
3613bd91
 				free(*to);
 			}
b151ef55
 #ifdef	CL_DEBUG
3613bd91
 			if(debug_level >= 9)
 				puts("Free privdata->to");
b151ef55
 #endif
3613bd91
 			free(privdata->to);
 			privdata->to = NULL;
 		}
b151ef55
 
3613bd91
 		if(privdata->cmdSocket >= 0) {
 			char buf[64];
b151ef55
 
3613bd91
 			/*
 			 * Flush the remote end so that clamd doesn't get a SIGPIPE
 			 */
 			while(recv(privdata->cmdSocket, buf, sizeof(buf), 0) > 0)
 				;
 			close(privdata->cmdSocket);
 			privdata->cmdSocket = -1;
 		}
b151ef55
 
 #ifdef	CL_DEBUG
3613bd91
 		if(debug_level >= 9)
 			puts("Free privdata");
b151ef55
 #endif
3613bd91
 		free(privdata);
 		smfi_setpriv(ctx, NULL);
 	}
b151ef55
 
 	if(max_children > 0) {
 		pthread_mutex_lock(&n_children_mutex);
 		/*
 		 * Deliberately errs on the side of broadcasting too many times
dd0d5a8c
 		 *
 		 * No need to check for underflow since n_children must be > 0
b151ef55
 		 */
 		--n_children;
 		if((n_children < max_children) && (n_children > 0)) {
 #ifdef	CL_DEBUG
 			puts("pthread_cond_broadcast");
 #endif
 			if(pthread_cond_broadcast(&n_children_cond) < 0)
 				perror("pthread_cond_broadcast");
 		}
 #ifdef	CL_DEBUG
 		printf("<n_children = %d\n", n_children);
 #endif
 		pthread_mutex_unlock(&n_children_mutex);
 	}
 }
 
 static int
 clamfi_send(const struct privdata *privdata, size_t len, const char *format, ...)
 {
 	char output[BUFSIZ];
 	const char *ptr;
 
 	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);
 		vsnprintf(output, sizeof(output), format, argp);
 		va_end(argp);
 
 		len = strlen(output);
 		ptr = output;
 	}
 #ifdef	CL_DEBUG
 	if(debug_level >= 9)
 		printf("clamfi_send: len=%u bufsiz=%u\n", len, sizeof(output));
 #endif
 
 	while(len > 0) {
 		int nbytes = send(privdata->dataSocket, ptr, len, 0);
 
 		if(nbytes == -1) {
 			if(errno == EINTR)
 				continue;
 			perror("send");
 			if(use_syslog)
 				syslog(LOG_ERR, "write failure to clamd");
 
 			return -1;
 		}
 		len -= nbytes;
 		ptr = &ptr[nbytes];
 	}
 	return 0;
 }
 
 /*
  * Like strcpy, but return the END of the destination, allowing a quicker
  * means of adding to the end of a string than strcat
  */
 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);
 }