Compiler quietening
[exim.git] / src / src / mime.c
index 95d3da4723cf5de2115014dfebb73ae728639ac0..b9442af23c50b05013dfbb9eb42194444ef510ab 100644 (file)
@@ -2,8 +2,12 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 */
-/* License: GPL */
+/*
+ * Copyright (c) The Exim Maintainers 2015 - 2024
+ * Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2015
+ * License: GPL
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
 
 #include "exim.h"
 #ifdef WITH_CONTENT_SCAN       /* entire file */
 FILE *mime_stream = NULL;
 uschar *mime_current_boundary = NULL;
 
+static mime_header mime_header_list[] = {
+  /*   name                    namelen         value */
+  { US"content-type:",              13, &mime_content_type },
+  { US"content-disposition:",       20, &mime_content_disposition },
+  { US"content-transfer-encoding:", 26, &mime_content_transfer_encoding },
+  { US"content-id:",                11, &mime_content_id },
+  { US"content-description:",       20, &mime_content_description }
+};
+
+static int mime_header_list_size = nelem(mime_header_list);
+
+static mime_parameter mime_parameter_list[] = {
+  /*   name    namelen  value */
+  { US"name",     4, &mime_filename },
+  { US"filename", 8, &mime_filename },
+  { US"charset",  7, &mime_charset  },
+  { US"boundary", 8, &mime_boundary }
+};
+
+
 /*************************************************
 * set MIME anomaly level + text                  *
 *************************************************/
 
 /* Small wrapper to set the two expandables which
    give info on detected "problems" in MIME
-   encodings. Those are defined in mime.h. */
+   encodings. Indexes are defined in mime.h. */
 
 void
-mime_set_anomaly(int level, const char *text)
+mime_set_anomaly(int idx)
 {
-  mime_anomaly_level = level;
-  mime_anomaly_text = CUS text;
+struct anom {
+  int level;
+  const uschar * text;
+} anom[] = { {1, CUS"Broken Quoted-Printable encoding detected"},
+            {2, CUS"Broken BASE64 encoding detected"} };
+
+mime_anomaly_level = anom[idx].level;
+mime_anomaly_text =  anom[idx].text;
 }
 
 
@@ -41,7 +71,7 @@ mime_set_anomaly(int level, const char *text)
            0-255 - char to write
 */
 
