compiler quietening
[exim.git] / src / src / malware.c
index e74ba983004f179aaa1b6ea23b1ac4acdb174564..99fa1c347cf6f21d694fc02999dee4c9d067d2a0 100644 (file)
@@ -4,7 +4,7 @@
 
 /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
  * License: GPL
- * Copyright (c) The Exim Maintainers 2017 - 2018
+ * Copyright (c) The Exim Maintainers 2015 - 2018
  */
 
 /* Code for calling virus (malware) scanners. Called from acl.c. */
@@ -137,9 +137,11 @@ static const pcre * kav_re_inf = NULL;
 
 #ifndef DISABLE_MAL_AVAST
 static const uschar * ava_re_clean_str = US "(?!\\\\)\\t\\[\\+\\]";
-static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d\\.\\d\\t\\d\\s(.*)";
+static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d+\\.0\\t0\\s(.*)";
+static const uschar * ava_re_error_str = US "(?!\\\\)\\t\\[E\\]\\d+\\.0\\tError\\s\\d+\\s(.*)";
 static const pcre * ava_re_clean = NULL;
 static const pcre * ava_re_virus = NULL;
+static const pcre * ava_re_error = NULL;
 #endif
 
 #ifndef DISABLE_MAL_FFROT6D
@@ -176,27 +178,60 @@ extern int spool_mbox_ok;
 extern uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
 
 
+/* Some (currently avast only) use backslash escaped whitespace,
+this function undoes these escapes */
 
+static inline void
+unescape(uschar *p)
+{
+uschar *p0;
+for (; *p; ++p)
+  if (*p == '\\' && (isspace(p[1]) || p[1] == '\\'))
+    for (p0 = p; *p0; ++p0) *p0 = p0[1];
+}
+
+/* --- malware_*_defer --- */
 static inline int
-malware_errlog_defer(const uschar * str)
+malware_panic_defer(const uschar * str)
 {
 log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str);
 return DEFER;
 }
