clamav-devel/clamav-milter/clamav-milter.c
e3aaff8e
 /*
  * 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.
  *
7e10f99b
  * Install into /usr/local/sbin/clamav-milter
e3aaff8e
  *
  * 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:
049a18b9
  * 1) Ensure that you have the sendmail-devel RPM installed
  * 2) Add to /etc/mail/sendmail.mc:
ecaaaf05
  *	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clmilter.sock, F=, T=S:4m;R:4m')dnl
e3aaff8e
  *	define(`confINPUT_MAIL_FILTERS', `clamav')
049a18b9
  * 3) Check entry in /usr/local/etc/clamav.conf of the form:
ecaaaf05
  *	LocalSocket /var/run/clamav/clamd.sock
e3aaff8e
  *	StreamSaveToDisk
049a18b9
  * 4) If you already have a filter (such as spamassassin-milter from
e3aaff8e
  * http://savannah.nongnu.org/projects/spamass-milt) add it thus:
ecaaaf05
  *	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clmilter.sock, F=, T=S:4m;R:4m')dnl
e3aaff8e
  *	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
7e10f99b
  *	mkdir /var/run/clamav
  *	chown clamav /var/run/clamav	(if you use User clamav in clamav.conf)
  *	chmod 700 /var/run/clamav
1f7a8360
  *
  * The above example shows clamav-milter, clamd and sendmail all on the
  * same machine, however using TCP they may reside on different machines,
  * indeed clamav-milter is capable of talking to multiple clamds for redundancy
  * and load balancing.
049a18b9
  * 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
e3aaff8e
  * with that.
049a18b9
  * 6) I suggest putting SpamAssassin first since you're more likely to get spam
e3aaff8e
  * than a virus/worm sent to you.
049a18b9
  * 7) Add to /etc/sysconfig/clamav-milter
ecaaaf05
  *	CLAMAV_FLAGS="--max-children=2 local:/var/run/clamav/clmilter.sock"
e3aaff8e
  * or if clamd is on a different machine
ecaaaf05
  *	CLAMAV_FLAGS="--max-children=2 --server=192.168.1.9 local:/var/run/clamav/clmilter.sock"
1f7a8360
  *
  * If you want clamav-milter to listen on TCP for communication with sendmail,
  * for example if they are on different machines use inet:<port>.
  * On machine A (running sendmail) you would have in sendmail.mc:
  *	INPUT_MAIL_FILTER(`clamav', `S=inet:3311@machineb, F=, T=S:4m;R:4m')dnl
  * On machine B (running clamav-milter) you would start up clamav-milter thus:
  *	clamav-milter inet:3311
  *
049a18b9
  * 8) You should have received a script to put into /etc/init.d with this
  * software.
e004f1c5
  * 9) run 'chown clamav /usr/local/sbin/clamav-milter; chmod 4700 /usr/local/sbin/clamav-milter
e3aaff8e
  *
  * 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.
049a18b9
  * 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
e3aaff8e
  *
  * Sendmail on MacOS/X (10.1) is provided without a development package so this
  * can't be run "out of the box"
  *
049a18b9
  * Solaris 8 doesn't have milter support so clamav-milter won't work unless
  * you rebuild sendmail from source.
e3aaff8e
  *
  * 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
  *
2defd014
  * FreeBSD4.8: compiles out of the box with either gcc2.95 or gcc3
fe3d8be8
  *
  * OpenBSD3.4: the supplied sendmail does not come with Milter support.
  * Do this *before* running configure (thanks for Per-Olov Sjöhol
  * <peo_s@incedo.org>for these instructions).
  *
  *	echo WANT_LIBMILTER=1 > /etc/mk.conf
  *	cd /usr/src/gnu/usr.sbin/sendmail
  *	make depend
  *	make
  *	make install
  *	kill -HUP `sed q /var/run/sendmail.pid`
  *
  * Then do this to make the milter headers available to clamav...
  * (the libmilter.a file is already in the right place after the sendmail
  * recompiles above)
  *
  *	cd /usr/include
  *	ln -s ../src/gnu/usr.sbin/sendmail/include/libmilter libmilter
049a18b9
  *
2a2f7ff3
  * Solaris 9 and FreeBSD5 have 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 on Solaris
  *
e3aaff8e
  * 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
049a18b9
  *	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
1fcdb893
  *	0.60d	26/8/03	Removed superfluous buffer and unneeded strerror call
049a18b9
  *			ETIMEDOUT isn't an error, but should give a warning
7a9b0f05
  *	0.60e	09/9/03	Added -P and -q flags by "Nicholas M. Kirsch"
  *			<nick@kirsch.org>
01ab0124
  *	0.60f	24/9/03	Changed fprintf to fputs where possible
  *			Redirect stdin from /dev/null, stdout&stderr to
  *			/dev/console
7418fb74
  *	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
2cd8b9d4
  *	0.60h	28/9/03	Support MaxThreads option in config file,
  *			overriden by --max-children.
  *			Patch from "Richard G. Roberto" <rgr@dedlegend.com>
b9d3b885
  *	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>
6909adb8
  *	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
db035545
  *	0.60k	5/10/03	Only remove old UNIX domain socket if FixStaleSocket
  *			is set
9148ec6d
  *	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
a4371160
  *	0.60m	12/10/03 Now does sanity check if using localSocket
  *			Gets version info from clamd
  *			Only reset fd's 0/1/2 if !ForeGround
198d714a
  *	0.60n	22/10/03 Call pthread_cont_broadcast more often
68d5a5f3
  *	0.60o	31/10/03 Optionally accept all mails if scanning procedure
  *			fails (Joe Talbott <josepht@cstone.net>)
88f28d8c
  *	0.60p	5/11/03	Only call mutex_unlock when max_children is set
  *			Tidy up the call to pthread_cond_timedwait
d3f8fcf7
  *	0.60q	11/11/03 Fixed handling of % characters in e-mail addresses
  *			pointed out by dotslash@snosoft.com
4d0bd1f7
  *	0.65	15/11/03 Upissue of clamav
dad136d5
  *	0.65a	19/11/03 Close cmdSocket earlier
  *			Added setpgrp()
e004f1c5
  *	0.65b	22/11/03 Ensure milter is not run as root if requested
  *			Added quarantine support
3aa15b4c
  *	0.65c	24/11/03 Support AllowSupplementaryGroups
  *			Fix warning about root usage
2f5f8390
  *	0.65d	25/11/03 Handle empty hostname or hostaddr
  *			Fix based on a submission by Michael Dankov <misha@btrc.ru>
b312f172
  *	0.65e	29/11/03 Fix problem of possible confused pointers if large
  *			number of recipients given.
  *			Fix by Michael Dankov <misha@btrc.ru>.
668c7570
  *	0.65f	29/11/03 Added --quarantine-dir
  *			Thanks to Michael Dankov <misha@btrc.ru>.
2defd014
  *	0.65g	2/12/03	Use setsid if setpgrp is not present.
  *			Thanks to Eugene Crosser <crosser@rol.ru>
332e6334
  *	0.65h	4/12/03	Added call to umask to ensure that the local socket
  *			is not publically writeable. If it is sendmail
  *			will (correctly!) refuse to start this program
  *			Thanks for Nicklaus Wicker <n.wicker@cnk-networks.de>
  *			Don't sent From as the first line since that means
  *			clamd will think it is an mbox and not handle
  *			unescaped From at the start of lines properly
  *			Thanks to Michael Dankov <misha@btrc.ru>
c9af1776
  *	0.65i	9/12/03	Use the location of sendmail discovered by configure
66ff992e
  *	0.65j	10/12/03 Timeout on waiting for data from clamd
98135801
  *	0.65k	12/12/03 A couple of calls to clamfi_cleanup were missing
  *			before return cl_error
8ea7fdd0
  *	0.66	13/12/03 Upissue
fa9628f2
  *	0.66a	22/12/03 Added --sign
51b03ecb
  *	0.66b	27/12/03 --sign moved to privdata
2a2f7ff3
  *	0.66c	31/12/03 Included the sendmail queue ID in the log, from an
  *			idea by Andy Fiddaman <af@jeamland.org>
fe3d8be8
  *	0.66d	10/1/04	Added OpenBSD instructions
  *			Added --signature-file option
fa2c672a
  *	0.66e	12/1/04	FixStaleSocket: no longer complain if asked to remove
  *			an old socket when there was none to remove
0e244102
  *	0.66f	24/1/04	-s: Allow clamd server name as well as IPaddress
44d08756
  *	0.66g	25/1/04 Corrected usage message
  *			Started to honour --debug
  *			Dump core on LINUX if CL_DEBUG set
  *			Support multiple servers separated by colons
e576bf42
  *	0.66h	26/1/04	Corrected endian problem (ntohs instead of htons)
952f2560
  *	0.66i	28/1/04	Fixed compilation error with --enable-debug
3a03d183
  *	0.66j	29/1/03	Added --noreject flag, based on a patch by
  *			"Vijay Sarvepalli" <vssarvep@office.uncg.edu>
d4b2e2e4
  *	0.66k	2/2/04	When --postmaster-only is given, include the system
  *			ID of the message in the warning e-mail, since that
  *			will help the administrator when sifting through the
  *			mail logs. Based on an idea by Jim Allen,
  *			<Jim.Allen@Heartsine.co.uk>
7908713f
  *	0.66l	7/2/04	Updated URL reference
  *			Added new config.h mechanism
9e1e77b9
  *	0.66m	9/2/04	Added Hflag from "Leonid Zeitlin" <lz@europe.com>
b2fb75ea
  *	0.66n	13/2/04	Added TCPwrappers support
  *			Removed duplication in version string
  *			Handle machines that don't have in_port_t
46a4aaa1
  *	0.
c54d7329
  *	0.67a	16/2/04	Added clamfi_free
055404a5
  *	0.67b	17/2/04	Removed compilation warning - now compiles on FreeBSD5.2
  *			Don't allow --force to overwride TCPwrappers
fdc6066a
  *	0.67c	18/2/04	Added dont-log-clean flag
f9c88a98
  *	0.67d	19/2/04	Reworked TCPwrappers code
  *			Thanks to "Hector M. Rulot Segovia" <Hector.Rulot@uv.es>
  *			Changed some printf/puts to cli_dbgmsg
1fcdb893
  *	0.67e	20/2/04	Moved the definition of the sendmail pipe
  *			The recent changes to the configure script changed
b5648b5a
  *			the order of includes so some prototypes weren't
1fcdb893
  *			getting in
b5648b5a
  *	0.67f	20/2/04	Added checkClamd() - if possible attempts to see
  *			if clamd has died
bd547be2
  *	0.67g	21/2/04	Don't run if the quarantine-dir is publically accessable
e84162a4
  *	0.67h	22/2/04	Change the log level TCPwrapper denying
  *			Handle ERROR message from clamd
  *			Moved smfi_setconn to avoid race condictions when
  *			an e-mail is received just as the milter is starting
  *			but isn't ready to handle it causing the milter to
  *			go to an error state
  *			Hardend umask
664f9ff6
  *	0.67i	27/2/04	Dropping priv message now same as clamd
  *			Only use TCPwrappers when using TCP/IP to establish
  *			communications with the milter
ecaaaf05
  *	0.67j	27/2/04	Call checkClamd() before attempting to connect, it's
  *			a way of warning the user if they've started the
  *			milter before clamd
  *			checkClamd() now stashes pid in syslog
  *			Ensure installation instructions tally with man page
  *			and put sockets into subdirectory for security
1f7a8360
  *			clamfi_close debug, change assert to debug message
  *			Better way to force TCPwrappers only with TCP/IP
5b6bb93b
  *	0.67k	7/3/04	Ensure cli_dbgmsg's end with \n
  *			Fixed some warning messages with icc
  *			Use cli_[cm]alloc
  *			Included extra information if --headers is given (based
  *			on an idea from "Leonid Zeitlin" <lz@europe.com>
1070b274
  *	0.67l	10/3/04	Use new HAVE_STRERROR_R rather than TARGET_OS_SOLARIS
  *			to determine if strerror_r exists
46a4aaa1
  *	0.70	17/3/04	Up-issued to 0.70
268a4f2d
  *	0.70a	20/3/04	strerror_r is a bit confused on Fedora Linux. The
  *			man page says it returns an int, but the prototype
  *			in string.h says it returns a char *
  *			Say how many bytes can't be written to clamd - it may
  *			give a clue what's wrong
8ac80fb8
  *	0.70b	26/3/04	Display errno information on write failure to clamd
  *			Ensure errno is passed to strerror
  *			Print fd in clamfi_send debug
f7ab4278
  *	0.70c	27/3/04	Timestamp clamfi_send messages
  *			Call cli_warnmsg if ERROR received
  *			Minor code tidy
  *			Delay connection to clamd to handle clamd's appetite
  *			for timing out when the remote end (the end talking to
  *			sendmail) is slow
  *			Prefer cli_dbgmsg/cli_warnmsg over printf
86b3e542
  *	0.70d	29/3/04	Print the sendmail ID with the virus note in syslog
  *			config file location has changed
00727a0e
  *	0.70e	1/4/04	Fix a remote possibility of a file descriptor leak
  *			in PingServer() if clamd has died
  *			Fix by Andrey J. Melnikoff (TEMHOTA) <temnota@kmv.ru>
  *			Corrected some debug messages reported by
  *			Sergey Y. Afonin <asy@kraft-s.ru>
  *	0.70f	1/4/04	Added auto-submitted header to messages generated here
  *			Suggested by "Andrey J. Melnikoff (TEMHOTA)"
  *			<temnota@kmv.ru>
  *			Add advice that --quarantine-dir may improve
  *			performance when LocalSocket is used
  *			ThreadTimeout seems to have been changed to ReadTimeout
96f3d93b
  *	0.70g	3/4/04	Error if ReadTimeout is -ve
  *			Honour StreamMaxLength
5b6bb93b
  *
2cd8b9d4
  * Change History:
  * $Log: clamav-milter.c,v $
658f19f8
  * Revision 1.70  2004/04/06 22:43:43  kojm
  * reverse strlcpy/strlcat patch
  *
db1cae22
  * Revision 1.69  2004/04/06 12:14:52  kojm
  * use strlcpy/strlcat
  *
96f3d93b
  * Revision 1.68  2004/04/03 04:47:22  nigelhorne
  * Honour StreamMaxLength
  *
00727a0e
  * Revision 1.67  2004/04/01 15:34:00  nigelhorne
  * ThreadTimeout has been renamed ReadTimeout
  *
86b3e542
  * Revision 1.66  2004/03/31 20:48:03  nigelhorne
  * Config file has changed
  *
f7ab4278
  * Revision 1.65  2004/03/27 21:44:21  nigelhorne
  * Attempt to handle clamd quick timeout for slow remote sites
  *
8ac80fb8
  * Revision 1.64  2004/03/26 11:10:27  nigelhorne
  * Added debug information
  *
268a4f2d
  * Revision 1.63  2004/03/20 12:30:00  nigelhorne
  * strerror_r is confused on Linux
  *
46a4aaa1
  * Revision 1.62  2004/03/17 19:46:49  nigelhorne
  * Upissue to 0.70@
  *
a2f725f6
  * Revision 1.61  2004/03/15 19:54:12  kojm
  * 0.70-rc
  *
1070b274
  * Revision 1.60  2004/03/10 11:31:03  nigelhorne
  * Use HAVE_STRERROR_R
  *
5b6bb93b
  * Revision 1.59  2004/03/07 15:11:15  nigelhorne
  * Added more information to headers flag
  *
1f7a8360
  * Revision 1.58  2004/03/03 09:14:55  nigelhorne
  * Change way check for TCPwrappers on TCP/IP
  *
ecaaaf05
  * Revision 1.57  2004/02/27 15:27:11  nigelhorne
  * call checkClamd on start
  *
664f9ff6
  * Revision 1.56  2004/02/27 09:23:56  nigelhorne
  * Don't use TCP wrappers when UNIX domain sockets are used
  *
e84162a4
  * Revision 1.55  2004/02/22 22:53:50  nigelhorne
  * Handle ERROR message from clamd
  *
7e10f99b
  * Revision 1.54  2004/02/22 17:27:40  nigelhorne
  * Updated installation instructions now that privileges are dropped
  *
bd547be2
  * Revision 1.53  2004/02/21 11:03:23  nigelhorne
  * Error if quarantine-dir is publically accessable
  *
b5648b5a
  * Revision 1.52  2004/02/20 17:07:24  nigelhorne
  * Added checkClamd
  *
1fcdb893
  * Revision 1.51  2004/02/20 09:50:42  nigelhorne
  * Removed warnings added by new configuration script
  *
f9c88a98
  * Revision 1.50  2004/02/19 10:00:26  nigelhorne
  * Rework TCPWrappers support
  *
fdc6066a
  * Revision 1.49  2004/02/18 13:30:34  nigelhorne
  * Added dont-long-clean argument
  *
055404a5
  * Revision 1.48  2004/02/18 10:06:51  nigelhorne
  * Fix FreeBSD
  *
c54d7329
  * Revision 1.47  2004/02/16 11:55:24  nigelhorne
  * Added clamfi_free which helps with the tidying up
  *
3adce0a6
  * Revision 1.46  2004/02/16 09:39:22  nigelhorne
  * Upissued to 0.67
  *
b2fb75ea
  * Revision 1.45  2004/02/14 17:20:38  nigelhorne
  * Add TCPwrappers support
  *
9e1e77b9
  * Revision 1.44  2004/02/09 11:05:33  nigelhorne
  * Added Hflag
  *
7908713f
  * Revision 1.43  2004/02/07 12:16:20  nigelhorne
  * Added config.h
  *
d4b2e2e4
  * Revision 1.42  2004/02/02 13:44:31  nigelhorne
  * Include the ID of the message when warnings are sent to the postmaster-only
  *
3a03d183
  * Revision 1.41  2004/01/29 12:52:35  nigelhorne
  * Added --noreject flag
  *
952f2560
  * Revision 1.40  2004/01/28 15:55:59  nigelhorne
  * Fixed compilation error with --enable-debug
  *
e576bf42
  * Revision 1.39  2004/01/26 14:12:42  nigelhorne
  * Corrected endian problem (ntohs instead of htons)
  *
44d08756
  * Revision 1.38  2004/01/25 14:23:51  nigelhorne
  * Support multiple clamd servers
  *
0e244102
  * Revision 1.37  2004/01/24 18:09:39  nigelhorne
  * Allow clamd server name as well as IPaddress in -s option
  *
fa2c672a
  * Revision 1.36  2004/01/12 15:30:53  nigelhorne
  * FixStaleSocket no longer complains on ENOENT
  *
fe3d8be8
  * Revision 1.35  2004/01/10 16:22:14  nigelhorne
  * Added OpenBSD instructions and --signature-file
  *
2a2f7ff3
  * Revision 1.34  2003/12/31 14:46:35  nigelhorne
  * Include the sendmail queue ID in the log
  *
51b03ecb
  * Revision 1.33  2003/12/27 17:28:56  nigelhorne
  * Moved --sign data to private area
  *
fa9628f2
  * Revision 1.32  2003/12/22 14:05:31  nigelhorne
  * Added --sign option
  *
8ea7fdd0
  * Revision 1.31  2003/12/13 16:43:21  nigelhorne
  * Upissue
  *
98135801
  * Revision 1.30  2003/12/12 13:42:47  nigelhorne
  * alls to clamfi_cleanup were missing
  *
66ff992e
  * Revision 1.29  2003/12/10 12:00:39  nigelhorne
  * Timeout on waiting for data from clamd
  *
c9af1776
  * Revision 1.28  2003/12/09 09:22:14  nigelhorne
  * Use the location of sendmail discovered by configure
  *
332e6334
  * Revision 1.27  2003/12/05 19:14:07  nigelhorne
  * Set umask; handle unescaped From in mailboxes
  *
2defd014
  * Revision 1.26  2003/12/02 06:37:26  nigelhorne
  * Use setsid if setpgrp not present
  *
668c7570
  * Revision 1.25  2003/11/30 06:12:06  nigelhorne
  * Added --quarantine-dir option
  *
b312f172
  * Revision 1.24  2003/11/29 11:51:19  nigelhorne
  * Fix problem of possible confused pointers if large number of recipients given
  *
2f5f8390
  * Revision 1.23  2003/11/25 05:56:43  nigelhorne
  * Handle empty hostname or hostaddr
  *
3aa15b4c
  * Revision 1.22  2003/11/24 04:48:44  nigelhorne
  * Support AllowSupplementaryGroups
  *
e004f1c5
  * Revision 1.21  2003/11/22 11:47:45  nigelhorne
bd547be2
  * Drop root priviliges and support quarantine
e004f1c5
  *
dad136d5
  * Revision 1.20  2003/11/19 16:32:22  nigelhorne
  * Close cmdSocket earlier
  *
4d0bd1f7
  * Revision 1.19  2003/11/17 04:48:30  nigelhorne
  * Up issue to version 0.65
  *
d3f8fcf7
  * Revision 1.18  2003/11/11 08:19:20  nigelhorne
  * Handle % characters in e-mail addresses
  *
88f28d8c
  * Revision 1.17  2003/11/05 15:41:11  nigelhorne
  * Tidyup pthread_cond_timewait call
  *
68d5a5f3
  * Revision 1.16  2003/10/31 13:33:40  nigelhorne
  * Added dont scan on error flag
  *
198d714a
  * Revision 1.15  2003/10/22 19:44:01  nigelhorne
  * more calls to pthread_cond_broadcast
  *
a4371160
  * Revision 1.14  2003/10/12 08:37:21  nigelhorne
  * Uses VERSION command to get version information
  *
9148ec6d
  * Revision 1.13  2003/10/11 15:42:15  nigelhorne
  * Don't call clamdscan
  *
db035545
  * Revision 1.12  2003/10/05 17:30:04  nigelhorne
  * Only fix old socket when FixStaleSocket is set
  *
429f2e92
  * Revision 1.11  2003/10/05 13:57:47  nigelhorne
  * Fixed handling of MaxThreads
  *
6909adb8
  * Revision 1.10  2003/10/03 11:54:53  nigelhorne
  * Added white list of recipients
  *
b9d3b885
  * Revision 1.9  2003/09/30 11:53:55  nigelhorne
  * clamfi_envfrom was returning EX_TEMPFAIL in some places rather than SMFIS_TEMPFAIL
  *
9e7b0c12
  * Revision 1.8  2003/09/29 06:20:17  nigelhorne
  * max_children now overrides MaxThreads
  *
1c8d6635
  * Revision 1.7  2003/09/29 06:07:49  nigelhorne
  * Ensure remoteIP is set before usage
  *
2cd8b9d4
  * Revision 1.6  2003/09/28 16:37:23  nigelhorne
  * Added -f flag use MaxThreads if --max-children not set
e3aaff8e
  */