-uschar *
+static uschar *
 mime_decode_qp_char(uschar *qp_p, int *c)
 {
 uschar *initial_pos = qp_p;
@@ -79,104 +109,26 @@ return initial_pos;
 static ssize_t
 mime_decode_asis(FILE* in, FILE* out, uschar* boundary)
 {
-  ssize_t len, size = 0;
-  uschar buffer[MIME_MAX_LINE_LENGTH];
+ssize_t len, size = 0;
+uschar buffer[MIME_MAX_LINE_LENGTH];
 
-  while(fgets(CS buffer, MIME_MAX_LINE_LENGTH, mime_stream) != NULL)
-    {
-    if (boundary != NULL
-       && Ustrncmp(buffer, "--", 2) == 0
-       && Ustrncmp((buffer+2), boundary, Ustrlen(boundary)) == 0
-       )
-      break;
+while(fgets(CS buffer, MIME_MAX_LINE_LENGTH, mime_stream) != NULL)
+  {
+  if (boundary != NULL
+     && Ustrncmp(buffer, "--", 2) == 0
+     && Ustrncmp((buffer+2), boundary, Ustrlen(boundary)) == 0
+     )
+    break;
 
-    len = Ustrlen(buffer);
-    if (fwrite(buffer, 1, (size_t)len, out) < len)
-      return -1;
-    size += len;
-    } /* while */
-  return size;
+  len = Ustrlen(buffer);
+  if (fwrite(buffer, 1, (size_t)len, out) < len)
+    return -1;
+  size += len;
+  } /* while */
+return size;
 }
 
 
-/* decode base64 MIME part */
-static ssize_t
-mime_decode_base64(FILE* in, FILE* out, uschar* boundary)
-{
-  uschar ibuf[MIME_MAX_LINE_LENGTH], obuf[MIME_MAX_LINE_LENGTH];
-  uschar *ipos, *opos;
-  ssize_t len, size = 0;
-  int bytestate = 0;
-
-  opos = obuf;
-
-  while (Ufgets(ibuf, MIME_MAX_LINE_LENGTH, in) != NULL)
-    {
-    if (boundary != NULL
-       && Ustrncmp(ibuf, "--", 2) == 0
-       && Ustrncmp((ibuf+2), boundary, Ustrlen(boundary)) == 0
-       )
-      break;
-
-    for (ipos = ibuf ; *ipos != '\r' && *ipos != '\n' && *ipos != 0; ++ipos)
-      {
-      if (*ipos == '=')                        /* skip padding */
-        {
-        ++bytestate;
-        continue;
-       }
-      if (mime_b64[*ipos] == 128)      /* skip bad characters */
-        {
-        mime_set_anomaly(MIME_ANOMALY_BROKEN_BASE64);
-        continue;
-       }
-
-      /* simple state-machine */
-      switch((bytestate++) & 3)
-        {
-        case 0:
-          *opos = mime_b64[*ipos] << 2;
-           break;
-        case 1:
-          *opos |= mime_b64[*ipos] >> 4;
-          ++opos;
-          *opos = mime_b64[*ipos] << 4;
-          break;
-        case 2:
-          *opos |= mime_b64[*ipos] >> 2;
-          ++opos;
-          *opos = mime_b64[*ipos] << 6;
-          break;
-        case 3:
-          *opos |= mime_b64[*ipos];
-          ++opos;
-          break;
-       } /* switch */
-      } /* for */
-
-    /* something to write? */
-    len = opos - obuf;
-    if (len > 0)
-      {
-      if (fwrite(obuf, 1, len, out) != len) return -1; /* error */
-      size += len;
-      /* copy incomplete last byte to start of obuf, where we continue */
-      if ((bytestate & 3) != 0)
-        *obuf = *opos;
-      opos = obuf;
-      }
-    } /* while */
-
-  /* write out last byte if it was incomplete */
-  if (bytestate & 3)
-    {
-    if (fwrite(obuf, 1, 1, out) != 1) return -1;
-    ++size;
-    }
-
-  return size;
-}
-
 
 /* decode quoted-printable MIME part */
 static ssize_t
@@ -209,24 +161,16 @@ while (fgets(CS ibuf, MIME_MAX_LINE_LENGTH, in) != NULL)
        {
        /* Error from decoder. ipos is unchanged. */
        mime_set_anomaly(MIME_ANOMALY_BROKEN_QP);
-       *opos = '=';
-       ++opos;
+       *opos++ = '=';
        ++ipos;
        }
       else if (decode_qp_result == -1)
        break;
       else if (decode_qp_result >= 0)
-       {
-       *opos = decode_qp_result;
-       ++opos;
-       }
+       *opos++ = decode_qp_result;
       }
     else
-      {
-      *opos = *ipos;
-      ++opos;
-      ++ipos;
-      }
+      *opos++ = *ipos++;
     }
   /* something to write? */
   len = opos - obuf;
@@ -240,21 +184,17 @@ return size;
 }
 
 
