Accept "exim -bp -qGname"
[users/jgh/exim.git] / src / src / mime.c
index aeab33d9c22ac62e2f57a424769fc2dcc82bb79f..0339295f37c242010fe4924b5265be17143a6f47 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 */
-/* License: GPL */
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004, 2015
+ * License: GPL
+ * Copyright (c) The Exim Maintainers 2016
+ */
 
 #include "exim.h"
 #ifdef WITH_CONTENT_SCAN       /* entire file */
 
 #include "exim.h"
 #ifdef WITH_CONTENT_SCAN       /* entire file */
 FILE *mime_stream = NULL;
 uschar *mime_current_boundary = NULL;
 
 FILE *mime_stream = NULL;
 uschar *mime_current_boundary = NULL;
 
+static mime_header mime_header_list[] = {
+  { 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[] = {
+  { US"name=",     5, &mime_filename },
+  { US"filename=", 9, &mime_filename },
+  { US"charset=",  8, &mime_charset  },
+  { US"boundary=", 9, &mime_boundary }
+};
+
+
 /*************************************************
 * set MIME anomaly level + text                  *
 *************************************************/
 
 /* Small wrapper to set the two expandables which
    give info on detected "problems" in MIME
 /*************************************************
 * 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. */
 
 
-static void
-mime_set_anomaly(int level, const char *text)
+void
+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;
 }
 
 
 }
 
 
@@ -99,84 +125,6 @@ mime_decode_asis(FILE* in, FILE* out, uschar* boundary)
 }
 
 
 }
 
 
-/* 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
 
 /* decode quoted-printable MIME part */
 static ssize_t
@@ -358,7 +306,8 @@ decode_function =
 size_counter = decode_function(mime_stream, decode_file, mime_current_boundary);
 
 clearerr(mime_stream);
 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;
 
 if (fclose(decode_file) != 0 || size_counter < 0)
   return DEFER;
@@ -516,11 +465,11 @@ while (*s && *s != ';')           /* ; terminates */
     {
     s++;                       /* skip opening " */
     while (*s && *s != '"')    /* " protects ; */
     {
     s++;                       /* skip opening " */
     while (*s && *s != '"')    /* " protects ; */
-      val = string_cat(val, &size, &ptr, s++, 1);
+      val = string_catn(val, &size, &ptr, s++, 1);
     if (*s) s++;               /* skip closing " */
     }
   else
     if (*s) s++;               /* skip closing " */
     }
   else
-    val = string_cat(val, &size, &ptr, s++, 1);
+    val = string_catn(val, &size, &ptr, s++, 1);
 if (val) val[ptr] = '\0';
 *sp = s;
 return val;
 if (val) val[ptr] = '\0';
 *sp = s;
 return val;
@@ -543,6 +492,33 @@ return s;
 }
 
 
 }
 
 
+static uschar *
+rfc2231_to_2047(const uschar * fname, const uschar * charset, int * len)
+{
+int size = 0, ptr = 0;
+uschar * val = string_catn(NULL, &size, &ptr, US"=?", 2);
+uschar c;
+
+if (charset)
+  val = string_cat(val, &size, &ptr, charset);
+val = string_catn(val, &size, &ptr, US"?Q?", 3);
+
+while ((c = *fname))
+  if (c == '%' && isxdigit(fname[1]) && isxdigit(fname[2]))
+    {
+    val = string_catn(val, &size, &ptr, US"=", 1);
+    val = string_catn(val, &size, &ptr, ++fname, 2);
+    fname += 2;
+    }
+  else
+    val = string_catn(val, &size, &ptr, fname++, 1);
+
+val = string_catn(val, &size, &ptr, US"?=", 2);
+val[*len = ptr] = '\0';
+return val;
+}
+
+
 int
 mime_acl_check(uschar *acl, FILE *f, struct mime_boundary_context *context,
     uschar **user_msgptr, uschar **log_msgptr)
 int
 mime_acl_check(uschar *acl, FILE *f, struct mime_boundary_context *context,
     uschar **user_msgptr, uschar **log_msgptr)
@@ -581,7 +557,7 @@ while(1)
     if (!fgets(CS header, MIME_MAX_HEADER_SIZE, f))
       {
       /* Hit EOF or read error. Ugh. */
     if (!fgets(CS header, MIME_MAX_HEADER_SIZE, f))
       {
       /* Hit EOF or read error. Ugh. */
-      DEBUG(D_acl) debug_printf("Hit EOF ...\n");
+      DEBUG(D_acl) debug_printf("MIME: Hit EOF ...\n");
       return rc;
       }
 
       return rc;
       }
 
@@ -593,12 +569,12 @@ while(1)
       if (Ustrncmp((header+2+Ustrlen(context->boundary)), "--", 2) == 0)
        {
        /* END boundary found */
       if (Ustrncmp((header+2+Ustrlen(context->boundary)), "--", 2) == 0)
        {
        /* END boundary found */
-       DEBUG(D_acl) debug_printf("End boundary found %s\n",
+       DEBUG(D_acl) debug_printf("MIME: End boundary found %s\n",
          context->boundary);
        return rc;
        }
 
          context->boundary);
        return rc;
        }
 
-      DEBUG(D_acl) debug_printf("Next part with boundary %s\n",
+      DEBUG(D_acl) debug_printf("MIME: Next part with boundary %s\n",
        context->boundary);
       break;
       }
        context->boundary);
       break;
       }
