clamav-milter/clamav-milter.c
e3aaff8e
 /*
  * clamav-milter.c
  *	.../clamav-milter/clamav-milter.c
  *
83ed1043
  *  Copyright (C) 2003-2007 Nigel Horne <njh@bandsman.co.uk>
e3aaff8e
  *
  *  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
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
e3aaff8e
  *
7e10f99b
  * Install into /usr/local/sbin/clamav-milter
abced581
  * See http://www.elandsys.com/resources/sendmail/libmilter/overview.html
e3aaff8e
  *
81bdf63b
  * For installation instructions see the file INSTALL that came with this file
83ed1043
  *
  * NOTE: first character of strings to logg():
  *	! Error
  *	^ Warning
  *	* Verbose
  *	# Info, but not logged in foreground
  *	Default Info
e3aaff8e
  */
27395a6e
 static	char	const	rcsid[] = "$Id: clamav-milter.c,v 1.312 2007/02/12 22:24:21 njh Exp $";
e3aaff8e
 
7908713f
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
86b3e542
 #include "cfgparser.h"
7b2de1a6
 #include "target.h"
44d08756
 #include "str.h"
1fcdb893
 #include "../libclamav/others.h"
b4b5c17d
 #include "output.h"
1fcdb893
 #include "clamav.h"
2f34cd3f
 #include "table.h"
eaf74461
 #include "network.h"
e3aaff8e
 
 #ifndef	CL_DEBUG
 #define	NDEBUG
 #endif
 
 #include <stdio.h>
 #include <sysexits.h>
feb192dd
 #ifdef	HAVE_SYS_STAT_H
332e6334
 #include <sys/stat.h>
feb192dd
 #endif
 #if	HAVE_STDLIB_H
e3aaff8e
 #include <stdlib.h>
da33d9f8
 #endif
df6c5998
 #if	HAVE_MEMORY_H
 #include <memory.h>
 #endif
da33d9f8
 #if	HAVE_STRING_H
e3aaff8e
 #include <string.h>
da33d9f8
 #endif
e3aaff8e
 #include <sys/wait.h>
 #include <assert.h>
3aa5c1c9
 #include <sys/socket.h>
1e2aaf5e
 #include <netinet/in.h>
5d79922b
 #include <net/if.h>
e3aaff8e
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <stdarg.h>
 #include <errno.h>
da33d9f8
 #if	HAVE_LIBMILTER_MFAPI_H
e3aaff8e
 #include <libmilter/mfapi.h>
da33d9f8
 #endif
e3aaff8e
 #include <pthread.h>
 #include <sys/time.h>
9a03413e
 #include <sys/resource.h>
e3aaff8e
 #include <signal.h>
01ab0124
 #include <fcntl.h>
e004f1c5
 #include <pwd.h>
3aa15b4c
 #include <grp.h>
da33d9f8
 #if	HAVE_SYS_PARAM_H
44ba5c0e
 #include <sys/param.h>
da33d9f8
 #endif
2d4d05c4
 #if	HAVE_RESOLV_H
 #include <arpa/nameser.h>	/* for HEADER */
 #include <resolv.h>
 #endif
9c872b2e
 #ifdef	HAVE_UNISTD_H
 #include <unistd.h>
 #endif
feb192dd
 #include <ctype.h>
e3aaff8e
 
19575eba
 #if HAVE_MMAP
 #if HAVE_SYS_MMAN_H
 #include <sys/mman.h>
 #else /* HAVE_SYS_MMAN_H */
 #undef HAVE_MMAP
 #endif
 #endif
 
9fe789f8
 #define NONBLOCK_SELECT_MAX_FAILURES	3
 #define NONBLOCK_MAX_ATTEMPTS	10
 #define	CONNECT_TIMEOUT	5	/* Allow 5 seconds to connect */
 
eba8ebeb
 #ifdef	C_LINUX
c86c794b
 #include <sys/sendfile.h>	/* FIXME: use sendfile on BSD not Linux */
eba8ebeb
 #include <libintl.h>
3aa5c1c9
 #include <locale.h>
eba8ebeb
 
 #define	gettext_noop(s)	s
 #define	_(s)	gettext(s)
 #define	N_(s)	gettext_noop(s)
 
 #else
 
 #define	_(s)	s
 #define	N_(s)	s
 
 #endif
 
b43b45a9
 #ifdef	USE_SYSLOG
 #include <syslog.h>
 #endif
 
b2fb75ea
 #ifdef	WITH_TCPWRAP
da33d9f8
 #if	HAVE_TCPD_H
b2fb75ea
 #include <tcpd.h>
da33d9f8
 #endif
f9c88a98
 
 int	allow_severity = LOG_DEBUG;
e84162a4
 int	deny_severity = LOG_NOTICE;
83ed1043
 #endif
f9c88a98
 
c17d8aba
 #ifdef	CL_DEBUG
4375361f
 static	char	console[] = "/dev/console";
89a2d133
 #endif
 
44d08756
 #if defined(CL_DEBUG) && defined(C_LINUX)
 #include <sys/resource.h>
 #endif
 
e3aaff8e
 #define _GNU_SOURCE
eba8ebeb
 #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
 
152f7c23
 #ifndef	HAVE_IN_ADDR_T
 typedef	unsigned int	in_addr_t;
 #endif
 
df1ec3e8
 #ifndef	INET6_ADDRSTRLEN
 #ifdef	AF_INET6
 #define	INET6_ADDRSTRLEN	40
 #else
 #define	INET6_ADDRSTRLEN	16
 #endif
 #endif
 
b1409e43
 #ifndef	EX_CONFIG	/* HP-UX */
 #define	EX_CONFIG	78
 #endif
 
1fcd39ef
 #define	VERSION_LENGTH	128
e1773c9e
 #define	DEFAULT_TIMEOUT	120
4a5aa10e
 #define	NTRIES	5	/* How many times we try to connect to a clamd */
1fcd39ef
 
8cc2edc9
 /*#define	SESSION*/
df1ec3e8
 		/* Keep one command connexion open to clamd, otherwise a new
 		 * command connexion is created for each new email
8528bc29
 		 *
 		 * FIXME: When SESSIONS are open, freshclam can hang when
 		 *	notfying clamd of an update. This is most likely to be a
 		 *	problem with the implementation of SESSIONS on clamd.
 		 *	The problem seems worst on BSD.
7089d180
 		 *
 		 * Note that clamd is buggy and can hang or even crash if you
 		 *	send SESSION command so be aware
06bfd678
 		 */
 
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
02b1cb1f
  *	can use wall(1) in the VirusEvent entry in clamd.conf
7908713f
  * 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.
a11b9be3
  * TODO: Encrypt mails sent to clamd to stop sniffers. Sending by UNIX domain
  *	sockets is better
235ab38f
  * TODO: Load balancing, allow local machine to talk via UNIX domain socket.
feb192dd
  * TODO: allow each To: line in the whitelist file to specify a quarantine email
e13e1f7c
  *	address
feb192dd
  * TODO: optionally use zlib to compress data sent to remote hosts
684d3122
  * TODO: Finish IPv6 support (serverIPs array and SPF are IPv4 only)
6ab97c20
  * TODO: Check domainkeys as well as SPF for phish false positives
e3aaff8e
  */
 
9e1e77b9
 struct header_node_t {
9c872b2e
 	char	*header;
 	struct	header_node_t *next;
9e1e77b9
 };
 
 struct header_list_struct {
9c872b2e
 	struct	header_node_t *first;
 	struct	header_node_t *last;
9e1e77b9
 };
 
 typedef struct header_list_struct *header_list_t;
 
e3aaff8e
 /*
eef726b0
  * Local addresses are those not scanned if --local is not set
  * 127.0.0.0 is not in this table since that's goverend by --outgoing
df1ec3e8
  * Andy Fiddaman <clam@fiddaman.net> added 169.254.0.0/16
eef726b0
  *	(Microsoft default DHCP)
df1ec3e8
  * TODO: compare this with RFC1918/RFC3330
eef726b0
  */
932fb5f5
 #define PACKADDR(a, b, c, d) (((uint32_t)(a) << 24) | ((b) << 16) | ((c) << 8) | (d))
df1ec3e8
 #define MAKEMASK(bits)	((uint32_t)(0xffffffff << (32 - bits)))
eef726b0
 
e3f334f6
 static struct cidr_net {	/* don't make this const because of -I flag */
eef726b0
 	uint32_t	base;
 	uint32_t	mask;
 } localNets[] = {
9ad21276
 	/*{ PACKADDR(127,   0,   0,   0), MAKEMASK(8) },	/*   127.0.0.0/8 */
 	{ PACKADDR(192, 168,   0,   0), MAKEMASK(16) },	/* 192.168.0.0/16 - RFC3330 */
 	/*{ PACKADDR(192, 18,   0,   0), MAKEMASK(15) },	/* 192.18.0.0/15 - RFC2544 */
 	/*{ PACKADDR(192, 0,   2,   0), MAKEMASK(24) },	/* 192.0.2.0/24 - RFC3330 */
df1ec3e8
 	{ PACKADDR( 10,   0,   0,   0), MAKEMASK(8) },	/*    10.0.0.0/8 */
 	{ PACKADDR(172,  16,   0,   0), MAKEMASK(12) },	/*  172.16.0.0/12 */
feb192dd
 	{ PACKADDR(169, 254,   0,   0), MAKEMASK(16) },	/* 169.254.0.0/16 */
df1ec3e8
 	{ 0, 0 },	/* space to put eight more via -I addr */
 	{ 0, 0 },
 	{ 0, 0 },
 	{ 0, 0 },
 	{ 0, 0 },
 	{ 0, 0 },
 	{ 0, 0 },
 	{ 0, 0 },
eef726b0
 	{ 0, 0 }
 };
df1ec3e8
 #define IFLAG_MAX 8
 
477961e9
 #ifdef	AF_INET6
df1ec3e8
 typedef struct cidr_net6 {
 	struct in6_addr	base;
 	int preflen;
 } cidr_net6;
 static	cidr_net6	localNets6[IFLAG_MAX];
 static	int	localNets6_cnt;
477961e9
 #endif
eef726b0
 
 /*
1cc545df
  * Each libmilter thread has one of these
e3aaff8e
  */
 struct	privdata {
 	char	*from;	/* Who sent the message */
7089d180
 	char	*subject;	/* Original subject */
 	char	*sender;	/* Secretary - often used in mailing lists */
e3aaff8e
 	char	**to;	/* Who is the message going to */
df1ec3e8
 	char	ip[INET6_ADDRSTRLEN];	/* IP address of the other end */
e3aaff8e
 	int	numTo;	/* Number of people the message is going to */
06bfd678
 #ifndef	SESSION
e3aaff8e
 	int	cmdSocket;	/*
 				 * Socket to send/get commands e.g. PORT for
 				 * dataSocket
 				 */
06bfd678
 #endif
e3aaff8e
 	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 */
bb09a2f7
 	long	numBytes;	/* Number of bytes sent so far */
502c8234
 	char	*received;	/* keep track of received from */
b5c80361
 	const	char	*rejectCode;	/* 550 or 554? */
a32f3ba8
 	unsigned	int	discard:1;	/*
b5c80361
 				 * looks like the remote end is playing ping
 				 * pong with us
 				 */
510212e4
 #ifdef	HAVE_RESOLV_H
a32f3ba8
 	unsigned	int	spf_ok:1;
 #endif
28071296
 	int	statusCount;	/* number of X-Virus-Status headers */
0abc0a57
 	int	serverNumber;	/* Index into serverIPs */
1cc545df
 	struct	cl_node	*root;	/* database of viruses used to scan this one */
e3aaff8e
 };
 
03bc6e11
 #ifdef	SESSION
89a2d133
 static	int		createSession(unsigned int s);
03bc6e11
 #else
44d08756
 static	int		pingServer(int serverNumber);
2dda0caf
 static	void		*try_server(void *var);
37ba3b97
 static	int		active_servers(int *active);
2dda0caf
 struct	try_server_struct {
 	int	sock;
 	int	rc;
 	struct	sockaddr_in *server;
 	int	server_index;
 };
03bc6e11
 #endif
44d08756
 static	int		findServer(void);
e3aaff8e
 static	sfsistat	clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr);
7089d180
 #ifdef	CL_DEBUG
 static	sfsistat	clamfi_helo(SMFICTX *ctx, char *helostring);
 #endif
e3aaff8e
 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);
cdf082d4
 static	void		clamfi_free(struct privdata *privdata, int keep);
7d81c053
 static	int		clamfi_send(struct privdata *privdata, size_t len, const char *format, ...);
eaf74461
 static	long		clamd_recv(int sock, char *buf, size_t len);
fe3d8be8
 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);
7089d180
 static	int	sendToFrom(struct privdata *privdata);
83fc7301
 static	int	checkClamd(int log_result);
a0c42dae
 static	int	sendtemplate(SMFICTX *ctx, const char *filename, FILE *sendmail, const char *virusname);
31ee8076
 static	int	qfile(struct privdata *privdata, const char *sendmailId, const char *virusname);
dfa70a67
 static	int	move(const char *oldfile, const char *newfile);
459b60af
 static	void	setsubject(SMFICTX *ctx, const char *virusname);
85883a02
 /*static	int	clamfi_gethostbyname(const char *hostname, struct hostent *hp, char *buf, size_t len);*/
df1ec3e8
 static	int	add_local_ip(char *address);
eef726b0
 static	int	isLocalAddr(in_addr_t addr);
df1ec3e8
 static	int	isLocal(const char *addr);
03bc6e11
 static	void	clamdIsDown(void);
 static	void	*watchdog(void *a);
f1617494
 static	int	check_and_reload_database(void);
93928eab
 static	void	timeoutBlacklist(char *ip_address, int time_of_blacklist, void *v);
152f7c23
 static	void	quit(void);
 static	void	broadcast(const char *mess);
93899a2c
 static	int	loadDatabase(void);
df1ec3e8
 static	int	increment_connexions(void);
 static	void	decrement_connexions(void);
93928eab
 static	void	dump_blacklist(char *key, int value, void *v);
9fe789f8
 static	int	nonblock_connect(int sock, const struct sockaddr_in *sin, const char *hostname);
 static	int	connect_error(int sock, const char *hostname);
e3aaff8e
 
1fcd39ef
 #ifdef	SESSION
0863025b
 static	pthread_mutex_t	version_mutex = PTHREAD_MUTEX_INITIALIZER;
9fcf8647
 static	char	**clamav_versions;	/* max_children elements in the array */
1fcd39ef
 #define	clamav_version	(clamav_versions[0])
 #else
 static	char	clamav_version[VERSION_LENGTH + 1];
 #endif
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? */
a0ff4f89
 static	int	Iflag = 0;	/* Added an IP addr to localNets? */
640dbd4c
 static	const	char	*progname;	/* our name - usually clamav-milter */
93899a2c
 
8528bc29
 /* Variables for --external */
 static	int	external = 0;	/* scan messages ourself or use clamd? */
aaade7d3
 static	pthread_mutex_t	root_mutex = PTHREAD_MUTEX_INITIALIZER;
d5e48ace
 static	struct	cl_node	*root = NULL;
 static	struct	cl_limits	limits;
 static	struct	cl_stat	dbstat;
950ecb65
 static	int	options = CL_SCAN_STDOPT;
d5e48ace
 
4c5e69c8
 #ifdef	BOUNCE
e3aaff8e
 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
89a2d133
 				 *
 				 * TODO: Perhaps we can have an option to
 				 * bounce outgoing mail, but not incoming?
e3aaff8e
 				 */
4c5e69c8
 #endif
5d79922b
 static	const	char	*iface;	/*
152f7c23
 				 * Broadcast a message when a virus is found,
 				 * this allows remote network management
 				 */
5d79922b
 static	int	broadcastSock = -1;
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
02b1cb1f
 				 * enable LogSyslog in clamd.conf
7a9b0f05
 				 */
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>
 				 */
e1773c9e
 static	int	readTimeout = DEFAULT_TIMEOUT; /*
66ff992e
 				 * number of seconds to wait for clamd to
02b1cb1f
 				 * respond, see ReadTimeout in clamd.conf
66ff992e
 				 */
02b1cb1f
 static	long	streamMaxLength = -1;	/* StreamMaxLength from clamd.conf */
c17d8aba
 static	int	logok = 0;	/*
fdc6066a
 				 * Add clean items to the log file
 				 */
eba8ebeb
 static	char	*signature = N_("-- \nScanned by ClamAv - http://www.clamav.net\n");
fe3d8be8
 static	time_t	signatureStamp;
e8a362f9
 static	char	*templateFile;	/* e-mail to be sent when virus detected */
 static	char	*templateHeaders;	/* headers to be added to the above */
932fb5f5
 static	const char	*tmpdir;
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;
3d1678b1
 static	int	n_children = 0;
 static	int	max_children = 0;
6bc34381
 static	unsigned	int	freshclam_monitor = 10;	/*
 							 * how often, in
 							 * seconds, to scan for
 							 * database updates
 							 */
1cc545df
 static	int	child_timeout = 300;	/* number of seconds to wait for
4a944387
 					 * a child to die. Set to 0 to
 					 * wait forever
 					 */
459b60af
 static	int	dont_wait = 0;	/*
 				 * If 1 send retry later to the remote end
 				 * if max_chilren is exceeded, otherwise we
 				 * wait for the number to go down
 				 */
f410a3aa
 static	int	dont_sanitise = 0; /*
 				 * Don't check for ";" and "|" chars in 
 				 * email addresses.
 				 */
459b60af
 static	int	advisory = 0;	/*
 				 * Run clamav-milter in advisory mode - viruses
 				 * are flagged rather than deleted. Incompatible
 				 * with quarantine options
 				 */
358facc3
 static	int	detect_forged_local_address;	/*
 				 * for incoming only mail servers, drop emails
 				 * claiming to be from us that must be false
 				 * Requires that -o, -l or -f are NOT given
 				 */
b5648b5a
 static	const	char	*pidFile;
e3aaff8e
 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 */
06bfd678
 
44d08756
 static	const	char	*serverHostNames = "127.0.0.1";
3d1678b1
 #if	HAVE_IN_ADDR_T
83fc7301
 static	in_addr_t	*serverIPs;	/* IPv4 only, in network byte order */
3d1678b1
 #else
83fc7301
 static	long	*serverIPs;	/* IPv4 only, in network byte order */
3d1678b1
 #endif
9c1c533d
 static	int	numServers;	/* number of elements in serverIPs array */
3844e1f0
 #ifndef	SESSION
 #define	RETRY_SECS	300	/* How often to retry a server that's down */
655f1c23
 static	time_t	*last_failed_pings;	/* For servers that are down. NB: not mutexed */
3844e1f0
 #endif
b76fc985
 static	char	*rootdir;	/* for chroot */
 
06bfd678
 #ifdef	SESSION
ae0895b4
 static	struct	session {
 	int	sock;	/* fd */
 	enum	{ CMDSOCKET_FREE, CMDSOCKET_INUSE, CMDSOCKET_DOWN }	status;
9c1c533d
 } *sessions;	/* max_children elements in the array */
03bc6e11
 static	pthread_mutex_t sstatus_mutex = PTHREAD_MUTEX_INITIALIZER;
06bfd678
 #endif	/*SESSION*/
 
45221e8c
 static	pthread_cond_t	watchdog_cond = PTHREAD_COND_INITIALIZER;
 
235ab38f
 #ifndef	SHUT_RD
 #define	SHUT_RD		0
 #endif
 #ifndef	SHUT_WR
 #define	SHUT_WR		1
 #endif
 
049a18b9
 static	const	char	*postmaster = "postmaster";
502c8234
 static	const	char	*from = "MAILER-DAEMON";
152f7c23
 static	int	quitting;
040708a3
 static	int	reload;	/* reload database when SIGUSR2 is received */
a32f3ba8
 static	const	char	*report;	/* Report Phishing to this address */
1a2b28a9
 static	const	char	*report_fps;	/* Report Phish FPs to this address */
e3aaff8e
 
7b2de1a6
 static	const	char	*whitelistFile;	/*
 					 * file containing destination email
 					 * addresses that we don't scan
 					 */
 static	const	char	*sendmailCF;	/* location of sendmail.cf to verify */
1d05987b
 static	const	char	*pidfile;
f1617494
 static	int	black_hole_mode; /*
 				 * Since sendmail calls its milters before it
 				 * looks in /etc/aliases we can spend time
 				 * looking for malware that's going to be
 				 * thrown away even if the message is clean.
 				 * Enable this to not scan these messages.
 				 * Sadly, because these days sendmail -bv
 				 * only works as root, you can't use this with
 				 * the User directive, which some won't like
 				 * which also may contain the real target name
7df6b4c0
 				 *
 				 * smfi_getsymval(ctx, "{rcpt_addr}") only
 				 * handles virtuser, it doesn't also deref
 				 * the alias table, so it isn't any help
f1617494
 				 */
6909adb8
 
a0ff4f89
 static	table_t	*blacklist;	/* never freed */
 static	int	blacklist_time;	/* How long to blacklist an IP */
 static	pthread_mutex_t	blacklist_mutex = PTHREAD_MUTEX_INITIALIZER;
 
9dac9585
 #ifdef	CL_DEBUG
 #if __GLIBC__ == 2 && __GLIBC_MINOR__ >= 1
 #define HAVE_BACKTRACE
 #endif
 #endif
 
92fbb0d6
 static	void	sigsegv(int sig);
040708a3
 static	void	sighup(int sig);
 static	void	sigusr2(int sig);
92fbb0d6
 
9dac9585
 #ifdef HAVE_BACKTRACE
 #include <execinfo.h>
 
9c9e9b9b
 static	void	print_trace(void);
9dac9585
 
 #define	BACKTRACE_SIZE	200
 
 #endif
 
19575eba
 static	int	verifyIncomingSocketName(const char *sockName);
feb192dd
 static	int	isWhitelisted(const char *emailaddress, int to);
a0ff4f89
 static	int	isBlacklisted(const char *ip_address);
93928eab
 static	table_t	*mx(const char *host, table_t *t);
f1617494
 static	sfsistat	black_hole(const struct privdata *privdata);
2dda0caf
 static	int	useful_header(const char *cmd);
19575eba
 
c3429724
 extern	short	logg_foreground;
e13e1f7c
 
0217341d
 #ifdef HAVE_RESOLV_H
 static	table_t	*resolve(const char *host, table_t *t);
 static	int	spf(struct privdata *privdata, table_t *prevhosts);
 static	void	spf_ip(char *ip, int zero, void *v);
 
55144c47
 pthread_mutex_t	res_pool_mutex = PTHREAD_MUTEX_INITIALIZER;
 
 #ifdef HAVE_LRESOLV_R
8f8fa5de
 res_state res_pool;
 uint8_t *res_pool_state;
 pthread_cond_t res_pool_cond = PTHREAD_COND_INITIALIZER;
20699d4f
 
8f8fa5de
 int safe_res_query(const char *d, int c, int t, u_char *a, int l) {
 	int i = -1, ret;
 
 	pthread_mutex_lock(&res_pool_mutex);
 	while(i==-1) {
 		int j;
 		for(j=0; j<max_children+1; j++) {
 			if(!res_pool_state[j]) continue;
 			i = j;
 			break;
 		}
 		if(i!=-1) break;
 		pthread_cond_wait(&res_pool_cond, &res_pool_mutex);
 	}
 	res_pool_state[i]=0;
 	pthread_mutex_unlock(&res_pool_mutex);
 
 	ret = res_nquery(&res_pool[i], d, c, t, a, l);
   
 	pthread_mutex_lock(&res_pool_mutex);
 	res_pool_state[i]=1;
 	pthread_cond_signal(&res_pool_cond);
 	pthread_mutex_unlock(&res_pool_mutex);
 	return ret;
55144c47
 }
 
 #else /* !HAVE_LRESOLV_R - non thread safe resolver (old bsd's) */
 
 int safe_res_query(const char *d, int c, int t, u_char *a, int l) {
 	int ret;
 	pthread_mutex_lock(&res_pool_mutex);
 	ret = res_query(d, c, t, a, l);
 	pthread_mutex_unlock(&res_pool_mutex);
 	return ret;
 }
 
 #endif /* HAVE_LRESOLV_R */
 
20699d4f
 #endif /* HAVE_RESOLV_H */
8f8fa5de
 