-FILE *
+/*
+ * Return open filehandle for combo of path and file.
+ * Side-effect: set mime_decoded_filename, to copy in allocated mem
+ */
+static FILE *
 mime_get_decode_file(uschar *pname, uschar *fname)
 {
-FILE *f = NULL;
-uschar *filename;
-
-filename = (uschar *)malloc(2048);
-
 if (pname && fname)
-  {
-  (void)string_format(filename, 2048, "%s/%s", pname, fname);
-  f = modefopen(filename,"wb+",SPOOL_MODE);
-  }
+  mime_decoded_filename = string_sprintf("%s/%s", pname, fname);
 else if (!pname)
-  f = modefopen(fname,"wb+",SPOOL_MODE);
+  mime_decoded_filename = string_copy(fname);
 else if (!fname)
   {
   int file_nr = 0;
@@ -264,52 +204,41 @@ else if (!fname)
   do
     {
     struct stat mystat;
-    (void)string_format(filename, 2048,
-      "%s/%s-%05u", pname, message_id, file_nr++);
+    mime_decoded_filename = string_sprintf("%s/%s-%05u", pname, message_id, file_nr++);
     /* security break */
     if (file_nr >= 1024)
       break;
-    result = stat(CS filename, &mystat);
+    result = stat(CS mime_decoded_filename, &mystat);
     } while(result != -1);
-
-  f = modefopen(filename, "wb+", SPOOL_MODE);
   }
 
-/* set expansion variable */
-mime_decoded_filename = filename;
-
-return f;
+return modefopen(mime_decoded_filename, "wb+", SPOOL_MODE);
 }
 
 
 int
-mime_decode(uschar **listptr)
+mime_decode(const uschar **listptr)
 {
 int sep = 0;
-uschar *list = *listptr;
-uschar *option;
-uschar option_buffer[1024];
-uschar decode_path[1024];
+const uschar *list = *listptr;
+uschar * option;
+uschar * decode_path;
 FILE *decode_file = NULL;
 long f_pos = 0;
 ssize_t size_counter = 0;
 ssize_t (*decode_function)(FILE*, FILE*, uschar*);
 
-if (mime_stream == NULL)
+if (!mime_stream || (f_pos = ftell(mime_stream)) < 0)
   return FAIL;
 
-f_pos = ftell(mime_stream);
-
 /* build default decode path (will exist since MBOX must be spooled up) */
-(void)string_format(decode_path,1024,"%s/scan/%s",spool_directory,message_id);
+decode_path = string_sprintf("%s/scan/%s", spool_directory, message_id);
 
 /* try to find 1st option */
-if ((option = string_nextinlist(&list, &sep,
-                               option_buffer,
-                               sizeof(option_buffer))) != NULL)
+if ((option = string_nextinlist(&list, &sep, NULL, 0)))
   {
   /* parse 1st option */
-  if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) )
+  if ((Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0))
     /* explicitly no decoding */
     return FAIL;
 
@@ -358,7 +287,8 @@ decode_function =
 size_counter = decode_function(mime_stream, decode_file, mime_current_boundary);
 
 clearerr(mime_stream);
-fseek(mime_stream, f_pos, SEEK_SET);
+if (fseek(mime_stream, f_pos, SEEK_SET))
+  return DEFER;
 
 if (fclose(decode_file) != 0 || size_counter < 0)
   return DEFER;
@@ -369,7 +299,8 @@ mime_content_size = (size_counter + 1023) / 1024;
 return OK;
 }
 