658f19f8
 static	char	const	rcsid[] = "$Id: clamav-milter.c,v 1.70 2004/04/06 22:43:43 kojm Exp $";
e3aaff8e
 
96f3d93b
 #define	CM_VERSION	"0.70g"
e3aaff8e
 
 /*#define	CONFDIR	"/usr/local/etc"*/
 
7908713f
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
e3aaff8e
 #include "defaults.h"
86b3e542
 #include "cfgparser.h"
e3aaff8e
 #include "../target.h"
44d08756
 #include "str.h"
1fcdb893
 #include "../libclamav/others.h"
 #include "clamav.h"
e3aaff8e
 
 #ifndef	CL_DEBUG
 #define	NDEBUG
 #endif
 
 #include <stdio.h>
 #include <sysexits.h>
332e6334
 #include <sys/types.h>
 #include <sys/stat.h>
e3aaff8e
 #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>
01ab0124
 #include <regex.h>
 #include <fcntl.h>
e004f1c5
 #include <pwd.h>
3aa15b4c
 #include <grp.h>
0e244102
 #include <netdb.h>
e3aaff8e
 
b2fb75ea
 #ifdef	WITH_TCPWRAP
 #include <tcpd.h>
f9c88a98
 
 int	allow_severity = LOG_DEBUG;
