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 |
|
87379b21 |
#define CM_VERSION "0.93.1" |
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>
#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[] = {
/*{ PACKADDR(127, 0, 0, 0), MAKEMASK(24) }, /* 127.0.0.0/24 */ |
9fe789f8 |
{ PACKADDR(192, 168, 0, 0), MAKEMASK(24) }, /* 192.168.0.0/24 - RFC3330 */
/*{ PACKADDR(192, 18, 0, 0), MAKEMASK(17) }, /* 192.18.0.0/17 - RFC2544 */
/*{ PACKADDR(192, 0, 2, 0), MAKEMASK(8) }, /* 192.0.2.0/8 - 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
*/
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); |
cbdb077f |
#ifdef HAVE_RESOLV_H |
93928eab |
static table_t *resolve(const char *host, table_t *t); |
684d3122 |
static int spf(struct privdata *privdata, table_t *prevhosts); |
93928eab |
static void spf_ip(char *ip, int zero, void *v); |
a32f3ba8 |
#endif |
f1617494 |
static sfsistat black_hole(const struct privdata *privdata); |
2dda0caf |
static int useful_header(const char *cmd); |
19575eba |
|
c3429724 |
extern short logg_foreground; |
e13e1f7c |
|
e3aaff8e |
static void
help(void)
{
printf("\n\tclamav-milter version %s\n", CM_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.")); |
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; |
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)
struct rlimit rlim;
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",
VERSION, CM_VERSION);
|
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 |
1a2b28a9 |
const char *args = "a:AbB:c:C:dDefF:I:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:x:0:1:2"; |
e3aaff8e |
#else |
1a2b28a9 |
const char *args = "a:AbB:c:C:dDefF:I:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:0:1:2"; |
e3aaff8e |
#endif |
4c5e69c8 |
#else /*!BOUNCE*/
#ifdef CL_DEBUG |
1a2b28a9 |
const char *args = "a:AB:c:C:dDefF:I:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:x:0:1:2"; |
4c5e69c8 |
#else |
1a2b28a9 |
const char *args = "a:AB:c:C:dDefF:I:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:0: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'
},
{ |
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;
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); |
87379b21 |
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 |
|
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)); |
87379b21 |
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;
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 */ |
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 |
|
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 |
}
|
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)); |
87379b21 |
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", |
a4371160 |
buf, CM_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)); |
87379b21 |
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];
|
28d8b7bd |
for(ptr = to; *ptr; ptr++)
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)); |
87379b21 |
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) |
87379b21 |
strncpy(hostname, j, sizeof(hostname) - 1); |
734ea355 |
else |
8a7ef08f |
strcpy(hostname, _("Error determining host")); |
87379b21 |
hostname[sizeof(hostname)-1]='\0'; |
734ea355 |
} else if(strchr(hostname, '.') == NULL) {
/*
* Determine fully qualified name
*/
struct hostent hostent;
|
87379b21 |
if((r_gethostbyname(hostname, &hostent, buf, sizeof(buf)) == 0) && hostent.h_name) { |
734ea355 |
strncpy(hostname, hostent.h_name, sizeof(hostname)); |
87379b21 |
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)); |
87379b21 |
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();
}
|
7ec5167b |
daily = cli_malloc(strlen(dbdir) + 11); |
93899a2c |
sprintf(daily, "%s/daily.cvd", dbdir); |
66643f8f |
if(access(daily, R_OK) < 0) |
7ec5167b |
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, |
f9d522ea |
"ClamAV %s/%u/%s", 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",
VERSION, CM_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 |
}
|
93928eab |
len = 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 |
len = res_query(host, C_IN, T_A, (u_char *)&q, sizeof(q));
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? |
4a5aa10e |
* TODO: check res_query is thread safe |
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); |
93928eab |
len = 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); |
87379b21 |
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 */
static void
mx(void)
{
logg(_("^MX peers will not be immune from being blacklisted"));
if(blacklist == NULL)
blacklist = tableCreate();
}
#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;
} |