-int
+
+static int
 mime_get_header(FILE *f, uschar *header)
 {
 int c = EOF;
@@ -409,17 +340,16 @@ while(!done)
     if ( ((c == '\t') || (c == ' ')) && (header_value_mode == 1) )
       continue;
 
-      /* we have hit a non-whitespace char, start copying value data */
-      header_value_mode = 2;
+    /* we have hit a non-whitespace char, start copying value data */
+    header_value_mode = 2;
 
-      if (c == '"')       /* flip "quoted" mode */
-        header_value_mode = header_value_mode==2 ? 3 : 2;
+    if (c == '"')       /* flip "quoted" mode */
+      header_value_mode = header_value_mode==2 ? 3 : 2;
 
-      /* leave value mode on unquoted ';' */
-      if (header_value_mode == 2 && c == ';') {
-        header_value_mode = 0;
-      };
-      /* -------------------------------- */
+    /* leave value mode on unquoted ';' */
+    if (header_value_mode == 2 && c == ';')
+      header_value_mode = 0;
+    /* -------------------------------- */
     }
   else
     {
@@ -467,28 +397,113 @@ if ((num_copied > 0) && (header[num_copied-1] != ';'))
 header[num_copied] = '\0';
 
 /* return 0 for EOF or empty line */
-if ((c == EOF) || (num_copied == 1))
-  return 0;
-else
-  return 1;
+return c == EOF || num_copied == 1 ? 0 : 1;
+}
+
+
+/* reset all per-part mime variables */
+static void
+mime_vars_reset(void)
+{
+mime_anomaly_level     = 0;
+mime_anomaly_text      = NULL;
+mime_boundary          = NULL;
+mime_charset           = NULL;
+mime_decoded_filename  = NULL;
+mime_filename          = NULL;
+mime_content_description = NULL;
+mime_content_disposition = NULL;
+mime_content_id        = NULL;
+mime_content_transfer_encoding = NULL;
+mime_content_type      = NULL;
+mime_is_multipart      = 0;
+mime_content_size      = 0;
+}
+
+
+/* Grab a parameter value, dealing with quoting.
+
+Arguments:
+ str   Input string.  Updated on return to point to terminating ; or NUL
+
+Return:
+ Allocated string with parameter value
+*/
+static uschar *
+mime_param_val(uschar ** sp)
+{
+uschar * s = *sp;
+gstring * val = NULL;
+
+/* debug_printf_indent("   considering paramval '%s'\n", s); */
+
+while (*s && *s != ';')                /* ; terminates */
+  if (*s == '"')
+    {
+    s++;                       /* skip opening " */
+    while (*s && *s != '"')    /* " protects ; */
+      val = string_catn(val, s++, 1);
+    if (*s) s++;               /* skip closing " */
+    }
+  else
+    val = string_catn(val, s++, 1);
+*sp = s;
+return string_from_gstring(val);
+}
+
+static uschar *
+mime_next_semicolon(uschar * s)
+{
+while (*s && *s != ';')                /* ; terminates */
+  if (*s == '"')
+    {
+    s++;                       /* skip opening " */
+    while (*s && *s != '"')    /* " protects ; */
+      s++;
+    if (*s) s++;               /* skip closing " */
+    }
+  else
+    s++;
+return s;
+}
+
+
+static uschar *
+rfc2231_to_2047(const uschar * fname, const uschar * charset, int * len)
+{
+gstring * val = string_catn(NULL, US"=?", 2);
+uschar c;
+
+if (charset)
+  val = string_cat(val, charset);
+val = string_catn(val, US"?Q?", 3);
+
+while ((c = *fname))
+  if (c == '%' && isxdigit(fname[1]) && isxdigit(fname[2]))
+    {
+    val = string_catn(val, US"=", 1);
+    val = string_catn(val, ++fname, 2);
+    fname += 2;
+    }
+  else
+    val = string_catn(val, fname++, 1);
+
+val = string_catn(val, US"?=", 2);
+*len = gstring_length(val);
+return string_from_gstring(val);
 }
 
 
 int
 mime_acl_check(uschar *acl, FILE *f, struct mime_boundary_context *context,
-                   uschar **user_msgptr, uschar **log_msgptr)
+    uschar **user_msgptr, uschar **log_msgptr)
 {
 int rc = OK;
-uschar *header = NULL;
+uschar * header = NULL;
 struct mime_boundary_context nested_context;
 
-/* reserve a line buffer to work in */
-if (!(header = (uschar *)malloc(MIME_MAX_HEADER_SIZE+1)))
-  {
-  log_write(0, LOG_PANIC,
-       "MIME ACL: can't allocate %d bytes of memory.", MIME_MAX_HEADER_SIZE+1);
-  return DEFER;
-  }
+/* reserve a line buffer to work in.  Assume tainted data. */
+header = store_get(MIME_MAX_HEADER_SIZE+1, GET_TAINTED);
 
 /* Not actually used at the moment, but will be vital to fixing
  * some RFC 2046 nonconformance later... */
@@ -498,26 +513,12 @@ nested_context.parent = context;
 while(1)
   {
   /* reset all per-part mime variables */
-  mime_anomaly_level     = 0;
-  mime_anomaly_text      = NULL;
-  mime_boundary          = NULL;
-  mime_charset           = NULL;
-  mime_decoded_filename  = NULL;
-  mime_filename          = NULL;
-  mime_content_description = NULL;
-  mime_content_disposition = NULL;
-  mime_content_id        = NULL;
-  mime_content_transfer_encoding = NULL;
-  mime_content_type      = NULL;
-  mime_is_multipart      = 0;
-  mime_content_size      = 0;
-
-  /*
-  If boundary is null, we assume that *f is positioned on the start of headers (for example,
-  at the very beginning of a message.
-  If a boundary is given, we must first advance to it to reach the start of the next header
-  block.
-  */
+  mime_vars_reset();
+
+  /* If boundary is null, we assume that *f is positioned on the start of
+  headers (for example, at the very beginning of a message.  If a boundary is
+  given, we must first advance to it to reach the start of the next header
+  block.  */
 
   /* NOTE -- there's an error here -- RFC2046 specifically says to
    * check for outer boundaries.  This code doesn't do that, and
@@ -526,122 +527,195 @@ while(1)
    * (I have moved partway towards adding support, however, by adding
    * a "parent" field to my new boundary-context structure.)
    */
-  if (context != NULL)
+  if (context) for (;;)
     {
-    while(fgets(CS header, MIME_MAX_HEADER_SIZE, f) != NULL)
+    if (!fgets(CS header, MIME_MAX_HEADER_SIZE, f))
       {
-      /* boundary line must start with 2 dashes */
-      if (Ustrncmp(header,"--",2) == 0)
-        {
-       if (Ustrncmp((header+2),context->boundary,Ustrlen(context->boundary)) == 0)
-         {
-         /* found boundary */
-         if (Ustrncmp((header+2+Ustrlen(context->boundary)),"--",2) == 0)
-           {
-           /* END boundary found */
-           debug_printf("End boundary found %s\n", context->boundary);
-           return rc;
-           }
-         else
-           debug_printf("Next part with boundary %s\n", context->boundary);
-
-         /* can't use break here */
-         goto DECODE_HEADERS;
-         }
+      /* Hit EOF or read error. Ugh. */
+      DEBUG(D_acl) debug_printf_indent("MIME: Hit EOF ...\n");
+      return rc;
+      }
+
+    /* boundary line must start with 2 dashes */
+    if (  Ustrncmp(header, "--", 2) == 0
+       && Ustrncmp(header+2, context->boundary, Ustrlen(context->boundary)) == 0
+       )
+      {                        /* found boundary */
+      if (Ustrncmp((header+2+Ustrlen(context->boundary)), "--", 2) == 0)
+       {
+       /* END boundary found */
+       DEBUG(D_acl) debug_printf_indent("MIME: End boundary found %s\n",
+         context->boundary);
+       return rc;
        }
+
+      DEBUG(D_acl) debug_printf_indent("MIME: Next part with boundary %s\n",
+       context->boundary);
+      break;
       }
-    /* Hit EOF or read error. Ugh. */
-    debug_printf("Hit EOF ...\n");
-    return rc;
     }
 
-DECODE_HEADERS:
   /* parse headers, set up expansion variables */
-  while (mime_get_header(f,header))
-    {
-    int i;
-    /* loop through header list */
-    for (i = 0; i < mime_header_list_size; i++)
+  while (mime_get_header(f, header))
+
+    /* look for interesting headers */
+    for (struct mime_header * mh = mime_header_list;
+        mh < mime_header_list + mime_header_list_size;
+        mh++) if (strncmpic(mh->name, header, mh->namelen) == 0)
       {
-      uschar *header_value = NULL;
-      int header_value_len = 0;
+      uschar * p = header + mh->namelen;
+      uschar * q;
+
+      /* grab the value (normalize to lower case)
+      and copy to its corresponding expansion variable */
+
+      for (q = p; *q != ';' && *q; q++) ;
+      *mh->value = string_copynlc(p, q-p);
+      DEBUG(D_acl) debug_printf_indent("MIME: found %s header, value is '%s'\n",
+       mh->name, *mh->value);
+
+      if (*(p = q)) p++;                       /* jump past the ; */
 
-      /* found an interesting header? */
-      if (strncmpic(mime_header_list[i].name,header,mime_header_list[i].namelen) == 0)
        {
-       uschar *p = header + mime_header_list[i].namelen;
-       /* yes, grab the value (normalize to lower case)
-          and copy to its corresponding expansion variable */
-       while(*p != ';')
-         {
-         *p = tolower(*p);
-         p++;
-         }
-       header_value_len = (p - (header + mime_header_list[i].namelen));
-       header_value = (uschar *)malloc(header_value_len+1);
-       memset(header_value,0,header_value_len+1);
-       p = header + mime_header_list[i].namelen;
-       Ustrncpy(header_value, p, header_value_len);
-       debug_printf("Found %s MIME header, value is '%s'\n", mime_header_list[i].name, header_value);
-       *((uschar **)(mime_header_list[i].value)) = header_value;
-
-       /* make p point to the next character after the closing ';' */
-       p += (header_value_len+1);
-
-       /* grab all param=value tags on the remaining line, check if they are interesting */
-NEXT_PARAM_SEARCH:
-       while (*p != 0)
+       gstring * mime_fname = NULL;
+       gstring * mime_fname_rfc2231 = NULL;
+       uschar * mime_filename_charset = NULL;
+       BOOL decoding_failed = FALSE;
+
+       /* grab all param=value tags on the remaining line,
+       check if they are interesting */
+
+       while (*p)
          {
-         mime_parameter * mp;
-         for (mp = mime_parameter_list;
-              mp < &mime_parameter_list[mime_parameter_list_size];
-              mp++)
+         DEBUG(D_acl)
+           debug_printf_indent("MIME:   considering paramlist '%s'\n", p);
+
+         /* look for interesting parameters */
+         for (mime_parameter * mp = mime_parameter_list;
+              mp < mime_parameter_list + nelem(mime_parameter_list);
+              mp++
+             ) if (strncmpic(mp->name, p, mp->namelen) == 0)
            {
-           uschar *param_value = NULL;
-           int param_value_len = 0;
-
-           /* found an interesting parameter? */
-           if (strncmpic(mp->name, p, mp->namelen) == 0)
+           p += mp->namelen;
+           if (*p == '*')                      /* RFC 2231 */
              {
-             uschar *q = p + mp->namelen;
-             int size = 0;
-             int ptr = 0;
-
-             /* yes, grab the value and copy to its corresponding expansion variable */
-             while(*q && *q != ';')            /* ; terminates */
+             while (isdigit(*++p)) ;           /* ignore cont-cnt values */
+             if (*p == '*') p++;               /* step over sep chset mark */
+             if (*p == '=')
                {
-               if (*q == '"')
+               uschar * q;
+               p++;                            /* step over = */
+               q = mime_param_val(&p);         /* p now trailing ; or NUL */
+
+               if (q && *q)                    /* q is the dequoted value */
                  {
-                 q++;                          /* skip leading " */
-                 while(*q && *q != '"')        /* which protects ; */
-                   param_value = string_cat(param_value, &size, &ptr, q++, 1);
-                 if (*q) q++;                  /* skip trailing " */
-                 }
-               else
-                 param_value = string_cat(param_value, &size, &ptr, q++, 1);
-               }
-             param_value[ptr++] = '\0';
-             param_value_len = ptr;
-
-             param_value = rfc2047_decode(param_value, check_rfc2047_length, NULL, 32, &param_value_len, &q);
-             debug_printf("Found %s MIME parameter in %s header, value is '%s'\n", mp->name, mime_header_list[i].name, param_value);
-             *((uschar **)(mp->value)) = param_value;
-             p += (mp->namelen + param_value_len + 1);
-             goto NEXT_PARAM_SEARCH;
-           }
-         }
+                 uschar * err_msg, * fname = q;
+                 int slen;
+
+                 /* build up an un-decoded filename over successive
+                 filename*= parameters (for use when 2047 decode fails) */
+
+                 mime_fname_rfc2231 = string_cat(mime_fname_rfc2231, q);
+
+                 if (!decoding_failed)
+                   {
+                   if (!mime_filename_charset)
+                     {                 /* try for RFC 2231 chset/lang */
+                     uschar * s = q;
+
+                     /* look for a ' in the raw paramval */
+                     while(*s != '\'' && *s) s++;      /* s is 1st ' or NUL */
+
+                     if (*s)                           /* there was a ' */
+                       {
+                       int size;
+                       if ((size = s-q) > 0)
+                         mime_filename_charset = string_copyn(q, size);
+
+                       if (*(fname = s)) fname++;
+                       while(*fname == '\'') fname++;    /*fname is after 2nd '*/
+                       }
+                     }
+
+                   DEBUG(D_acl)
+                     debug_printf_indent("MIME:    charset %s fname '%s'\n",
+                       mime_filename_charset ? mime_filename_charset : US"<NULL>",
+                       fname);
+
+                   fname = rfc2231_to_2047(fname, mime_filename_charset,
+                                                 &slen);
+                   DEBUG(D_acl)
+                     debug_printf_indent("MIME:    2047-name %s\n", fname);
+
+                   fname = rfc2047_decode(fname, FALSE, NULL, ' ',
+                                                 NULL, &err_msg);
+                   DEBUG(D_acl) debug_printf_indent(
+                                   "MIME:    plain-name %s\n", fname);
+
+                   if (!fname || Ustrlen(fname) == slen)
+                     decoding_failed = TRUE;
+                   else if (mp->value == &mime_filename)
+                     {
+                     /* build up a decoded filename over successive
+                     filename*= parameters */
+
+                     mime_fname = string_cat(mime_fname, fname);
+                     mime_filename = string_from_gstring(mime_fname);
+                     }
+                   }   /*!decoding_failed*/
+                 }     /*q*/
+
+               if (*p) p++;                    /* p is past ; */
+               goto param_done;                /* done matching param names */
+               }               /*2231 param coding extension*/
+             }
+           else if (*p == '=')
+             {         /* non-2231 param */
+             uschar * q, * dummy_errstr;
+
+             /* grab the value and copy to its expansion variable */
+
+             if (*p) p++;                      /* step over = */
+             q = mime_param_val(&p);           /* p now trailing ; or NUL */
+
+             *mp->value = q && *q
+               ? rfc2047_decode(q, check_rfc2047_length, NULL, 32, NULL,
+                   &dummy_errstr)
+               : NULL;
+             DEBUG(D_acl) debug_printf_indent(
+               "MIME:  found %s parameter in %s header, value '%s'\n",
+               mp->name, mh->name, *mp->value);
+
+             if (*p) p++;                      /* p is past ; */
+             goto param_done;                  /* done matching param names */
+             }
+           }                                   /* interesting parameters */
+
          /* There is something, but not one of our interesting parameters.
-            Advance to the next semicolon */
-         while(*p != ';') p++;
-         p++;
+         Advance past the next semicolon */
+
+         p = mime_next_semicolon(p);
+         if (*p) p++;
+  param_done: ;
+         }                                     /* param scan on line */
+
+       if (strncmpic(CUS"content-disposition:", header, 20) == 0)
+         {
+         if (decoding_failed)
+           mime_filename = string_from_gstring(mime_fname_rfc2231);
+
+         DEBUG(D_acl) debug_printf_indent(
+           "MIME:  found %s parameter in %s header, value is '%s'\n",
+           "filename", mh->name, mime_filename);
+         }
        }
-      }
-    }
-  }
+      break;
+      }        /* interesting headers */
 
   /* set additional flag variables (easier access) */