e84162a4
 int	deny_severity = LOG_NOTICE;
f9c88a98
 
b2fb75ea
 #endif
 
44d08756
 #if defined(CL_DEBUG) && defined(C_LINUX)
 #include <sys/resource.h>
 #endif
 
e3aaff8e
 #define _GNU_SOURCE
049a18b9
 #include "getopt.h"
e3aaff8e
 
c9af1776
 #ifndef	SENDMAIL_BIN
 #define	SENDMAIL_BIN	"/usr/lib/sendmail"
 #endif
 
b2fb75ea
 #ifndef HAVE_IN_PORT_T
 typedef	unsigned short	in_port_t;
 #endif
 
e3aaff8e
 /*
  * TODO: optional: xmessage on console when virus stopped (SNMP would be real nice!)
3aa15b4c
  *	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
  *	can use wall(1) in the VirusEvent entry in clamav.conf
e3aaff8e
  * TODO: build with libclamav.so rather than libclamav.a
  * TODO: bounce message should optionally be read from a file
00727a0e
  * TODO: Support LogTime and Logfile from the conf file
7908713f
  * TODO: Warn if TCPAddr doesn't allow connection from us
  * 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.
e3aaff8e
  */
 
9e1e77b9
 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;
 
e3aaff8e
 /*
  * 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 */
668c7570
 	char	*filename;	/* Where to store the message in quarantine */
51b03ecb
 	u_char	*body;		/* body of the message if Sflag is set */
 	size_t	bodyLen;	/* number of bytes in body */
9e1e77b9
 	header_list_t headers;	/* Message headers */
96f3d93b
 	off_t	numBytes;	/* Number of bytes sent so far */
e3aaff8e
 };
 
44d08756
 static	int		pingServer(int serverNumber);
 static	int		findServer(void);
e3aaff8e
 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);
c54d7329
 static	void		clamfi_free(struct privdata *privdata);
e3aaff8e
 static	int		clamfi_send(const struct privdata *privdata, size_t len, const char *format, ...);
 static	char		*strrcpy(char *dest, const char *source);
fe3d8be8
 static	int		clamd_recv(int sock, char *buf, size_t len);
 static	off_t		updateSigFile(void);
9e1e77b9
 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);
f7ab4278
 static	int	connect2clamd(struct privdata *privdata);
b5648b5a
 static	void	checkClamd(void);
e3aaff8e
 
a4371160
 static	char	clamav_version[128];
2cd8b9d4
 static	int	fflag = 0;	/* force a scan, whatever */
e3aaff8e
 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
 				 */
7a9b0f05
 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
 				 */
fa9628f2
 static	int	Sflag = 0;	/*
 				 * Add a signature to each message that
 				 * has been scanned
 				 */
fe3d8be8
 static	const	char	*sigFilename;	/*
 				 * File where the scanned message signature
 				 * can be found
 				 */
668c7570
 static	char	*quarantine;	/*
e004f1c5
 				 * If a virus is found in an email redirect
 				 * it to this account
 				 */
668c7570
 static	char	*quarantine_dir; /*
 				 * Path to store messages before scanning.
 				 * Infected ones will be left there.
 				 */
7418fb74
 static	int	nflag = 0;	/*
 				 * Don't add X-Virus-Scanned to header. Patch
 				 * from Dirk Meyer <dirk.meyer@dinoex.sub.org>
 				 */
3a03d183
 static	int	rejectmail = 1;	/*
 				 * Send a 550 rejection when a virus is
 				 * found
 				 */
9e1e77b9
 static	int	hflag = 0;	/*
 				 * Include original message headers in
 				 * report
 				 */
68d5a5f3
 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>
 				 */
96f3d93b
 static	int	readTimeout = CL_DEFAULT_SCANTIMEOUT; /*
66ff992e
 				 * number of seconds to wait for clamd to
00727a0e
 				 * respond, see ReadTimeout in clamav.conf
66ff992e
 				 */
96f3d93b
 static	off_t	streamMaxLength = -1;	/* StreamMaxLength from clamav.conf */
fdc6066a
 static	int	logClean = 1;	/*
 				 * Add clean items to the log file
 				 */
fe3d8be8
 static	char	*signature = "-- \nScanned by ClamAv - http://www.clamav.net\n";
 static	time_t	signatureStamp;
e3aaff8e
 
 #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;
44d08756
 short	use_syslog = 0;
b5648b5a
 static	const	char	*pidFile;
e3aaff8e
 static	int	logVerbose = 0;
 static	struct	cfgstruct	*copt;
664f9ff6
 static	const	char	*localSocket;	/* milter->clamd comms */
 static	in_port_t	tcpSocket;	/* milter->clamd comms */
 static	char	*port = NULL;	/* sendmail->milter comms */
44d08756
 static	const	char	*serverHostNames = "127.0.0.1";
 static	long	*serverIPs;	/* IPv4 only */
 static	int	numServers;	/* numer of elements in serverIPs */
049a18b9
 static	const	char	*postmaster = "postmaster";
e3aaff8e
 
3aa15b4c
 /*
0e244102
  * Whitelist of source e-mail addresses that we do NOT scan
3aa15b4c
  * TODO: read in from a file
  */
6909adb8
 static	const	char	*ignoredEmailAddresses[] = {
 	/*"Mailer-Daemon@bandsman.co.uk",
 	"postmaster@bandsman.co.uk",*/
 	NULL
 };
 
e3aaff8e
 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.");
fdc6066a
 	puts("\t--debug\t\t\t-D\tPrint debug messages.");
 	puts("\t--dont-log-clean\t-C\tDon't add an entry to syslog that a mail is clean.");
e004f1c5
 	puts("\t--dont-scan-on-error\t-d\tPass e-mails through unscanned if a system error occurs.");
 	puts("\t--force-scan\t\t-f\tForce scan all messages (overrides (-o and -l).");
e3aaff8e
 	puts("\t--help\t\t\t-h\tThis message.");
9e1e77b9
 	puts("\t--headers\t\t-H\tInclude original message headers in the report.");
e3aaff8e
 	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.");
3a03d183
 	puts("\t--noreject\t\t-N\tDon't reject viruses, silently throw them away.");
4d0bd1f7
 	puts("\t--noxheader\t\t-n\tSuppress X-Virus-Scanned header.");
3aa15b4c
 	puts("\t--postmaster\t\t-p EMAIL\tPostmaster address [default=postmaster].");
e004f1c5
 	puts("\t--postmaster-only\t-P\tSend warnings only to the postmaster.");
7a9b0f05
 	puts("\t--quiet\t\t\t-q\tDon't send e-mail notifications of interceptions.");
3aa15b4c
 	puts("\t--quarantine=USER\t-Q EMAIL\tQuanrantine e-mail account.");
668c7570
 	puts("\t--quarantine-dir=DIR\t-U DIR\tDirectory to store infected emails.");
fdc6066a
 	puts("\t--server=SERVER\t\t-s SERVER\tHostname/IP address of server(s) running clamd (when using TCPsocket).");
fe3d8be8
 	puts("\t--sign\t\t\t-S\tAdd a hard-coded signature to each scanned message.");
 	puts("\t--signature-file\t-F\tLocation of signature file.");
e3aaff8e
 	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;
 	const char *cfgfile = CL_DEFAULT_CFG;
 	struct cfgstruct *cpt;
668c7570
 	struct passwd *user;
e3aaff8e
 	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 */
 	};
 
44d08756
 #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
a4371160
 	/*
 	 * Temporarily enter guessed value into clamav_version, will
 	 * be overwritten later by the value returned by clamd
 	 */
9148ec6d
 	snprintf(clamav_version, sizeof(clamav_version),
 		"ClamAV version %s, clamav-milter version %s",
 		VERSION, CM_VERSION);
 