e3aaff8e
 static void
 help(void)
 {
add738d2
 	printf("\n\tclamav-milter version %s\n", get_version());
83ed1043
 	puts("\tCopyright (C) 2007 Nigel Horne <njh@clamav.net>\n");
e3aaff8e
 
eba8ebeb
 	puts(_("\t--advisory\t\t-A\tFlag viruses rather than deleting them."));
3844e1f0
 	puts(_("\t--blacklist-time=SECS\t-k\tTime (in seconds) to blacklist an IP."));
f1617494
 	puts(_("\t--black-hole-mode\t\tDon't scan messages aliased to /dev/null."));
4c5e69c8
 #ifdef	BOUNCE
eba8ebeb
 	puts(_("\t--bounce\t\t-b\tSend a failure message to the sender."));
4c5e69c8
 #endif
5d79922b
 	puts(_("\t--broadcast\t\t-B [IFACE]\tBroadcast to a network manager when a virus is found."));
b76fc985
 	puts(_("\t--chroot=DIR\t\t-C DIR\tChroot to dir when starting."));
eba8ebeb
 	puts(_("\t--config-file=FILE\t-c FILE\tRead configuration from FILE."));
 	puts(_("\t--debug\t\t\t-D\tPrint debug messages."));
358facc3
 	puts(_("\t--detect-forged-local-address\t-L\tReject mails that claim to be from us."));
2ed3a9a3
 	puts(_("\t--dont-blacklist\t-K\tDon't blacklist a given IP."));
eba8ebeb
 	puts(_("\t--dont-scan-on-error\t-d\tPass e-mails through unscanned if a system error occurs."));
 	puts(_("\t--dont-wait\t\t\tAsk remote end to resend if max-children exceeded."));
f410a3aa
 	puts(_("\t--dont-sanitise\t\t\tAllow semicolon and pipe characters in email addresses."));
8528bc29
 	puts(_("\t--external\t\t-e\tUse an external scanner (usually clamd)."));
6bc34381
 	puts(_("\t--freshclam-monitor=SECS\t-M SECS\tHow often to check for database update."));
eba8ebeb
 	puts(_("\t--from=EMAIL\t\t-a EMAIL\tError messages come from here."));
 	puts(_("\t--force-scan\t\t-f\tForce scan all messages (overrides (-o and -l)."));
 	puts(_("\t--help\t\t\t-h\tThis message."));
 	puts(_("\t--headers\t\t-H\tInclude original message headers in the report."));
a0ff4f89
 	puts(_("\t--ignore IPaddr\t\t-I IPaddr\tAdd IPaddr to LAN IP list (see --local)."));
eba8ebeb
 	puts(_("\t--local\t\t\t-l\tScan messages sent from machines on our LAN."));
d23dc60e
 	puts(_("\t--max-childen\t\t-m\tMaximum number of concurrent scans."));
eba8ebeb
 	puts(_("\t--outgoing\t\t-o\tScan outgoing messages from this machine."));
 	puts(_("\t--noreject\t\t-N\tDon't reject viruses, silently throw them away."));
 	puts(_("\t--noxheader\t\t-n\tSuppress X-Virus-Scanned/X-Virus-Status headers."));
 	puts(_("\t--pidfile=FILE\t\t-i FILE\tLocation of pidfile."));
 	puts(_("\t--postmaster\t\t-p EMAIL\tPostmaster address [default=postmaster]."));
1f7da695
 	puts(_("\t--postmaster-only\t-P\tSend notifications only to the postmaster."));
eba8ebeb
 	puts(_("\t--quiet\t\t\t-q\tDon't send e-mail notifications of interceptions."));
31cd44cb
 	puts(_("\t--quarantine=USER\t-Q EMAIL\tQuarantine e-mail account."));
 	puts(_("\t--report-phish=EMAIL\t-r EMAIL\tReport phish to this email address."));
1a2b28a9
 	puts(_("\t--report-phish-false-positives=EMAIL\t-R EMAIL\tReport phish false positves to this email address."));
eba8ebeb
 	puts(_("\t--quarantine-dir=DIR\t-U DIR\tDirectory to store infected emails."));
 	puts(_("\t--server=SERVER\t\t-s SERVER\tHostname/IP address of server(s) running clamd (when using TCPsocket)."));
7b2de1a6
 	puts(_("\t--sendmail-cf=FILE\t\tLocation of the sendmail.cf file to verify"));
eba8ebeb
 	puts(_("\t--sign\t\t\t-S\tAdd a hard-coded signature to each scanned message."));
 	puts(_("\t--signature-file=FILE\t-F FILE\tLocation of signature file."));
 	puts(_("\t--template-file=FILE\t-t FILE\tLocation of e-mail template file."));
e8a362f9
 	puts(_("\t--template-headers=FILE\t\tLocation of e-mail headers for template file."));
eba8ebeb
 	puts(_("\t--timeout=SECS\t\t-T SECS\tTimeout waiting to childen to die."));
7b2de1a6
 	puts(_("\t--whitelist-file=FILE\t-W FILE\tLocation of the file of whitelisted addresses"));
eba8ebeb
 	puts(_("\t--version\t\t-V\tPrint the version number of this software."));
e3aaff8e
 #ifdef	CL_DEBUG
eba8ebeb
 	puts(_("\t--debug-level=n\t\t-x n\tSets the debug level to 'n'."));
e3aaff8e
 #endif
eba8ebeb
 	puts(_("\nFor more information type \"man clamav-milter\"."));
27395a6e
 	puts(_("For bug reports, please refer to http://www.clamav.net/bugs"));
e3aaff8e
 }
 
 int
 main(int argc, char **argv)
 {
 	extern char *optarg;
a11b9be3
 	int i, Bflag = 0, server = 0;
9c6b98a9
 	char *cfgfile = NULL;
2ed3a9a3
 	const char *wont_blacklist = NULL;
950ecb65
 	const struct cfgstruct *cpt;
1fcd39ef
 	char version[VERSION_LENGTH + 1];
03bc6e11
 	pthread_t tid;
9a03413e
 	struct rlimit rlim;
c17d8aba
 #ifdef	CL_DEBUG
4375361f
 	int consolefd;
 #endif
83ed1043
 
af23d049
 	/*
 	 * The SMFI_VERSION checks are for Sendmail 8.14, which I don't have
 	 * yet, so I can't verify them
 	 * Patch from Andy Fiddaman <clam@fiddaman.net>
 	 */
e3aaff8e
 	struct smfiDesc smfilter = {
 		"ClamAv", /* filter name */
 		SMFI_VERSION,	/* version code -- leave untouched */
3844e1f0
 		SMFIF_ADDHDRS|SMFIF_CHGHDRS,	/* flags - we add and delete headers */
df1ec3e8
 		clamfi_connect, /* connexion callback */
7089d180
 #ifdef	CL_DEBUG
 		clamfi_helo,	/* HELO filter callback */
 #else
 		NULL,
 #endif
e3aaff8e
 		clamfi_envfrom, /* envelope sender filter callback */
 		clamfi_envrcpt, /* envelope recipient filter callback */
27395a6e
 		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 */
df1ec3e8
 		clamfi_close,	/* connexion cleanup callback */
af23d049
 #if	SMFI_VERSION > 2
 		NULL,		/* Unrecognised command */
 #endif
 #if	SMFI_VERSION > 3
 		NULL,		/* DATA command callback */
 #endif
 #if	SMFI_VERSION >= 0x01000000
 		NULL,		/* Negotiation callback */
 #endif
e3aaff8e
 	};
 
44d08756
 #if defined(CL_DEBUG) && defined(C_LINUX)
 	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
 	if(setrlimit(RLIMIT_CORE, &rlim) < 0)
 		perror("setrlimit");
 #endif
a0ff4f89
 
a4371160
 	/*
1fcd39ef
 	 * Temporarily enter guessed value into version, will
a4371160
 	 * be overwritten later by the value returned by clamd
 	 */
1fcd39ef
 	snprintf(version, sizeof(version) - 1,
9148ec6d
 		"ClamAV version %s, clamav-milter version %s",
add738d2
 		cl_retver(), get_version());
9148ec6d
 
640dbd4c
 	progname = strrchr(argv[0], '/');
 	if(progname)
 		progname++;
 	else
 		progname = "clamav-milter";
 
eba8ebeb
 #ifdef	C_LINUX
 	setlocale(LC_ALL, "");
640dbd4c
 	bindtextdomain(progname, DATADIR"/clamav-milter/locale");
 	textdomain(progname);
eba8ebeb
 #endif
 
e3aaff8e
 	for(;;) {
 		int opt_index = 0;
4c5e69c8
 #ifdef	BOUNCE
e3aaff8e
 #ifdef	CL_DEBUG
f410a3aa
 		const char *args = "a:AbB:c:C:dDefF:I:i:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:x:z0:1:2";
e3aaff8e
 #else
f410a3aa
 		const char *args = "a:AbB:c:C:dDefF:I:i:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:z0:1:2";
e3aaff8e
 #endif
4c5e69c8
 #else	/*!BOUNCE*/
 #ifdef	CL_DEBUG
f410a3aa
 		const char *args = "a:AB:c:C:dDefF:I:i:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:x:z0:1:2";
4c5e69c8
 #else
f410a3aa
 		const char *args = "a:AB:c:C:dDefF:I:i:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:z0:1:2";
4c5e69c8
 #endif
 #endif	/*BOUNCE*/
e004f1c5
 
e3aaff8e
 		static struct option long_options[] = {
 			{
7ea21452
 				"from", 2, NULL, 'a'
502c8234
 			},
 			{
459b60af
 				"advisory", 0, NULL, 'A'
 			},
4c5e69c8
 #ifdef	BOUNCE
459b60af
 			{
e3aaff8e
 				"bounce", 0, NULL, 'b'
 			},
4c5e69c8
 #endif
e3aaff8e
 			{
5d79922b
 				"broadcast", 2, NULL, 'B'
152f7c23
 			},
 			{
e3aaff8e
 				"config-file", 1, NULL, 'c'
 			},
 			{
b76fc985
 				"chroot", 1, NULL, 'C'
 			},
 			{
358facc3
 				"detect-forged-local-address", 0, NULL, 'L'
 			},
 			{
2ed3a9a3
 				"dont-blacklist", 1, NULL, 'K'
 			},
 			{
68d5a5f3
 				"dont-scan-on-error", 0, NULL, 'd'
 			},
 			{
459b60af
 				"dont-wait", 0, NULL, 'w'
 			},
 			{
f410a3aa
 				"dont-sanitise", 0, NULL, 'z'
 			},
 			{
44d08756
 				"debug", 0, NULL, 'D'
 			},
 			{
8528bc29
 				"external", 0, NULL, 'e'
 			},
 			{
668c7570
 				"force-scan", 0, NULL, 'f'
2cd8b9d4
 			},
 			{
5b6bb93b
 				"headers", 0, NULL, 'H'
9e1e77b9
 			},
 			{
e3aaff8e
 				"help", 0, NULL, 'h'
 			},
 			{
feea329f
 				"ignore", 1, NULL, 'I'
 			},
a0ff4f89
 			{
e84cbd98
 				"pidfile", 1, NULL, 'i'
 			},
 			{
065207af
 				"blacklist-time", 1, NULL, 'k'
a0ff4f89
 			},
 			{
e3aaff8e
 				"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',
 			},
 			{
1a2b28a9
 				"report-phish", 1, NULL, 'r'
 			},
 			{
 				"report-phish-false-positives", 1, NULL, 'R'
31cd44cb
 			},
 			{
668c7570
 				"quarantine-dir", 1, NULL, 'U',
 			},
 			{
e3aaff8e
 				"max-children", 1, NULL, 'm'
 			},
6ee06700
 			{
6bc34381
 				"freshclam-monitor", 1, NULL, 'M'
 			},
 			{
 				"sendmail-cf", 1, NULL, '0'
 			},
e3aaff8e
 			{
 				"server", 1, NULL, 's'
 			},
 			{
fe3d8be8
 				"sign", 0, NULL, 'S'
 			},
 			{
 				"signature-file", 1, NULL, 'F'
fa9628f2
 			},
 			{
3ce543c7
 				"template-file", 1, NULL, 't'
 			},
 			{
e8a362f9
 				"template-headers", 1, NULL, '1'
 			},
 			{
4a944387
 				"timeout", 1, NULL, 'T'
 			},
 			{
7b2de1a6
 				"whitelist-file", 1, NULL, 'W'
 			},
 			{
e3aaff8e
 				"version", 0, NULL, 'V'
 			},
f1617494
 			{
 				"black-hole-mode", 0, NULL, '2'
 			},
e3aaff8e
 #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) {
502c8234
 			case 'a':	/* e-mail errors from here */
7ea21452
 				/*
 				 * optarg is optional - if you give --from
 				 * then the --from is set to the orginal,
 				 * probably forged, email address
 				 */
502c8234
 				from = optarg;
 				break;
459b60af
 			case 'A':
 				advisory++;
 				break;
4c5e69c8
 #ifdef	BOUNCE
e3aaff8e
 			case 'b':	/* bounce worms/viruses */
 				bflag++;
 				break;
4c5e69c8
 #endif
152f7c23
 			case 'B':	/* broadcast */
 				Bflag++;
5d79922b
 				if(optarg)
 					iface = optarg;
152f7c23
 				break;
02b1cb1f
 			case 'c':	/* where is clamd.conf? */
e3aaff8e
 				cfgfile = optarg;
 				break;
b76fc985
 			case 'C':	/* chroot */
 				rootdir = optarg;
 				break;
68d5a5f3
 			case 'd':	/* don't scan on error */
 				cl_error = SMFIS_ACCEPT;
 				break;
44d08756
 			case 'D':	/* enable debug messages */
 				cl_debug();
 				break;
8528bc29
 			case 'e':	/* use clamd */
 				external++;
 				break;
2cd8b9d4
 			case 'f':	/* force the scan */
 				fflag++;
 				break;
e3aaff8e
 			case 'h':
 				help();
 				return EX_OK;
9e1e77b9
 			case 'H':
 				hflag++;
 				break;
e84cbd98
 			case 'i':	/* pidfile */
 				pidfile = optarg;
 				break;
a0ff4f89
 			case 'k':	/* blacklist time */
 				blacklist_time = atoi(optarg);
 				break;
2ed3a9a3
 			case 'K':	/* don't black list given IP */
 				wont_blacklist = optarg;
 				break;
a0ff4f89
 			case 'I':	/* --ignore, -I hostname */
 				/*
 				 * Based on patch by jpd@louisiana.edu
 				 */
df1ec3e8
 				if(Iflag == IFLAG_MAX) {
feea329f
 					fprintf(stderr,
df1ec3e8
 						_("%s: %s, -I may only be given %d times\n"),
 							argv[0], optarg, IFLAG_MAX);
feea329f
 					return EX_USAGE;
a0ff4f89
 				}
df1ec3e8
 				if(!add_local_ip(optarg)) {
feea329f
 					fprintf(stderr,
df1ec3e8
 						_("%s: Cannot convert -I%s to IPaddr\n"),
a0ff4f89
 							argv[0], optarg);
feea329f
 					return EX_USAGE;
 				}
 				Iflag++;
 				break;
e3aaff8e
 			case 'l':	/* scan mail from the lan */
 				lflag++;
 				break;
358facc3
 			case 'L':	/* detect forged local addresses */
 				detect_forged_local_address++;
 				break;
049a18b9
 			case 'm':	/* maximum number of children */
 				max_children = atoi(optarg);
 				break;
6bc34381
 			case 'M':	/* how often to monitor for freshclam */
 				freshclam_monitor = atoi(optarg);
 				break;
7418fb74
 			case 'n':	/* don't add X-Virus-Scanned */
 				nflag++;
add5094f
 				smfilter.xxfi_flags &= ~(SMFIF_ADDHDRS|SMFIF_CHGHDRS);
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;
31cd44cb
 			case 'r':	/* report phishing here */
 				/* e.g. reportphishing@antiphishing.org */
 				report = optarg;
 				break;
1a2b28a9
 			case 'R':	/* report phishing false positives here */
 				report_fps = optarg;
 				break;
e3aaff8e
 			case 's':	/* server running clamd */
a11b9be3
 				server++;
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;
3ce543c7
 			case 't':	/* e-mail template file */
e8a362f9
 				templateFile = optarg;
 				break;
 			case '1':	/* headers for the template file */
 				templateHeaders = optarg;
3ce543c7
 				break;
f1617494
 			case '2':
 				black_hole_mode++;
 				break;
4a944387
 			case 'T':	/* time to wait for child to die */
 				child_timeout = atoi(optarg);
 				break;
668c7570
 			case 'U':	/* quarantine path */
 				quarantine_dir = optarg;
 				break;
e3aaff8e
 			case 'V':
1fcd39ef
 				puts(version);
e3aaff8e
 				return EX_OK;
459b60af
 			case 'w':
 				dont_wait++;
 				break;
7b2de1a6
 			case 'W':
 				whitelistFile = optarg;
 				break;
f410a3aa
 			case 'z':
 				dont_sanitise=1;
 				break;
7b2de1a6
 			case '0':
 				sendmailCF = optarg;
 				break;
e3aaff8e
 #ifdef	CL_DEBUG
 			case 'x':
 				debug_level = atoi(optarg);
 				break;
 #endif
 			default:
 #ifdef	CL_DEBUG
6bc34381
 				fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-e] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-s SERVER] [-S] [-x#] [-U PATH] [-M#] socket-addr\n", argv[0]);
e3aaff8e
 #else
6bc34381
 				fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-e] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-s SERVER] [-S] [-U PATH] [-M#] socket-addr\n", argv[0]);
e3aaff8e
 #endif
 				return EX_USAGE;
 		}
 	}
 
a11b9be3
 	/*
 	 * Check sanity of --external and --server arguments
 	 */
 	if(server && !external) {
 		fprintf(stderr,
 			"%s: --server can only be used with --external\n",
 			argv[0]);
 		return EX_USAGE;
 	}
2806296b
 #ifdef	SESSION
 	if(!external) {
 		fprintf(stderr,
a63603ad
 			_("%s: SESSIONS mode requires --external\n"), argv[0]);
2806296b
 		return EX_USAGE;
 	}
 #endif
a11b9be3
 
8528bc29
 	/* TODO: support freshclam's daemon notify if --external is not given */
 
bf16b485
 	if(optind == argc) {
eba8ebeb
 		fprintf(stderr, _("%s: No socket-addr given\n"), argv[0]);
e3aaff8e
 		return EX_USAGE;
 	}
 	port = argv[optind];
 
b76fc985
 	if(rootdir == NULL)	/* FIXME: Handle CHROOT */
 		if(verifyIncomingSocketName(port) < 0) {
 			fprintf(stderr, _("%s: socket-addr (%s) doesn't agree with sendmail.cf\n"), argv[0], port);
 			return EX_CONFIG;
 		}
 
4e9e7dcb
 	if(strncasecmp(port, "inet:", 5) == 0)
 		if(!lflag) {
 			/*
 			 * Barmy but true. It seems that clamfi_connect will,
 			 * in this case, get the IP address of the machine
 			 * running sendmail, not of the machine sending the
 			 * mail, so the remote end will be a local address so
 			 * we must scan by enabling --local
c5e2c4a9
 			 *
 			 * TODO: this is probably not needed if the remote
 			 * machine is localhost, need to check though
4e9e7dcb
 			 */
df1ec3e8
 			fprintf(stderr, _("%s: when using inet: connexion to sendmail you must enable --local\n"), argv[0]);
4e9e7dcb
 			return EX_USAGE;
 		}
19575eba
 
e3aaff8e
 	/*
 	 * Sanity checks on the clamav configuration file
 	 */
