b151ef55 |
/*
* clamav-milter.c
* .../clamav-milter/clamav-milter.c
*
* Copyright (C) 2003 Nigel Horne <njh@bandsman.co.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Install into /usr/local/sbin/clamav-milter, mode 744
*
* See http://www.nmt.edu/~wcolburn/sendmail-8.12.5/libmilter/docs/sample.html
*
* Installations for RedHat Linux and it's derivatives such as YellowDog: |
c6259ac5 |
* 1) Ensure that you have the sendmail-devel RPM installed
* 2) Add to /etc/mail/sendmail.mc: |
b151ef55 |
* INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav.sock, F=, T=S:4m;R:4m')dnl
* define(`confINPUT_MAIL_FILTERS', `clamav') |
c6259ac5 |
* 3) Check entry in /usr/local/etc/clamav.conf of the form: |
b151ef55 |
* LocalSocket /var/run/clamd.sock
* StreamSaveToDisk |
c6259ac5 |
* 4) If you already have a filter (such as spamassassin-milter from |
b151ef55 |
* http://savannah.nongnu.org/projects/spamass-milt) add it thus:
* INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav.sock, F=, T=S:4m;R:4m')dnl
* INPUT_MAIL_FILTER(`spamassassin', `S=local:/var/run/spamass.sock, F=, T=C:15m;S:4m;R:4m;E:10m')
* define(`confINPUT_MAIL_FILTERS', `spamassassin,clamav')dnl |
c6259ac5 |
* 5) You may find INPUT_MAIL_FILTERS is not needed on your machine, however it
* is recommended by the Sendmail documentation and I suggest going along |
b151ef55 |
* with that. |
c6259ac5 |
* 6) I suggest putting SpamAssassin first since you're more likely to get spam |
b151ef55 |
* than a virus/worm sent to you. |
c6259ac5 |
* 7) Add to /etc/sysconfig/clamav-milter |
b151ef55 |
* CLAMAV_FLAGS="--max-children=2 local:/var/run/clamav.sock"
* or if clamd is on a different machine
* CLAMAV_FLAGS="--max-children=2 --server=192.168.1.9 local:/var/run/clamav.sock" |
c6259ac5 |
* 8) You should have received a script to put into /etc/init.d with this
* software. |
bb9979e6 |
* 9) run 'chown clamav /usr/local/sbin/clamav-milter; chmod 4700 /usr/local/sbin/clamav-milter |
b151ef55 |
*
* Tested OK on Linux/x86 (RH8.0) with gcc3.2.
* cc -O3 -pedantic -Wuninitialized -Wall -pipe -mcpu=pentium -march=pentium -fomit-frame-pointer -ffast-math -finline-functions -funroll-loops clamav-milter.c -pthread -lmilter ../libclamav/.libs/libclamav.a ../clamd/cfgfile.o ../clamd/others.o
* Compiles OK on Linux/x86 with tcc 0.9.16, but fails to link errors with 'atexit'
* tcc -g -b -lmilter -lpthread clamav-milter.c...
* Fails to compile on Linux/x86 with icc6.0 (complains about stdio.h...)
* icc -O3 -tpp7 -xiMKW -ipo -parallel -i_dynamic -w2 clamav-milter.c...
* Fails to build on Linux/x86 with icc7.1 with -ipo (fails on libclamav.a - keeps saying run ranlib). Otherwise it builds and runs OK.
* icc -O2 -tpp7 -xiMKW -parallel -i_dynamic -w2 -march=pentium4 -mcpu=pentium4 clamav-milter.c...
* Tested with Electric Fence 2.2.2
*
* Compiles OK on Linux/ppc (YDL2.3) with gcc2.95.4. Needs -lsmutil to link.
* cc -O3 -pedantic -Wuninitialized -Wall -pipe -fomit-frame-pointer -ffast-math -finline-functions -funroll-loop -pthread -lmilter ../libclamav/.libs/libclamav.a ../clamd/cfgfile.o ../clamd/others.o -lsmutil
* I haven't tested it further on this platform yet. |
c6259ac5 |
* YDL3.0 should compile out of the box
cc -O3 -pedantic -Wuninitialized -Wall -pipe -fomit-frame-pointer -ffast-math -finline-functions -funroll-loop -pthread -lmilter ../libclamav/.libs/libclamav.a ../clamd/cfgfile.o ../clamd/others.o -lsmutil |
b151ef55 |
*
* Sendmail on MacOS/X (10.1) is provided without a development package so this
* can't be run "out of the box"
* |
c6259ac5 |
* Solaris 8 doesn't have milter support so clamav-milter won't work unless
* you rebuild sendmail from source. |
b151ef55 |
*
* FreeBSD4.7 use /usr/local/bin/gcc30. GCC3.0 is an optional extra on
* FreeBSD. It comes with getopt.h which is handy. To link you need
* -lgnugetopt
* gcc30 -O3 -DCONFDIR=\"/usr/local/etc\" -I. -I.. -I../clamd -I../libclamav -pedantic -Wuninitialized -Wall -pipe -mcpu=pentium -march=pentium -fomit-frame-pointer -ffast-math -finline-functions -funroll-loops clamav-milter.c -pthread -lmilter ../libclamav/.libs/libclamav.a ../clamd/cfgfile.o ../clamd/others.o -lgnugetopt
* |
65a8d561 |
* FreeBSD4.8: compiles out of the box with either gcc2.95 or gcc3 |
cdbe607d |
*
* OpenBSD3.4: the supplied sendmail does not come with Milter support.
* Do this *before* running configure (thanks for Per-Olov Sjöhol
* <peo_s@incedo.org>for these instructions).
*
* echo WANT_LIBMILTER=1 > /etc/mk.conf
* cd /usr/src/gnu/usr.sbin/sendmail
* make depend
* make
* make install
* kill -HUP `sed q /var/run/sendmail.pid`
*
* Then do this to make the milter headers available to clamav...
* (the libmilter.a file is already in the right place after the sendmail
* recompiles above)
*
* cd /usr/include
* ln -s ../src/gnu/usr.sbin/sendmail/include/libmilter libmilter |
c6259ac5 |
* |
e2bb746e |
* Solaris 9 and FreeBSD5 have milter support in the supplied sendmail, but
* doesn't include libmilter so you can't develop milter applications on it.
* Go to sendmail.org, download the lastest sendmail, cd to libmilter and
* "make install" there.
*
* Needs -lresolv on Solaris
* |
b151ef55 |
* Changes
* 0.2: 4/3/03 clamfi_abort() now always calls pthread_mutex_unlock
* 5/3/03 Only send a bounce if -b is set
* Version now uses -v not -V
* --config-file couldn't be set by -c
* 0.3 7/3/03 Enhanced the Solaris compile time comment
* No need to save the return result of LogSyslog
* Use LogVerbose
* 0.4 9/3/03 Initialise dataSocket/cmdSocket correctly
* 10/3/03 Say why we don't connect() to clamd
* Enhanced '-l' usage message
* 0.5 18/3/03 Ported to FreeBSD 4.7
* Source no longer in support, so remove one .. from
* the build instructions
* Corrected the use of strerror_r
* 0.51 20/3/03 Mention StreamSaveToDisk in the installation
* Added -s option which allows clamd to run on a
* different machine from the milter
* 0.52 20/3/03 -b flag now only stops the bounce, sends warning
* to recipient and postmaster
* 0.53 24/3/03 %d->%u in syslog call
* 27/3/03 tcpSocket is now of type in_port_t
* 27/3/03 Use PING/PONG
* 0.54 23/5/03 Allow a range of IP addresses as outgoing ones
* that need not be checked
* 0.55 24/5/03 Use inet_ntop() instead of inet_ntoa()
* Thanks to Krzysztof Olędzki <ole@ans.pl>
* 0.60 11/7/03 Added suggestions by Nigel Kukard <nkukard@lbsd.net>
* Should stop a couple of remote chances of crashes |
c6259ac5 |
* 0.60a 22/7/03 Tidied up message when sender is unknown
* 0.60b 17/8/03 Optionally set postmaster address. Usually one uses
* /etc/aliases, but not everyone want's to...
* 0.60c 22/8/03 Another go at Solaris support
* 0.60d 26/8/03 Removed superflous buffer and unneeded strerror call
* ETIMEDOUT isn't an error, but should give a warning |
1c3f1ce1 |
* 0.60e 09/9/03 Added -P and -q flags by "Nicholas M. Kirsch"
* <nick@kirsch.org> |
1f025849 |
* 0.60f 24/9/03 Changed fprintf to fputs where possible
* Redirect stdin from /dev/null, stdout&stderr to
* /dev/console |
3613bd91 |
* 0.60g 26/9/03 Handle sendmail calling abort after calling cleanup
* (Should never happen - but it does)
* Added -noxheader patch from dirk.meyer@dinoex.sub.org |
b5d15e64 |
* 0.60h 28/9/03 Support MaxThreads option in config file,
* overriden by --max-children.
* Patch from "Richard G. Roberto" <rgr@dedlegend.com> |
ecb8e6b4 |
* 0.60i 30/9/03 clamfi_envfrom() now correctly returns SMFIS_TEMPFAIL,
* in a few circumstances it used to return EX_TEMPFAIL
* Patch from Matt Sullivan <matt@sullivan.gen.nz> |
b14e9e77 |
* 0.60j 1/10/03 strerror_r doesn't work on Linux, attempting workaround
* Added support for hard-coded list of email addresses
* who's e-mail is not scanned |
3a0b4e5b |
* 0.60k 5/10/03 Only remove old UNIX domain socket if FixStaleSocket
* is set |
dd0d5a8c |
* 0.60l 11/10/03 port is now unsigned
* Removed remote possibility of crash if the target
* e-mail address is very long
* No longer calls clamdscan to get the version |
f0d6f5a1 |
* 0.60m 12/10/03 Now does sanity check if using localSocket
* Gets version info from clamd
* Only reset fd's 0/1/2 if !ForeGround |
390a7661 |
* 0.60n 22/10/03 Call pthread_cont_broadcast more often |
b0a42ec6 |
* 0.60o 31/10/03 Optionally accept all mails if scanning procedure
* fails (Joe Talbott <josepht@cstone.net>) |
3a805bfd |
* 0.60p 5/11/03 Only call mutex_unlock when max_children is set
* Tidy up the call to pthread_cond_timedwait |
a6f07599 |
* 0.60q 11/11/03 Fixed handling of % characters in e-mail addresses
* pointed out by dotslash@snosoft.com |
c2ac5345 |
* 0.65 15/11/03 Upissue of clamav |
0b6bce84 |
* 0.65a 19/11/03 Close cmdSocket earlier
* Added setpgrp() |
bb9979e6 |
* 0.65b 22/11/03 Ensure milter is not run as root if requested
* Added quarantine support |
434012b8 |
* 0.65c 24/11/03 Support AllowSupplementaryGroups
* Fix warning about root usage |
2a1ff3e4 |
* 0.65d 25/11/03 Handle empty hostname or hostaddr
* Fix based on a submission by Michael Dankov <misha@btrc.ru> |
4ba69cb6 |
* 0.65e 29/11/03 Fix problem of possible confused pointers if large
* number of recipients given.
* Fix by Michael Dankov <misha@btrc.ru>. |
3454ad43 |
* 0.65f 29/11/03 Added --quarantine-dir
* Thanks to Michael Dankov <misha@btrc.ru>. |
65a8d561 |
* 0.65g 2/12/03 Use setsid if setpgrp is not present.
* Thanks to Eugene Crosser <crosser@rol.ru> |
709d1342 |
* 0.65h 4/12/03 Added call to umask to ensure that the local socket
* is not publically writeable. If it is sendmail
* will (correctly!) refuse to start this program
* Thanks for Nicklaus Wicker <n.wicker@cnk-networks.de>
* Don't sent From as the first line since that means
* clamd will think it is an mbox and not handle
* unescaped From at the start of lines properly
* Thanks to Michael Dankov <misha@btrc.ru> |
b696653a |
* 0.65i 9/12/03 Use the location of sendmail discovered by configure |
d2efb3fa |
* 0.65j 10/12/03 Timeout on waiting for data from clamd |
85c1dbfd |
* 0.65k 12/12/03 A couple of calls to clamfi_cleanup were missing
* before return cl_error |
5dc96b9f |
* 0.66 13/12/03 Upissue |
e6bffccb |
* 0.66a 22/12/03 Added --sign |
3166c010 |
* 0.66b 27/12/03 --sign moved to privdata |
e2bb746e |
* 0.66c 31/12/03 Included the sendmail queue ID in the log, from an
* idea by Andy Fiddaman <af@jeamland.org> |
cdbe607d |
* 0.66d 10/1/04 Added OpenBSD instructions
* Added --signature-file option |
462b5251 |
* 0.66e 12/1/04 FixStaleSocket: no longer complain if asked to remove
* an old socket when there was none to remove |
ecb8e6b4 |
* |
b5d15e64 |
* Change History:
* $Log: clamav-milter.c,v $ |
462b5251 |
* Revision 1.36 2004/01/12 15:30:53 nigelhorne
* FixStaleSocket no longer complains on ENOENT
* |
cdbe607d |
* Revision 1.35 2004/01/10 16:22:14 nigelhorne
* Added OpenBSD instructions and --signature-file
* |
e2bb746e |
* Revision 1.34 2003/12/31 14:46:35 nigelhorne
* Include the sendmail queue ID in the log
* |
3166c010 |
* Revision 1.33 2003/12/27 17:28:56 nigelhorne
* Moved --sign data to private area
* |
e6bffccb |
* Revision 1.32 2003/12/22 14:05:31 nigelhorne
* Added --sign option
* |
5dc96b9f |
* Revision 1.31 2003/12/13 16:43:21 nigelhorne
* Upissue
* |
85c1dbfd |
* Revision 1.30 2003/12/12 13:42:47 nigelhorne
* alls to clamfi_cleanup were missing
* |
d2efb3fa |
* Revision 1.29 2003/12/10 12:00:39 nigelhorne
* Timeout on waiting for data from clamd
* |
b696653a |
* Revision 1.28 2003/12/09 09:22:14 nigelhorne
* Use the location of sendmail discovered by configure
* |
709d1342 |
* Revision 1.27 2003/12/05 19:14:07 nigelhorne
* Set umask; handle unescaped From in mailboxes
* |
65a8d561 |
* Revision 1.26 2003/12/02 06:37:26 nigelhorne
* Use setsid if setpgrp not present
* |
3454ad43 |
* Revision 1.25 2003/11/30 06:12:06 nigelhorne
* Added --quarantine-dir option
* |
4ba69cb6 |
* Revision 1.24 2003/11/29 11:51:19 nigelhorne
* Fix problem of possible confused pointers if large number of recipients given
* |
2a1ff3e4 |
* Revision 1.23 2003/11/25 05:56:43 nigelhorne
* Handle empty hostname or hostaddr
* |
434012b8 |
* Revision 1.22 2003/11/24 04:48:44 nigelhorne
* Support AllowSupplementaryGroups
* |
bb9979e6 |
* Revision 1.21 2003/11/22 11:47:45 nigelhorne
* Drop root priviliges and support quanrantine
* |
0b6bce84 |
* Revision 1.20 2003/11/19 16:32:22 nigelhorne
* Close cmdSocket earlier
* |
c2ac5345 |
* Revision 1.19 2003/11/17 04:48:30 nigelhorne
* Up issue to version 0.65
* |
a6f07599 |
* Revision 1.18 2003/11/11 08:19:20 nigelhorne
* Handle % characters in e-mail addresses
* |
3a805bfd |
* Revision 1.17 2003/11/05 15:41:11 nigelhorne
* Tidyup pthread_cond_timewait call
* |
b0a42ec6 |
* Revision 1.16 2003/10/31 13:33:40 nigelhorne
* Added dont scan on error flag
* |
390a7661 |
* Revision 1.15 2003/10/22 19:44:01 nigelhorne
* more calls to pthread_cond_broadcast
* |
f0d6f5a1 |
* Revision 1.14 2003/10/12 08:37:21 nigelhorne
* Uses VERSION command to get version information
* |
dd0d5a8c |
* Revision 1.13 2003/10/11 15:42:15 nigelhorne
* Don't call clamdscan
* |
3a0b4e5b |
* Revision 1.12 2003/10/05 17:30:04 nigelhorne
* Only fix old socket when FixStaleSocket is set
* |
31268a5c |
* Revision 1.11 2003/10/05 13:57:47 nigelhorne
* Fixed handling of MaxThreads
* |
b14e9e77 |
* Revision 1.10 2003/10/03 11:54:53 nigelhorne
* Added white list of recipients
* |
ecb8e6b4 |
* Revision 1.9 2003/09/30 11:53:55 nigelhorne
* clamfi_envfrom was returning EX_TEMPFAIL in some places rather than SMFIS_TEMPFAIL
* |
c15d1d2c |
* Revision 1.8 2003/09/29 06:20:17 nigelhorne
* max_children now overrides MaxThreads
* |
80a72b0d |
* Revision 1.7 2003/09/29 06:07:49 nigelhorne
* Ensure remoteIP is set before usage
* |
b5d15e64 |
* Revision 1.6 2003/09/28 16:37:23 nigelhorne
* Added -f flag use MaxThreads if --max-children not set |
b151ef55 |
*/ |
462b5251 |
static char const rcsid[] = "$Id: clamav-milter.c,v 1.36 2004/01/12 15:30:53 nigelhorne Exp $"; |
b151ef55 |
|
cdbe607d |
#define CM_VERSION "0.66d" |
b151ef55 |
/*#define CONFDIR "/usr/local/etc"*/
#include "defaults.h"
#include "cfgfile.h"
#include "../target.h"
#ifndef CL_DEBUG
#define NDEBUG
#endif
#include <stdio.h>
#include <sysexits.h> |
709d1342 |
#ifndef HAVE_MALLOC_H |
b151ef55 |
#include <malloc.h>
#endif |
709d1342 |
#include <sys/types.h>
#include <sys/stat.h> |
b151ef55 |
#include <syslog.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <assert.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdarg.h>
#include <errno.h>
#include <libmilter/mfapi.h>
#include <pthread.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <signal.h> |
1f025849 |
#include <regex.h>
#include <fcntl.h> |
bb9979e6 |
#include <pwd.h> |
434012b8 |
#include <grp.h> |
b151ef55 |
#define _GNU_SOURCE |
c6259ac5 |
#include "getopt.h" |
b151ef55 |
|
b696653a |
#ifndef SENDMAIL_BIN
#define SENDMAIL_BIN "/usr/lib/sendmail"
#endif
|
b151ef55 |
/*
* TODO: optional: xmessage on console when virus stopped (SNMP would be real nice!) |
434012b8 |
* Having said that, with LogSysLog you can (on Linux) configure the system
* to get messages on the system console, see syslog.conf(5), also you
* can use wall(1) in the VirusEvent entry in clamav.conf |
b151ef55 |
* TODO: allow -s server to use a name as well as an IP address
* TODO: build with libclamav.so rather than libclamav.a
* TODO: bounce message should optionally be read from a file
* TODO: Support ThreadTimeout, LogTime and Logfile from the conf
* file
* TODO: Allow more than one clamdscan server to be given
*/
/*
* Each thread has one of these
*/
struct privdata {
char *from; /* Who sent the message */
char **to; /* Who is the message going to */
int numTo; /* Number of people the message is going to */
int cmdSocket; /*
* Socket to send/get commands e.g. PORT for
* dataSocket
*/
int dataSocket; /* Socket to send data to clamd */ |
3454ad43 |
char *filename; /* Where to store the message in quarantine */ |
3166c010 |
u_char *body; /* body of the message if Sflag is set */
size_t bodyLen; /* number of bytes in body */ |
b151ef55 |
};
|
cdbe607d |
static int pingServer(void); |
b151ef55 |
static sfsistat clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr);
static sfsistat clamfi_envfrom(SMFICTX *ctx, char **argv);
static sfsistat clamfi_envrcpt(SMFICTX *ctx, char **argv);
static sfsistat clamfi_header(SMFICTX *ctx, char *headerf, char *headerv);
static sfsistat clamfi_eoh(SMFICTX *ctx);
static sfsistat clamfi_body(SMFICTX *ctx, u_char *bodyp, size_t len);
static sfsistat clamfi_eom(SMFICTX *ctx);
static sfsistat clamfi_abort(SMFICTX *ctx);
static sfsistat clamfi_close(SMFICTX *ctx);
static void clamfi_cleanup(SMFICTX *ctx);
static int clamfi_send(const struct privdata *privdata, size_t len, const char *format, ...);
static char *strrcpy(char *dest, const char *source); |
cdbe607d |
static int clamd_recv(int sock, char *buf, size_t len);
static off_t updateSigFile(void); |
b151ef55 |
|
f0d6f5a1 |
static char clamav_version[128]; |
b5d15e64 |
static int fflag = 0; /* force a scan, whatever */ |
b151ef55 |
static int oflag = 0; /* scan messages from our machine? */
static int lflag = 0; /* scan messages from our site? */
static int bflag = 0; /*
* send a failure (bounce) message to the
* sender. This probably isn't a good idea
* since most reply addresses will be fake
*/ |
1c3f1ce1 |
static int pflag = 0; /*
* Send a warning to the postmaster only,
* this means user's won't be told when someone
* sent them a virus
*/
static int qflag = 0; /*
* Send no warnings when a virus is found,
* this means that the only log of viruses
* found is the syslog, so it's best to
* enable LogSyslog in clamav.conf
*/ |
e6bffccb |
static int Sflag = 0; /*
* Add a signature to each message that
* has been scanned
*/ |
cdbe607d |
static const char *sigFilename; /*
* File where the scanned message signature
* can be found
*/ |
3454ad43 |
static char *quarantine; /* |
bb9979e6 |
* If a virus is found in an email redirect
* it to this account
*/ |
3454ad43 |
static char *quarantine_dir; /*
* Path to store messages before scanning.
* Infected ones will be left there.
*/ |
3613bd91 |
static int nflag = 0; /*
* Don't add X-Virus-Scanned to header. Patch
* from Dirk Meyer <dirk.meyer@dinoex.sub.org>
*/ |
b0a42ec6 |
static int cl_error = SMFIS_TEMPFAIL; /*
* If an error occurs, return
* this status. Allows messages
* to be passed through
* unscanned in the event of
* an error. Patch from
* Joe Talbott <josepht@cstone.net>
*/ |
d2efb3fa |
static int threadtimeout = CL_DEFAULT_SCANTIMEOUT; /*
* number of seconds to wait for clamd to
* respond
*/ |
cdbe607d |
static char *signature = "-- \nScanned by ClamAv - http://www.clamav.net\n";
static time_t signatureStamp; |
b151ef55 |
#ifdef CL_DEBUG
static int debug_level = 0;
#endif
static pthread_mutex_t n_children_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t n_children_cond = PTHREAD_COND_INITIALIZER;
static unsigned int n_children = 0;
static unsigned int max_children = 0;
static int use_syslog = 0;
static int logVerbose = 0;
static struct cfgstruct *copt;
static const char *localSocket;
static in_port_t tcpSocket;
static const char *serverIP = "127.0.0.1"; |
c6259ac5 |
static const char *postmaster = "postmaster"; |
b151ef55 |
|
434012b8 |
/*
* Whitelist of e-mail addresses that we do NOT scan
* TODO: read in from a file
*/ |
b14e9e77 |
static const char *ignoredEmailAddresses[] = {
/*"Mailer-Daemon@bandsman.co.uk",
"postmaster@bandsman.co.uk",*/
NULL
};
|
b151ef55 |
static void
help(void)
{
printf("\n\tclamav-milter version %s\n", CM_VERSION);
puts("\tCopyright (C) 2003 Nigel Horne <njh@despammed.com>\n");
puts("\t--bounce\t\t-b\tSend a failure message to the sender.");
puts("\t--config-file=FILE\t-c FILE\tRead configuration from FILE."); |
bb9979e6 |
puts("\t--dont-scan-on-error\t-d\tPass e-mails through unscanned if a system error occurs.");
puts("\t--force-scan\t\t-f\tForce scan all messages (overrides (-o and -l)."); |
b151ef55 |
puts("\t--help\t\t\t-h\tThis message.");
puts("\t--local\t\t\t-l\tScan messages sent from machines on our LAN.");
puts("\t--outgoing\t\t-o\tScan outgoing messages from this machine."); |
c2ac5345 |
puts("\t--noxheader\t\t-n\tSuppress X-Virus-Scanned header."); |
434012b8 |
puts("\t--postmaster\t\t-p EMAIL\tPostmaster address [default=postmaster]."); |
bb9979e6 |
puts("\t--postmaster-only\t-P\tSend warnings only to the postmaster."); |
1c3f1ce1 |
puts("\t--quiet\t\t\t-q\tDon't send e-mail notifications of interceptions."); |
434012b8 |
puts("\t--quarantine=USER\t-Q EMAIL\tQuanrantine e-mail account."); |
3454ad43 |
puts("\t--quarantine-dir=DIR\t-U DIR\tDirectory to store infected emails."); |
bb9979e6 |
puts("\t--server=ADDRESS\t-s ADDR\tIP address of server running clamd (when using TCPsocket)."); |
cdbe607d |
puts("\t--sign\t\t\t-S\tAdd a hard-coded signature to each scanned message.");
puts("\t--signature-file\t-F\tLocation of signature file."); |
b151ef55 |
puts("\t--version\t\t-V\tPrint the version number of this software.");
#ifdef CL_DEBUG
puts("\t--debug-level=n\t\t-x n\tSets the debug level to 'n'.");
#endif
}
int
main(int argc, char **argv)
{
extern char *optarg; |
dd0d5a8c |
char *port = NULL; |
b151ef55 |
const char *cfgfile = CL_DEFAULT_CFG;
struct cfgstruct *cpt; |
3454ad43 |
struct passwd *user; |
b151ef55 |
struct smfiDesc smfilter = {
"ClamAv", /* filter name */
SMFI_VERSION, /* version code -- leave untouched */
SMFIF_ADDHDRS, /* flags - we add headers */
clamfi_connect, /* connection callback */
NULL, /* HELO filter callback */
clamfi_envfrom, /* envelope sender filter callback */
clamfi_envrcpt, /* envelope recipient filter callback */
clamfi_header, /* header filter callback */
clamfi_eoh, /* end of header callback */
clamfi_body, /* body filter callback */
clamfi_eom, /* end of message callback */
clamfi_abort, /* message aborted callback */
clamfi_close, /* connection cleanup callback */
};
|
f0d6f5a1 |
/*
* Temporarily enter guessed value into clamav_version, will
* be overwritten later by the value returned by clamd
*/ |
dd0d5a8c |
snprintf(clamav_version, sizeof(clamav_version),
"ClamAV version %s, clamav-milter version %s",
VERSION, CM_VERSION);
|
b151ef55 |
for(;;) {
int opt_index = 0;
#ifdef CL_DEBUG |
cdbe607d |
const char *args = "bc:fF:lm:nop:PqQ:dhs:SU:Vx:"; |
b151ef55 |
#else |
cdbe607d |
const char *args = "bc:fF:lm:nop:PqQ:dhs:SU:V"; |
b151ef55 |
#endif |
bb9979e6 |
|
b151ef55 |
static struct option long_options[] = {
{
"bounce", 0, NULL, 'b'
},
{
"config-file", 1, NULL, 'c'
},
{ |
b0a42ec6 |
"dont-scan-on-error", 0, NULL, 'd'
},
{ |
3454ad43 |
"force-scan", 0, NULL, 'f' |
b5d15e64 |
},
{ |
b151ef55 |
"help", 0, NULL, 'h'
},
{
"local", 0, NULL, 'l'
},
{ |
3613bd91 |
"noxheader", 0, NULL, 'n'
},
{ |
b151ef55 |
"outgoing", 0, NULL, 'o'
},
{ |
434012b8 |
"postmaster", 1, NULL, 'p' |
c6259ac5 |
},
{ |
1c3f1ce1 |
"postmaster-only", 0, NULL, 'P',
},
{
"quiet", 0, NULL, 'q'
},
{ |
bb9979e6 |
"quarantine", 1, NULL, 'Q',
},
{ |
3454ad43 |
"quarantine-dir", 1, NULL, 'U',
},
{ |
b151ef55 |
"max-children", 1, NULL, 'm'
},
{
"server", 1, NULL, 's'
},
{ |
cdbe607d |
"sign", 0, NULL, 'S'
},
{
"signature-file", 1, NULL, 'F' |
e6bffccb |
},
{ |
b151ef55 |
"version", 0, NULL, 'V'
},
#ifdef CL_DEBUG
{
"debug-level", 1, NULL, 'x'
},
#endif
{
NULL, 0, NULL, '\0'
}
};
int ret = getopt_long(argc, argv, args, long_options, &opt_index);
if(ret == -1)
break;
else if(ret == 0)
ret = long_options[opt_index].val;
switch(ret) {
case 'b': /* bounce worms/viruses */
bflag++;
break;
case 'c': /* where is clamav.conf? */
cfgfile = optarg;
break; |
b0a42ec6 |
case 'd': /* don't scan on error */
cl_error = SMFIS_ACCEPT;
break; |
b5d15e64 |
case 'f': /* force the scan */
fflag++;
break; |
b151ef55 |
case 'h':
help();
return EX_OK;
case 'l': /* scan mail from the lan */
lflag++;
break; |
c6259ac5 |
case 'm': /* maximum number of children */
max_children = atoi(optarg);
break; |
3613bd91 |
case 'n': /* don't add X-Virus-Scanned */
nflag++; |
bb9979e6 |
smfilter.xxfi_flags &= ~SMFIF_ADDHDRS; |
3613bd91 |
break; |
b151ef55 |
case 'o': /* scan outgoing mail */
oflag++;
break; |
c6259ac5 |
case 'p': /* postmaster e-mail address */
postmaster = optarg; |
b151ef55 |
break; |
1c3f1ce1 |
case 'P': /* postmaster only */
pflag++;
break;
case 'q': /* send NO notification email */
qflag++;
break; |
bb9979e6 |
case 'Q': /* quarantine e-mail address */
quarantine = optarg;
smfilter.xxfi_flags |= SMFIF_CHGHDRS|SMFIF_ADDRCPT|SMFIF_DELRCPT;
break; |
b151ef55 |
case 's': /* server running clamd */
serverIP = optarg;
break; |
cdbe607d |
case 'F': /* signature file */
sigFilename = optarg;
signature = NULL;
/* fall through */ |
e6bffccb |
case 'S': /* sign */
smfilter.xxfi_flags |= SMFIF_CHGBODY;
Sflag++;
break; |
3454ad43 |
case 'U': /* quarantine path */
quarantine_dir = optarg;
break; |
b151ef55 |
case 'V': |
dd0d5a8c |
puts(clamav_version); |
b151ef55 |
return EX_OK;
#ifdef CL_DEBUG
case 'x':
debug_level = atoi(optarg);
break;
#endif
default:
#ifdef CL_DEBUG |
cdbe607d |
fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-S] [-x#] [-U PATH] socket-addr\n", argv[0]); |
b151ef55 |
#else |
cdbe607d |
fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-S] [-U PATH] socket-addr\n", argv[0]); |
b151ef55 |
#endif
return EX_USAGE;
}
}
if (optind == argc) {
fprintf(stderr, "%s: No socket-addr given\n", argv[0]);
return EX_USAGE;
}
port = argv[optind];
/*
* Sanity checks on the clamav configuration file
*/
if((copt = parsecfg(cfgfile)) == NULL) {
fprintf(stderr, "%s: Can't parse the config file %s\n",
argv[0], cfgfile);
return EX_CONFIG;
}
|
434012b8 |
/*
* Drop privileges
*/
if(getuid() == 0) {
if((cpt = cfgopt(copt, "User")) != NULL) {
if((user = getpwnam(cpt->strarg)) == NULL) {
fprintf(stderr, "%s: Can't get information about user %s\n", argv[0], cpt->strarg);
return EX_CONFIG;
} |
bb9979e6 |
|
434012b8 |
if(cfgopt(copt, "AllowSupplementaryGroups"))
initgroups(cpt->strarg, user->pw_gid);
else
setgroups(1, &user->pw_gid);
setgid(user->pw_gid);
setuid(user->pw_uid);
} else
fprintf(stderr, "%s: running as root is not recommended\n", argv[0]);
} |
3454ad43 |
if(quarantine_dir && (access(quarantine_dir, W_OK) < 0)) {
perror(quarantine_dir);
return EX_CONFIG;
} |
bb9979e6 |
|
cdbe607d |
if(sigFilename && !updateSigFile())
return EX_USAGE;
|
b151ef55 |
if(!cfgopt(copt, "StreamSaveToDisk")) {
fprintf(stderr, "%s: StreamSavetoDisk not enabled in %s\n",
argv[0], cfgfile);
return EX_CONFIG;
}
if(!cfgopt(copt, "ScanMail")) {
fprintf(stderr, "%s: ScanMail not enabled in %s\n",
argv[0], cfgfile);
return EX_CONFIG;
}
/* |
b5d15e64 |
* patch from "Richard G. Roberto" <rgr@dedlegend.com>
* If the --max-children flag isn't set, see if MaxThreads
* is set in the config file
*/ |
c15d1d2c |
if((max_children == 0) && ((cpt = cfgopt(copt, "MaxThreads")) != NULL)) |
31268a5c |
max_children = cpt->numarg; |
b5d15e64 |
|
d2efb3fa |
if((cpt = cfgopt(copt, "ThreadTimeout")) != NULL) {
threadtimeout = cpt->numarg;
if(threadtimeout < 0) {
fprintf(stderr, "%s: ThreadTimeout must not be negative in %s\n",
argv[0], cfgfile);
}
}
|
b5d15e64 |
/* |
b151ef55 |
* Get the outgoing socket details - the way to talk to clamd
*/ |
f0d6f5a1 |
if((cpt = cfgopt(copt, "LocalSocket")) != NULL) {
if(cfgopt(copt, "TCPSocket") != NULL) {
fprintf(stderr, "%s: You can select one server type only (local/TCP) in %s\n",
argv[0], cfgfile);
return EX_CONFIG;
} |
b151ef55 |
/*
* TODO: check --server hasn't been set
*/
localSocket = cpt->strarg; |
f0d6f5a1 |
if(!pingServer()) {
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;
} |
709d1342 |
umask(022); |
f0d6f5a1 |
} else if((cpt = cfgopt(copt, "TCPSocket")) != NULL) { |
b151ef55 |
/*
* TCPSocket is in fact a port number not a full socket
*/ |
3454ad43 |
if(quarantine_dir) {
fprintf(stderr, "%s: --quarantine-dir not supported for remote scanning - use --quarantine\n", argv[0]);
return EX_CONFIG;
}
tcpSocket = cpt->numarg;
|
b151ef55 |
if(!pingServer()) {
fprintf(stderr, "Can't talk to clamd server at %s on port %d\n",
serverIP, tcpSocket);
fprintf(stderr, "Check your entry for TCPSocket in %s\n",
cfgfile);
return EX_CONFIG;
}
} else {
fprintf(stderr, "%s: You must select server type (local/TCP) in %s\n",
argv[0], cfgfile);
return EX_CONFIG;
}
|
f0d6f5a1 |
if(!cfgopt(copt, "Foreground")) { |
cdbe607d |
#ifdef CL_DEBUG
printf("When debugging it is recommended that you use Foreground mode in %s\n", cfgfile);
puts("So that you can see all of the messages");
#endif
|
b151ef55 |
switch(fork()) {
case -1:
perror("fork");
return EX_TEMPFAIL;
case 0: /* child */
break;
default: /* parent */
return EX_OK;
} |
f0d6f5a1 |
close(0);
close(1);
close(2);
open("/dev/null", O_RDONLY);
if(open("/dev/console", O_WRONLY) == 1)
dup(1); |
65a8d561 |
#ifdef HAVE_SETPGRP
#ifdef SETPGRP_VOID |
0b6bce84 |
setpgrp(); |
65a8d561 |
#else
setpgrp(0,0);
#endif
#else
#ifdef HAVE_SETSID
setsid();
#endif
#endif |
f0d6f5a1 |
} |
b151ef55 |
if(smfi_setconn(port) == MI_FAILURE) {
fprintf(stderr, "%s: smfi_setconn failed\n",
argv[0]);
return EX_SOFTWARE;
}
if(cfgopt(copt, "LogSyslog")) {
openlog("clamav-milter", LOG_CONS|LOG_PID, LOG_MAIL);
syslog(LOG_INFO, clamav_version);
#ifdef CL_DEBUG
if(debug_level > 0)
syslog(LOG_DEBUG, "Debugging is on");
#endif
use_syslog = 1;
if(cfgopt(copt, "LogVerbose"))
logVerbose = 1; |
1c3f1ce1 |
} else {
if(qflag)
fprintf(stderr, "%s: (-q && !LogSysLog): warning - all interception message methods are off\n",
argv[0]); |
b151ef55 |
use_syslog = 0; |
1c3f1ce1 |
} |
b151ef55 |
|
3a0b4e5b |
if(cfgopt(copt, "FixStaleSocket")) {
/*
* Get the incoming socket details - the way sendmail talks to
* us
* |
434012b8 |
* TODO: There's a security problem here that'll need fixing if
* the User entry of clamav.conf is not used |
3a0b4e5b |
*/
if(strncasecmp(port, "unix:", 5) == 0) {
if(unlink(&port[5]) < 0) |
462b5251 |
if(errno != ENOENT)
perror(&port[5]); |
3a0b4e5b |
} else if(strncasecmp(port, "local:", 6) == 0) {
if(unlink(&port[6]) < 0) |
462b5251 |
if(errno != ENOENT)
perror(&port[6]); |
3a0b4e5b |
}
} |
b151ef55 |
if(smfi_register(smfilter) == MI_FAILURE) { |
1f025849 |
fputs("smfi_register failure\n", stderr); |
b151ef55 |
return EX_UNAVAILABLE;
}
signal(SIGPIPE, SIG_IGN);
return smfi_main();
}
/*
* Verify that the server is where we think it is
* Returns true or false
*/
static int
pingServer(void)
{ |
f0d6f5a1 |
char *ptr; |
b151ef55 |
int sock, nbytes; |
f0d6f5a1 |
char buf[128]; |
b151ef55 |
|
f0d6f5a1 |
if(localSocket) {
struct sockaddr_un server; |
b151ef55 |
|
f0d6f5a1 |
memset((char *)&server, 0, sizeof(struct sockaddr_un));
server.sun_family = AF_UNIX;
strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
if((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("socket");
return 0;
}
if(connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
perror(localSocket);
return 0;
}
} else {
struct sockaddr_in server;
memset((char *)&server, 0, sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = htons(tcpSocket);
server.sin_addr.s_addr = inet_addr(serverIP);
if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
return 0;
}
if(connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
perror("connect");
return 0;
} |
b151ef55 |
} |
f0d6f5a1 |
/*
* It would be better to use PING, check for PONG then issue the
* VERSION command, since that would better validate that we're
* talking to clamd, however clamd closes the session after
* sending PONG :-(
* So this code does not really validate that we're talking to clamd
* Needs a fix to clamd
* Also version command is verbose: says "clamd / ClamAV version"
* instead of "clamAV version"
*/
if(send(sock, "VERSION\n", 8, 0) < 8) { |
b151ef55 |
perror("send");
close(sock);
return 0;
}
shutdown(sock, SHUT_WR);
|
d2efb3fa |
nbytes = clamd_recv(sock, buf, sizeof(buf)); |
b151ef55 |
close(sock);
if(nbytes < 0) {
perror("recv");
return 0;
} |
d2efb3fa |
if(nbytes == 0)
return 0;
|
b151ef55 |
buf[nbytes] = '\0';
|
f0d6f5a1 |
/* Remove the trailing new line from the reply */
if((ptr = strchr(buf, '\n')) != NULL)
*ptr = '\0';
/*
* No real validation is done here
*/
snprintf(clamav_version, sizeof(clamav_version),
"ClamAV version '%s', clamav-milter version '%s'",
buf, CM_VERSION); |
bb9979e6 |
|
f0d6f5a1 |
return 1; |
b151ef55 |
}
static sfsistat
clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
{
char buf[INET_ADDRSTRLEN]; /* IPv4 only */ |
2a1ff3e4 |
const char *remoteIP; |
b151ef55 |
|
2a1ff3e4 |
if(hostname == NULL) {
if(use_syslog)
syslog(LOG_ERR, "clamfi_connect: hostname is null");
return cl_error;
}
if(hostaddr == NULL) {
if(use_syslog)
syslog(LOG_ERR, "clamfi_connect: hostaddr is null");
return cl_error;
}
remoteIP = inet_ntop(AF_INET, &((struct sockaddr_in *)(hostaddr))->sin_addr, buf, sizeof(buf));
if(remoteIP == NULL) {
if(use_syslog)
syslog(LOG_ERR, "clamfi_connect: remoteIP is null");
return cl_error;
} |
80a72b0d |
|
1f025849 |
#ifdef CL_DEBUG |
e2bb746e |
if(debug_level >= 4) {
if(use_syslog)
syslog(LOG_NOTICE, "clamfi_connect: connection from %s [%s]", hostname, remoteIP); |
1f025849 |
printf("clamfi_connect: connection from %s [%s]\n", hostname, remoteIP); |
e2bb746e |
} |
1f025849 |
#endif |
b151ef55 |
|
b5d15e64 |
if(fflag)
/*
* Patch from "Richard G. Roberto" <rgr@dedlegend.com>
* Always scan whereever the message is from
*/
return SMFIS_CONTINUE;
|
b151ef55 |
if(!oflag)
if(strcmp(remoteIP, "127.0.0.1") == 0) {
#ifdef CL_DEBUG
if(use_syslog)
syslog(LOG_DEBUG, "clamfi_connect: not scanning outgoing messages");
puts("clamfi_connect: not scanning outgoing messages");
#endif
return SMFIS_ACCEPT;
}
if(!lflag) {
/*
* Decide what constitutes a local IP address. Emails from
* local machines are not scanned.
*
* TODO: read these from clamav.conf
*/
static const char *localAddresses[] = {
/*"^192\\.168\\.[0-9]+\\.[0-9]+$",*/
"^192\\.168\\.[0-9]*\\.[0-9]*$",
"^10\\.0\\.0\\.[0-9]*$",
"127.0.0.1",
NULL
};
const char **possible;
for(possible = localAddresses; *possible; possible++) {
int rc;
regex_t reg;
if(regcomp(®, *possible, 0) != 0) {
if(use_syslog)
syslog(LOG_ERR, "Couldn't parse local regexp"); |
b0a42ec6 |
return cl_error; |
b151ef55 |
}
rc = (regexec(®, remoteIP, 0, NULL, 0) == REG_NOMATCH) ? 0 : 1;
regfree(®);
if(rc) {
#ifdef CL_DEBUG
if(use_syslog)
syslog(LOG_DEBUG, "clamfi_connect: not scanning local messages");
puts("clamfi_connect: not scanning outgoing messages");
#endif
return SMFIS_ACCEPT;
}
}
}
return SMFIS_CONTINUE;
}
static sfsistat
clamfi_envfrom(SMFICTX *ctx, char **argv)
{
struct privdata *privdata;
struct sockaddr_in reply; |
dd0d5a8c |
unsigned short port; |
b151ef55 |
int nbytes, rc;
char buf[64];
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_envfrom: %s", argv[0]);
#ifdef CL_DEBUG
printf("clamfi_envfrom: %s\n", argv[0]);
#endif
if(max_children > 0) {
rc = 0;
pthread_mutex_lock(&n_children_mutex);
|
3a805bfd |
/*
* Not a while since sendmail doesn't like it if we
* take too long replying. Effectively this means that
* max_children is more of a hint than a rule
*/
if(n_children >= max_children) { |
b151ef55 |
struct timeval now;
struct timespec timeout;
struct timezone tz;
/*
* Use pthread_cond_timedwait rather than
* pthread_cond_wait since the sendmail which calls
* us will have a timeout that we don't want to exceed
*
* Wait for a maximum of 1 minute.
*
* TODO: this timeout should be configurable
* It stops sendmail getting fidgety.
*/
gettimeofday(&now, &tz);
timeout.tv_sec = now.tv_sec + 60;
timeout.tv_nsec = 0;
if(use_syslog)
syslog(LOG_NOTICE,
"hit max-children limit (%u >= %u): waiting for some to exit",
n_children, max_children); |
3a805bfd |
do
rc = pthread_cond_timedwait(&n_children_cond, &n_children_mutex, &timeout);
while(rc != ETIMEDOUT); |
b151ef55 |
}
n_children++;
#ifdef CL_DEBUG
printf(">n_children = %d\n", n_children);
#endif
pthread_mutex_unlock(&n_children_mutex);
if(rc == ETIMEDOUT) {
#ifdef CL_DEBUG
if(use_syslog)
syslog(LOG_NOTICE, "Timeout waiting for a child to die");
puts("Timeout waiting for a child to die");
#endif
}
}
privdata = (struct privdata *)calloc(1, sizeof(struct privdata));
privdata->dataSocket = -1; /* 0.4 */
privdata->cmdSocket = -1; /* 0.4 */
|
3454ad43 |
if(quarantine_dir) {
/*
* quarantine_dir is specified
* store message in a temporary file
*/
int ntries = 5; |
b151ef55 |
|
3454ad43 |
privdata->filename = malloc(strlen(quarantine_dir) + 12); |
b151ef55 |
|
3454ad43 |
do {
sprintf(privdata->filename, "%s/msg.XXXXXX", quarantine_dir);
#if defined(C_LINUX) || defined(C_BSD)
privdata->dataSocket = mkstemp(privdata->filename);
#else
if(mktemp(privdata->filename) == NULL) {
if(use_syslog)
syslog(LOG_ERR, "mktemp %s failed", privdata->filename);
free(privdata->filename);
privdata->filename = NULL;
return cl_error;
} |
cdbe607d |
privdata->dataSocket = open(privdata->filename, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC, 0600); |
3454ad43 |
#endif |
e6bffccb |
} while((--ntries > 0) && (privdata->dataSocket < 0)); |
3454ad43 |
if(privdata->dataSocket < 0) {
if(use_syslog)
syslog(LOG_ERR, "tempfile %s creation failed", privdata->filename);
free(privdata->filename);
privdata->filename = NULL; |
b0a42ec6 |
return cl_error; |
b151ef55 |
}
} else { |
3454ad43 |
/*
* Create socket to talk to clamd. It will tell us the port to use
* to send the data. That will require another socket.
*/
if(localSocket) {
struct sockaddr_un server; |
b151ef55 |
|
3454ad43 |
memset((char *)&server, 0, sizeof(struct sockaddr_un));
server.sun_family = AF_UNIX;
strncpy(server.sun_path, localSocket, sizeof(server.sun_path)); |
b151ef55 |
|
3454ad43 |
if((privdata->cmdSocket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("socket");
return cl_error;
}
if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
perror(localSocket);
return cl_error;
}
} else {
struct sockaddr_in server;
memset((char *)&server, 0, sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = htons(tcpSocket);
server.sin_addr.s_addr = inet_addr(serverIP);
if((privdata->cmdSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
return cl_error;
}
if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
perror("connect");
return cl_error;
} |
b151ef55 |
} |
3454ad43 |
/*
* Create socket that we'll use to send the data to clamd
*/
if((privdata->dataSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
close(privdata->cmdSocket);
free(privdata);
if(use_syslog)
syslog(LOG_ERR, "failed to create socket"); |
2a1ff3e4 |
return cl_error; |
b151ef55 |
}
|
3454ad43 |
shutdown(privdata->dataSocket, SHUT_RD); |
b151ef55 |
|
3454ad43 |
if(send(privdata->cmdSocket, "STREAM\n", 7, 0) < 7) {
perror("send");
close(privdata->dataSocket);
close(privdata->cmdSocket);
free(privdata);
if(use_syslog)
syslog(LOG_ERR, "send failed to clamd");
return cl_error;
} |
b151ef55 |
|
3454ad43 |
shutdown(privdata->cmdSocket, SHUT_WR); |
b151ef55 |
|
d2efb3fa |
nbytes = clamd_recv(privdata->cmdSocket, buf, sizeof(buf)); |
3454ad43 |
if(nbytes < 0) {
perror("recv");
close(privdata->dataSocket);
close(privdata->cmdSocket);
free(privdata);
if(use_syslog)
syslog(LOG_ERR, "recv failed from clamd getting PORT");
return cl_error;
}
buf[nbytes] = '\0'; |
d2efb3fa |
#ifdef CL_DEBUG |
3454ad43 |
if(debug_level >= 4)
printf("Received: %s", buf); |
d2efb3fa |
#endif |
3454ad43 |
if(sscanf(buf, "PORT %hu\n", &port) != 1) {
close(privdata->dataSocket);
close(privdata->cmdSocket);
free(privdata);
if(use_syslog)
syslog(LOG_ERR, "Expected port information from clamd, got '%s'",
buf);
else
fprintf(stderr, "Expected port information from clamd, got '%s'\n",
buf);
return cl_error;
} |
b151ef55 |
|
3454ad43 |
memset((char *)&reply, 0, sizeof(struct sockaddr_in));
reply.sin_family = AF_INET;
reply.sin_port = ntohs(port); |
b151ef55 |
|
3454ad43 |
reply.sin_addr.s_addr = inet_addr(serverIP); |
b151ef55 |
|
d2efb3fa |
#ifdef CL_DEBUG |
3454ad43 |
if(debug_level >= 4)
printf("Connecting to local port %d\n", port); |
d2efb3fa |
#endif |
b151ef55 |
|
3454ad43 |
rc = connect(privdata->dataSocket, (struct sockaddr *)&reply, sizeof(struct sockaddr_in)); |
b151ef55 |
|
3454ad43 |
if(rc < 0) {
perror("connect"); |
b151ef55 |
|
3454ad43 |
close(privdata->dataSocket);
close(privdata->cmdSocket);
free(privdata);
/* 0.4 - use better error message */
if(use_syslog) { |
e6bffccb |
#ifdef TARGET_OS_SOLARIS /* no strerror_r */ |
3454ad43 |
syslog(LOG_ERR, "Failed to connect to port %d given by clamd: %s", port, strerror(rc)); |
e6bffccb |
#else |
3454ad43 |
strerror_r(rc, buf, sizeof(buf));
syslog(LOG_ERR, "Failed to connect to port %d given by clamd: %s", port, buf); |
e6bffccb |
#endif |
3454ad43 |
} |
b151ef55 |
|
3454ad43 |
return cl_error; |
b151ef55 |
}
}
|
e2bb746e |
clamfi_send(privdata, 0, "Received: by clamav-milter\nFrom: %s\n", argv[0]); |
b151ef55 |
privdata->from = strdup(argv[0]);
privdata->to = NULL;
|
b0a42ec6 |
return (smfi_setpriv(ctx, privdata) == MI_SUCCESS) ? SMFIS_CONTINUE : cl_error; |
b151ef55 |
}
static sfsistat
clamfi_envrcpt(SMFICTX *ctx, char **argv)
{
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_envrcpt: %s", argv[0]);
#ifdef CL_DEBUG
printf("clamfi_envrcpt: %s \n", argv[0]);
#endif
clamfi_send(privdata, 0, "To: %s\n", argv[0]);
if(privdata->to == NULL) {
privdata->to = malloc(sizeof(char *) * 2);
assert(privdata->numTo == 0);
} else
privdata->to = realloc(privdata->to, sizeof(char *) * (privdata->numTo + 2));
privdata->to[privdata->numTo] = strdup(argv[0]);
privdata->to[++privdata->numTo] = NULL;
return SMFIS_CONTINUE;
}
static sfsistat
clamfi_header(SMFICTX *ctx, char *headerf, char *headerv)
{
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_header: %s: %s", headerf, headerv);
#ifdef CL_DEBUG
if(debug_level >= 9)
printf("clamfi_header: %s: %s\n", headerf, headerv);
else
puts("clamfi_header");
#endif
if(clamfi_send(privdata, 0, "%s: %s\n", headerf, headerv) < 0) {
clamfi_cleanup(ctx); |
b0a42ec6 |
return cl_error; |
b151ef55 |
}
return SMFIS_CONTINUE;
}
static sfsistat
clamfi_eoh(SMFICTX *ctx)
{
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx); |
b14e9e77 |
char **to; |
b151ef55 |
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_eoh");
#ifdef CL_DEBUG
puts("clamfi_eoh");
#endif
if(clamfi_send(privdata, 1, "\n") < 0) {
clamfi_cleanup(ctx); |
b0a42ec6 |
return cl_error; |
b151ef55 |
}
|
b14e9e77 |
/*
* See if the e-mail is only going to members of the list
* of users we don't scan for. If it is, don't scan, otherwise
* scan
*
* scan = false
* FORALL recipients
* IF receipient NOT MEMBER OF white address list
* THEN
* scan = true
* FI
* ENDFOR
*/
for(to = privdata->to; *to; to++) {
const char **s;
for(s = ignoredEmailAddresses; *s; s++)
if(strcasecmp(*s, *to) == 0)
/*
* This recipient is on the whitelist
*/
break;
if(*s == NULL)
/*
* This recipient is not on the whitelist,
* no need to check any further
*/
return SMFIS_CONTINUE;
}
/*
* Didn't find a recipient who is not on the white list, so all
* must be on the white list, so just accept the e-mail
*/
if(use_syslog)
syslog(LOG_NOTICE, "clamfi_connect: ignoring whitelisted message");
#ifdef CL_DEBUG
puts("clamfi_connect: not scanning outgoing messages");
#endif
clamfi_cleanup(ctx);
return SMFIS_ACCEPT; |
b151ef55 |
}
static sfsistat
clamfi_body(SMFICTX *ctx, u_char *bodyp, size_t len)
{
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_envbody: %u bytes", len);
#ifdef CL_DEBUG
printf("clamfi_envbody: %u bytes\n", len);
#endif
if(clamfi_send(privdata, len, (char *)bodyp) < 0) {
clamfi_cleanup(ctx); |
b0a42ec6 |
return cl_error; |
b151ef55 |
} |
e6bffccb |
if(Sflag) { |
3166c010 |
if(privdata->body) {
assert(privdata->bodyLen > 0);
privdata->body = realloc(privdata->body, privdata->bodyLen + len);
memcpy(&privdata->body[privdata->bodyLen], bodyp, len);
privdata->bodyLen += len; |
e6bffccb |
} else { |
3166c010 |
assert(privdata->bodyLen == 0);
privdata->body = malloc(len);
memcpy(privdata->body, bodyp, len);
privdata->bodyLen = len; |
e6bffccb |
}
} |
b151ef55 |
return SMFIS_CONTINUE;
}
static sfsistat
clamfi_eom(SMFICTX *ctx)
{
int rc = SMFIS_CONTINUE;
char *ptr;
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
char mess[128];
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_eom");
#ifdef CL_DEBUG
puts("clamfi_eom");
assert(privdata != NULL); |
3454ad43 |
assert((privdata->cmdSocket >= 0) || (privdata->filename != NULL));
assert(!((privdata->cmdSocket >= 0) && (privdata->filename != NULL))); |
b151ef55 |
assert(privdata->dataSocket >= 0);
#endif
close(privdata->dataSocket);
privdata->dataSocket = -1;
|
3454ad43 |
if(quarantine_dir != NULL) {
char cmdbuf[1024];
/*
* Create socket to talk to clamd.
*/
struct sockaddr_un server;
int nbytes;
assert(localSocket != NULL);
memset((char *)&server, 0, sizeof(struct sockaddr_un));
server.sun_family = AF_UNIX;
strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
if((privdata->cmdSocket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("socket"); |
85c1dbfd |
clamfi_cleanup(ctx); |
3454ad43 |
return cl_error;
}
if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
perror(localSocket); |
85c1dbfd |
clamfi_cleanup(ctx); |
3454ad43 |
return cl_error;
}
snprintf(cmdbuf, sizeof(cmdbuf) - 1, "SCAN %s", privdata->filename);
nbytes = (int)strlen(cmdbuf);
if(send(privdata->cmdSocket, cmdbuf, nbytes, 0) < nbytes) {
perror("send");
clamfi_cleanup(ctx);
if(use_syslog)
syslog(LOG_ERR, "send failed to clamd");
return cl_error;
}
shutdown(privdata->cmdSocket, SHUT_WR);
}
|
d2efb3fa |
if(clamd_recv(privdata->cmdSocket, mess, sizeof(mess)) > 0) { |
b151ef55 |
if((ptr = strchr(mess, '\n')) != NULL)
*ptr = '\0';
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_eom: read %s", mess);
#ifdef CL_DEBUG
printf("clamfi_eom: read %s\n", mess);
#endif
} else { |
85c1dbfd |
clamfi_cleanup(ctx); |
b151ef55 |
syslog(LOG_NOTICE, "clamfi_eom: read nothing from clamd");
#ifdef CL_DEBUG
puts("clamfi_eom: read nothing from clamd");
#endif |
d2efb3fa |
return cl_error; |
b151ef55 |
}
|
0b6bce84 |
close(privdata->cmdSocket);
privdata->cmdSocket = -1;
|
b151ef55 |
if(strstr(mess, "FOUND") == NULL) { |
3613bd91 |
if(!nflag)
smfi_addheader(ctx, "X-Virus-Scanned", clamav_version); |
b151ef55 |
/*
* TODO: if privdata->from is NULL it's probably SPAM, and
* me might consider bouncing it...
*/
if(use_syslog) |
e2bb746e |
/* Include the sendmail queue ID in the log */
syslog(LOG_NOTICE, "%s: clean message from %s",
smfi_getsymval(ctx, "i"), |
c6259ac5 |
(privdata->from) ? privdata->from : "an unknown sender"); |
e6bffccb |
|
3166c010 |
if(privdata->body) { |
e2bb746e |
/*
* Add a signature that all has been scanned OK
*/ |
cdbe607d |
off_t len = updateSigFile(); |
e6bffccb |
|
cdbe607d |
if(len) {
assert(Sflag != 0); |
e6bffccb |
|
cdbe607d |
privdata->body = realloc(privdata->body, privdata->bodyLen + len);
memcpy(&privdata->body[privdata->bodyLen], signature, len); |
e6bffccb |
|
cdbe607d |
smfi_replacebody(ctx, privdata->body, privdata->bodyLen + len);
} |
e6bffccb |
} |
b151ef55 |
} else {
int i;
char **to, *err;
FILE *sendmail;
if(use_syslog)
syslog(LOG_NOTICE, mess);
/*
* Setup err as a list of recipients
*/ |
c6259ac5 |
err = (char *)malloc(1024); |
b151ef55 |
|
dd0d5a8c |
/*
* Use snprintf rather than printf since we don't know the
* length of privdata->from and may get a buffre overrun
* causing a crash
*/
snprintf(err, 1024, "Intercepted virus from %s to", privdata->from); |
b151ef55 |
ptr = strchr(err, '\0');
|
c6259ac5 |
i = 1024;
|
b151ef55 |
for(to = privdata->to; *to; to++) {
/*
* Re-alloc if we are about run out of buffer space
*/
if(&ptr[strlen(*to) + 2] >= &err[i]) {
i += 1024;
err = realloc(err, i); |
4ba69cb6 |
ptr = strchr(err, '\0'); |
b151ef55 |
}
ptr = strrcpy(ptr, " ");
ptr = strrcpy(ptr, *to);
} |
c6259ac5 |
(void)strcpy(ptr, "\n"); |
b151ef55 |
if(use_syslog) |
e2bb746e |
/* Include the sendmail queue ID in the log */
syslog(LOG_NOTICE, "%s: %s",
smfi_getsymval(ctx, "i"),
err); |
b151ef55 |
#ifdef CL_DEBUG
puts(err);
#endif |
bb9979e6 |
free(err); |
b151ef55 |
|
1c3f1ce1 |
if(!qflag) { |
b696653a |
char cmd[128];
snprintf(cmd, sizeof(cmd), "%s -t", SENDMAIL_BIN);
sendmail = popen(cmd, "w");
|
1c3f1ce1 |
if(sendmail) { |
dd0d5a8c |
/*
* TODO: Make this e-mail message customisable
* perhaps by means of a template
*/ |
1c3f1ce1 |
fputs("From: MAILER-DAEMON\n", sendmail);
if(bflag) {
fprintf(sendmail, "To: %s\n", privdata->from);
fprintf(sendmail, "Cc: %s\n", postmaster);
} else
fprintf(sendmail, "To: %s\n", postmaster);
if(!pflag)
for(to = privdata->to; *to; to++)
fprintf(sendmail, "Cc: %s\n", *to);
fputs("Subject: Virus intercepted\n\n", sendmail);
if(bflag)
fputs("A message you sent to\n\t", sendmail);
else
fprintf(sendmail, "A message sent from %s to\n\t", privdata->from);
for(to = privdata->to; *to; to++)
fprintf(sendmail, "%s\n", *to);
fputs("contained a virus and has not been delivered.\n\t", sendmail);
fputs(mess, sendmail);
|
3454ad43 |
if(privdata->filename != NULL)
fprintf(sendmail, "\nThe message in question is quarantined as %s\n", privdata->filename);
|
1c3f1ce1 |
pclose(sendmail);
} |
b151ef55 |
} |
3454ad43 |
if(privdata->filename) {
assert(quarantine_dir != NULL);
if(use_syslog)
syslog(LOG_NOTICE, "Quarantined infected mail as %s", privdata->filename);
/*
* Cleanup filename here! Default procedure would delete quarantine file
*/
free(privdata->filename);
privdata->filename = NULL;
}
|
bb9979e6 |
if(quarantine) {
for(to = privdata->to; *to; to++) {
smfi_delrcpt(ctx, *to); |
3454ad43 |
smfi_addheader(ctx, "X-Original-To", *to); |
bb9979e6 |
free(*to);
}
free(privdata->to);
privdata->to = NULL;
if(smfi_addrcpt(ctx, quarantine) == MI_FAILURE) {
if(use_syslog)
syslog(LOG_DEBUG, "Can't set quarantine user %s", quarantine);
else
fprintf(stderr, "Can't set quarantine user %s\n", quarantine); |
3454ad43 |
} else |
bb9979e6 |
/*
* FIXME: doesn't work if there's no subject
*/
smfi_chgheader(ctx, "Subject", 1, mess);
} else
rc = SMFIS_REJECT; /* Delete the e-mail */ |
b151ef55 |
smfi_setreply(ctx, "550", "5.7.1", "Virus detected by ClamAV - http://clamav.elektrapro.com");
}
clamfi_cleanup(ctx);
return rc;
}
static sfsistat
clamfi_abort(SMFICTX *ctx)
{
#ifdef CL_DEBUG
if(use_syslog)
syslog(LOG_DEBUG, "clamfi_abort");
puts("clamfi_abort");
#endif
/*
* Unlock incase we're called during a cond_timedwait in envfrom
*
* TODO: There *must* be a tidier way of doing this!
*/ |
3a805bfd |
if(max_children > 0)
(void)pthread_mutex_unlock(&n_children_mutex); |
b151ef55 |
clamfi_cleanup(ctx);
|
b0a42ec6 |
return cl_error; |
b151ef55 |
}
static sfsistat
clamfi_close(SMFICTX *ctx)
{
#ifdef CL_DEBUG
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
puts("clamfi_close");
assert(privdata == NULL);
#endif
if(logVerbose)
syslog(LOG_DEBUG, "clamfi_close");
return SMFIS_CONTINUE;
}
static void
clamfi_cleanup(SMFICTX *ctx)
{
struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
|
3613bd91 |
if(privdata) { |
3166c010 |
if(privdata->body)
free(privdata->body);
|
3613bd91 |
if(privdata->dataSocket >= 0) {
close(privdata->dataSocket);
privdata->dataSocket = -1;
} |
b151ef55 |
|
3454ad43 |
if(privdata->filename != NULL) {
if(unlink(privdata->filename) < 0)
perror(privdata->filename);
free(privdata->filename);
privdata->filename = NULL;
}
|
3613bd91 |
if(privdata->from) { |
b151ef55 |
#ifdef CL_DEBUG |
3613bd91 |
if(debug_level >= 9)
puts("Free privdata->from"); |
b151ef55 |
#endif |
3613bd91 |
free(privdata->from);
privdata->from = NULL;
} |
b151ef55 |
|
3613bd91 |
if(privdata->to) {
char **to; |
b151ef55 |
|
3613bd91 |
for(to = privdata->to; *to; to++) { |
b151ef55 |
#ifdef CL_DEBUG |
3613bd91 |
if(debug_level >= 9)
puts("Free *privdata->to"); |
b151ef55 |
#endif |
3613bd91 |
free(*to);
} |
b151ef55 |
#ifdef CL_DEBUG |
3613bd91 |
if(debug_level >= 9)
puts("Free privdata->to"); |
b151ef55 |
#endif |
3613bd91 |
free(privdata->to);
privdata->to = NULL;
} |
b151ef55 |
|
3613bd91 |
if(privdata->cmdSocket >= 0) {
char buf[64]; |
b151ef55 |
|
3613bd91 |
/*
* Flush the remote end so that clamd doesn't get a SIGPIPE
*/ |
d2efb3fa |
while(clamd_recv(privdata->cmdSocket, buf, sizeof(buf)) > 0) |
3613bd91 |
;
close(privdata->cmdSocket);
privdata->cmdSocket = -1;
} |
b151ef55 |
#ifdef CL_DEBUG |
3613bd91 |
if(debug_level >= 9)
puts("Free privdata"); |
b151ef55 |
#endif |
3613bd91 |
free(privdata);
smfi_setpriv(ctx, NULL);
} |
b151ef55 |
if(max_children > 0) {
pthread_mutex_lock(&n_children_mutex);
/*
* Deliberately errs on the side of broadcasting too many times
*/ |
390a7661 |
if(n_children > 0)
--n_children; |
b151ef55 |
#ifdef CL_DEBUG |
390a7661 |
puts("pthread_cond_broadcast"); |
b151ef55 |
#endif |
3a805bfd |
pthread_cond_broadcast(&n_children_cond); |
b151ef55 |
#ifdef CL_DEBUG
printf("<n_children = %d\n", n_children);
#endif
pthread_mutex_unlock(&n_children_mutex);
}
}
static int
clamfi_send(const struct privdata *privdata, size_t len, const char *format, ...)
{
char output[BUFSIZ];
const char *ptr;
assert(format != NULL);
if(len > 0)
/*
* It isn't a NUL terminated string. We have a set number of
* bytes to output.
*/
ptr = format;
else {
va_list argp;
va_start(argp, format);
vsnprintf(output, sizeof(output), format, argp);
va_end(argp);
len = strlen(output);
ptr = output;
}
#ifdef CL_DEBUG
if(debug_level >= 9)
printf("clamfi_send: len=%u bufsiz=%u\n", len, sizeof(output));
#endif
while(len > 0) { |
e2bb746e |
const int nbytes = (quarantine_dir) ? |
3454ad43 |
write(privdata->dataSocket, ptr, len) :
send(privdata->dataSocket, ptr, len, 0); |
b151ef55 |
if(nbytes == -1) {
if(errno == EINTR)
continue;
perror("send");
if(use_syslog)
syslog(LOG_ERR, "write failure to clamd");
return -1;
}
len -= nbytes;
ptr = &ptr[nbytes];
}
return 0;
}
/*
* Like strcpy, but return the END of the destination, allowing a quicker
* means of adding to the end of a string than strcat
*/
static char *
strrcpy(char *dest, const char *source)
{
/* Pre assertions */
assert(dest != NULL);
assert(source != NULL);
assert(dest != source);
while((*dest++ = *source++) != '\0')
;
return(--dest);
} |
d2efb3fa |
/*
* Read from clamav - timeout if necessary
*/
static int
clamd_recv(int sock, char *buf, size_t len)
{
fd_set rfds;
struct timeval tv;
if(threadtimeout == 0)
return recv(sock, buf, len, 0);
FD_ZERO(&rfds);
FD_SET(sock, &rfds);
tv.tv_sec = threadtimeout;
tv.tv_usec = 0;
switch(select(sock + 1, &rfds, NULL, NULL, &tv)) {
case -1:
perror("select");
return -1;
case 0:
if(use_syslog)
syslog(LOG_ERR, "No data received from clamd in %d seconds\n", threadtimeout);
return 0;
}
return recv(sock, buf, len, 0);
} |
cdbe607d |
/*
* Read in the signature file
*/
static off_t
updateSigFile(void)
{
struct stat statb;
int fd;
if(sigFilename == NULL)
/* nothing to read */
return signature ? strlen(signature) : 0;
if(stat(sigFilename, &statb) < 0) {
perror(sigFilename);
if(use_syslog)
syslog(LOG_ERR, "Can't stat %s\n", sigFilename);
return 0;
}
if(statb.st_mtime <= signatureStamp)
return statb.st_size; /* not changed */
fd = open(sigFilename, O_RDONLY);
if(fd < 0) {
perror(sigFilename);
if(use_syslog)
syslog(LOG_ERR, "Can't open %s\n", sigFilename);
return 0;
}
signatureStamp = statb.st_mtime;
signature = realloc(signature, statb.st_size);
read(fd, signature, statb.st_size);
close(fd);
return statb.st_size;
} |