e3aaff8e
 	for(;;) {
 		int opt_index = 0;
 #ifdef	CL_DEBUG
fdc6066a
 		const char *args = "bc:CDfF:lm:nNop:PqQ:dhHs:SU:Vx:";
e3aaff8e
 #else
fdc6066a
 		const char *args = "bc:CDfF:lm:nNop:PqQ:dhHs:SU:V";
e3aaff8e
 #endif
e004f1c5
 
e3aaff8e
 		static struct option long_options[] = {
 			{
 				"bounce", 0, NULL, 'b'
 			},
 			{
 				"config-file", 1, NULL, 'c'
 			},
 			{
fdc6066a
 				"dont-log-clean", 0, NULL, 'C'
 			},
 			{
68d5a5f3
 				"dont-scan-on-error", 0, NULL, 'd'
 			},
 			{
44d08756
 				"debug", 0, NULL, 'D'
 			},
 			{
668c7570
 				"force-scan", 0, NULL, 'f'
2cd8b9d4
 			},
 			{
5b6bb93b
 				"headers", 0, NULL, 'H'
9e1e77b9
 			},
 			{
e3aaff8e
 				"help", 0, NULL, 'h'
 			},
 			{
 				"local", 0, NULL, 'l'
 			},
 			{
3a03d183
 				"noreject", 0, NULL, 'N'
 			},
 			{
7418fb74
 				"noxheader", 0, NULL, 'n'
 			},
 			{
e3aaff8e
 				"outgoing", 0, NULL, 'o'
 			},
 			{
3aa15b4c
 				"postmaster", 1, NULL, 'p'
049a18b9
 			},
 			{
7a9b0f05
 				"postmaster-only", 0, NULL, 'P',
 			},
 			{
 				"quiet", 0, NULL, 'q'
 			},
 			{
e004f1c5
 				"quarantine", 1, NULL, 'Q',
 			},
 			{
668c7570
 				"quarantine-dir", 1, NULL, 'U',
 			},
 			{
e3aaff8e
 				"max-children", 1, NULL, 'm'
 			},
 			{
 				"server", 1, NULL, 's'
 			},
 			{
fe3d8be8
 				"sign", 0, NULL, 'S'
 			},
 			{
 				"signature-file", 1, NULL, 'F'
fa9628f2
 			},
 			{
e3aaff8e
 				"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;
fdc6066a
 			case 'C':	/* dont log clean */
 				logClean = 0;
 				break;
68d5a5f3
 			case 'd':	/* don't scan on error */
 				cl_error = SMFIS_ACCEPT;
 				break;
44d08756
 			case 'D':	/* enable debug messages */
 				cl_debug();
 				break;
2cd8b9d4
 			case 'f':	/* force the scan */
 				fflag++;
 				break;
e3aaff8e
 			case 'h':
 				help();
 				return EX_OK;
9e1e77b9
 			case 'H':
 				hflag++;
 				break;
e3aaff8e
 			case 'l':	/* scan mail from the lan */
 				lflag++;
 				break;
049a18b9
 			case 'm':	/* maximum number of children */
 				max_children = atoi(optarg);
 				break;
7418fb74
 			case 'n':	/* don't add X-Virus-Scanned */
 				nflag++;
e004f1c5
 				smfilter.xxfi_flags &= ~SMFIF_ADDHDRS;
7418fb74
 				break;
3a03d183
 			case 'N':	/* Do we reject mail or silently drop it */
 				rejectmail = 0;
 				break;
e3aaff8e
 			case 'o':	/* scan outgoing mail */
 				oflag++;
 				break;
049a18b9
 			case 'p':	/* postmaster e-mail address */
 				postmaster = optarg;
e3aaff8e
 				break;
7a9b0f05
 			case 'P':	/* postmaster only */
 				pflag++;
 				break;
 			case 'q':	/* send NO notification email */
 				qflag++;
 				break;
e004f1c5
 			case 'Q':	/* quarantine e-mail address */
 				quarantine = optarg;
 				smfilter.xxfi_flags |= SMFIF_CHGHDRS|SMFIF_ADDRCPT|SMFIF_DELRCPT;
 				break;
e3aaff8e
 			case 's':	/* server running clamd */
44d08756
 				serverHostNames = optarg;
e3aaff8e
 				break;
fe3d8be8
 			case 'F':	/* signature file */
 				sigFilename = optarg;
 				signature = NULL;
 				/* fall through */
fa9628f2
 			case 'S':	/* sign */
 				smfilter.xxfi_flags |= SMFIF_CHGBODY;
 				Sflag++;
 				break;
668c7570
 			case 'U':	/* quarantine path */
 				quarantine_dir = optarg;
 				break;
e3aaff8e
 			case 'V':
9148ec6d
 				puts(clamav_version);
e3aaff8e
 				return EX_OK;
 #ifdef	CL_DEBUG
 			case 'x':
 				debug_level = atoi(optarg);
 				break;
 #endif
 			default:
 #ifdef	CL_DEBUG
44d08756
 				fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-s SERVER] [-S] [-x#] [-U PATH] socket-addr\n", argv[0]);
e3aaff8e
 #else
44d08756
 				fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-s SERVER] [-S] [-U PATH] socket-addr\n", argv[0]);
e3aaff8e
 #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;
 	}
 
3aa15b4c
 	/*
 	 * Drop privileges
 	 */
 	if(getuid() == 0) {
 		if((cpt = cfgopt(copt, "User")) != NULL) {
 			if((user = getpwnam(cpt->strarg)) == NULL) {
 				fprintf(stderr, "%s: Can't get information about user %s\n", argv[0], cpt->strarg);
 				return EX_CONFIG;
 			}
e004f1c5
 
3aa15b4c
 			if(cfgopt(copt, "AllowSupplementaryGroups"))
 				initgroups(cpt->strarg, user->pw_gid);
 			else
 				setgroups(1, &user->pw_gid);
 
 			setgid(user->pw_gid);
 			setuid(user->pw_uid);
664f9ff6
 
 			cli_dbgmsg("Running as user %s (UID %d, GID %d)\n",
 				cpt->strarg, user->pw_uid, user->pw_gid);
3aa15b4c
 		} else
 			fprintf(stderr, "%s: running as root is not recommended\n", argv[0]);
 	}
bd547be2
 	if(quarantine_dir) {
 		struct stat statb;
 
 		if(access(quarantine_dir, W_OK) < 0) {
 			perror(quarantine_dir);
 			return EX_CONFIG;
 		}
 		if(stat(quarantine_dir, &statb) < 0) {
 			perror(quarantine_dir);
 			return EX_CONFIG;
 		}
 		/*
 		 * Quit if the quarantine directory is publically readable
 		 * or writeable
 		 */
 		if(statb.st_mode & 077) {
 			fprintf(stderr, "%s: unsafe quarantine directory %s\n",
 				argv[0], quarantine_dir);
 			return EX_CONFIG;
 		}
668c7570
 	}
e004f1c5
 
fe3d8be8
 	if(sigFilename && !updateSigFile())
 		return EX_USAGE;
 
e3aaff8e
 	if(!cfgopt(copt, "StreamSaveToDisk")) {
 		fprintf(stderr, "%s: StreamSavetoDisk not enabled in %s\n",
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 
 	if(!cfgopt(copt, "ScanMail")) {
96f3d93b
 		/*
 		 * In fact ScanMail isn't needed if this machine doesn't run
 		 * clamd.
 		 */
e3aaff8e
 		fprintf(stderr, "%s: ScanMail not enabled in %s\n",
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 
 	/*
2cd8b9d4
 	 * 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
 	 */
9e7b0c12
 	if((max_children == 0) && ((cpt = cfgopt(copt, "MaxThreads")) != NULL))
429f2e92
 		max_children = cpt->numarg;
2cd8b9d4
 
00727a0e
 	if((cpt = cfgopt(copt, "ReadTimeout")) != NULL) {
96f3d93b
 		readTimeout = cpt->numarg;
66ff992e
 
96f3d93b
 		if(readTimeout < 0) {
00727a0e
 			fprintf(stderr, "%s: ReadTimeout must not be negative in %s\n",
66ff992e
 				argv[0], cfgfile);
96f3d93b
 			return EX_CONFIG;
66ff992e
 		}
 	}
96f3d93b
 	if((cpt = cfgopt(copt, "StreamMaxLength")) != NULL) {
 		if(cpt->numarg < 0) {
 			fprintf(stderr, "%s: StreamMaxLength must not be negative in %s\n",
 				argv[0], cfgfile);
 			return EX_CONFIG;
 		}
 		streamMaxLength = cpt->numarg;
 	}
2cd8b9d4
 	/*
e3aaff8e
 	 * Get the outgoing socket details - the way to talk to clamd
 	 */
a4371160
 	if((cpt = cfgopt(copt, "LocalSocket")) != NULL) {
 		if(cfgopt(copt, "TCPSocket") != NULL) {
 			fprintf(stderr, "%s: You can select one server type only (local/TCP) in %s\n",
 				argv[0], cfgfile);
 			return EX_CONFIG;
 		}
e3aaff8e
 		/*
 		 * TODO: check --server hasn't been set
 		 */
 		localSocket = cpt->strarg;
44d08756
 		if(!pingServer(-1)) {
a4371160
 			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;
 		}
00727a0e
 		if(quarantine_dir == NULL)
 			fprintf(stderr, "When using Localsocket in %s\nyou may improve performance if you use the --quarantine_dir option\n", cfgfile);
 
e84162a4
 		umask(077);
44d08756
 
 		serverIPs = (long *)cli_malloc(sizeof(long));
 		serverIPs[0] = inet_addr("127.0.0.1");
a4371160
 	} else if((cpt = cfgopt(copt, "TCPSocket")) != NULL) {
44d08756
 		int i;
 
e3aaff8e
 		/*
 		 * TCPSocket is in fact a port number not a full socket
 		 */
668c7570
 		if(quarantine_dir) {
 			fprintf(stderr, "%s: --quarantine-dir not supported for remote scanning - use --quarantine\n", argv[0]);
 			return EX_CONFIG;
 		}
 
5b6bb93b
 		tcpSocket = (in_port_t)cpt->numarg;
44d08756
 
0e244102
 		/*
44d08756
 		 * cli_strtok's fieldno counts from 0
0e244102
 		 */
44d08756
 		for(;;) {
 			char *hostname;
0e244102
 
44d08756
 			hostname = cli_strtok(serverHostNames, numServers, ":");
 			if(hostname == NULL)
 				break;
 			numServers++;
 			free(hostname);
0e244102
 		}
 
44d08756
 		cli_dbgmsg("numServers: %d\n", numServers);
668c7570
 
44d08756
 		serverIPs = (long *)cli_malloc(numServers * sizeof(long));
 
 		for(i = 0; i < numServers; i++) {
 			char *hostname;
 
 			/*
 			 * Translate server's name to IP address
 			 */
 			hostname = cli_strtok(serverHostNames, i, ":");
 			serverIPs[i] = inet_addr(hostname);
 			if(serverIPs[i] == -1L) {
 				const struct hostent *h = gethostbyname(hostname);
 
 				if(h == NULL) {
 					fprintf(stderr, "%s: Unknown host %s\n",
 						argv[0], hostname);
 					return EX_USAGE;
 				}
 
 				memcpy((char *)&serverIPs[i], h->h_addr, sizeof(serverIPs[i]));
 			}
 
 			if(!pingServer(i)) {
 				fprintf(stderr, "Can't talk to clamd server %s on port %d\n",
 					hostname, tcpSocket);
 				fprintf(stderr, "Check your entry for TCPSocket in %s\n",
 					cfgfile);
 				return EX_CONFIG;
 			}
 			free(hostname);
e3aaff8e
 		}
 	} else {
 		fprintf(stderr, "%s: You must select server type (local/TCP) in %s\n",
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 
a4371160
 	if(!cfgopt(copt, "Foreground")) {
fe3d8be8
 
 #ifdef	CL_DEBUG
 		printf("When debugging it is recommended that you use Foreground mode in %s\n", cfgfile);
 		puts("So that you can see all of the messages");
 #endif
 
e3aaff8e
 		switch(fork()) {
 			case -1:
 				perror("fork");
 				return EX_TEMPFAIL;
 			case 0:	/* child */
 				break;
 			default:	/* parent */
 				return EX_OK;
 		}
a4371160
 		close(0);
 		close(1);
 		close(2);
 		open("/dev/null", O_RDONLY);
 		if(open("/dev/console", O_WRONLY) == 1)
 			dup(1);
2defd014
 #ifdef HAVE_SETPGRP
 #ifdef SETPGRP_VOID
dad136d5
 		setpgrp();
2defd014
 #else
 		setpgrp(0,0);
 #endif
 #else
 #ifdef HAVE_SETSID
 		 setsid();
 #endif
 #endif
a4371160
 	}
e3aaff8e
 
b5648b5a
 	if((cpt = cfgopt(copt, "PidFile")) != NULL)
 		pidFile = cpt->strarg;
 
e3aaff8e
 	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;
7a9b0f05
 	} else {
 		if(qflag)
 			fprintf(stderr, "%s: (-q && !LogSysLog): warning - all interception message methods are off\n",
 				argv[0]);
e3aaff8e
 		use_syslog = 0;
7a9b0f05
 	}
e3aaff8e
 
db035545
 	if(cfgopt(copt, "FixStaleSocket")) {
 		/*
 		 * Get the incoming socket details - the way sendmail talks to
 		 * us
 		 *
3aa15b4c
 		 * TODO: There's a security problem here that'll need fixing if
 		 * the User entry of clamav.conf is not used
db035545
 		 */
 		if(strncasecmp(port, "unix:", 5) == 0) {
 			if(unlink(&port[5]) < 0)
fa2c672a
 				if(errno != ENOENT)
 					perror(&port[5]);
db035545
 		} else if(strncasecmp(port, "local:", 6) == 0) {
 			if(unlink(&port[6]) < 0)
fa2c672a
 				if(errno != ENOENT)
 					perror(&port[6]);
db035545
 		}
 	}
e3aaff8e
 
e84162a4
 	if(smfi_setconn(port) == MI_FAILURE) {
 		fprintf(stderr, "%s: smfi_setconn failed\n",
 			argv[0]);
 		return EX_SOFTWARE;
 	}
 
e3aaff8e
 	if(smfi_register(smfilter) == MI_FAILURE) {
f9c88a98
 		cli_errmsg("smfi_register failure\n");
e3aaff8e
 		return EX_UNAVAILABLE;
 	}
 
 	signal(SIGPIPE, SIG_IGN);
 
 	return smfi_main();
 }
 
 /*
  * Verify that the server is where we think it is
  * Returns true or false
44d08756
  *
  * serverNumber counts from 0
e3aaff8e
  */
 static int
44d08756
 pingServer(int serverNumber)
e3aaff8e
 {
a4371160
 	char *ptr;
e3aaff8e
 	int sock, nbytes;
a4371160
 	char buf[128];
e3aaff8e
 
a4371160
 	if(localSocket) {
 		struct sockaddr_un server;
e3aaff8e
 
a4371160
 		memset((char *)&server, 0, sizeof(struct sockaddr_un));
 		server.sun_family = AF_UNIX;
658f19f8
 		strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
a4371160
 
 		if((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
 			perror("socket");
 			return 0;
 		}
ecaaaf05
 		checkClamd();
a4371160
 		if(connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
 			perror(localSocket);
f7ab4278
 			close(sock);
a4371160
 			return 0;
 		}
 	} else {
 		struct sockaddr_in server;
 
 		memset((char *)&server, 0, sizeof(struct sockaddr_in));
 		server.sin_family = AF_INET;
5b6bb93b
 		server.sin_port = (in_port_t)htons(tcpSocket);
 
952f2560
 		assert(serverIPs != NULL);
44d08756
 		assert(serverIPs[0] != -1L);
0e244102
 
44d08756
 		server.sin_addr.s_addr = serverIPs[serverNumber];
a4371160
 
 		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");
00727a0e
 			close(sock);
a4371160
 			return 0;
 		}
e3aaff8e
 	}
a4371160
 
 	/*
 	 * 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"
 	 */
 	if(send(sock, "VERSION\n", 8, 0) < 8) {
e3aaff8e
 		perror("send");
 		close(sock);
 		return 0;
 	}
 
 	shutdown(sock, SHUT_WR);
 
66ff992e
 	nbytes = clamd_recv(sock, buf, sizeof(buf));
e3aaff8e
 
 	close(sock);
 
 	if(nbytes < 0) {
 		perror("recv");
 		return 0;
 	}
66ff992e
 	if(nbytes == 0)
 		return 0;
 
e3aaff8e
 	buf[nbytes] = '\0';
 
a4371160
 	/* Remove the trailing new line from the reply */
 	if((ptr = strchr(buf, '\n')) != NULL)
 		*ptr = '\0';
 
 	/*
 	 * No real validation is done here
44d08756
 	 *
 	 * TODO: When connecting to more than one server, give a warning
 	 *	if they're running different versions, or if the virus DBs
 	 *	are out of date
a4371160
 	 */
 	snprintf(clamav_version, sizeof(clamav_version),
b2fb75ea
 		"%s, clamav-milter version %s",
a4371160
 		buf, CM_VERSION);
e004f1c5
 
a4371160
 	return 1;
e3aaff8e
 }
 
44d08756
 /*
  * Find the best server to connect to. No intelligence to this, if one
  * of the servers goes down it'll be silently ignored, which is probably
  * ok. It is best to weight the order of the servers from most wanted to
  * least wanted
  *
  * Return value is from 0 - index into serverIPs
  */
 static int
 findServer(void)
 {
 	struct sockaddr_in *servers, *server;
 	int *socks, maxsock = 0, i;
 	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);
 
 	for(i = 0, server = servers; i < numServers; i++, server++) {
 		int sock;
 
 		server->sin_family = AF_INET;
5b6bb93b
 		server->sin_port = (in_port_t)htons(tcpSocket);
44d08756
 		server->sin_addr.s_addr = serverIPs[i];
 
 		sock = socks[i] = socket(AF_INET, SOCK_STREAM, 0);
 		if(sock < 0) {
 			perror("socket");
 			do
 				if(socks[i] >= 0)
 					close(socks[i]);
 			while(--i >= 0);
 			free(socks);
 			free(servers);
 			return 0;	/* Use the first server on failure */
 		}
 
 		if((connect(sock, (struct sockaddr *)server, sizeof(struct sockaddr)) < 0) ||
 		   (send(sock, "PING\n", 5, 0) < 5)) {
 			cli_errmsg("findServer: Check server %d - it may be down\n", i);
 			if(use_syslog)
 				syslog(LOG_WARNING, "findServer: Check server %d - it may be down", i);
 			socks[i] = -1;
f7ab4278
 			close(sock);
44d08756
 			continue;
 		}
 
 		shutdown(sock, SHUT_WR);
 
 		FD_SET(sock, &rfds);
 		if(sock > maxsock)
 			maxsock = sock;
 	}
 
 	free(servers);
 
96f3d93b
 	tv.tv_sec = readTimeout;
44d08756
 	tv.tv_usec = 0;
5b6bb93b
 
44d08756
 	retval = select(maxsock, &rfds, NULL, NULL, &tv);
 	if(retval < 0)
 		perror("select");
 
 	for(i = 0; i < numServers; i++)
 		if(socks[i] >= 0)
 			close(socks[i]);
 
 	if(retval == 0) {
 		free(socks);
 		cli_dbgmsg("findServer: No response from any server\n");
 		if(use_syslog)
 			syslog(LOG_WARNING, "findServer: No response from any server");
 		return 0;
 	} else if(retval < 0) {
 		free(socks);
 		if(use_syslog)
 			syslog(LOG_ERR, "findServer: select failed\n");
 		return 0;
 	}
 
 	for(i = 0; i < numServers; i++)
 		if(FD_ISSET(socks[i], &rfds)) {
 			free(socks);
5b6bb93b
 			cli_dbgmsg("findServer: using server %d\n", i);
44d08756
 			return i;
 		}
 
 	free(socks);
 	cli_dbgmsg("findServer: No response from any server\n");
 	if(use_syslog)
 		syslog(LOG_WARNING, "findServer: No response from any server");
 	return 0;
 }
 
f7ab4278
 /*
  * Sendmail wants to establish a connection to us
  */
e3aaff8e
 static sfsistat
 clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
f7ab4278
 {
f9c88a98
 	char ip[INET_ADDRSTRLEN];	/* IPv4 only */
055404a5
 	char *remoteIP;
e3aaff8e
 
2f5f8390
 	if(hostname == NULL) {
 		if(use_syslog)
 			syslog(LOG_ERR, "clamfi_connect: hostname is null");
 		return cl_error;
 	}
 	if(hostaddr == NULL) {
 		if(use_syslog)
 			syslog(LOG_ERR, "clamfi_connect: hostaddr is null");
 		return cl_error;
 	}
 
f9c88a98
 	remoteIP = (char *)inet_ntop(AF_INET, &((struct sockaddr_in *)(hostaddr))->sin_addr, ip, sizeof(ip));
2f5f8390
 
 	if(remoteIP == NULL) {
 		if(use_syslog)
 			syslog(LOG_ERR, "clamfi_connect: remoteIP is null");
 		return cl_error;
 	}
1c8d6635
 
01ab0124
 #ifdef	CL_DEBUG
2a2f7ff3
 	if(debug_level >= 4) {
 		if(use_syslog)
 			syslog(LOG_NOTICE, "clamfi_connect: connection from %s [%s]", hostname, remoteIP);
f9c88a98
 		cli_dbgmsg("clamfi_connect: connection from %s [%s]\n", hostname, remoteIP);
2a2f7ff3
 	}
01ab0124
 #endif
e3aaff8e
 
055404a5
 #ifdef	WITH_TCPWRAP
 	/*
 	 * Support /etc/hosts.allow and /etc/hosts.deny
 	 */
1f7a8360
 	if(strncasecmp(port, "inet:", 5) == 0) {
664f9ff6
 		const char *hostmail;
 		const struct hostent *hp = NULL;
f9c88a98
 
5b6bb93b
 		/*
664f9ff6
 		 * Using TCP/IP for the sendmail->clamav-milter connection
 		 */
 		if((hostmail = smfi_getsymval(ctx, "{if_name}")) == NULL) {
 			if(use_syslog)
 				syslog(LOG_WARNING, "Can't get sendmail hostname");
 			hostmail = "unknown";
 		}
f9c88a98
 
664f9ff6
 		if((hp = gethostbyname(hostmail)) == NULL) {
 			if(use_syslog)
 				syslog(LOG_WARNING, "Access Denied: Host Unknown (%s)", hostname);
 			return SMFIS_TEMPFAIL;
 		}
f9c88a98
 
664f9ff6
 		strcpy(ip, (char *)inet_ntoa(*(struct in_addr *)hp->h_addr));
 
 		/*
 		 * Ask is this is a allowed name or IP number
 		 */
 		if(!hosts_ctl("clamav-milter", hp->h_name, ip, STRING_UNKNOWN)) {
 			if(use_syslog)
 				syslog(LOG_WARNING, "Access Denied for %s[%s]", hp->h_name, ip);
 			return SMFIS_TEMPFAIL;
 		}
055404a5
 	}
 #endif
 
2cd8b9d4
 	if(fflag)
 		/*
 		 * Patch from "Richard G. Roberto" <rgr@dedlegend.com>
 		 * Always scan whereever the message is from
 		 */
 		return SMFIS_CONTINUE;
 
e3aaff8e
 	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");
5b6bb93b
 			cli_dbgmsg("clamfi_connect: not scanning outgoing messages\n");
e3aaff8e
 #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");
68d5a5f3
 				return cl_error;
e3aaff8e
 			}
 
 			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");
5b6bb93b
 				cli_dbgmsg("clamfi_connect: not scanning outgoing messages\n");
e3aaff8e
 #endif
 				return SMFIS_ACCEPT;
 			}
 		}
 	}
b2fb75ea
 
e3aaff8e
 	return SMFIS_CONTINUE;
 }
 
 static sfsistat
 clamfi_envfrom(SMFICTX *ctx, char **argv)
 {
 	struct privdata *privdata;
 
 	if(logVerbose)
 		syslog(LOG_DEBUG, "clamfi_envfrom: %s", argv[0]);
 
f7ab4278
 	cli_dbgmsg("clamfi_envfrom: %s\n", argv[0]);
e3aaff8e
 
 	if(max_children > 0) {
f7ab4278
 		int rc = 0;
e3aaff8e
 
 		pthread_mutex_lock(&n_children_mutex);
 
88f28d8c
 		/*
 		 * Not a while since sendmail doesn't like it if we
 		 * take too long replying. Effectively this means that
 		 * max_children is more of a hint than a rule
 		 */
 		if(n_children >= max_children) {
e3aaff8e
 			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);
88f28d8c
 			do
 				rc = pthread_cond_timedwait(&n_children_cond, &n_children_mutex, &timeout);
 			while(rc != ETIMEDOUT);
e3aaff8e
 		}
 		n_children++;
 
f7ab4278
 		cli_dbgmsg(">n_children = %d\n", n_children);
e3aaff8e
 		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");
 #endif
5b6bb93b
 			cli_dbgmsg("Timeout waiting for a child to die\n");
e3aaff8e
 		}
 	}
 