9c6b98a9
 	if(cfgfile == NULL) {
a0ff4f89
 		cfgfile = cli_malloc(strlen(CONFDIR) + 12);	/* leak */
9c6b98a9
 		sprintf(cfgfile, "%s/clamd.conf", CONFDIR);
 	}
 	if((copt = getcfg(cfgfile, 1)) == NULL) {
eba8ebeb
 		fprintf(stderr, _("%s: Can't parse the config file %s\n"),
e3aaff8e
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 
358facc3
 	if(detect_forged_local_address) {
 		if(oflag) {
 			fprintf(stderr, _("%s: --detect-forged-local-addresses is not compatible with --outgoing\n"), argv[0]);
 			return EX_CONFIG;
 		}
 		if(lflag) {
 			fprintf(stderr, _("%s: --detect-forged-local-addresses is not compatible with --local\n"), argv[0]);
 			return EX_CONFIG;
 		}
 		if(fflag) {
 			fprintf(stderr, _("%s: --detect-forged-local-addresses is not compatible with --force\n"), argv[0]);
 			return EX_CONFIG;
 		}
 	}
 
5d79922b
 	if(Bflag) {
 		int on;
 
 		broadcastSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
 		/*
 		 * SO_BROADCAST doesn't sent to all NICs on Linux, it only
 		 * broadcasts on eth0, which is why there's an optional argument
 		 * to --broadcast to say which NIC to broadcast on. You can use
 		 * SO_BINDTODEVICE to get around that, but you need to have
 		 * uid == 0 for that
 		 */
 		on = 1;
 		if(setsockopt(broadcastSock, SOL_SOCKET, SO_BROADCAST, (int *)&on, sizeof(on)) < 0) {
 			perror("setsockopt");
 			return EX_UNAVAILABLE;
 		}
 		shutdown(broadcastSock, SHUT_RD);
 	}
 
3aa15b4c
 	/*
 	 * Drop privileges
 	 */
c17d8aba
 #ifdef	CL_DEBUG
4375361f
 	/* Save the fd for later, open while we can */
 	consolefd = open(console, O_WRONLY);
 #endif
 
3aa15b4c
 	if(getuid() == 0) {
5d79922b
 		if(iface) {
c5a386b3
 #ifdef	SO_BINDTODEVICE
5d79922b
 			struct ifreq ifr;
 
 			memset(&ifr, '\0', sizeof(struct ifreq));
 			strncpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name) - 1);
72ce4b70
 			ifr.ifr_name[sizeof(ifr.ifr_name)-1]='\0';
5d79922b
 			if(setsockopt(broadcastSock, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0) {
 				perror(iface);
c5a386b3
 				return EX_CONFIG;
5d79922b
 			}
ccabb6be
 #else
c5a386b3
 			fprintf(stderr, _("%s: The iface option to --broadcast is not supported on your operating system\n"), argv[0]);
 			return EX_CONFIG;
ccabb6be
 #endif
c5a386b3
 		}
950ecb65
 
9c6b98a9
 		if(((cpt = cfgopt(copt, "User")) != NULL) && cpt->enabled) {
d5e48ace
 			const struct passwd *user;
 
3aa15b4c
 			if((user = getpwnam(cpt->strarg)) == NULL) {
eba8ebeb
 				fprintf(stderr, _("%s: Can't get information about user %s\n"), argv[0], cpt->strarg);
3aa15b4c
 				return EX_CONFIG;
 			}
e004f1c5
 
9aefc424
 			if(cfgopt(copt, "AllowSupplementaryGroups")->enabled) {
03d91eec
 #ifdef HAVE_INITGROUPS
 				if(initgroups(cpt->strarg, user->pw_gid) < 0) {
 					perror(cpt->strarg);
 					return EX_CONFIG;
 				}
 #else
eba8ebeb
 				fprintf(stderr, _("%s: AllowSupplementaryGroups: initgroups not supported.\n"),
03d91eec
 					argv[0]);
 				return EX_CONFIG;
 #endif
 			} else {
 #ifdef	HAVE_SETGROUPS
 				if(setgroups(1, &user->pw_gid) < 0) {
 					perror(cpt->strarg);
 					return EX_CONFIG;
 				}
 #endif
 			}
3aa15b4c
 
 			setgid(user->pw_gid);
f1617494
 
ba2dfeb2
 			if(setuid(user->pw_uid) < 0)
 				perror(cpt->strarg);
49766e16
 #ifdef	CL_DEBUG
ba2dfeb2
 			else
49766e16
 				printf(_("Running as user %s (UID %d, GID %d)\n"),
510212e4
 					cpt->strarg, (int)user->pw_uid,
 					(int)user->pw_gid);
49766e16
 #endif
f1617494
 
a474daaa
 			/*
 			 * Note, some O/Ss (e.g. OpenBSD/Fedora Linux) FORCE
 			 * you to run as root in black-hole-mode because
 			 * /var/spool/mqueue is mode 700 owned by root!
 			 * Flames to them, not to me, please.
 			 */
671973aa
 			if(black_hole_mode && (user->pw_uid != 0)) {
 				int are_trusted;
 				FILE *sendmail;
 				char cmd[128];
 
 				/*
 				 * Determine if we're a "trusted user"
 				 */
 				snprintf(cmd, sizeof(cmd) - 1, "%s -bv root</dev/null 2>&1",
 					SENDMAIL_BIN);
 
 				sendmail = popen(cmd, "r");
 
 				if(sendmail == NULL) {
 					perror(SENDMAIL_BIN);
 					are_trusted = 0;
 				} else {
55b25431
 					int status;
671973aa
 					char buf[BUFSIZ];
 
 					while(fgets(buf, sizeof(buf), sendmail) != NULL)
 						;
55b25431
 					/*
 					 * Can't do
 					 * switch(WEXITSTATUS(pclose(sendmail)))
 					 * because that fails to compile on
 					 * NetBSD2.0
 					 */
 					status = pclose(sendmail);
 					switch(WEXITSTATUS(status)) {
671973aa
 						case EX_NOUSER:
 							/*
 							 * No root? But at least
 							 * we're trusted enough
 							 * to find out!
 							 */
 							are_trusted = 1;
 							break;
 						default:
 							are_trusted = 0;
 							break;
 						case EX_OK:
 							are_trusted = 1;
 					}
 				}
 				if(!are_trusted) {
a474daaa
 					fprintf(stderr, _("%s: You cannot use black hole mode unless %s is a TrustedUser\n"),
 						argv[0], cpt->strarg);
671973aa
 					return EX_CONFIG;
 				}
 			}
 		} else
49766e16
 			printf(_("^%s: running as root is not recommended (check \"User\" in %s)\n"), argv[0], cfgfile);
5d79922b
 	} else if(iface) {
 		fprintf(stderr, _("%s: Only root can set an interface for --broadcast\n"), argv[0]);
 		return EX_USAGE;
3aa15b4c
 	}
5d79922b
 
459b60af
 	if(advisory && quarantine) {
eba8ebeb
 		fprintf(stderr, _("%s: Advisory mode doesn't work with quarantine mode\n"), argv[0]);
459b60af
 		return EX_USAGE;
 	}
bd547be2
 	if(quarantine_dir) {
 		struct stat statb;
 
459b60af
 		if(advisory) {
d982f8e4
 			fprintf(stderr,
 				_("%s: Advisory mode doesn't work with quarantine directories\n"),
 				argv[0]);
 			return EX_USAGE;
 		}
 		if(strstr(quarantine_dir, "ERROR") != NULL) {
 			fprintf(stderr,
 				_("%s: the quarantine directory must not contain the string 'ERROR'\n"),
 				argv[0]);
 			return EX_USAGE;
 		}
 		if(strstr(quarantine_dir, "FOUND") != NULL) {
 			fprintf(stderr,
 				_("%s: the quarantine directory must not contain the string 'FOUND'\n"),
 				argv[0]);
 			return EX_USAGE;
 		}
 		if(strstr(quarantine_dir, "OK") != NULL) {
 			fprintf(stderr,
 				_("%s: the quarantine directory must not contain the string 'OK'\n"),
 				argv[0]);
459b60af
 			return EX_USAGE;
 		}
bd547be2
 		if(access(quarantine_dir, W_OK) < 0) {
 			perror(quarantine_dir);
459b60af
 			return EX_USAGE;
bd547be2
 		}
 		if(stat(quarantine_dir, &statb) < 0) {
 			perror(quarantine_dir);
459b60af
 			return EX_USAGE;
bd547be2
 		}
 		/*
 		 * Quit if the quarantine directory is publically readable
 		 * or writeable
 		 */
 		if(statb.st_mode & 077) {
eba8ebeb
 			fprintf(stderr, _("%s: insecure quarantine directory %s (mode 0%o)\n"),
69d29ed1
 				argv[0], quarantine_dir, (int)statb.st_mode & 0777);
bd547be2
 			return EX_CONFIG;
 		}
668c7570
 	}
e004f1c5
 
fe3d8be8
 	if(sigFilename && !updateSigFile())
 		return EX_USAGE;
 
e8a362f9
 	if(templateFile && (access(templateFile, R_OK) < 0)) {
 		perror(templateFile);
0c933aca
 		return EX_CONFIG;
3ce543c7
 	}
e8a362f9
 	if(templateHeaders) {
 		if(templateFile == NULL) {
 			fputs(("%s: --template-headers requires --template-file\n"),
 				stderr);
 			return EX_CONFIG;
 		}
 		if(access(templateHeaders, R_OK) < 0) {
 			perror(templateHeaders);
 			return EX_CONFIG;
 		}
 	}
7f9a33ee
 	if(whitelistFile && (access(whitelistFile, R_OK) < 0)) {
2f5d67ba
 		perror(whitelistFile);
7f9a33ee
 		return EX_CONFIG;
 	}
3ce543c7
 
e3aaff8e
 	/*
2cd8b9d4
 	 * If the --max-children flag isn't set, see if MaxThreads
477961e9
 	 * is set in the config file. Based on an idea by "Richard G. Roberto"
 	 * <rgr@dedlegend.com>
2cd8b9d4
 	 */
23a00eaf
 	if((max_children == 0) && ((cpt = cfgopt(copt, "MaxThreads")) != NULL))
 		max_children = cfgopt(copt, "MaxThreads")->numarg;
2cd8b9d4
 
55144c47
 #ifdef HAVE_LRESOLV_R
8f8fa5de
 	/* allocate a pool of resolvers */
 	if(!(res_pool=cli_calloc(max_children+1, sizeof(*res_pool))))
 		return EX_OSERR;
 	if(!(res_pool_state=cli_malloc(max_children+1)))
 		return EX_OSERR;
 	memset(res_pool_state, 1, max_children+1);
 	for(i = 0; i < max_children+1; i++)
 		res_ninit(&res_pool[i]);
20699d4f
 #endif
8f8fa5de
 
23a00eaf
 	if((cpt = cfgopt(copt, "ReadTimeout")) != NULL) {
96f3d93b
 		readTimeout = cpt->numarg;
66ff992e
 
96f3d93b
 		if(readTimeout < 0) {
eba8ebeb
 			fprintf(stderr, _("%s: ReadTimeout must not be negative in %s\n"),
66ff992e
 				argv[0], cfgfile);
96f3d93b
 			return EX_CONFIG;
66ff992e
 		}
23a00eaf
 	}
950ecb65
 
23a00eaf
 	if((cpt = cfgopt(copt, "StreamMaxLength")) != NULL) {
 		streamMaxLength = (long)cpt->numarg;
 		if(streamMaxLength < 0L) {
eba8ebeb
 			fprintf(stderr, _("%s: StreamMaxLength must not be negative in %s\n"),
23a00eaf
 			argv[0], cfgfile);
96f3d93b
 			return EX_CONFIG;
 		}
 	}
d5e48ace
 
23a00eaf
 	if(((cpt = cfgopt(copt, "LogSyslog")) != NULL) && cpt->enabled) {
b43b45a9
 #if defined(USE_SYSLOG) && !defined(C_AIX)
d5e48ace
 		int fac = LOG_LOCAL6;
b43b45a9
 #endif
d5e48ace
 
821dd71e
 		if(cfgopt(copt, "LogVerbose")->enabled) {
 			logg_verbose = 1;
b4b5c17d
 #ifdef	CL_DEBUG
aaade7d3
 #if	((SENDMAIL_VERSION_A > 8) || ((SENDMAIL_VERSION_A == 8) && (SENDMAIL_VERSION_B >= 13)))
a343bd2b
 			if(debug_level >= 15)
821dd71e
 				smfi_setdbg(6);
333c62ba
 #endif
b4b5c17d
 #endif
821dd71e
 		}
83ed1043
 #if defined(USE_SYSLOG) && !defined(C_AIX)
 		logg_syslog = 1;
d5e48ace
 
9c6b98a9
 		if(((cpt = cfgopt(copt, "LogFacility")) != NULL) && cpt->enabled)
d5e48ace
 			if((fac = logg_facility(cpt->strarg)) == -1) {
 				fprintf(stderr, "%s: LogFacility: %s: No such facility\n",
 					argv[0], cpt->strarg);
 				return EX_CONFIG;
 			}
640dbd4c
 		openlog(progname, LOG_CONS|LOG_PID, fac);
b43b45a9
 #endif
d5e48ace
 	} else {
 		if(qflag)
 			fprintf(stderr, _("%s: (-q && !LogSyslog): warning - all interception message methods are off\n"),
 				argv[0]);
83ed1043
 #if defined(USE_SYSLOG) && !defined(C_AIX)
 		logg_syslog = 0;
 #endif
d5e48ace
 	}
2cd8b9d4
 	/*
d5e48ace
 	 * Get the outgoing socket details - the way to talk to clamd, unless
 	 * we're doing the scanning internally
e3aaff8e
 	 */
8528bc29
 	if(!external) {
53321388
 #ifdef	C_LINUX
 		const char *lang;
 #endif
 
93899a2c
 		if(max_children == 0) {
b7c71c9f
 			fprintf(stderr, _("%s: --max-children must be given if --external is not given\n"), argv[0]);
d5e48ace
 			return EX_CONFIG;
 		}
6bc34381
 		if(freshclam_monitor <= 0) {
 			fprintf(stderr, _("%s: --freshclam_monitor must be at least one second\n"), argv[0]);
 			return EX_CONFIG;
 		}
a32f3ba8
 #ifdef	C_LINUX
53321388
 		lang = getenv("LANG");
 
 		if(lang && (strstr(lang, "UTF-8") != NULL)) {
a32f3ba8
 			fprintf(stderr, "Your LANG environment variable is set to '%s'\n", lang);
53321388
 			fprintf(stderr, "This is known to cause problems for some %s installations.\n", argv[0]);
 			fputs("If you get failures with temporary files, please try again with LANG unset.\n", stderr);
 		}
 #endif
1cc545df
 #if	0
93899a2c
 		if(child_timeout) {
b7c71c9f
 			fprintf(stderr, _("%s: --timeout must not be given if --external is not given\n"), argv[0]);
d5e48ace
 			return EX_CONFIG;
 		}
1cc545df
 #endif
f4ab6fc6
 		if(loadDatabase() != 0) {
 			/*
 			 * Handle the dont-scan-on-error option, which says
 			 * that we pass on emails, unscanned, if an error has
 			 * occurred
 			 */
 			if(cl_error != SMFIS_ACCEPT)
 				return EX_CONFIG;
 
 			fprintf(stderr, _("%s: No emails will be scanned"),
 				argv[0]);
 		}
9c1c533d
 		numServers = 1;
9c6b98a9
 	} else if(((cpt = cfgopt(copt, "LocalSocket")) != NULL) && cpt->enabled) {
03bc6e11
 #ifdef	SESSION
cdf082d4
 		struct sockaddr_un sockun;
03bc6e11
 #endif
44ba5c0e
 		char *sockname = NULL;
03bc6e11
 
9c6b98a9
 		if(cfgopt(copt, "TCPSocket")->enabled) {
eba8ebeb
 			fprintf(stderr, _("%s: You can select one server type only (local/TCP) in %s\n"),
a4371160
 				argv[0], cfgfile);
 			return EX_CONFIG;
 		}
620ed9a7
 		if(server) {
 			fprintf(stderr, _("%s: You cannot use the --server option when using LocalSocket in %s\n"),
 				argv[0], cfgfile);
 			return EX_USAGE;
 		}
44ba5c0e
 		if(strncasecmp(port, "unix:", 5) == 0)
 			sockname = &port[5];
 		else if(strncasecmp(port, "local:", 6) == 0)
 			sockname = &port[6];
 
 		if(sockname && (strcmp(sockname, cpt->strarg) == 0)) {
df1ec3e8
 			fprintf(stderr, _("The connexion from sendmail to %s (%s) must not\n"),
44ba5c0e
 				argv[0], sockname);
df1ec3e8
 			fprintf(stderr, _("be the same as the connexion to clamd (%s) in %s\n"),
44ba5c0e
 				cpt->strarg, cfgfile);
 			return EX_CONFIG;
 		}
e3aaff8e
 		/*
 		 * TODO: check --server hasn't been set
 		 */
 		localSocket = cpt->strarg;
03bc6e11
 #ifndef	SESSION
44d08756
 		if(!pingServer(-1)) {
eba8ebeb
 			fprintf(stderr, _("Can't talk to clamd server via %s\n"),
a4371160
 				localSocket);
eba8ebeb
 			fprintf(stderr, _("Check your entry for LocalSocket in %s\n"),
a4371160
 				cfgfile);
 			return EX_CONFIG;
 		}
03bc6e11
 #endif
ea4465c4
 		/*if(quarantine_dir == NULL)
eba8ebeb
 			fprintf(stderr, _("When using Localsocket in %s\nyou may improve performance if you use the --quarantine-dir option\n"), cfgfile);*/
00727a0e
 
e84162a4
 		umask(077);
44d08756
 
3d1678b1
 		serverIPs = (in_addr_t *)cli_malloc(sizeof(in_addr_t));
 #ifdef	INADDR_LOOPBACK
83fc7301
 		serverIPs[0] = htonl(INADDR_LOOPBACK);
3d1678b1
 #else
44d08756
 		serverIPs[0] = inet_addr("127.0.0.1");
3d1678b1
 #endif
03bc6e11
 
 #ifdef	SESSION
cdf082d4
 		memset((char *)&sockun, 0, sizeof(struct sockaddr_un));
 		sockun.sun_family = AF_UNIX;
 		strncpy(sockun.sun_path, localSocket, sizeof(sockun.sun_path));
72ce4b70
 		sockun.sun_path[sizeof(sockun.sun_path)-1]='\0';
03bc6e11
 
ae0895b4
 		sessions = (struct session *)cli_malloc(sizeof(struct session));
 		if((sessions[0].sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
03bc6e11
 			perror(localSocket);
 			fprintf(stderr, _("Can't talk to clamd server via %s\n"),
 				localSocket);
 			fprintf(stderr, _("Check your entry for LocalSocket in %s\n"),
 				cfgfile);
 			return EX_CONFIG;
 		}
cdf082d4
 		if(connect(sessions[0].sock, (struct sockaddr *)&sockun, sizeof(struct sockaddr_un)) < 0) {
03bc6e11
 			perror(localSocket);
 			return EX_UNAVAILABLE;
 		}
3844e1f0
 		if(send(sessions[0].sock, "SESSION\n", 8, 0) < 8) {
03bc6e11
 			perror("send");
c17d8aba
 			fputs(_("!Can't create a clamd session"), stderr);
03bc6e11
 			return EX_UNAVAILABLE;
 		}
ae0895b4
 		sessions[0].status = CMDSOCKET_FREE;
03bc6e11
 #endif
44ba5c0e
 		/*
df1ec3e8
 		 * FIXME: Allow connexion to remote servers by TCP/IP whilst
44ba5c0e
 		 * connecting to the localserver via a UNIX domain socket
 		 */
03bc6e11
 		numServers = 1;
9c6b98a9
 	} else if(((cpt = cfgopt(copt, "TCPSocket")) != NULL) && cpt->enabled) {
06bfd678
 		int activeServers;
44d08756
 
e3aaff8e
 		/*
 		 * TCPSocket is in fact a port number not a full socket
 		 */
668c7570
 		if(quarantine_dir) {
835c9751
 			fprintf(stderr, _("%s: --quarantine-dir not supported for TCPSocket - use --quarantine\n"), argv[0]);
668c7570
 			return EX_CONFIG;
 		}
 
5b6bb93b
 		tcpSocket = (in_port_t)cpt->numarg;
44d08756
 
0e244102
 		/*
44d08756
 		 * cli_strtok's fieldno counts from 0
0e244102
 		 */
44d08756
 		for(;;) {
81bdf63b
 			char *hostname = cli_strtok(serverHostNames, numServers, ":");
44d08756
 			if(hostname == NULL)
 				break;
44ba5c0e
 #ifdef	MAXHOSTNAMELEN
 			if(strlen(hostname) > MAXHOSTNAMELEN) {
 				fprintf(stderr, _("%s: hostname %s is longer than %d characters\n"),
 					argv[0], hostname, MAXHOSTNAMELEN);
 				return EX_CONFIG;
 			}
 #endif
44d08756
 			numServers++;
 			free(hostname);
0e244102
 		}
 
49766e16
 #ifdef	CL_DEBUG
 		printf("numServers: %d\n", numServers);
 #endif
668c7570
 
3d1678b1
 		serverIPs = (in_addr_t *)cli_malloc(numServers * sizeof(in_addr_t));
9fe789f8
 		if(serverIPs == NULL)
 			return EX_OSERR;
736c8d91
 		activeServers = 0;
44d08756
 
03bc6e11
 #ifdef	SESSION
 		/*
df1ec3e8
 		 * We need to know how many connexion to establish to clamd
03bc6e11
 		 */
 		if(max_children == 0) {
944e61ea
 			fprintf(stderr, _("%s: --max-children must be given in sessions mode\n"), argv[0]);
03bc6e11
 			return EX_CONFIG;
 		}
 #endif
 
821dd71e
 		if(numServers > max_children) {
 			fprintf(stderr, _("%1$s: --max-children (%2$d) is lower than the number of servers you have (%3$d)\n"),
 				argv[0], max_children, numServers);
 			return EX_CONFIG;
 		}
 
44d08756
 		for(i = 0; i < numServers; i++) {
44ba5c0e
 #ifdef	MAXHOSTNAMELEN
 			char hostname[MAXHOSTNAMELEN + 1];
 
 			if(cli_strtokbuf(serverHostNames, i, ":", hostname) == NULL)
 				break;
 #else
81bdf63b
 			char *hostname = cli_strtok(serverHostNames, i, ":");
44ba5c0e
 #endif
44d08756
 
 			/*
 			 * Translate server's name to IP address
 			 */
 			serverIPs[i] = inet_addr(hostname);
a5717226
 #ifdef	INADDR_NONE
3d1678b1
 			if(serverIPs[i] == INADDR_NONE) {
a5717226
 #else
 			if(serverIPs[i] == (in_addr_t)-1) {
 #endif
44d08756
 				const struct hostent *h = gethostbyname(hostname);
 
 				if(h == NULL) {
eba8ebeb
 					fprintf(stderr, _("%s: Unknown host %s\n"),
44d08756
 						argv[0], hostname);
 					return EX_USAGE;
 				}
 
 				memcpy((char *)&serverIPs[i], h->h_addr, sizeof(serverIPs[i]));
 			}
 
83fc7301
 #if	defined(NTRIES) && ((NTRIES > 1))
03bc6e11
 #ifndef	SESSION
83fc7301
 #ifdef	INADDR_LOOPBACK
 			if(serverIPs[i] == htonl(INADDR_LOOPBACK)) {
 #else
f9d522ea
 #if	HAVE_IN_ADDR_T
 			if(serverIPs[i] == (in_addr_t)inet_addr("127.0.0.1")) {
 #else
 			if(serverIPs[i] == (long)inet_addr("127.0.0.1")) {
 #endif
83fc7301
 #endif
 				int tries;
 
530999cb
 				/*
 				 * Fudge to allow clamd to come up on
 				 * our local machine
 				 */
83fc7301
 				for(tries = 0; tries < NTRIES - 1; tries++) {
 					if(pingServer(i))
a98e7aab
 						break;
e1a1ce90
 					if(checkClamd(1))	/* will try all servers */
83fc7301
 						break;
49766e16
 					puts(_("Waiting for clamd to come up"));
83fc7301
 					/*
 					 * something to do as the system starts
 					 */
 					sync();
 					sleep(1);
 				}
 				/* Will try one more time */
530999cb
 			}
83fc7301
 #endif	/* NTRIES > 1 */
530999cb
 
736c8d91
 			if(pingServer(i))
 				activeServers++;
 			else {
49766e16
 				printf(_("Can't talk to clamd server %s on port %d\n"),
44d08756
 					hostname, tcpSocket);
83fc7301
 				if(serverIPs[i] == htonl(INADDR_LOOPBACK)) {
9c6b98a9
 					if(cfgopt(copt, "TCPAddr")->enabled)
49766e16
 						printf(_("Check the value for TCPAddr in %s\n"), cfgfile);
e13e1f7c
 				} else
49766e16
 					printf(_("Check the value for TCPAddr in clamd.conf on %s\n"), hostname);
44d08756
 			}
03bc6e11
 #endif
44ba5c0e
 
 #ifndef	MAXHOSTNAMELEN
44d08756
 			free(hostname);
44ba5c0e
 #endif
e3aaff8e
 		}
03bc6e11
 #ifdef	SESSION
 		activeServers = numServers;
1fcd39ef
 
ae0895b4
 		sessions = (struct session *)cli_calloc(max_children, sizeof(struct session));
89a2d133
 		for(i = 0; i < (int)max_children; i++)
03bc6e11
 			if(createSession(i) < 0)
 				return EX_UNAVAILABLE;
add1de69
 		if(activeServers == 0) {
49766e16
 			fprintf(stderr, _("Check your entry for TCPSocket in %s\n"),
add1de69
 				cfgfile);
 		}
03bc6e11
 #else
736c8d91
 		if(activeServers == 0) {
49766e16
 			fprintf(stderr, _("Check your entry for TCPSocket in %s\n"),
736c8d91
 				cfgfile);
49766e16
 			fputs(_("Can't find any clamd server\n"), stderr);
736c8d91
 			return EX_CONFIG;
 		}
3844e1f0
 		last_failed_pings = (time_t *)cli_calloc(numServers, sizeof(time_t));
03bc6e11
 #endif
e3aaff8e
 	} else {
eba8ebeb
 		fprintf(stderr, _("%s: You must select server type (local/TCP) in %s\n"),
e3aaff8e
 			argv[0], cfgfile);
 		return EX_CONFIG;
 	}
 
d982f8e4
 #ifdef	SESSION
8528bc29
 	if(!external) {
93899a2c
 		if(clamav_versions == NULL) {
 			clamav_versions = (char **)cli_malloc(sizeof(char *));
 			if(clamav_versions == NULL)
 				return EX_TEMPFAIL;
16d1f4d0
 			clamav_version = cli_strdup(version);
93899a2c
 		}
d5e48ace
 	} else {
89a2d133
 		unsigned int session;
 
e33dc0e9
 		/*
9fe789f8
 		 * We need to know how many connexions to establish to clamd
e33dc0e9
 		 */
 		if(max_children == 0) {
 			fprintf(stderr, _("%s: --max-children must be given in sessions mode\n"), argv[0]);
 			return EX_CONFIG;
 		}
 
9fcf8647
 		clamav_versions = (char **)cli_malloc(max_children * sizeof(char *));
d5e48ace
 		if(clamav_versions == NULL)
1c916533
 			return EX_TEMPFAIL;
d5e48ace
 
89a2d133
 		for(session = 0; session < max_children; session++) {
16d1f4d0
 			clamav_versions[session] = cli_strdup(version);
89a2d133
 			if(clamav_versions[session] == NULL)
d5e48ace
 				return EX_TEMPFAIL;
 		}
 	}
8a7ef08f
 #else
 	strcpy(clamav_version, version);
 #endif
1c916533
 
7b2de1a6
 	if(((quarantine_dir == NULL) && localSocket) || !external) {
235ab38f
 		/* set the temporary dir */
9c6b98a9
 		if((cpt = cfgopt(copt, "TemporaryDirectory")) && cpt->enabled) {
235ab38f
 			tmpdir = cpt->strarg;
9c6b98a9
 			cl_settempdir(tmpdir, (short)(cfgopt(copt, "LeaveTemporaryFiles")->enabled));
31ee8076
 		} else if((tmpdir = getenv("TMPDIR")) == (char *)NULL)
235ab38f
 			if((tmpdir = getenv("TMP")) == (char *)NULL)
 				if((tmpdir = getenv("TEMP")) == (char *)NULL)
 #ifdef	P_tmpdir
 					tmpdir = P_tmpdir;
 #else
 					tmpdir = "/tmp";
 #endif
 
1d05987b
 		/*
 		 * TODO: investigate mkdtemp on LINUX and possibly others
 		 */
31ee8076
 		tmpdir = cli_gentemp(NULL);
235ab38f
 
7b2de1a6
 		cli_dbgmsg("Making %s\n", tmpdir);
 
235ab38f
 		if(mkdir(tmpdir, 0700)) {
 			perror(tmpdir);
 			return EX_CANTCREAT;
 		}
 	} else
 		tmpdir = NULL;
 
1b798a3a
 	if(report) {
ab9079a4
 		if(!cfgopt(copt, "PhishingSignatures")->enabled) {
1a2b28a9
 			fprintf(stderr, "%s: You have chosen --report-phish, but PhishingSignatures is off in %s\n",
cbdb077f
 				argv[0], cfgfile);
 			return EX_USAGE;
 		}
1b798a3a
 		if((quarantine_dir == NULL) && (tmpdir == NULL)) {
 			/*
 			 * Limitation: doesn't store message in a temporary
 			 * file, so we won't be able to use mail < file
 			 */
 			fprintf(stderr, "%s: when using --external, --report-phish cannot be used without either LocalSocket or --quarantine\n",
 				argv[0]);
 			return EX_USAGE;
 		}
 		if(lflag) {
 			/*
 			 * Naturally, if you attempt to scan the phish you've
 			 * just reported, it'll be blocked!
 			 */
 			fprintf(stderr, "%s: --report-phish cannot be used with --local\n",
 				argv[0]);
 			return EX_USAGE;
 		}
 	}
1a2b28a9
 	if(report_fps)
 		if(!cfgopt(copt, "PhishingSignatures")->enabled) {
 			fprintf(stderr, "%s: You have chosen --report-phish-false-positives, but PhishingSignatures is off in %s\n",
 				argv[0], cfgfile);
 			return EX_USAGE;
 		}
1b798a3a
 
821dd71e
 	if(cfgopt(copt, "Foreground")->enabled)
 		logg_foreground = 1;
 	else {
c17d8aba
 		logg_foreground = 0;
fe3d8be8
 #ifdef	CL_DEBUG
eba8ebeb
 		printf(_("When debugging it is recommended that you use Foreground mode in %s\n"), cfgfile);
d5e48ace
 		puts(_("\tso that you can see all of the messages"));
fe3d8be8
 #endif
 
e3aaff8e
 		switch(fork()) {
 			case -1:
 				perror("fork");
1fcd39ef
 				return EX_OSERR;
e3aaff8e
 			case 0:	/* child */
 				break;
 			default:	/* parent */
 				return EX_OK;
 		}
a4371160
 		close(0);
b5c80361
 		open("/dev/null", O_RDONLY);
 
c17d8aba
 		/* initialize logger */
 		logg_lock = cfgopt(copt, "LogFileUnlock")->enabled;
 		logg_time = cfgopt(copt, "LogTime")->enabled;
 		logok = cfgopt(copt, "LogClean")->enabled;
 		logg_size = cfgopt(copt, "LogFileMaxSize")->numarg;
 		logg_verbose = mprintf_verbose = cfgopt(copt, "LogVerbose")->enabled;
19575eba
 
c17d8aba
 		if(cfgopt(copt, "Debug")->enabled) /* enable debug messages in libclamav */
040708a3
 			cl_debug();
c17d8aba
 
 		if((cpt = cfgopt(copt, "LogFile"))->enabled) {
 			time_t currtime;
 
 			logg_file = cpt->strarg;
 			if((strlen(logg_file) < 2) ||
 			   ((logg_file[0] != '/') && (logg_file[0] != '\\') && (logg_file[1] != ':'))) {
 				fprintf(stderr, "ERROR: LogFile requires full path.\n");
 				logg_close();
 				freecfg(copt);
 				return 1;
19575eba
 			}
c17d8aba
 			time(&currtime);
040708a3
 			close(1);
c17d8aba
 			if(logg("#ClamAV-milter started at %s", ctime(&currtime))) {
 				fprintf(stderr, "ERROR: Problem with internal logger. Please check the permissions on the %s file.\n", logg_file);
 				logg_close();
 				freecfg(copt);
 				return 1;
a7d8f61f
 			}
 		} else {
c17d8aba
 #ifdef	CL_DEBUG
040708a3
 			close(1);
c17d8aba
 			logg_file = console;
4375361f
 			if(consolefd < 0) {
 				perror(console);
a7d8f61f
 				return EX_OSFILE;
 			}
4375361f
 			dup(consolefd);
c17d8aba
 #else
 			logg_file = NULL;
 #endif
a7d8f61f
 		}
c17d8aba
 
a7d8f61f
 		close(2);
 		dup(1);
c17d8aba
 
 #ifdef	CL_DEBUG
4375361f
 		if(consolefd >= 0)
 			close(consolefd);
c17d8aba
 #endif
b5c80361
 
2defd014
 #ifdef HAVE_SETPGRP
 #ifdef SETPGRP_VOID
dad136d5
 		setpgrp();
2defd014
 #else
 		setpgrp(0,0);
 #endif
 #else
 #ifdef HAVE_SETSID
51f29138
 		setsid();
2defd014
 #endif
 #endif
a4371160
 	}
e3aaff8e
 
821dd71e
 	if(cfgopt(copt, "Debug")->enabled)
 		/*
 		 * enable debug messages in libclamav, --debug also does this
 		 */
 		cl_debug();
b4b5c17d
 
152f7c23
 	atexit(quit);
 
8528bc29
 	if(!external) {
950ecb65
 		/* TODO: read the limits from clamd.conf */
 
9c6b98a9
 		if(!cfgopt(copt, "ScanMail")->enabled)
 			printf(_("%s: ScanMail not defined in %s (needed without --external), enabling\n"),
 				argv[0], cfgfile);
 
950ecb65
 		options |= CL_SCAN_MAIL;	/* no choice */
9c6b98a9
 		/*if(!cfgopt(copt, "ScanRAR")->enabled)
 			options |= CL_SCAN_DISABLERAR;*/
 		if(cfgopt(copt, "ArchiveBlockEncrypted")->enabled)
950ecb65
 			options |= CL_SCAN_BLOCKENCRYPTED;
9c6b98a9
 		if(cfgopt(copt, "ScanPE")->enabled)
950ecb65
 			options |= CL_SCAN_PE;
9c6b98a9
 		if(cfgopt(copt, "DetectBrokenExecutables")->enabled)
950ecb65
 			options |= CL_SCAN_BLOCKBROKEN;
9c6b98a9
 		if(cfgopt(copt, "MailFollowURLs")->enabled)
950ecb65
 			options |= CL_SCAN_MAILURL;
9c6b98a9
 		if(cfgopt(copt, "ScanOLE2")->enabled)
950ecb65
 			options |= CL_SCAN_OLE2;
9c6b98a9
 		if(cfgopt(copt, "ScanHTML")->enabled)
950ecb65
 			options |= CL_SCAN_HTML;
 
 		memset(&limits, '\0', sizeof(struct cl_limits));
 
a5d91be7
 		if(((cpt = cfgopt(copt, "MaxScanSize")) != NULL) && cpt->enabled)
 			limits.maxscansize = cpt->numarg;
 		else
 			limits.maxscansize = 104857600;
 		if(((cpt = cfgopt(copt, "MaxFileSize")) != NULL) && cpt->enabled)
 			limits.maxfilesize = cpt->numarg;
 		else
 			limits.maxfilesize = 10485760;
 
9a03413e
 		if(getrlimit(RLIMIT_FSIZE, &rlim) == 0) {
 			if((rlim.rlim_max < limits.maxfilesize) || (rlim.rlim_max < limits.maxscansize))
 				logg("^System limit for file size is lower than maxfilesize or maxscansize\n");
 		} else {
 			logg("^Cannot obtain resource limits for file size\n");
 		}
 
a5d91be7
 		if(((cpt = cfgopt(copt, "MaxRecursion")) != NULL) && cpt->enabled)
 			limits.maxreclevel = cpt->numarg;
 		else
 			limits.maxreclevel = 8;
 
 		if(((cpt = cfgopt(copt, "MaxFiles")) != NULL) && cpt->enabled)
 			limits.maxfiles = cpt->numarg;
 		else
 			limits.maxfiles = 1000;
23a00eaf
 
9c6b98a9
 		if(cfgopt(copt, "ScanArchive")->enabled) {
950ecb65
 			options |= CL_SCAN_ARCHIVE;
9c6b98a9
 			if(cfgopt(copt, "ArchiveLimitMemoryUsage")->enabled)
950ecb65
 				limits.archivememlim = 1;
 			else
 				limits.archivememlim = 0;
 		}
 	}
 
f1617494
 	pthread_create(&tid, NULL, watchdog, NULL);
1f82a167
 
9c6b98a9
 	if(((cpt = cfgopt(copt, "PidFile")) != NULL) && cpt->enabled)
b5648b5a
 		pidFile = cpt->strarg;
 
ccabb6be
 	broadcast(_("Starting clamav-milter"));
e3aaff8e
 
b76fc985
 	if(rootdir) {
 		if(getuid() == 0) {
 			if(chdir(rootdir) < 0) {
 				perror(rootdir);
1a2b28a9
 				logg("!chdir %s failed\n", rootdir);
b76fc985
 				return EX_CONFIG;
 			}
 			if(chroot(rootdir) < 0) {
 				perror(rootdir);
1a2b28a9
 				logg("!chroot %s failed\n", rootdir);
b76fc985
 				return EX_CONFIG;
 			}
 			logg("Chrooted to %s\n", rootdir);
 		} else {
 			logg("!chroot option needs root\n");
 			return EX_CONFIG;
 		}
 	}
 
e84cbd98
 	if(pidfile) {
 		/* save the PID */
a7d8f61f
 		char *p, *q;
e84cbd98
 		FILE *fd;
 		const mode_t old_umask = umask(0006);
 
a7d8f61f
 		if(pidfile[0] != '/') {
83ed1043
 			logg(_("!pidfile: '%s' must be a full pathname"),
 				pidfile);
a7d8f61f
 
 			return EX_CONFIG;
 		}
16d1f4d0
 		p = cli_strdup(pidfile);
a7d8f61f
 		q = strrchr(p, '/');
 		*q = '\0';
 
b76fc985
 		if(rootdir == NULL)
 			if(chdir(p) < 0)	/* safety */
 				perror(p);
 
a7d8f61f
 		free(p);
 
e84cbd98
 		if((fd = fopen(pidfile, "w")) == NULL) {
3d9fff4d
 			logg(_("!Can't save PID in file %s\n"), pidfile);
4e9e7dcb
 			return EX_CONFIG;
a7d8f61f
 		}
dfa70a67
 #ifdef	C_LINUX
a7d8f61f
 		/* Ensure that all threads are kill()ed */
 		fprintf(fd, "-%d\n", (int)getpgrp());
dfa70a67
 #else
a7d8f61f
 		fprintf(fd, "%d\n", (int)getpid());
dfa70a67
 #endif
a7d8f61f
 		fclose(fd);
e84cbd98
 		umask(old_umask);
b76fc985
 	} else if(tmpdir) {
 		if(rootdir == NULL)
 			chdir(tmpdir);	/* safety */
 	} else
 		if(rootdir == NULL)
a7d8f61f
 #ifdef	P_tmpdir
b76fc985
 			chdir(P_tmpdir);
a7d8f61f
 #else
b76fc985
 			chdir("/tmp");
a7d8f61f
 #endif
e84cbd98
 
9c6b98a9
 	if(cfgopt(copt, "FixStaleSocket")->enabled) {
db035545
 		/*
 		 * Get the incoming socket details - the way sendmail talks to
 		 * us
 		 *
3aa15b4c
 		 * TODO: There's a security problem here that'll need fixing if
02b1cb1f
 		 * the User entry of clamd.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]);
3844e1f0
 		} else if(port[0] == '/') {
 			if(unlink(port) < 0)
 				if(errno != ENOENT)
 					perror(port);
db035545
 		}
 	}
e3aaff8e
 
e84162a4
 	if(smfi_setconn(port) == MI_FAILURE) {
51f29138
 		cli_errmsg("smfi_setconn failure\n");
e84162a4
 		return EX_SOFTWARE;
 	}
 
e3aaff8e
 	if(smfi_register(smfilter) == MI_FAILURE) {
49766e16
 		fprintf(stderr, "smfi_register failure, ensure that you have linked against the correct version of sendmail\n");
e3aaff8e
 		return EX_UNAVAILABLE;
 	}
 
aaade7d3
 #if	((SENDMAIL_VERSION_A > 8) || ((SENDMAIL_VERSION_A == 8) && (SENDMAIL_VERSION_B >= 13)))
333c62ba
 	if(smfi_opensocket(1) == MI_FAILURE) {
b76fc985
 		perror(port);
49766e16
 		fprintf(stderr, "Can't open/create %s\n", port);
333c62ba
 		return EX_CONFIG;
 	}
 #endif
51f29138
 
a7d8f61f
 	signal(SIGPIPE, SIG_IGN);	/* libmilter probably does this as well */
9a03413e
 	signal(SIGXFSZ, SIG_IGN); /* TODO: check if it's safe to call signal() here */
e3aaff8e
 
8a7ef08f
 #ifdef	SESSION
0863025b
 	pthread_mutex_lock(&version_mutex);
8a7ef08f
 #endif
16d1f4d0
 	logg(_("Starting %s\n"), clamav_version);
 	logg(_("*Debugging is on\n"));
d5e48ace
 
0217341d
 #ifdef HAVE_RESOLV_H
ead6d252
 	if(!(_res.options&RES_INIT))
 		if(res_init() < 0) {
 			fprintf(stderr, "%s: Can't initialise the resolver\n",
 				argv[0]);
 			return EX_UNAVAILABLE;
 		}
 
a0ff4f89
 	if(blacklist_time) {
93928eab
 		char name[MAXHOSTNAMELEN + 1];
 
 		if(gethostname(name, sizeof(name)) < 0) {
 			perror("gethostname");
 			return EX_UNAVAILABLE;
 		}
 
 		blacklist = mx(name, NULL);
a0ff4f89
 		if(blacklist)
 			/* We must never blacklist ourself */
 			tableInsert(blacklist, "127.0.0.1", 0);
2ed3a9a3
 
 		if(wont_blacklist) {
f9d522ea
 			char *w;
4ac317d6
 
 			i = 0;
 			while((w = cli_strtok(wont_blacklist, i++, ",")) != NULL) {
 				(void)tableInsert(blacklist, w, 0);
 				free(w);
 			}
2ed3a9a3
 		}
93928eab
 		tableIterate(blacklist, dump_blacklist, NULL);
a0ff4f89
 	}
0217341d
 #endif /* HAVE_RESOLV_H */
a0ff4f89
 
8a7ef08f
 #ifdef	SESSION
0863025b
 	pthread_mutex_unlock(&version_mutex);
8a7ef08f
 #endif
ba2dfeb2
 
9dac9585
 	(void)signal(SIGSEGV, sigsegv);
49766e16
 	if(!logg_foreground)
 		(void)signal(SIGHUP, sighup);
040708a3
 	if(!external)
 		(void)signal(SIGUSR2, sigusr2);
9dac9585
 
e3aaff8e
 	return smfi_main();
 }
 
03bc6e11
 #ifdef	SESSION
 /*
  * Use the SESSION command of clamd.
  * Returns -1 for terminal failure, 0 for OK, 1 for nonterminal failure
ae0895b4
  * The caller must take care of locking the sessions array
03bc6e11
  */
 static int
89a2d133
 createSession(unsigned int s)
03bc6e11
 {
7c9b51b6
 	int ret = 0, fd;
ae0895b4
 	const int serverNumber = s % numServers;
 	struct session *session = &sessions[s];
eaf74461
 	const struct protoent *proto;
3844e1f0
 	struct sockaddr_in server;
03bc6e11
 
9dac9585
 	cli_dbgmsg("createSession session %d, server %d\n", s, serverNumber);
9c1c533d
 	assert(s < max_children);
 
03bc6e11
 	memset((char *)&server, 0, sizeof(struct sockaddr_in));
 	server.sin_family = AF_INET;
 	server.sin_port = (in_port_t)htons(tcpSocket);
 
 	server.sin_addr.s_addr = serverIPs[serverNumber];
 
7c9b51b6
 	session->sock = -1;
0b768f73
 	proto = getprotobyname("tcp");
eaf74461
 	if(proto == NULL) {
 		fputs("Unknown prototol tcp, check /etc/protocols\n", stderr);
8cc2edc9
 		fd = ret = -1;
0b768f73
 	} else if((fd = socket(AF_INET, SOCK_STREAM, proto->p_proto)) < 0) {
03bc6e11
 		perror("socket");
32e5b819
 		ret = -1;
7c9b51b6
 	} else if(connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
03bc6e11
 		perror("connect");
32e5b819
 		ret = 1;
3844e1f0
 	} else if(send(fd, "SESSION\n", 8, 0) < 8) {
32e5b819
 		perror("send");
 		ret = 1;
03bc6e11
 	}
32e5b819
 
 	if(ret != 0) {
44ba5c0e
 #ifdef	MAXHOSTNAMELEN
 		char hostname[MAXHOSTNAMELEN + 1];
 
 		cli_strtokbuf(serverHostNames, serverNumber, ":", hostname);
bf16b485
 		if(strcmp(hostname, "127.0.0.1") == 0)
 			gethostname(hostname, sizeof(hostname));
44ba5c0e
 #else
03bc6e11
 		char *hostname = cli_strtok(serverHostNames, serverNumber, ":");
44ba5c0e
 #endif
 
7c9b51b6
 		session->status = CMDSOCKET_DOWN;
 
 		if(fd >= 0)
 			close(fd);
1fcd39ef
 
03bc6e11
 		cli_warnmsg(_("Check clamd server %s - it may be down\n"), hostname);
44ba5c0e
 #ifndef	MAXHOSTNAMELEN
03bc6e11
 		free(hostname);
44ba5c0e
 #endif
03bc6e11
 
9dac9585
 		broadcast(_("Check clamd server - it may be down"));
 	} else
 		session->sock = fd;
1fcd39ef
 
32e5b819
 	return ret;
03bc6e11
 }
 
 #else
 
e3aaff8e
 /*
  * Verify that the server is where we think it is
  * Returns true or false
44d08756
  *
736c8d91
  * serverNumber counts from 0, but is only used for TCPSocket
e3aaff8e
  */
 static int
44d08756
 pingServer(int serverNumber)
e3aaff8e
 {
a4371160
 	char *ptr;
eaf74461
 	int sock;
 	long 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));
72ce4b70
 		server.sun_path[sizeof(server.sun_path)-1]='\0';
a4371160
 
 		if((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
06bfd678
 			perror(localSocket);
a4371160
 			return 0;
 		}
83fc7301
 		checkClamd(1);
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;
9fe789f8
 		char *hostname;
a4371160
 
 		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);
a5717226
 #ifdef	INADDR_NONE
3d1678b1
 		assert(serverIPs[0] != INADDR_NONE);
a5717226
 #else
 		assert(serverIPs[0] != (in_addr_t)-1);
 #endif
0e244102
 
44d08756
 		server.sin_addr.s_addr = serverIPs[serverNumber];
a4371160
 
 		if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
 			perror("socket");
 			return 0;
 		}
9fe789f8
 		hostname = cli_strtok(serverHostNames, serverNumber, ":");
1a2b28a9
 		/*
9fe789f8
 		 * FIXME: use non-blocking connect, once the code is
 		 * amalgomated
1a2b28a9
 		 */
9fe789f8
 		if(nonblock_connect(sock, &server, hostname) < 0) {
a0ff4f89
 			int is_connected = 0;
 
83fc7301
 #if	(!defined(NTRIES)) || ((NTRIES <= 1))
a0ff4f89
 			if(errno == ECONNREFUSED) {
 				/*
 				 * During startup there is a race condition:
 				 * clamd can start and fork, then rc will start
 				 * clamav-milter before clamd has run accept(2),
 				 * so we fail to connect.
 				 * In case this is the situation here, we wait
 				 * for a couple of seconds and try again. The
 				 * sync() is because during startup the machine
 				 * won't be doing much for most of the time, so
 				 * we may as well do something constructive!
 				 */
 				sync();
 				sleep(2);
9fe789f8
 				if(nonblock_connect(sock, &server, hostname) >= 0)
a0ff4f89
 					is_connected = 1;
 			}
83fc7301
 #endif
a0ff4f89
 			if(!is_connected) {
9fe789f8
 				if(errno != EINPROGRESS)
 					perror(hostname ? hostname : "connect");
36512a76
 				close(sock);
83fc7301
 				if(hostname)
 					free(hostname);
36512a76
 				return 0;
 			}
a4371160
 		}
9fe789f8
 		if(hostname)
 			free(hostname);
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"
 	 */
152f7c23
 	cli_dbgmsg("pingServer%d: sending VERSION\n", serverNumber);
a4371160
 	if(send(sock, "VERSION\n", 8, 0) < 8) {
e3aaff8e
 		perror("send");
4a5aa10e
 		return close(sock);
e3aaff8e
 	}
 
 	shutdown(sock, SHUT_WR);
 
679c573b
 	nbytes = clamd_recv(sock, buf, sizeof(buf) - 1);
e3aaff8e
 
 	close(sock);
 
 	if(nbytes < 0) {
 		perror("recv");
 		return 0;
 	}
66ff992e
 	if(nbytes == 0)
 		return 0;
 
679c573b
 	buf[nbytes] = '\0';
e3aaff8e
 
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
aead37b9
 	 *	are out of date (say more than a month old)
a4371160
 	 */
1fcd39ef
 	snprintf(clamav_version, sizeof(clamav_version) - 1,
06bfd678
 		"%s\n\tclamav-milter version %s",
add738d2
 		buf, get_version());
e004f1c5
 
a4371160
 	return 1;
e3aaff8e
 }
03bc6e11
 #endif
e3aaff8e
 
44d08756
 /*
736c8d91
  * Find the best server to connect to. No intelligence to this.
  * It is best to weight the order of the servers from most wanted to least
  * wanted
44d08756
  *
d982f8e4
  * Return value is from 0 - index into sessions array
736c8d91
  *
  * If the load balancing fails return the first server in the list, not
  * an error, to be on the safe side
44d08756
  */
03bc6e11
 #ifdef	SESSION
 static int
 findServer(void)
 {
89a2d133
 	unsigned int i, j;
ae0895b4
 	struct session *session;
03bc6e11
 
 	/*
 	 * FIXME: Sessions code isn't flexible at handling servers
ae0895b4
 	 *	appearing and disappearing, e.g. sessions[n_children].sock == -1
03bc6e11
 	 */
51f29138
 	i = 0;
03bc6e11
 	pthread_mutex_lock(&n_children_mutex);
 	assert(n_children > 0);
 	assert(n_children <= max_children);
9c1c533d
 	j = n_children - 1;
03bc6e11
 	pthread_mutex_unlock(&n_children_mutex);
 
 	pthread_mutex_lock(&sstatus_mutex);
9c1c533d
 	for(; i < max_children; i++) {
7c9b51b6
 		const int sess = (j + i) % max_children;
 
 		session = &sessions[sess];
 		cli_dbgmsg("findServer: try server %d\n", sess);
ae0895b4
 		if(session->status == CMDSOCKET_FREE) {
 			session->status = CMDSOCKET_INUSE;
03bc6e11
 			pthread_mutex_unlock(&sstatus_mutex);
7c9b51b6
 			return sess;
03bc6e11
 		}
9c1c533d
 	}
03bc6e11
 	pthread_mutex_unlock(&sstatus_mutex);
 
d982f8e4
 	/*
 	 * No session free - wait until one comes available. Only
 	 * retries once.
 	 */
1f82a167
 	if(pthread_cond_broadcast(&watchdog_cond) < 0)
 		perror("pthread_cond_broadcast");
03bc6e11
 
add1de69
 	i = 0;
ae0895b4
 	session = sessions;
03bc6e11
 	pthread_mutex_lock(&sstatus_mutex);
9c1c533d
 	for(; i < max_children; i++, session++) {
 		cli_dbgmsg("findServer: try server %d\n", i);
ae0895b4
 		if(session->status == CMDSOCKET_FREE) {
 			session->status = CMDSOCKET_INUSE;
03bc6e11
 			pthread_mutex_unlock(&sstatus_mutex);
 			return i;
 		}
9c1c533d
 	}
03bc6e11
 	pthread_mutex_unlock(&sstatus_mutex);
 
 	cli_warnmsg(_("No free clamd sessions\n"));
 
 	return -1;	/* none available - must fail */
 }
 #else
d982f8e4
 /*
  * Return value is from 0 - index into serverIPs
  */
44d08756
 static int
 findServer(void)
 {
 	struct sockaddr_in *servers, *server;
37ba3b97
 	int maxsock, i, j, active;
44d08756
 	int retval;
2dda0caf
 	pthread_t *tids;
 	struct try_server_struct *socks;
3844e1f0
 	fd_set rfds;
44d08756
 
 	assert(tcpSocket != 0);
 	assert(numServers > 0);
 
 	if(numServers == 1)
 		return 0;
 
37ba3b97
 	if(active_servers(&active) <= 1)
 		return active;
3844e1f0
 
44d08756
 	servers = (struct sockaddr_in *)cli_calloc(numServers, sizeof(struct sockaddr_in));
00e4d46a
 	if(servers == NULL)
 		return 0;
2dda0caf
 	socks = (struct try_server_struct *)cli_malloc(numServers * sizeof(struct try_server_struct));
44d08756
 
03bc6e11
 	if(max_children > 0) {
 		assert(n_children > 0);
 		assert(n_children <= max_children);
 
 		/*
 		 * Don't worry about no lock - it's doesn't matter if it's
 		 * not really accurate
 		 */
821dd71e
 		j = n_children - 1;	/* look at the next free one */
b6798877
 		if(j < 0)
 			j = 0;
03bc6e11
 	} else
6302a4f6
 		/*
66682bfa
 		 * cli_rndnum returns 0..max
6302a4f6
 		 */
66682bfa
 		j = cli_rndnum(numServers - 1);
1e2aaf5e
 
2dda0caf
 	for(i = 0; i < numServers; i++)
 		socks[i].sock = -1;
 
e89c412a
 	tids = cli_malloc(numServers * sizeof(pthread_t));
 
44d08756
 	for(i = 0, server = servers; i < numServers; i++, server++) {
 		int sock;
821dd71e
 		int server_index = (i + j) % numServers;
44d08756
 
 		server->sin_family = AF_INET;
5b6bb93b
 		server->sin_port = (in_port_t)htons(tcpSocket);
821dd71e
 		server->sin_addr.s_addr = serverIPs[server_index];
1e2aaf5e
 
821dd71e
 		logg("*findServer: try server %d\n", server_index);
44d08756
 
2dda0caf
 		sock = socks[i].sock = socket(AF_INET, SOCK_STREAM, 0);
03bc6e11
 
44d08756
 		if(sock < 0) {
 			perror("socket");
2dda0caf
 			do {
7c907c8f
 				pthread_join(tids[i], NULL);
2dda0caf
 				if(socks[i].sock >= 0)
 					close(socks[i].sock);
 			} while(--i >= 0);
44d08756
 			free(socks);
 			free(servers);
e89c412a
 			free(tids);
44d08756
 			return 0;	/* Use the first server on failure */
 		}
 
2dda0caf
 		socks[i].server = server;
 		socks[i].server_index = server_index;
44ba5c0e
 
2dda0caf
 		if(pthread_create(&tids[i], NULL, try_server, &socks[i]) != 0) {
 			perror("pthread_create");
 			do {
7c907c8f
 				pthread_join(tids[i], NULL);
2dda0caf
 				if(socks[i].sock >= 0)
 					close(socks[i].sock);
 			} while(--i >= 0);
 			free(socks);
 			free(servers);
e89c412a
 			free(tids);
2dda0caf
 			return 0;	/* Use the first server on failure */
44d08756
 		}
2dda0caf
 	}
 
 	maxsock = -1;
 	FD_ZERO(&rfds);
44d08756
 
2dda0caf
 	for(i = 0; i < numServers; i++) {
 		struct try_server_struct *rc;
44d08756
 
f9d522ea
 		pthread_join(tids[i], (void **)&rc);
2dda0caf
 		assert(rc->sock == socks[i].sock);
 		if(rc->rc == 0) {
 			close(rc->sock);
 			socks[i].sock = -1;
 		} else {
 			shutdown(rc->sock, SHUT_WR);
 			FD_SET(rc->sock, &rfds);
 			if(rc->sock > maxsock)
 				maxsock = rc->sock;
 		}
44d08756
 	}
 
 	free(servers);
e89c412a
 	free(tids);
44d08756
 
00e4d46a
 	if(maxsock == -1) {
df1ec3e8
 		logg(_("^Couldn't establish a connexion to any clamd server\n"));
e368b3dd
 		retval = 0;
00e4d46a
 	} else {
e1773c9e
 		struct timeval tv;
 
 		tv.tv_sec = readTimeout ? readTimeout : DEFAULT_TIMEOUT;
 		tv.tv_usec = 0;
 
e368b3dd
 		retval = select(maxsock + 1, &rfds, NULL, NULL, &tv);
e1773c9e
 	}
e368b3dd
 
44d08756
 	if(retval < 0)
 		perror("select");
 
 	for(i = 0; i < numServers; i++)
2dda0caf
 		if(socks[i].sock >= 0)
 			close(socks[i].sock);
44d08756
 
 	if(retval == 0) {
 		free(socks);
03bc6e11
 		clamdIsDown();
44d08756
 		return 0;
 	} else if(retval < 0) {
 		free(socks);
2dda0caf
 		logg(_("^findServer: select failed (maxsock = %d)\n"), maxsock);
44d08756
 		return 0;
 	}
 
 	for(i = 0; i < numServers; i++)
2dda0caf
 		if((socks[i].sock >= 0) && (FD_ISSET(socks[i].sock, &rfds))) {
226d027a
 			const int s = (i + j) % numServers;
1e2aaf5e
 
44d08756
 			free(socks);
821dd71e
 			logg("*findServer: use server %d\n", s);
226d027a
 			return s;
44d08756
 		}
 
 	free(socks);
00e4d46a
 	logg(_("^findServer: No response from any server\n"));
44d08756
 	return 0;
 }
2dda0caf
 
 /*
3844e1f0
  * How many servers are up at the moment? If a server is marked as down,
  *	don't keep on flooding it with requests to see if it's now back up
dd649bab
  * If only one server is active, let the caller know, which server is the
  *	active one
3844e1f0
  */
 static int
37ba3b97
 active_servers(int *active)
3844e1f0
 {
 	int server, count;
 	time_t now = (time_t)0;
 
 	for(count = server = 0; server < numServers; server++)
37ba3b97
 		if(last_failed_pings[server] == (time_t)0) {
 			*active = server;
3844e1f0
 			count++;
37ba3b97
 		} else {
3844e1f0
 			if(now == (time_t)0)
 				time(&now);
 			if(now - last_failed_pings[server] >= RETRY_SECS)
 				/* Try this server again next time */
 				last_failed_pings[server] = (time_t)0;
 		}
 
37ba3b97
 	if(count != 1)
 		*active = 0;
3844e1f0
 	return count;
 }
 
 /*
2dda0caf
  * Connecting to remote servers can take some time, so let's connect to
  *	them in parallel. This routine is started as a thread
  */
 static void *
 try_server(void *var)
 {
 	struct try_server_struct *s = (struct try_server_struct *)var;
 	int sock = s->sock;
 	struct sockaddr *server = (struct sockaddr *)s->server;
 	int server_index = s->server_index;
 
3844e1f0
 	if(last_failed_pings[server_index]) {
 		s->rc = 0;
 		return var;
 	}
 
2dda0caf
 	logg("*try_server: sock %d\n", sock);
 
83fc7301
 	if((connect(sock, server, sizeof(struct sockaddr)) < 0) ||
655f1c23
 	   (send(sock, "PING\n", 5, 0) < 5)) {
 		time(&last_failed_pings[server_index]);
2dda0caf
 		s->rc = 0;
655f1c23
 	} else
2dda0caf
 		s->rc = 1;
 
 	if(s->rc == 0) {
 #ifdef	MAXHOSTNAMELEN
 		char hostname[MAXHOSTNAMELEN + 1];
 
 		cli_strtokbuf(serverHostNames, server_index, ":", hostname);
 		if(strcmp(hostname, "127.0.0.1") == 0)
 			gethostname(hostname, sizeof(hostname));
 #else
 		char *hostname = cli_strtok(serverHostNames, server_index, ":");
 #endif
83fc7301
 		perror(hostname);
2dda0caf
 		logg(_("^Check clamd server %s - it may be down\n"), hostname);
 #ifndef	MAXHOSTNAMELEN
 		free(hostname);
 #endif
 		broadcast(_("Check clamd server - it may be down\n"));
 	}
 
 	return var;
 }
03bc6e11
 #endif
44d08756
 
f7ab4278
 /*
df1ec3e8
  * Sendmail wants to establish a connexion to us
dd649bab
  * TODO: is it possible (desirable?) to determine if the remote machine has been
  *	compromised?
f7ab4278
  */
e3aaff8e
 static sfsistat
 clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
f7ab4278
 {
95df50d1
 #if	defined(HAVE_INET_NTOP) || defined(WITH_TCPWRAP)
df1ec3e8
 	char ip[INET6_ADDRSTRLEN];
95df50d1
 #endif
7df6b4c0
 	int t;
eba8ebeb
 	const char *remoteIP;
a0ff4f89
 	struct privdata *privdata;
e3aaff8e
 
152f7c23
 	if(quitting)
 		return cl_error;
 
0fbdd75a
 	if(ctx == NULL) {
83ed1043
 		logg(_("!clamfi_connect: ctx is null"));
0fbdd75a
 		return cl_error;
 	}
2f5f8390
 	if(hostname == NULL) {
83ed1043
 		logg(_("!clamfi_connect: hostname is null"));
2f5f8390
 		return cl_error;
 	}
abced581
 	if(smfi_getpriv(ctx) != NULL) {
df1ec3e8
 		/* More than one connexion command, "can't happen" */
abced581
 		cli_warnmsg("clamfi_connect: called more than once\n");
 		clamfi_cleanup(ctx);
 		return cl_error;
 	}
df1ec3e8
 #ifdef AF_INET6
 	if((hostaddr == NULL) ||
 	   ((hostaddr->sa_family == AF_INET) && (&(((struct sockaddr_in *)(hostaddr))->sin_addr) == NULL)) ||
 	   ((hostaddr->sa_family == AF_INET6) && (&(((struct sockaddr_in6 *)(hostaddr))->sin6_addr) == NULL)))
 #else
9cbf69cc
 	if((hostaddr == NULL) || (&(((struct sockaddr_in *)(hostaddr))->sin_addr) == NULL))
df1ec3e8
 #endif
23248fbc
 		/*
 		 * According to the sendmail API hostaddr is NULL if
 		 * "the type is not supported in the current version". What
0fbdd75a
 		 * the documentation doesn't say is the type of what.
23248fbc
 		 *
ba2dfeb2
 		 * Possibly the input is not a TCP/IP socket e.g. stdin?
23248fbc
 		 */
 		remoteIP = "127.0.0.1";
 	else {
95df50d1
 #ifdef HAVE_INET_NTOP
3844e1f0
 		switch(hostaddr->sa_family) {
df1ec3e8
 			case AF_INET:
 				remoteIP = (char *)inet_ntop(AF_INET, &((struct sockaddr_in *)(hostaddr))->sin_addr, ip, sizeof(ip));
 				break;
 #ifdef AF_INET6
 			case AF_INET6:
 				remoteIP = (char *)inet_ntop(AF_INET6, &((struct sockaddr_in6 *)(hostaddr))->sin6_addr, ip, sizeof(ip));
 				break;
 #endif
 			default:
 				logg(_("clamfi_connect: Unexpected sa_family %d\n"),
 					hostaddr->sa_family);
 				return cl_error;
 		}
 
95df50d1
 #else
23248fbc
 		remoteIP = inet_ntoa(((struct sockaddr_in *)(hostaddr))->sin_addr);
95df50d1
 #endif
2f5f8390
 
23248fbc
 		if(remoteIP == NULL) {
83ed1043
 			logg(_("clamfi_connect: remoteIP is null"));
23248fbc
 			return cl_error;
 		}
2f5f8390
 	}
1c8d6635
 
01ab0124
 #ifdef	CL_DEBUG
2a2f7ff3
 	if(debug_level >= 4) {
83ed1043
 		if(hostname[0] == '[')
df1ec3e8
 			logg(_("clamfi_connect: connexion from %s"), remoteIP);
83ed1043
 		else
df1ec3e8
 			logg(_("clamfi_connect: connexion from %s [%s]"), 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;
150789eb
 		struct hostent hostent;
734ea355
 		char buf[BUFSIZ];
226d027a
 		static pthread_mutex_t wrap_mutex = PTHREAD_MUTEX_INITIALIZER;
f9c88a98
 
5b6bb93b
 		/*
df1ec3e8
 		 * Using TCP/IP for the sendmail->clamav-milter connexion
664f9ff6
 		 */
df1ec3e8
 		if(((hostmail = smfi_getsymval(ctx, "{if_name}")) == NULL) &&
 		   ((hostmail = smfi_getsymval(ctx, "j")) == NULL)) {
83ed1043
 			logg(_("Can't get sendmail hostname"));
144bc6cf
 			return cl_error;
664f9ff6
 		}
9c9e9b9b
 		/*
 		 * Use hostmail for error statements, not hostname, suggestion
 		 * by Yar Tikhiy <yar@comp.chem.msu.su>
 		 */
85883a02
 		if(r_gethostbyname(hostmail, &hostent, buf, sizeof(buf)) != 0) {
83ed1043
 			logg(_("^Access Denied: Host Unknown (%s)"), hostmail);
9c9e9b9b
 			if(hostmail[0] == '[')
0863025b
 				/*
 				 * A case could be made that it's not clamAV's
 				 * job to check a system's DNS configuration
 				 * and let this message through. However I am
 				 * just too worried about any knock on effects
 				 * to do that...
 				 */
 				cli_warnmsg(_("Can't find entry for IP address %s in DNS - check your DNS setting\n"),
9c9e9b9b
 					hostmail);
150789eb
 			return cl_error;
 		}
f9c88a98
 
144bc6cf
 #ifdef HAVE_INET_NTOP
734ea355
 		if(hostent.h_addr &&
 		   (inet_ntop(AF_INET, (struct in_addr *)hostent.h_addr, ip, sizeof(ip)) == NULL)) {
 			perror(hostent.h_name);
226d027a
 			/*strcpy(ip, (char *)inet_ntoa(*(struct in_addr *)hostent.h_addr));*/
83ed1043
 			logg(_("^Access Denied: Can't get IP address for (%s)"), hostent.h_name);
ba2dfeb2
 			return cl_error;
 		}
144bc6cf
 #else
734ea355
 		strncpy(ip, (char *)inet_ntoa(*(struct in_addr *)hostent.h_addr), sizeof(ip));
72ce4b70
 		ip[sizeof(ip)-1]='\0';
144bc6cf
 #endif
664f9ff6
 
 		/*
 		 * Ask is this is a allowed name or IP number
226d027a
 		 *
 		 * hosts_ctl uses strtok so it is not thread safe, see
 		 * hosts_access(3)
664f9ff6
 		 */
226d027a
 		pthread_mutex_lock(&wrap_mutex);
640dbd4c
 		if(!hosts_ctl(progname, hostent.h_name, ip, STRING_UNKNOWN)) {
226d027a
 			pthread_mutex_unlock(&wrap_mutex);
83ed1043
 			logg(_("^Access Denied for %s[%s]"), hostent.h_name, ip);
664f9ff6
 			return SMFIS_TEMPFAIL;
 		}
226d027a
 		pthread_mutex_unlock(&wrap_mutex);
055404a5
 	}
741a082e
 #endif	/*WITH_TCPWRAP*/
055404a5
 
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) {
83ed1043
 			logg(_("*clamfi_connect: not scanning outgoing messages"));
e3aaff8e
 			return SMFIS_ACCEPT;
 		}
 
df1ec3e8
 	if((!lflag) && isLocal(remoteIP)) {
e3aaff8e
 #ifdef	CL_DEBUG
72e8ae80
 		logg(_("*clamfi_connect: not scanning local messages\n"));
e3aaff8e
 #endif
eef726b0
 		return SMFIS_ACCEPT;
e3aaff8e
 	}
b2fb75ea
 
835c9751
 #if	defined(HAVE_INET_NTOP) || defined(WITH_TCPWRAP)
df1ec3e8
 	if(detect_forged_local_address && !isLocal(ip)) {
835c9751
 #else
df1ec3e8
 	if(detect_forged_local_address && !isLocal(remoteIP)) {
835c9751
 #endif
358facc3
 		char me[MAXHOSTNAMELEN + 1];
 
 		if(gethostname(me, sizeof(me) - 1) < 0) {
72e8ae80
 			logg(_("^clamfi_connect: gethostname failed"));
358facc3
 			return SMFIS_CONTINUE;
 		}
72e8ae80
 		logg("*me '%s' hostname '%s'\n", me, hostname);
358facc3
 		if(strcasecmp(hostname, me) == 0) {
7c907c8f
 			logg(_("Rejected connexion falsely claiming to be from here\n"));
358facc3
 			smfi_setreply(ctx, "550", "5.7.1", _("You have claimed to be me, but you are not"));
 			broadcast(_("Forged local address detected"));
 			return SMFIS_REJECT;
 		}
 	}
a0ff4f89
 	if(isBlacklisted(remoteIP)) {
2ed3a9a3
 		char mess[128];
7c907c8f
 
a0ff4f89
 		/*
 		 * TODO: Option to greylist rather than blacklist, by sending
 		 *	a try again code
7c907c8f
 		 * TODO: state *which* virus
2ed3a9a3
 		 * TODO: add optional list of IP addresses that won't be
 		 *	blacklisted
a0ff4f89
 		 */
2ed3a9a3
 		logg("Rejected connexion from blacklisted IP %s\n", remoteIP);
 
 		snprintf(mess, sizeof(mess), _("%s is blacklisted because your machine is infected with a virus"), remoteIP);
 		smfi_setreply(ctx, "550", "5.7.1", mess);
a0ff4f89
 		broadcast(_("Blacklisted IP detected"));
f1617494
 
 		/*
 		 * Keep them blacklisted
 		 */
 		pthread_mutex_lock(&blacklist_mutex);
 		(void)tableUpdate(blacklist, remoteIP, (int)time((time_t *)0));
 		pthread_mutex_unlock(&blacklist_mutex);
 
a0ff4f89
 		return SMFIS_REJECT;
 	}
 
6338dd5e
 	if(blacklist_time == 0)
 		return SMFIS_CONTINUE;	/* allocate privdata per message */
 
7df6b4c0
 	pthread_mutex_lock(&blacklist_mutex);
 	t = tableFind(blacklist, remoteIP);
 	pthread_mutex_unlock(&blacklist_mutex);
 
1b415e36
 	if(t == 0)
7df6b4c0
 		return SMFIS_CONTINUE;	/* this IP will never be blacklisted */
 
a0ff4f89
 	privdata = (struct privdata *)cli_calloc(1, sizeof(struct privdata));
 	if(privdata == NULL)
 		return cl_error;
 
6a15a50f
 #ifdef	SESSION
 	privdata->dataSocket = -1;
 #else
 	privdata->dataSocket = privdata->cmdSocket = -1;
 #endif
 
a0ff4f89
 	if(smfi_setpriv(ctx, privdata) == MI_SUCCESS) {
3d1678b1
 		strcpy(privdata->ip, remoteIP);
a0ff4f89
 		return SMFIS_CONTINUE;
 	}
 
f1617494
 	free(privdata);
a0ff4f89
 
 	return cl_error;
e3aaff8e
 }
 
a5a0ec72
 /*
  * Since sendmail requires that MAIL FROM is called before RCPT TO, it is
  *	safe to assume that this routine is called first, so the n_children
  *	handler is put here
  */
e3aaff8e
 static sfsistat
 clamfi_envfrom(SMFICTX *ctx, char **argv)
 {
 	struct privdata *privdata;
7089d180
 	const char *mailaddr = argv[0];
e3aaff8e
 
2dda0caf
 	logg("*clamfi_envfrom: %s\n", argv[0]);
e3aaff8e
 
feb192dd
 	if(isWhitelisted(argv[0], 0)) {
 		logg(_("*clamfi_envfrom: ignoring whitelisted message"));
 		return SMFIS_ACCEPT;
 	}
 
7089d180
 	if(strcmp(argv[0], "<>") == 0) {
 		mailaddr = smfi_getsymval(ctx, "{mail_addr}");
c5e2c4a9
 		if(mailaddr == NULL)
 			mailaddr = smfi_getsymval(ctx, "_");
 
 		if(mailaddr && *mailaddr)
 			cli_dbgmsg("Message from \"%s\" has no from field\n", mailaddr);
7089d180
 		else {
 #if	0
 			if(use_syslog)
 				syslog(LOG_NOTICE, _("Rejected email with empty from field"));
 			smfi_setreply(ctx, "554", "5.7.1", _("You have not said who the email is from"));
 			broadcast(_("Reject email with empty from field"));
 			clamfi_cleanup(ctx);
 			return SMFIS_REJECT;
 #endif
c5e2c4a9
 			mailaddr = "<>";
7089d180
 		}
 	}
a0ff4f89
 	privdata = smfi_getpriv(ctx);
a5a0ec72
 
a0ff4f89
 	if(privdata == NULL) {
 		privdata = (struct privdata *)cli_calloc(1, sizeof(struct privdata));
 		if(privdata == NULL)
 			return cl_error;
 		if(smfi_setpriv(ctx, privdata) != MI_SUCCESS) {
 			free(privdata);
 			return cl_error;
 		}
df1ec3e8
 		if(!increment_connexions()) {
530999cb
 			smfi_setreply(ctx, "451", "4.3.2", _("AV system temporarily overloaded - please try later"));
 			free(privdata);
 			smfi_setpriv(ctx, NULL);
 			return SMFIS_TEMPFAIL;
e3aaff8e
 		}
9c872b2e
 	} else {
df1ec3e8
 		/* More than one message on this connexion */
 		char ip[INET6_ADDRSTRLEN];
e3aaff8e
 
9c872b2e
 		strcpy(ip, privdata->ip);
 		if(isBlacklisted(ip)) {
a32f3ba8
 			char mess[80 + INET6_ADDRSTRLEN];
 
9c872b2e
 			logg("Rejected email from blacklisted IP %s\n", ip);
 
 			/*
530999cb
 			 * TODO: Option to greylist rather than blacklist, by
 			 *	sending	a try again code
9c872b2e
 			 * TODO: state *which* virus
 			 */
a32f3ba8
 			sprintf(mess, "Your IP (%s) is blacklisted because your machine is infected with a virus", ip);
 			smfi_setreply(ctx, "550", "5.7.1", mess);
9c872b2e
 			broadcast(_("Blacklisted IP detected"));
 
 			/*
 			 * Keep them blacklisted
 			 */
 			pthread_mutex_lock(&blacklist_mutex);
 			(void)tableUpdate(blacklist, ip, (int)time((time_t *)0));
 			pthread_mutex_unlock(&blacklist_mutex);
 
 			return SMFIS_REJECT;
e3aaff8e
 		}
530999cb
 		clamfi_free(privdata, 1);
9c872b2e
 		strcpy(privdata->ip, ip);
e3aaff8e
 	}
 
9c872b2e
 #ifdef	SESSION
 	privdata->dataSocket = -1;
 #else
 	privdata->dataSocket = privdata->cmdSocket = -1;
 #endif
 
b5c80361
 	/*
 	 * Rejection is via 550 until DATA is received. We know that
 	 * DATA has been sent when either we get a header or the end of
 	 * header statement
 	 */
 	privdata->rejectCode = "550";
 
16d1f4d0
 	privdata->from = cli_strdup(mailaddr);
e3aaff8e
 
9fe789f8
 	if(hflag) {
96f3d93b
 		privdata->headers = header_list_new();
c54d7329
 
9fe789f8
 		if(privdata->headers == NULL) {
 			clamfi_free(privdata, 1);
 			return cl_error;
 		}
 	}
 
a0ff4f89
 	return SMFIS_CONTINUE;
e3aaff8e
 }
 
7089d180
 #ifdef	CL_DEBUG
 static sfsistat
 clamfi_helo(SMFICTX *ctx, char *helostring)
 {
72e8ae80
 	logg("HELO '%s'\n", helostring);
7089d180
 
 	return SMFIS_CONTINUE;
 }
 #endif
 
e3aaff8e
 static sfsistat
 clamfi_envrcpt(SMFICTX *ctx, char **argv)
 {
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
28d8b7bd
 	const char *to, *ptr;
e3aaff8e
 
b4b5c17d
 	logg("*clamfi_envrcpt: %s\n", argv[0]);
e3aaff8e
 
9fe789f8
 	if(privdata == NULL)	/* sanity check */
 		return cl_error;
 
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
 
502c8234
 	if(privdata->to == NULL)
08b0add9
 		return cl_error;
502c8234
 
b4b5c17d
 	to = smfi_getsymval(ctx, "{rcpt_addr}");
 	if(to == NULL)
 		to = argv[0];
 
f410a3aa
 	for(ptr = to; !dont_sanitise && *ptr; ptr++)
28d8b7bd
 		if(strchr("|;", *ptr) != NULL) {
 			smfi_setreply(ctx, "554", "5.7.1", _("Suspicious recipient address blocked"));
ba74b333
 			logg("^Suspicious recipient address blocked: '%s'\n", to);
f277e9ae
 			privdata->to[privdata->numTo] = NULL;
ba74b333
 			if(blacklist_time && privdata->ip[0]) {
 				logg(_("Will blacklist %s for %d seconds because of cracking attempt\n"),
 					privdata->ip, blacklist_time);
 				pthread_mutex_lock(&blacklist_mutex);
 				(void)tableUpdate(blacklist, privdata->ip,
 					(int)time((time_t *)0));
 				pthread_mutex_unlock(&blacklist_mutex);
 			}
671973aa
 			/*
 			 * REJECT rejects this recipient, not the entire email
 			 */
28d8b7bd
 			return SMFIS_REJECT;
 		}
 
16d1f4d0
 	privdata->to[privdata->numTo] = cli_strdup(to);
e3aaff8e
 	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);
 
 #ifdef	CL_DEBUG
 	if(debug_level >= 9)
b4b5c17d
 		logg("*clamfi_header: %s: %s\n", headerf, headerv);
e3aaff8e
 	else
2dda0caf
 		logg("*clamfi_header: %s\n", headerf);
b4b5c17d
 #else
2dda0caf
 	logg("*clamfi_header: %s\n", headerf);
e3aaff8e
 #endif
 
b5c80361
 	/*
 	 * The DATA instruction from SMTP (RFC2821) must have been sent
 	 */
 	privdata->rejectCode = "554";
 
9e1e77b9
 	if(hflag)
5b6bb93b
 		header_list_add(privdata->headers, headerf, headerv);
736c8d91
 	else if((strcasecmp(headerf, "Received") == 0) &&
2027c1fc
 		(strncasecmp(headerv, "from ", 5) == 0) &&
 		(strstr(headerv, "localhost") != 0)) {
736c8d91
 		if(privdata->received)
502c8234
 			free(privdata->received);
16d1f4d0
 		privdata->received = cli_strdup(headerv);
502c8234
 	}
 
b5c80361
 	if((strcasecmp(headerf, "Message-ID") == 0) &&
 	   (strncasecmp(headerv, "<MDAEMON", 8) == 0))
 		privdata->discard = 1;
1a2b28a9
 	else if((strcasecmp(headerf, "Subject") == 0) && headerv) {
add1de69
 		if(privdata->subject)
 			free(privdata->subject);
 		if(headerv)
16d1f4d0
 			privdata->subject = cli_strdup(headerv);
28071296
 	} else if(strcasecmp(headerf, "X-Virus-Status") == 0)
 		privdata->statusCount++;
1a2b28a9
 	else if((strcasecmp(headerf, "Sender") == 0) && headerv) {
7089d180
 		if(privdata->sender)
 			free(privdata->sender);
1a2b28a9
 		privdata->sender = cli_strdup(headerv);
 	}
 #ifdef	HAVE_RESOLV_H
 	else if((strcasecmp(headerf, "From") == 0) && headerv) {
 		/*
 		 * SPF check against the from header, since the SMTP header
 		 * may be valid. This is not what the SPF spec says, but I
 		 * have seen SPF matches on what are clearly phishes, so by
 		 * checking against the from: header we're less likely to
 		 * FP a real phish
 		 */
 		if(privdata->from)
 			free(privdata->from);
 		privdata->from = cli_strdup(headerv);
7089d180
 	}
1a2b28a9
 #endif
c5e2c4a9
 
2dda0caf
 	if(!useful_header(headerf)) {
 		logg("*Discarded the header\n");
 		return SMFIS_CONTINUE;
 	}
 
 	if(privdata->dataSocket == -1)
 		/*
df1ec3e8
 		 * First header - make connexion with clamd
2dda0caf
 		 */
 		if(!connect2clamd(privdata)) {
 			clamfi_cleanup(ctx);
 			return cl_error;
 		}
 
 	if(clamfi_send(privdata, 0, "%s: %s\n", headerf, headerv) <= 0) {
 		clamfi_cleanup(ctx);
 		return cl_error;
 	}
 
e3aaff8e
 	return SMFIS_CONTINUE;
 }
 
b5c80361
 /*
  * At this point DATA will have been received, so we really ought to
  * send 554 back not 550
  */
e3aaff8e
 static sfsistat
 clamfi_eoh(SMFICTX *ctx)
 {
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
6909adb8
 	char **to;
e3aaff8e
 
83ed1043
 	logg(_("*clamfi_eoh\n"));
e3aaff8e
 
b5c80361
 	/*
 	 * The DATA instruction from SMTP (RFC2821) must have been sent
 	 */
 	privdata->rejectCode = "554";
 
f7ab4278
 	if(privdata->dataSocket == -1)
 		/*
df1ec3e8
 		 * No headers - make connexion with clamd
f7ab4278
 		 */
 		if(!connect2clamd(privdata)) {
 			clamfi_cleanup(ctx);
 			return cl_error;
 		}
 
72e8ae80
 #if	0
 	/* Mailing lists often say our own posts are from us */
7089d180
 	if(detect_forged_local_address && privdata->from &&
feb192dd
 	   (!privdata->sender) && !isWhitelisted(privdata->from, 1)) {
7089d180
 		char me[MAXHOSTNAMELEN + 1];
 		const char *ptr;
 
 		if(gethostname(me, sizeof(me) - 1) < 0) {
 			if(use_syslog)
 				syslog(LOG_WARNING, _("clamfi_eoh: gethostname failed"));
 			return SMFIS_CONTINUE;
 		}
 		ptr = strstr(privdata->from, me);
 		if(ptr && (ptr != privdata->from) && (*--ptr == '@')) {
 			if(use_syslog)
 				syslog(LOG_NOTICE, _("Rejected email falsely claiming to be from %s"), privdata->from);
 			smfi_setreply(ctx, "554", "5.7.1", _("You have claimed to be from me, but you are not"));
 			broadcast(_("Forged local address detected"));
 			clamfi_cleanup(ctx);
 			return SMFIS_REJECT;
 		}
 	}
72e8ae80
 #endif
7089d180
 
7d81c053
 	if(clamfi_send(privdata, 1, "\n") != 1) {
e3aaff8e
 		clamfi_cleanup(ctx);
68d5a5f3
 		return cl_error;
e3aaff8e
 	}
 
f1617494
 	if(black_hole_mode) {
 		sfsistat rc = black_hole(privdata);
df6c5998
 
f1617494
 		if(rc != SMFIS_CONTINUE) {
 			clamfi_cleanup(ctx);
 			return rc;
 		}
df6c5998
 	}
 
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
 	 */
835c9751
 	for(to = privdata->to; *to; to++)
feb192dd
 		if(!isWhitelisted(*to, 1))
6909adb8
 			/*
 			 * This recipient is not on the whitelist,
 			 * no need to check any further
 			 */
 			return SMFIS_CONTINUE;
df6c5998
 
6909adb8
 	/*
 	 * 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
 	 */
feb192dd
 	logg(_("*clamfi_enveoh: ignoring whitelisted message"));
6909adb8
 	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);
7d81c053
 	int nbytes;
e3aaff8e
 
9e3242ca
 	logg(_("*clamfi_envbody: %lu bytes"), (unsigned long)len);
e3aaff8e
 
a7d8f61f
 	if(len == 0)	/* unlikely */
 		return SMFIS_CONTINUE;
 
9fe789f8
 	if(privdata == NULL)	/* sanity check */
 		return cl_error;
 
dce71a3b
 	/*
2a60a108
 	 * TODO:
1b9d2e0d
 	 *	If not in external mode, call cli_scanbuff here, at least for
 	 * the first block
2a60a108
 	 */
 	/*
dce71a3b
 	 * Lines starting with From are changed to >From, to
 	 *	avoid FP matches in the scanning code, which will speed it up
 	 */
ea72dd11
 	if((len >= 6) && cli_memstr((char *)bodyp, len, "\nFrom ", 6)) {
a0ff4f89
 		const char *ptr = (const char *)bodyp;
dce71a3b
 		int left = len;
 
 		nbytes = 0;
 
 		/*
 		 * FIXME: sending one byte at a time down a socket is
 		 *	inefficient
 		 */
c86c794b
 		do {
dce71a3b
 			if(*ptr == '\n') {
b4b5c17d
 				/*
 				 * FIXME: doesn't work if the \nFrom straddles
 				 * multiple calls to clamfi_body
 				 */
dce71a3b
 				if(strncmp(ptr, "\nFrom ", 6) == 0) {
ea72dd11
 					nbytes += clamfi_send(privdata, 7, "\n>From ");
0b01e4d5
 					ptr += 6;
dce71a3b
 					left -= 6;
 				} else {
ea72dd11
 					nbytes += clamfi_send(privdata, 1, "\n");
dce71a3b
 					ptr++;
 					left--;
 				}
 			} else {
ea72dd11
 				nbytes += clamfi_send(privdata, 1, ptr++);
dce71a3b
 				left--;
 			}
c86c794b
 			if(left < 6) {
 				nbytes += clamfi_send(privdata, left, ptr);
 				break;
 			}
 		} while(left > 0);
dce71a3b
 	} else
 		nbytes = clamfi_send(privdata, len, (char *)bodyp);
 
bb09a2f7
 	if(streamMaxLength > 0L) {
96f3d93b
 		if(privdata->numBytes > streamMaxLength) {
83ed1043
 			const char *sendmailId = smfi_getsymval(ctx, "i");
 
 			if(sendmailId == NULL)
 				sendmailId = "Unknown";
16d1f4d0
 			logg(_("%s: Message more than StreamMaxLength (%ld) bytes - not scanned\n"),
83ed1043
 				sendmailId, streamMaxLength);
7d81c053
 			if(!nflag)
eba8ebeb
 				smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned - StreamMaxLength exceeded"));
7d81c053
 
 			return SMFIS_ACCEPT;	/* clamfi_close will be called */
96f3d93b
 		}
 	}
16d1f4d0
 	if(nbytes < (int)len) {
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);
b5c80361
 			privdata->body = cli_realloc(privdata->body, privdata->bodyLen + len);
51b03ecb
 			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];
ae0895b4
 #ifdef	SESSION
 	struct session *session;
 #endif
e3aaff8e
 
83ed1043
 	logg("*clamfi_eom\n");
ae0895b4
 
 #ifdef	CL_DEBUG
e3aaff8e
 	assert(privdata != NULL);
06bfd678
 #ifndef	SESSION
668c7570
 	assert((privdata->cmdSocket >= 0) || (privdata->filename != NULL));
 	assert(!((privdata->cmdSocket >= 0) && (privdata->filename != NULL)));
06bfd678
 #endif
e3aaff8e
 #endif
 
8528bc29
 	if(external) {
a32f3ba8
 		shutdown(privdata->dataSocket, SHUT_WR);	/* bug 487 */
d5e48ace
 		close(privdata->dataSocket);
 		privdata->dataSocket = -1;
 	}
 
a32f3ba8
 	if(!nflag) {
 		/*
 		 * remove any existing claims that it's virus free so that
 		 * downstream checkers aren't fooled by a carefully crafted
 		 * virus.
 		 */
 		int i;
 
 		for(i = privdata->statusCount; i > 0; --i)
 			if(smfi_chgheader(ctx, "X-Virus-Status", i, NULL) == MI_FAILURE)
8affc406
 				logg(_("^Failed to delete X-Virus-Status header %d\n"), i);
a32f3ba8
 	}
 
8528bc29
 	if(!external) {
d5e48ace
 		const char *virname;
 
aaade7d3
 		pthread_mutex_lock(&root_mutex);
1cc545df
 		privdata->root = cl_dup(root);
aaade7d3
 		pthread_mutex_unlock(&root_mutex);
1cc545df
 		if(privdata->root == NULL) {
6a7568dc
 			logg("!privdata->root == NULL\n");
aaade7d3
 			clamfi_cleanup(ctx);
 			return cl_error;
 		}
530999cb
 		switch(cl_scanfile(privdata->filename, &virname, NULL, privdata->root, &limits, options)) {
19575eba
 			case CL_CLEAN:
c17d8aba
 				if(logok)
df1ec3e8
 					logg("#%s: OK\n", privdata->filename);
19575eba
 				strcpy(mess, "OK");
 				break;
 			case CL_VIRUS:
6a7568dc
 				snprintf(mess, sizeof(mess), "%s: %s FOUND", privdata->filename, virname);
df1ec3e8
 				logg("#%s\n", mess);
19575eba
 				break;
 			default:
6a7568dc
 				snprintf(mess, sizeof(mess), "%s: %s ERROR", privdata->filename, cl_strerror(rc));
df1ec3e8
 				logg("!%s\n", mess);
19575eba
 				break;
 		}
1cc545df
 		cl_free(privdata->root);
 		privdata->root = NULL;
e3aaff8e
 
d5e48ace
 #ifdef	SESSION
 		session = NULL;
 #endif
 	} else if(privdata->filename) {
668c7570
 		char cmdbuf[1024];
 		/*
 		 * Create socket to talk to clamd.
 		 */
d5e48ace
 #ifndef	SESSION
668c7570
 		struct sockaddr_un server;
ae0895b4
 #endif
eaf74461
 		long nbytes;
668c7570
 
06bfd678
 		snprintf(cmdbuf, sizeof(cmdbuf) - 1, "SCAN %s", privdata->filename);
235ab38f
 		cli_dbgmsg("clamfi_eom: SCAN %s\n", privdata->filename);
06bfd678
 
 		nbytes = (int)strlen(cmdbuf);
 
 #ifdef	SESSION
ae0895b4
 		session = sessions;
 		if(send(session->sock, cmdbuf, nbytes, 0) < nbytes) {
06bfd678
 			perror("send");
 			clamfi_cleanup(ctx);
8affc406
 			logg(_("failed to send SCAN %s command to clamd\n"), privdata->filename);
06bfd678
 			return cl_error;
 		}
 #else
668c7570
 		if((privdata->cmdSocket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
 			perror("socket");
98135801
 			clamfi_cleanup(ctx);
668c7570
 			return cl_error;
 		}
ae0895b4
 		memset((char *)&server, 0, sizeof(struct sockaddr_un));
 		server.sun_family = AF_UNIX;
 		strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
72ce4b70
 		server.sun_path[sizeof(server.sun_path)-1]='\0';
ae0895b4
 
668c7570
 		if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
 			perror(localSocket);
98135801
 			clamfi_cleanup(ctx);
668c7570
 			return cl_error;
 		}
 		if(send(privdata->cmdSocket, cmdbuf, nbytes, 0) < nbytes) {
 			perror("send");
 			clamfi_cleanup(ctx);
8affc406
 			logg(_("failed to send SCAN command to clamd\n"));
668c7570
 			return cl_error;
 		}
 
 		shutdown(privdata->cmdSocket, SHUT_WR);
06bfd678
 #endif
668c7570
 	}
ae0895b4
 #ifdef	SESSION
 	else
 		session = &sessions[privdata->serverNumber];
 #endif
668c7570
 
9fe789f8
 	sendmailId = smfi_getsymval(ctx, "i");
 	if(sendmailId == NULL)
 		sendmailId = "Unknown";
 
8528bc29
 	if(external) {
358facc3
 		int nbytes;
06bfd678
 #ifdef	SESSION
d5e48ace
 #ifdef	CL_DEBUG
 		if(debug_level >= 4)
c5e2c4a9
 			cli_dbgmsg(_("Waiting to read status from fd %d\n"),
d5e48ace
 				session->sock);
 #endif
358facc3
 		nbytes = clamd_recv(session->sock, mess, sizeof(mess) - 1);
06bfd678
 #else
358facc3
 		nbytes = clamd_recv(privdata->cmdSocket, mess, sizeof(mess) - 1);
06bfd678
 #endif
358facc3
 		if(nbytes > 0) {
 			mess[nbytes] = '\0';
d5e48ace
 			if((ptr = strchr(mess, '\n')) != NULL)
 				*ptr = '\0';
e3aaff8e
 
83ed1043
 			logg(_("*clamfi_eom: read %s\n"), mess);
d5e48ace
 		} else {
bf16b485
 #ifdef	MAXHOSTNAMELEN
 			char hostname[MAXHOSTNAMELEN + 1];
 
 			cli_strtokbuf(serverHostNames, privdata->serverNumber, ":", hostname);
 			if(strcmp(hostname, "127.0.0.1") == 0)
 				gethostname(hostname, sizeof(hostname));
 #else
 			char *hostname = cli_strtok(serverHostNames, privdata->serverNumber, ":");
 #endif
040708a3
 			if(privdata->subject)
9fe789f8
 				logg(_("^%s: clamfi_eom: read nothing from clamd on %s, from %s (%s)\n"),
 					sendmailId, hostname, privdata->from,
 					privdata->subject);
040708a3
 			else
9fe789f8
 				logg(_("^%s: clamfi_eom: read nothing from clamd on %s, from %s\n"),
 					sendmailId, hostname, privdata->from);
040708a3
 
9fe789f8
 			if((!nflag) && (cl_error == SMFIS_ACCEPT))
 				smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned - Read timeout exceeded"));
040708a3
 #ifndef	MAXHOSTNAMELEN
 			free(hostname);
 #endif
9fe789f8
 
 #ifdef	CL_DEBUG
 			/*
 			 * Save the file which caused the timeout, for
 			 * debugging
 			 */
 			if(quarantine_dir) {
 				logg(_("Quarantining failed email\n"));
 				qfile(privdata, sendmailId, "scanning-timeout");
 			}
 #endif
 
d5e48ace
 			/*
 			 * TODO: if more than one host has been specified, try
a7d8f61f
 			 * another one - setting cl_error to SMFIS_TEMPFAIL
 			 * helps by forcing a retry
d5e48ace
 			 */
 			clamfi_cleanup(ctx);
83ed1043
 
add5094f
 #ifdef	SESSION
d5e48ace
 			pthread_mutex_lock(&sstatus_mutex);
 			session->status = CMDSOCKET_DOWN;
 			pthread_mutex_unlock(&sstatus_mutex);
add5094f
 #endif
d5e48ace
 			return cl_error;
 		}
e3aaff8e
 
03bc6e11
 #ifdef	SESSION
d5e48ace
 		pthread_mutex_lock(&sstatus_mutex);
 		if(session->status == CMDSOCKET_INUSE)
 			session->status = CMDSOCKET_FREE;
 		pthread_mutex_unlock(&sstatus_mutex);
03bc6e11
 #else
d5e48ace
 		close(privdata->cmdSocket);
 		privdata->cmdSocket = -1;
06bfd678
 #endif
d5e48ace
 	}
dad136d5
 
81bdf63b
 	if(!nflag) {
 		char buf[1024];
 
 		/*
456edb55
 		 * Include the hostname where the scan took place
81bdf63b
 		 */
8528bc29
 		if(localSocket || !external) {
1fcd39ef
 #ifdef	MAXHOSTNAMELEN
 			char hostname[MAXHOSTNAMELEN + 1];
 #else
 			char hostname[65];
 #endif
456edb55
 
734ea355
 			if(gethostname(hostname, sizeof(hostname)) < 0) {
226d027a
 				const char *j = smfi_getsymval(ctx, "{j}");
734ea355
 
226d027a
 				if(j)
72ce4b70
 					strncpy(hostname, j, sizeof(hostname) - 1);
734ea355
 				else
8a7ef08f
 					strcpy(hostname, _("Error determining host"));
72ce4b70
 				hostname[sizeof(hostname)-1]='\0';
734ea355
 			} else if(strchr(hostname, '.') == NULL) {
 				/*
 				 * Determine fully qualified name
 				 */
 				struct hostent hostent;
 
72ce4b70
 				if((r_gethostbyname(hostname, &hostent, buf, sizeof(buf)) == 0) && hostent.h_name) {
734ea355
 					strncpy(hostname, hostent.h_name, sizeof(hostname));
72ce4b70
 					hostname[sizeof(hostname)-1]='\0';
 				}
734ea355
 			}
456edb55
 
1fcd39ef
 #ifdef	SESSION
0863025b
 			pthread_mutex_lock(&version_mutex);
1fcd39ef
 			snprintf(buf, sizeof(buf) - 1, "%s on %s",
 				clamav_versions[privdata->serverNumber], hostname);
0863025b
 			pthread_mutex_unlock(&version_mutex);
1fcd39ef
 #else
 			snprintf(buf, sizeof(buf) - 1, "%s on %s",
456edb55
 				clamav_version, hostname);
1fcd39ef
 #endif
456edb55
 		} else {
44ba5c0e
 #ifdef	MAXHOSTNAMELEN
 			char hostname[MAXHOSTNAMELEN + 1];
0abc0a57
 
44ba5c0e
 			if(cli_strtokbuf(serverHostNames, privdata->serverNumber, ":", hostname)) {
bf16b485
 				if(strcmp(hostname, "127.0.0.1") == 0)
 					gethostname(hostname, sizeof(hostname));
44ba5c0e
 #else
 			char *hostname = cli_strtok(serverHostNames, privdata->serverNumber, ":");
0abc0a57
 			if(hostname) {
44ba5c0e
 #endif
 
1fcd39ef
 #ifdef	SESSION
0863025b
 				pthread_mutex_lock(&version_mutex);
1fcd39ef
 				snprintf(buf, sizeof(buf) - 1, "%s on %s",
 					clamav_versions[privdata->serverNumber], hostname);
0863025b
 				pthread_mutex_unlock(&version_mutex);
1fcd39ef
 #else
 				snprintf(buf, sizeof(buf) - 1, "%s on %s",
 					clamav_version, hostname);
 #endif
44ba5c0e
 #ifndef	MAXHOSTNAMELEN
0abc0a57
 				free(hostname);
44ba5c0e
 #endif
0abc0a57
 			} else
 				/* sanity check failed - should issue warning */
eba8ebeb
 				strcpy(buf, _("Error determining host"));
0abc0a57
 		}
81bdf63b
 		smfi_addheader(ctx, "X-Virus-Scanned", buf);
 	}
23248fbc
 
6bc34381
 	/*
 	 * TODO: it would be useful to add a header if mbox.c/FOLLOWURLS was
feea329f
 	 * exceeded
6bc34381
 	 */
510212e4
 #ifdef	HAVE_RESOLV_H
684d3122
 	if((strstr(mess, "FOUND") != NULL) && (strstr(mess, "Phishing") != NULL)) {
 		table_t *prevhosts = tableCreate();
 
35daded5
 		if(spf(privdata, prevhosts)) {
0cd8e40b
 			logg(_("%s: Ignoring %s false positive from %s received from %s\n"),
 				sendmailId, mess, privdata->from, privdata->ip);
420cda0b
 			strcpy(mess, "OK");
1a2b28a9
 			/*
 			 * Report false positive to ClamAV, works best when
 			 * clamav-milter has had to create a local copy of
 			 * the email, e.g. when --quarantine-dir is on
 			 */
 			if(report_fps &&
 			   (smfi_addrcpt(ctx, report_fps) == MI_FAILURE)) {
 				if(privdata->filename) {
 					char cmd[1024];
 
 					snprintf(cmd, sizeof(cmd) - 1,
 						"mail -s \"False Positive: %s\" %s < %s",
 						mess, report_fps,
 						privdata->filename);
 					if(system(cmd) == 0)
 						logg(_("#Reported phishing false positive to %s"), report_fps);
 					else
 						logg(_("^Couldn't report false positive to %s\n"), report_fps);
 				} else
 					/*
 					 * Most likely this is because we're
 					 * attempting to add a recipient on
 					 * another host
 					 */
 					logg(_("^Can't set phish FP header\n"));
 			}
420cda0b
 		}
684d3122
 		tableDestroy(prevhosts);
 	}
a32f3ba8
 #endif
86b3e542
 	if(strstr(mess, "ERROR") != NULL) {
dd2edd28
 		if(strstr(mess, "Size limit reached") != NULL) {
f9f239aa
 			/*
 			 * Clamd has stopped on StreamMaxLength before us
 			 */
83ed1043
 			logg(_("%s: Message more than StreamMaxLength (%ld) bytes - not scanned"),
 				sendmailId, streamMaxLength);
7d81c053
 			if(!nflag)
eba8ebeb
 				smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned - StreamMaxLength exceeded"));
0abc0a57
 			clamfi_cleanup(ctx);	/* not needed, but just to be safe */
f9f239aa
 			return SMFIS_ACCEPT;
 		}
7d81c053
 		if(!nflag)
eba8ebeb
 			smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned"));
f7ab4278
 
83ed1043
 		logg("!%s: %s\n", sendmailId, mess);
ae0895b4
 		rc = cl_error;
31ee8076
 	} else if((ptr = strstr(mess, "FOUND")) != NULL) {
d982f8e4
 		/*
6bc34381
 		 * FIXME: This will give false positives if the
d982f8e4
 		 *	word "FOUND" is in the email, e.g. the
 		 *	quarantine directory is /tmp/VIRUSES-FOUND
 		 */
83ed1043
 		int i;
 		char **to, *virusname, *err;
6a54127a
 		char reject[1024];
144bc6cf
 
734ea355
 		/*
620ed9a7
 		 * Remove the "FOUND" word, and the space before it
734ea355
 		 */
 		*--ptr = '\0';
e3aaff8e
 
736c8d91
 		/* skip over 'stream/filename: ' at the start */
 		if((virusname = strchr(mess, ':')) != NULL)
 			virusname = &virusname[2];
 		else
 			virusname = mess;
 
28071296
 		if(!nflag) {
 			char buf[129];
 
 			snprintf(buf, sizeof(buf) - 1, "%s %s", _("Infected with"), virusname);
 			smfi_addheader(ctx, "X-Virus-Status", buf);
 		}
23248fbc
 
c1a4d3de
 		if(quarantine_dir)
 			qfile(privdata, sendmailId, virusname);
 
83ed1043
 		/*
 		 * Setup err as a list of recipients
 		 */
 		err = (char *)cli_malloc(1024);
e3aaff8e
 
83ed1043
 		if(err == NULL) {
 			clamfi_cleanup(ctx);
 			return cl_error;
 		}
502c8234
 
83ed1043
 		/*
 		 * Use snprintf rather than printf since we don't know
 		 * the length of privdata->from and may get a buffer
 		 * overrun
 		 */
 		snprintf(err, 1023, _("Intercepted virus from %s to"),
 			privdata->from);
e3aaff8e
 
83ed1043
 		ptr = strchr(err, '\0');
e3aaff8e
 
83ed1043
 		i = 1024;
049a18b9
 
83ed1043
 		for(to = privdata->to; *to; to++) {
 			/*
 			 * Re-alloc if we are about run out of buffer
 			 * space
 			 *
 			 * TODO: Only append *to if it's a valid, local
 			 *	email address
 			 */
 			if(&ptr[strlen(*to) + 2] >= &err[i]) {
 				i += 1024;
 				err = cli_realloc(err, i);
 				if(err == NULL) {
 					clamfi_cleanup(ctx);
 					return cl_error;
144bc6cf
 				}
83ed1043
 				ptr = strchr(err, '\0');
e3aaff8e
 			}
83ed1043
 			ptr = cli_strrcpy(ptr, " ");
 			ptr = cli_strrcpy(ptr, *to);
144bc6cf
 		}
83ed1043
 		(void)strcpy(ptr, "\n");
 
 		/* Include the sendmail queue ID in the log */
 		logg("%s: %s %s", sendmailId, mess, err);
 		free(err);
e3aaff8e
 
7a9b0f05
 		if(!qflag) {
c9af1776
 			char cmd[128];
1fcdb893
 			FILE *sendmail;
c9af1776
 
736c8d91
 			/*
 			 * If the oflag is given this sendmail command
 			 * will cause the mail being generated here to be
 			 * scanned. So if oflag is given we just put the
 			 * item in the queue so there's no scanning of two
 			 * messages at once. It'll still be scanned, but
 			 * not at the same time as the incoming message
1e2aaf5e
 			 *
aead37b9
 			 * FIXME: there is a race condition here when sendmail
 			 * and clamav-milter run on the same machine. If the
1e2aaf5e
 			 * system is very overloaded this sendmail can
 			 * take a long time to start - and may even fail
 			 * is the LA is > REFUSE_LA. In all the time we're
 			 * taking to start this sendmail, the sendmail that's
 			 * started us may timeout waiting for a response and
 			 * let the virus through (albeit tagged with
 			 * X-Virus-Status: Infected) because we haven't
 			 * sent SMFIS_DISCARD or SMFIS_REJECT
4818aa96
 			 *
 			 * -i flag, suggested by Michal Jaegermann
 			 *	<michal@harddata.com>
736c8d91
 			 */
 			snprintf(cmd, sizeof(cmd) - 1,
4818aa96
 				(oflag || fflag) ? "%s -t -i -odq" : "%s -t -i",
a321a25b
 				SENDMAIL_BIN);
c9af1776
 
835c9751
 			cli_dbgmsg("Calling %s\n", cmd);
c9af1776
 			sendmail = popen(cmd, "w");
 
7a9b0f05
 			if(sendmail) {
7ea21452
 				if(from && from[0])
 					fprintf(sendmail, "From: %s\n", from);
 				else
 					fprintf(sendmail, "From: %s\n", privdata->from);
4c5e69c8
 #ifdef	BOUNCE
c5e2c4a9
 				if(bflag && privdata->from) {
 					fprintf(sendmail, "To: %s\n", privdata->from);
7a9b0f05
 					fprintf(sendmail, "Cc: %s\n", postmaster);
 				} else
4c5e69c8
 #endif
7a9b0f05
 					fprintf(sendmail, "To: %s\n", postmaster);
 
c5e2c4a9
 				if((!pflag) && privdata->to)
7a9b0f05
 					for(to = privdata->to; *to; to++)
 						fprintf(sendmail, "Cc: %s\n", *to);
00727a0e
 				/*
ba2dfeb2
 				 * Auto-submitted is still a draft, keep an
00727a0e
 				 * eye on its format
 				 */
 				fputs("Auto-Submitted: auto-submitted (antivirus notify)\n", sendmail);
ba2dfeb2
 				/* "Sergey Y. Afonin" <asy@kraft-s.ru> */
 				if((ptr = smfi_getsymval(ctx, "{_}")) != NULL)
 					fprintf(sendmail,
 						"X-Infected-Received-From: %s\n",
 						ptr);
e8a362f9
 				fputs(_("Subject: Virus intercepted\n"), sendmail);
 
 				if(templateHeaders) {
 					/*
 					 * For example, to state the character
 					 * set of the message:
 					 *	Content-Type: text/plain; charset=koi8-r
 					 *
 					 * Based on a suggestion by Denis
 					 *	Eremenko <moonshade@mail.kz>
 					 */
 					FILE *fin = fopen(templateHeaders, "r");
 
 					if(fin == NULL) {
 						perror(templateHeaders);
83ed1043
 						logg(_("!Can't open e-mail template header file %s"),
e8a362f9
 								templateHeaders);
 					} else {
 						int c;
c497a659
 						int lastc = EOF;
 
 						while((c = getc(fin)) != EOF) {
e8a362f9
 							putc(c, sendmail);
c497a659
 							lastc = c;
 						}
e8a362f9
 						fclose(fin);
c497a659
 						/*
 						 * File not new line terminated
 						 */
 						if(lastc != '\n')
 							fputs(_("\n"), sendmail);
e8a362f9
 					}
 				}
 
 				fputs(_("\n"), sendmail);
7a9b0f05
 
e8a362f9
 				if((templateFile == NULL) ||
 				   (sendtemplate(ctx, templateFile, sendmail, virusname) < 0)) {
e368b3dd
 					/*
ff46437f
 					 * Use our own hardcoded template
 					 */
4c5e69c8
 #ifdef	BOUNCE
3ce543c7
 					if(bflag)
eba8ebeb
 						fputs(_("A message you sent to\n"), sendmail);
3ce543c7
 					else if(pflag)
4c5e69c8
 #else
 					if(pflag)
 #endif
3ce543c7
 						/*
ff46437f
 						 * The message is only going to
 						 * the postmaster, so include
 						 * some useful information
3ce543c7
 						 */
eba8ebeb
 						fprintf(sendmail, _("The message %1$s sent from %2$s to\n"),
c5e2c4a9
 							sendmailId, privdata->from);
3ce543c7
 					else
eba8ebeb
 						fprintf(sendmail, _("A message sent from %s to\n"),
c5e2c4a9
 							privdata->from);
3ce543c7
 
 					for(to = privdata->to; *to; to++)
144bc6cf
 						fprintf(sendmail, "\t%s\n", *to);
2a60a108
 					fprintf(sendmail, _("contained %s and has not been accepted for delivery.\n"), virusname);
3ce543c7
 
235ab38f
 					if(quarantine_dir != NULL)
d982f8e4
 						fprintf(sendmail, _("\nThe message in question has been quarantined as %s\n"), privdata->filename);
3ce543c7
 
736c8d91
 					if(hflag) {
eba8ebeb
 						fprintf(sendmail, _("\nThe message was received by %1$s from %2$s via %3$s\n\n"),
c5e2c4a9
 							smfi_getsymval(ctx, "j"), privdata->from,
736c8d91
 							smfi_getsymval(ctx, "_"));
eba8ebeb
 						fputs(_("For your information, the original message headers were:\n\n"), sendmail);
736c8d91
 						header_list_print(privdata->headers, sendmail);
 					} else if(privdata->received)
502c8234
 						/*
 						 * TODO: parse this to find
 						 * real infected machine.
 						 * Need to decide how to find
 						 * if it's a dynamic IP from a
 						 * dial up account in which
 						 * case there may not be much
 						 * we can do if that DHCP has
 						 * set the hostname...
 						 */
eba8ebeb
 						fprintf(sendmail, _("\nThe infected machine is likely to be here:\n%s\t\n"),
502c8234
 							privdata->received);
 
9e1e77b9
 				}
668c7570
 
835c9751
 				cli_dbgmsg("Waiting for %s to finish\n", cmd);
e13e1f7c
 				if(pclose(sendmail) != 0)
2dbc6ff7
 					logg(_("%s: Failed to notify clamAV interception - see dead.letter\n"), sendmailId);
83ed1043
 			} else
 				logg(_("^Can't execute '%s' to send virus notice"), cmd);
e3aaff8e
 		}
668c7570
 
31cd44cb
 		if(report && (quarantine == NULL) && (!advisory) &&
6bc34381
 		   (strstr(virusname, "Phishing") != NULL)) {
a32f3ba8
 			/*
 			 * Report phishing to an agency
 			 */
6bc34381
 			for(to = privdata->to; *to; to++) {
 				smfi_delrcpt(ctx, *to);
 				smfi_addheader(ctx, "X-Original-To", *to);
 			}
31cd44cb
 			if(smfi_addrcpt(ctx, report) == MI_FAILURE) {
6bc34381
 				/* It's a remote site */
 				if(privdata->filename) {
0b768f73
 					char cmd[1024];
6bc34381
 
0b768f73
 					snprintf(cmd, sizeof(cmd) - 1,
 						"mail -s \"%s\" %s < %s",
31cd44cb
 						virusname, report,
6942e399
 						privdata->filename);
6bc34381
 					if(system(cmd) == 0)
31cd44cb
 						logg(_("#Reported phishing to %s"), report);
0b768f73
 					else
 						logg(_("^Couldn't report to %s\n"), report);
a32f3ba8
 					if((!rejectmail) || privdata->discard)
 						rc = SMFIS_DISCARD;
 					else
 						rc = SMFIS_REJECT;
e1773c9e
 				} else {
 					logg(_("^Can't set anti-phish header\n"));
 					rc = (privdata->discard) ? SMFIS_DISCARD : SMFIS_REJECT;
6bc34381
 				}
 			} else {
e1773c9e
 				setsubject(ctx, "Phishing attempt trapped by ClamAV and redirected");
6bc34381
 
31cd44cb
 				logg("Redirected phish to %s\n", report);
6bc34381
 			}
31cd44cb
 		} else if(quarantine) {
e004f1c5
 			for(to = privdata->to; *to; to++) {
 				smfi_delrcpt(ctx, *to);
668c7570
 				smfi_addheader(ctx, "X-Original-To", *to);
e004f1c5
 			}
a321a25b
 			/*
 			 * NOTE: on a closed relay this will not work
 			 * if the recipient is a remote address
 			 */
e004f1c5
 			if(smfi_addrcpt(ctx, quarantine) == MI_FAILURE) {
e1773c9e
 				logg(_("^Can't set quarantine user %s"), quarantine);
a7d8f61f
 				rc = (privdata->discard) ? SMFIS_DISCARD : SMFIS_REJECT;
19575eba
 			} else {
31cd44cb
 				if(report &&
 				   strstr(virusname, "Phishing") != NULL)
 					(void)smfi_addrcpt(ctx, report);
 				setsubject(ctx, virusname);
6bc34381
 
e1773c9e
 				logg("Redirected virus to %s", quarantine);
19575eba
 			}
459b60af
 		} else if(advisory)
 			setsubject(ctx, virusname);
 		else if(rejectmail) {
b5c80361
 			if(privdata->discard)
 				rc = SMFIS_DISCARD;
 			else
 				rc = SMFIS_REJECT;	/* Delete the e-mail */
 		} else
3a03d183
 			rc = SMFIS_DISCARD;
e3aaff8e
 
72e8ae80
 		if(quarantine_dir) {
 			/*
 			 * Cleanup filename here otherwise clamfi_free() will
 			 * delete the file that we wish to keep because it
 			 * is infected
 			 */
 			free(privdata->filename);
 			privdata->filename = NULL;
 		}
 
19575eba
 		/*
 		 * Don't drop the message if it's been forwarded to a
 		 * quarantine email
 		 */
f0a816b1
 		snprintf(reject, sizeof(reject) - 1, _("virus %s detected by ClamAV - http://www.clamav.net"), virusname);
b5c80361
 		smfi_setreply(ctx, (char *)privdata->rejectCode, "5.7.1", reject);
ccabb6be
 		broadcast(mess);
a0ff4f89
 
7c907c8f
 		if(blacklist_time && privdata->ip[0]) {
fb540995
 			logg(_("Will blacklist %s for %d seconds because of %s\n"),
7c907c8f
 				privdata->ip, blacklist_time, virusname);
a0ff4f89
 			pthread_mutex_lock(&blacklist_mutex);
23a5e3b7
 			(void)tableUpdate(blacklist, privdata->ip,
a0ff4f89
 				(int)time((time_t *)0));
 			pthread_mutex_unlock(&blacklist_mutex);
 		}
f44cdd98
 	} else if((strstr(mess, "OK") == NULL) && (strstr(mess, "Empty file") == NULL)) {
31ee8076
 		if(!nflag)
 			smfi_addheader(ctx, "X-Virus-Status", _("Unknown"));
83ed1043
 		logg(_("!%s: incorrect message \"%s\" from clamd"),
 				sendmailId, mess);
31ee8076
 		rc = cl_error;
 	} else {
 		if(!nflag)
 			smfi_addheader(ctx, "X-Virus-Status", _("Clean"));
 
83ed1043
 		/* Include the sendmail queue ID in the log */
c17d8aba
 		if(logok)
9011890c
 			logg(_("%s: clean message from %s\n"),
 				sendmailId,
 				(privdata->from) ? privdata->from : _("an unknown sender"));
31ee8076
 
 		if(privdata->body) {
 			/*
 			 * Add a signature that all has been scanned OK
741a082e
 			 *
 			 * Note that this is simple minded and isn't aware of
 			 *	any MIME segments in the message. In practice
 			 *	this means that the message will only display
 			 *	on users' terminals if the message is
 			 *	plain/text
31ee8076
 			 */
 			off_t len = updateSigFile();
 
 			if(len) {
 				assert(Sflag != 0);
 
 				privdata->body = cli_realloc(privdata->body, privdata->bodyLen + len);
 				if(privdata->body) {
 					memcpy(&privdata->body[privdata->bodyLen], signature, len);
 					smfi_replacebody(ctx, privdata->body, privdata->bodyLen + len);
 				}
 			}
 		}
e3aaff8e
 	}
 
 	return rc;
 }
 
 static sfsistat
 clamfi_abort(SMFICTX *ctx)
 {
83ed1043
 	logg("*clamfi_abort\n");
e3aaff8e
 
 	clamfi_cleanup(ctx);
df1ec3e8
 	decrement_connexions();
e3aaff8e
 
83ed1043
 	logg("*clamfi_abort returns\n");
9dac9585
 
68d5a5f3
 	return cl_error;
e3aaff8e
 }
 
 static sfsistat
 clamfi_close(SMFICTX *ctx)
 {
b4b5c17d
 	logg("*clamfi_close\n");
6338dd5e
 
 	clamfi_cleanup(ctx);
df1ec3e8
 	decrement_connexions();
e3aaff8e
 
 	return SMFIS_CONTINUE;
 }
 
 static void
 clamfi_cleanup(SMFICTX *ctx)
 {
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
 
9dac9585
 	cli_dbgmsg("clamfi_cleanup\n");
 
7418fb74
 	if(privdata) {
530999cb
 		clamfi_free(privdata, 0);
c54d7329
 		smfi_setpriv(ctx, NULL);
 	}
 }
 
 static void
530999cb
 clamfi_free(struct privdata *privdata, int keep)
c54d7329
 {
03bc6e11
 	cli_dbgmsg("clamfi_free\n");
d982f8e4
 
c54d7329
 	if(privdata) {
ae0895b4
 #ifdef	SESSION
 		struct session *session;
 #endif
51b03ecb
 		if(privdata->body)
 			free(privdata->body);
 
789aab57
 		if(privdata->dataSocket >= 0)
7418fb74
 			close(privdata->dataSocket);
e3aaff8e
 
668c7570
 		if(privdata->filename != NULL) {
456edb55
 			/*
9dac9585
 			 * Don't print an error if the file hasn't been
 			 * created yet
456edb55
 			 */
 			if((unlink(privdata->filename) < 0) && (errno != ENOENT)) {
668c7570
 				perror(privdata->filename);
83ed1043
 				logg(_("!Can't remove clean file %s"),
 					privdata->filename);
7d81c053
 			}
668c7570
 			free(privdata->filename);
 		}
 
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);
 		}
e3aaff8e
 
789aab57
 		if(privdata->subject)
add1de69
 			free(privdata->subject);
789aab57
 		if(privdata->sender)
7089d180
 			free(privdata->sender);
add1de69
 
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);
 		}
