summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/doc-docbook/spec.xfpt48
-rw-r--r--doc/doc-txt/ChangeLog3
-rw-r--r--src/src/ip.c5
-rw-r--r--src/src/malware.c223
-rw-r--r--src/src/spam.c82
-rw-r--r--src/src/spam.h2
-rw-r--r--test/README4
-rw-r--r--test/confs/40056
-rw-r--r--test/confs/40094
-rw-r--r--test/log/40053
-rw-r--r--test/log/40098
-rw-r--r--test/scripts/4000-scanning/400521
-rw-r--r--test/scripts/4000-scanning/4009123
-rw-r--r--test/src/server.c15
-rw-r--r--test/stdout/400518
-rw-r--r--test/stdout/4009111
16 files changed, 544 insertions, 132 deletions
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index b5133a201..dfe0432c6 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -30470,17 +30470,39 @@ av_scanner = aveserver:/var/run/aveserver
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.
@@ -30731,8 +30753,7 @@ deny message = This message contains malware ($malware_name)
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
@@ -30802,9 +30823,10 @@ The supported option are:
.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>
@@ -30812,10 +30834,16 @@ in the local time zone; each element being one or more digits.
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
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index 7c5c7c87e..c2959d32c 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -67,6 +67,9 @@ JH/17 Bug 68: The spamd_address main option now supports an optional
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
diff --git a/src/src/ip.c b/src/src/ip.c
index e4a43e69a..f6072c2e8 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -337,7 +337,8 @@ for (h = &shost; h != NULL; h = h->next)
{
if (fd != fd6) close(fd6);
if (fd != fd4) close(fd4);
- if (connhost) {
+ if (connhost)
+ {
h->port = port;
*connhost = *h;
connhost->next = NULL;
@@ -720,3 +721,5 @@ for (i=0; i < dscp_table_size; ++i)
/* End of ip.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/malware.c b/src/src/malware.c
index 2e49751c1..c13e70616 100644
--- a/src/src/malware.c
+++ b/src/src/malware.c
@@ -38,14 +38,12 @@ static struct scan
/* 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]))
@@ -115,21 +113,21 @@ extern uschar spooled_message_id[17];
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);
}
/*************************************************/
@@ -141,60 +139,61 @@ static inline int
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;
}
/*
@@ -375,6 +374,27 @@ if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer), tmo) < 0)
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 *
*************************************************/
@@ -1205,7 +1225,7 @@ if (!malware_ok)
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;
@@ -1215,46 +1235,80 @@ if (!malware_ok)
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,
@@ -1262,9 +1316,8 @@ if (!malware_ok)
"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)
@@ -1288,43 +1341,53 @@ if (!malware_ok)
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
diff --git a/src/src/spam.c b/src/src/spam.c
index 99e524d3c..96780f59e 100644
--- a/src/src/spam.c
+++ b/src/src/spam.c
@@ -21,7 +21,6 @@ int spam_ok = 0;
int spam_rc = 0;
uschar *prev_spamd_address_work = NULL;
-static int timeout_sec;
static const uschar * loglabel = US"spam acl condition:";
@@ -29,9 +28,11 @@ static int
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;
}
@@ -41,6 +42,7 @@ spamd_param(const uschar *param, spamd_address_container *spamd)
{
static int timesinceday = -1;
const uschar * s;
+const uschar * name;
/* check backup parameter */
if (Ustrcmp(param, "backup") == 0)
@@ -67,6 +69,7 @@ if (Ustrncmp(param, "time=", 5) == 0)
unsigned int time_start, time_end;
const uschar * end_string;
+ name = US"time";
s = param+5;
if ((end_string = Ustrchr(s, '-')))
{
@@ -74,18 +77,10 @@ if (Ustrncmp(param, "time=", 5) == 0)
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)
{
@@ -112,19 +107,31 @@ if (Ustrcmp(param, "variant=rspamd") == 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 */
}
@@ -188,7 +195,6 @@ FILE *mbox_file;
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;
@@ -206,6 +212,7 @@ struct timeval select_tv; /* and applied by PH */
fd_set select_fd;
#endif
uschar *spamd_address_work;
+spamd_address_container * sd;
/* stop compiler warning */
result = 0;
@@ -224,8 +231,6 @@ if ( (Ustrcmp(user_name,"0") == 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 */
@@ -278,7 +283,6 @@ start = time(NULL);
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 */
@@ -298,7 +302,7 @@ start = time(NULL);
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;
@@ -331,17 +335,24 @@ start = time(NULL);
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);
@@ -350,12 +361,11 @@ start = time(NULL);
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)
@@ -367,7 +377,7 @@ 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;
@@ -449,7 +459,7 @@ again:
"%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);
@@ -494,7 +504,7 @@ offset = 0;
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 */
@@ -509,7 +519,7 @@ if (i <= 0 && errno != 0)
/* 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,
diff --git a/src/src/spam.h b/src/src/spam.h
index 7ab6d2a58..0e3acfc25 100644
--- a/src/src/spam.h
+++ b/src/src/spam.h
@@ -30,6 +30,8 @@ typedef struct spamd_address_container
int is_failed:1;
int is_backup:1;
unsigned int weight;
+ unsigned int timeout;
+ unsigned int retry;
} spamd_address_container;
#endif
diff --git a/test/README b/test/README
index 80c35117b..e54485788 100644
--- a/test/README
+++ b/test/README
@@ -897,13 +897,15 @@ input, details of which are given below. A number of options are implemented:
-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
diff --git a/test/confs/4005 b/test/confs/4005
index 8ed28d4a6..fd15dfc04 100644
--- a/test/confs/4005
+++ b/test/confs/4005
@@ -1,6 +1,9 @@
# Exim test configuration 4005
# Content-scan: clamav interface
+OPT=
+CONTROL=
+
exim_path = EXIM_PATH
host_lookup_order = bydns
primary_hostname = myhost.test.ex
@@ -10,7 +13,8 @@ gecos_pattern = ""
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 -----
diff --git a/test/confs/4009 b/test/confs/4009
index b635195b6..573aa6a4a 100644
--- a/test/confs/4009
+++ b/test/confs/4009
@@ -1,6 +1,8 @@
# Exim test configuration 4009
# Content-scan: spamassassin interface
+OPT=
+
exim_path = EXIM_PATH
host_lookup_order = bydns
primary_hostname = myhost.test.ex
@@ -10,7 +12,7 @@ gecos_pattern = ""
gecos_name = CALLER_NAME
log_selector = +subject
-spamd_address = 127.0.0.1 7833
+spamd_address = 127.0.0.1 7833 OPT
# ----- Main settings -----
diff --git a/test/log/4005 b/test/log/4005
index 7a4bb1cd0..821bed132 100644
--- a/test/log/4005
+++ b/test/log/4005
@@ -11,3 +11,6 @@
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
diff --git a/test/log/4009 b/test/log/4009
index 0aa7ba3ce..4522ddb43 100644
--- a/test/log/4009
+++ b/test/log/4009
@@ -2,3 +2,11 @@
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
diff --git a/test/scripts/4000-scanning/4005 b/test/scripts/4000-scanning/4005
index 0095157d1..d251c1a1f 100644
--- a/test/scripts/4000-scanning/4005
+++ b/test/scripts/4000-scanning/4005
@@ -107,4 +107,23 @@ quit
****
#
#
-# 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
+****
+#
diff --git a/test/scripts/4000-scanning/4009 b/test/scripts/4000-scanning/4009
index 8e34c9802..4c2ab81c6 100644
--- a/test/scripts/4000-scanning/4009
+++ b/test/scripts/4000-scanning/4009
@@ -1,4 +1,7 @@
# 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:
@@ -53,3 +56,123 @@ test
.
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
+****
+#
+#
diff --git a/test/src/server.c b/test/src/server.c
index 0d6e5fe90..f4173ecd8 100644
--- a/test/src/server.c
+++ b/test/src/server.c
@@ -143,6 +143,7 @@ int connection_count = 1;
int count;
int on = 1;
int timeout = 5;
+int initial_pause = 0;
int use_ipv4 = 1;
int use_ipv6 = 1;
int debug = 0;
@@ -180,6 +181,7 @@ while (na < argc && argv[na][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
@@ -213,11 +215,22 @@ na++;
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)
{
diff --git a/test/stdout/4005 b/test/stdout/4005
index 0a8a5ea9a..4d858c5b1 100644
--- a/test/stdout/4005
+++ b/test/stdout/4005
@@ -53,6 +53,17 @@
354 Enter message, ending with "." on a line by itself
250 OK id=10HmaZ-0005vi-00
221 myhost.test.ex closing connection
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmbC-0005vi-00
+221 myhost.test.ex closing connection
******** SERVER ********
Listening on TESTSUITE/eximdir/clam_sock ...
@@ -81,3 +92,10 @@ 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
diff --git a/test/stdout/4009 b/test/stdout/4009
index a1d7f2ea7..9220c7d0e 100644
--- a/test/stdout/4009
+++ b/test/stdout/4009
@@ -9,6 +9,28 @@
354 Enter message, ending with "." on a line by itself
250 OK id=10HmaX-0005vi-00
221 myhost.test.ex closing connection
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmaY-0005vi-00
+221 myhost.test.ex closing connection
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmaZ-0005vi-00
+221 myhost.test.ex closing connection
******** SERVER ********
Listening on port 7833 ...
@@ -55,3 +77,92 @@ Connection request from [127.0.0.1]
>
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