c54d7329
 	privdata = (struct privdata *)cli_calloc(1, sizeof(struct privdata));
e3aaff8e
 	privdata->dataSocket = -1;	/* 0.4 */
 	privdata->cmdSocket = -1;	/* 0.4 */
 
 	privdata->from = strdup(argv[0]);
 
96f3d93b
 	if(streamMaxLength > 0)
 		privdata->numBytes = strlen(argv[0]) + 6;
 
 	if(hflag)
 		privdata->headers = header_list_new();
c54d7329
 
 	if(smfi_setpriv(ctx, privdata) == MI_SUCCESS)
 		return SMFIS_CONTINUE;
 
 	clamfi_free(privdata);
9e1e77b9
 
c54d7329
 	return cl_error;
e3aaff8e
 }
 
 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]);
 
f7ab4278
 	cli_dbgmsg("clamfi_envrcpt: %s\n", argv[0]);
e3aaff8e
 
 	if(privdata->to == NULL) {
5b6bb93b
 		privdata->to = cli_malloc(sizeof(char *) * 2);
e3aaff8e
 
 		assert(privdata->numTo == 0);
 	} else
8ac80fb8
 		privdata->to = cli_realloc(privdata->to, sizeof(char *) * (privdata->numTo + 2));
e3aaff8e
 
 	privdata->to[privdata->numTo] = strdup(argv[0]);
 	privdata->to[++privdata->numTo] = NULL;
 