e3aaff8e
 
8528bc29
 		if(external) {
d982f8e4
 #ifdef	SESSION
d5e48ace
 			session = &sessions[privdata->serverNumber];
 			pthread_mutex_lock(&sstatus_mutex);
 			if(session->status == CMDSOCKET_INUSE) {
 				/*
 				 * Probably we've got here because
8a7ef08f
 				 * StreamMaxLength has been reached
d5e48ace
 				 */
3aa5c1c9
 #if	0
d5e48ace
 				pthread_mutex_unlock(&sstatus_mutex);
 				if(readTimeout) {
 					char buf[64];
 					const int fd = session->sock;
 
 					cli_dbgmsg("clamfi_free: flush server %d fd %d\n",
 						privdata->serverNumber, fd);
 
 					/*
 					 * FIXME: whenever this code gets
 					 *	executed, all of the PINGs fail
 					 *	in the next watchdog cycle
 					 */
 					while(clamd_recv(fd, buf, sizeof(buf)) > 0)
 						;
 				}
 				pthread_mutex_lock(&sstatus_mutex);
 #endif
 				/* Force a reset */
 				session->status = CMDSOCKET_DOWN;
 			}
03bc6e11
 			pthread_mutex_unlock(&sstatus_mutex);
d5e48ace
 #else
 			if(privdata->cmdSocket >= 0) {
cc2e9929
 #if	0
03bc6e11
 				char buf[64];
06bfd678
 
1f82a167
 				/*
d5e48ace
 				 * Flush the remote end so that clamd doesn't
 				 * get a SIGPIPE
1f82a167
 				 */
d5e48ace
 				if(readTimeout)
 					while(clamd_recv(privdata->cmdSocket, buf, sizeof(buf)) > 0)
 						;
cc2e9929
 #endif
d5e48ace
 				close(privdata->cmdSocket);
03bc6e11
 			}
3aa5c1c9
 #endif
1cc545df
 		} else if(privdata->root)
