gpg: Unfinished support for v5 signatures.
[gnupg.git] / tools / mime-maker.c
index 88f9d5f..91eab82 100644 (file)
@@ -1,20 +1,21 @@
 /* mime-maker.c - Create MIME structures
  * Copyright (C) 2016 g10 Code GmbH
+ * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
  *
  * This file is part of GnuPG.
  *
- * GnuPG is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
  *
- * GnuPG is distributed in the hope that it will be useful,
+ * This file is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
+ * GNU Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
@@ -22,8 +23,9 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "util.h"
-#include "zb32.h"
+#include "../common/util.h"
+#include "../common/zb32.h"
+#include "rfc822parse.h"
 #include "mime-maker.h"
 
 
@@ -43,13 +45,13 @@ struct part_s
 {
   struct part_s *next;  /* Next part in the current container.  */
   struct part_s *child; /* Child container.  */
-  char *mediatype;      /* Mediatype of the container (malloced). */
   char *boundary;       /* Malloced boundary string.  */
   header_t headers;     /* List of headers.  */
   header_t *headers_tail;/* Address of last header in chain.  */
   size_t bodylen;       /* Length of BODY.   */
   char *body;           /* Malloced buffer with the body.  This is the
                          * non-encoded value.  */
+  unsigned int partid;   /* The part ID.  */
 };
 typedef struct part_s *part_t;
 
@@ -66,6 +68,8 @@ struct mime_maker_context_s
   part_t mail;                 /* The MIME tree.  */
   part_t current_part;
 
+  unsigned int partid_counter; /* Counter assign part ids.  */
+
   int boundary_counter;  /* Used to create easy to read boundaries.  */
   char *boundary_suffix; /* Random string used in the boundaries.  */
 
@@ -109,7 +113,6 @@ release_parts (part_t part)
           part->headers = hdrnext;
         }
       release_parts (part->child);
-      xfree (part->mediatype);
       xfree (part->boundary);
       xfree (part->body);
       xfree (part);