96f3d93b
 	if(streamMaxLength > 0)
 		privdata->numBytes += strlen(argv[0]) + 4;
 
e3aaff8e
 	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)
f7ab4278
 		cli_dbgmsg("clamfi_header: %s: %s\n", headerf, headerv);
e3aaff8e
 	else
5b6bb93b
 		cli_dbgmsg("clamfi_header\n");
e3aaff8e
 #endif
 
f7ab4278
 	if(privdata->dataSocket == -1)
 		/*
 		 * First header - make connection with clamd
 		 */
 		if(!connect2clamd(privdata)) {
 			clamfi_cleanup(ctx);
 			return cl_error;
 		}
 
e3aaff8e
 	if(clamfi_send(privdata, 0, "%s: %s\n", headerf, headerv) < 0) {
 		clamfi_cleanup(ctx);
68d5a5f3
 		return cl_error;
e3aaff8e
 	}
9e1e77b9
 
96f3d93b
 	if(streamMaxLength > 0)
 		privdata->numBytes += strlen(headerf) + strlen(headerv) + 3;
 
9e1e77b9
 	if(hflag)
5b6bb93b
 		header_list_add(privdata->headers, headerf, headerv);
9e1e77b9
 
e3aaff8e
 	return SMFIS_CONTINUE;
 }
 
 static sfsistat
 clamfi_eoh(SMFICTX *ctx)
 {
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
6909adb8
 	char **to;
e3aaff8e
 
 	if(logVerbose)
 		syslog(LOG_DEBUG, "clamfi_eoh");
 #ifdef	CL_DEBUG
f7ab4278
 	if(debug_level >= 4)
 		cli_dbgmsg("clamfi_eoh\n");
e3aaff8e
 #endif
 
f7ab4278
 	if(privdata->dataSocket == -1)
 		/*
 		 * No headers - make connection with clamd
 		 */
 		if(!connect2clamd(privdata)) {
 			clamfi_cleanup(ctx);
 			return cl_error;
 		}
 
e3aaff8e
 	if(clamfi_send(privdata, 1, "\n") < 0) {
 		clamfi_cleanup(ctx);
68d5a5f3
 		return cl_error;
e3aaff8e
 	}
96f3d93b
 	if(streamMaxLength > 0)
 		privdata->numBytes++;
e3aaff8e
 
6909adb8
 	/*
 	 * 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)
00727a0e
 		syslog(LOG_NOTICE, "clamfi_eoh: ignoring whitelisted message");
6909adb8
 #ifdef	CL_DEBUG
00727a0e
 	cli_dbgmsg("clamfi_eoh: not scanning outgoing messages\n");
6909adb8
 #endif
 	clamfi_cleanup(ctx);
 
 	return SMFIS_ACCEPT;
e3aaff8e
 }
 
 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
f7ab4278
 	cli_dbgmsg("clamfi_envbody: %u bytes\n", len);
e3aaff8e
 #endif
 
96f3d93b
 	if(streamMaxLength > 0) {
 		privdata->numBytes += len;
 		if(privdata->numBytes > streamMaxLength) {
 			if(use_syslog)
 				syslog(LOG_NOTICE, "%s: Message more than StreamMaxLength (%ld) bytes - not scanned\n",
 					smfi_getsymval(ctx, "i"),
 					streamMaxLength);
 			clamfi_cleanup(ctx);	/* not needed, but just to be safe */
 			return SMFIS_ACCEPT;
 		}
 	}
e3aaff8e
 	if(clamfi_send(privdata, len, (char *)bodyp) < 0) {
96f3d93b
 		clamfi_cleanup(ctx);	/* not needed, but just to be safe */
68d5a5f3
 		return cl_error;
e3aaff8e
 	}
fa9628f2
 	if(Sflag) {
51b03ecb
 		if(privdata->body) {
 			assert(privdata->bodyLen > 0);
 			privdata->body = realloc(privdata->body, privdata->bodyLen + len);
 			memcpy(&privdata->body[privdata->bodyLen], bodyp, len);
 			privdata->bodyLen += len;
fa9628f2
 		} else {
51b03ecb
 			assert(privdata->bodyLen == 0);
5b6bb93b
 			privdata->body = cli_malloc(len);
51b03ecb
 			memcpy(privdata->body, bodyp, len);
 			privdata->bodyLen = len;
fa9628f2
 		}
 	}
e3aaff8e
 	return SMFIS_CONTINUE;
 }
 
 static sfsistat
 clamfi_eom(SMFICTX *ctx)
 {
 	int rc = SMFIS_CONTINUE;
 	char *ptr;
86b3e542
 	const char *sendmailId;
e3aaff8e
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
 	char mess[128];
 
 	if(logVerbose)
 		syslog(LOG_DEBUG, "clamfi_eom");
 #ifdef	CL_DEBUG
5b6bb93b
 	cli_dbgmsg("clamfi_eom\n");
e3aaff8e
 	assert(privdata != NULL);
668c7570
 	assert((privdata->cmdSocket >= 0) || (privdata->filename != NULL));
 	assert(!((privdata->cmdSocket >= 0) && (privdata->filename != NULL)));
e3aaff8e
 	assert(privdata->dataSocket >= 0);
 #endif
 
 	close(privdata->dataSocket);
 	privdata->dataSocket = -1;
 
668c7570
 	if(quarantine_dir != NULL) {
 		char cmdbuf[1024];
 		/*
 		 * Create socket to talk to clamd.
 		 */
 		struct sockaddr_un server;
 		int nbytes;
 
 		assert(localSocket != NULL);
 
 		memset((char *)&server, 0, sizeof(struct sockaddr_un));
 		server.sun_family = AF_UNIX;
658f19f8
 		strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
668c7570
 
 		if((privdata->cmdSocket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
 			perror("socket");
98135801
 			clamfi_cleanup(ctx);
668c7570
 			return cl_error;
 		}
 		if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
 			perror(localSocket);
98135801
 			clamfi_cleanup(ctx);
668c7570
 			return cl_error;
 		}
 
 		snprintf(cmdbuf, sizeof(cmdbuf) - 1, "SCAN %s", privdata->filename);
 
 		nbytes = (int)strlen(cmdbuf);
 
 		if(send(privdata->cmdSocket, cmdbuf, nbytes, 0) < nbytes) {
 			perror("send");
 			clamfi_cleanup(ctx);
 			if(use_syslog)
 				syslog(LOG_ERR, "send failed to clamd");
 			return cl_error;
 		}
 
 		shutdown(privdata->cmdSocket, SHUT_WR);
 	}
 
66ff992e
 	if(clamd_recv(privdata->cmdSocket, mess, sizeof(mess)) > 0) {
e3aaff8e
 		if((ptr = strchr(mess, '\n')) != NULL)
 			*ptr = '\0';
 
 		if(logVerbose)
 			syslog(LOG_DEBUG, "clamfi_eom: read %s", mess);
f7ab4278
 		cli_dbgmsg("clamfi_eom: read %s\n", mess);
e3aaff8e
 	} else {
98135801
 		clamfi_cleanup(ctx);
e3aaff8e
 		syslog(LOG_NOTICE, "clamfi_eom: read nothing from clamd");
 #ifdef	CL_DEBUG
5b6bb93b
 		cli_dbgmsg("clamfi_eom: read nothing from clamd\n");
e3aaff8e
 #endif
66ff992e
 		return cl_error;
e3aaff8e
 	}
 
dad136d5
 	close(privdata->cmdSocket);
 	privdata->cmdSocket = -1;
 
86b3e542
 	sendmailId = smfi_getsymval(ctx, "i");
f7ab4278
 
86b3e542
 	if(strstr(mess, "ERROR") != NULL) {
f7ab4278
 
86b3e542
 		cli_warnmsg("%s: %s\n", sendmailId, mess);
e84162a4
 		if(use_syslog)
86b3e542
 			syslog(LOG_ERR, "%s: %s\n", sendmailId, mess);
e84162a4
 		clamfi_cleanup(ctx);
 		return cl_error;
 	}
 
e3aaff8e
 	if(strstr(mess, "FOUND") == NULL) {
7418fb74
 		if(!nflag)
 			smfi_addheader(ctx, "X-Virus-Scanned", clamav_version);
e3aaff8e
 
 		/*
 		 * TODO: if privdata->from is NULL it's probably SPAM, and
 		 * me might consider bouncing it...
 		 */
fdc6066a
 		if(use_syslog && logClean)
2a2f7ff3
 			/* Include the sendmail queue ID in the log */
 			syslog(LOG_NOTICE, "%s: clean message from %s",
86b3e542
 				sendmailId,
049a18b9
 				(privdata->from) ? privdata->from : "an unknown sender");
fa9628f2
 
51b03ecb
 		if(privdata->body) {
2a2f7ff3
 			/*
 			 * Add a signature that all has been scanned OK
 			 */
fe3d8be8
 			off_t len = updateSigFile();
fa9628f2
 
fe3d8be8
 			if(len) {
 				assert(Sflag != 0);
fa9628f2
 
fe3d8be8
 				privdata->body = realloc(privdata->body, privdata->bodyLen + len);
 				memcpy(&privdata->body[privdata->bodyLen], signature, len);
fa9628f2
 
fe3d8be8
 				smfi_replacebody(ctx, privdata->body, privdata->bodyLen + len);
 			}
fa9628f2
 		}
e3aaff8e
 	} else {
 		int i;
 		char **to, *err;
 
 		/*
 		 * Setup err as a list of recipients
 		 */
5b6bb93b
 		err = (char *)cli_malloc(1024);
e3aaff8e
 
9148ec6d
 		/*
 		 * Use snprintf rather than printf since we don't know the
 		 * length of privdata->from and may get a buffre overrun
 		 * causing a crash
 		 */
86b3e542
 		snprintf(err, 1024, "Intercepted virus from %s to",
 			privdata->from);
e3aaff8e
 
 		ptr = strchr(err, '\0');
 
049a18b9
 		i = 1024;
 
e3aaff8e
 		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);
b312f172
 				ptr = strchr(err, '\0');
e3aaff8e
 			}
 			ptr = strrcpy(ptr, " ");
 			ptr = strrcpy(ptr, *to);
 		}