-
-static int
-m_errlog_defer(struct scan * scanent, const uschar * hostport,
+static inline int
+malware_log_defer(const uschar * str)
+{
+log_write(0, LOG_MAIN, "malware acl condition: %s", str);
+return DEFER;
+}
+/* --- m_*_defer --- */
+static inline int
+m_panic_defer(struct scan * scanent, const uschar * hostport,
   const uschar * str)
 {
-return malware_errlog_defer(string_sprintf("%s %s : %s",
+return malware_panic_defer(string_sprintf("%s %s : %s",
   scanent->name, hostport ? hostport : CUS"", str));
 }
-static int
-m_errlog_defer_3(struct scan * scanent, const uschar * hostport,
+static inline int
+m_log_defer(struct scan * scanent, const uschar * hostport,
+  const uschar * str)
+{
+return malware_log_defer(string_sprintf("%s %s : %s",
+  scanent->name, hostport ? hostport : CUS"", str));
+}
+/* --- m_*_defer_3 */
+static inline int
+m_panic_defer_3(struct scan * scanent, const uschar * hostport,
   const uschar * str, int fd_to_close)
 {
 (void) close(fd_to_close);
-return m_errlog_defer(scanent, hostport, str);
+return m_panic_defer(scanent, hostport, str);
+}
+static inline int
+m_log_defer_3(struct scan * scanent, const uschar * hostport,
+  const uschar * str, int fd_to_close)
+{
+(void) close(fd_to_close);
+return m_log_defer(scanent, hostport, str);
 }
 
 /*************************************************/
@@ -341,7 +376,7 @@ for (;;)
   while (i < 0 && errno == EINTR);
   if (i <= 0)
     {
-    (void) malware_errlog_defer(
+    (void) malware_panic_defer(
            US"unable to write to mksd UNIX socket (/var/run/mksd/socket)");
     return -1;
     }
@@ -373,7 +408,7 @@ do
   i = ip_recv(sock, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
   if (i <= 0)
     {
-    (void) malware_errlog_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
+    (void) malware_panic_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
     return -1;
     }
 
@@ -381,7 +416,7 @@ do
   /* offset == av_buffer_size -> buffer full */
   if (offset == av_buffer_size)
     {
-    (void) malware_errlog_defer(US"malformed reply received from mksd");
+    (void) malware_panic_defer(US"malformed reply received from mksd");
     return -1;
     }
   } while (av_buffer[offset-1] != '\n');
@@ -404,7 +439,7 @@ switch (*line)
   case 'A': /* ERR */
     if ((p = strchr (line, '\n')) != NULL)
       *p = '\0';
-    return m_errlog_defer(scanent, NULL,
+    return m_panic_defer(scanent, NULL,
       string_sprintf("scanner failed: %s", line));
 
   default: /* VIR */
@@ -422,7 +457,7 @@ switch (*line)
        return OK;
        }
       }
-    return m_errlog_defer(scanent, NULL,
+    return m_panic_defer(scanent, NULL,
       string_sprintf("malformed reply received: %s", line));
   }
 }
@@ -515,7 +550,7 @@ if (!malware_re)
 /* Ensure the eml mbox file is spooled up */
 
 if (!(mbox_file = spool_mbox(&mbox_size, scan_filename, &eml_filename)))
-  return malware_errlog_defer(US"error while creating mbox spool file");
+  return malware_panic_defer(US"error while creating mbox spool file");
 
 /* None of our current scanners need the mbox file as a stream (they use
 the name), so we can close it right away.  Get the directory too. */
@@ -535,20 +570,20 @@ if (  strcmpic(malware_re,US"true") == 0
   {
   if (  !malware_default_re
      && !(malware_default_re = m_pcre_compile(malware_regex_default, &errstr)))
-    return malware_errlog_defer(errstr);
+    return malware_panic_defer(errstr);
   malware_re = malware_regex_default;
   re = malware_default_re;
   }
 
 /* compile the regex, see if it works */
 else if (!(re = m_pcre_compile(malware_re, &errstr)))
-  return malware_errlog_defer(errstr);
+  return malware_panic_defer(errstr);
 
 /* if av_scanner starts with a dollar, expand it first */
 if (*av_scanner == '$')
   {
   if (!(av_scanner_work = expand_string(av_scanner)))
-    return malware_errlog_defer(
+    return malware_panic_defer(
         string_sprintf("av_scanner starts with $, but expansion failed: %s",
         expand_string_message));
 
@@ -564,14 +599,14 @@ if (!malware_ok)
   {
   /* find the scanner type from the av_scanner option */
   if (!(scanner_name = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
-    return malware_errlog_defer(US"av_scanner configuration variable is empty");
+    return malware_panic_defer(US"av_scanner configuration variable is empty");
   if (!timeout) timeout = MALWARE_TIMEOUT;
   tmo = time(NULL) + timeout;
 
   for (scanent = m_scans; ; scanent++)
     {
     if (!scanent->name)
-      return malware_errlog_defer(string_sprintf("unknown scanner type '%s'",
+      return malware_panic_defer(string_sprintf("unknown scanner type '%s'",
        scanner_name));
     if (strcmpic(scanner_name, US scanent->name) != 0)
       continue;
@@ -592,7 +627,7 @@ if (!malware_ok)
     default: /* compiler quietening */ break;
     }
     if (sock < 0)
-      return m_errlog_defer(scanent, CUS callout_address, errstr);
+      return m_panic_defer(scanent, CUS callout_address, errstr);
     break;
   }
 
@@ -623,7 +658,7 @@ if (!malware_ok)
 
       /* send scan request */
       if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       while ((len = recv_line(sock, buf, sizeof(buf), tmo)) >= 0)
        if (len > 0)
@@ -665,24 +700,24 @@ if (!malware_ok)
 
       if ((!fprot6d_re_virus && !(fprot6d_re_virus = m_pcre_compile(fprot6d_re_virus_str, &errstr)))
         || (!fprot6d_re_error && !(fprot6d_re_error = m_pcre_compile(fprot6d_re_error_str, &errstr))))
-        return malware_errlog_defer(errstr);
+        return malware_panic_defer(errstr);
 
       scanrequest = string_sprintf("SCAN FILE %s\n", eml_filename);
       DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s: %s\n",
         scanner_name, scanrequest);
 
       if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0)
-        return m_errlog_defer(scanent, CUS callout_address, errstr);
+        return m_panic_defer(scanent, CUS callout_address, errstr);
 
       bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
 
       if (bread <= 0)
-        return m_errlog_defer_3(scanent, CUS callout_address,
+        return m_panic_defer_3(scanent, CUS callout_address,
           string_sprintf("unable to read from socket (%s)", strerror(errno)),
           sock);
 
       if (bread == sizeof(av_buffer))
-        return m_errlog_defer_3(scanent, CUS callout_address,
+        return m_panic_defer_3(scanent, CUS callout_address,
           US"buffer too small", sock);
 
       av_buffer[bread] = '\0';
@@ -691,7 +726,7 @@ if (!malware_ok)
       m_sock_send(sock, US"QUIT\n", 5, 0);
 
       if ((e = m_pcre_exec(fprot6d_re_error, linebuffer)))
-        return m_errlog_defer_3(scanent, CUS callout_address,
+        return m_panic_defer_3(scanent, CUS callout_address,
           string_sprintf("scanner reported error (%s)", e), sock);
 
       if (!(malware_name = m_pcre_exec(fprot6d_re_virus, linebuffer)))
@@ -721,7 +756,7 @@ if (!malware_ok)
        {
        /* calc file size */
        if ((drweb_fd = open(CCS eml_filename, O_RDONLY)) == -1)
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't open spool file %s: %s",
              eml_filename, strerror(errno)),
            sock);
@@ -731,7 +766,7 @@ if (!malware_ok)
          int err;
 badseek:  err = errno;
          (void)close(drweb_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't seek spool file %s: %s",
              eml_filename, strerror(err)),
            sock);
@@ -740,7 +775,7 @@ badseek:  err = errno;
        if ((off_t)fsize_uint != fsize)
          {
          (void)close(drweb_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("seeking spool file %s, size overflow",
              eml_filename),
            sock);
@@ -759,7 +794,7 @@ badseek:  err = errno;
            (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0))
          {
          (void)close(drweb_fd);
-         return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+         return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
            "unable to send commands to socket (%s)", scanner_options),
            sock);
          }
@@ -767,7 +802,7 @@ badseek:  err = errno;
        if (!(drweb_fbuf = US malloc(fsize_uint)))
          {
          (void)close(drweb_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("unable to allocate memory %u for file (%s)",
              fsize_uint, eml_filename),
            sock);
@@ -778,7 +813,7 @@ badseek:  err = errno;
          int err = errno;
          (void)close(drweb_fd);
          free(drweb_fbuf);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't read spool file %s: %s",
              eml_filename, strerror(err)),
            sock);
@@ -789,7 +824,7 @@ badseek:  err = errno;
        if (send(sock, drweb_fbuf, fsize, 0) < 0)
          {
          free(drweb_fbuf);
-         return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+         return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
            "unable to send file body to socket (%s)", scanner_options),
            sock);
          }
@@ -807,19 +842,19 @@ badseek:  err = errno;
            (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) ||
            (send(sock, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
            (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0))
-         return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+         return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
            "unable to send commands to socket (%s)", scanner_options),
            sock);
        }
 
       /* wait for result */
       if (!recv_len(sock, &drweb_rc, sizeof(drweb_rc), tmo))
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
                    US"unable to read return code", sock);
       drweb_rc = ntohl(drweb_rc);
 
       if (!recv_len(sock, &drweb_vnum, sizeof(drweb_vnum), tmo))
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
                            US"unable to read the number of viruses", sock);
       drweb_vnum = ntohl(drweb_vnum);
 