@@ -614,8 +590,6 @@ while(1)
         mh < mime_header_list + mime_header_list_size;
         mh++) if (strncmpic(mh->name, header, mh->namelen) == 0)
       {
         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;
 
       uschar * p = header + mh->namelen;
       uschar * q;
 
@@ -624,7 +598,7 @@ while(1)
 
       for (q = p; *q != ';' && *q; q++) ;
       *mh->value = string_copynlc(p, q-p);
 
       for (q = p; *q != ';' && *q; q++) ;
       *mh->value = string_copynlc(p, q-p);
-      DEBUG(D_acl) debug_printf("found %s MIME header, value is '%s'\n",
+      DEBUG(D_acl) debug_printf("MIME: found %s header, value is '%s'\n",
        mh->name, *mh->value);
 
       if (*(p = q)) p++;                       /* jump past the ; */
        mh->name, *mh->value);
 
       if (*(p = q)) p++;                       /* jump past the ; */
@@ -642,11 +616,11 @@ while(1)
          {
          mime_parameter * mp;
 
          {
          mime_parameter * mp;
 
-         DEBUG(D_acl) debug_printf("  considering paramlist '%s'\n", p);
+         DEBUG(D_acl) debug_printf("MIME:   considering paramlist '%s'\n", p);
 
          if (  !mime_filename
 
          if (  !mime_filename
-            && strncmpic("content-disposition:", header, 20) == 0
-            && strncmpic("filename*", p, 9) == 0
+            && strncmpic(CUS"content-disposition:", header, 20) == 0
+            && strncmpic(CUS"filename*", p, 9) == 0
             )
            {                                   /* RFC 2231 filename */
            uschar * q;
             )
            {                                   /* RFC 2231 filename */
            uschar * q;
@@ -676,26 +650,27 @@ while(1)
                  uschar * s = q;
 
                  /* look for a ' in the "filename" */
                  uschar * s = q;
 
                  /* look for a ' in the "filename" */
-                 while(*s != '\'' && *s) s++;  /* s is ' or NUL */
+                 while(*s != '\'' && *s) s++;  /* s is 1st ' or NUL */
 
                  if ((size = s-q) > 0)
 
                  if ((size = s-q) > 0)
-                   {
                    mime_filename_charset = string_copyn(q, size);
                    mime_filename_charset = string_copyn(q, size);
-                   p = s;
 
 
-                   while(*p == '\'' && *p) p++; /* p is after ' */
-                   }
+                 if (*(p = s)) p++;
+                 while(*p == '\'') p++;        /* p is after 2nd ' */
                  }
                else
                  p = q;
 
                  }
                else
                  p = q;
 
-               temp_string = expand_string(string_sprintf(
-                 "=?%s?Q?${sg{%s}{\\N%%([\\dA-Fa-f]{2})\\N}{=\\$1}}?=",
-                 mime_filename_charset, p));
-               slen = Ustrlen(temp_string);
+               DEBUG(D_acl) debug_printf("MIME:    charset %s fname '%s'\n",
+                 mime_filename_charset ? mime_filename_charset : US"<NULL>", p);
 
 
-               temp_string = rfc2047_decode(temp_string, FALSE, NULL, 32,
+               temp_string = rfc2231_to_2047(p, mime_filename_charset, &slen);
+               DEBUG(D_acl) debug_printf("MIME:    2047-name %s\n", temp_string);
+
+               temp_string = rfc2047_decode(temp_string, FALSE, NULL, ' ',
                  NULL, &err_msg);
                  NULL, &err_msg);
+               DEBUG(D_acl) debug_printf("MIME:    plain-name %s\n", temp_string);
+
                size = Ustrlen(temp_string);
 
                if (size == slen)
                size = Ustrlen(temp_string);
 
                if (size == slen)
@@ -730,7 +705,7 @@ while(1)
                    &dummy_errstr)
                : NULL;
              DEBUG(D_acl) debug_printf(
                    &dummy_errstr)
                : NULL;
              DEBUG(D_acl) debug_printf(
-               " found %s MIME parameter in %s header, value '%s'\n",
+               "MIME:  found %s parameter in %s header, value '%s'\n",
                mp->name, mh->name, *mp->value);
 
              break;                    /* done matching param names */
                mp->name, mh->name, *mp->value);
 
              break;                    /* done matching param names */
@@ -743,12 +718,12 @@ while(1)
          if (*p) p++;
          }                             /* param scan on line */
 
          if (*p) p++;
          }                             /* param scan on line */
 
-       if (strncmpic("content-disposition:", header, 20) == 0)
+       if (strncmpic(CUS"content-disposition:", header, 20) == 0)
          {
          if (decoding_failed) mime_filename = mime_fname_rfc2231;
 
          DEBUG(D_acl) debug_printf(
          {
          if (decoding_failed) mime_filename = mime_fname_rfc2231;
 
          DEBUG(D_acl) debug_printf(
-           " found %s MIME parameter in %s header, value is '%s'\n",
+           "MIME:  found %s parameter in %s header, value is '%s'\n",
            "filename", mh->name, mime_filename);
          }
        }
            "filename", mh->name, mime_filename);
          }
        }
@@ -789,8 +764,9 @@ while(1)
        (nested_context.boundary != NULL) &&
        (Ustrncmp(mime_content_type,"multipart",9) == 0) )
     {
        (nested_context.boundary != NULL) &&
        (Ustrncmp(mime_content_type,"multipart",9) == 0) )
     {
-    DEBUG(D_acl) debug_printf("Entering multipart recursion, boundary '%s'\n",
-      nested_context.boundary);
+    DEBUG(D_acl)
+      debug_printf("MIME: Entering multipart recursion, boundary '%s'\n",
+       nested_context.boundary);
 
     nested_context.context =
       context && context->context == MBC_ATTACHMENT
 
     nested_context.context =
       context && context->context == MBC_ATTACHMENT