049a18b9
 		(void)strcpy(ptr, "\n");
e3aaff8e
 
 		if(use_syslog)
2a2f7ff3
 			/* Include the sendmail queue ID in the log */
86b3e542
 			syslog(LOG_NOTICE, "%s: %s %s", sendmailId, mess, err);
e3aaff8e
 #ifdef	CL_DEBUG
5b6bb93b
 		cli_dbgmsg("%s\n", err);
e3aaff8e
 #endif
e004f1c5
 		free(err);
e3aaff8e
 
7a9b0f05
 		if(!qflag) {
c9af1776
 			char cmd[128];
1fcdb893
 			FILE *sendmail;
c9af1776
 
 			snprintf(cmd, sizeof(cmd), "%s -t", SENDMAIL_BIN);
 
 			sendmail = popen(cmd, "w");
 
7a9b0f05
 			if(sendmail) {
b2fb75ea
 				const char *from;
 
 				/*
 				 * Try to determine who sent the message.
 				 * In the days of faked from addresses this is
 				 * not easy!
 				 */
 				if(privdata->from)
 					from = (strcmp(privdata->from, "<>") == 0) ?
 						smfi_getsymval(ctx, "_") :
 						privdata->from;
 				else
 					from = smfi_getsymval(ctx, "_");
 
9148ec6d
 				/*
 				 * TODO: Make this e-mail message customisable
 				 * perhaps by means of a template
 				 */
7a9b0f05
 				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);
00727a0e
 				/*
 				 * Auto-submittied is still a draft, keep an
 				 * eye on its format
 				 */
 				fputs("Auto-Submitted: auto-submitted (antivirus notify)\n", sendmail);
7a9b0f05
 				fputs("Subject: Virus intercepted\n\n", sendmail);
 
 				if(bflag)
 					fputs("A message you sent to\n\t", sendmail);
d4b2e2e4
 				else if(pflag)
 					/*
 					 * The message is only going to the
 					 * postmaster, so include some useful
 					 * information
 					 */
 					fprintf(sendmail, "The message %s sent from %s to\n\t",
86b3e542
 						sendmailId, from);
7a9b0f05
 				else
9e1e77b9
 					fprintf(sendmail, "A message sent from %s to\n\t",
b2fb75ea
 						from);
7a9b0f05
 
 				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);
 
668c7570
 				if(privdata->filename != NULL)
9e1e77b9
 					fprintf(sendmail, "\nThe message in question has been quarantined as %s\n", privdata->filename);
 
5b6bb93b
 				if(hflag) {
 					fprintf(sendmail, "\nThe message was received by %s from %s via %s\n\n",
 						smfi_getsymval(ctx, "j"), from,
 						smfi_getsymval(ctx, "_"));
9e1e77b9
 					fputs("For your information, the original message headers were:\n\n", sendmail);
 					header_list_print(privdata->headers, sendmail);
 				}
668c7570
 
7a9b0f05
 				pclose(sendmail);
 			}
e3aaff8e
 		}
668c7570
 
 		if(privdata->filename) {
 			assert(quarantine_dir != NULL);
 
 			if(use_syslog)
 				syslog(LOG_NOTICE, "Quarantined infected mail as %s", privdata->filename);
 			/*
 			 * Cleanup filename here! Default procedure would delete quarantine file
 			 */
 			free(privdata->filename);
 			privdata->filename = NULL;
 		}
 
e004f1c5
 		if(quarantine) {
 			for(to = privdata->to; *to; to++) {
 				smfi_delrcpt(ctx, *to);
668c7570
 				smfi_addheader(ctx, "X-Original-To", *to);
e004f1c5
 				free(*to);
 			}
 			free(privdata->to);
 			privdata->to = NULL;
 			if(smfi_addrcpt(ctx, quarantine) == MI_FAILURE) {
 				if(use_syslog)
 					syslog(LOG_DEBUG, "Can't set quarantine user %s", quarantine);
 				else
f7ab4278
 					cli_warnmsg("Can't set quarantine user %s\n", quarantine);
668c7570
 			} else
e004f1c5
 				/*
 				 * FIXME: doesn't work if there's no subject
 				 */
 				smfi_chgheader(ctx, "Subject", 1, mess);
3a03d183
 		} else if(rejectmail)
e004f1c5
 			rc = SMFIS_REJECT;	/* Delete the e-mail */
3a03d183
 		else
 			rc = SMFIS_DISCARD;
e3aaff8e
 
7908713f
 		smfi_setreply(ctx, "550", "5.7.1", "Virus detected by ClamAV - http://www.clamav.net");
e3aaff8e
 	}
 	clamfi_cleanup(ctx);
 
 	return rc;
 }
 
 static sfsistat
 clamfi_abort(SMFICTX *ctx)
 {
 #ifdef	CL_DEBUG
 	if(use_syslog)
 		syslog(LOG_DEBUG, "clamfi_abort");
5b6bb93b
 	cli_dbgmsg("clamfi_abort\n");
e3aaff8e
 #endif
 
 	/*
 	 * Unlock incase we're called during a cond_timedwait in envfrom
 	 *
 	 * TODO: There *must* be a tidier way of doing this!
 	 */
88f28d8c
 	if(max_children > 0)
 		(void)pthread_mutex_unlock(&n_children_mutex);
e3aaff8e
 
 	clamfi_cleanup(ctx);
 
68d5a5f3
 	return cl_error;
e3aaff8e
 }
 
 static sfsistat
 clamfi_close(SMFICTX *ctx)
 {
 #ifdef	CL_DEBUG
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
 
5b6bb93b
 	cli_dbgmsg("clamfi_close\n");
1f7a8360
 	if(privdata != NULL) {
 		if(use_syslog)
 			syslog(LOG_DEBUG, "clamfi_close, privdata != NULL");
 		else
f7ab4278
 			cli_warnmsg("clamfi_close, privdata != NULL");
1f7a8360
 	}
e3aaff8e
 #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);
 
7418fb74
 	if(privdata) {
c54d7329
 		clamfi_free(privdata);
 		smfi_setpriv(ctx, NULL);
 	}
 }
 
 static void
 clamfi_free(struct privdata *privdata)
 {
 	if(privdata) {
51b03ecb
 		if(privdata->body)
 			free(privdata->body);
 
7418fb74
 		if(privdata->dataSocket >= 0) {
 			close(privdata->dataSocket);
 			privdata->dataSocket = -1;
 		}
e3aaff8e
 
668c7570
 		if(privdata->filename != NULL) {
 			if(unlink(privdata->filename) < 0)
 				perror(privdata->filename);
 			free(privdata->filename);
 			privdata->filename = NULL;
 		}
 
7418fb74
 		if(privdata->from) {
e3aaff8e
 #ifdef	CL_DEBUG
7418fb74
 			if(debug_level >= 9)
5b6bb93b
 				cli_dbgmsg("Free privdata->from\n");
e3aaff8e
 #endif
7418fb74
 			free(privdata->from);
 			privdata->from = NULL;
 		}
e3aaff8e
 
7418fb74
 		if(privdata->to) {
 			char **to;
e3aaff8e
 
7418fb74
 			for(to = privdata->to; *to; to++) {
e3aaff8e
 #ifdef	CL_DEBUG
7418fb74
 				if(debug_level >= 9)
5b6bb93b
 					cli_dbgmsg("Free *privdata->to\n");
e3aaff8e
 #endif
7418fb74
 				free(*to);
 			}
e3aaff8e
 #ifdef	CL_DEBUG
7418fb74
 			if(debug_level >= 9)
5b6bb93b
 				cli_dbgmsg("Free privdata->to\n");
e3aaff8e
 #endif
7418fb74
 			free(privdata->to);
 			privdata->to = NULL;
 		}
e3aaff8e
 
7418fb74
 		if(privdata->cmdSocket >= 0) {
 			char buf[64];
e3aaff8e
 
7418fb74
 			/*
 			 * Flush the remote end so that clamd doesn't get a SIGPIPE
 			 */
66ff992e
 			while(clamd_recv(privdata->cmdSocket, buf, sizeof(buf)) > 0)
7418fb74
 				;
 			close(privdata->cmdSocket);
 			privdata->cmdSocket = -1;
 		}
9e1e77b9
 		if(privdata->headers)
 			header_list_free(privdata->headers);
e3aaff8e
 
 #ifdef	CL_DEBUG
7418fb74
 		if(debug_level >= 9)
5b6bb93b
 			cli_dbgmsg("Free privdata\n");
e3aaff8e
 #endif
7418fb74
 		free(privdata);
 	}
e3aaff8e
 
 	if(max_children > 0) {
 		pthread_mutex_lock(&n_children_mutex);
 		/*
 		 * Deliberately errs on the side of broadcasting too many times
 		 */
198d714a
 		if(n_children > 0)
 			--n_children;
e3aaff8e
 #ifdef	CL_DEBUG
5b6bb93b
 		cli_dbgmsg("pthread_cond_broadcast\n");
e3aaff8e
 #endif
88f28d8c
 		pthread_cond_broadcast(&n_children_cond);
e3aaff8e
 #ifdef	CL_DEBUG
f9c88a98
 		cli_dbgmsg("<n_children = %d\n", n_children);
e3aaff8e
 #endif
 		pthread_mutex_unlock(&n_children_mutex);
 	}
 }
 