@@ -843,14 +878,14 @@ badseek:  err = errno;
 
          /* read the size of report */
          if (!recv_len(sock, &drweb_slen, sizeof(drweb_slen), tmo))
-           return m_errlog_defer_3(scanent, CUS callout_address,
+           return m_panic_defer_3(scanent, CUS callout_address,
                              US"cannot read report size", sock);
          drweb_slen = ntohl(drweb_slen);
          tmpbuf = store_get(drweb_slen);
 
          /* read report body */
          if (!recv_len(sock, tmpbuf, drweb_slen, tmo))
-           return m_errlog_defer_3(scanent, CUS callout_address,
+           return m_panic_defer_3(scanent, CUS callout_address,
                              US"cannot read report string", sock);
          tmpbuf[drweb_slen] = '\0';
 
@@ -888,7 +923,7 @@ badseek:  err = errno;
         * DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR
         * and others are ignored */
        if (drweb_s)
-         return m_errlog_defer_3(scanent, CUS callout_address,
+         return m_panic_defer_3(scanent, CUS callout_address,
            string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s),
            sock);
 
@@ -910,7 +945,7 @@ badseek:  err = errno;
       recv_line(sock, buf, sizeof(buf), tmo);
 
       if (buf[0] != '2')               /* aveserver is having problems */
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
          string_sprintf("unavailable (Responded: %s).",
                          ((buf[0] != 0) ? buf : US "nothing") ),
          sock);
@@ -923,7 +958,7 @@ badseek:  err = errno;
       DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s %s\n",
        scanner_name, buf);
       if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       malware_name = NULL;
       result = 0;
@@ -934,7 +969,7 @@ badseek:  err = errno;
          break;
        if (buf[0] == '5')              /* aveserver is having problems */
          {
-         result = m_errlog_defer(scanent, CUS callout_address,
+         result = m_panic_defer(scanent, CUS callout_address,
             string_sprintf("unable to scan file %s (Responded: %s).",
                             eml_filename, buf));
          break;
@@ -948,14 +983,14 @@ badseek:  err = errno;
        }
 
       if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       /* read aveserver's greeting and see if it is ready (2xx greeting) */
       buf[0] = 0;
       recv_line(sock, buf, sizeof(buf), tmo);
 
       if (buf[0] != '2')               /* aveserver is having problems */
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
          string_sprintf("unable to quit dialogue (Responded: %s).",
                        ((buf[0] != 0) ? buf : US "nothing") ),
          sock);
@@ -990,12 +1025,12 @@ badseek:  err = errno;
        {
 
        if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
-         return m_errlog_defer(scanent, CUS callout_address, errstr);
+         return m_panic_defer(scanent, CUS callout_address, errstr);
 
        bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
        if (bread > 0) av_buffer[bread]='\0';
        if (bread < 0)
-         return m_errlog_defer_3(scanent, CUS callout_address,
+         return m_panic_defer_3(scanent, CUS callout_address,
            string_sprintf("unable to read answer %d (%s)", i, strerror(errno)),
            sock);
        for (j = 0; j < bread; j++)
@@ -1007,7 +1042,7 @@ badseek:  err = errno;
       file_name = string_sprintf("SCAN\t%s\n", eml_filename);
 
       if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       /* set up match */
       /* todo also SUSPICION\t */
@@ -1025,7 +1060,7 @@ badseek:  err = errno;
          errno = ETIMEDOUT;
          i =  av_buffer+sizeof(av_buffer)-p;
          if ((bread= ip_recv(sock, p, i-1, tmo-time(NULL))) < 0)
-           return m_errlog_defer_3(scanent, CUS callout_address,
+           return m_panic_defer_3(scanent, CUS callout_address,
              string_sprintf("unable to read result (%s)", strerror(errno)),
              sock);
 
@@ -1085,11 +1120,11 @@ badseek:  err = errno;
 
       /* send scan request */
       if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       /* wait for result */
       if (!recv_len(sock, tmpbuf, 2, tmo))
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
                            US"unable to read 2 bytes from socket.", sock);
 
       /* get errorcode from one nibble */
@@ -1097,14 +1132,14 @@ badseek:  err = errno;
       switch(kav_rc)
       {
       case 5: case 6: /* improper kavdaemon configuration */
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
                US"please reconfigure kavdaemon to NOT disinfect or remove infected files.",
                sock);
       case 1:
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
                US"reported 'scanning not completed' (code 1).", sock);
       case 7:
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
                US"reported 'kavdaemon damaged' (code 7).", sock);
       }
 