-  if ( (mime_content_type != NULL) &&
-       (Ustrncmp(mime_content_type,"multipart",9) == 0) )
+  if (  mime_content_type
+     && Ustrncmp(mime_content_type,"multipart",9) == 0
+     )
     mime_is_multipart = 1;
 
   /* Make a copy of the boundary pointer.
@@ -668,11 +742,12 @@ NEXT_PARAM_SEARCH:
   if (rc != OK) break;
 
   /* If we have a multipart entity and a boundary, go recursive */
-  if ( (mime_content_type != NULL) &&
-       (nested_context.boundary != NULL) &&
-       (Ustrncmp(mime_content_type,"multipart",9) == 0) )
+  if (  mime_content_type && nested_context.boundary 
+     && Ustrncmp(mime_content_type,"multipart",9) == 0)
     {
-    debug_printf("Entering multipart recursion, boundary '%s'\n", nested_context.boundary);
+    DEBUG(D_acl)
+      debug_printf_indent("MIME: Entering multipart recursion, boundary '%s'\n",
+       nested_context.boundary);
 
     nested_context.context =
       context && context->context == MBC_ATTACHMENT
@@ -685,25 +760,25 @@ NEXT_PARAM_SEARCH:
     rc = mime_acl_check(acl, f, &nested_context, user_msgptr, log_msgptr);
     if (rc != OK) break;
     }