f7ab4278
 /*
  * Returns < 0 for failure
  */
e3aaff8e
 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
f7ab4278
 	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);
 	}
e3aaff8e
 #endif
 
 	while(len > 0) {
2a2f7ff3
 		const int nbytes = (quarantine_dir) ?
668c7570
 			write(privdata->dataSocket, ptr, len) :
 			send(privdata->dataSocket, ptr, len, 0);
e3aaff8e
 
8ac80fb8
 		assert(privdata->dataSocket >= 0);
 
e3aaff8e
 		if(nbytes == -1) {
 			if(errno == EINTR)
 				continue;
 			perror("send");
8ac80fb8
 			if(use_syslog) {
 #ifdef HAVE_STRERROR_R
 				char buf[32];
 				strerror_r(errno, buf, sizeof(buf));
 				syslog(LOG_ERR,
 					"write failure (%u bytes) to clamd: %s",
 					len, buf);
 #else
 				syslog(LOG_ERR, "write failure (%u bytes) to clamd: %s", len, strerror(errno));
 #endif
 			}
b5648b5a
 			checkClamd();
e3aaff8e
 
 			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);
 }
66ff992e
 
 /*
  * Read from clamav - timeout if necessary
  */
 static int
 clamd_recv(int sock, char *buf, size_t len)
 {
 	fd_set rfds;
 	struct timeval tv;
 
96f3d93b
 	if(readTimeout == 0)
66ff992e
 		return recv(sock, buf, len, 0);
 
 	FD_ZERO(&rfds);
 	FD_SET(sock, &rfds);
 
96f3d93b
 	tv.tv_sec = readTimeout;
66ff992e
 	tv.tv_usec = 0;
 
 	switch(select(sock + 1, &rfds, NULL, NULL, &tv)) {
 		case -1:
 			perror("select");
 			return -1;
 		case 0:
 			if(use_syslog)
96f3d93b
 				syslog(LOG_ERR, "No data received from clamd in %d seconds\n", readTimeout);
66ff992e
 			return 0;
 	}
 	return recv(sock, buf, len, 0);
 }
fe3d8be8
 
 /*
  * Read in the signature file
  */
 static off_t
 updateSigFile(void)
 {
 	struct stat statb;
 	int fd;
 
 	if(sigFilename == NULL)
 		/* nothing to read */
 		return signature ? strlen(signature) : 0;
 
 	if(stat(sigFilename, &statb) < 0) {
 		perror(sigFilename);
 		if(use_syslog)
 			syslog(LOG_ERR, "Can't stat %s\n", sigFilename);
 		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)
 			syslog(LOG_ERR, "Can't open %s\n", sigFilename);
 		return 0;
 	}
 
 	signatureStamp = statb.st_mtime;
 
 	signature = realloc(signature, statb.st_size);
 	read(fd, signature, statb.st_size);
 	close(fd);
 
 	return statb.st_size;
 }
9e1e77b9
 
 static header_list_t
1f7a8360
 header_list_new(void)
9e1e77b9
 {
 	header_list_t ret;
 
 	ret = (header_list_t)cli_malloc(sizeof(struct header_list_struct));
 	ret->first = NULL;
 	ret->last = NULL;
 	return ret;
 }
 
 static void
 header_list_free(header_list_t list)
 {
 	struct header_node_t *iter;
 
 	iter = list->first;
 	while (iter) {
 		struct header_node_t *iter2 = iter->next;
 		free(iter->header);
 		free(iter);
 		iter = iter2;
 	}
 	free(list);
 }
 
 static void
 header_list_add(header_list_t list, const char *headerf, const char *headerv)
 {
 	char *header;
 	size_t len;
 	struct header_node_t *new_node;
 
 	len = strlen(headerf) + strlen(headerv) + 3;
 
 	header = (char *)cli_malloc(len);
 	snprintf(header, len, "%s: %s", headerf, headerv);
5b6bb93b
 	new_node = (struct header_node_t *)cli_malloc(sizeof(struct header_node_t));
9e1e77b9
 	new_node->header = header;
 	new_node->next = NULL;
 	if(!list->first)
 		list->first = new_node;
 	if(list->last)
 		list->last->next = new_node;
 
 	list->last = new_node;
 }
 
 static void
 header_list_print(header_list_t list, FILE *fp)
 {
 	const struct header_node_t *iter;
 
 	for(iter = list->first; iter; iter = iter->next)
 		fprintf(fp, "%s\n", iter->header);
 }
f7ab4278
  
 /*
  * Establish a connection to clamd
  *	Returns success (1) or failure (0)
  */
 static int
 connect2clamd(struct privdata *privdata)
 {
 	char **to;
 
 	assert(privdata->dataSocket == -1);
 	assert(privdata->from != NULL);
 	assert(privdata->to != NULL);
 
 #ifdef	CL_DEBUG
 	if((debug_level > 0) && use_syslog)
 		syslog(LOG_DEBUG, "connect2clamd");
 	if(debug_level >= 4)
 		cli_dbgmsg("connect2clamd\n");
 #endif
 
 	if(quarantine_dir) {
 		/*
 		 * quarantine_dir is specified
 		 * store message in a temporary file
 		 */
 		int ntries = 5;
 
 		privdata->filename = (char *)cli_malloc(strlen(quarantine_dir) + 12);
 
 		do {
 			sprintf(privdata->filename, "%s/msg.XXXXXX", quarantine_dir);
 #if	defined(C_LINUX) || defined(C_BSD) || defined(HAVE_MKSTEMP) || defined(C_SOLARIS)
 			privdata->dataSocket = mkstemp(privdata->filename);
 #else
 			if(mktemp(privdata->filename) == NULL) {
 				if(use_syslog)
 					syslog(LOG_ERR, "mktemp %s failed", privdata->filename);
 				return 0;
 			}
 			privdata->dataSocket = open(privdata->filename, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC, 0600);
 #endif
 		} while((--ntries > 0) && (privdata->dataSocket < 0));
 
 		if(privdata->dataSocket < 0) {
 			if(use_syslog)
 				syslog(LOG_ERR, "tempfile %s creation failed", privdata->filename);
 			return 0;
 		}
 	} else {
 		int freeServer, nbytes;
 		struct sockaddr_in reply;
 		unsigned short port;
 		char buf[64];
 
 		assert(privdata->cmdSocket == -1);
 
 		/*
 		 * 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;
658f19f8
 			strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
f7ab4278
 
 			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;
 			}
 			freeServer = 0;
 		} else {
 			struct sockaddr_in server;
 
 			memset((char *)&server, 0, sizeof(struct sockaddr_in));
 			server.sin_family = AF_INET;
 			server.sin_port = (in_port_t)htons(tcpSocket);
 
 			assert(serverIPs != NULL);
 
 			freeServer = findServer();
 			if(freeServer < 0)
 				return 0;
 
 			server.sin_addr.s_addr = serverIPs[freeServer];
 
 			if((privdata->cmdSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
 				perror("socket");
 				return 0;
 			}
 			if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
 				perror("connect");
 				return 0;
 			}
 		}
 
 		/*
 		 * 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 socket");
 			return 0;
 		}
 
 		shutdown(privdata->dataSocket, SHUT_RD);
 
 		if(send(privdata->cmdSocket, "STREAM\n", 7, 0) < 7) {
 			perror("send");
 			if(use_syslog)
 				syslog(LOG_ERR, "send failed to clamd");
 			return 0;
 		}
 
 		shutdown(privdata->cmdSocket, SHUT_WR);
 
 		nbytes = clamd_recv(privdata->cmdSocket, buf, sizeof(buf));
 		if(nbytes < 0) {
 			perror("recv");
 			if(use_syslog)
 				syslog(LOG_ERR, "recv failed from clamd getting PORT");
 			return 0;
 		}
 		buf[nbytes] = '\0';
 #ifdef	CL_DEBUG
 		if(debug_level >= 4)
 			cli_dbgmsg("Received: %s", buf);
 #endif
 		if(sscanf(buf, "PORT %hu\n", &port) != 1) {
 			if(use_syslog)
 				syslog(LOG_ERR, "Expected port information from clamd, got '%s'",
 					buf);
 			else
 				cli_warnmsg("Expected port information from clamd, got '%s'\n",
 					buf);
 			return 0;
 		}
 
 		memset((char *)&reply, 0, sizeof(struct sockaddr_in));
 		reply.sin_family = AF_INET;
 		reply.sin_port = (in_port_t)htons(port);
 
 		assert(serverIPs != NULL);
 
 		reply.sin_addr.s_addr = serverIPs[freeServer];
 
 #ifdef	CL_DEBUG
 		if(debug_level >= 4)
 			cli_dbgmsg("Connecting to local port %d\n", port);
 #endif
 
 		if(connect(privdata->dataSocket, (struct sockaddr *)&reply, sizeof(struct sockaddr_in)) < 0) {
 			perror("connect");
 
 			/* 0.4 - use better error message */
 			if(use_syslog) {
 #ifdef HAVE_STRERROR_R
 				strerror_r(errno, buf, sizeof(buf));
 				syslog(LOG_ERR,
 					"Failed to connect to port %d given by clamd: %s",
 					port, buf);
 #else
 				syslog(LOG_ERR, "Failed to connect to port %d given by clamd: %s", port, strerror(errno));
 #endif
 			}
 			return 0;
 		}
 	}
 
 	/*
 	 * TODO:
 	 *	Put from and to data into a buffer and call clamfi_send once
 	 * to save bandwidth when using TCP/IP to connect with a remote clamd
 	 */
 	clamfi_send(privdata, 0,
 		"Received: by clamav-milter\nFrom: %s\n",
 		privdata->from);
 
 	for(to = privdata->to; *to; to++)
 		if(clamfi_send(privdata, 0, "To: %s\n", *to) < 0)
 			return 0;
 
 	cli_dbgmsg("connect2clamd OK\n");
 
 	return 1;
 }
b5648b5a
 
 /*
  * 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)
8ac80fb8
 		return;	/* communicating via TCP */
b5648b5a
 
 	if(pidFile == NULL)
8ac80fb8
 		return;	/* PidFile directive missing from clamav.conf */
b5648b5a
 
 	fd = open(pidFile, O_RDONLY);
 	if(fd < 0) {
 		perror(pidFile);
 		if(use_syslog)
 			syslog(LOG_ERR, "Can't open %s\n", pidFile);
 		return;
 	}
 	nbytes = read(fd, buf, sizeof(buf) - 1);
 	close(fd);
 	buf[nbytes] = '\0';
 	pid = atoi(buf);
ecaaaf05
 	if((kill(pid, 0) < 0) && (errno == ESRCH)) {
 		if(use_syslog)
 			syslog(LOG_ERR, "Clamd (pid %d) seems to have died\n",
 				pid);
 		perror("clamd");
b5648b5a
 	}
 }