Add retry option to clamd and spamd. Bug 392
authorJeremy Harris <jgh146exb@wizmail.org>
Mon, 2 Feb 2015 00:11:05 +0000 (00:11 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Mon, 9 Feb 2015 16:00:55 +0000 (16:00 +0000)
16 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
src/src/ip.c
src/src/malware.c
src/src/spam.c
src/src/spam.h
test/README
test/confs/4005
test/confs/4009
test/log/4005
test/log/4009
test/scripts/4000-scanning/4005
test/scripts/4000-scanning/4009
test/src/server.c
test/stdout/4005
test/stdout/4009

index b5133a20140e613cda22ca3467add8ba8e4e6ed5..dfe0432c66c70d85d91db631de0e7adfd81ac788 100644 (file)
@@ -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
index 7c5c7c87e692efba07ca03eedbb9580c3edd3eaf..c2959d32c92ef3009968572053e28c9f8f2153b5 100644 (file)
@@ -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
index e4a43e69a9820888ce2dd5fc30a0739d93884178..f6072c2e80019e4df87e34f3d490f956bb8c39b9 100644 (file)
@@ -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
+*/
index 2e49751c10275392a5b26712709b20264e1d7131..c13e70616da0b783081eab4ce975ac578e15cd21 100644 (file)
@@ -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
index 99e524d3ca18df7872534558246b64932bec1d7d..96780f59e0ba94a9d3d221c84018d5744bdcf788 100644 (file)
@@ -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,
index 7ab6d2a586d986595e8e32e5c511c67af4f27c4f..0e3acfc25a396fc3ce044fdf5d3844a1965aef1b 100644 (file)
@@ -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
index 80c35117b4de9f0605df5de522563a19cfa3954a..e544857889535c0c54a6f5e6bd793742263d4e9e 100644 (file)
@@ -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
index 8ed28d4a62111f4bec52f181f99f3078fce19200..fd15dfc04f49c83cf092f36eefea4f01717944a3 100644 (file)
@@ -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 -----
 
index b635195b61c60c2cd55444365852347e57c04b33..573aa6a4aa8c957f06defc74dd6916aa19dd9b4d 100644 (file)
@@ -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 -----
 
index 7a4bb1cd08137c783aadfc89275074b72aebc494..821bed13271d3054e68f9d7c78f6c85a26e41c83 100644 (file)
@@ -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
index 0aa7ba3ce564a3b97f07fe0f3ff45c305f36510a..4522ddb431032bb891f167724bced2989396883d 100644 (file)
@@ -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
index 0095157d14b6aaadaa28eb9f31314799d418b228..d251c1a1f626b46cd922532d387c6c6b25058dbc 100644 (file)
@@ -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
+****
+#
index 8e34c9802168a332abf0ae887c5767e45ae3a2e6..4c2ab81c6498bf6d74d765cddcead7db34b0f234 100644 (file)
@@ -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
+****
+#
+#
index 0d6e5fe907f7fc44ac3b254749cc1449265a559b..f4173ecd86976f476ea51089cc104e46cd363619 100644 (file)
@@ -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)
     {
index 0a8a5ea9a2c66c860addcf31cb9bf65f05f3dada..4d858c5b11271d25149b64ddaa8a7e50143ea0de 100644 (file)
 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 ... 
@@ -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
index a1d7f2ea71e13cc3be5375b0be6bfd229a6e8d25..9220c7d0e8546ed9a08b9f4d56bdd5d132d31a66 100644 (file)
@@ -9,6 +9,28 @@
 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 ... 
@@ -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