2365449e
 			/*
 			 * Since only one of clamfi_abort() and clamfi_eom()
 			 * can ever be called, and the only cl_dup is in
 			 * clamfi_eom() which calls cl_free soon after, this
 			 * should be overkill, since this can "never happen"
 			 */
1cc545df
 			cl_free(privdata->root);
 
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
736c8d91
 		if(privdata->received)
502c8234
 			free(privdata->received);
e3aaff8e
 
789aab57
 		if(keep) {
530999cb
 			memset(privdata, '\0', sizeof(struct privdata));
8cc2edc9
 #ifdef	SESSION
 			privdata->dataSocket = -1;
 #else
789aab57
 			privdata->dataSocket = privdata->cmdSocket = -1;
8cc2edc9
 #endif
789aab57
 		} else
530999cb
 			free(privdata);
e3aaff8e
 	}
530999cb
 
9dac9585
 	cli_dbgmsg("clamfi_free returns\n");
e3aaff8e
 }
 
f7ab4278
 /*
7d81c053
  * Returns < 0 for failure, otherwise the number of bytes sent
f7ab4278
  */
e3aaff8e
 static int
7d81c053
 clamfi_send(struct privdata *privdata, size_t len, const char *format, ...)
e3aaff8e
 {
 	char output[BUFSIZ];
 	const char *ptr;
7d81c053
 	int ret = 0;
e3aaff8e
 
 	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);