-  else if ( (mime_content_type != NULL) &&
-         (Ustrncmp(mime_content_type,"message/rfc822",14) == 0) )
+  else if (  mime_content_type
+         && Ustrncmp(mime_content_type,"message/rfc822",14) == 0)
     {
-    uschar *rfc822name = NULL;
-    uschar filename[2048];
+    const uschar * rfc822name = NULL;
+    uschar * filename;
     int file_nr = 0;
     int result = 0;
 
     /* must find first free sequential filename */
-    do
+    for (gstring * g = string_get(64); result != -1; gstring_reset(g))
       {
       struct stat mystat;
-      (void)string_format(filename, 2048,
+      g = string_fmt_append(g,
        "%s/scan/%s/__rfc822_%05u", spool_directory, message_id, file_nr++);
       /* security break */
       if (file_nr >= 128)
        goto NO_RFC822;
-      result = stat(CS filename,&mystat);
-      } while (result != -1);
+      result = stat(CS (filename = string_from_gstring(g)), &mystat);
+      }
 
     rfc822name = filename;
 
@@ -717,24 +792,27 @@ NEXT_PARAM_SEARCH:
     if (!mime_decoded_filename)                /* decoding failed */
       {
       log_write(0, LOG_MAIN,
-          "mime_regex acl condition warning - could not decode RFC822 MIME part to file.");
-      return DEFER;
+          "MIME acl condition warning - could not decode RFC822 MIME part to file.");
+      rc = DEFER;
+      goto out;
       }
     mime_decoded_filename = NULL;
     }
 
 NO_RFC822:
   /* If the boundary of this instance is NULL, we are finished here */
-  if (context == NULL) break;
+  if (!context) break;
 
   if (context->context == MBC_COVERLETTER_ONESHOT)
     context->context = MBC_ATTACHMENT;
   }
 
+out:
+mime_vars_reset();
 return rc;
 }
 
 #endif /*WITH_CONTENT_SCAN*/
 
-/* vi: sw ai sw=2
+/* vi: aw ai sw=2
 */