This daemon-type scanner is GPL and free. You can get it at
&url(http://www.clamav.net/). Some older versions of clamd do not seem to
unpack MIME containers, so it used to be recommended to unpack MIME attachments
-in the MIME ACL. This no longer believed to be necessary. One option is
-required: either the path and name of a UNIX socket file, or a hostname or IP
-number, and a port, separated by space, as in the second of these examples:
+in the MIME ACL. This is no longer believed to be necessary.
+
+The options are a list of server specifiers, which may be
+a UNIX socket specification,
+a TCP socket specification,
+or a (global) option.
+
+A socket specification consists of a space-separated list.
+For a Unix socket the first element is a full path for the socket,
+for a TCP socket the first element is the IP address
+and the second a port number,
+Any further elements are per-server (non-global) options.
+These per-server options are supported:
+.code
+retry=<timespec> Retry on connect fail
+.endd
+
+The &`retry`& option specifies a time after which a single retry for
+a failed connect is made. The default is to not retry.
+
+If a Unix socket file is specified, only one server is supported.
+
+Examples:
.code
av_scanner = clamd:/opt/clamd/socket
av_scanner = clamd:192.0.2.3 1234
av_scanner = clamd:192.0.2.3 1234:local
+av_scanner = clamd:192.0.2.3 1234 retry=10s
av_scanner = clamd:192.0.2.3 1234 : 192.0.2.4 1234
.endd
-If the value of av_scanner points to a UNIX socket file or contains the local
-keyword, then the ClamAV interface will pass a filename containing the data
+If the value of av_scanner points to a UNIX socket file or contains the
+&`local`&
+option, then the ClamAV interface will pass a filename containing the data
to be scanned, which will should normally result in less I/O happening and be
more efficient. Normally in the TCP case, the data is streamed to ClamAV as
Exim does not assume that there is a common filesystem with the remote host.
The &%spam%& ACL condition calls SpamAssassin's &%spamd%& daemon to get a spam
score and a report for the message.
.new
-Support is also provided for Rspamd (which can speak SpamAssassin's protocol but
-provides reduced functionality when used in this mode).
+Support is also provided for Rspamd.
For more information about installation and configuration of SpamAssassin or
Rspamd refer to their respective websites at
.code
variant=rspamd Use Rspamd rather than SpamAssassin protocol
time=<start>-<end> Use only between these times of day
-tmo=<timespec> Connection time limit.
+tmo=<timespec> Connection time limit
weight=<value> Selection bias
backup Use only if all non-backup servers fail
+retry=<timespec> Retry on connect fail
.endd
Time specifications for the &`time`& option are <hour>.<minute>.<second>
Either the seconds or both minutes and seconds, plus the leading &`.`&
characters, may be omitted and will be taken as zero.
-Timeout specifications for the &`tmo`& option are the usual Exim
-time interval standard, eg. &`20s`& or &`1m`&.
+Timeout specifications for the &`tmo`& and &`retry`& options
+are the usual Exim time interval standard, eg. &`20s`& or &`1m`&.
+
+The &`tmo`& option specifies an overall timeout for communication.
The default value is two minutes.
+The &`retry`& option specifies a time after which a single retry for
+a failed connect is made.
+The default is to not retry.
+
Servers are queried in a random fashion, weighted by the selection bias.
The default value for selection bias is 1.
.wen
JH/18 Bug 1581: Router and transport options headers_add/remove can
now have the list separator specified.
+JH/19 Bug 392: spamd_address, and clamd av_scanner, now support retry
+ option values.
+
Exim version 4.85
{
if (fd != fd6) close(fd6);
if (fd != fd4) close(fd4);
- if (connhost) {
+ if (connhost)
+ {
h->port = port;
*connhost = *h;
connhost->next = NULL;
/* End of ip.c */
+/* vi: aw ai sw=2
+*/
/* The maximum number of clamd servers that are supported in the configuration */
#define MAX_CLAMD_SERVERS 32
#define MAX_CLAMD_SERVERS_S "32"
-/* Maximum length of the hostname that can be specified in the clamd address list */
-#define MAX_CLAMD_ADDRESS_LENGTH 64
-#define MAX_CLAMD_ADDRESS_LENGTH_S "64"
-typedef struct clamd_address_container {
- uschar tcp_addr[MAX_CLAMD_ADDRESS_LENGTH+1];
- unsigned int tcp_port;
-} clamd_address_container;
+typedef struct clamd_address {
+ uschar * hostspec;
+ unsigned tcp_port;
+ unsigned retry;
+} clamd_address;
#ifndef nelements
# define nelements(arr) (sizeof(arr) / sizeof(arr[0]))
static inline int
malware_errlog_defer(const uschar * str)
{
- log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str);
- return DEFER;
+log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str);
+return DEFER;
}
static int
m_errlog_defer(struct scan * scanent, const uschar * str)
{
- return malware_errlog_defer(string_sprintf("%s: %s", scanent->name, str));
+return malware_errlog_defer(string_sprintf("%s: %s", scanent->name, str));
}
static int
m_errlog_defer_3(struct scan * scanent, const uschar * str,
int fd_to_close)
{
- (void) close(fd_to_close);
- return m_errlog_defer(scanent, str);
+(void) close(fd_to_close);
+return m_errlog_defer(scanent, str);
}
/*************************************************/
m_tcpsocket(const uschar * hostname, unsigned int port,
host_item * host, uschar ** errstr)
{
- return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr);
+return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr);
}
static int
m_sock_send(int sock, uschar * buf, int cnt, uschar ** errstr)
{
- if (send(sock, buf, cnt, 0) < 0) {
- int err = errno;
- (void)close(sock);
- *errstr = string_sprintf("unable to send to socket (%s): %s",
- buf, strerror(err));
- return -1;
- }
- return sock;
+if (send(sock, buf, cnt, 0) < 0)
+ {
+ int err = errno;
+ (void)close(sock);
+ *errstr = string_sprintf("unable to send to socket (%s): %s",
+ buf, strerror(err));
+ return -1;
+ }
+return sock;
}
static const pcre *
m_pcre_compile(const uschar * re, uschar ** errstr)
{
- const uschar * rerror;
- int roffset;
- const pcre * cre;
-
- cre = pcre_compile(CS re, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
- if (!cre)
- *errstr= string_sprintf("regular expression error in '%s': %s at offset %d",
- re, rerror, roffset);
- return cre;
+const uschar * rerror;
+int roffset;
+const pcre * cre;
+
+cre = pcre_compile(CS re, PCRE_COPT, (const char **)&rerror, &roffset, NULL);
+if (!cre)
+ *errstr= string_sprintf("regular expression error in '%s': %s at offset %d",
+ re, rerror, roffset);
+return cre;
}
uschar *
m_pcre_exec(const pcre * cre, uschar * text)
{
- int ovector[10*3];
- int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0,
- ovector, nelements(ovector));
- uschar * substr = NULL;
- if (i >= 2) /* Got it */
- pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr);
- return substr;
+int ovector[10*3];
+int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0,
+ ovector, nelements(ovector));
+uschar * substr = NULL;
+if (i >= 2) /* Got it */
+ pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr);
+return substr;
}
static const pcre *
m_pcre_nextinlist(const uschar ** list, int * sep,
char * listerr, uschar ** errstr)
{
- const uschar * list_ele;
- const pcre * cre = NULL;
+const uschar * list_ele;
+const pcre * cre = NULL;
- if (!(list_ele = string_nextinlist(list, sep, NULL, 0)))
- *errstr = US listerr;
- else
- cre = m_pcre_compile(CUS list_ele, errstr);
- return cre;
+if (!(list_ele = string_nextinlist(list, sep, NULL, 0)))
+ *errstr = US listerr;
+else
+ cre = m_pcre_compile(CUS list_ele, errstr);
+return cre;
}
/*
return mksd_parse_line (scanent, CS av_buffer);
}
+
+static int
+clamd_option(clamd_address * cd, const uschar * optstr, int * subsep)
+{
+uschar * s;
+
+cd->retry = 0;
+while ((s = string_nextinlist(&optstr, subsep, NULL, 0)))
+ {
+ if (Ustrncmp(s, "retry=", 6) == 0)
+ {
+ int sec = readconf_readtime((s += 6), '\0', FALSE);
+ if (sec < 0)
+ return FAIL;
+ cd->retry = sec;
+ }
+ else
+ return FAIL;
+ }
+}
+
/*************************************************
* Scan content for malware *
*************************************************/
off_t fsize;
unsigned int fsize_uint;
BOOL use_scan_command = FALSE;
- clamd_address_container * clamd_address_vector[MAX_CLAMD_SERVERS];
+ clamd_address * cv[MAX_CLAMD_SERVERS];
int num_servers = 0;
#ifdef WITH_OLD_CLAMAV_STREAM
unsigned int port;
uint32_t send_size, send_final_zeroblock;
#endif
+ /*XXX if unixdomain socket, only one server supported. Needs fixing;
+ there's no reason we should not mix local and remote servers */
+
if (*scanner_options == '/')
+ {
+ clamd_address * cd;
+ const uschar * sublist;
+ int subsep = ' ';
+
/* Local file; so we def want to use_scan_command and don't want to try
* passing IP/port combinations */
use_scan_command = TRUE;
+ cd = (clamd_address *) store_get(sizeof(clamd_address));
+
+ /* extract socket-path part */
+ sublist = scanner_options;
+ cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0);
+
+ /* parse options */
+ if (clamd_option(cd, sublist, &subsep) != OK)
+ return m_errlog_defer(scanent,
+ string_sprintf("bad option '%s'", scanner_options));
+ cv[0] = cd;
+ }
else
{
- const uschar *address = scanner_options;
- uschar address_buffer[MAX_CLAMD_ADDRESS_LENGTH + 20];
-
/* Go through the rest of the list of host/port and construct an array
* of servers to try. The first one is the bit we just passed from
* scanner_options so process that first and then scan the remainder of
* the address buffer */
do
{
- clamd_address_container *this_clamd;
+ clamd_address * cd;
+ const uschar * sublist;
+ int subsep = ' ';
+ uschar * s;
/* The 'local' option means use the SCAN command over the network
* socket (ie common file storage in use) */
- if (strcmpic(address,US"local") == 0)
+ /*XXX we could accept this also as a local option? */
+ if (strcmpic(scanner_options, US"local") == 0)
{
use_scan_command = TRUE;
continue;
}
- /* XXX: If unsuccessful we should free this memory */
- this_clamd =
- (clamd_address_container *)store_get(sizeof(clamd_address_container));
+ cd = (clamd_address *) store_get(sizeof(clamd_address));
/* extract host and port part */
- if( sscanf(CS address, "%" MAX_CLAMD_ADDRESS_LENGTH_S "s %u",
- this_clamd->tcp_addr, &(this_clamd->tcp_port)) != 2 )
+ sublist = scanner_options;
+ if (!(cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0)))
{
(void) m_errlog_defer(scanent,
- string_sprintf("invalid address '%s'", address));
+ string_sprintf("missing address: '%s'", scanner_options));
continue;
}
+ if (!(s = string_nextinlist(&sublist, &subsep, NULL, 0)))
+ {
+ (void) m_errlog_defer(scanent,
+ string_sprintf("missing port: '%s'", scanner_options));
+ continue;
+ }
+ cd->tcp_port = atoi(s);
- clamd_address_vector[num_servers] = this_clamd;
- num_servers++;
+ /* parse options */
+ /*XXX should these options be common over scanner types? */
+ if (clamd_option(cd, sublist, &subsep) != OK)
+ {
+ return m_errlog_defer(scanent,
+ string_sprintf("bad option '%s'", scanner_options));
+ continue;
+ }
+
+ cv[num_servers++] = cd;
if (num_servers >= MAX_CLAMD_SERVERS)
{
(void) m_errlog_defer(scanent,
"specified; only using the first " MAX_CLAMD_SERVERS_S );
break;
}
- } while ((address = string_nextinlist(&av_scanner_work, &sep,
- address_buffer,
- sizeof(address_buffer))) != NULL);
+ } while ((scanner_options = string_nextinlist(&av_scanner_work, &sep,
+ NULL, 0)));
/* check if we have at least one server */
if (!num_servers)
while (num_servers > 0)
{
- int i;
- int current_server = random_number( num_servers );
+ int i = random_number( num_servers );
+ clamd_address * cd = cv[i];
- DEBUG(D_acl)
- debug_printf("trying server name %s, port %u\n",
- clamd_address_vector[current_server]->tcp_addr,
- clamd_address_vector[current_server]->tcp_port);
+ DEBUG(D_acl) debug_printf("trying server name %s, port %u\n",
+ cd->hostspec, cd->tcp_port);
/* Lookup the host. This is to ensure that we connect to the same IP
* on both connections (as one host could resolve to multiple ips) */
- sock= m_tcpsocket(clamd_address_vector[current_server]->tcp_addr,
- clamd_address_vector[current_server]->tcp_port,
- &connhost, &errstr);
- if (sock >= 0)
+ for (;;)
{
- /* Connection successfully established with a server */
- hostname = clamd_address_vector[current_server]->tcp_addr;
- break;
+ sock= m_tcpsocket(cd->hostspec, cd->tcp_port, &connhost, &errstr);
+ if (sock >= 0)
+ {
+ /* Connection successfully established with a server */
+ hostname = cd->hostspec;
+ break;
+ }
+ if (cd->retry <= 0) break;
+ while (cd->retry > 0) cd->retry = sleep(cd->retry);
}
+ if (sock >= 0)
+ break;
log_write(0, LOG_MAIN, "malware acl condition: %s: %s",
scanent->name, errstr);
/* Remove the server from the list. XXX We should free the memory */
num_servers--;
- for (i = current_server; i < num_servers; i++)
- clamd_address_vector[i] = clamd_address_vector[i+1];
+ for (; i < num_servers; i++)
+ cv[i] = cv[i+1];
}
if (num_servers == 0)
return m_errlog_defer(scanent, US"all servers failed");
}
else
- {
- if ((sock = ip_unixsocket(scanner_options, &errstr)) < 0)
- return m_errlog_defer(scanent, errstr);
- }
+ for (;;)
+ {
+ if ((sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0)
+ {
+ hostname = cv[0]->hostspec;
+ break;
+ }
+ if (cv[0]->retry <= 0)
+ return m_errlog_defer(scanent, errstr);
+ while (cv[0]->retry > 0) cv[0]->retry = sleep(cv[0]->retry);
+ }
/* have socket in variable "sock"; command to use is semi-independent of
* the socket protocol. We use SCAN if is local (either Unix/local
int spam_rc = 0;
uschar *prev_spamd_address_work = NULL;
-static int timeout_sec;
static const uschar * loglabel = US"spam acl condition:";
spamd_param_init(spamd_address_container *spamd)
{
/* default spamd server weight, time and backup value */
-spamd->weight = SPAMD_WEIGHT;
spamd->is_failed = FALSE;
spamd->is_backup = FALSE;
+spamd->weight = SPAMD_WEIGHT;
+spamd->timeout = SPAMD_TIMEOUT;
+spamd->retry = 0;
return 0;
}
{
static int timesinceday = -1;
const uschar * s;
+const uschar * name;
/* check backup parameter */
if (Ustrcmp(param, "backup") == 0)
unsigned int time_start, time_end;
const uschar * end_string;
+ name = US"time";
s = param+5;
if ((end_string = Ustrchr(s, '-')))
{
if ( sscanf(CS end_string, "%u.%u.%u", &end_h, &end_m, &end_s) == 0
|| sscanf(CS s, "%u.%u.%u", &start_h, &start_m, &start_s) == 0
)
- {
- log_write(0, LOG_MAIN,
- "%s warning - invalid spamd time value: '%s'", loglabel, s);
- return -1; /* syntax error */
- }
+ goto badval;
}
else
- {
- log_write(0, LOG_MAIN,
- "%s warning - invalid spamd time value: '%s'", loglabel, s);
- return -1; /* syntax error */
- }
+ goto badval;
if (timesinceday < 0)
{
if (Ustrncmp(param, "tmo=", 4) == 0)
{
int sec = readconf_readtime((s = param+4), '\0', FALSE);
+ name = US"timeout";
if (sec < 0)
- {
- log_write(0, LOG_MAIN,
- "%s warning - invalid spamd timeout value: '%s'", loglabel, s);
- return -1; /* syntax error */
- }
- timeout_sec = sec;
+ goto badval;
+ spamd->timeout = sec;
+ return 0;
+ }
+
+if (Ustrncmp(param, "retry=", 6) == 0)
+ {
+ int sec = readconf_readtime((s = param+6), '\0', FALSE);
+ name = US"retry";
+ if (sec < 0)
+ goto badval;
+ spamd->retry = sec;
return 0;
}
log_write(0, LOG_MAIN, "%s warning - invalid spamd parameter: '%s'",
loglabel, param);
return -1; /* syntax error */
+
+badval:
+ log_write(0, LOG_MAIN,
+ "%s warning - invalid spamd %s value: '%s'", loglabel, name, s);
+ return -1; /* syntax error */
}
int spamd_sock = -1;
uschar spamd_buffer[32600];
int i, j, offset, result;
-BOOL is_rspamd;
uschar spamd_version[8];
uschar spamd_short_result[8];
uschar spamd_score_char;
fd_set select_fd;
#endif
uschar *spamd_address_work;
+spamd_address_container * sd;
/* stop compiler warning */
result = 0;
(strcmpic(user_name,US"false") == 0) )
return FAIL;
-timeout_sec = SPAMD_TIMEOUT;
-
/* if there is an additional option, check if it is "true" */
if (strcmpic(list,US"true") == 0)
/* in that case, always return true later */
uschar *address;
const uschar *spamd_address_list_ptr = spamd_address_work;
spamd_address_container * spamd_address_vector[32];
- spamd_address_container * sd;
/* Check how many spamd servers we have
and register their addresses */
args++
)
{
- HDEBUG(D_acl) debug_printf("spamd: addr parm '%s'\n", s);
+ HDEBUG(D_acl) debug_printf("spamd: addr parm '%s'\n", s);
switch (args)
{
case 0: sd->hostspec = s;
goto defer;
}
- while (1)
+ current_server = spamd_get_server(spamd_address_vector, num_servers);
+ sd = spamd_address_vector[current_server];
+ for(;;)
{
uschar * errstr;
- current_server = spamd_get_server(spamd_address_vector, num_servers);
- sd = spamd_address_vector[current_server];
-
debug_printf("trying server %s\n", sd->hostspec);
- /* contact a spamd */
- if ((spamd_sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0)
+ for (;;)
+ {
+ if ( (spamd_sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0
+ || sd->retry <= 0
+ )
+ break;
+ debug_printf("server %s: retry conn\n", sd->hostspec);
+ while (sd->retry > 0) sd->retry = sleep(sd->retry);
+ }
+ if (spamd_sock >= 0)
break;
log_write(0, LOG_MAIN, "%s spamd: %s", loglabel, errstr);
current_server = spamd_get_server(spamd_address_vector, num_servers);
if (current_server < 0)
{
- log_write(0, LOG_MAIN|LOG_PANIC, "%s all spamd servers failed",
- loglabel);
+ log_write(0, LOG_MAIN|LOG_PANIC, "%s all spamd servers failed", loglabel);
goto defer;
}
+ sd = spamd_address_vector[current_server];
}
- is_rspamd = sd->is_rspamd;
}
if (spamd_sock == -1)
(void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
/* now we are connected to spamd on spamd_sock */
-if (is_rspamd)
+if (sd->is_rspamd)
{ /* rspamd variant */
uschar *req_str;
const char *helo;
"%s %s on spamd socket", loglabel, strerror(errno));
else
{
- if (time(NULL) - start < timeout_sec)
+ if (time(NULL) - start < sd->timeout)
goto again;
log_write(0, LOG_MAIN|LOG_PANIC,
"%s timed out writing spamd socket", loglabel);
while ((i = ip_recv(spamd_sock,
spamd_buffer + offset,
sizeof(spamd_buffer) - offset - 1,
- timeout_sec - time(NULL) + start)) > 0 )
+ sd->timeout - time(NULL) + start)) > 0 )
offset += i;
/* error handling */
/* reading done */
(void)close(spamd_sock);
-if (is_rspamd)
+if (sd->is_rspamd)
{ /* rspamd variant of reply */
int r;
if ((r = sscanf(CS spamd_buffer,
int is_failed:1;
int is_backup:1;
unsigned int weight;
+ unsigned int timeout;
+ unsigned int retry;
} spamd_address_container;
#endif
-d causes the server to output debugging information
- -t sets a timeout in seconds (default 5) for when the server is
+ -t <sec> sets a timeout (default 5) for when the server is
awaiting an incoming connection
-noipv4 causes the server not to set up an IPv4 socket
-noipv6 causes the server not to set up an IPv6 socket
+ -i <sec> sets an initial pause, to delay before creating the listen sockets
+
By default, in an IPv6 environment, both kinds of socket are set up. However,
the test script knows which interfaces actually exist on the host, and it adds
-noipv4 or -noipv6 to the server command as required. An error occurs if both
# Exim test configuration 4005
# Content-scan: clamav interface
+OPT=
+CONTROL=
+
exim_path = EXIM_PATH
host_lookup_order = bydns
primary_hostname = myhost.test.ex
gecos_name = CALLER_NAME
log_selector = +subject
-av_scanner = clamd : DIR/eximdir/clam_sock
+#XXX we need an additional test for tcp-connected clamd
+av_scanner = clamd : DIR/eximdir/clam_sock CONTROL
# ----- Main settings -----
# Exim test configuration 4009
# Content-scan: spamassassin interface
+OPT=
+
exim_path = EXIM_PATH
host_lookup_order = bydns
primary_hostname = myhost.test.ex
gecos_name = CALLER_NAME
log_selector = +subject
-spamd_address = 127.0.0.1 7833
+spamd_address = 127.0.0.1 7833 OPT
# ----- Main settings -----
1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local-esmtp S=sss T="accept this one despite timeout"
1999-03-02 09:44:33 10HmaZ-0005vi-00 => :blackhole: <userx@test.ex> R=r
1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local-esmtp S=sss T="message should be accepted after a retry"
+1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <userx@test.ex> R=r
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local-esmtp S=sss
1999-03-02 09:44:33 10HmaX-0005vi-00 => :blackhole: <userx@test.ex> R=r
1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaY-0005vi-00 U=CALLER Warning: no action Spam detection software, running on the system "demo",\n has NOT identified this incoming email as spam. The original\n message has been attached to this so you can view it or label\n similar future email. If you have any questions, see\n @@CONTACT_ADDRESS@@ for details.\n \n Content preview: test [...]\n \n Content analysis details: (4.5 points, 5.0 required)\n \n pts rule name description\n ---- ---------------------- --------------------------------------------------\n -1.0 ALL_TRUSTED Passed through trusted hosts only via SMTP\n 1.2 MISSING_HEADERS Missing To: header\n 1.0 MISSING_FROM Missing From: header\n 1.8 MISSING_SUBJECT Missing Subject: header\n 1.4 MISSING_DATE Missing Date: header\n 0.1 MISSING_MID Missing Message-Id: header
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local-esmtp S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <userx@test.ex> R=r
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaZ-0005vi-00 U=CALLER Warning: no action Spam detection software, running on the system "demo",\n has NOT identified this incoming email as spam. The original\n message has been attached to this so you can view it or label\n similar future email. If you have any questions, see\n @@CONTACT_ADDRESS@@ for details.\n \n Content preview: test [...]\n \n Content analysis details: (4.5 points, 5.0 required)\n \n pts rule name description\n ---- ---------------------- --------------------------------------------------\n -1.0 ALL_TRUSTED Passed through trusted hosts only via SMTP\n 1.2 MISSING_HEADERS Missing To: header\n 1.0 MISSING_FROM Missing From: header\n 1.8 MISSING_SUBJECT Missing Subject: header\n 1.4 MISSING_DATE Missing Date: header\n 0.1 MISSING_MID Missing Message-Id: header
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local-esmtp S=sss
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => :blackhole: <userx@test.ex> R=r
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
****
#
#
-# Need to additionally test the timeout / defer_ok case
+#
+#
+server -i 2 DIR/eximdir/clam_sock
+<SCAN
+>LF>scanned_file_name: OK
+<*eof
+****
+#
+exim -odi -bs -DCONTROL="retry=4s"
+ehlo test.ex
+mail from:<>
+rcpt to:<userx@test.ex>
+data
+Date: Fri, 17 Dec 2004 14:35:01 +0100
+Subject: message should be accepted after a retry
+
+.
+quit
+****
+#
# content scan interface: spamassassin
+#
+# A good-comms test, returning not-spam.
+# (we could use a second one that returns is-spam...)
server 7833
<REPORT SPAMC
<User:
.
quit
****
+#
+#
+#
+#
+# Server spec line with timeout option, not exercised
+# (could we cut down the massive content?)
+server 7833
+<REPORT SPAMC
+<User:
+<Content-length:
+<
+<From
+<X-Envelope-From
+<X-Envelope-To
+<Received:
+< by
+< (envelope
+< id
+< for
+<Content-type: text/plain
+<Message-Id:
+<From:
+<Date:
+<
+<test
+>SPAMD/1.1 0 EX_OK
+>Spam: False ; 4.5 / 5.0
+>
+>Spam detection software, running on the system "demo",
+>has NOT identified this incoming email as spam. The original
+>message has been attached to this so you can view it or label
+>similar future email. If you have any questions, see
+>@@CONTACT_ADDRESS@@ for details.
+>
+>Content preview: test [...]
+>
+>Content analysis details: (4.5 points, 5.0 required)
+>
+> pts rule name description
+>---- ---------------------- --------------------------------------------------
+>-1.0 ALL_TRUSTED Passed through trusted hosts only via SMTP
+> 1.2 MISSING_HEADERS Missing To: header
+> 1.0 MISSING_FROM Missing From: header
+> 1.8 MISSING_SUBJECT Missing Subject: header
+> 1.4 MISSING_DATE Missing Date: header
+> 0.1 MISSING_MID Missing Message-Id: header
+>
+*eof
+****
+exim -odi -bs -DOPT='retry=10s'
+ehlo test.ex
+mail from:<>
+rcpt to:<userx@test.ex>
+data
+Content-type: text/plain
+
+test
+.
+quit
+****
+#
+#
+#
+# Server spec line with timeout option, exercised
+server -i 2 7833
+<REPORT SPAMC
+<User:
+<Content-length:
+<
+<From
+<X-Envelope-From
+<X-Envelope-To
+<Received:
+< by
+< (envelope
+< id
+< for
+<Content-type: text/plain
+<Message-Id:
+<From:
+<Date:
+<
+<test
+>SPAMD/1.1 0 EX_OK
+>Spam: False ; 4.5 / 5.0
+>
+>Spam detection software, running on the system "demo",
+>has NOT identified this incoming email as spam. The original
+>message has been attached to this so you can view it or label
+>similar future email. If you have any questions, see
+>@@CONTACT_ADDRESS@@ for details.
+>
+>Content preview: test [...]
+>
+>Content analysis details: (4.5 points, 5.0 required)
+>
+> pts rule name description
+>---- ---------------------- --------------------------------------------------
+>-1.0 ALL_TRUSTED Passed through trusted hosts only via SMTP
+> 1.2 MISSING_HEADERS Missing To: header
+> 1.0 MISSING_FROM Missing From: header
+> 1.8 MISSING_SUBJECT Missing Subject: header
+> 1.4 MISSING_DATE Missing Date: header
+> 0.1 MISSING_MID Missing Message-Id: header
+>
+*eof
+****
+exim -odi -bs -DOPT='retry=4s'
+ehlo test.ex
+mail from:<>
+rcpt to:<userx@test.ex>
+data
+Content-type: text/plain
+
+test
+.
+quit
+****
+#
+#
int count;
int on = 1;
int timeout = 5;
+int initial_pause = 0;
int use_ipv4 = 1;
int use_ipv6 = 1;
int debug = 0;
{
if (strcmp(argv[na], "-d") == 0) debug = 1;
else if (strcmp(argv[na], "-t") == 0) timeout = atoi(argv[++na]);
+ else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
else
if (na < argc) connection_count = atoi(argv[na]);
+/* Initial pause (before creating listen sockets */
+if (initial_pause > 0)
+ {
+ if (debug)
+ printf("%d: Inital pause of %d seconds\n", time(NULL), initial_pause);
+ else
+ printf("Inital pause of %d seconds\n", initial_pause);
+ while (initial_pause > 0)
+ initial_pause = sleep(initial_pause);
+ }
+
/* Create sockets */
if (port == 0) /* Unix domain */
{
- if (debug) printf("Creating Unix domain socket\n");
+ if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
if (listen_socket[udn] < 0)
{
354 Enter message, ending with "." on a line by itself\r
250 OK id=10HmaZ-0005vi-00\r
221 myhost.test.ex closing connection\r
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000\r
+250-myhost.test.ex Hello CALLER at test.ex\r
+250-SIZE 52428800\r
+250-8BITMIME\r
+250-PIPELINING\r
+250 HELP\r
+250 OK\r
+250 Accepted\r
+354 Enter message, ending with "." on a line by itself\r
+250 OK id=10HmbC-0005vi-00\r
+221 myhost.test.ex closing connection\r
******** SERVER ********
Listening on TESTSUITE/eximdir/clam_sock ...
Connection request
*sleep 3
End of script
+Inital pause of 2 seconds
+Listening on TESTSUITE/eximdir/clam_sock ...
+Connection request
+<SCAN TESTSUITE/spool/scan/10HmbC-0005vi-00/10HmbC-0005vi-00.eml
+>LF>scanned_file_name: OK
+Unexpected EOF read from client
+End of script
354 Enter message, ending with "." on a line by itself\r
250 OK id=10HmaX-0005vi-00\r
221 myhost.test.ex closing connection\r
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000\r
+250-myhost.test.ex Hello CALLER at test.ex\r
+250-SIZE 52428800\r
+250-8BITMIME\r
+250-PIPELINING\r
+250 HELP\r
+250 OK\r
+250 Accepted\r
+354 Enter message, ending with "." on a line by itself\r
+250 OK id=10HmaY-0005vi-00\r
+221 myhost.test.ex closing connection\r
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000\r
+250-myhost.test.ex Hello CALLER at test.ex\r
+250-SIZE 52428800\r
+250-8BITMIME\r
+250-PIPELINING\r
+250 HELP\r
+250 OK\r
+250 Accepted\r
+354 Enter message, ending with "." on a line by itself\r
+250 OK id=10HmaZ-0005vi-00\r
+221 myhost.test.ex closing connection\r
******** SERVER ********
Listening on port 7833 ...
>
Expected EOF read from client
End of script
+Listening on port 7833 ...
+Connection request from [127.0.0.1]
+<REPORT SPAMC/1.2
+<User: nobody
+<Content-length: 479
+<
+<From MAILER-DAEMON Tue Mar 02 09:44:33 1999
+<X-Envelope-From: <CALLER@myhost.test.ex>
+<X-Envelope-To: userx@test.ex
+<Received: from CALLER (helo=test.ex)
+< by myhost.test.ex with local-esmtp (Exim x.yz)
+< (envelope-from <CALLER@myhost.test.ex>)
+< id 10HmaY-0005vi-00
+< for userx@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+<Content-type: text/plain
+<Message-Id: <E10HmaY-0005vi-00@myhost.test.ex>
+<From: CALLER_NAME <CALLER@myhost.test.ex>
+<Date: Tue, 2 Mar 1999 09:44:33 +0000
+<
+<test
+>SPAMD/1.1 0 EX_OK
+>Spam: False ; 4.5 / 5.0
+>
+>Spam detection software, running on the system "demo",
+>has NOT identified this incoming email as spam. The original
+>message has been attached to this so you can view it or label
+>similar future email. If you have any questions, see
+>@@CONTACT_ADDRESS@@ for details.
+>
+>Content preview: test [...]
+>
+>Content analysis details: (4.5 points, 5.0 required)
+>
+> pts rule name description
+>---- ---------------------- --------------------------------------------------
+>-1.0 ALL_TRUSTED Passed through trusted hosts only via SMTP
+> 1.2 MISSING_HEADERS Missing To: header
+> 1.0 MISSING_FROM Missing From: header
+> 1.8 MISSING_SUBJECT Missing Subject: header
+> 1.4 MISSING_DATE Missing Date: header
+> 0.1 MISSING_MID Missing Message-Id: header
+>
+Expected EOF read from client
+End of script
+Inital pause of 2 seconds
+Listening on port 7833 ...
+Connection request from [127.0.0.1]
+<REPORT SPAMC/1.2
+<User: nobody
+<Content-length: 479
+<
+<From MAILER-DAEMON Tue Mar 02 09:44:33 1999
+<X-Envelope-From: <CALLER@myhost.test.ex>
+<X-Envelope-To: userx@test.ex
+<Received: from CALLER (helo=test.ex)
+< by myhost.test.ex with local-esmtp (Exim x.yz)
+< (envelope-from <CALLER@myhost.test.ex>)
+< id 10HmaZ-0005vi-00
+< for userx@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+<Content-type: text/plain
+<Message-Id: <E10HmaZ-0005vi-00@myhost.test.ex>
+<From: CALLER_NAME <CALLER@myhost.test.ex>
+<Date: Tue, 2 Mar 1999 09:44:33 +0000
+<
+<test
+>SPAMD/1.1 0 EX_OK
+>Spam: False ; 4.5 / 5.0
+>
+>Spam detection software, running on the system "demo",
+>has NOT identified this incoming email as spam. The original
+>message has been attached to this so you can view it or label
+>similar future email. If you have any questions, see
+>@@CONTACT_ADDRESS@@ for details.
+>
+>Content preview: test [...]
+>
+>Content analysis details: (4.5 points, 5.0 required)
+>
+> pts rule name description
+>---- ---------------------- --------------------------------------------------
+>-1.0 ALL_TRUSTED Passed through trusted hosts only via SMTP
+> 1.2 MISSING_HEADERS Missing To: header
+> 1.0 MISSING_FROM Missing From: header
+> 1.8 MISSING_SUBJECT Missing Subject: header
+> 1.4 MISSING_DATE Missing Date: header
+> 0.1 MISSING_MID Missing Message-Id: header
+>
+Expected EOF read from client
+End of script