0c933aca
 		vsnprintf(output, sizeof(output) - 1, format, argp);
e3aaff8e
 		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) {
ae0895b4
 		const int nbytes = (privdata->filename) ?
668c7570
 			write(privdata->dataSocket, ptr, len) :
 			send(privdata->dataSocket, ptr, len, 0);
e3aaff8e
 
8ac80fb8
 		assert(privdata->dataSocket >= 0);
 
e3aaff8e
 		if(nbytes == -1) {
ae0895b4
 			if(privdata->filename) {
ea4465c4
 #ifdef HAVE_STRERROR_R
83ed1043
 				char buf[32];
 
 				perror(privdata->filename);
 				strerror_r(errno, buf, sizeof(buf));
7dd38665
 				logg(_("!write failure (%lu bytes) to %s: %s\n"),
 					(unsigned long)len, privdata->filename, buf);
ea4465c4
 #else
83ed1043
 				perror(privdata->filename);
7dd38665
 				logg(_("!write failure (%lu bytes) to %s: %s\n"),
 					(unsigned long)len, privdata->filename,
83ed1043
 					strerror(errno));
ea4465c4
 #endif
 			} else {
 				if(errno == EINTR)
 					continue;
 				perror("send");
8ac80fb8
 #ifdef HAVE_STRERROR_R
83ed1043
 				{
ea4465c4
 					char buf[32];
 					strerror_r(errno, buf, sizeof(buf));
7dd38665
 					logg(_("!write failure (%lu bytes) to clamd: %s\n"),
 						(unsigned long)len, buf);
83ed1043
 				}
8ac80fb8
 #else
7dd38665
 				logg(_("!write failure (%lu bytes) to clamd: %s\n"),
 					(unsigned long)len, strerror(errno));
8ac80fb8
 #endif
83fc7301
 				checkClamd(1);
8ac80fb8
 			}
e3aaff8e
 
 			return -1;
 		}
7d81c053
 		ret += nbytes;
e3aaff8e
 		len -= nbytes;
 		ptr = &ptr[nbytes];
7d81c053
 
 		if(streamMaxLength > 0L) {
 			privdata->numBytes += nbytes;
 			if(privdata->numBytes >= streamMaxLength)
 				break;
 		}
e3aaff8e
 	}
7d81c053
 	return ret;
e3aaff8e
 }
 
 /*
  * Like strcpy, but return the END of the destination, allowing a quicker
  * means of adding to the end of a string than strcat
  */
20bca05f
 #if	0
e3aaff8e
 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);
 }
20bca05f
 #endif
66ff992e
 
 /*
  * Read from clamav - timeout if necessary
  */
eaf74461
 static long
66ff992e
 clamd_recv(int sock, char *buf, size_t len)
 {
 	struct timeval tv;
eaf74461
 	long ret;
66ff992e
 
06bfd678
 	assert(sock >= 0);
 
ebc4ec8f
 	if(readTimeout == 0) {
 		do
eaf74461
 			/* TODO: Needs a test for ssize_t in configure */
 			ret = (long)recv(sock, buf, len, 0);
ebc4ec8f
 		while((ret < 0) && (errno == EINTR));
 
 		return ret;
 	}
66ff992e
 
96f3d93b
 	tv.tv_sec = readTimeout;
66ff992e
 	tv.tv_usec = 0;
 
ebc4ec8f
 	for(;;) {
620ed9a7
 		fd_set rfds;
 
 		FD_ZERO(&rfds);
 		FD_SET(sock, &rfds);
 
ebc4ec8f
 		switch(select(sock + 1, &rfds, NULL, NULL, &tv)) {
 			case -1:
 				if(errno == EINTR)
 					/* FIXME: work out time left */
 					continue;
 				perror("select");
 				return -1;
 			case 0:
83ed1043
 				logg(_("!No data received from clamd in %d seconds\n"), readTimeout);
ebc4ec8f
 				return 0;
 		}
 		break;
66ff992e
 	}
ebc4ec8f
 
 	do
 		ret = recv(sock, buf, len, 0);
 	while((ret < 0) && (errno == EINTR));
 
 	return ret;
66ff992e
 }
fe3d8be8
 
 /*
  * Read in the signature file
  */
 static off_t
 updateSigFile(void)
 {
 	struct stat statb;
 	int fd;
 
 	if(sigFilename == NULL)
 		/* nothing to read */
b5c80361
 		return 0;
fe3d8be8
 
 	if(stat(sigFilename, &statb) < 0) {
 		perror(sigFilename);
83ed1043
 		logg(_("Can't stat %s"), sigFilename);
fe3d8be8
 		return 0;
 	}
 
 	if(statb.st_mtime <= signatureStamp)
 		return statb.st_size;	/* not changed */
 
 	fd = open(sigFilename, O_RDONLY);
 	if(fd < 0) {
 		perror(sigFilename);
83ed1043
 		logg(_("Can't open %s"), sigFilename);
fe3d8be8
 		return 0;
 	}
 
 	signatureStamp = statb.st_mtime;
 
d23d480c
 	signature = cli_realloc(signature, statb.st_size);
456edb55
 	if(signature)
80386ef0
 		cli_readn(fd, signature, statb.st_size);
fe3d8be8
 	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));
502c8234
 	if(ret) {
 		ret->first = NULL;
 		ret->last = NULL;
 	}