@@ -1126,7 +1161,7 @@ badseek:  err = errno;
          {
          /* read report size */
          if (!recv_len(sock, &kav_reportlen, 4, tmo))
-           return m_errlog_defer_3(scanent, CUS callout_address,
+           return m_panic_defer_3(scanent, CUS callout_address,
                  US"cannot read report size", sock);
 
          /* it's possible that avp returns av_buffer[1] == 1 but the
@@ -1187,19 +1222,19 @@ badseek:  err = errno;
       uschar *p;
 
       if (!cmdline_scanner)
-       return m_errlog_defer(scanent, NULL, errstr);
+       return m_panic_defer(scanent, NULL, errstr);
 
       /* find scanner output trigger */
       cmdline_trigger_re = m_pcre_nextinlist(&av_scanner_work, &sep,
                                "missing trigger specification", &errstr);
       if (!cmdline_trigger_re)
-       return m_errlog_defer(scanent, NULL, errstr);
+       return m_panic_defer(scanent, NULL, errstr);
 
       /* find scanner name regex */
       cmdline_regex_re = m_pcre_nextinlist(&av_scanner_work, &sep,
                          "missing virus name regex specification", &errstr);
       if (!cmdline_regex_re)
-       return m_errlog_defer(scanent, NULL, errstr);
+       return m_panic_defer(scanent, NULL, errstr);
 
       /* prepare scanner call; despite the naming, file_name holds a directory
       name which is documented as the value given to %s. */
@@ -1224,7 +1259,7 @@ badseek:  err = errno;
        {
        int err = errno;
        signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
-       return m_errlog_defer(scanent, NULL,
+       return m_panic_defer(scanent, NULL,
          string_sprintf("call (%s) failed: %s.", commandline, strerror(err)));
        }
       scanner_fd = fileno(scanner_out);
@@ -1236,7 +1271,7 @@ badseek:  err = errno;
        int err = errno;
        (void) pclose(scanner_out);
        signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
-       return m_errlog_defer(scanent, NULL, string_sprintf(
+       return m_panic_defer(scanent, NULL, string_sprintf(
            "opening scanner output file (%s) failed: %s.",
            file_name, strerror(err)));
        }
@@ -1252,7 +1287,7 @@ badseek:  err = errno;
            break;
          (void) pclose(scanner_out);
          signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
-         return m_errlog_defer(scanent, NULL, string_sprintf(
+         return m_panic_defer(scanent, NULL, string_sprintf(
              "unable to read from scanner (%s): %s",
              commandline, strerror(err)));
          }
@@ -1262,7 +1297,7 @@ badseek:  err = errno;
          /* short write */
          (void) pclose(scanner_out);
          signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
-         return m_errlog_defer(scanent, NULL, string_sprintf(
+         return m_panic_defer(scanent, NULL, string_sprintf(
            "short write on scanner output file (%s).", file_name));
          }
        putc('\n', scanner_record);
@@ -1277,7 +1312,7 @@ badseek:  err = errno;
       sep = pclose(scanner_out);
       signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
       if (sep != 0)
-         return m_errlog_defer(scanent, NULL, 
+         return m_panic_defer(scanent, NULL,
              sep == -1
              ? string_sprintf("running scanner failed: %s", strerror(sep))
              : string_sprintf("scanner returned error code: %d", sep));
@@ -1323,14 +1358,14 @@ badseek:  err = errno;
       if (  write(sock, file_name, Ustrlen(file_name)) < 0
         || write(sock, "\n", 1) != 1
         )
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
          string_sprintf("unable to write to UNIX socket (%s)", scanner_options),
          sock);
 
       /* wait for result */
       memset(av_buffer, 0, sizeof(av_buffer));
       if ((bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
          string_sprintf("unable to read from UNIX socket (%s)", scanner_options),
          sock);
 
@@ -1342,7 +1377,7 @@ badseek:  err = errno;
        malware_name = string_copy(&av_buffer[2]);
       }
       else if (!strncmp(CS av_buffer, "-1", 2))
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
                US"scanner reported error", sock);
       else /* all ok, no virus */
        malware_name = NULL;
@@ -1400,7 +1435,7 @@ badseek:  err = errno;
 
        /* parse options */
        if (clamd_option(cd, sublist, &subsep) != OK)
-         return m_errlog_defer(scanent, NULL,
+         return m_panic_defer(scanent, NULL,
            string_sprintf("bad option '%s'", scanner_options));
        cv[0] = cd;
        }
@@ -1432,13 +1467,13 @@ badseek:  err = errno;
          sublist = scanner_options;
          if (!(cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0)))
            {
-           (void) m_errlog_defer(scanent, NULL, 
+           (void) m_panic_defer(scanent, NULL,
                      string_sprintf("missing address: '%s'", scanner_options));
            continue;
            }
          if (!(s = string_nextinlist(&sublist, &subsep, NULL, 0)))
            {
-           (void) m_errlog_defer(scanent, NULL, 
+           (void) m_panic_defer(scanent, NULL,
                      string_sprintf("missing port: '%s'", scanner_options));
            continue;
            }
@@ -1447,13 +1482,13 @@ badseek:  err = errno;
          /* parse options */
          /*XXX should these options be common over scanner types? */
          if (clamd_option(cd, sublist, &subsep) != OK)
-           return m_errlog_defer(scanent, NULL,
+           return m_panic_defer(scanent, NULL,
              string_sprintf("bad option '%s'", scanner_options));
 
          cv[num_servers++] = cd;
          if (num_servers >= MAX_CLAMD_SERVERS)
            {
-           (void) m_errlog_defer(scanent, NULL,
+           (void) m_panic_defer(scanent, NULL,
                  US"More than " MAX_CLAMD_SERVERS_S " clamd servers "
                  "specified; only using the first " MAX_CLAMD_SERVERS_S );
            break;
@@ -1463,14 +1498,14 @@ badseek:  err = errno;
 
        /* check if we have at least one server */
        if (!num_servers)
-         return m_errlog_defer(scanent, NULL,
+         return m_panic_defer(scanent, NULL,
            US"no useable server addresses in malware configuration option.");
        }
 
       /* See the discussion of response formats below to see why we really
       don't like colons in filenames when passing filenames to ClamAV. */
       if (use_scan_command && Ustrchr(eml_filename, ':'))
-       return m_errlog_defer(scanent, NULL,
+       return m_panic_defer(scanent, NULL,
          string_sprintf("local/SCAN mode incompatible with" \
            " : in path to email filename [%s]", eml_filename));
 
@@ -1516,7 +1551,7 @@ badseek:  err = errno;
          if (sock >= 0)
            break;
 
-         (void) m_errlog_defer(scanent, CUS callout_address, errstr);
+         (void) m_panic_defer(scanent, CUS callout_address, errstr);
 
          /* Remove the server from the list. XXX We should free the memory */
          num_servers--;
@@ -1525,7 +1560,7 @@ badseek:  err = errno;
          }
 
        if (num_servers == 0)
-         return m_errlog_defer(scanent, NULL, US"all servers failed");
+         return m_panic_defer(scanent, NULL, US"all servers failed");
        }
       else
        for (;;)
@@ -1536,7 +1571,7 @@ badseek:  err = errno;
            break;
            }
          if (cv[0]->retry <= 0)
-           return m_errlog_defer(scanent, CUS callout_address, errstr);
+           return m_panic_defer(scanent, CUS callout_address, errstr);
          while (cv[0]->retry > 0) cv[0]->retry = sleep(cv[0]->retry);
          }
 
@@ -1558,7 +1593,7 @@ badseek:  err = errno;
        /* Pass the string to ClamAV (10 = "zINSTREAM\0"), if not already sent */
        if (cmd_str.len)
          if (send(sock, cmd_str.data, cmd_str.len, 0) < 0)
-           return m_errlog_defer_3(scanent, CUS hostname,
+           return m_panic_defer_3(scanent, CUS hostname,
              string_sprintf("unable to send zINSTREAM to socket (%s)",
                strerror(errno)),
              sock);
@@ -1567,7 +1602,7 @@ badseek:  err = errno;
        if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0)
          {
          int err = errno;
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't open spool file %s: %s",
              eml_filename, strerror(err)),
            sock);
@@ -1577,7 +1612,7 @@ badseek:  err = errno;
          int err;
 b_seek:   err = errno;
          (void)close(clam_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't seek spool file %s: %s",
              eml_filename, strerror(err)),
            sock);
@@ -1586,7 +1621,7 @@ b_seek:   err = errno;
        if ((off_t)fsize_uint != fsize)
          {
          (void)close(clam_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("seeking spool file %s, size overflow",
              eml_filename),
            sock);
@@ -1597,7 +1632,7 @@ b_seek:   err = errno;
        if (!(clamav_fbuf = US malloc(fsize_uint)))
          {
          (void)close(clam_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("unable to allocate memory %u for file (%s)",
              fsize_uint, eml_filename),
            sock);
@@ -1607,7 +1642,7 @@ b_seek:   err = errno;
          {
          int err = errno;
          free(clamav_fbuf); (void)close(clam_fd);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("can't read spool file %s: %s",
              eml_filename, strerror(err)),
            sock);
@@ -1622,7 +1657,7 @@ b_seek:   err = errno;
            (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
          {
          free(clamav_fbuf);
-         return m_errlog_defer_3(scanent, NULL,
+         return m_panic_defer_3(scanent, NULL,
            string_sprintf("unable to send file body to socket (%s)", hostname),
            sock);
          }
@@ -1652,7 +1687,7 @@ b_seek:   err = errno;
 
        if (cmd_str.len)
          if (send(sock, cmd_str.data, cmd_str.len, 0) < 0)
-           return m_errlog_defer_3(scanent, CUS callout_address,
+           return m_panic_defer_3(scanent, CUS callout_address,
              string_sprintf("unable to write to socket (%s)", strerror(errno)),
              sock);
 
@@ -1669,12 +1704,12 @@ b_seek:   err = errno;
       sock = -1;
 
       if (bread <= 0)
-       return m_errlog_defer(scanent, CUS callout_address,
+       return m_panic_defer(scanent, CUS callout_address,
          string_sprintf("unable to read from socket (%s)",
          errno == 0 ? "EOF" : strerror(errno)));
 
       if (bread == sizeof(av_buffer))
-       return m_errlog_defer(scanent, CUS callout_address,
+       return m_panic_defer(scanent, CUS callout_address,
                US"buffer too small");
       /* We're now assured of a NULL at the end of av_buffer */
 
@@ -1699,7 +1734,7 @@ b_seek:   err = errno;
       passing a filename to clamd). */
 
       if (!(*av_buffer))
-       return m_errlog_defer(scanent, CUS callout_address,
+       return m_panic_defer(scanent, CUS callout_address,
                US"ClamAV returned null");
 
       /* strip newline at the end (won't be present for zINSTREAM)
@@ -1716,7 +1751,7 @@ b_seek:   err = errno;
 
       /* colon in returned output? */
       if(!(p = Ustrchr(av_buffer,':')))
-       return m_errlog_defer(scanent, CUS callout_address, string_sprintf(
+       return m_panic_defer(scanent, CUS callout_address, string_sprintf(
                  "ClamAV returned malformed result (missing colon): %s",
                  av_buffer));
 
@@ -1749,7 +1784,7 @@ b_seek:   err = errno;
 
        }
       else if (Ustrcmp(result_tag, "ERROR") == 0)
-       return m_errlog_defer(scanent, CUS callout_address,
+       return m_panic_defer(scanent, CUS callout_address,
          string_sprintf("ClamAV returned: %s", av_buffer));
 
       else if (Ustrcmp(result_tag, "OK") == 0)
@@ -1760,7 +1795,7 @@ b_seek:   err = errno;
 
        }
       else
-       return m_errlog_defer(scanent, CUS callout_address,
+       return m_panic_defer(scanent, CUS callout_address,
          string_sprintf("unparseable response from ClamAV: {%s}", av_buffer));
 
       break;
@@ -1791,7 +1826,7 @@ b_seek:   err = errno;
        uschar * s = Ustrchr(sockline_scanner, '%');
        if (s++)
          if ((*s != 's' && *s != '%') || Ustrchr(s+1, '%'))
-           return m_errlog_defer_3(scanent, NULL,
+           return m_panic_defer_3(scanent, NULL,
                                  US"unsafe sock scanner call spec", sock);
       }
       else
@@ -1803,13 +1838,13 @@ b_seek:   err = errno;
       sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep,
                                "missing trigger specification", &errstr);
       if (!sockline_trig_re)
-       return m_errlog_defer_3(scanent, NULL, errstr, sock);
+       return m_panic_defer_3(scanent, NULL, errstr, sock);
 
       /* find virus name regex */
       sockline_name_re = m_pcre_nextinlist(&av_scanner_work, &sep,
                          "missing virus name regex specification", &errstr);
       if (!sockline_name_re)
-       return m_errlog_defer_3(scanent, NULL, errstr, sock);
+       return m_panic_defer_3(scanent, NULL, errstr, sock);
 
       /* prepare scanner call - security depends on expansions check above */
       commandline = string_sprintf( CS sockline_scanner, CS eml_filename);
@@ -1818,18 +1853,18 @@ b_seek:   err = errno;
 
       /* Pass the command string to the socket */
       if (m_sock_send(sock, commandline, Ustrlen(commandline), &errstr) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       /* Read the result */
       bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
 
       if (bread <= 0)
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
          string_sprintf("unable to read from socket (%s)", strerror(errno)),
          sock);
 
       if (bread == sizeof(av_buffer))
-       return m_errlog_defer_3(scanent, CUS callout_address,
+       return m_panic_defer_3(scanent, CUS callout_address,
                US"buffer too small", sock);
       av_buffer[bread] = '\0';
       linebuffer = string_copy(av_buffer);
@@ -1865,12 +1900,12 @@ b_seek:   err = errno;
           || mksd_maxproc < 1
           || mksd_maxproc > 32
           )
-         return m_errlog_defer(scanent, CUS callout_address,
+         return m_panic_defer(scanent, CUS callout_address,
            string_sprintf("invalid option '%s'", scanner_options));
        }
 
       if((sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
-       return m_errlog_defer(scanent, CUS callout_address, errstr);
+       return m_panic_defer(scanent, CUS callout_address, errstr);
 
       malware_name = NULL;
 
@@ -1888,11 +1923,13 @@ b_seek:   err = errno;
 #ifndef DISABLE_MAL_AVAST
     case M_AVAST: /* "avast" scanner type ----------------------------------- */
       {
-      int ovector[1*3];
       uschar buf[1024];
       uschar * scanrequest;
       enum {AVA_HELO, AVA_OPT, AVA_RSP, AVA_DONE} avast_stage;
       int nread;
+      uschar * error_message = NULL;
+      BOOL more_data = FALSE;
+      BOOL strict = TRUE;
 
       /* According to Martin Tuma @avast the protocol uses "escaped
       whitespace", that is, every embedded whitespace is backslash
@@ -1902,13 +1939,46 @@ b_seek:   err = errno;
       [+] - not infected
       [L] - infected
       [E] - some error occured
-      Such marker follows the first non-escaped TAB.  */
+      Such marker follows the first non-escaped TAB.  For more information
+      see avast-protocol(5)
+
+      We observed two cases:
+      -> SCAN /file
+      <- /file [E]0.0 Error 13 Permission denied
+      <- 451 SCAN Engine error 13 permission denied
+
+      -> SCAN /file
+      <- /file… [E]3.0 Error 41120 The file is a decompression bomb
+      <- /file… [+]2.0
+      <- /file… [+]2.0 0 Eicar Test Virus!!!
+      <- 200 SCAN OK
+
+      If the scanner returns 4xx, DEFER is a good decision, combined
+      with a panic log entry, to get the admin's attention.
+
+      If the scanner returns 200, we reject it as malware, if found any,
+      or, in case of an error, we set the malware message to the error
+      string.
+
+      Some of the >= 42000 errors are message related - usually some
+      broken archives etc, but some of them are e.g. license related.
+      Once the license expires the engine starts returning errors for
+      every scanning attempt.  I¹ have the full list of the error codes
+      but it is not a public API and is subject to change. It is hard
+      for me to say what you should do in case of an engine error. You
+      can have a “Treat * unscanned file as infection” policy or “Treat
+      unscanned file as clean” policy.  ¹) Jakub Bednar
+
+       */
+
       if (  (  !ava_re_clean
             && !(ava_re_clean = m_pcre_compile(ava_re_clean_str, &errstr)))
         || (  !ava_re_virus
            && !(ava_re_virus = m_pcre_compile(ava_re_virus_str, &errstr)))
+        || (  !ava_re_error
+           && !(ava_re_error = m_pcre_compile(ava_re_error_str, &errstr)))
         )
-       return malware_errlog_defer(errstr);
+       return malware_panic_defer(errstr);
 
       /* wait for result */
       for (avast_stage = AVA_HELO;
@@ -1918,17 +1988,25 @@ b_seek:   err = errno;
        int slen = Ustrlen(buf);
        if (slen >= 1)
          {
-         DEBUG(D_acl) debug_printf_indent("got from avast: %s\n", buf);
+
+          /* Multi line responses are bracketed between 210 … and nnn … */
+          if (Ustrncmp(buf, "210", 3) == 0)
+            {
+            more_data = 1;
+            continue;
+            }
+          else if (more_data && isdigit(buf[0])) more_data = 0;
+
          switch (avast_stage)
            {
            case AVA_HELO:
+              if (more_data) continue;
              if (Ustrncmp(buf, "220", 3) != 0)
                goto endloop;                   /* require a 220 */
              goto sendreq;
 
            case AVA_OPT:
-             if (Ustrncmp(buf, "210", 3) == 0)
-               break;                          /* ignore 210 responses */
+              if (more_data) continue;
              if (Ustrncmp(buf, "200", 3) != 0)
                goto endloop;                   /* require a 200 */
 
@@ -1939,21 +2017,29 @@ b_seek:   err = errno;
              if ((scanrequest = string_nextinlist(&av_scanner_work, &sep,
                                NULL, 0)))
                {
+                if (Ustrcmp(scanrequest, "pass_unscanned") == 0)
+                  {
+                  DEBUG(D_acl) debug_printf_indent("pass unscanned files as clean\n");
+                  strict = FALSE;
+                  goto sendreq;
+                  }
                scanrequest = string_sprintf("%s\n", scanrequest);
                avast_stage = AVA_OPT;          /* just sent option */
+               DEBUG(D_acl) debug_printf_indent("send to avast OPTION: %s", scanrequest);
                }
              else
                {
                scanrequest = string_sprintf("SCAN %s\n", eml_dir);
                avast_stage = AVA_RSP;          /* just sent command */
+               DEBUG(D_acl) debug_printf_indent("send to avast REQUEST: SCAN %s\n", eml_dir);
                }
 
              /* send config-cmd or scan-request to socket */
              len = Ustrlen(scanrequest);
-             if (send(sock, scanrequest, len, 0) < 0)
+             if (send(sock, scanrequest, len, 0) == -1)
                {
                scanrequest[len-1] = '\0';
-               return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+               return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
                      "unable to send request '%s' to socket (%s): %s",
                      scanrequest, scanner_options, strerror(errno)), sock);
                }
@@ -1961,39 +2047,43 @@ b_seek:   err = errno;
              }
 
            case AVA_RSP:
-             if (Ustrncmp(buf, "210", 3) == 0)
-               break;  /* ignore the "210 SCAN DATA" message */
 
-             if (pcre_exec(ava_re_clean, NULL, CS buf, slen,
-                   0, 0, ovector, nelem(ovector)) > 0)
-               break;
+             if (isdigit(buf[0]))  /* We're done */
+                goto endloop;
 
-             if (  !malware_name
-                && (malware_name = m_pcre_exec(ava_re_virus, buf)))
-               { /* remove backslash in front of [whitespace|backslash] */
-               uschar * p, * p0;
-               for (p = malware_name; *p; ++p)
-                 if (*p == '\\' && (isspace(p[1]) || p[1] == '\\'))
-                   for (p0 = p; *p0; ++p0) *p0 = p0[1];
+              if (malware_name)     /* Nothing else matters, just read on */
+                break;
 
-               DEBUG(D_acl)
-                 debug_printf_indent("unescaped m-name: '%s'\n", malware_name);
+             if (pcre_exec(ava_re_clean, NULL, CS buf, slen, 0, 0, NULL, 0) == 0)
                break;
-               }
 
-             if (Ustrncmp(buf, "200 SCAN OK", 11) == 0)
-               { /* we're done finally */
-               if (send(sock, "QUIT\n", 5, 0) < 0) /* courtesy */
-                 return m_errlog_defer_3(scanent, CUS callout_address,
-                         string_sprintf(
-                             "unable to send quit request to socket (%s): %s",
-                             scanner_options, strerror(errno)),
-                             sock);
-
-               avast_stage = AVA_DONE;
-               }
+              if ((malware_name = m_pcre_exec(ava_re_virus, buf)))
+                {
+                unescape(malware_name);
+                DEBUG(D_acl)
+                  debug_printf_indent("unescaped malware name: '%s'\n", malware_name);
+                break;
+                }
+
+              if (strict)           /* treat scanner errors as malware */
+                {
+                if ((malware_name = m_pcre_exec(ava_re_error, buf)))
+                  {
+                  unescape(malware_name);
+                  DEBUG(D_acl)
+                    debug_printf_indent("unescaped error message: '%s'\n", malware_name);
+                  break;
+                  }
+                }
+              else if (pcre_exec(ava_re_error, NULL, CS buf, slen, 0, 0, NULL, 0) == 0)
+                {
+                log_write(0, LOG_MAIN, "internal scanner error (ignored): %s", buf);
+                break;
+                }
 
              /* here also for any unexpected response from the scanner */
+              DEBUG(D_acl) debug_printf("avast response not handled: '%s'\n", buf);
+
              goto endloop;
 
            default:    log_write(0, LOG_PANIC, "%s:%d:%s: should not happen",
@@ -2001,23 +2091,23 @@ b_seek:   err = errno;
            }
          }
        }
+
       endloop:
 
-      switch(avast_stage)
-       {
-       case AVA_HELO:
-       case AVA_OPT:
-       case AVA_RSP:   return m_errlog_defer_3(scanent, CUS callout_address,
-                           nread >= 0
-                           ? string_sprintf(
-                               "invalid response from scanner: '%s'", buf)
-                           : nread == -1
-                           ? US"EOF from scanner"
-                           : US"timeout from scanner",
-                         sock);
-       default:        break;
-       }
-      break;
+      if (nread == -1) error_message = US"EOF from scanner";
+      else if (nread < 0) error_message = US"timeout from scanner";
+      else if (nread == 0) error_message = US"got nothing from scanner";
+      else if (buf[0] != '2') error_message = buf;
+
+      DEBUG(D_acl) debug_printf_indent("sent to avast QUIT\n");
+      if (send(sock, "QUIT\n", 5, 0) == -1)
+        return m_panic_defer_3(scanent, CUS callout_address,
+          string_sprintf("unable to send quit request to socket (%s): %s",
+            scanner_options, strerror(errno)), sock);
+
+      if (error_message)
+        return m_panic_defer_3(scanent, CUS callout_address, error_message, sock);
+
       }
 #endif
   }    /* scanner type switch */
@@ -2133,13 +2223,15 @@ if (!kav_re_sus)
 if (!kav_re_inf)
   kav_re_inf = regex_must_compile(kav_re_inf_str, FALSE, TRUE);
 #endif
-#ifndef DISABLE_MAL_AVA
+#ifndef DISABLE_MAL_AVAST
 if (!ava_re_clean)
   ava_re_clean = regex_must_compile(ava_re_clean_str, FALSE, TRUE);
 if (!ava_re_virus)
   ava_re_virus = regex_must_compile(ava_re_virus_str, FALSE, TRUE);
+if (!ava_re_error)
+  ava_re_error = regex_must_compile(ava_re_error_str, FALSE, TRUE);
 #endif
-#ifndef DISABLE_MAL_FPROT6D
+#ifndef DISABLE_MAL_FFROT6D
 if (!fprot6d_re_error)
   fprot6d_re_error = regex_must_compile(fprot6d_re_error_str, FALSE, TRUE);
 if (!fprot6d_re_virus)