@@ -156,12 +159,13 @@ dump_parts (part_t part, int level)
 
   for (; part; part = part->next)
     {
-      log_debug ("%*s[part]\n", level*2, "");
+      log_debug ("%*s[part %u]\n", level*2, "", part->partid);
       for (hdr = part->headers; hdr; hdr = hdr->next)
         {
           log_debug ("%*s%s: %s\n", level*2, "", hdr->name, hdr->value);
         }
-      log_debug ("%*s[body %zu bytes]\n", level*2, "", part->bodylen);
+      if (part->body)
+        log_debug ("%*s[body %zu bytes]\n", level*2, "", part->bodylen);
       if (part->child)
         {
           log_debug ("%*s[container]\n", level*2, "");
@@ -195,10 +199,26 @@ find_parent (part_t root, part_t needle)
   return NULL;
 }
 
+/* Find the part node from the PARTID.  */
+static part_t
+find_part (part_t root, unsigned int partid)
+{
+  part_t node, n;
+
+  for (node = root->child; node; node = node->next)
+    {
+      if (node->partid == partid)
+        return root;
+      if ((n = find_part (node, partid)))
+        return n;
+    }
+  return NULL;
+}
+
 
 /* Create a boundary string.  Outr codes is aware of the general
  * structure of that string (gebins with "=-=") so that
- * it can protect against accidently used boundaries within the
+ * it can protect against accidentally-used boundaries within the
  * content.   */
 static char *
 generate_boundary (mime_maker_t ctx)
@@ -228,7 +248,11 @@ ensure_part (mime_maker_t ctx, part_t *r_parent)
     {
       ctx->mail = xtrycalloc (1, sizeof *ctx->mail);
       if (!ctx->mail)
-        return gpg_error_from_syserror ();
+        {
+          if (r_parent)
+            *r_parent = NULL;
+          return gpg_error_from_syserror ();
+        }
       log_assert (!ctx->current_part);
       ctx->current_part = ctx->mail;
       ctx->current_part->headers_tail = &ctx->current_part->headers;
@@ -241,38 +265,6 @@ ensure_part (mime_maker_t ctx, part_t *r_parent)
 }
 
 
-/* Transform a header name into a standard capitalized format.
- * "Content-Type".  Conversion stops at the colon. */
-static void
-capitalize_header_name (char *name)
-{
-  unsigned char *p = name;
-  int first = 1;
-
-  /* Special cases first.  */
-  if (!ascii_strcasecmp (name, "MIME-Version"))
-    {
-      strcpy (name, "MIME-Version");
-      return;
-    }
-
-  /* Regular cases.  */
-  for (; *p && *p != ':'; p++)
-    {
-      if (*p == '-')
-        first = 1;
-      else if (first)
-        {
-          if (*p >= 'a' && *p <= 'z')
-            *p = *p - 'a' + 'A';
-          first = 0;
-        }
-      else if (*p >= 'A' && *p <= 'Z')
-        *p = *p - 'A' + 'a';
-    }
-}
-
-
 /* Check whether a header with NAME has already been set into PART.
  * NAME must be in canonical capitalized format.  Return true or
  * false. */
@@ -294,13 +286,36 @@ add_header (part_t part, const char *name, const char *value)
 {
   gpg_error_t err;
   header_t hdr;
+  size_t namelen;
+  const char *s;
+  char *p;
 
-  hdr = xtrymalloc (sizeof *hdr + strlen (name));
+  if (!value)
+    {
+      s = strchr (name, '=');
+      if (!s)
+        return gpg_error (GPG_ERR_INV_ARG);
+      namelen = s - name;
+      value = s+1;
+    }
+  else
+    namelen = strlen (name);
+
+  hdr = xtrymalloc (sizeof *hdr + namelen);
   if (!hdr)
     return gpg_error_from_syserror ();
   hdr->next = NULL;
-  strcpy (hdr->name, name);
-  capitalize_header_name (hdr->name);
+  memcpy (hdr->name, name, namelen);
+  hdr->name[namelen] = 0;
+
+  /* Check that the header name is valid.  */
+  if (!rfc822_valid_header_name_p (hdr->name))
+    {
+      xfree (hdr);
+      return gpg_error (GPG_ERR_INV_NAME);
+    }
+
+  rfc822_capitalize_header_name (hdr->name);
   hdr->value = xtrystrdup (value);
   if (!hdr->value)
     {
@@ -308,21 +323,42 @@ add_header (part_t part, const char *name, const char *value)
       xfree (hdr);
       return err;
     }
-  *part->headers_tail = hdr;
-  part->headers_tail = &hdr->next;
+
+  for (p = hdr->value + strlen (hdr->value) - 1;
+       (p >= hdr->value
+        && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r'));
+       p--)
+    *p = 0;
+  if (!(p >= hdr->value))
+    {
+      xfree (hdr->value);
+      xfree (hdr);
+      return gpg_error (GPG_ERR_INV_VALUE);  /* Only spaces.  */
+    }
+
+  if (part)
+    {
+      *part->headers_tail = hdr;
+      part->headers_tail = &hdr->next;
+    }
+  else
+    xfree (hdr);
 
   return 0;
 }
 
 
 /* Add a header with NAME and VALUE to the current mail.  A LF in the
- * VALUE will be handled automagically.  If no container has been
- * added, the header will be used for the regular mail headers and not
- * for a MIME part.  If the current part is in a container and a body
- * has been added, we append a new part to the current container.
- * Thus for a non-MIME mail the caller needs to call this function
- * followed by a call to add a body.  When adding a Content-Type the
- * boundary parameter must not be included.
+ * VALUE will be handled automagically.  If NULL is used for VALUE it
+ * is expected that the NAME has the format "NAME=VALUE" and VALUE is
+ * taken from there.
+ *
+ * If no container has been added, the header will be used for the
+ * regular mail headers and not for a MIME part.  If the current part
+ * is in a container and a body has been added, we append a new part
+ * to the current container.  Thus for a non-MIME mail the caller
+ * needs to call this function followed by a call to add a body.  When
+ * adding a Content-Type the boundary parameter must not be included.
  */
 gpg_error_t
 mime_maker_add_header (mime_maker_t ctx, const char *name, const char *value)
@@ -330,24 +366,29 @@ mime_maker_add_header (mime_maker_t ctx, const char *name, const char *value)
   gpg_error_t err;
   part_t part, parent;
 
+  /* Hack to use this function for a syntax check of NAME and VALUE.  */
+  if (!ctx)
+    return add_header (NULL, name, value);
+
   err = ensure_part (ctx, &parent);
   if (err)
     return err;
   part = ctx->current_part;
 
-  if (part->body && !parent)
+  if ((part->body || part->child) && !parent)
     {
       /* We already have a body but no parent.  Adding another part is
        * thus not possible.  */
       return gpg_error (GPG_ERR_CONFLICT);
     }
-  if (part->body)
+  if (part->body || part->child)
     {
       /* We already have a body and there is a parent.  We now append
        * a new part to the current container.  */
       part = xtrycalloc (1, sizeof *part);
       if (!part)
         return gpg_error_from_syserror ();
+      part->partid = ++ctx->partid_counter;
       part->headers_tail = &part->headers;
       log_assert (!ctx->current_part->next);
       ctx->current_part->next = part;
@@ -398,7 +439,8 @@ add_body (mime_maker_t ctx, const void *data, size_t datalen)
 
 
 /* Add STRING as body to the mail or the current MIME container.  A
- * second call to this function is not allowed.
+ * second call to this function or mime_make_add_body_data is not
+ * allowed.
  *
  * FIXME: We may want to have an append_body to add more data to a body.
  */
@@ -409,6 +451,16 @@ mime_maker_add_body (mime_maker_t ctx, const char *string)
 }
 
 
+/* Add (DATA,DATALEN) as body to the mail or the current MIME
+ * container.  Note that a second call to this function or to
+ * mime_make_add_body is not allowed.  */
+gpg_error_t
+mime_maker_add_body_data (mime_maker_t ctx, const void *data, size_t datalen)
+{
+  return add_body (ctx, data, datalen);
+}
+
+
 /* This is the same as mime_maker_add_body but takes a stream as
  * argument.  As of now the stream is copied to the MIME object but
  * eventually we may delay that and read the stream only at the time
@@ -431,96 +483,139 @@ mime_maker_add_stream (mime_maker_t ctx, estream_t *stream_addr)
 }
 
 
-/* Add a new MIME container.  The caller needs to provide the media
- * and media-subtype in MEDIATYPE.  If MEDIATYPE is NULL
- * "multipart/mixed" is assumed.  This function will then add a
- * Content-Type header with that media type and an approriate boundary
- * string to the parent part.  */
+/* Add a new MIME container.  A container can be used instead of a
+ * body.  */
 gpg_error_t
-mime_maker_add_container (mime_maker_t ctx, const char *mediatype)
+mime_maker_add_container (mime_maker_t ctx)
 {
   gpg_error_t err;
   part_t part;
 
-  if (!mediatype)
-    mediatype = "multipart/mixed";
-
   err = ensure_part (ctx, NULL);
   if (err)
     return err;
   part = ctx->current_part;
+
   if (part->body)
     return gpg_error (GPG_ERR_CONFLICT); /* There is already a body. */
-  if (part->child || part->mediatype || part->boundary)
+  if (part->child || part->boundary)
     return gpg_error (GPG_ERR_CONFLICT); /* There is already a container. */
 
-  /* If a content type has not yet been set, do it now.  The boundary
-   * will be added while writing the headers.  */
-  if (!have_header (ctx->mail, "Content-Type"))
-    {
-      err = add_header (ctx->mail, "Content-Type", mediatype);
-      if (err)
-        return err;
-    }
-
   /* Create a child node.  */
   part->child = xtrycalloc (1, sizeof *part->child);
   if (!part->child)
     return gpg_error_from_syserror ();
   part->child->headers_tail = &part->child->headers;
 
-  part->mediatype = xtrystrdup (mediatype);
-  if (!part->mediatype)
-    {
-      err = gpg_error_from_syserror ();
-      xfree (part->child);
-      part->child = NULL;
-      return err;
-    }
-
   part->boundary = generate_boundary (ctx);
   if (!part->boundary)
     {
       err = gpg_error_from_syserror ();
       xfree (part->child);
       part->child = NULL;
-      xfree (part->mediatype);
-      part->mediatype = NULL;
       return err;
     }
 
   part = part->child;
+  part->partid = ++ctx->partid_counter;
   ctx->current_part = part;
 
   return 0;
 }
 
 
-/* Write the Content-Type header with the boundary value.  */
+/* Finish the current container.  */
+gpg_error_t
+mime_maker_end_container (mime_maker_t ctx)
+{
+  gpg_error_t err;
+  part_t parent;
+
+  err = ensure_part (ctx, &parent);
+  if (err)
+    return err;
+  if (!parent)
+    return gpg_error (GPG_ERR_CONFLICT); /* No container.  */
+  while (parent->next)
+    parent = parent->next;
+  ctx->current_part = parent;
+  return 0;
+}
+
+
+/* Return the part-ID of the current part. */
+unsigned int
+mime_maker_get_partid (mime_maker_t ctx)
+{
+  if (ensure_part (ctx, NULL))
+    return 0; /* Ooops.  */
+  return ctx->current_part->partid;
+}
+
+
+/* Write a header and handle emdedded LFs.  If BOUNDARY is not NULL it
+ * is appended to the value.  */
+/* Fixme: Add automatic line wrapping.  */
 static gpg_error_t
-write_ct_with_boundary (mime_maker_t ctx,
-                        const char *value, const char *boundary)
+write_header (mime_maker_t ctx, const char *name, const char *value,
+              const char *boundary)
 {
   const char *s;
 
-  if (!*value)
-    return gpg_error (GPG_ERR_INV_VALUE);  /* Empty string.  */
-
-  for (s=value + strlen (value) - 1;
-       (s >= value
-        && (*s == ' ' || *s == '\t' || *s == '\n'));
-       s--)
-    ;
-  if (!(s >= value))
-    return gpg_error (GPG_ERR_INV_VALUE);  /* Only spaces.  */
-
-  /* Fixme: We should use a dedicated header write functions which
-   * properly wraps the header.  */
-  es_fprintf (ctx->outfp, "Content-Type: %s%s\n\tboundary=\"%s\"\n",
-              value,
-              (*s == ';')? "":";",
-              boundary);
-  return 0;
+  es_fprintf (ctx->outfp, "%s: ", name);
+
+  /* Note that add_header made sure that VALUE does not end with a LF.
+   * Thus we can assume that a LF is followed by non-whitespace.  */
+  for (s = value; *s; s++)
+    {
+      if (*s == '\n')
+        es_fputs ("\r\n\t", ctx->outfp);
+      else
+        es_fputc (*s, ctx->outfp);
+    }
+  if (boundary)
+    {
+      if (s > value && s[-1] != ';')
+        es_fputc (';', ctx->outfp);
+      es_fprintf (ctx->outfp, "\r\n\tboundary=\"%s\"", boundary);
+    }
+
+  es_fputs ("\r\n", ctx->outfp);
+
+  return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
+}
+
+
+static gpg_error_t
+write_gap (mime_maker_t ctx)
+{
+  es_fputs ("\r\n", ctx->outfp);
+  return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
+}
+
+
+static gpg_error_t
+write_boundary (mime_maker_t ctx, const char *boundary, int last)
+{
+  es_fprintf (ctx->outfp, "\r\n--%s%s\r\n", boundary, last?"--":"");
+  return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
+}
+
+
+/* Fixme: Apply required encoding.  */
+static gpg_error_t
+write_body (mime_maker_t ctx, const void *body, size_t bodylen)
+{
+  const char *s;
+
+  for (s = body; bodylen; s++, bodylen--)
+    {
+      if (*s == '\n' && !(s > (const char *)body && s[-1] == '\r'))
+        es_fputc ('\r', ctx->outfp);
+      es_fputc (*s, ctx->outfp);
+    }
+
+  return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0;
 }
 
 
@@ -536,33 +631,39 @@ write_tree (mime_maker_t ctx, part_t parent, part_t part)
       for (hdr = part->headers; hdr; hdr = hdr->next)
         {
           if (part->child && !strcmp (hdr->name, "Content-Type"))
-            write_ct_with_boundary (ctx, hdr->value, part->boundary);
+            err = write_header (ctx, hdr->name, hdr->value, part->boundary);
           else
-            es_fprintf (ctx->outfp, "%s: %s\n", hdr->name, hdr->value);
+            err = write_header (ctx, hdr->name, hdr->value, NULL);
+          if (err)
+            return err;
         }
-      es_fputc ('\n', ctx->outfp);
+      err = write_gap (ctx);
+      if (err)
+        return err;
       if (part->body)
         {
-          if (es_write (ctx->outfp, part->body, part->bodylen, NULL))
-            return gpg_error_from_syserror ();
+          err = write_body (ctx, part->body, part->bodylen);
+          if (err)
+            return err;
         }
       if (part->child)
         {
           log_assert (part->boundary);
-          if (es_fprintf (ctx->outfp, "\n--%s\n", part->boundary) < 0)
-            return gpg_error_from_syserror ();
-          err = write_tree (ctx, part, part->child);
+          err = write_boundary (ctx, part->boundary, 0);
+          if (!err)
+            err = write_tree (ctx, part, part->child);
+          if (!err)
+            err = write_boundary (ctx, part->boundary, 1);
           if (err)
             return err;
-          if (es_fprintf (ctx->outfp, "\n--%s--\n", part->boundary) < 0)
-            return gpg_error_from_syserror ();
         }
 
       if (part->next)
         {
           log_assert (parent && parent->boundary);
-          if (es_fprintf (ctx->outfp, "\n--%s\n", parent->boundary) < 0)
-            return gpg_error_from_syserror ();
+          err = write_boundary (ctx, parent->boundary, 0);
+          if (err)
+            return err;
         }
     }
   return 0;
@@ -598,13 +699,14 @@ add_missing_headers (mime_maker_t ctx)
         goto leave;
     }
 