9e1e77b9
 	return ret;
 }
 
 static void
 header_list_free(header_list_t list)
 {
 	struct header_node_t *iter;
 
9fe789f8
 	if(list == NULL)
 		return;
 
9e1e77b9
 	iter = list->first;
df1ec3e8
 	while(iter) {
9e1e77b9
 		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;
 
9fe789f8
 	if(list == NULL)
 		return;
 
81bdf63b
 	len = (size_t)(strlen(headerf) + strlen(headerv) + 3);
9e1e77b9
 
 	header = (char *)cli_malloc(len);
502c8234
 	if(header == NULL)
 		return;
 
f9f239aa
 	sprintf(header, "%s: %s", headerf, headerv);
5b6bb93b
 	new_node = (struct header_node_t *)cli_malloc(sizeof(struct header_node_t));
502c8234
 	if(new_node == NULL) {
 		free(header);
 		return;
 	}
9e1e77b9
 	new_node->header = header;
 	new_node->next = NULL;
d5e48ace
 	if(list->first == NULL)
9e1e77b9
 		list->first = new_node;
 	if(list->last)
 		list->last->next = new_node;
 
 	list->last = new_node;
 }
 
 static void
54a9f64e
 header_list_print(const header_list_t list, FILE *fp)
9e1e77b9
 {
 	const struct header_node_t *iter;
 
54a9f64e
 	if(list == NULL)
 		return;
 
736c8d91
 	for(iter = list->first; iter; iter = iter->next) {
a321a25b
 		if(strncmp(iter->header, "From ", 5) == 0)
736c8d91
 			putc('>', fp);
9e1e77b9
 		fprintf(fp, "%s\n", iter->header);
736c8d91
 	}
9e1e77b9
 }
3ce543c7
 
f7ab4278
 /*
df1ec3e8
  * Establish a connexion to clamd
f7ab4278
  *	Returns success (1) or failure (0)
  */
 static int
 connect2clamd(struct privdata *privdata)
 {
b5c80361
 	assert(privdata != NULL);
f7ab4278
 	assert(privdata->dataSocket == -1);
 	assert(privdata->from != NULL);
 	assert(privdata->to != NULL);
 
83ed1043
 	logg("*connect2clamd\n");
f7ab4278
 
31ee8076
 	if(quarantine_dir || tmpdir) {	/* store message in a temporary file */
f7ab4278
 		int ntries = 5;
235ab38f
 		const char *dir = (tmpdir) ? tmpdir : quarantine_dir;
f7ab4278
 
1d05987b
 		/*
 		 * TODO: investigate mkdtemp on LINUX and possibly others
 		 */
89a2d133
 #ifdef	C_AIX
 		/*
 		 * Patch by Andy Feldt <feldt@nhn.ou.edu>, AIX 5.2 sets errno
 		 * to ENOENT often and sometimes sets errno to 0 (after a
 		 * database reload) for the mkdir call
 		 */
 		if((mkdir(dir, 0700) < 0) && (errno != EEXIST) && (errno > 0) &&
 		    (errno != ENOENT)) {
 #else
d982f8e4
 		if((mkdir(dir, 0700) < 0) && (errno != EEXIST)) {
89a2d133
 #endif
d982f8e4
 			perror(dir);
83ed1043
 			logg(_("mkdir %s failed"), dir);
456edb55
 			return 0;
 		}
d982f8e4
 		privdata->filename = (char *)cli_malloc(strlen(dir) + 12);
f7ab4278
 
e84d28a1
 		if(privdata->filename == NULL)
 			return 0;
 
f7ab4278
 		do {
d982f8e4
 			sprintf(privdata->filename, "%s/msg.XXXXXX", dir);
f7ab4278
 #if	defined(C_LINUX) || defined(C_BSD) || defined(HAVE_MKSTEMP) || defined(C_SOLARIS)
 			privdata->dataSocket = mkstemp(privdata->filename);
 #else
 			if(mktemp(privdata->filename) == NULL) {
83ed1043
 				logg(_("mktemp %s failed"), privdata->filename);
f7ab4278
 				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) {
b5c80361
 			perror(privdata->filename);
83ed1043
 			logg(_("Temporary quarantine file %s creation failed"),
 				privdata->filename);
e84d28a1
 			free(privdata->filename);
 			privdata->filename = NULL;
f7ab4278
 			return 0;
 		}
06bfd678
 		privdata->serverNumber = 0;
31ee8076
 		cli_dbgmsg("Saving message to %s to scan later\n", privdata->filename);
 	} else {	/* communicate to clamd */
f7ab4278
 		int freeServer, nbytes;
3844e1f0
 		in_port_t p;
f7ab4278
 		struct sockaddr_in reply;
 		char buf[64];
 
d982f8e4
 #ifdef	SESSION
 		struct session *session;
 #else
f7ab4278
 		assert(privdata->cmdSocket == -1);
06bfd678
 #endif
f7ab4278
 
 		/*
 		 * 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) {
06bfd678
 #ifndef	SESSION
f7ab4278
 			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));
72ce4b70
 			server.sun_path[sizeof(server.sun_path)-1]='\0';
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;
 			}
06bfd678
 			privdata->serverNumber = 0;
 #endif
f7ab4278
 			freeServer = 0;
31ee8076
 		} else {	/* TCP/IP */
06bfd678
 #ifdef	SESSION
 			freeServer = findServer();
 			if(freeServer < 0)
 				return 0;
d468d5b1
 			assert(freeServer < (int)max_children);
06bfd678
 #else
f7ab4278
 			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;
3d1678b1
 			assert(freeServer < (int)numServers);
f7ab4278
 
 			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) {
37ba3b97
 				char *hostname = cli_strtok(serverHostNames, freeServer, ":");
 
 				perror(hostname ? hostname : "connect");
 				close(privdata->cmdSocket);
 				privdata->cmdSocket = -1;
 				if(hostname)
 					free(hostname);
 				time(&last_failed_pings[freeServer]);
f7ab4278
 				return 0;
 			}
37ba3b97
 			last_failed_pings[freeServer] = (time_t)0;
06bfd678
 #endif
0abc0a57
 			privdata->serverNumber = freeServer;
f7ab4278
 		}
 
06bfd678
 #ifdef	SESSION
89a2d133
 		if(serverIPs[freeServer] == (int)inet_addr("127.0.0.1")) {
31ee8076
 			privdata->filename = cli_gentemp(NULL);
ae0895b4
 			if(privdata->filename) {
 				cli_dbgmsg("connect2clamd(%d): creating %s\n", freeServer, privdata->filename);
 #ifdef	O_TEXT
 				privdata->dataSocket = open(privdata->filename, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_TEXT, 0600);
 #else
 				privdata->dataSocket = open(privdata->filename, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0600);
 #endif
 				if(privdata->dataSocket < 0) {
 					perror(privdata->filename);
 					free(privdata->filename);
 					privdata->filename = NULL;
 				} else
7089d180
 					return sendToFrom(privdata);
ae0895b4
 			}
 		}
9dac9585
 		cli_dbgmsg("connect2clamd(%d): STREAM\n", freeServer);
235ab38f
 
ae0895b4
 		session = &sessions[freeServer];
9dac9585
 		if((session->sock < 0) || (send(session->sock, "STREAM\n", 7, 0) < 7)) {
06bfd678
 			perror("send");
03bc6e11
 			pthread_mutex_lock(&sstatus_mutex);
ae0895b4
 			session->status = CMDSOCKET_DOWN;
03bc6e11
 			pthread_mutex_unlock(&sstatus_mutex);
83ed1043
 			logg(_("!failed to send STREAM command clamd server %d"),
 				freeServer);
9c1c533d
 
f7ab4278
 			return 0;
 		}
06bfd678
 #else
f7ab4278
 		if(send(privdata->cmdSocket, "STREAM\n", 7, 0) < 7) {
 			perror("send");
83ed1043
 			logg(_("!failed to send STREAM command clamd"));
f7ab4278
 			return 0;
 		}
 		shutdown(privdata->cmdSocket, SHUT_WR);
06bfd678
 #endif
 
 		/*
 		 * Create socket that we'll use to send the data to clamd
 		 */
 		if((privdata->dataSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
 			perror("socket");
710c1e6c
 			logg(_("!failed to create TCPSocket to talk to clamd\n"));
06bfd678
 			return 0;
 		}
f7ab4278
 
06bfd678
 		shutdown(privdata->dataSocket, SHUT_RD);
 
 #ifdef	SESSION
ae0895b4
 		nbytes = clamd_recv(session->sock, buf, sizeof(buf));
d982f8e4
 		if(nbytes <= 0) {
 			if(nbytes < 0) {
 				perror("recv");
710c1e6c
 				logg(_("!recv failed from clamd getting PORT\n"));
83ed1043
 			} else
710c1e6c
 				logg(_("!EOF from clamd getting PORT\n"));
83ed1043
 
add5094f
 			pthread_mutex_lock(&sstatus_mutex);
ae0895b4
 			session->status = CMDSOCKET_DOWN;
4a5aa10e
 			return pthread_mutex_unlock(&sstatus_mutex);
add5094f
 		}
06bfd678
 #else
f7ab4278
 		nbytes = clamd_recv(privdata->cmdSocket, buf, sizeof(buf));
d982f8e4
 		if(nbytes <= 0) {
 			if(nbytes < 0) {
 				perror("recv");
710c1e6c
 				logg(_("!recv failed from clamd getting PORT\n"));
83ed1043
 			} else
710c1e6c
 				logg(_("!EOF from clamd getting PORT\n"));
83ed1043
 
f7ab4278
 			return 0;
 		}
add5094f
 #endif
f7ab4278
 		buf[nbytes] = '\0';
 #ifdef	CL_DEBUG
 		if(debug_level >= 4)
 			cli_dbgmsg("Received: %s", buf);
 #endif
226d027a
 		if(sscanf(buf, "PORT %hu\n", &p) != 1) {
710c1e6c
 			logg(_("!Expected port information from clamd, got '%s'\n"),
83ed1043
 				buf);
c5a386b3
 #ifdef	SESSION
ae0895b4
 			session->status = CMDSOCKET_DOWN;
c5a386b3
 			pthread_mutex_unlock(&sstatus_mutex);
 #endif
f7ab4278
 			return 0;
 		}
 
 		memset((char *)&reply, 0, sizeof(struct sockaddr_in));
 		reply.sin_family = AF_INET;
226d027a
 		reply.sin_port = (in_port_t)htons(p);
f7ab4278
 
 		assert(serverIPs != NULL);
 
 		reply.sin_addr.s_addr = serverIPs[freeServer];
 
 #ifdef	CL_DEBUG
 		if(debug_level >= 4)
d5e48ace
 #ifdef	SESSION
 			cli_dbgmsg(_("Connecting to local port %d - data %d cmd %d\n"),
 				p, privdata->dataSocket, session->sock);
 #else
 			cli_dbgmsg(_("Connecting to local port %d - data %d cmd %d\n"),
 				p, privdata->dataSocket, privdata->cmdSocket);
 #endif
f7ab4278
 #endif
 
 		if(connect(privdata->dataSocket, (struct sockaddr *)&reply, sizeof(struct sockaddr_in)) < 0) {
 			perror("connect");
 
9dac9585
 			cli_dbgmsg("Failed to connect to port %d given by clamd",
 				p);
f7ab4278
 			/* 0.4 - use better error message */
 #ifdef HAVE_STRERROR_R
83ed1043
 			strerror_r(errno, buf, sizeof(buf));
 			logg(_("!Failed to connect to port %d given by clamd: %s"),
226d027a
 					p, buf);
f7ab4278
 #else
83ed1043
 			logg(_("!Failed to connect to port %d given by clamd: %s"), p, strerror(errno));
f7ab4278
 #endif
add5094f
 #ifdef	SESSION
4ec64d57
 			pthread_mutex_lock(&sstatus_mutex);
ae0895b4
 			session->status = CMDSOCKET_DOWN;
4ec64d57
 			pthread_mutex_unlock(&sstatus_mutex);
add5094f
 #endif
f7ab4278
 			return 0;
 		}
 	}
 
7089d180
 	if(!sendToFrom(privdata))
 		return 0;
 
 	cli_dbgmsg("connect2clamd: serverNumber = %d\n", privdata->serverNumber);
 
 	return 1;
 }
 
 /*
  * Combine the To and From into one clamfi_send to save bandwidth
  * when sending using TCP/IP to connect to a remote clamd, by band
  * width here I mean number of packets
  */
 static int
 sendToFrom(struct privdata *privdata)
 {
 	char **to;
 	char *msg;
 	int length;
 
aead37b9
 	length = strlen(privdata->from) + 34;
f7ab4278
 	for(to = privdata->to; *to; to++)
e368b3dd
 		length += strlen(*to) + 5;
aead37b9
 
 	msg = cli_malloc(length + 1);
 
 	if(msg) {
 		sprintf(msg, "Received: by clamav-milter\nFrom: %s\n",
 			privdata->from);
 
 		for(to = privdata->to; *to; to++) {
 			char *eom = strchr(msg, '\0');
 
 			sprintf(eom, "To: %s\n", *to);
 		}
 		if(clamfi_send(privdata, length, msg) != length) {
 			free(msg);
f7ab4278
 			return 0;
aead37b9
 		}
 		free(msg);
 	} else {
f44cdd98
 		if(clamfi_send(privdata, 0,
 		    "Received: by clamav-milter\nFrom: %s\n",
 		    privdata->from) <= 0)
 			return 0;
aead37b9
 
 		for(to = privdata->to; *to; to++)
 			if(clamfi_send(privdata, 0, "To: %s\n", *to) <= 0)
 				return 0;
 	}
f7ab4278
 
 	return 1;
 }
b5648b5a
 
 /*
83fc7301
  * If possible, check if clamd has died, and, if requested, report if it has
  * Returns true if OK or unknown, otherwise false
b5648b5a
  */
83fc7301
 static int
 checkClamd(int log_result)
b5648b5a
 {
 	pid_t pid;
 	int fd, nbytes;
 	char buf[9];
 
83fc7301
 	if(!localSocket) {
 		/* communicating via TCP, is one of the servers localhost? */
 		int i, onlocal;
 
 		onlocal = 0;
 		for(i = 0; i < numServers; i++)
 #ifdef	INADDR_LOOPBACK
 			if(serverIPs[0] == htonl(INADDR_LOOPBACK)) {
 #else
 			if(serverIPs[0] == inet_addr("127.0.0.1")) {
 #endif
 				onlocal = 1;
 				break;
 			}
 
 		if(!onlocal) {
 			/* No local clamd, use pingServer() to tell */
 			for(i = 0; i < numServers; i++)
e1a1ce90
 				if(serverIPs[i] && pingServer(i))
83fc7301
 					return 1;
 			if(log_result)
 				logg(_("!Can't find any clamd server\n"));
 			return 0;
 		}
 	}
b5648b5a
 
 	if(pidFile == NULL)
83fc7301
 		return 1;	/* PidFile directive missing from clamd.conf */
b5648b5a
 
 	fd = open(pidFile, O_RDONLY);
 	if(fd < 0) {
83fc7301
 		if(log_result) {
 			perror(pidFile);
 			logg(_("!Can't open %s\n"), pidFile);
 		}
 		return 1;	/* unknown */
b5648b5a
 	}
 	nbytes = read(fd, buf, sizeof(buf) - 1);
0c933aca
 	if(nbytes < 0)
 		perror(pidFile);
 	else
 		buf[nbytes] = '\0';
b5648b5a
 	close(fd);
 	pid = atoi(buf);
ecaaaf05
 	if((kill(pid, 0) < 0) && (errno == ESRCH)) {
83fc7301
 		if(log_result) {
 			perror("clamd");
 			logg(_("!Clamd (pid %d) seems to have died\n"), (int)pid);
 		}
 		return 0;	/* down */
b5648b5a
 	}
83fc7301
 	return 1;	/* up */
b5648b5a
 }
3ce543c7
 
 /*
  * Send a templated message about an intercepted message. Very basic for
  * now, just to prove it works, will enhance the flexability later, only
734ea355
  * supports %v and $sendmail_variables$ at present.
502c8234
  *
  * TODO: more template features
  * TODO: allow filename to start with a '|' taken to mean the output of
  *	a program
3ce543c7
  */
 static int
a0c42dae
 sendtemplate(SMFICTX *ctx, const char *filename, FILE *sendmail, const char *virusname)
3ce543c7
 {
 	FILE *fin = fopen(filename, "r");
 	struct stat statb;
734ea355
 	char *buf, *ptr /* , *ptr2 */;
e368b3dd
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
3ce543c7
 
 	if(fin == NULL) {
 		perror(filename);
83ed1043
 		logg(_("!Can't open e-mail template file %s"), filename);
3ce543c7
 		return -1;
 	}
 
 	if(fstat(fileno(fin), &statb) < 0) {
 		/* File disappeared in race condition? */
 		perror(filename);
83ed1043
 		logg(_("!Can't stat e-mail template file %s"), filename);
3ce543c7
 		fclose(fin);
 		return -1;
 	}
 	buf = cli_malloc(statb.st_size + 1);
 	if(buf == NULL) {
456edb55
 		fclose(fin);
83ed1043
 		logg(_("!Out of memory"));
3ce543c7
 		return -1;
 	}
cc2e9929
 	if(fread(buf, sizeof(char), statb.st_size, fin) != (size_t)statb.st_size) {
80386ef0
 		perror(filename);
83ed1043
 		logg(_("!Error reading e-mail template file %s"),
 			filename);
80386ef0
 		fclose(fin);
bd25d9ef
 		free(buf);
80386ef0
 		return -1;
 	}
3ce543c7
 	fclose(fin);
 	buf[statb.st_size] = '\0';
 
734ea355
 	for(ptr = buf; *ptr; ptr++)
e368b3dd
 		switch(*ptr) {
 			case '%': /* clamAV variable */
 				switch(*++ptr) {
 					case 'v':	/* virus name */
 						fputs(virusname, sendmail);
 						break;
 					case '%':
 						putc('%', sendmail);
 						break;
 					case 'h':	/* headers */
 						if(privdata)
 							header_list_print(privdata->headers, sendmail);
 						break;
 					case '\0':
 						putc('%', sendmail);
 						--ptr;
 						continue;
 					default:
83ed1043
 						logg(_("!%s: Unknown clamAV variable \"%c\"\n"),
e368b3dd
 							filename, *ptr);
 						break;
 				}
5e9f7430
 				break;
e368b3dd
 			case '$': /* sendmail string */ {
 				const char *val;
 				char *end = strchr(++ptr, '$');
734ea355
 
e368b3dd
 				if(end == NULL) {
83ed1043
 					logg(_("!%s: Unterminated sendmail variable \"%s\"\n"),
 						filename, ptr);
e368b3dd
 					continue;
 				}
 				*end = '\0';
 
 				val = smfi_getsymval(ctx, ptr);
 				if(val == NULL) {
 					fputs(ptr, sendmail);
83ed1043
 						logg(_("!%s: Unknown sendmail variable \"%s\"\n"),
e368b3dd
 							filename, ptr);
 				} else
 					fputs(val, sendmail);
 				ptr = end;
5e9f7430
 				break;
e368b3dd
 			}
 			case '\\':
 				if(*++ptr == '\0') {
 					--ptr;
 					continue;
 				}
 				putc(*ptr, sendmail);
 				break;
 			default:
 				putc(*ptr, sendmail);
 		}
3ce543c7
 
 	free(buf);
 
734ea355
 	return 0;
3ce543c7
 }
459b60af
 
 /*
b475d5ea
  * Keep the infected file in quarantine, return success (0) or failure
c86c794b
  *
  * It's quicker if the quarantine directory is on the same filesystem
  *	as the temporary directory
b475d5ea
  */
 static int
31ee8076
 qfile(struct privdata *privdata, const char *sendmailId, const char *virusname)
b475d5ea
 {
d982f8e4
 	int MM, YY, DD;
 	time_t t;
3aa5c1c9
 	size_t len;
d982f8e4
 	char *newname, *ptr;
 	const struct tm *tm;
b475d5ea
 
 	assert(privdata != NULL);
 
 	if((privdata->filename == NULL) || (virusname == NULL))
 		return -1;
 
d982f8e4
 	cli_dbgmsg("qfile filename '%s' sendmailId '%s' virusname '%s'\n", privdata->filename, sendmailId, virusname);
3aa5c1c9
 
d982f8e4
 	len = strlen(quarantine_dir);
 
 	newname = cli_malloc(len + strlen(sendmailId) + strlen(virusname) + 10);
b475d5ea
 
 	if(newname == NULL)
 		return -1;
 
d982f8e4
 	t = time((time_t *)0);
 	tm = localtime(&t);
 	MM = tm->tm_mon + 1;
 	YY = tm->tm_year - 100;
 	DD = tm->tm_mday;
 
e6e4f99f
 	sprintf(newname, "%s/%02d%02d%02d", quarantine_dir, YY, MM, DD);
89a2d133
 #ifdef	C_AIX
 	if((mkdir(newname, 0700) < 0) && (errno != EEXIST) && (errno > 0) &&
 	    (errno != ENOENT)) {
 #else
e6e4f99f
 	if((mkdir(newname, 0700) < 0) && (errno != EEXIST)) {
89a2d133
 #endif
e6e4f99f
 		perror(newname);
0b768f73
 		logg(_("!mkdir %s failed\n"), newname);
e6e4f99f
 		return -1;
 	}
d982f8e4
 	sprintf(newname, "%s/%02d%02d%02d/%s.%s",
 		quarantine_dir, YY, MM, DD, sendmailId, virusname);
aead37b9
 
 	/*
 	 * Strip out funnies that may be in the name of the virus, such as '/'
 	 * that would cause the quarantine to fail to save since the name
 	 * of the virus is included in the filename
 	 */
d982f8e4
 	for(ptr = &newname[len + 8]; *ptr; ptr++) {
eba8ebeb
 #ifdef	C_DARWIN
 		*ptr &= '\177';
 #endif
0b768f73
 #if	defined(MSDOS) || defined(C_CYGWIN) || defined(C_WINDOWS) || defined(C_OS2)
45221e8c
 		if(strchr("/*?<>|\\\"+=,;:\t ", *ptr))
aead37b9
 #else
 		if(*ptr == '/')
 #endif
 			*ptr = '_';
 	}
d982f8e4
 	cli_dbgmsg("qfile move '%s' to '%s'\n", privdata->filename, newname);
 
dfa70a67
 	if(move(privdata->filename, newname) < 0) {
0b768f73
 		logg(_("^Can't rename %1$s to %2$s\n"),
 			privdata->filename, newname);
e6e4f99f
 		free(newname);
 		return -1;
 	}
b475d5ea
 	free(privdata->filename);
 	privdata->filename = newname;
 
0b768f73
 	logg(_("Email quarantined as %s\n"), newname);
d982f8e4
 
b475d5ea
 	return 0;
 }
 
 /*
dfa70a67
  * Move oldfile to newfile using the fastest possible method
  */
 static int
 move(const char *oldfile, const char *newfile)
 {
65cdd146
 	int ret, c;
 	FILE *fin, *fout;
dfa70a67
 #ifdef	C_LINUX
 	struct stat statb;
65cdd146
 	int in, out;
dfa70a67
 	off_t offset;
7b2de1a6
 #endif
dfa70a67
 
7b2de1a6
 	ret = rename(oldfile, newfile);
08b19537
 	if(ret >= 0)
7b2de1a6
 		return 0;
dfa70a67
 
 	if((ret < 0) && (errno != EXDEV)) {
 		perror(newfile);
 		return -1;
 	}
 
 #ifdef	C_LINUX	/* >= 2.2 */
65cdd146
 	in = open(oldfile, O_RDONLY);
 	if(in < 0) {
dfa70a67
 		perror(oldfile);
 		return -1;
 	}
 
65cdd146
 	if(fstat(in, &statb) < 0) {
dfa70a67
 		perror(oldfile);
65cdd146
 		close(in);
dfa70a67
 		return -1;
 	}
65cdd146
 	out = open(newfile, O_WRONLY|O_CREAT, 0600);
 	if(out < 0) {
dfa70a67
 		perror(newfile);
65cdd146
 		close(in);
dfa70a67
 		return -1;
 	}
 	offset = (off_t)0;
65cdd146
 	ret = sendfile(out, in, &offset, statb.st_size);
 	close(in);
dfa70a67
 	if(ret < 0) {
530999cb
 		/*
 		 * Fall back if sendfile fails, which will happen on Linux
 		 * 2.6 :-(. FreeBSD works correctly, so the ifdef should be
 		 * fixed
 		 */
65cdd146
 		close(out);
dfa70a67
 		unlink(newfile);
65cdd146
 
 		fin = fopen(oldfile, "r");
 		if(fin == NULL)
 			return -1;
 
 		fout = fopen(newfile, "w");
 		if(fout == NULL) {
 			fclose(fin);
 			return -1;
 		}
 		while((c = getc(fin)) != EOF)
 			putc(c, fout);
 
 		fclose(fin);
 		fclose(fout);
 	} else
 		close(out);
dfa70a67
 #else
 	fin = fopen(oldfile, "r");
 	if(fin == NULL)
 		return -1;
 
 	fout = fopen(newfile, "w");
 	if(fout == NULL) {
 		fclose(fin);
 		return -1;
 	}
 	while((c = getc(fin)) != EOF)
 		putc(c, fout);
 
 	fclose(fin);
 	fclose(fout);
 #endif
 
358facc3
 	cli_dbgmsg("removing %s\n", oldfile);
 
dfa70a67
 	return unlink(oldfile);
 }
 
 /*
459b60af
  * Store the name of the virus in the subject of the e-mail
  */
 static void
 setsubject(SMFICTX *ctx, const char *virusname)
 {
add1de69
 	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
459b60af
 	char subject[128];
 
add1de69
 	if(privdata->subject)
 		smfi_addheader(ctx, "X-Original-Subject", privdata->subject);
 
eba8ebeb
 	snprintf(subject, sizeof(subject) - 1, _("[Virus] %s"), virusname);
add1de69
 	if(privdata->subject)
 		smfi_chgheader(ctx, "Subject", 1, subject);
 	else
 		smfi_addheader(ctx, "Subject", subject);
459b60af
 }
734ea355
 
85883a02
 #if	0
734ea355
 /*
  * TODO: gethostbyname_r is non-standard so different operating
  * systems do it in different ways. Need more examples
dfa70a67
  * Perhaps we could use res_search()?
c88c200f
  * Perhaps we could use http://www.chiark.greenend.org.uk/~ian/adns/
734ea355
  *
  * Returns 0 for success
  */
 static int
 clamfi_gethostbyname(const char *hostname, struct hostent *hp, char *buf, size_t len)
 {
 #if	defined(HAVE_GETHOSTBYNAME_R_6)
 	/* e.g. Linux */
 	struct hostent *hp2;
0863025b
 	int ret = -1;
734ea355
 
 	if((hostname == NULL) || (hp == NULL))
 		return -1;
 	if(gethostbyname_r(hostname, hp, buf, len, &hp2, &ret) < 0)
0863025b
 		return ret;
734ea355
 #elif	defined(HAVE_GETHOSTBYNAME_R_5)
 	/* e.g. BSD, Solaris, Cygwin */
0863025b
 	int ret = -1;
734ea355
 
 	if((hostname == NULL) || (hp == NULL))
 		return -1;
 	if(gethostbyname_r(hostname, hp, buf, len, &ret) == NULL)
0863025b
 		return ret;
734ea355
 #elif	defined(HAVE_GETHOSTBYNAME_R_3)
 	/* e.g. HP/UX, AIX */
 	if((hostname == NULL) || (hp == NULL))
 		return -1;
 	if(gethostbyname_r(hostname, &hp, (struct hostent_data *)buf) < 0)
0863025b
 		return h_errno;
734ea355
 #else
 	/* Single thread the code */
 	struct hostent *hp2;
 	static pthread_mutex_t hostent_mutex = PTHREAD_MUTEX_INITIALIZER;
 
 	if((hostname == NULL) || (hp == NULL))
 		return -1;
 
 	pthread_mutex_lock(&hostent_mutex);
 	if((hp2 = gethostbyname(hostname)) == NULL) {
 		pthread_mutex_unlock(&hostent_mutex);
0863025b
 		return h_errno;
734ea355
 	}
 	memcpy(hp, hp2, sizeof(struct hostent));
 	pthread_mutex_unlock(&hostent_mutex);
 #endif
 
 	return 0;
 }
85883a02
 #endif
eef726b0
 
 /*
df1ec3e8
  * Handle the -I flag
  */
 static int
 add_local_ip(char *address)
 {
 	char *opt, *pref;
 	int preflen;
 	int retval;
 	struct in_addr ignoreIP;
477961e9
 #ifdef	AF_INET6
df1ec3e8
 	struct in6_addr ignoreIP6;
477961e9
 #endif
df1ec3e8
 
 	opt = cli_strdup(address);
 	if(opt == NULL)
 		return 0;
 
 	pref = strchr(opt, '/'); /* search for "/prefix" */
 	if(pref)
 		*pref = '\0';
 #ifdef HAVE_INET_NTOP
 	/* IPv4 address ? */
 	if(inet_pton(AF_INET, opt, &ignoreIP) > 0) {
 #else
 	if(inet_aton(address, &ignoreIP)) {
 #endif
 		struct cidr_net *net;
 
 		for(net = (struct cidr_net *)localNets; net->base; net++)
 			;
 		if(pref && *(pref+1))
a32f3ba8
 			preflen = atoi(pref+1);
df1ec3e8
 		else
 			preflen = 32;
 
 		net->base = ntohl(ignoreIP.s_addr);
a32f3ba8
 		net->mask = MAKEMASK(preflen);
df1ec3e8
 
 		retval = 1;
 	}
 
 #ifdef HAVE_INET_NTOP
 #ifdef AF_INET6
 	else if(inet_pton(AF_INET6, opt, &ignoreIP6) > 0) {
 		/* IPv6 address ? */
 		localNets6[localNets6_cnt].base = ignoreIP6;
 
 		if(pref && *(pref+1))
 			preflen = atoi (pref+1);
 		else
 			preflen = 128;
 		localNets6[localNets6_cnt].preflen = preflen;
 		localNets6_cnt++;
 
 		retval = 1;
 	}
 #endif
 #endif
7dd38665
 	else
 		retval = 0;
 
a32f3ba8
 	free(opt);
df1ec3e8
 	return retval;
 }
 
 /*
  * Determine if an IPv6 email address is "local". The address is the
  *	human readable version. Calls isLocalAddr if the given address is
  *	IPv4
  */
 static int
 isLocal(const char *addr)
 {
 	struct in_addr ip;
477961e9
 #ifdef	AF_INET6
df1ec3e8
 	struct in6_addr ip6;
477961e9
 #endif
df1ec3e8
 
 #ifdef HAVE_INET_NTOP
 	if(inet_pton(AF_INET, addr, &ip) > 0)
9bb9dd03
 		return isLocalAddr(ip.s_addr);
df1ec3e8
 #ifdef AF_INET6
 	else if(inet_pton (AF_INET6, addr, &ip6) > 0) {
 		int i;
 		const cidr_net6 *pnet6 = localNets6;
 
 		for (i = 0; i < localNets6_cnt; i++) {
 			int match = 1;
 			int j;
 
b1409e43
 			for(j = 0; match && j < (pnet6->preflen >> 3); j++)
df1ec3e8
 				if(pnet6->base.s6_addr[j] != ip6.s6_addr[j])
 					match = 0;
3d9fff4d
 			if(match && (j < 16)) {
b1409e43
 				uint8_t mask = (uint8_t)(0xff << (8 - (pnet6->preflen & 7)) & 0xFF);
df1ec3e8
 
 				if((pnet6->base.s6_addr[j] & mask) != (ip6.s6_addr[j] & mask))
 					match = 0;
 			}
 			if(match)
 				return 1;	/* isLocal */
 			pnet6++;
 		 }
 	}
9bb9dd03
 #endif	/* AF_INET6 */
 #endif	/* HAVE_INET_NTOP */
df1ec3e8
 	return isLocalAddr(inet_addr(addr));
 }
 
 /*
eef726b0
  * David Champion <dgc@uchicago.edu>
  *
  * Check whether addr is on network by applying netmasks.
  * addr must be a 32-bit integer-packed IPv4 address in network order.
  * For example:
3aa5c1c9
  *	struct in_addr IPAddress;
  *	isLocal = isLocalAddr(IPAddress.s_addr);
eef726b0
  */
 static int
 isLocalAddr(in_addr_t addr)
 {
 	const struct cidr_net *net;
 
 	for(net = localNets; net->base; net++)
df1ec3e8
 		if((net->base & net->mask) == (ntohl(addr) & net->mask))
eef726b0
 			return 1;
 
 	return 0;	/* is non-local */
 }
03bc6e11
 
 /*
  * Can't connect to any clamd server. This is serious, we need to inform
  * someone. In the absence of SNMP the best way is by e-mail. We
  * don't want to flood so there's a need to restrict to
  * no more than say one message every 15 minutes
  */
 static void
 clamdIsDown(void)
 {
 	static time_t lasttime;
 	time_t thistime, diff;
 	static pthread_mutex_t time_mutex = PTHREAD_MUTEX_INITIALIZER;
 
b4b5c17d
 	logg(_("!No response from any clamd server - your AV system is not scanning emails\n"));
03bc6e11
 
 	time(&thistime);
 	pthread_mutex_lock(&time_mutex);
 	diff = thistime - lasttime;
 	pthread_mutex_unlock(&time_mutex);
 
 	if(diff >= (time_t)(15 * 60)) {
 		char cmd[128];
 		FILE *sendmail;
 
4818aa96
 		snprintf(cmd, sizeof(cmd) - 1, "%s -t -i", SENDMAIL_BIN);
03bc6e11
 
 		sendmail = popen(cmd, "w");
 
 		if(sendmail) {
 			fprintf(sendmail, "To: %s\n", postmaster);
 			fprintf(sendmail, "From: %s\n", postmaster);
 			fputs(_("Subject: ClamAV Down\n"), sendmail);
 			fputs("Priority: High\n\n", sendmail);
 
 			fputs(_("This is an automatic message\n\n"), sendmail);
 
 			if(numServers == 1)
 				fputs(_("The clamd program cannot be contacted.\n"), sendmail);
 			else
 				fputs(_("No clamd server can be contacted.\n"), sendmail);
 
 			fputs(_("Emails may not be being scanned, please check your servers.\n"), sendmail);
 
 			if(pclose(sendmail) == 0) {
 				pthread_mutex_lock(&time_mutex);
 				time(&lasttime);
 				pthread_mutex_unlock(&time_mutex);
 			}
 		}
 	}
 }
 
 #ifdef	SESSION
 /*
  * Thread to monitor the links to clamd sessions. Any marked as being in
  * an error state because of previous I/O errors are restarted, and a heartbeat
  * is sent the others
1fcd39ef
  *
  * It is woken up when the milter goes idle, when there are no free servers
  * available and once every readTimeout-1 seconds
fea63953
  *
7b2de1a6
  * TODO: reload the whiteList file if it's been changed
  *
fea63953
  * TODO: localSocket support
03bc6e11
  */
 static void *
 watchdog(void *a)
 {
 	static pthread_mutex_t watchdog_mutex = PTHREAD_MUTEX_INITIALIZER;
 
152f7c23
 	while(!quitting) {
89a2d133
 		unsigned int i;
03bc6e11
 		struct timespec ts;
 		struct timeval tp;
ae0895b4
 		struct session *session;
03bc6e11
 
 		gettimeofday(&tp, NULL);
1fcd39ef
 
6bc34381
 		ts.tv_sec = tp.tv_sec + freshclam_monitor;
03bc6e11
 		ts.tv_nsec = tp.tv_usec * 1000;
 		cli_dbgmsg("watchdog sleeps\n");
1f82a167
 		pthread_mutex_lock(&watchdog_mutex);
 		/*
 		 * Sometimes this returns EPIPE which isn't listed as a
 		 * return value in the Linux man page for pthread_cond_timedwait
 		 * so I'm not sure why it happens
 		 */
 		switch(pthread_cond_timedwait(&watchdog_cond, &watchdog_mutex, &ts)) {
 			case ETIMEDOUT:
 			case 0:
 				break;
 			default:
 				perror("pthread_cond_timedwait");
 		}
 		pthread_mutex_unlock(&watchdog_mutex);
040708a3
 
6338dd5e
 		cli_dbgmsg("watchdog wakes\n");
03bc6e11
 
f1617494
 		if(check_and_reload_database() != 0) {
5e90d12b
 			if(cl_error != SMFIS_ACCEPT) {
 				smfi_stop();
 				return NULL;
 			}
 			logg(_("!No emails will be scanned"));
d5e48ace
 		}
f1617494
 
ae0895b4
 		i = 0;
 		session = sessions;
03bc6e11
 		pthread_mutex_lock(&sstatus_mutex);
ae0895b4
 		for(; i < max_children; i++, session++) {
 			const int sock = session->sock;
03bc6e11
 
 			/*
 			 * Check all free sessions are still usable
 			 * This could take some time with many free
 			 * sessions to slow remote servers, so only do this
 			 * when the system is quiet (not 100% accurate when
 			 * determining this since n_children isn't locked but
 			 * that doesn't really matter)
 			 */
 			cli_dbgmsg("watchdog: check server %d\n", i);
9fcf8647
 			if((n_children == 0) &&
 			   (session->status == CMDSOCKET_FREE) &&
 			   (clamav_versions != NULL)) {
1fcd39ef
 				if(send(sock, "VERSION\n", 8, 0) == 8) {
 					char buf[81];
 					const int nbytes = clamd_recv(sock, buf, sizeof(buf) - 1);
03bc6e11
 
1fcd39ef
 					if(nbytes <= 0)
ae0895b4
 						session->status = CMDSOCKET_DOWN;
1fcd39ef
 					else {
 						buf[nbytes] = '\0';
 						if(strncmp(buf, "ClamAV ", 7) == 0) {
 							/* Remove the trailing new line from the reply */
 							char *ptr;
31ee8076
 
1fcd39ef
 							if((ptr = strchr(buf, '\n')) != NULL)
 								*ptr = '\0';
0863025b
 							pthread_mutex_lock(&version_mutex);
31ee8076
 							if(clamav_versions[i] == NULL)
16d1f4d0
 								clamav_versions[i] = cli_strdup(buf);
31ee8076
 							else if(strcmp(buf, clamav_versions[i]) != 0) {
83ed1043
 								logg("New version received for server %d: '%s'\n", i, buf);
1fcd39ef
 								free(clamav_versions[i]);
16d1f4d0
 								clamav_versions[i] = cli_strdup(buf);
1fcd39ef
 							}
0863025b
 							pthread_mutex_unlock(&version_mutex);
1fcd39ef
 						} else {
 							cli_warnmsg("watchdog: expected \"ClamAV\", got \"%s\"\n", buf);
ae0895b4
 							session->status = CMDSOCKET_DOWN;
1fcd39ef
 						}
1f82a167
 					}
 				} else {
 					perror("send");
ae0895b4
 					session->status = CMDSOCKET_DOWN;
1f82a167
 				}
03bc6e11
 
ae0895b4
 				if(session->status == CMDSOCKET_DOWN)
03bc6e11
 					cli_warnmsg("Session %d has gone down\n", i);
 			}
 			/*
 			 * Reset all all dead sessions
 			 */
ae0895b4
 			if(session->status == CMDSOCKET_DOWN) {
03bc6e11
 				/*
 				 * The END command probably won't get through,
 				 * but let's give it a go anyway
 				 */
4ec64d57
 				if(sock >= 0) {
 					send(sock, "END\n", 4, 0);
 					close(sock);
 				}
03bc6e11
 
 				cli_dbgmsg("Trying to restart session %d\n", i);
 				if(createSession(i) == 0) {
ae0895b4
 					session->status = CMDSOCKET_FREE;
03bc6e11
 					cli_warnmsg("Session %d restarted OK\n", i);
 				}
 			}
 		}
 		for(i = 0; i < max_children; i++)
ae0895b4
 			if(sessions[i].status != CMDSOCKET_DOWN)
03bc6e11
 				break;
 
 		if(i == max_children)
 			clamdIsDown();
 		pthread_mutex_unlock(&sstatus_mutex);
f1617494
 
 		/* Garbage collect IP addresses no longer blacklisted */
 		if(blacklist) {
 			pthread_mutex_lock(&blacklist_mutex);
93928eab
 			tableIterate(blacklist, timeoutBlacklist, NULL);
f1617494
 			pthread_mutex_unlock(&blacklist_mutex);
 		}
03bc6e11
 	}
d982f8e4
 	cli_dbgmsg("watchdog quits\n");
03bc6e11
 	return NULL;
 }
45221e8c
 #else	/*!SESSION*/
 /*
  * Reload the database from time to time, when using the internal scanner
7b2de1a6
  *
  * TODO: reload the whiteList file if it's been changed
45221e8c
  */
c5e2c4a9
 /*ARGSUSED*/
45221e8c
 static void *
 watchdog(void *a)
 {
 	static pthread_mutex_t watchdog_mutex = PTHREAD_MUTEX_INITIALIZER;
 
6338dd5e
 	if((!blacklist_time) && external)
 		return NULL;	/* no need for this thread */
 
45221e8c
 	while(!quitting) {
 		struct timespec ts;
 		struct timeval tp;
 
 		gettimeofday(&tp, NULL);
 
6bc34381
 		ts.tv_sec = tp.tv_sec + freshclam_monitor;
45221e8c
 		ts.tv_nsec = tp.tv_usec * 1000;
 		cli_dbgmsg("watchdog sleeps\n");
040708a3
 
45221e8c
 		pthread_mutex_lock(&watchdog_mutex);
 		/*
 		 * Sometimes this returns EPIPE which isn't listed as a
 		 * return value in the Linux man page for pthread_cond_timedwait
 		 * so I'm not sure why it happens
 		 */
 		switch(pthread_cond_timedwait(&watchdog_cond, &watchdog_mutex, &ts)) {
 			case ETIMEDOUT:
 			case 0:
 				break;
 			default:
 				perror("pthread_cond_timedwait");
 		}
 		pthread_mutex_unlock(&watchdog_mutex);
6338dd5e
 		cli_dbgmsg("watchdog wakes\n");
45221e8c
 
 		/*
2365449e
 		 * TODO: sanity check that if n_children == 0, that
 		 * root->refcount == 0. Unfortunatly root->refcount isn't
 		 * thread-safe, since it's governed by a mutex that we can't
 		 * see, and there's no access to it via an approved method
 		 */
f1617494
 		if(check_and_reload_database() != 0) {
5e90d12b
 			if(cl_error != SMFIS_ACCEPT) {
 				smfi_stop();
 				return NULL;
 			}
 			logg(_("!No emails will be scanned"));
f1617494
 		}
 		/* Garbage collect IP addresses no longer blacklisted */
 		if(blacklist) {
 			pthread_mutex_lock(&blacklist_mutex);
93928eab
 			tableIterate(blacklist, timeoutBlacklist, NULL);
f1617494
 			pthread_mutex_unlock(&blacklist_mutex);
45221e8c
 		}
 	}
 	cli_dbgmsg("watchdog quits\n");
 	return NULL;
 }
03bc6e11
 #endif
3aa5c1c9
 
f1617494
 /*
  * Check to see if the database needs to be reloaded
  *	Return 0 for success
  */
 static int
 check_and_reload_database(void)
 {
 	int rc;
 
 	if(external)
 		return 0;
 
040708a3
 	if(reload) {
 		rc = 1;
 		reload = 0;
 	} else
 		rc = cl_statchkdir(&dbstat);
 
 	switch(rc) {
f1617494
 		case 1:
5e90d12b
 			logg("^Database has changed, loading updated database\n");
f1617494
 			cl_statfree(&dbstat);
 			rc = loadDatabase();
 			if(rc != 0) {
5e90d12b
 				logg("!Failed to load updated database\n");
f1617494
 				return rc;
 			}
 			break;
 		case 0:
e04ec6cc
 			logg("*Database has not changed\n");
f1617494
 			break;
 		default:
9e3242ca
 			logg("Database error %d - %s is stopping\n",
 				rc, progname);
f1617494
 			return 1;
 	}
 	return 0;	/* all OK */
 }
 
 static void
93928eab
 timeoutBlacklist(char *ip_address, int time_of_blacklist, void *v)
f1617494
 {
 	if(time_of_blacklist == 0)	/* Must not blacklist this IP address */
 		return;
 	if((time((time_t *)0) - time_of_blacklist) > blacklist_time)
 		tableRemove(blacklist, ip_address);
 }
 
152f7c23
 static void
 quit(void)
 {
 	quitting++;
 
8a7ef08f
 #ifdef	SESSION
0863025b
 	pthread_mutex_lock(&version_mutex);
8a7ef08f
 #endif
3d9fff4d
 	logg(_("Stopping %s\n"), clamav_version);
8a7ef08f
 #ifdef	SESSION
0863025b
 	pthread_mutex_unlock(&version_mutex);
8a7ef08f
 #endif
152f7c23
 
8528bc29
 	if(!external) {
aaade7d3
 		pthread_mutex_lock(&root_mutex);
d5e48ace
 		if(root) {
 			cl_free(root);
 			root = NULL;
152f7c23
 		}
aaade7d3
 		pthread_mutex_unlock(&root_mutex);
d5e48ace
 	} else {
 #ifdef	SESSION
 		int i = 0;
 		struct session *session = sessions;
152f7c23
 
d5e48ace
 		pthread_mutex_lock(&sstatus_mutex);
89a2d133
 		for(; i < ((localSocket != NULL) ? 1 : (int)max_children); i++) {
d5e48ace
 			/*
 			 * Check all free sessions are still usable
 			 * This could take some time with many free
 			 * sessions to slow remote servers, so only do this
 			 * when the system is quiet (not 100% accurate when
 			 * determining this since n_children isn't locked but
 			 * that doesn't really matter)
 			 */
 			cli_dbgmsg("quit: close server %d\n", i);
 			if(session->status == CMDSOCKET_FREE) {
 				const int sock = session->sock;
 
 				send(sock, "END\n", 4, 0);
 				shutdown(sock, SHUT_WR);
 				session->status = CMDSOCKET_DOWN;
 				pthread_mutex_unlock(&sstatus_mutex);
 				close(sock);
 				pthread_mutex_lock(&sstatus_mutex);
 			}
7b2de1a6
 			session++;
d5e48ace
 		}
 		pthread_mutex_unlock(&sstatus_mutex);
152f7c23
 #endif
d5e48ace
 	}
235ab38f
 
 	if(tmpdir && !cli_leavetemps_flag)
7b2de1a6
 		if(rmdir(tmpdir) < 0)
 			perror(tmpdir);
235ab38f
 
7089d180
 	broadcast(_("Stopping clamav-milter"));
 
1d05987b
 	if(pidfile)
 		if(unlink(pidfile) < 0)
 			perror(pidfile);
 
83ed1043
 	logg_close();
152f7c23
 }
 
 static void
 broadcast(const char *mess)
 {
 	struct sockaddr_in s;
 
ccabb6be
 	if(broadcastSock < 0)
 		return;
 
152f7c23
 	memset(&s, '\0', sizeof(struct sockaddr_in));
 	s.sin_family = AF_INET;
835c9751
 	s.sin_port = (in_port_t)htons(tcpSocket ? tcpSocket : 3310);
152f7c23
 	s.sin_addr.s_addr = htonl(INADDR_BROADCAST);
 
9dac9585
 	cli_dbgmsg("broadcast %s to %d\n", mess, broadcastSock);
5d79922b
 	if(sendto(broadcastSock, mess, strlen(mess), 0, (struct sockaddr *)&s, sizeof(struct sockaddr_in)) < 0)
152f7c23
 		perror("sendto");
 }
93899a2c
 
 /*
aaade7d3
  * Load a new database into the internal scanner
93899a2c
  */
 static int
 loadDatabase(void)
 {
cf691ad7
 	/*extern const char *cl_retdbdir(void);	/* FIXME: should be included */
bd7ead0c
 	int ret;
5e90d12b
 	unsigned int signatures, dboptions;
bd7ead0c
 	char *daily;
93899a2c
 	struct cl_cvd *d;
 	const struct cfgstruct *cpt;
aaade7d3
 	struct cl_node *newroot, *oldroot;
93899a2c
 	static const char *dbdir;
 
8528bc29
 	assert(!external);
93899a2c
 
9fcf8647
 	if(dbdir == NULL) {
a1a2f947
 		/*
 		 * First time through, find out in which directory the signature
 		 * databases are
 		 */
23a00eaf
 		if((cpt = cfgopt(copt, "DatabaseDirectory")) && cpt->enabled)
93899a2c
 			dbdir = cpt->strarg;
 		else
 			dbdir = cl_retdbdir();
 	}
 
eaad4daa
 	daily = cli_malloc(strlen(dbdir) + 11);
93899a2c
 	sprintf(daily, "%s/daily.cvd", dbdir);
66643f8f
 	if(access(daily, R_OK) < 0)
eaad4daa
 		sprintf(daily, "%s/daily.cld", dbdir);
 
93899a2c
 
1d05987b
 	cli_dbgmsg("loadDatabase: check %s for updates\n", daily);
 
93899a2c
 	d = cl_cvdhead(daily);
 
bd7ead0c
 	if(d) {
 		char *ptr;
 		time_t t = d->stime;
6fa82cd3
 		char buf[26];
93899a2c
 
bd7ead0c
 		snprintf(clamav_version, VERSION_LENGTH,
add738d2
 			"ClamAV %s/%u/%s", get_version(), d->version,
92697347
 			cli_ctime(&t, buf, sizeof(buf)));
bd7ead0c
 
 		/* Remove ctime's trailing \n */
 		if((ptr = strchr(clamav_version, '\n')) != NULL)
 			*ptr = '\0';
3d324ca4
 
 		cl_cvdfree(d);
bd7ead0c
 	} else
 		snprintf(clamav_version, VERSION_LENGTH,
 			"ClamAV version %s, clamav-milter version %s",
add738d2
 			cl_retver(), get_version());
93899a2c
 
 	free(daily);
 
 #ifdef	SESSION
0863025b
 	pthread_mutex_lock(&version_mutex);
93899a2c
 	if(clamav_versions == NULL) {
 		clamav_versions = (char **)cli_malloc(sizeof(char *));
0863025b
 		if(clamav_versions == NULL) {
 			pthread_mutex_unlock(&version_mutex);
93899a2c
 			return -1;
0863025b
 		}
93899a2c
 		clamav_version = cli_malloc(VERSION_LENGTH + 1);
 		if(clamav_version == NULL) {
 			free(clamav_versions);
 			clamav_versions = NULL;
0863025b
 			pthread_mutex_unlock(&version_mutex);
93899a2c
 			return -1;
 		}
 	}
0863025b
 	pthread_mutex_unlock(&version_mutex);
93899a2c
 #endif
 	signatures = 0;
aaade7d3
 	newroot = NULL;
5e90d12b
 
ab9079a4
 	if(!cfgopt(copt, "PhishingSignatures")->enabled) {
5e90d12b
 		logg("Not loading phishing signatures.\n");
814c4a95
 		dboptions = 0;
 	} else
 		dboptions = CL_DB_PHISHING;
5e90d12b
 
 	ret = cl_load(dbdir, &newroot, &signatures, dboptions);
93899a2c
 	if(ret != 0) {
5e90d12b
 		logg("!%s\n", cl_strerror(ret));
93899a2c
 		return -1;
 	}
aaade7d3
 	if(newroot == NULL) {
5e90d12b
 		logg("!Can't initialize the virus database.\n");
93899a2c
 		return -1;
 	}
 
aaade7d3
 	ret = cl_build(newroot);
93899a2c
 	if(ret != 0) {
5e90d12b
 		logg("!Database initialization error: %s\n", cl_strerror(ret));
aaade7d3
 		cl_free(newroot);
93899a2c
 		return -1;
 	}
aaade7d3
 	pthread_mutex_lock(&root_mutex);
 	oldroot = root;
 	root = newroot;
 	pthread_mutex_unlock(&root_mutex);
93899a2c
 
8a7ef08f
 #ifdef	SESSION
83ed1043
 	pthread_mutex_lock(&version_mutex);
8a7ef08f
 #endif
c17d8aba
 	logg( _("Loaded %s\n"), clamav_version);
8a7ef08f
 #ifdef	SESSION
83ed1043
 	pthread_mutex_unlock(&version_mutex);
8a7ef08f
 #endif
c17d8aba
 	logg(_("ClamAV: Protecting against %u viruses\n"), signatures);
aaade7d3
 	if(oldroot) {
 		cl_free(oldroot);
c17d8aba
 		logg("#Database correctly reloaded (%u viruses)\n", signatures);
aaade7d3
 	} else
710c1e6c
 		logg("*Database loaded\n");
93899a2c
 
 	return cl_statinidir(dbdir, &dbstat);
 }
9dac9585
 
 static void
 sigsegv(int sig)
 {
 	signal(SIGSEGV, SIG_DFL);
92fbb0d6
 
 #ifdef HAVE_BACKTRACE
9c9e9b9b
 	print_trace();
92fbb0d6
 #endif
 
9fe789f8
 	logg("!Segmentation fault :-( Bye.., notify bugs@clamav.net\n");
9dac9585
 
040708a3
 	quitting++;
bf16b485
 	smfi_stop();
9dac9585
 }
 
040708a3
 static void
 sighup(int sig)
 {
49766e16
 	extern FILE *logg_fd;
040708a3
 
 	signal(SIGHUP, sighup);
 
49766e16
 	if(!(cfgopt(copt, "LogFile"))->enabled)
 		return;
 
040708a3
 	logg("SIGHUP caught: re-opening log file\n");
 	logg_close();
49766e16
 	logg("*Log file re-opened\n");
 	close(2);
 	dup(fileno(logg_fd));
040708a3
 }
 
 static void
 sigusr2(int sig)
 {
710c1e6c
 	signal(SIGUSR2, sigusr2);
040708a3
 
 	logg("^SIGUSR2 caught: scheduling database reload\n");
 	reload++;
 }
 
92fbb0d6
 #ifdef HAVE_BACKTRACE
9dac9585
 static void
9c9e9b9b
 print_trace(void)
9dac9585
 {
 	void *array[BACKTRACE_SIZE];
 	size_t size, i;
 	char **strings;
 	pid_t pid = getpid();
 
 	size = backtrace(array, BACKTRACE_SIZE);
 	strings = backtrace_symbols(array, size);
 
49766e16
 	logg("*Backtrace of pid %d:\n", pid);
9dac9585
 
83ed1043
 	for(i = 0; i < size; i++)
 		logg("bt[%u]: %s", i, strings[i]);
9dac9585
 
 	/* TODO: dump the current email */
 
 	free(strings);
 }
 #endif
19575eba
 
 /*
  * Check that the correct port name has been given, i.e. that the
  * input socket to clamav-milter from sendmail, is the same that
  * sendmail has been configured to use as it's output socket
  * Return:	<0 invalid
  *		=0 valid
  *		>0 unknown
c86c794b
  *
  * You wouldn't believe the amount of time I used to waste chasing bug reports
  *	from people who's sendmail.cf didn't tally with the arguments given to
9bb9dd03
  *	clamav-milter before I put this check in, which is why bug 726 must
  *	never be acted upon.
9fe789f8
  *
  * FIXME: return different codes for "the value is wrong" and "sendmail.cf"
66682bfa
  *	hasn't been set up, though that's not so easy to work out.
19575eba
  */
 static int
 verifyIncomingSocketName(const char *sockName)
 {
 #if HAVE_MMAP
 	int fd, ret;
 	char *ptr;
 	size_t size;
 	struct stat statb;
 
932fb5f5
 	if(strncmp(sockName, "inet:", 5) == 0)
 		/*
 		 * clamav-milter is running on a different machine from sendmail
 		 */
 		return 1;
 
7b2de1a6
 	if(sendmailCF)
 		fd = open(sendmailCF, O_RDONLY);
 	else {
 		fd = open("/etc/mail/sendmail.cf", O_RDONLY);
 		if(fd < 0)
 			fd = open("/etc/sendmail.cf", O_RDONLY);
 	}
19575eba
 
 	if(fd < 0)
 		return 1;
 
 	if(fstat(fd, &statb) < 0) {
 		close(fd);
 		return 1;
 	}
 
 	size = statb.st_size;
 
 	if(size == 0) {
 		close(fd);
 		return -1;
 	}
 
 	ptr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
 	if(ptr == MAP_FAILED) {
 		perror("mmap");
 		close(fd);
 		return -1;
 	}
 
 	ret = (cli_memstr(ptr, size, sockName, strlen(sockName)) != NULL) ? 1 : -1;
 
 	munmap(ptr, size);
 	close(fd);
 
 	return ret;
 #else	/*!HAVE_MMAP*/
 	return 1;
 #endif
 }
835c9751
 
 /*
508f5288
  * If the given email address is whitelisted don't scan emails to them,
  *	the addresses are in angle brackets e.g. <foo@bar.com>.
7b2de1a6
  *
620ed9a7
  * TODO: Allow regular expressions in the addresses
7b2de1a6
  * TODO: Syntax check the contents of the files
1d05987b
  * TODO: Allow emails of the form "name <address>"
508f5288
  * TODO: Allow emails not of the form "<address>", i.e. no angle brackets
cf691ad7
  * TODO: Assume that if a '@' is missing from the address, that all emails
  *	to that domain are to be whitelisted
835c9751
  */
 static int
feb192dd
 isWhitelisted(const char *emailaddress, int to)
835c9751
 {
feb192dd
 	static table_t *to_whitelist, *from_whitelist;	/* never freed */
 	table_t *table;
835c9751
 
49766e16
 	logg("*isWhitelisted %s\n", emailaddress);
7f9a33ee
 
 	/*
 	 * Don't scan messages to the quarantine email address
 	 */
 	if(quarantine && (strcasecmp(quarantine, emailaddress) == 0))
 		return 1;
 
feb192dd
 	if((to_whitelist == NULL) && whitelistFile) {
7b2de1a6
 		FILE *fin;
 		char buf[BUFSIZ + 1];
 
 		fin = fopen(whitelistFile, "r");
 
 		if(fin == NULL) {
 			perror(whitelistFile);
83ed1043
 			logg(_("!Can't open whitelist file %s"), whitelistFile);
7b2de1a6
 			return 0;
 		}
feb192dd
 		to_whitelist = tableCreate();
 		from_whitelist = tableCreate();
7b2de1a6
 
feb192dd
 		if((to_whitelist == NULL) || (from_whitelist == NULL)) {
83ed1043
 			logg(_("!Can't create whitelist table"));
feb192dd
 			if(to_whitelist) {
 				tableDestroy(to_whitelist);
 				to_whitelist = NULL;
 			} else {
 				tableDestroy(from_whitelist);
 				from_whitelist = NULL;
 			}
a0ff4f89
 			fclose(fin);
 			return 0;
 		}
 
7b2de1a6
 		while(fgets(buf, sizeof(buf), fin) != NULL) {
feb192dd
 			const char *ptr;
 
7b2de1a6
 			/* comment line? */
 			switch(buf[0]) {
 				case '#':
 				case '/':
 				case ':':
 					continue;
 			}
feb192dd
 			if(cli_chomp(buf) > 0) {
 				if((ptr = strchr(buf, ':')) != NULL) {
 					do
 						ptr++;
 					while(*ptr && isspace(*ptr));
 
 					if(*ptr == '\0') {
 						logg("*Ignoring bad line '%s'\n",
 							buf);
 						continue;
 					}
 				} else
 					ptr = buf;
 
 				if(strncasecmp(buf, "From:", 5) == 0)
 					table = from_whitelist;
 				else
 					table = to_whitelist;
 
 				(void)tableInsert(table, ptr, 1);
 			}
7b2de1a6
 		}
 		fclose(fin);
 	}
feb192dd
 	table = (to) ? to_whitelist : from_whitelist;
 
 	if(table && (tableFind(table, emailaddress) == 1))
7b2de1a6
 		/*
 		 * This recipient is on the whitelist
 		 */
 		return 1;
835c9751
 
 	return 0;
 }
89a2d133
 
a0ff4f89
 /*
  * Blacklist IP addresses that send malware. Often in the phishing world, one
  * phish is quickly followed by another. IP addresses are blacklisted for one
  * minute. We can't blacklist for longer since DHCP means we could hit innocent
  * parties, and in theory malware could go through a smart host and affect
  * innocent parties
  *
  * Note that sites which can't be blacklisted will have their timestamp set
  * to 0, since that can never be less than blacklist_time seconds from now
  */
 static int
 isBlacklisted(const char *ip_address)
 {
7df6b4c0
 	time_t t;
a0ff4f89
 
 	if(blacklist_time == 0)
 		/* Blacklisting not being used */
 		return 0;
 
49766e16
 	logg("*isBlacklisted %s\n", ip_address);
a0ff4f89
 
df1ec3e8
 	if(isLocal(ip_address))
a0ff4f89
 		return 0;
 
 	pthread_mutex_lock(&blacklist_mutex);
 	if(blacklist == NULL) {
 		blacklist = tableCreate();
 
f1617494
 		pthread_mutex_unlock(&blacklist_mutex);
 
 		if(blacklist == NULL)
83ed1043
 			logg(_("!Can't create blacklist table"));
a0ff4f89
 		return 0;
 	}
 	t = tableFind(blacklist, ip_address);
 	pthread_mutex_unlock(&blacklist_mutex);
 
 	if(t == (time_t)-1)
 		/* IP address is not blacklisted */
 		return 0;
 
 	if(t == (time_t)0)
 		/* IP cannot be blacklisted */
 		return 0;
 
7df6b4c0
 	if((time((time_t *)0) - t) <= blacklist_time)
a0ff4f89
 		return 1;
 
821dd71e
 	/* timedout: remove the IP from the blacklist */
e79aea7e
 	pthread_mutex_lock(&blacklist_mutex);
620ed9a7
 	tableRemove(blacklist, ip_address);
 	pthread_mutex_unlock(&blacklist_mutex);
 
a0ff4f89
 	return 0;
 }
 
cbdb077f
 #ifdef	HAVE_RESOLV_H
a0ff4f89
 /*
  * Determine our MX peers, they must never be blacklisted
  * See RFC1034 for the definition of the record formats
7df6b4c0
  *
  * This is only ever called once, which is wrong, but the overheard of calling
  * this from the watchdog isn't worth it
a0ff4f89
  */
93928eab
 static table_t *
 mx(const char *host, table_t *t)
a0ff4f89
 {
16d1f4d0
 	u_char *p, *end;
a32f3ba8
 	const HEADER *hp;
ead6d252
 	int len, i;
a0ff4f89
 	union {
 		HEADER h;
23a5e3b7
 		u_char u[PACKETSZ];
a0ff4f89
 	} q;
a32f3ba8
 	char buf[BUFSIZ];
a0ff4f89
 
93928eab
 	if(t == NULL) {
 		t = tableCreate();
a0ff4f89
 
93928eab
 		if(t == NULL)
 			return NULL;
a0ff4f89
 	}
 
8f8fa5de
 	len = safe_res_query(host, C_IN, T_MX, (u_char *)&q, sizeof(q));
ead6d252
 	if(len < 0)
93928eab
 		return t;	/* Host has no MX records */
a0ff4f89
 
ead6d252
 	if((unsigned int)len > sizeof(q))
93928eab
 		return t;
a0ff4f89
 
 	hp = &(q.h);
 	p = q.u + HFIXEDSZ;
 	end = q.u + len;
 
 	for(i = ntohs(hp->qdcount); i--; p += len + QFIXEDSZ)
ead6d252
 		if((len = dn_skipname(p, end)) < 0)
93928eab
 			return t;
a0ff4f89
 
 	i = ntohs(hp->ancount);
 
 	while((--i >= 0) && (p < end)) {
3d1678b1
 		in_addr_t addr;
a11b9be3
 		u_short type, pref;
 		u_long ttl;	/* unused */
a0ff4f89
 
 		if((len = dn_expand(q.u, end, p, buf, sizeof(buf) - 1)) < 0)
 			break;
 		p += len;
 		GETSHORT(type, p);
 		p += INT16SZ;
 		GETLONG(ttl, p);
 		GETSHORT(len, p);
 		if(type != T_MX) {
 			p += len;
 			continue;
 		}
 		GETSHORT(pref, p);
 		if((len = dn_expand(q.u, end, p, buf, sizeof(buf) - 1)) < 0)
 			break;
 		p += len;
 		addr = inet_addr(buf);
a5717226
 #ifdef	INADDR_NONE
3d1678b1
 		if(addr != INADDR_NONE) {
a5717226
 #else
 		if(addr != (in_addr_t)-1) {
 #endif
93928eab
 			(void)tableInsert(t, buf, 0);
a0ff4f89
 		} else
93928eab
 			t = resolve(buf, t);
a0ff4f89
 	}
93928eab
 	return t;
a0ff4f89
 }
 
0d945389
 /*
  * If the MX record points to a name, we need to resolve that name. This routine
  * does that
  */
93928eab
 static table_t *
 resolve(const char *host, table_t *t)
a0ff4f89
 {
16d1f4d0
 	u_char *p, *end;
a32f3ba8
 	const HEADER *hp;
 	int len, i;
a0ff4f89
 	union {
 		HEADER h;
23a5e3b7
 		u_char u[PACKETSZ];
a0ff4f89
 	} q;
a32f3ba8
 	char buf[BUFSIZ];
a0ff4f89
 
 	if((host == NULL) || (*host == '\0'))
93928eab
 		return t;
a0ff4f89
 
8f8fa5de
 	len = safe_res_query(host, C_IN, T_A, (u_char *)&q, sizeof(q));
a0ff4f89
 	if(len < 0)
93928eab
 		return t;	/* Host has no A records */
a0ff4f89
 
 	if((unsigned int)len > sizeof(q))
93928eab
 		return t;
a0ff4f89
 
 	hp = &(q.h);
 	p = q.u + HFIXEDSZ;
 	end = q.u + len;
 
 	for(i = ntohs(hp->qdcount); i--; p += len + QFIXEDSZ)
 		if((len = dn_skipname(p, end)) < 0)
93928eab
 			return t;
a0ff4f89
 
 	i = ntohs(hp->ancount);
 
 	while((--i >= 0) && (p < end)) {
a11b9be3
 		u_short type;
 		u_long ttl;
a0ff4f89
 		const char *ip;
93928eab
 		struct in_addr addr;
a0ff4f89
 
 		if((len = dn_expand(q.u, end, p, buf, sizeof(buf) - 1)) < 0)
9dfd6f20
 			return t;
a0ff4f89
 		p += len;
 		GETSHORT(type, p);
 		p += INT16SZ;
a11b9be3
 		GETLONG(ttl, p);	/* unused */
a0ff4f89
 		GETSHORT(len, p);
 		if(type != T_A) {
 			p += len;
 			continue;
 		}
 		memcpy(&addr, p, sizeof(struct in_addr));
a32f3ba8
 		p += 4;	/* Should check len == 4 */
a0ff4f89
 		ip = inet_ntoa(addr);
 		if(ip) {
93928eab
 			if(t == NULL) {
 				t = tableCreate();
 
 				if(t == NULL)
 					return NULL;
 			}
 			(void)tableInsert(t, ip, 0);
a0ff4f89
 		}
 	}
93928eab
 	return t;
a0ff4f89
 }
a32f3ba8
 
 /*
  * Validate SPF records to help to stop Phish false positives
684d3122
  * http://www.openspf.org/SPF_Record_Syntax
  *
9dfd6f20
  * Currently only handles ip4, a and mx fields in the DNS record
  * Having said that, this is NOT a replacement for spf-milter, it is NOT
  *	an SPF system, we ONLY use SPF records to reduce phish false positives
30ca616f
  * TODO: IPv6?
684d3122
  * TODO: cache queries?
420cda0b
  *
684d3122
  * INPUT: prevhosts, a list of hosts already searched: stops include loops
  *	e.g. mercado.com includes medrcadosw.com which includes mercado.com,
  *	causing a loop
420cda0b
  * Return 1 if SPF says this email is from a legitimate source
  *	0 for fail or unknown
a32f3ba8
  */
420cda0b
 static int
684d3122
 spf(struct privdata *privdata, table_t *prevhosts)
a32f3ba8
 {
93928eab
 	char *host, *ptr;
a32f3ba8
 	u_char *p, *end;
 	const HEADER *hp;
 	int len, i;
 	union {
 		HEADER h;
 		u_char u[PACKETSZ];
 	} q;
 	char buf[BUFSIZ];
 
684d3122
 	if(privdata->spf_ok)
 		return 1;
a32f3ba8
 	if(privdata->ip[0] == '\0')
420cda0b
 		return 0;
a32f3ba8
 	if(strcmp(privdata->ip, "127.0.0.1") == 0) {
 		/* Loopback always pass SPF */
 		privdata->spf_ok = 1;
420cda0b
 		return 1;
a32f3ba8
 	}
 	if(isLocal(privdata->ip)) {
 		/* Local addresses always pass SPF */
 		privdata->spf_ok = 1;
420cda0b
 		return 1;
a32f3ba8
 	}
 
 	if(privdata->from == NULL)
420cda0b
 		return 0;
1a2b28a9
 	if((host = strrchr(privdata->from, '@')) == NULL)
420cda0b
 		return 0;
a32f3ba8
 
93928eab
 	host = cli_strdup(++host);
a32f3ba8
 
93928eab
 	if(host == NULL)
420cda0b
 		return 0;
a32f3ba8
 
93928eab
 	ptr = strchr(host, '>');
a32f3ba8
 
 	if(ptr)
 		*ptr = '\0';
 
4a5aa10e
 	logg("*SPF query '%s'\n", host);
8f8fa5de
 	len = safe_res_query(host, C_IN, T_TXT, (u_char *)&q, sizeof(q));
a32f3ba8
 	if(len < 0) {
93928eab
 		free(host);
420cda0b
 		return 0;	/* Host has no TXT records */
a32f3ba8
 	}
 
 	if((unsigned int)len > sizeof(q)) {
93928eab
 		free(host);
420cda0b
 		return 0;
a32f3ba8
 	}
 
 	hp = &(q.h);
 	p = q.u + HFIXEDSZ;
 	end = q.u + len;
 
 	for(i = ntohs(hp->qdcount); i--; p += len + QFIXEDSZ)
 		if((len = dn_skipname(p, end)) < 0) {
93928eab
 			free(host);
420cda0b
 			return 0;
a32f3ba8
 		}
 
 	i = ntohs(hp->ancount);
 
 	while((--i >= 0) && (p < end) && !privdata->spf_ok) {
 		u_short type;
 		u_long ttl;
 		char txt[BUFSIZ];
 
 		if((len = dn_expand(q.u, end, p, buf, sizeof(buf) - 1)) < 0) {
93928eab
 			free(host);
420cda0b
 			return 0;
a32f3ba8
 		}
 		p += len;
 		GETSHORT(type, p);
 		p += INT16SZ;
 		GETLONG(ttl, p);	/* unused */
 		GETSHORT(len, p);
 		if(type != T_TXT) {
 			p += len;
 			continue;
 		}
b1409e43
 		strncpy(txt, (const char *)&p[1], sizeof(txt) - 1);
72ce4b70
 		txt[sizeof(txt)-1]='\0';
a32f3ba8
 		txt[len - 1] = '\0';
 		if((strncmp(txt, "v=spf1 ", 7) == 0) || (strncmp(txt, "spf2.0/pra ", 11) == 0)) {
 			int j;
 			char *record;
 			struct in_addr remote_ip;	/* IP connecting to us */
 
6ab97c20
 			logg("*%s(%s): SPF record %s\n",
93928eab
 				host, privdata->ip, txt);
a32f3ba8
 #ifdef HAVE_INET_NTOP
 			/* IPv4 address ? */
 			if(inet_pton(AF_INET, privdata->ip, &remote_ip) <= 0) {
 				p += len;
 				continue;
 			}
 #else
 			if(inet_aton(privdata->ip, &remote_ip) == 0) {
 				p += len;
 				continue;
 			}
 #endif
 
 			j = 1;	/* strtok 0 would give the v= part */
 			while((record = cli_strtok(txt, j++, " ")) != NULL) {
 				if(strncmp(record, "ip4:", 4) == 0) {
 					int preflen;
 					char *ip, *pref;
 					uint32_t mask;
 					struct in_addr spf_range;	/* acceptable range of IPs */
 
 					ip = &record[4];
 
 					pref = strchr(ip, '/');
 					preflen = 32;
 					if(pref) {
 						*pref++ = '\0';
 						if(*pref)
 							preflen = atoi(pref);
 					}
 
 #ifdef HAVE_INET_NTOP
 					/* IPv4 address ? */
6ab97c20
 					if(inet_pton(AF_INET, ip, &spf_range) <= 0) {
 						free(record);
a32f3ba8
 						continue;
6ab97c20
 					}
a32f3ba8
 #else
6ab97c20
 					if(inet_aton(ip, &spf_range) == 0) {
 						free(record);
a32f3ba8
 						continue;
6ab97c20
 					}
a32f3ba8
 #endif
 					mask = MAKEMASK(preflen);
 					if((ntohl(remote_ip.s_addr) & mask) == (ntohl(spf_range.s_addr) & mask)) {
1a2b28a9
 						if(privdata->subject)
 							logg("#SPF ip4 pass (%s) %s is valid for %s\n",
 								privdata->subject, ip, host);
 						else
 							logg("#SPF ip4 pass %s is valid for %s\n", ip, host);
a32f3ba8
 						privdata->spf_ok = 1;
93928eab
 					}
 				} else if(strcmp(record, "mx") == 0) {
 					table_t *t = mx(host, NULL);
 
 					if(t) {
 						tableIterate(t, spf_ip,
 							(void *)privdata);
 						tableDestroy(t);
 					}
 				} else if(strcmp(record, "a") == 0) {
 					table_t *t = resolve(host, NULL);
 
 					if(t) {
 						tableIterate(t, spf_ip,
 							(void *)privdata);
 						tableDestroy(t);
a32f3ba8
 					}
36f79c60
 				} else if(strncmp(record, "a:", 2) == 0) {
 					const char *ahost = &record[2];
 
 					if(*ahost && (strcmp(ahost, host) != 0)) {
 						table_t *t = resolve(ahost, NULL);
 
 						if(t) {
 							tableIterate(t, spf_ip,
 								(void *)privdata);
 							tableDestroy(t);
 						}
 					}
 				} else if(strncmp(record, "mx:", 3) == 0) {
 					const char *mxhost = &record[3];
 
 					if(*mxhost && (strcmp(mxhost, host) != 0)) {
 						table_t *t = mx(mxhost, NULL);
 
 						if(t) {
 							tableIterate(t, spf_ip,
 								(void *)privdata);
 							tableDestroy(t);
 						}
 					}
 				} else if(strncmp(record, "include:", 8) == 0) {
 					const char *inchost = &record[8];
 
684d3122
 					/*
 					 * Ensure we haven't already looked at
 					 *	the host that's to be included
 					 */
 					if(*inchost &&
 					   (strcmp(inchost, host) != 0) &&
 					   (tableFind(prevhosts, inchost) == -1)) {
b1409e43
 						char *real_from = privdata->from;
36f79c60
 						privdata->from = cli_malloc(strlen(inchost) + 3);
 						sprintf(privdata->from, "n@%s", inchost);
5063200a
 						tableInsert(prevhosts, host, 0);
684d3122
 						spf(privdata, prevhosts);
36f79c60
 						free(privdata->from);
 						privdata->from = real_from;
 					}
a32f3ba8
 				}
 				free(record);
93928eab
 				if(privdata->spf_ok)
 					break;
a32f3ba8
 			}
 		}
 		p += len;
 	}
93928eab
 	free(host);
420cda0b
 
 	return privdata->spf_ok;
93928eab
 }
 
 static void
 spf_ip(char *ip, int zero, void *v)
 {
 	struct privdata *privdata = (struct privdata *)v;
 
 	if(strcmp(ip, privdata->ip) == 0) {
1a2b28a9
 		if(privdata->subject)
 			logg("#SPF mx/a pass (%s) %s\n", privdata->subject, ip);
 		else
 			logg("#SPF mx/a pass %s\n", ip);
93928eab
 		privdata->spf_ok = 1;
 	}
a32f3ba8
 }
 
cbdb077f
 #else	/*!HAVE_RESOLV_H */
0217341d
 static table_t *
 mx(const char *host, table_t *t)
cbdb077f
 {
 	logg(_("^MX peers will not be immune from being blacklisted"));
 
 	if(blacklist == NULL)
 		blacklist = tableCreate();
0217341d
 	return NULL;
cbdb077f
 }
 #endif	/* HAVE_RESOLV_H */
f1617494
 
 static sfsistat
 black_hole(const struct privdata *privdata)
 {
 	int must_scan;
 	char **to;
 
 	to = privdata->to;
 	must_scan = (*to) ? 0 : 1;
 
 	for(; *to; to++) {
a474daaa
 		pid_t pid, w;
 		int pv[2], status;
f1617494
 		FILE *sendmail;
a474daaa
 		char buf[BUFSIZ];
f1617494
 
49766e16
 		logg("*Calling \"%s -bv %s\"\n", SENDMAIL_BIN, *to);
f1617494
 
a474daaa
 		if(pipe(pv) < 0) {
 			perror("pipe");
 			logg(_("!Can't create pipe\n"));
 			must_scan = 1;
 			break;
 		}
 		pid = fork();
 		if(pid == 0) {
 			close(1);
 			close(pv[0]);
 			dup2(pv[1], 1);
 			close(pv[1]);
f1617494
 
a474daaa
 			/*
 			 * Avoid calling popen() since *to isn't trusted
 			 */
 			execl(SENDMAIL_BIN, "sendmail", "-bv", *to, NULL);
 			perror(SENDMAIL_BIN);
 			logg("Can't execl %s\n", SENDMAIL_BIN);
 			_exit(errno ? errno : 1);
 		}
 		if(pid == -1) {
 			perror("fork");
 			logg(_("!Can't fork\n"));
 			close(pv[0]);
 			close(pv[1]);
 			must_scan = 1;
 			break;
 		}
 		close(pv[1]);
 		sendmail = fdopen(pv[0], "r");
f1617494
 
a474daaa
 		if(sendmail == NULL) {
 			logg("fdopen failed\n");
 			close(pv[0]);
 			must_scan = 1;
 			break;
 		}
f1617494
 
a474daaa
 		while(fgets(buf, sizeof(buf), sendmail) != NULL) {
 			if(cli_chomp(buf) == 0)
 				continue;
f1617494
 
49766e16
 			logg("*sendmail output: %s\n", buf);
f1617494
 
a474daaa
 			if(strstr(buf, "... deliverable: mailer ")) {
 				const char *p = strstr(buf, ", user ");
 
 				if(strcmp(&p[7], "/dev/null") != 0) {
 					must_scan = 1;
 					break;
f1617494
 				}
 			}
a474daaa
 		}
 		fclose(sendmail);
 
 		status = -1;
 		do
 			w = wait(&status);
 		while((w != pid) && (w != -1));
 
 		if(w == -1)
 			status = -1;
 		else
 			status = WEXITSTATUS(status);
 
 		switch(status) {
 			case EX_NOUSER:
 			case EX_OK:
 				break;
 			default:
 				logg(_("^Can't execute '%s' to expand '%s' (error %d)\n"),
 					SENDMAIL_BIN, *to, WEXITSTATUS(status));
2dbc6ff7
 				must_scan = 1;
f1617494
 		}
 		if(must_scan)
 			break;
 	}
 	if(!must_scan) {
 		/* All recipients map to /dev/null */
83ed1043
 		to = privdata->to;
 		if(*to)
16d1f4d0
 			logg("Discarded, since all recipients (e.g. \"%s\") are /dev/null\n", *to);
83ed1043
 		else
16d1f4d0
 			logg("Discarded, since all recipients are /dev/null\n");
f1617494
 		return SMFIS_DISCARD;
 	}
 	return SMFIS_CONTINUE;
 }
2dda0caf
 
 /* See also libclamav/mbox.c */
 static int
 useful_header(const char *cmd)
 {
 	if(strcasecmp(cmd, "From") == 0)
 		return 1;
 	if(strcasecmp(cmd, "Received") == 0)
 		return 1;
 	if(strcasecmp(cmd, "Content-Type") == 0)
 		return 1;
 	if(strcasecmp(cmd, "Content-Transfer-Encoding") == 0)
 		return 1;
 	if(strcasecmp(cmd, "Content-Disposition") == 0)
 		return 1;
 	if(strcasecmp(cmd, "De") == 0)
 		return 1;
 
 	return 0;
 }
530999cb
 
 static int
df1ec3e8
 increment_connexions(void)
530999cb
 {
 	if(max_children > 0) {
 		int rc = 0;
 
 		pthread_mutex_lock(&n_children_mutex);
 
 		/*
 		 * Wait 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) {
 			struct timespec timeout;
 			struct timeval now;
 			struct timezone tz;
 
 			logg((dont_wait) ?
 					_("hit max-children limit (%u >= %u)\n") :
 					_("hit max-children limit (%u >= %u): waiting for some to exit\n"),
 				n_children, max_children);
 
 			if(dont_wait) {
 				pthread_mutex_unlock(&n_children_mutex);
 				return 0;
 			}
 			/*
 			 * Wait for an amount of time for a child to go
 			 *
 			 * 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, stops sendmail getting
 			 * fidgety.
 			 *
 			 * Patch from Damian Menscher
 			 * <menscher@uiuc.edu> to ensure it wakes up
 			 * when a child goes away
 			 */
 			gettimeofday(&now, &tz);
 			do {
d6381448
 				logg(_("n_children %d: waiting %d seconds for some to exit\n"),
530999cb
 					n_children, child_timeout);
 
 				if(child_timeout == 0) {
 					pthread_cond_wait(&n_children_cond, &n_children_mutex);
 					rc = 0;
 				} else {
 					timeout.tv_sec = now.tv_sec + child_timeout;
 					timeout.tv_nsec = 0;
 
 					rc = pthread_cond_timedwait(&n_children_cond, &n_children_mutex, &timeout);
 				}
 			} while((n_children >= max_children) && (rc != ETIMEDOUT));
 			logg(_("Finished waiting, n_children = %d\n"), n_children);
 		}
 		n_children++;
 
710c1e6c
 		logg("*>n_children = %d\n", n_children);
530999cb
 		pthread_mutex_unlock(&n_children_mutex);
 
 		if(child_timeout && (rc == ETIMEDOUT))
710c1e6c
 			logg(_("Timeout waiting for a child to die\n"));
530999cb
 	}
 
 	return 1;
 }
 
 static void
df1ec3e8
 decrement_connexions(void)
530999cb
 {
 	if(max_children > 0) {
 		pthread_mutex_lock(&n_children_mutex);
710c1e6c
 		logg("*decrement_connexions: n_children = %d\n", n_children);
530999cb
 		/*
 		 * Deliberately errs on the side of broadcasting too many times
 		 */
 		if(n_children > 0)
 			if(--n_children == 0) {
710c1e6c
 				logg("*%s is idle\n", progname);
530999cb
 				if(pthread_cond_broadcast(&watchdog_cond) < 0)
 					perror("pthread_cond_broadcast");
 			}
 #ifdef	CL_DEBUG
710c1e6c
 		logg("*pthread_cond_broadcast\n");
530999cb
 #endif
 		if(pthread_cond_broadcast(&n_children_cond) < 0)
 			perror("pthread_cond_broadcast");
710c1e6c
 		logg("*<n_children = %d\n", n_children);
530999cb
 		pthread_mutex_unlock(&n_children_mutex);
 	}
 }
93928eab
 
 static void
 dump_blacklist(char *key, int value, void *v)
 {
 	logg(_("Won't blacklist %s\n"), key);
 }
9fe789f8
 
 /*
  * Non-blocking connect, based on an idea by Everton da Silva Marques
  *	 <everton.marques@gmail.com>
  * FIXME: There are lots of copies of this code :-(
  */
 static int
 nonblock_connect(int sock, const struct sockaddr_in *sin, const char *hostname)
 {
 	int select_failures;	/* Max. of unexpected select() failures */
 	int attempts;
 	struct timeval timeout;	/* When we should time out */
 	int numfd;		/* Highest fdset fd plus 1 */
 	long flags;
 
 	gettimeofday(&timeout, 0);	/* store when we started to connect */
 
 	if(hostname == NULL)
 		hostname = "clamav-milter";	/* It's only used in debug messages */
 
 #ifdef	F_GETFL
 	flags = fcntl(sock, F_GETFL, 0);
 
 	if(flags == -1L)
49766e16
 		logg("^getfl: %s\n", strerror(errno));
9fe789f8
 	else if(fcntl(sock, F_SETFL, (long)(flags | O_NONBLOCK)) < 0)
49766e16
 		logg("^setfl: %s\n", strerror(errno));
9fe789f8
 #else
 	flags = -1L;
 #endif
 	if(connect(sock, (const struct sockaddr *)sin, sizeof(struct sockaddr_in)) != 0)
 		switch(errno) {
 			case EALREADY:
 			case EINPROGRESS:
49766e16
 				logg("*%s: connect: %s\n", hostname,
9fe789f8
 					strerror(errno));
 				break; /* wait for connection */
 			case EISCONN:
 				return 0; /* connected */
 			default:
49766e16
 				logg("^%s: connect: %s\n", hostname,
 					strerror(errno));
9fe789f8
 #ifdef	F_SETFL
 				if(flags != -1L)
 					if(fcntl(sock, F_SETFL, flags))
49766e16
 						logg("^f_setfl: %s\n", strerror(errno));
9fe789f8
 #endif
 				return -1; /* failed */
 		}
 	else {
 #ifdef	F_SETFL
 		if(flags != -1L)
 			if(fcntl(sock, F_SETFL, flags))
49766e16
 				logg("^f_setfl: %s\n", strerror(errno));
9fe789f8
 #endif
 		return connect_error(sock, hostname);
 	}
 
 	numfd = (int)sock + 1;
 	select_failures = NONBLOCK_SELECT_MAX_FAILURES;
 	attempts = 1;
 	timeout.tv_sec += CONNECT_TIMEOUT;
 
 	for (;;) {
 		int n, t;
 		fd_set fds;
 		struct timeval now, waittime;
 
 		/* Force timeout if we ran out of time */
 		gettimeofday(&now, 0);
 		t = (now.tv_sec == timeout.tv_sec) ?
 			(now.tv_usec > timeout.tv_usec) :
 			(now.tv_sec > timeout.tv_sec);
 
 		if(t) {
49766e16
 			logg("^%s: connect timeout (%d secs)\n",
9fe789f8
 				hostname, CONNECT_TIMEOUT);
 			break;
 		}
 
 		/* Calculate how long to wait */
 		waittime.tv_sec = timeout.tv_sec - now.tv_sec;
 		waittime.tv_usec = timeout.tv_usec - now.tv_usec;
 		if(waittime.tv_usec < 0) {
 			waittime.tv_sec--;
 			waittime.tv_usec += 1000000;
 		}
 
 		/* Init fds with 'sock' as the only fd */
 		FD_ZERO(&fds);
 		FD_SET(sock, &fds);
 
 		n = select(numfd, 0, &fds, 0, &waittime);
 		if(n < 0) {
49766e16
 			logg("^%s: select attempt %d %s\n",
9fe789f8
 				hostname, select_failures, strerror(errno));
 			if(--select_failures >= 0)
 				continue; /* not timed-out, try again */
 			break; /* failed */
 		}
 
49766e16
 		logg("*%s: select = %d\n", hostname, n);
9fe789f8
 
 		if(n) {
 #ifdef	F_SETFL
 			if(flags != -1L)
 				if(fcntl(sock, F_SETFL, flags))
49766e16
 					logg("^f_setfl: %s\n", strerror(errno));
9fe789f8
 #endif
 			return connect_error(sock, hostname);
 		}
 
 		/* timeout */
 		if(attempts++ == NONBLOCK_MAX_ATTEMPTS) {
49766e16
 			logg("^timeout connecting to %s\n", hostname);
9fe789f8
 			break;
 		}
 	}
 
 #ifdef	F_SETFL
 	if(flags != -1L)
 		if(fcntl(sock, F_SETFL, flags))
49766e16
 			logg("^f_setfl: %s\n", strerror(errno));
9fe789f8
 #endif
 	return -1; /* failed */
 }
 
 static int
 connect_error(int sock, const char *hostname)
 {
 #ifdef	SO_ERROR
 	int optval;
 	socklen_t optlen = sizeof(optval);
 
 	getsockopt(sock, SOL_SOCKET, SO_ERROR, &optval, &optlen);
 
 	if(optval) {
49766e16
 		logg("^%s: %s\n", hostname, strerror(optval));
9fe789f8
 		return -1;
 	}
 #endif
 
 	return 0;
 }