2016-06-29 11:02:36 +02:00
|
|
|
/* mime-maker.c - Create MIME structures
|
|
|
|
* Copyright (C) 2016 g10 Code GmbH
|
2017-06-19 12:37:52 +02:00
|
|
|
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
|
2016-06-29 11:02:36 +02:00
|
|
|
*
|
|
|
|
* This file is part of GnuPG.
|
|
|
|
*
|
2017-06-19 12:37:52 +02:00
|
|
|
* 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.
|
2016-06-29 11:02:36 +02:00
|
|
|
*
|
2017-06-19 12:37:52 +02:00
|
|
|
* This file is distributed in the hope that it will be useful,
|
2016-06-29 11:02:36 +02:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2017-06-19 12:37:52 +02:00
|
|
|
* GNU Lesser General Public License for more details.
|
2016-06-29 11:02:36 +02:00
|
|
|
*
|
2017-06-19 12:37:52 +02:00
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
2016-11-05 12:02:19 +01:00
|
|
|
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
2016-06-29 11:02:36 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2017-03-07 12:21:23 +01:00
|
|
|
#include "../common/util.h"
|
|
|
|
#include "../common/zb32.h"
|
2016-06-29 11:02:36 +02:00
|
|
|
#include "mime-maker.h"
|
|
|
|
|
|
|
|
|
Fix more spelling
* NEWS, acinclude.m4, agent/command-ssh.c, agent/command.c,
agent/gpg-agent.c, agent/keyformat.txt, agent/protect-tool.c,
common/asshelp.c, common/b64enc.c, common/recsel.c, doc/DETAILS,
doc/HACKING, doc/Notes, doc/TRANSLATE, doc/dirmngr.texi,
doc/faq.org, doc/gpg-agent.texi, doc/gpg.texi, doc/gpgsm.texi,
doc/instguide.texi, g10/armor.c, g10/gpg.c, g10/keyedit.c,
g10/mainproc.c, g10/pkclist.c, g10/tofu.c, g13/sh-cmd.c,
g13/sh-dmcrypt.c, kbx/keybox-init.c, m4/pkg.m4, sm/call-dirmngr.c,
sm/gpgsm.c, tests/Makefile.am, tests/gpgscm/Manual.txt,
tests/gpgscm/scheme.c, tests/openpgp/gpgv-forged-keyring.scm,
tests/openpgp/multisig.test, tests/openpgp/verify.scm,
tests/pkits/README, tools/applygnupgdefaults,
tools/gpg-connect-agent.c, tools/mime-maker.c, tools/mime-parser.c:
minor spelling cleanup.
Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
2016-09-15 20:21:15 +02:00
|
|
|
/* All valid characters in a header name. */
|
2016-07-02 18:55:22 +02:00
|
|
|
#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \
|
|
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
|
|
|
|
"-01234567890")
|
|
|
|
|
2016-06-29 11:02:36 +02:00
|
|
|
/* An object to store an header. Also used for a list of headers. */
|
|
|
|
struct header_s
|
|
|
|
{
|
|
|
|
struct header_s *next;
|
|
|
|
char *value; /* Malloced value. */
|
|
|
|
char name[1]; /* Name. */
|
|
|
|
};
|
|
|
|
typedef struct header_s *header_t;
|
|
|
|
|
|
|
|
|
|
|
|
/* An object to store a MIME part. A part is the header plus the
|
|
|
|
* content (body). */
|
|
|
|
struct part_s
|
|
|
|
{
|
|
|
|
struct part_s *next; /* Next part in the current container. */
|
|
|
|
struct part_s *child; /* Child container. */
|
|
|
|
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. */
|
2016-09-29 12:29:27 +02:00
|
|
|
unsigned int partid; /* The part ID. */
|
2016-06-29 11:02:36 +02:00
|
|
|
};
|
|
|
|
typedef struct part_s *part_t;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Definition of the mime parser object. */
|
|
|
|
struct mime_maker_context_s
|
|
|
|
{
|
|
|
|
void *cookie; /* Cookie passed to all callbacks. */
|
|
|
|
|
|
|
|
unsigned int verbose:1; /* Enable verbose mode. */
|
|
|
|
unsigned int debug:1; /* Enable debug mode. */
|
|
|
|
|
|
|
|
part_t mail; /* The MIME tree. */
|
|
|
|
part_t current_part;
|
|
|
|
|
2016-09-29 12:29:27 +02:00
|
|
|
unsigned int partid_counter; /* Counter assign part ids. */
|
|
|
|
|
2016-06-29 11:02:36 +02:00
|
|
|
int boundary_counter; /* Used to create easy to read boundaries. */
|
|
|
|
char *boundary_suffix; /* Random string used in the boundaries. */
|
|
|
|
|
|
|
|
struct b64state *b64state; /* NULL or malloced Base64 decoder state. */
|
|
|
|
|
|
|
|
/* Helper to convey the output stream to recursive functions. */
|
|
|
|
estream_t outfp;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* Create a new mime make object. COOKIE is a values woich will be
|
|
|
|
* used as first argument for all callbacks registered with this
|
|
|
|
* object. */
|
|
|
|
gpg_error_t
|
|
|
|
mime_maker_new (mime_maker_t *r_maker, void *cookie)
|
|
|
|
{
|
|
|
|
mime_maker_t ctx;
|
|
|
|
|
|
|
|
*r_maker = NULL;
|
|
|
|
|
|
|
|
ctx = xtrycalloc (1, sizeof *ctx);
|
|
|
|
if (!ctx)
|
|
|
|
return gpg_error_from_syserror ();
|
|
|
|
ctx->cookie = cookie;
|
|
|
|
|
|
|
|
*r_maker = ctx;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
release_parts (part_t part)
|
|
|
|
{
|
|
|
|
while (part)
|
|
|
|
{
|
|
|
|
part_t partnext = part->next;
|
|
|
|
while (part->headers)
|
|
|
|
{
|
|
|
|
header_t hdrnext = part->headers->next;
|
|
|
|
xfree (part->headers);
|
|
|
|
part->headers = hdrnext;
|
|
|
|
}
|
|
|
|
release_parts (part->child);
|
|
|
|
xfree (part->boundary);
|
|
|
|
xfree (part->body);
|
|
|
|
xfree (part);
|
|
|
|
part = partnext;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Release a mime maker object. */
|
|
|
|
void
|
|
|
|
mime_maker_release (mime_maker_t ctx)
|
|
|
|
{
|
|
|
|
if (!ctx)
|
|
|
|
return;
|
|
|
|
|
|
|
|
release_parts (ctx->mail);
|
|
|
|
xfree (ctx->boundary_suffix);
|
|
|
|
xfree (ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Set verbose and debug mode. */
|
|
|
|
void
|
|
|
|
mime_maker_set_verbose (mime_maker_t ctx, int level)
|
|
|
|
{
|
|
|
|
if (!level)
|
|
|
|
{
|
|
|
|
ctx->verbose = 0;
|
|
|
|
ctx->debug = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ctx->verbose = 1;
|
|
|
|
if (level > 10)
|
|
|
|
ctx->debug = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
dump_parts (part_t part, int level)
|
|
|
|
{
|
|
|
|
header_t hdr;
|
|
|
|
|
|
|
|
for (; part; part = part->next)
|
|
|
|
{
|
2016-09-29 12:29:27 +02:00
|
|
|
log_debug ("%*s[part %u]\n", level*2, "", part->partid);
|
2016-06-29 11:02:36 +02:00
|
|
|
for (hdr = part->headers; hdr; hdr = hdr->next)
|
|
|
|
{
|
|
|
|
log_debug ("%*s%s: %s\n", level*2, "", hdr->name, hdr->value);
|
|
|
|
}
|
2016-09-29 10:20:38 +02:00
|
|
|
if (part->body)
|
|
|
|
log_debug ("%*s[body %zu bytes]\n", level*2, "", part->bodylen);
|
2016-06-29 11:02:36 +02:00
|
|
|
if (part->child)
|
|
|
|
{
|
|
|
|
log_debug ("%*s[container]\n", level*2, "");
|
|
|
|
dump_parts (part->child, level+1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Dump the mime tree for debugging. */
|
|
|
|
void
|
|
|
|
mime_maker_dump_tree (mime_maker_t ctx)
|
|
|
|
{
|
|
|
|
dump_parts (ctx->mail, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Find the parent node for NEEDLE starting at ROOT. */
|
|
|
|
static part_t
|
|
|
|
find_parent (part_t root, part_t needle)
|
|
|
|
{
|
|
|
|
part_t node, n;
|
|
|
|
|
|
|
|
for (node = root->child; node; node = node->next)
|
|
|
|
{
|
|
|
|
if (node == needle)
|
|
|
|
return root;
|
|
|
|
if ((n = find_parent (node, needle)))
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-09-29 17:38:06 +02:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2016-06-29 11:02:36 +02:00
|
|
|
|
|
|
|
/* Create a boundary string. Outr codes is aware of the general
|
|
|
|
* structure of that string (gebins with "=-=") so that
|
Fix more spelling
* NEWS, acinclude.m4, agent/command-ssh.c, agent/command.c,
agent/gpg-agent.c, agent/keyformat.txt, agent/protect-tool.c,
common/asshelp.c, common/b64enc.c, common/recsel.c, doc/DETAILS,
doc/HACKING, doc/Notes, doc/TRANSLATE, doc/dirmngr.texi,
doc/faq.org, doc/gpg-agent.texi, doc/gpg.texi, doc/gpgsm.texi,
doc/instguide.texi, g10/armor.c, g10/gpg.c, g10/keyedit.c,
g10/mainproc.c, g10/pkclist.c, g10/tofu.c, g13/sh-cmd.c,
g13/sh-dmcrypt.c, kbx/keybox-init.c, m4/pkg.m4, sm/call-dirmngr.c,
sm/gpgsm.c, tests/Makefile.am, tests/gpgscm/Manual.txt,
tests/gpgscm/scheme.c, tests/openpgp/gpgv-forged-keyring.scm,
tests/openpgp/multisig.test, tests/openpgp/verify.scm,
tests/pkits/README, tools/applygnupgdefaults,
tools/gpg-connect-agent.c, tools/mime-maker.c, tools/mime-parser.c:
minor spelling cleanup.
Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
2016-09-15 20:21:15 +02:00
|
|
|
* it can protect against accidentally-used boundaries within the
|
2016-06-29 11:02:36 +02:00
|
|
|
* content. */
|
|
|
|
static char *
|
|
|
|
generate_boundary (mime_maker_t ctx)
|
|
|
|
{
|
|
|
|
if (!ctx->boundary_suffix)
|
|
|
|
{
|
|
|
|
char buffer[12];
|
|
|
|
|
|
|
|
gcry_create_nonce (buffer, sizeof buffer);
|
|
|
|
ctx->boundary_suffix = zb32_encode (buffer, 8 * sizeof buffer);
|
|
|
|
if (!ctx->boundary_suffix)
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->boundary_counter++;
|
|
|
|
return es_bsprintf ("=-=%02d-%s=-=",
|
|
|
|
ctx->boundary_counter, ctx->boundary_suffix);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Ensure that the context has a MAIL and CURRENT_PART object and
|
|
|
|
* return the parent object if available */
|
|
|
|
static gpg_error_t
|
|
|
|
ensure_part (mime_maker_t ctx, part_t *r_parent)
|
|
|
|
{
|
|
|
|
if (!ctx->mail)
|
|
|
|
{
|
|
|
|
ctx->mail = xtrycalloc (1, sizeof *ctx->mail);
|
|
|
|
if (!ctx->mail)
|
2016-12-08 13:04:06 +01:00
|
|
|
{
|
|
|
|
if (r_parent)
|
|
|
|
*r_parent = NULL;
|
|
|
|
return gpg_error_from_syserror ();
|
|
|
|
}
|
2016-06-29 11:02:36 +02:00
|
|
|
log_assert (!ctx->current_part);
|
|
|
|
ctx->current_part = ctx->mail;
|
|
|
|
ctx->current_part->headers_tail = &ctx->current_part->headers;
|
|
|
|
}
|
|
|
|
log_assert (ctx->current_part);
|
|
|
|
if (r_parent)
|
|
|
|
*r_parent = find_parent (ctx->mail, ctx->current_part);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
static int
|
|
|
|
have_header (part_t part, const char *name)
|
|
|
|
{
|
|
|
|
header_t hdr;
|
|
|
|
|
|
|
|
for (hdr = part->headers; hdr; hdr = hdr->next)
|
|
|
|
if (!strcmp (hdr->name, name))
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Helper to add a header to a part. */
|
|
|
|
static gpg_error_t
|
|
|
|
add_header (part_t part, const char *name, const char *value)
|
|
|
|
{
|
|
|
|
gpg_error_t err;
|
|
|
|
header_t hdr;
|
2016-07-02 18:55:22 +02:00
|
|
|
size_t namelen;
|
|
|
|
const char *s;
|
2016-09-29 12:29:27 +02:00
|
|
|
char *p;
|
2016-07-02 18:55:22 +02:00
|
|
|
|
|
|
|
if (!value)
|
|
|
|
{
|
|
|
|
s = strchr (name, '=');
|
|
|
|
if (!s)
|
|
|
|
return gpg_error (GPG_ERR_INV_ARG);
|
|
|
|
namelen = s - name;
|
|
|
|
value = s+1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
namelen = strlen (name);
|
2016-06-29 11:02:36 +02:00
|
|
|
|
2016-07-02 18:55:22 +02:00
|
|
|
hdr = xtrymalloc (sizeof *hdr + namelen);
|
2016-06-29 11:02:36 +02:00
|
|
|
if (!hdr)
|
|
|
|
return gpg_error_from_syserror ();
|
|
|
|
hdr->next = NULL;
|
2016-07-02 18:55:22 +02:00
|
|
|
memcpy (hdr->name, name, namelen);
|
|
|
|
hdr->name[namelen] = 0;
|
|
|
|
|
|
|
|
/* Check that the header name is valid. We allow all lower and
|
|
|
|
* uppercase letters and, except for the first character, digits and
|
|
|
|
* the dash. */
|
|
|
|
if (strspn (hdr->name, HEADER_NAME_CHARS) != namelen
|
|
|
|
|| strchr ("-0123456789", *hdr->name))
|
|
|
|
{
|
|
|
|
xfree (hdr);
|
|
|
|
return gpg_error (GPG_ERR_INV_NAME);
|
|
|
|
}
|
|
|
|
|
2016-06-29 11:02:36 +02:00
|
|
|
capitalize_header_name (hdr->name);
|
|
|
|
hdr->value = xtrystrdup (value);
|
|
|
|
if (!hdr->value)
|
|
|
|
{
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
xfree (hdr);
|
|
|
|
return err;
|
|
|
|
}
|
2016-07-02 18:55:22 +02:00
|
|
|
|
2016-09-29 12:29:27 +02:00
|
|
|
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. */
|
|
|
|
}
|
|
|
|
|
2016-07-02 18:55:22 +02:00
|
|
|
if (part)
|
|
|
|
{
|
|
|
|
*part->headers_tail = hdr;
|
|
|
|
part->headers_tail = &hdr->next;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
xfree (hdr);
|
2016-06-29 11:02:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Add a header with NAME and VALUE to the current mail. A LF in the
|
2016-07-02 18:55:22 +02:00
|
|
|
* 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.
|
2016-06-29 11:02:36 +02:00
|
|
|
*/
|
|
|
|
gpg_error_t
|
|
|
|
mime_maker_add_header (mime_maker_t ctx, const char *name, const char *value)
|
|
|
|
{
|
|
|
|
gpg_error_t err;
|
|
|
|
part_t part, parent;
|
|
|
|
|
Fix more spelling
* NEWS, acinclude.m4, agent/command-ssh.c, agent/command.c,
agent/gpg-agent.c, agent/keyformat.txt, agent/protect-tool.c,
common/asshelp.c, common/b64enc.c, common/recsel.c, doc/DETAILS,
doc/HACKING, doc/Notes, doc/TRANSLATE, doc/dirmngr.texi,
doc/faq.org, doc/gpg-agent.texi, doc/gpg.texi, doc/gpgsm.texi,
doc/instguide.texi, g10/armor.c, g10/gpg.c, g10/keyedit.c,
g10/mainproc.c, g10/pkclist.c, g10/tofu.c, g13/sh-cmd.c,
g13/sh-dmcrypt.c, kbx/keybox-init.c, m4/pkg.m4, sm/call-dirmngr.c,
sm/gpgsm.c, tests/Makefile.am, tests/gpgscm/Manual.txt,
tests/gpgscm/scheme.c, tests/openpgp/gpgv-forged-keyring.scm,
tests/openpgp/multisig.test, tests/openpgp/verify.scm,
tests/pkits/README, tools/applygnupgdefaults,
tools/gpg-connect-agent.c, tools/mime-maker.c, tools/mime-parser.c:
minor spelling cleanup.
Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
2016-09-15 20:21:15 +02:00
|
|
|
/* Hack to use this function for a syntax check of NAME and VALUE. */
|
2016-07-02 18:55:22 +02:00
|
|
|
if (!ctx)
|
|
|
|
return add_header (NULL, name, value);
|
|
|
|
|
2016-06-29 11:02:36 +02:00
|
|
|
err = ensure_part (ctx, &parent);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
part = ctx->current_part;
|
|
|
|
|
2016-09-29 10:20:38 +02:00
|
|
|
if ((part->body || part->child) && !parent)
|
2016-06-29 11:02:36 +02:00
|
|
|
{
|
|
|
|
/* We already have a body but no parent. Adding another part is
|
|
|
|
* thus not possible. */
|
|
|
|
return gpg_error (GPG_ERR_CONFLICT);
|
|
|
|
}
|
2016-09-29 10:20:38 +02:00
|
|
|
if (part->body || part->child)
|
2016-06-29 11:02:36 +02:00
|
|
|
{
|
|
|
|
/* 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 ();
|
2016-09-29 12:29:27 +02:00
|
|
|
part->partid = ++ctx->partid_counter;
|
2016-06-29 11:02:36 +02:00
|
|
|
part->headers_tail = &part->headers;
|
|
|
|
log_assert (!ctx->current_part->next);
|
|
|
|
ctx->current_part->next = part;
|
|
|
|
ctx->current_part = part;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If no NAME and no VALUE has been given we do not add a header.
|
|
|
|
* This can be used to create a new part without any header. */
|
|
|
|
if (!name && !value)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* If we add Content-Type, make sure that we have a MIME-version
|
|
|
|
* header first; this simply looks better. */
|
|
|
|
if (!ascii_strcasecmp (name, "Content-Type")
|
|
|
|
&& !have_header (ctx->mail, "MIME-Version"))
|
|
|
|
{
|
|
|
|
err = add_header (ctx->mail, "MIME-Version", "1.0");
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
return add_header (part, name, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Helper for mime_maker_add_{body,stream}. */
|
|
|
|
static gpg_error_t
|
|
|
|
add_body (mime_maker_t ctx, const void *data, size_t datalen)
|
|
|
|
{
|
|
|
|
gpg_error_t err;
|
|
|
|
part_t part, parent;
|
|
|
|
|
|
|
|
err = ensure_part (ctx, &parent);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
part = ctx->current_part;
|
|
|
|
if (part->body)
|
|
|
|
return gpg_error (GPG_ERR_CONFLICT);
|
|
|
|
|
|
|
|
part->body = xtrymalloc (datalen? datalen : 1);
|
|
|
|
if (!part->body)
|
|
|
|
return gpg_error_from_syserror ();
|
|
|
|
part->bodylen = datalen;
|
|
|
|
if (data)
|
|
|
|
memcpy (part->body, data, datalen);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Add STRING as body to the mail or the current MIME container. A
|
|
|
|
* second call to this function is not allowed.
|
|
|
|
*
|
|
|
|
* FIXME: We may want to have an append_body to add more data to a body.
|
|
|
|
*/
|
|
|
|
gpg_error_t
|
|
|
|
mime_maker_add_body (mime_maker_t ctx, const char *string)
|
|
|
|
{
|
|
|
|
return add_body (ctx, string, strlen (string));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 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
|
|
|
|
* it is needed. Note that the address of the stream object must be
|
|
|
|
* passed and that the ownership of the stream is transferred to this
|
|
|
|
* MIME object. To indicate the latter the function will store NULL
|
|
|
|
* at the ADDR_STREAM so that a caller can't use that object anymore
|
|
|
|
* except for es_fclose which accepts a NULL pointer. */
|
|
|
|
gpg_error_t
|
|
|
|
mime_maker_add_stream (mime_maker_t ctx, estream_t *stream_addr)
|
|
|
|
{
|
|
|
|
void *data;
|
|
|
|
size_t datalen;
|
|
|
|
|
|
|
|
es_rewind (*stream_addr);
|
|
|
|
if (es_fclose_snatch (*stream_addr, &data, &datalen))
|
|
|
|
return gpg_error_from_syserror ();
|
|
|
|
*stream_addr = NULL;
|
|
|
|
return add_body (ctx, data, datalen);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-09-29 10:20:38 +02:00
|
|
|
/* Add a new MIME container. A container can be used instead of a
|
|
|
|
* body. */
|
2016-06-29 11:02:36 +02:00
|
|
|
gpg_error_t
|
2016-09-29 10:20:38 +02:00
|
|
|
mime_maker_add_container (mime_maker_t ctx)
|
2016-06-29 11:02:36 +02:00
|
|
|
{
|
|
|
|
gpg_error_t err;
|
|
|
|
part_t part;
|
|
|
|
|
|
|
|
err = ensure_part (ctx, NULL);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
part = ctx->current_part;
|
2016-09-29 10:20:38 +02:00
|
|
|
|
2016-06-29 11:02:36 +02:00
|
|
|
if (part->body)
|
|
|
|
return gpg_error (GPG_ERR_CONFLICT); /* There is already a body. */
|
2016-09-29 10:20:38 +02:00
|
|
|
if (part->child || part->boundary)
|
2016-06-29 11:02:36 +02:00
|
|
|
return gpg_error (GPG_ERR_CONFLICT); /* There is already a container. */
|
|
|
|
|
|
|
|
/* 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->boundary = generate_boundary (ctx);
|
|
|
|
if (!part->boundary)
|
|
|
|
{
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
xfree (part->child);
|
|
|
|
part->child = NULL;
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
part = part->child;
|
2016-09-29 12:29:27 +02:00
|
|
|
part->partid = ++ctx->partid_counter;
|
2016-06-29 11:02:36 +02:00
|
|
|
ctx->current_part = part;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-09-29 10:20:38 +02:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-09-29 12:29:27 +02:00
|
|
|
/* 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. */
|
2016-06-29 11:02:36 +02:00
|
|
|
static gpg_error_t
|
2016-09-29 12:29:27 +02:00
|
|
|
write_header (mime_maker_t ctx, const char *name, const char *value,
|
|
|
|
const char *boundary)
|
2016-06-29 11:02:36 +02:00
|
|
|
{
|
|
|
|
const char *s;
|
|
|
|
|
2016-09-29 12:29:27 +02:00
|
|
|
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;
|
2016-06-29 11:02:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Recursive worker for mime_maker_make. */
|
|
|
|
static gpg_error_t
|
|
|
|
write_tree (mime_maker_t ctx, part_t parent, part_t part)
|
|
|
|
{
|
|
|
|
gpg_error_t err;
|
|
|
|
header_t hdr;
|
|
|
|
|
|
|
|
for (; part; part = part->next)
|
|
|
|
{
|
|
|
|
for (hdr = part->headers; hdr; hdr = hdr->next)
|
|
|
|
{
|
|
|
|
if (part->child && !strcmp (hdr->name, "Content-Type"))
|
2016-09-29 12:29:27 +02:00
|
|
|
err = write_header (ctx, hdr->name, hdr->value, part->boundary);
|
2016-06-29 11:02:36 +02:00
|
|
|
else
|
2016-09-29 12:29:27 +02:00
|
|
|
err = write_header (ctx, hdr->name, hdr->value, NULL);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2016-06-29 11:02:36 +02:00
|
|
|
}
|
2016-09-29 12:29:27 +02:00
|
|
|
err = write_gap (ctx);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2016-06-29 11:02:36 +02:00
|
|
|
if (part->body)
|
|
|
|
{
|
2016-09-29 12:29:27 +02:00
|
|
|
err = write_body (ctx, part->body, part->bodylen);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2016-06-29 11:02:36 +02:00
|
|
|
}
|
|
|
|
if (part->child)
|
|
|
|
{
|
|
|
|
log_assert (part->boundary);
|
2016-09-29 12:29:27 +02:00
|
|
|
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);
|
2016-06-29 11:02:36 +02:00
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (part->next)
|
|
|
|
{
|
|
|
|
log_assert (parent && parent->boundary);
|
2016-09-29 12:29:27 +02:00
|
|
|
err = write_boundary (ctx, parent->boundary, 0);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2016-06-29 11:02:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Add headers we always require. */
|
|
|
|
static gpg_error_t
|
|
|
|
add_missing_headers (mime_maker_t ctx)
|
|
|
|
{
|
|
|
|
gpg_error_t err;
|
|
|
|
|
|
|
|
if (!ctx->mail)
|
|
|
|
return gpg_error (GPG_ERR_NO_DATA);
|
|
|
|
if (!have_header (ctx->mail, "MIME-Version"))
|
|
|
|
{
|
|
|
|
/* Even if a Content-Type has never been set, we want to
|
|
|
|
* announce that we do MIME. */
|
|
|
|
err = add_header (ctx->mail, "MIME-Version", "1.0");
|
|
|
|
if (err)
|
|
|
|
goto leave;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!have_header (ctx->mail, "Date"))
|
|
|
|
{
|
|
|
|
char *p = rfctimestamp (make_timestamp ());
|
|
|
|
if (!p)
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
else
|
|
|
|
err = add_header (ctx->mail, "Date", p);
|
|
|
|
xfree (p);
|
|
|
|
if (err)
|
|
|
|
goto leave;
|
|
|
|
}
|
|
|
|
|
2016-12-08 13:04:06 +01:00
|
|
|
err = 0;
|
2016-06-29 11:02:36 +02:00
|
|
|
|
|
|
|
leave:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-09-29 10:20:38 +02:00
|
|
|
/* Create message from the tree MIME and write it to FP. Note that
|
2016-06-29 11:02:36 +02:00
|
|
|
* the output uses only a LF and a later called sendmail(1) is
|
|
|
|
* expected to convert them to network line endings. */
|
|
|
|
gpg_error_t
|
|
|
|
mime_maker_make (mime_maker_t ctx, estream_t fp)
|
|
|
|
{
|
|
|
|
gpg_error_t err;
|
|
|
|
|
|
|
|
err = add_missing_headers (ctx);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
ctx->outfp = fp;
|
|
|
|
err = write_tree (ctx, NULL, ctx->mail);
|
|
|
|
|
|
|
|
ctx->outfp = NULL;
|
|
|
|
return err;
|
|
|
|
}
|
2016-09-29 17:38:06 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|