+  err = 0;
 
  leave:
   return err;
 }
 
 
-/* Create message from the tree MIME and write it to FP.  Noet that
+/* Create message from the tree MIME and write it to FP.  Note that
  * the output uses only a LF and a later called sendmail(1) is
  * expected to convert them to network line endings.  */
 gpg_error_t
@@ -622,3 +724,54 @@ mime_maker_make (mime_maker_t ctx, estream_t fp)
   ctx->outfp = NULL;
   return err;
 }
+
+
+/* Create a stream object from the MIME part identified by PARTID and
+ * store it at R_STREAM.  If PARTID identifies a container the entire
+ * tree is returned. Using that function may read stream objects
+ * which have been added as MIME bodies.  The caller must close the
+ * stream object. */
+gpg_error_t
+mime_maker_get_part (mime_maker_t ctx, unsigned int partid, estream_t *r_stream)
+{
+  gpg_error_t err;
+  part_t part;
+  estream_t fp;
+
+  *r_stream = NULL;
+
+  /* When the entire tree is requested, we make sure that all missing
+   * headers are applied.  We don't do that if only a part is
+   * requested because the additional headers (like Date:) will only
+   * be added to part 0 headers anyway. */
+  if (!partid)
+    {
+       err = add_missing_headers (ctx);
+       if (err)
+         return err;
+       part = ctx->mail;
+    }
+  else
+    part = find_part (ctx->mail, partid);
+
+  /* For now we use a memory stream object; however it would also be
+   * possible to create an object created on the fly while the caller
+   * is reading the returned stream.  */
+  fp = es_fopenmem (0, "w+b");
+  if (!fp)
+    return gpg_error_from_syserror ();
+
+  ctx->outfp = fp;
+  err = write_tree (ctx, NULL, part);
+  ctx->outfp = NULL;
+
+  if (!err)
+    {
+      es_rewind (fp);
+      *r_stream = fp;
+    }
+  else
+    es_fclose (fp);
+
+  return err;
+}