/* wks-receive.c - Receive a WKS mail
* Copyright (C) 2016 g10 Code GmbH
*
* 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.
*
* GnuPG 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#include
#include
#include
#include
#include "util.h"
#include "ccparray.h"
#include "exectool.h"
#include "gpg-wks.h"
#include "mime-parser.h"
/* Limit of acceptable signed data. */
#define MAX_SIGNEDDATA 10000
/* Limit of acceptable signature. */
#define MAX_SIGNATURE 10000
/* Limit of acceptable encrypted data. */
#define MAX_ENCRYPTED 100000
/* Data for a received object. */
struct receive_ctx_s
{
estream_t encrypted;
estream_t plaintext;
estream_t signeddata;
estream_t signature;
estream_t key_data;
estream_t wkd_data;
unsigned int collect_key_data:1;
unsigned int collect_wkd_data:1;
};
typedef struct receive_ctx_s *receive_ctx_t;
static void
decrypt_data_status_cb (void *opaque, const char *keyword, char *args)
{
receive_ctx_t ctx = opaque;
(void)ctx;
log_debug ("%s: %s\n", keyword, args);
}
/* Decrypt the collected data. */
static void
decrypt_data (receive_ctx_t ctx)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
int c;
es_rewind (ctx->encrypted);
if (!ctx->plaintext)
ctx->plaintext = es_fopenmem (0, "w+b");
if (!ctx->plaintext)
{
err = gpg_error_from_syserror ();
log_error ("error allocating space for plaintext: %s\n",
gpg_strerror (err));
return;
}
ccparray_init (&ccp, 0);
/* We limit the output to 64 KiB to avoid DoS using compression
* tricks. A regular client will anyway only send a minimal key;
* that is one w/o key signatures and attribute packets. */
ccparray_put (&ccp, "--max-output=0xf0000"); /*FIXME: Change s/F/1/ */
ccparray_put (&ccp, "--batch");
if (opt.verbose)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--decrypt");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, ctx->encrypted,
NULL, ctx->plaintext,
decrypt_data_status_cb, ctx);
if (err)
{
log_error ("decryption failed: %s\n", gpg_strerror (err));
goto leave;
}
if (opt.debug)
{
es_rewind (ctx->plaintext);
log_debug ("plaintext: '");
while ((c = es_getc (ctx->plaintext)) != EOF)
log_printf ("%c", c);
log_printf ("'\n");
}
es_rewind (ctx->plaintext);
leave:
xfree (argv);
}
static void
verify_signature_status_cb (void *opaque, const char *keyword, char *args)
{
receive_ctx_t ctx = opaque;
(void)ctx;
log_debug ("%s: %s\n", keyword, args);
}
/* Verify the signed data. */
static void
verify_signature (receive_ctx_t ctx)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
log_assert (ctx->signeddata);
log_assert (ctx->signature);
es_rewind (ctx->signeddata);
es_rewind (ctx->signature);
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--batch");
if (opt.verbose)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--enable-special-filenames");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--verify");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, "-&@INEXTRA@");
ccparray_put (&ccp, "-");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, ctx->signeddata,
ctx->signature, NULL,
verify_signature_status_cb, ctx);
if (err)
{
log_error ("verification failed: %s\n", gpg_strerror (err));
goto leave;
}
leave:
xfree (argv);
}
static gpg_error_t
collect_encrypted (void *cookie, const char *data)
{
receive_ctx_t ctx = cookie;
if (!ctx->encrypted)
if (!(ctx->encrypted = es_fopenmem (MAX_ENCRYPTED, "w+b,samethread")))
return gpg_error_from_syserror ();
if (data)
es_fputs (data, ctx->encrypted);
if (es_ferror (ctx->encrypted))
return gpg_error_from_syserror ();
if (!data)
{
decrypt_data (ctx);
}
return 0;
}
static gpg_error_t
collect_signeddata (void *cookie, const char *data)
{
receive_ctx_t ctx = cookie;
if (!ctx->signeddata)
if (!(ctx->signeddata = es_fopenmem (MAX_SIGNEDDATA, "w+b,samethread")))
return gpg_error_from_syserror ();
if (data)
es_fputs (data, ctx->signeddata);
if (es_ferror (ctx->signeddata))
return gpg_error_from_syserror ();
return 0;
}
static gpg_error_t
collect_signature (void *cookie, const char *data)
{
receive_ctx_t ctx = cookie;
if (!ctx->signature)
if (!(ctx->signature = es_fopenmem (MAX_SIGNATURE, "w+b,samethread")))
return gpg_error_from_syserror ();
if (data)
es_fputs (data, ctx->signature);
if (es_ferror (ctx->signature))
return gpg_error_from_syserror ();
if (!data)
{
verify_signature (ctx);
}
return 0;
}
static gpg_error_t
new_part (void *cookie, const char *mediatype, const char *mediasubtype)
{
receive_ctx_t ctx = cookie;
gpg_error_t err = 0;
ctx->collect_key_data = 0;
ctx->collect_wkd_data = 0;
if (!strcmp (mediatype, "application")
&& !strcmp (mediasubtype, "pgp-keys"))
{
log_info ("new '%s/%s' message part\n", mediatype, mediasubtype);
if (ctx->key_data)
{
log_error ("we already got a key - ignoring this part\n");
err = gpg_error (GPG_ERR_FALSE);
}
else
{
ctx->key_data = es_fopenmem (0, "w+b");
if (!ctx->key_data)
{
err = gpg_error_from_syserror ();
log_error ("error allocating space for key: %s\n",
gpg_strerror (err));
}
else
{
ctx->collect_key_data = 1;
err = gpg_error (GPG_ERR_TRUE); /* We want the part decoded. */
}
}
}
else if (!strcmp (mediatype, "application")
&& !strcmp (mediasubtype, "vnd.gnupg.wks"))
{
log_info ("new '%s/%s' message part\n", mediatype, mediasubtype);
if (ctx->wkd_data)
{
log_error ("we already got a wkd part - ignoring this part\n");
err = gpg_error (GPG_ERR_FALSE);
}
else
{
ctx->wkd_data = es_fopenmem (0, "w+b");
if (!ctx->wkd_data)
{
err = gpg_error_from_syserror ();
log_error ("error allocating space for key: %s\n",
gpg_strerror (err));
}
else
{
ctx->collect_wkd_data = 1;
err = gpg_error (GPG_ERR_TRUE); /* We want the part decoded. */
}
}
}
else
{
log_error ("unexpected '%s/%s' message part\n", mediatype, mediasubtype);
err = gpg_error (GPG_ERR_FALSE); /* We do not want the part. */
}
return err;
}
static gpg_error_t
part_data (void *cookie, const void *data, size_t datalen)
{
receive_ctx_t ctx = cookie;
if (data)
{
if (opt.debug)
log_debug ("part_data: '%.*s'\n", (int)datalen, (const char*)data);
if (ctx->collect_key_data)
{
if (es_write (ctx->key_data, data, datalen, NULL)
|| es_fputs ("\n", ctx->key_data))
return gpg_error_from_syserror ();
}
if (ctx->collect_wkd_data)
{
if (es_write (ctx->wkd_data, data, datalen, NULL)
|| es_fputs ("\n", ctx->wkd_data))
return gpg_error_from_syserror ();
}
}
else
{
if (opt.debug)
log_debug ("part_data: finished\n");
ctx->collect_key_data = 0;
ctx->collect_wkd_data = 0;
}
return 0;
}
/* Receive a WKS mail from FP and process it accordingly. On success
* the RESULT_CB is called with the mediatype and a stream with the
* decrypted data. */
gpg_error_t
wks_receive (estream_t fp,
gpg_error_t (*result_cb)(void *opaque,
const char *mediatype,
estream_t data),
void *cb_data)
{
gpg_error_t err;
receive_ctx_t ctx;
mime_parser_t parser;
estream_t plaintext = NULL;
int c;
ctx = xtrycalloc (1, sizeof *ctx);
if (!ctx)
return gpg_error_from_syserror ();
err = mime_parser_new (&parser, ctx);
if (err)
goto leave;
if (opt.verbose > 1 || opt.debug)
mime_parser_set_verbose (parser, opt.debug? 10: 1);
mime_parser_set_new_part (parser, new_part);
mime_parser_set_part_data (parser, part_data);
mime_parser_set_collect_encrypted (parser, collect_encrypted);
mime_parser_set_collect_signeddata (parser, collect_signeddata);
mime_parser_set_collect_signature (parser, collect_signature);
err = mime_parser_parse (parser, fp);
if (err)
goto leave;
if (ctx->key_data)
log_info ("key data found\n");
if (ctx->wkd_data)
log_info ("wkd data found\n");
if (ctx->plaintext)
{
if (opt.verbose)
log_info ("parsing decrypted message\n");
plaintext = ctx->plaintext;
ctx->plaintext = NULL;
if (ctx->encrypted)
es_rewind (ctx->encrypted);
if (ctx->signeddata)
es_rewind (ctx->signeddata);
if (ctx->signature)
es_rewind (ctx->signature);
err = mime_parser_parse (parser, plaintext);
if (err)
return err;
}
if (!ctx->key_data && !ctx->wkd_data)
{
log_error ("no suitable data found in the message\n");
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
if (ctx->key_data)
{
if (opt.debug)
{
es_rewind (ctx->key_data);
log_debug ("Key: '");
log_printf ("\n");
while ((c = es_getc (ctx->key_data)) != EOF)
log_printf ("%c", c);
log_printf ("'\n");
}
if (result_cb)
{
es_rewind (ctx->key_data);
err = result_cb (cb_data, "application/pgp-keys", ctx->key_data);
if (err)
goto leave;
}
}
if (ctx->wkd_data)
{
if (opt.debug)
{
es_rewind (ctx->wkd_data);
log_debug ("WKD: '");
log_printf ("\n");
while ((c = es_getc (ctx->wkd_data)) != EOF)
log_printf ("%c", c);
log_printf ("'\n");
}
if (result_cb)
{
es_rewind (ctx->wkd_data);
err = result_cb (cb_data, "application/vnd.gnupg.wks", ctx->wkd_data);
if (err)
goto leave;
}
}
leave:
es_fclose (plaintext);
mime_parser_release (parser);
es_fclose (ctx->encrypted);
es_fclose (ctx->plaintext);
es_fclose (ctx->signeddata);
es_fclose (ctx->signature);
es_fclose (ctx->key_data);
es_fclose (ctx->wkd_data);
xfree (ctx);
return err;
}