diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/Makefile.in | 65 | ||||
-rw-r--r-- | src/cJSON.c | 89 | ||||
-rw-r--r-- | src/context.h | 7 | ||||
-rw-r--r-- | src/data-estream.c | 99 | ||||
-rw-r--r-- | src/data-mem.c | 34 | ||||
-rw-r--r-- | src/data.c | 285 | ||||
-rw-r--r-- | src/data.h | 29 | ||||
-rw-r--r-- | src/decrypt-verify.c | 6 | ||||
-rw-r--r-- | src/decrypt.c | 111 | ||||
-rw-r--r-- | src/edit.c | 6 | ||||
-rw-r--r-- | src/encrypt-sign.c | 2 | ||||
-rw-r--r-- | src/encrypt.c | 2 | ||||
-rw-r--r-- | src/engine-backend.h | 2 | ||||
-rw-r--r-- | src/engine-gpg.c | 180 | ||||
-rw-r--r-- | src/engine-gpgsm.c | 18 | ||||
-rw-r--r-- | src/engine.c | 6 | ||||
-rw-r--r-- | src/engine.h | 3 | ||||
-rw-r--r-- | src/export.c | 1 | ||||
-rw-r--r-- | src/genkey.c | 8 | ||||
-rw-r--r-- | src/getauditlog.c | 9 | ||||
-rw-r--r-- | src/gpgme-json.c | 2574 | ||||
-rw-r--r-- | src/gpgme.c | 20 | ||||
-rw-r--r-- | src/gpgme.def | 2 | ||||
-rw-r--r-- | src/gpgme.h.in | 26 | ||||
-rw-r--r-- | src/keysign.c | 2 | ||||
-rw-r--r-- | src/libgpgme.vers | 3 | ||||
-rw-r--r-- | src/op-support.c | 7 | ||||
-rw-r--r-- | src/ops.h | 3 | ||||
-rw-r--r-- | src/passwd.c | 2 | ||||
-rw-r--r-- | src/sign.c | 2 | ||||
-rw-r--r-- | src/w32-util.c | 25 |
32 files changed, 3244 insertions, 385 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 0a196e0..1394c02 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -70,6 +70,7 @@ main_sources = \ parsetlv.c parsetlv.h \ mbox-util.c mbox-util.h \ data.h data.c data-fd.c data-stream.c data-mem.c data-user.c \ + data-estream.c \ data-compat.c data-identify.c \ signers.c sig-notation.c \ wait.c wait-global.c wait-private.c wait-user.c wait.h \ diff --git a/src/Makefile.in b/src/Makefile.in index 40fec0f..9423e3f 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -124,7 +124,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs -CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_HEADER = $(top_builddir)/conf/config.h CONFIG_CLEAN_FILES = versioninfo.rc gpgme.h gpgme-config CONFIG_CLEAN_VPATH_FILES = am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; @@ -162,14 +162,14 @@ LTLIBRARIES = $(lib_LTLIBRARIES) am__libgpgme_glib_la_SOURCES_DIST = util.h conversion.c b64dec.c \ get-env.c context.h ops.h parsetlv.c parsetlv.h mbox-util.c \ mbox-util.h data.h data.c data-fd.c data-stream.c data-mem.c \ - data-user.c data-compat.c data-identify.c signers.c \ - sig-notation.c wait.c wait-global.c wait-private.c wait-user.c \ - wait.h op-support.c encrypt.c encrypt-sign.c decrypt.c \ - decrypt-verify.c verify.c sign.c passphrase.c progress.c key.c \ - keylist.c keysign.c trust-item.c trustlist.c tofupolicy.c \ - import.c export.c genkey.c delete.c edit.c getauditlog.c \ - opassuan.c passwd.c spawn.c assuan-support.c engine.h \ - engine-backend.h engine.c engine-gpg.c status-table.c \ + data-user.c data-estream.c data-compat.c data-identify.c \ + signers.c sig-notation.c wait.c wait-global.c wait-private.c \ + wait-user.c wait.h op-support.c encrypt.c encrypt-sign.c \ + decrypt.c decrypt-verify.c verify.c sign.c passphrase.c \ + progress.c key.c keylist.c keysign.c trust-item.c trustlist.c \ + tofupolicy.c import.c export.c genkey.c delete.c edit.c \ + getauditlog.c opassuan.c passwd.c spawn.c assuan-support.c \ + engine.h engine-backend.h engine.c engine-gpg.c status-table.c \ engine-gpgsm.c engine-assuan.c engine-gpgconf.c \ engine-uiserver.c engine-g13.c vfs-mount.c vfs-create.c \ engine-spawn.c gpgconf.c queryswdb.c sema.h priv-io.h ath.h \ @@ -183,14 +183,14 @@ am__libgpgme_glib_la_SOURCES_DIST = util.h conversion.c b64dec.c \ @HAVE_DOSISH_SYSTEM_TRUE@am__objects_3 = w32-util.lo $(am__objects_2) am__objects_4 = conversion.lo b64dec.lo get-env.lo parsetlv.lo \ mbox-util.lo data.lo data-fd.lo data-stream.lo data-mem.lo \ - data-user.lo data-compat.lo data-identify.lo signers.lo \ - sig-notation.lo wait.lo wait-global.lo wait-private.lo \ - wait-user.lo op-support.lo encrypt.lo encrypt-sign.lo \ - decrypt.lo decrypt-verify.lo verify.lo sign.lo passphrase.lo \ - progress.lo key.lo keylist.lo keysign.lo trust-item.lo \ - trustlist.lo tofupolicy.lo import.lo export.lo genkey.lo \ - delete.lo edit.lo getauditlog.lo opassuan.lo passwd.lo \ - spawn.lo assuan-support.lo engine.lo engine-gpg.lo \ + data-user.lo data-estream.lo data-compat.lo data-identify.lo \ + signers.lo sig-notation.lo wait.lo wait-global.lo \ + wait-private.lo wait-user.lo op-support.lo encrypt.lo \ + encrypt-sign.lo decrypt.lo decrypt-verify.lo verify.lo sign.lo \ + passphrase.lo progress.lo key.lo keylist.lo keysign.lo \ + trust-item.lo trustlist.lo tofupolicy.lo import.lo export.lo \ + genkey.lo delete.lo edit.lo getauditlog.lo opassuan.lo \ + passwd.lo spawn.lo assuan-support.lo engine.lo engine-gpg.lo \ status-table.lo engine-gpgsm.lo engine-assuan.lo \ engine-gpgconf.lo $(am__objects_1) engine-g13.lo vfs-mount.lo \ vfs-create.lo engine-spawn.lo gpgconf.lo queryswdb.lo \ @@ -211,19 +211,20 @@ libgpgme_glib_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ am__libgpgme_la_SOURCES_DIST = util.h conversion.c b64dec.c get-env.c \ context.h ops.h parsetlv.c parsetlv.h mbox-util.c mbox-util.h \ data.h data.c data-fd.c data-stream.c data-mem.c data-user.c \ - data-compat.c data-identify.c signers.c sig-notation.c wait.c \ - wait-global.c wait-private.c wait-user.c wait.h op-support.c \ - encrypt.c encrypt-sign.c decrypt.c decrypt-verify.c verify.c \ - sign.c passphrase.c progress.c key.c keylist.c keysign.c \ - trust-item.c trustlist.c tofupolicy.c import.c export.c \ - genkey.c delete.c edit.c getauditlog.c opassuan.c passwd.c \ - spawn.c assuan-support.c engine.h engine-backend.h engine.c \ - engine-gpg.c status-table.c engine-gpgsm.c engine-assuan.c \ - engine-gpgconf.c engine-uiserver.c engine-g13.c vfs-mount.c \ - vfs-create.c engine-spawn.c gpgconf.c queryswdb.c sema.h \ - priv-io.h ath.h posix-util.c posix-io.c w32-ce.h w32-ce.c \ - w32-util.c sys-util.h dirinfo.c debug.c debug.h gpgme.c \ - version.c error.c ath.c w32-io.c + data-estream.c data-compat.c data-identify.c signers.c \ + sig-notation.c wait.c wait-global.c wait-private.c wait-user.c \ + wait.h op-support.c encrypt.c encrypt-sign.c decrypt.c \ + decrypt-verify.c verify.c sign.c passphrase.c progress.c key.c \ + keylist.c keysign.c trust-item.c trustlist.c tofupolicy.c \ + import.c export.c genkey.c delete.c edit.c getauditlog.c \ + opassuan.c passwd.c spawn.c assuan-support.c engine.h \ + engine-backend.h engine.c engine-gpg.c status-table.c \ + engine-gpgsm.c engine-assuan.c engine-gpgconf.c \ + engine-uiserver.c engine-g13.c vfs-mount.c vfs-create.c \ + engine-spawn.c gpgconf.c queryswdb.c sema.h priv-io.h ath.h \ + posix-util.c posix-io.c w32-ce.h w32-ce.c w32-util.c \ + sys-util.h dirinfo.c debug.c debug.h gpgme.c version.c error.c \ + ath.c w32-io.c @HAVE_DOSISH_SYSTEM_TRUE@am__objects_5 = w32-io.lo am_libgpgme_la_OBJECTS = $(am__objects_4) $(am__objects_5) libgpgme_la_OBJECTS = $(am_libgpgme_la_OBJECTS) @@ -254,7 +255,7 @@ AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = -DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/conf depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp am__depfiles_maybe = depfiles am__mv = mv -f @@ -533,6 +534,7 @@ main_sources = \ parsetlv.c parsetlv.h \ mbox-util.c mbox-util.h \ data.h data.c data-fd.c data-stream.c data-mem.c data-user.c \ + data-estream.c \ data-compat.c data-identify.c \ signers.c sig-notation.c \ wait.c wait-global.c wait-private.c wait-user.c wait.h \ @@ -837,6 +839,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cJSON.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conversion.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-compat.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-estream.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-fd.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-identify.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-mem.Plo@am__quote@ diff --git a/src/cJSON.c b/src/cJSON.c index cf0cb13..9e53012 100644 --- a/src/cJSON.c +++ b/src/cJSON.c @@ -22,7 +22,14 @@ * SPDX-License-Identifier: MIT * * Note that this code has been modified from the original code taken - * from cjson-code-58.zip. + * from cjson-code-58.zip before 2014 (my first local commit was in + * 2014 but I may used the code even earlier). Since 2016 the project + * was revived and moved to https://github.com/DaveGamble/cJSON.git. + * It is now a lot more complex and has substantial changes so that it + * is not possible to merge them directly. In any case we only need a + * simple parser and not a complete library. I have looked through + * the commits and fixed a few things which should apply; I also added + * a few references to the upstream code. Regression test are missing! */ #ifdef HAVE_CONFIG_H @@ -38,20 +45,42 @@ #include <ctype.h> #include <errno.h> +#include <gpg-error.h> + #include "cJSON.h" +/* Only use calloc. */ +#define CALLOC_ONLY 1 + +/* To avoid that a compiler optimizes certain memset calls away, these + macros may be used instead. */ +#define wipememory2(_ptr,_set,_len) do { \ + volatile char *_vptr=(volatile char *)(_ptr); \ + size_t _vlen=(_len); \ + while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \ + } while(0) +#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) + /* We use malloc function wrappers from gpgrt (aka libgpg-error). */ -#if 1 +#if GPGRT_VERSION_NUMBER >= 0x011c00 /* 1.28 */ # include <gpgrt.h> -# define xtrymalloc(a) gpgrt_malloc ((a)) # define xtrycalloc(a,b) gpgrt_calloc ((a), (b)) # define xtrystrdup(a) gpgrt_strdup ((a)) # define xfree(a) gpgrt_free ((a)) -#else -# define xtrymalloc(a) malloc ((a)) +# if CALLOC_ONLY +# define xtrymalloc(a) gpgrt_calloc (1, (a)) +# else +# define xtrymalloc(a) gpgrt_malloc ((a)) +# endif +#else /* Without gpgrt (aka libgpg-error). */ # define xtrycalloc(a,b) calloc ((a), (b)) # define xtrystrdup(a) strdup ((a)) # define xfree(a) free ((a)) +# if CALLOC_ONLY +# define xtrymalloc(a) calloc (1, (a)) +# else +# define xtrymalloc(a) malloc ((a)) +# endif #endif @@ -94,9 +123,15 @@ cJSON_Delete (cJSON * c) if (!(c->type & cJSON_IsReference) && c->child) cJSON_Delete (c->child); if (!(c->type & cJSON_IsReference) && c->valuestring) - xfree (c->valuestring); + { + wipememory (c->valuestring, strlen (c->valuestring)); + xfree (c->valuestring); + } if (c->string) - xfree (c->string); + { + wipememory (c->string, strlen (c->string)); + xfree (c->string); + } xfree (c); c = next; } @@ -232,6 +267,9 @@ parse_string (cJSON * item, const char *str, const char **ep) char *out; int len = 0; unsigned uc, uc2; + + /* FIXME: We should consider eary failure like it is done with + * commit 8656386c4f4a12f1cf3d6b26158407fd05e65029 in upstream. */ if (*str != '\"') { *ep = str; @@ -239,11 +277,13 @@ parse_string (cJSON * item, const char *str, const char **ep) } /* not a string! */ while (*ptr != '\"' && *ptr && ++len) - if (*ptr++ == '\\') + if (*ptr++ == '\\' && *ptr) ptr++; /* Skip escaped quotes. */ - out = xtrymalloc (len + 1); /* This is how long we need for the - string, roughly. */ + out = xtrymalloc (len + 2); /* This is how long we need for the + * string, roughly. We add one extra + * byte in case the last input + * character is a backslash. */ if (!out) return 0; @@ -256,6 +296,8 @@ parse_string (cJSON * item, const char *str, const char **ep) else { ptr++; + if (!*ptr) + break; switch (*ptr) { case 'b': @@ -275,17 +317,22 @@ parse_string (cJSON * item, const char *str, const char **ep) break; case 'u': /* transcode utf16 to utf8. */ uc = parse_hex4 (ptr + 1); + if (!uc) + break; /* Bad hex; continue right after the 'u'. */ ptr += 4; /* get the unicode char. */ - if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) + if ((uc >= 0xDC00 && uc <= 0xDFFF)) break; /* check for invalid. */ if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */ { if (ptr[1] != '\\' || ptr[2] != 'u') break; /* missing second-half of surrogate. */ - uc2 = parse_hex4 (ptr + 3); - ptr += 6; + ptr += 2; + uc2 = parse_hex4 (ptr + 1); + if (!uc2) + break; /* Bad hex; continue right after the 'u'. */ + ptr += 4; if (uc2 < 0xDC00 || uc2 > 0xDFFF) break; /* invalid second-half of surrogate. */ uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF)); @@ -317,6 +364,8 @@ parse_string (cJSON * item, const char *str, const char **ep) ptr2 += len; break; default: + /* Fixme: Should we fail here: See + * https://github.com/DaveGamble/cJSON/issues/10 */ *ptr2++ = *ptr; break; } @@ -929,9 +978,11 @@ create_reference (cJSON * item) void cJSON_AddItemToArray (cJSON * array, cJSON * item) { - cJSON *c = array->child; - if (!item) + cJSON *c; + + if (!item || !array) return; + c = array->child; if (!c) { array->child = item; @@ -1132,6 +1183,8 @@ cJSON_ReplaceItemInObject (cJSON * object, const char *string, i++, c = c->next; if (c) { + /* FIXME: I guess we should free newitem->string here. See + * upstream commit 0d10e279c8b604f71829b5d49d092719f4ae96b6. */ newitem->string = xtrystrdup (string); cJSON_ReplaceItemInArray (object, i, newitem); } @@ -1393,9 +1446,11 @@ cJSON_Minify (char *json) { if (*json == '\\') *into++ = *json++; - *into++ = *json++; + if (*json) + *into++ = *json++; } - *into++ = *json++; + if (*json) + *into++ = *json++; } /* String literals, which are \" sensitive. */ else *into++ = *json++; /* All other characters. */ diff --git a/src/context.h b/src/context.h index c8e75ba..1c9379b 100644 --- a/src/context.h +++ b/src/context.h @@ -124,6 +124,10 @@ struct gpgme_context /* Do not use the symmtric encryption passphrase cache. */ unsigned int no_symkey_cache : 1; + /* Pass --ignore-mdc-error to gpg. Note that this flag is reset + * after the operation. */ + unsigned int ignore_mdc_error : 1; + /* Flags for keylist mode. */ gpgme_keylist_mode_t keylist_mode; @@ -151,6 +155,9 @@ struct gpgme_context /* The optional request origin. */ char *request_origin; + /* The optional auto key locate options. */ + char *auto_key_locate; + /* The locale for the pinentry. */ char *lc_ctype; char *lc_messages; diff --git a/src/data-estream.c b/src/data-estream.c new file mode 100644 index 0000000..34f88a7 --- /dev/null +++ b/src/data-estream.c @@ -0,0 +1,99 @@ +/* data-stream.c - A stream based data object. + * Copyright (C) 2002, 2004, 2018 g10 Code GmbH + * + * This file is part of GPGME. + * + * GPGME 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. + * + * GPGME 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif + +#include "debug.h" +#include "data.h" + + +static gpgme_ssize_t +stream_es_read (gpgme_data_t dh, void *buffer, size_t size) +{ + size_t amt = gpgrt_fread (buffer, 1, size, dh->data.e_stream); + if (amt > 0) + return amt; + return gpgrt_ferror (dh->data.e_stream) ? -1 : 0; +} + + +static gpgme_ssize_t +stream_es_write (gpgme_data_t dh, const void *buffer, size_t size) +{ + size_t amt = gpgrt_fwrite (buffer, 1, size, dh->data.e_stream); + if (amt > 0) + return amt; + return gpgrt_ferror (dh->data.e_stream) ? -1 : 0; +} + + +static gpgme_off_t +stream_es_seek (gpgme_data_t dh, gpgme_off_t offset, int whence) +{ + int err; + + err = gpgrt_fseeko (dh->data.e_stream, offset, whence); + if (err) + return -1; + + return gpgrt_ftello (dh->data.e_stream); +} + + +static int +stream_es_get_fd (gpgme_data_t dh) +{ + gpgrt_fflush (dh->data.e_stream); + return gpgrt_fileno (dh->data.e_stream); +} + + +static struct _gpgme_data_cbs stream_es_cbs = + { + stream_es_read, + stream_es_write, + stream_es_seek, + NULL, + stream_es_get_fd + }; + + + +gpgme_error_t +gpgme_data_new_from_estream (gpgme_data_t *r_dh, gpgrt_stream_t stream) +{ + gpgme_error_t err; + TRACE_BEG1 (DEBUG_DATA, "gpgme_data_new_from_estream", r_dh, "estream=%p", + stream); + + err = _gpgme_data_new (r_dh, &stream_es_cbs); + if (err) + return TRACE_ERR (err); + + (*r_dh)->data.e_stream = stream; + return TRACE_SUC1 ("dh=%p", *r_dh); +} diff --git a/src/data-mem.c b/src/data-mem.c index a498b82..7569f7d 100644 --- a/src/data-mem.c +++ b/src/data-mem.c @@ -224,7 +224,10 @@ gpgme_data_new_from_mem (gpgme_data_t *r_dh, const char *buffer, char * gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len) { + gpg_error_t err; char *str = NULL; + size_t len; + int blankout; TRACE_BEG1 (DEBUG_DATA, "gpgme_data_release_and_get_mem", dh, "r_len=%p", r_len); @@ -236,10 +239,22 @@ gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len) return NULL; } + err = _gpgme_data_get_prop (dh, 0, DATA_PROP_BLANKOUT, &blankout); + if (err) + { + gpgme_data_release (dh); + TRACE_ERR (err); + return NULL; + } + str = dh->data.mem.buffer; + len = dh->data.mem.length; + if (blankout && len) + len = 1; + if (!str && dh->data.mem.orig_buffer) { - str = malloc (dh->data.mem.length); + str = malloc (len); if (!str) { int saved_err = gpg_error_from_syserror (); @@ -247,15 +262,22 @@ gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len) TRACE_ERR (saved_err); return NULL; } - memcpy (str, dh->data.mem.orig_buffer, dh->data.mem.length); + if (blankout) + memset (str, 0, len); + else + memcpy (str, dh->data.mem.orig_buffer, len); } else - /* Prevent mem_release from releasing the buffer memory. We must - not fail from this point. */ - dh->data.mem.buffer = NULL; + { + if (blankout && len) + *str = 0; + /* Prevent mem_release from releasing the buffer memory. We + * must not fail from this point. */ + dh->data.mem.buffer = NULL; + } if (r_len) - *r_len = dh->data.mem.length; + *r_len = len; gpgme_data_release (dh); @@ -28,6 +28,7 @@ #endif #include <errno.h> #include <string.h> +#include <assert.h> #include "gpgme.h" #include "data.h" @@ -36,10 +37,271 @@ #include "priv-io.h" #include "debug.h" + +/* The property table which has an entry for each active data object. + * The data object itself uses an index into this table and the table + * has a pointer back to the data object. All access to that table is + * controlled by the property_table_lock. + * + * We use a separate table instead of linking all data objects + * together for faster locating properties of the data object using + * the data objects serial number. We use 64 bit for the serial + * number which is good enough to create a new data object every + * nanosecond for more than 500 years. Thus no wrap around will ever + * happen. + */ +struct property_s +{ + gpgme_data_t dh; /* The data objcet or NULL if the slot is not used. */ + uint64_t dserial; /* The serial number of the data object. */ + struct { + unsigned int blankout : 1; /* Void the held data. */ + } flags; +}; +typedef struct property_s *property_t; + +static property_t property_table; +static unsigned int property_table_size; +DEFINE_STATIC_LOCK (property_table_lock); +#define PROPERTY_TABLE_ALLOCATION_CHUNK 32 + + + +/* Insert the newly created data object DH into the property table and + * store the index of it at R_IDX. An error code is returned on error + * and the table is not changed. */ +static gpg_error_t +insert_into_property_table (gpgme_data_t dh, unsigned int *r_idx) +{ + static uint64_t last_dserial; + gpg_error_t err; + unsigned int idx; + + LOCK (property_table_lock); + if (!property_table) + { + property_table_size = PROPERTY_TABLE_ALLOCATION_CHUNK; + property_table = calloc (property_table_size, sizeof *property_table); + if (!property_table) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + /* Find an empty slot. */ + for (idx = 0; idx < property_table_size; idx++) + if (!property_table[idx].dh) + break; + if (!(idx < property_table_size)) + { + /* No empty slot found. Enlarge the table. */ + property_t newtbl; + unsigned int newsize; + + newsize = property_table_size + PROPERTY_TABLE_ALLOCATION_CHUNK;; + if ((newsize * sizeof *property_table) + < (property_table_size * sizeof *property_table)) + { + err = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + newtbl = realloc (property_table, newsize * sizeof *property_table); + if (!newtbl) + { + err = gpg_error_from_syserror (); + goto leave; + } + property_table = newtbl; + for (idx = property_table_size; idx < newsize; idx++) + property_table[idx].dh = NULL; + idx = property_table_size; + property_table_size = newsize; + } + + /* Slot found. */ + property_table[idx].dh = dh; + property_table[idx].dserial = ++last_dserial; + memset (&property_table[idx].flags, 0, sizeof property_table[idx].flags); + *r_idx = idx; + err = 0; + + leave: + UNLOCK (property_table_lock); + return err; +} + + +/* Remove the data object at PROPIDX from the table. DH is only used + * for cross checking. */ +static void +remove_from_property_table (gpgme_data_t dh, unsigned int propidx) +{ + LOCK (property_table_lock); + assert (property_table); + assert (propidx < property_table_size); + assert (property_table[propidx].dh == dh); + property_table[propidx].dh = NULL; + UNLOCK (property_table_lock); +} + + +/* Return the data object's serial number for handle DH. This is a + * unique serial number for each created data object. */ +uint64_t +_gpgme_data_get_dserial (gpgme_data_t dh) +{ + uint64_t dserial; + unsigned int idx; + + if (!dh) + return 0; + + idx = dh->propidx; + LOCK (property_table_lock); + assert (property_table); + assert (idx < property_table_size); + assert (property_table[idx].dh == dh); + dserial = property_table[idx].dserial; + UNLOCK (property_table_lock); + + return dserial; +} + + +/* Set an internal property of a data object. The data object may + * either be identified by the usual DH or by using the data serial + * number DSERIAL. */ +gpg_error_t +_gpgme_data_set_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int value) +{ + gpg_error_t err = 0; + int idx; + TRACE_BEG3 (DEBUG_DATA, "gpgme_data_set_prop", dh, + "dserial=%llu %lu=%d", + (unsigned long long)dserial, + (unsigned long)name, value); + + LOCK (property_table_lock); + if ((!dh && !dserial) || (dh && dserial)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + if (dh) /* Lookup via handle. */ + { + idx = dh->propidx; + assert (property_table); + assert (idx < property_table_size); + assert (property_table[idx].dh == dh); + } + else /* Lookup via DSERIAL. */ + { + if (!property_table) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + for (idx = 0; idx < property_table_size; idx++) + if (property_table[idx].dh && property_table[idx].dserial == dserial) + break; + if (!(idx < property_table_size)) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + } + + switch (name) + { + case DATA_PROP_NONE: /* Nothing to to do. */ + break; + case DATA_PROP_BLANKOUT: + property_table[idx].flags.blankout = !!value; + break; + + default: + err = gpg_error (GPG_ERR_UNKNOWN_NAME); + break; + } + + leave: + UNLOCK (property_table_lock); + return TRACE_ERR (err); +} + + +/* Get an internal property of a data object. This is the counter + * part to _gpgme_data_set_property. The value of the property is + * stored at R_VALUE. On error 0 is stored at R_VALUE. */ +gpg_error_t +_gpgme_data_get_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int *r_value) +{ + gpg_error_t err = 0; + int idx; + TRACE_BEG2 (DEBUG_DATA, "gpgme_data_get_prop", dh, + "dserial=%llu %lu", + (unsigned long long)dserial, + (unsigned long)name); + + *r_value = 0; + + LOCK (property_table_lock); + if ((!dh && !dserial) || (dh && dserial)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + if (dh) /* Lookup via handle. */ + { + idx = dh->propidx; + assert (property_table); + assert (idx < property_table_size); + assert (property_table[idx].dh == dh); + } + else /* Lookup via DSERIAL. */ + { + if (!property_table) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + for (idx = 0; idx < property_table_size; idx++) + if (property_table[idx].dh && property_table[idx].dserial == dserial) + break; + if (!(idx < property_table_size)) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + } + + switch (name) + { + case DATA_PROP_NONE: /* Nothing to to do. */ + break; + case DATA_PROP_BLANKOUT: + *r_value = property_table[idx].flags.blankout; + break; + + default: + err = gpg_error (GPG_ERR_UNKNOWN_NAME); + break; + } + + leave: + UNLOCK (property_table_lock); + return TRACE_ERR (err); +} + + gpgme_error_t _gpgme_data_new (gpgme_data_t *r_dh, struct _gpgme_data_cbs *cbs) { + gpgme_error_t err; gpgme_data_t dh; if (!r_dh) @@ -56,6 +318,13 @@ _gpgme_data_new (gpgme_data_t *r_dh, struct _gpgme_data_cbs *cbs) dh->cbs = cbs; + err = insert_into_property_table (dh, &dh->propidx); + if (err) + { + free (dh); + return err; + } + *r_dh = dh; return 0; } @@ -67,11 +336,13 @@ _gpgme_data_release (gpgme_data_t dh) if (!dh) return; + remove_from_property_table (dh, dh->propidx); if (dh->file_name) free (dh->file_name); free (dh); } + /* Read up to SIZE bytes into buffer BUFFER from the data object with the handle DH. Return the number of characters read, 0 on EOF and @@ -80,6 +351,7 @@ gpgme_ssize_t gpgme_data_read (gpgme_data_t dh, void *buffer, size_t size) { gpgme_ssize_t res; + int blankout; TRACE_BEG2 (DEBUG_DATA, "gpgme_data_read", dh, "buffer=%p, size=%u", buffer, size); @@ -93,9 +365,16 @@ gpgme_data_read (gpgme_data_t dh, void *buffer, size_t size) gpg_err_set_errno (ENOSYS); return TRACE_SYSRES (-1); } - do - res = (*dh->cbs->read) (dh, buffer, size); - while (res < 0 && errno == EINTR); + + if (_gpgme_data_get_prop (dh, 0, DATA_PROP_BLANKOUT, &blankout) + || blankout) + res = 0; + else + { + do + res = (*dh->cbs->read) (dh, buffer, size); + while (res < 0 && errno == EINTR); + } return TRACE_SYSRES (res); } @@ -29,6 +29,7 @@ # include <sys/types.h> #endif #include <limits.h> +#include <stdint.h> #include "gpgme.h" @@ -73,6 +74,7 @@ struct gpgme_data { struct _gpgme_data_cbs *cbs; gpgme_data_encoding_t encoding; + unsigned int propidx; /* Index into the property table. */ #ifdef PIPE_BUF #define BUFFER_SIZE PIPE_BUF @@ -89,7 +91,7 @@ struct gpgme_data /* File name of the data object. */ char *file_name; - /* Hint on the to be expected toatl size of the data. */ + /* Hint on the to be expected total size of the data. */ gpgme_off_t size_hint; union @@ -100,6 +102,9 @@ struct gpgme_data /* For gpgme_data_new_from_stream. */ FILE *stream; + /* For gpgme_data_new_from_estream. */ + gpgrt_stream_t e_stream; + /* For gpgme_data_new_from_cbs. */ struct { @@ -127,7 +132,28 @@ struct gpgme_data } data; }; + +/* The data property types. */ +typedef enum + { + DATA_PROP_NONE = 0, /* Dummy property. */ + DATA_PROP_BLANKOUT /* Do not return the held data. */ + } data_prop_t; + + +/* Return the data object's serial number for handle DH. */ +uint64_t _gpgme_data_get_dserial (gpgme_data_t dh); + +/* Set an internal property of a data object. */ +gpg_error_t _gpgme_data_set_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int value); + +/* Get an internal property of a data object. */ +gpg_error_t _gpgme_data_get_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int *r_value); + +/* Create a new data object. */ gpgme_error_t _gpgme_data_new (gpgme_data_t *r_dh, struct _gpgme_data_cbs *cbs); @@ -140,4 +166,5 @@ int _gpgme_data_get_fd (gpgme_data_t dh); /* Get the size-hint value for DH or 0 if not available. */ gpgme_off_t _gpgme_data_get_size_hint (gpgme_data_t dh); + #endif /* DATA_H */ diff --git a/src/decrypt-verify.c b/src/decrypt-verify.c index 17f79ac..224edc1 100644 --- a/src/decrypt-verify.c +++ b/src/decrypt-verify.c @@ -58,7 +58,7 @@ decrypt_verify_start (gpgme_ctx_t ctx, int synchronous, if (err) return err; - err = _gpgme_op_decrypt_init_result (ctx); + err = _gpgme_op_decrypt_init_result (ctx, plain); if (err) return err; @@ -74,7 +74,7 @@ decrypt_verify_start (gpgme_ctx_t ctx, int synchronous, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -127,6 +127,7 @@ gpgme_op_decrypt_verify (gpgme_ctx_t ctx, gpgme_data_t cipher, err = decrypt_verify_start (ctx, 1, GPGME_DECRYPT_VERIFY, cipher, plain); if (!err) err = _gpgme_wait_one (ctx); + ctx->ignore_mdc_error = 0; /* Always reset. */ return TRACE_ERR (err); } @@ -177,5 +178,6 @@ gpgme_op_decrypt_ext (gpgme_ctx_t ctx, err = _gpgme_decrypt_start (ctx, 1, flags, cipher, plain); if (!err) err = _gpgme_wait_one (ctx); + ctx->ignore_mdc_error = 0; /* Always reset. */ return TRACE_ERR (err); } diff --git a/src/decrypt.c b/src/decrypt.c index 0fc7019..b51603a 100644 --- a/src/decrypt.c +++ b/src/decrypt.c @@ -32,7 +32,7 @@ #include "util.h" #include "context.h" #include "ops.h" - +#include "data.h" typedef struct @@ -53,13 +53,25 @@ typedef struct * status lines for each key the message has been encrypted to but * that secret key is not available. This can't be done for hidden * recipients, though. We track it here to allow for a better error - * message that the general DECRYPTION_FAILED. */ + * message than the general DECRYPTION_FAILED. */ int any_no_seckey; + /* If the engine emits a DECRYPTION_INFO status and that does not + * indicate that an integrity protection mode is active, this flag + * is set. */ + int not_integrity_protected; + + /* The error code from the first ERROR line. This is in some cases + * used to return a better matching error code to the caller. */ + gpg_error_t first_status_error; + /* A pointer to the next pointer of the last recipient in the list. This makes appending new invalid signers painless while preserving the order. */ gpgme_recipient_t *last_recipient_p; + + /* The data object serial number of the plaintext. */ + uint64_t plaintext_dserial; } *op_data_t; @@ -92,6 +104,8 @@ gpgme_op_decrypt_result (gpgme_ctx_t ctx) TRACE_BEG (DEBUG_CTX, "gpgme_op_decrypt_result", ctx); + ctx->ignore_mdc_error = 0; /* Always reset this flag. */ + err = _gpgme_op_data_lookup (ctx, OPDATA_DECRYPT, &hook, -1, NULL); opd = hook; if (err || !opd) @@ -209,6 +223,15 @@ parse_status_error (char *args, op_data_t opd) break; } } + else if (!strcmp (field[0], "nomdc_with_legacy_cipher")) + { + opd->result.legacy_cipher_nomdc = 1; + opd->not_integrity_protected = 1; + } + + /* Record the first error code. */ + if (err && !opd->first_status_error) + opd->first_status_error = err; free (args2); @@ -280,7 +303,7 @@ parse_decryption_info (char *args, op_data_t opd, gpgme_protocol_t protocol) char *field[3]; int nfields; char *args2; - int mdc, mode; + int mdc, aead_algo; const char *algostr, *modestr; if (!args) @@ -296,19 +319,22 @@ parse_decryption_info (char *args, op_data_t opd, gpgme_protocol_t protocol) mdc = atoi (field[0]); algostr = _gpgme_cipher_algo_name (atoi (field[1]), protocol); - mode = nfields < 3? 0 : atoi (field[2]); - modestr = _gpgme_cipher_mode_name (mode, protocol); + aead_algo = nfields < 3? 0 : atoi (field[2]); + modestr = _gpgme_cipher_mode_name (aead_algo, protocol); free (args2); free (opd->result.symkey_algo); - if (!mode && mdc != 2) + if (!aead_algo && mdc != 2) opd->result.symkey_algo = _gpgme_strconcat (algostr, ".PGPCFB", NULL); else opd->result.symkey_algo = _gpgme_strconcat (algostr, ".", modestr, NULL); if (!opd->result.symkey_algo) return gpg_error_from_syserror (); + if (!mdc && !aead_algo) + opd->not_integrity_protected = 1; + return 0; } @@ -338,18 +364,50 @@ _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code, break; case GPGME_STATUS_EOF: - /* FIXME: These error values should probably be attributed to - the underlying crypto engine (as error source). */ - if (opd->failed && opd->pkdecrypt_failed) - return opd->pkdecrypt_failed; - else if (opd->failed && opd->any_no_seckey) - return gpg_error (GPG_ERR_NO_SECKEY); - else if (opd->failed) - return gpg_error (GPG_ERR_DECRYPT_FAILED); + /* We force an encryption failure if we know that integrity + * protection is missing. For modern version of gpg using + * modern cipher algorithms this is not required because gpg + * will issue a failure anyway. However older gpg versions emit + * only a warning. + * Fixme: These error values should probably be attributed to + * the underlying crypto engine (as error source). */ + if (opd->failed) + { + /* This comes from a specialized ERROR status line. */ + if (opd->pkdecrypt_failed) + return opd->pkdecrypt_failed; + + /* For an integrity failure return just DECRYPTION_FAILED; + * the actual cause can be taken from an already set + * decryption result flag. */ + if ((opd->not_integrity_protected && !ctx->ignore_mdc_error)) + return gpg_error (GPG_ERR_DECRYPT_FAILED); + + /* If we have any other ERROR code we prefer that over + * NO_SECKEY because it is probably the better matching + * code. For example a garbled message with multiple + * plaintext will return BAD_DATA here but may also have + * indicated a NO_SECKEY. */ + if (opd->first_status_error) + return opd->first_status_error; + + /* No secret key is pretty common reason. */ + if (opd->any_no_seckey) + return gpg_error (GPG_ERR_NO_SECKEY); + + /* Generic decryption failed error code. */ + return gpg_error (GPG_ERR_DECRYPT_FAILED); + } else if (!opd->okay) - return gpg_error (GPG_ERR_NO_DATA); + { + /* No data was found. */ + return gpg_error (GPG_ERR_NO_DATA); + } else if (opd->failure_code) - return opd->failure_code; + { + /* The engine returned failure code at program exit. */ + return opd->failure_code; + } break; case GPGME_STATUS_DECRYPTION_INFO: @@ -364,12 +422,21 @@ _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code, case GPGME_STATUS_DECRYPTION_FAILED: opd->failed = 1; + /* Tell the data object that it shall not return any data. We + * use the serial number because the data object may be owned by + * another thread. We also don't check for an error because it + * is possible that the data object has already been destroyed + * and we are then not interested in returning an error. */ + if (!ctx->ignore_mdc_error) + _gpgme_data_set_prop (NULL, opd->plaintext_dserial, + DATA_PROP_BLANKOUT, 1); break; case GPGME_STATUS_ERROR: /* Note that this is an informational status code which should - not lead to an error return unless it is something not - related to the backend. */ + * not lead to an error return unless it is something not + * related to the backend. However, it is used to return a + * better matching final error code. */ err = parse_status_error (args, opd); if (err) return err; @@ -452,7 +519,7 @@ decrypt_status_handler (void *priv, gpgme_status_code_t code, char *args) gpgme_error_t -_gpgme_op_decrypt_init_result (gpgme_ctx_t ctx) +_gpgme_op_decrypt_init_result (gpgme_ctx_t ctx, gpgme_data_t plaintext) { gpgme_error_t err; void *hook; @@ -465,6 +532,7 @@ _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx) return err; opd->last_recipient_p = &opd->result.recipients; + opd->plaintext_dserial = _gpgme_data_get_dserial (plaintext); return 0; } @@ -482,7 +550,7 @@ _gpgme_decrypt_start (gpgme_ctx_t ctx, int synchronous, if (err) return err; - err = _gpgme_op_decrypt_init_result (ctx); + err = _gpgme_op_decrypt_init_result (ctx, plain); if (err) return err; @@ -497,7 +565,7 @@ _gpgme_decrypt_start (gpgme_ctx_t ctx, int synchronous, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -546,5 +614,6 @@ gpgme_op_decrypt (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain) err = _gpgme_decrypt_start (ctx, 1, 0, cipher, plain); if (!err) err = _gpgme_wait_one (ctx); + ctx->ignore_mdc_error = 0; /* Always reset. */ return TRACE_ERR (err); } @@ -139,8 +139,7 @@ interact_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t key, opd->fnc_old = NULL; opd->fnc_value = fnc_value; - err = _gpgme_engine_set_command_handler (ctx->engine, command_handler, - ctx, out); + err = _gpgme_engine_set_command_handler (ctx->engine, command_handler, ctx); if (err) return err; @@ -219,8 +218,7 @@ edit_start (gpgme_ctx_t ctx, int synchronous, int type, gpgme_key_t key, opd->fnc_old = fnc; opd->fnc_value = fnc_value; - err = _gpgme_engine_set_command_handler (ctx->engine, command_handler, - ctx, out); + err = _gpgme_engine_set_command_handler (ctx->engine, command_handler, ctx); if (err) return err; diff --git a/src/encrypt-sign.c b/src/encrypt-sign.c index 4db46e2..cc34fbd 100644 --- a/src/encrypt-sign.c +++ b/src/encrypt-sign.c @@ -93,7 +93,7 @@ encrypt_sign_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t recp[], if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } diff --git a/src/encrypt.c b/src/encrypt.c index 2318497..a27a53a 100644 --- a/src/encrypt.c +++ b/src/encrypt.c @@ -242,7 +242,7 @@ encrypt_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t recp[], { /* Symmetric encryption requires a passphrase. */ err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } diff --git a/src/engine-backend.h b/src/engine-backend.h index f692666..4f33da1 100644 --- a/src/engine-backend.h +++ b/src/engine-backend.h @@ -55,7 +55,7 @@ struct engine_ops void *fnc_value); gpgme_error_t (*set_command_handler) (void *engine, engine_command_handler_t fnc, - void *fnc_value, gpgme_data_t data); + void *fnc_value); gpgme_error_t (*set_colon_line_handler) (void *engine, engine_colon_line_handler_t fnc, void *fnc_value); diff --git a/src/engine-gpg.c b/src/engine-gpg.c index 173e940..2833374 100644 --- a/src/engine-gpg.c +++ b/src/engine-gpg.c @@ -26,7 +26,6 @@ #include <stdlib.h> #include <string.h> #include <assert.h> -#include <errno.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif @@ -136,23 +135,24 @@ struct engine_gpg char *keyword; /* what has been requested (malloced) */ engine_command_handler_t fnc; void *fnc_value; - /* The kludges never end. This is used to couple command handlers - with output data in edit key mode. */ - gpgme_data_t linked_data; - int linked_idx; } cmd; struct gpgme_io_cbs io_cbs; gpgme_pinentry_mode_t pinentry_mode; char request_origin[10]; + char *auto_key_locate; struct { unsigned int no_symkey_cache : 1; unsigned int offline : 1; + unsigned int ignore_mdc_error : 1; } flags; /* NULL or the data object fed to --override_session_key-fd. */ gpgme_data_t override_session_key; + + /* Memory data containing diagnostics (--logger-fd) of gpg */ + gpgme_data_t diagnostics; }; typedef struct engine_gpg *engine_gpg_t; @@ -454,8 +454,10 @@ gpg_release (void *engine) free_argv (gpg->argv); if (gpg->cmd.keyword) free (gpg->cmd.keyword); + free (gpg->auto_key_locate); gpgme_data_release (gpg->override_session_key); + gpgme_data_release (gpg->diagnostics); free (gpg); } @@ -503,8 +505,6 @@ gpg_new (void **engine, const char *file_name, const char *home_dir, gpg->colon.fd[1] = -1; gpg->cmd.fd = -1; gpg->cmd.idx = -1; - gpg->cmd.linked_data = NULL; - gpg->cmd.linked_idx = -1; /* Allocate the read buffer for the status pipe. */ gpg->status.bufsize = 1024; @@ -626,6 +626,16 @@ gpg_new (void **engine, const char *file_name, const char *home_dir, } } + rc = gpgme_data_new (&gpg->diagnostics); + if (rc) + goto leave; + + rc = add_arg (gpg, "--logger-fd"); + if (rc) + goto leave; + + rc = add_data (gpg, gpg->diagnostics, -2, 1); + leave: if (rc) gpg_release (gpg); @@ -651,11 +661,20 @@ gpg_set_engine_flags (void *engine, const gpgme_ctx_t ctx) else *gpg->request_origin = 0; + if (ctx->auto_key_locate && have_gpg_version (gpg, "2.1.18")) + { + if (gpg->auto_key_locate) + free (gpg->auto_key_locate); + gpg->auto_key_locate = _gpgme_strconcat ("--auto-key-locate=", + ctx->auto_key_locate, NULL); + } + gpg->flags.no_symkey_cache = (ctx->no_symkey_cache && have_gpg_version (gpg, "2.2.7")); - gpg->flags.offline = (ctx->offline && have_gpg_version (gpg, "2.1.23")); + gpg->flags.ignore_mdc_error = !!ctx->ignore_mdc_error; + } @@ -793,14 +812,14 @@ command_handler (void *opaque, int fd) -/* The Fnc will be called to get a value for one of the commands with - a key KEY. If the Code passed to FNC is 0, the function may release - resources associated with the returned value from another call. To - match such a second call to a first call, the returned value from - the first call is passed as keyword. */ +/* The FNC will be called to get a value for one of the commands with + * a key KEY. If the code passed to FNC is 0, the function may + * release resources associated with the returned value from another + * call. To match such a second call to a first call, the returned + * value from the first call is passed as keyword. */ static gpgme_error_t gpg_set_command_handler (void *engine, engine_command_handler_t fnc, - void *fnc_value, gpgme_data_t linked_data) + void *fnc_value) { engine_gpg_t gpg = engine; gpgme_error_t rc; @@ -819,7 +838,6 @@ gpg_set_command_handler (void *engine, engine_command_handler_t fnc, gpg->cmd.fnc = fnc; gpg->cmd.cb_data = (void *) &gpg->cmd; gpg->cmd.fnc_value = fnc_value; - gpg->cmd.linked_data = linked_data; gpg->cmd.used = 1; return 0; } @@ -950,6 +968,19 @@ build_argv (engine_gpg_t gpg, const char *pgmname) argc++; } + if (gpg->auto_key_locate) + { + argv[argc] = strdup (gpg->auto_key_locate); + if (!argv[argc]) + { + int saved_err = gpg_error_from_syserror (); + free (fd_data_map); + free_argv (argv); + return saved_err; + } + argc++; + } + if (gpg->flags.no_symkey_cache) { argv[argc] = strdup ("--no-symkey-cache"); @@ -963,6 +994,19 @@ build_argv (engine_gpg_t gpg, const char *pgmname) argc++; } + if (gpg->flags.ignore_mdc_error) + { + argv[argc] = strdup ("--ignore-mdc-error"); + if (!argv[argc]) + { + int saved_err = gpg_error_from_syserror (); + free (fd_data_map); + free_argv (argv); + return saved_err; + } + argc++; + } + if (gpg->flags.offline) { argv[argc] = strdup ("--disable-dirmngr"); @@ -1039,10 +1083,10 @@ build_argv (engine_gpg_t gpg, const char *pgmname) if (_gpgme_io_pipe (fds, fd_data_map[datac].inbound ? 1 : 0) == -1) { - int saved_errno = errno; + int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); - return gpg_error (saved_errno); + return saved_err; } if (_gpgme_io_set_close_notify (fds[0], close_notify_handler, gpg) @@ -1077,11 +1121,6 @@ build_argv (engine_gpg_t gpg, const char *pgmname) assert (gpg->cmd.idx == -1); gpg->cmd.idx = datac; } - else if (gpg->cmd.linked_data == a->data) - { - assert (gpg->cmd.linked_idx == -1); - gpg->cmd.linked_idx = datac; - } } fd_data_map[datac].data = a->data; @@ -1268,44 +1307,6 @@ read_status (engine_gpg_t gpg) if (err) return err; } - - if (r == GPGME_STATUS_END_STREAM) - { - if (gpg->cmd.used) - { - /* Before we can actually add the - command fd, we might have to flush - the linked output data pipe. */ - if (gpg->cmd.linked_idx != -1 - && gpg->fd_data_map[gpg->cmd.linked_idx].fd - != -1) - { - struct io_select_fd_s fds; - fds.fd = - gpg->fd_data_map[gpg->cmd.linked_idx].fd; - fds.for_read = 1; - fds.for_write = 0; - fds.opaque = NULL; - do - { - fds.signaled = 0; - _gpgme_io_select (&fds, 1, 1); - if (fds.signaled) - _gpgme_data_inbound_handler - (gpg->cmd.linked_data, fds.fd); - } - while (fds.signaled); - } - - /* XXX We must check if there are any - more fds active after removing this - one. */ - (*gpg->io_cbs.remove) - (gpg->fd_data_map[gpg->cmd.idx].tag); - gpg->cmd.fd = gpg->fd_data_map[gpg->cmd.idx].fd; - gpg->fd_data_map[gpg->cmd.idx].fd = -1; - } - } } } /* To reuse the buffer for the next line we have to @@ -2240,13 +2241,22 @@ export_common (engine_gpg_t gpg, gpgme_export_mode_t mode, return gpg_error (GPG_ERR_NOT_SUPPORTED); if ((mode & GPGME_EXPORT_MODE_MINIMAL)) - err = add_arg (gpg, "--export-options=export-minimal"); + { + if ((mode & GPGME_EXPORT_MODE_NOUID)) + err = add_arg (gpg, "--export-options=export-minimal,export-drop-uids"); + else + err = add_arg (gpg, "--export-options=export-minimal"); + } + else if ((mode & GPGME_EXPORT_MODE_NOUID)) + err = add_arg (gpg, "--export-options=export-drop-uids"); if (err) ; else if ((mode & GPGME_EXPORT_MODE_EXTERN)) { err = add_arg (gpg, "--send-keys"); + if (!err && (mode & GPGME_EXPORT_MODE_NOUID)) + err = add_arg (gpg, "--keyserver-options=export-drop-uids"); } else { @@ -3279,6 +3289,52 @@ gpg_set_pinentry_mode (void *engine, gpgme_pinentry_mode_t mode) } +static gpgme_error_t +gpg_getauditlog (void *engine, gpgme_data_t output, unsigned int flags) +{ + engine_gpg_t gpg = engine; +#define MYBUFLEN 4096 + char buf[MYBUFLEN]; + int nread; + int any_written = 0; + + if (!(flags & GPGME_AUDITLOG_DIAG)) + { + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + + if (!gpg || !output) + { + return gpg_error (GPG_ERR_INV_VALUE); + } + + if (!gpg->diagnostics) + { + return gpg_error (GPG_ERR_GENERAL); + } + + gpgme_data_rewind (gpg->diagnostics); + + while ((nread = gpgme_data_read (gpg->diagnostics, buf, MYBUFLEN)) > 0) + { + any_written = 1; + if (gpgme_data_write (output, buf, nread) == -1) + return gpg_error_from_syserror (); + } + if (!any_written) + { + return gpg_error (GPG_ERR_NO_DATA); + } + + if (nread == -1) + return gpg_error_from_syserror (); + + gpgme_data_rewind (output); + return 0; +#undef MYBUFLEN +} + + struct engine_ops _gpgme_engine_ops_gpg = { @@ -3316,7 +3372,7 @@ struct engine_ops _gpgme_engine_ops_gpg = gpg_sign, gpg_trustlist, gpg_verify, - NULL, /* getauditlog */ + gpg_getauditlog, NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c index da7e524..3266e36 100644 --- a/src/engine-gpgsm.c +++ b/src/engine-gpgsm.c @@ -37,7 +37,6 @@ #include <locale.h> #endif #include <fcntl.h> /* FIXME */ -#include <errno.h> #include "gpgme.h" #include "util.h" @@ -986,8 +985,7 @@ status_handler (void *opaque, int fd) while (linelen > 0) { nwritten = gpgme_data_write (gpgsm->inline_data, src, linelen); - if (!nwritten || (nwritten < 0 && errno != EINTR) - || nwritten > linelen) + if (nwritten <= 0 || nwritten > linelen) { err = gpg_error_from_syserror (); break; @@ -1013,8 +1011,17 @@ status_handler (void *opaque, int fd) *(rest++) = 0; r = _gpgme_parse_status (line + 2); + if (gpgsm->status.mon_cb && r != GPGME_STATUS_PROGRESS) + { + /* Note that we call the monitor even if we do + * not know the status code (r < 0). */ + err = gpgsm->status.mon_cb (gpgsm->status.mon_cb_value, + line + 2, rest); + } + else + err = 0; - if (r >= 0) + if (r >= 0 && !err) { if (gpgsm->status.fnc) { @@ -2057,6 +2064,9 @@ gpgsm_getauditlog (void *engine, gpgme_data_t output, unsigned int flags) if (!gpgsm || !output) return gpg_error (GPG_ERR_INV_VALUE); + if ((flags & GPGME_AUDITLOG_DIAG)) + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + #if USE_DESCRIPTOR_PASSING gpgsm->output_cb.data = output; err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0); diff --git a/src/engine.c b/src/engine.c index b716ca2..b629bea 100644 --- a/src/engine.c +++ b/src/engine.c @@ -596,8 +596,7 @@ _gpgme_engine_set_status_handler (engine_t engine, gpgme_error_t _gpgme_engine_set_command_handler (engine_t engine, engine_command_handler_t fnc, - void *fnc_value, - gpgme_data_t linked_data) + void *fnc_value) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); @@ -605,8 +604,7 @@ _gpgme_engine_set_command_handler (engine_t engine, if (!engine->ops->set_command_handler) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); - return (*engine->ops->set_command_handler) (engine->engine, - fnc, fnc_value, linked_data); + return (*engine->ops->set_command_handler) (engine->engine, fnc, fnc_value); } gpgme_error_t diff --git a/src/engine.h b/src/engine.h index 8b692f2..c512a25 100644 --- a/src/engine.h +++ b/src/engine.h @@ -78,8 +78,7 @@ void _gpgme_engine_set_status_handler (engine_t engine, void *fnc_value); gpgme_error_t _gpgme_engine_set_command_handler (engine_t engine, engine_command_handler_t fnc, - void *fnc_value, - gpgme_data_t data); + void *fnc_value); gpgme_error_t _gpgme_engine_set_colon_line_handler (engine_t engine, engine_colon_line_handler_t fnc, diff --git a/src/export.c b/src/export.c index cd94050..f460e85 100644 --- a/src/export.c +++ b/src/export.c @@ -123,6 +123,7 @@ export_start (gpgme_ctx_t ctx, int synchronous, const char *pattern, |GPGME_EXPORT_MODE_MINIMAL |GPGME_EXPORT_MODE_SECRET |GPGME_EXPORT_MODE_RAW + |GPGME_EXPORT_MODE_NOUID |GPGME_EXPORT_MODE_PKCS12))) return gpg_error (GPG_ERR_INV_VALUE); /* Invalid flags in MODE. */ diff --git a/src/genkey.c b/src/genkey.c index 16484ec..ffca7e8 100644 --- a/src/genkey.c +++ b/src/genkey.c @@ -259,7 +259,7 @@ genkey_start (gpgme_ctx_t ctx, int synchronous, const char *parms, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -345,7 +345,7 @@ createkey_start (gpgme_ctx_t ctx, int synchronous, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -433,7 +433,7 @@ createsubkey_start (gpgme_ctx_t ctx, int synchronous, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -519,7 +519,7 @@ addrevuid_start (gpgme_ctx_t ctx, int synchronous, int extraflags, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } diff --git a/src/getauditlog.c b/src/getauditlog.c index dbaf260..d70e66f 100644 --- a/src/getauditlog.c +++ b/src/getauditlog.c @@ -47,9 +47,12 @@ getauditlog_start (gpgme_ctx_t ctx, int synchronous, if (!output) return gpg_error (GPG_ERR_INV_VALUE); - err = _gpgme_op_reset (ctx, ((synchronous&255) | 256) ); - if (err) - return err; + if (!(flags & GPGME_AUDITLOG_DIAG)) + { + err = _gpgme_op_reset (ctx, ((synchronous&255) | 256) ); + if (err) + return err; + } _gpgme_engine_set_status_handler (ctx->engine, getauditlog_status_handler, ctx); diff --git a/src/gpgme-json.c b/src/gpgme-json.c index f1e9f25..b10331b 100644 --- a/src/gpgme-json.c +++ b/src/gpgme-json.c @@ -48,24 +48,25 @@ int main (void){fputs ("Build with Libgpg-error >= 1.28!\n", stderr);return 1;} /* We don't allow a request with more than 64 MiB. */ #define MAX_REQUEST_SIZE (64 * 1024 * 1024) -/* Minimal, default and maximum chunk size for returned data. The - * first chunk is returned directly. If the "more" flag is also - * returned, a "getmore" command needs to be used to get the next - * chunk. Right now this value covers just the value of the "data" - * element; so to cover for the other returned objects this values - * needs to be lower than the maximum allowed size of the browser. */ -#define MIN_REPLY_CHUNK_SIZE 512 -#define DEF_REPLY_CHUNK_SIZE (512 * 1024) +/* Minimal chunk size for returned data.*/ +#define MIN_REPLY_CHUNK_SIZE 30 + +/* If no chunksize is provided we print everything. Changing + * this to a positive value will result in all messages beeing + * chunked. */ +#define DEF_REPLY_CHUNK_SIZE 0 #define MAX_REPLY_CHUNK_SIZE (10 * 1024 * 1024) static void xoutofcore (const char *type) GPGRT_ATTR_NORETURN; static cjson_t error_object_v (cjson_t json, const char *message, - va_list arg_ptr) GPGRT_ATTR_PRINTF(2,0); + va_list arg_ptr, gpg_error_t err) + GPGRT_ATTR_PRINTF(2,0); static cjson_t error_object (cjson_t json, const char *message, ...) GPGRT_ATTR_PRINTF(2,3); static char *error_object_string (const char *message, ...) GPGRT_ATTR_PRINTF(1,2); +static char *process_request (const char *request); /* True if interactive mode is active. */ @@ -79,8 +80,6 @@ static struct char *buffer; /* Malloced data or NULL if not used. */ size_t length; /* Length of that data. */ size_t written; /* # of already written bytes from BUFFER. */ - const char *type;/* The "type" of the data. */ - int base64; /* The "base64" flag of the data. */ } pending_data; @@ -88,13 +87,7 @@ static struct * Helper functions and macros */ -#define xtrymalloc(a) gpgrt_malloc ((a)) #define xtrystrdup(a) gpgrt_strdup ((a)) -#define xmalloc(a) ({ \ - void *_r = gpgrt_malloc ((a)); \ - if (!_r) \ - xoutofcore ("malloc"); \ - _r; }) #define xcalloc(a,b) ({ \ void *_r = gpgrt_calloc ((a), (b)); \ if (!_r) \ @@ -112,6 +105,21 @@ static struct _r; }) #define xfree(a) gpgrt_free ((a)) +/* Only use calloc. */ +#define CALLOC_ONLY 1 + +#if CALLOC_ONLY +#define xtrymalloc(a) gpgrt_calloc (1, (a)) +#define xmalloc(a) xcalloc(1, (a)) +#else +#define xtrymalloc(a) gpgrt_malloc ((a)) +#define xmalloc(a) ({ \ + void *_r = gpgrt_malloc ((a)); \ + if (!_r) \ + xoutofcore ("malloc"); \ + _r; }) +#endif + #define spacep(p) (*(p) == ' ' || *(p) == '\t') #ifndef HAVE_STPCPY @@ -127,6 +135,19 @@ _my_stpcpy (char *a, const char *b) #endif /*!HAVE_STPCPY*/ +/* Free a NULL terminated array */ +static void +xfree_array (char **array) +{ + if (array) + { + int idx; + for (idx = 0; array[idx]; idx++) + xfree (array[idx]); + xfree (array); + } +} + static void xoutofcore (const char *type) @@ -147,6 +168,16 @@ xjson_CreateObject (void) return json; } +/* Call cJSON_CreateArray but terminate in case of an error. */ +static cjson_t +xjson_CreateArray (void) +{ + cjson_t json = cJSON_CreateArray (); + if (!json) + xoutofcore ("cJSON_CreateArray"); + return json; +} + /* Wrapper around cJSON_AddStringToObject which returns an gpg-error * code instead of the NULL or the new object. */ @@ -169,6 +200,15 @@ xjson_AddStringToObject (cjson_t object, const char *name, const char *string) } +/* Same as xjson_AddStringToObject but ignores NULL strings */ +static void +xjson_AddStringToObject0 (cjson_t object, const char *name, const char *string) +{ + if (!string) + return; + xjson_AddStringToObject (object, name, string); +} + /* Wrapper around cJSON_AddBoolToObject which terminates the process * in case of an error. */ static void @@ -179,6 +219,26 @@ xjson_AddBoolToObject (cjson_t object, const char *name, int abool) return ; } +/* Wrapper around cJSON_AddNumberToObject which terminates the process + * in case of an error. */ +static void +xjson_AddNumberToObject (cjson_t object, const char *name, double dbl) +{ + if (!cJSON_AddNumberToObject (object, name, dbl)) + xoutofcore ("cJSON_AddNumberToObject"); + return ; +} + +/* Wrapper around cJSON_AddItemToObject which terminates the process + * in case of an error. */ +static void +xjson_AddItemToObject (cjson_t object, const char *name, cjson_t item) +{ + if (!cJSON_AddItemToObject (object, name, item)) + xoutofcore ("cJSON_AddItemToObject"); + return ; +} + /* This is similar to cJSON_AddStringToObject but takes (DATA, * DATALEN) and adds it under NAME as a base 64 encoded string to * OBJECT. */ @@ -256,7 +316,8 @@ add_base64_to_object (cjson_t object, const char *name, /* Create a JSON error object. If JSON is not NULL the error message * is appended to that object. An existing "type" item will be replaced. */ static cjson_t -error_object_v (cjson_t json, const char *message, va_list arg_ptr) +error_object_v (cjson_t json, const char *message, va_list arg_ptr, + gpg_error_t err) { cjson_t response, j_tmp; char *msg; @@ -277,8 +338,10 @@ error_object_v (cjson_t json, const char *message, va_list arg_ptr) cJSON_ReplaceItemInObject (response, "type", j_tmp); } xjson_AddStringToObject (response, "msg", msg); - xfree (msg); + + xjson_AddNumberToObject (response, "code", err); + return response; } @@ -302,7 +365,20 @@ error_object (cjson_t json, const char *message, ...) va_list arg_ptr; va_start (arg_ptr, message); - response = error_object_v (json, message, arg_ptr); + response = error_object_v (json, message, arg_ptr, 0); + va_end (arg_ptr); + return response; +} + + +static cjson_t +gpg_error_object (cjson_t json, gpg_error_t err, const char *message, ...) +{ + cjson_t response; + va_list arg_ptr; + + va_start (arg_ptr, message); + response = error_object_v (json, message, arg_ptr, err); va_end (arg_ptr); return response; } @@ -316,7 +392,7 @@ error_object_string (const char *message, ...) char *msg; va_start (arg_ptr, message); - response = error_object_v (NULL, message, arg_ptr); + response = error_object_v (NULL, message, arg_ptr, 0); va_end (arg_ptr); msg = xjson_Print (response); @@ -398,12 +474,13 @@ get_chunksize (cjson_t json, size_t *r_chunksize) } -/* Extract the keys from the "keys" array in the JSON object. On - * success a string with the keys identifiers is stored at R_KEYS. +/* Extract the keys from the array or string with the name "name" + * in the JSON object. On success a string with the keys identifiers + * is stored at R_KEYS. * The keys in that string are LF delimited. On failure an error code * is returned. */ static gpg_error_t -get_keys (cjson_t json, char **r_keystring) +get_keys (cjson_t json, const char *name, char **r_keystring) { cjson_t j_keys, j_item; int i, nkeys; @@ -412,7 +489,7 @@ get_keys (cjson_t json, char **r_keystring) *r_keystring = NULL; - j_keys = cJSON_GetObjectItem (json, "keys"); + j_keys = cJSON_GetObjectItem (json, name); if (!j_keys) return gpg_error (GPG_ERR_NO_KEY); if (!cjson_is_array (j_keys) && !cjson_is_string (j_keys)) @@ -490,12 +567,12 @@ _create_new_context (gpgme_protocol_t proto) /* Return a context object for protocol PROTO. This is currently a - * statuically allocated context initialized for PROTO. Termnates + * statically allocated context initialized for PROTO. Terminates * process on failure. */ static gpgme_ctx_t get_context (gpgme_protocol_t proto) { - static gpgme_ctx_t ctx_openpgp, ctx_cms; + static gpgme_ctx_t ctx_openpgp, ctx_cms, ctx_conf; if (proto == GPGME_PROTOCOL_OpenPGP) { @@ -509,12 +586,17 @@ get_context (gpgme_protocol_t proto) ctx_cms = _create_new_context (proto); return ctx_cms; } + else if (proto == GPGME_PROTOCOL_GPGCONF) + { + if (!ctx_conf) + ctx_conf = _create_new_context (proto); + return ctx_conf; + } else log_bug ("invalid protocol %d requested\n", proto); } - /* Free context object retrieved by get_context. */ static void release_context (gpgme_ctx_t ctx) @@ -524,6 +606,23 @@ release_context (gpgme_ctx_t ctx) } +/* Create an addition context for short operations. */ +static gpgme_ctx_t +create_onetime_context (gpgme_protocol_t proto) +{ + return _create_new_context (proto); + +} + + +/* Release a one-time context. */ +static void +release_onetime_context (gpgme_ctx_t ctx) +{ + return gpgme_release (ctx); + +} + /* Given a Base-64 encoded string object in JSON return a gpgme data * object at R_DATA. */ @@ -590,28 +689,769 @@ data_from_base64_string (gpgme_data_t *r_data, cjson_t json) } - -/* - * Implementation of the commands. - */ +/* Create a keylist pattern array from a json keys object + * in the request. Returns either a malloced NULL terminated + * string array which can be used as patterns for + * op_keylist_ext or NULL. */ +static char ** +create_keylist_patterns (cjson_t request, const char *name) +{ + char *keystring; + char *p; + char *tmp; + char **ret; + int cnt = 2; /* Last NULL and one is not newline delimited */ + int i = 0; + + if (get_keys (request, name, &keystring)) + return NULL; + + for (p = keystring; *p; p++) + if (*p == '\n') + cnt++; + + ret = xcalloc (cnt, sizeof *ret); + + for (p = keystring, tmp = keystring; *p; p++) + { + if (*p != '\n') + continue; + *p = '\0'; + ret[i++] = xstrdup (tmp); + tmp = p + 1; + } + /* The last key is not newline delimted. */ + ret[i] = *tmp ? xstrdup (tmp) : NULL; + + xfree (keystring); + return ret; +} + + +/* Do a secret keylisting for protocol proto and add the fingerprints of + the secret keys for patterns to the result as "sec-fprs" array. */ +static gpg_error_t +add_secret_fprs (const char **patterns, gpgme_protocol_t protocol, + cjson_t result) +{ + gpgme_ctx_t ctx; + gpg_error_t err; + gpgme_key_t key = NULL; + cjson_t j_fprs = xjson_CreateArray (); + + ctx = create_onetime_context (protocol); + + gpgme_set_keylist_mode (ctx, GPGME_KEYLIST_MODE_LOCAL | + GPGME_KEYLIST_MODE_WITH_SECRET); + + err = gpgme_op_keylist_ext_start (ctx, patterns, 1, 0); + + if (err) + { + gpg_error_object (result, err, "Error listing keys: %s", + gpg_strerror (err)); + goto leave; + } + + while (!(err = gpgme_op_keylist_next (ctx, &key))) + { + if (!key || !key->fpr) + continue; + cJSON_AddItemToArray (j_fprs, cJSON_CreateString (key->fpr)); + gpgme_key_unref (key); + key = NULL; + } + err = 0; + + release_onetime_context (ctx); + ctx = NULL; + + xjson_AddItemToObject (result, "sec-fprs", j_fprs); + +leave: + release_onetime_context (ctx); + gpgme_key_unref (key); + + return err; +} + + +/* Create sigsum json array */ +static cjson_t +sigsum_to_json (gpgme_sigsum_t summary) +{ + cjson_t result = xjson_CreateObject (); + cjson_t sigsum_array = xjson_CreateArray (); + + if ( (summary & GPGME_SIGSUM_VALID )) + cJSON_AddItemToArray (sigsum_array, + cJSON_CreateString ("valid")); + if ( (summary & GPGME_SIGSUM_GREEN )) + cJSON_AddItemToArray (sigsum_array, + cJSON_CreateString ("green")); + if ( (summary & GPGME_SIGSUM_RED )) + cJSON_AddItemToArray (sigsum_array, + cJSON_CreateString ("red")); + if ( (summary & GPGME_SIGSUM_KEY_REVOKED)) + cJSON_AddItemToArray (sigsum_array, + cJSON_CreateString ("revoked")); + if ( (summary & GPGME_SIGSUM_KEY_EXPIRED)) + cJSON_AddItemToArray (sigsum_array, + cJSON_CreateString ("key-expired")); + if ( (summary & GPGME_SIGSUM_SIG_EXPIRED)) + cJSON_AddItemToArray (sigsum_array, + cJSON_CreateString ("sig-expired")); + if ( (summary & GPGME_SIGSUM_KEY_MISSING)) + cJSON_AddItemToArray (sigsum_array, + cJSON_CreateString ("key-missing")); + if ( (summary & GPGME_SIGSUM_CRL_MISSING)) + cJSON_AddItemToArray (sigsum_array, + cJSON_CreateString ("crl-missing")); + if ( (summary & GPGME_SIGSUM_CRL_TOO_OLD)) + cJSON_AddItemToArray (sigsum_array, + cJSON_CreateString ("crl-too-old")); + if ( (summary & GPGME_SIGSUM_BAD_POLICY )) + cJSON_AddItemToArray (sigsum_array, + cJSON_CreateString ("bad-policy")); + if ( (summary & GPGME_SIGSUM_SYS_ERROR )) + cJSON_AddItemToArray (sigsum_array, + cJSON_CreateString ("sys-error")); + /* The signature summary as string array. */ + xjson_AddItemToObject (result, "sigsum", sigsum_array); + + /* Bools for the same. */ + xjson_AddBoolToObject (result, "valid", + (summary & GPGME_SIGSUM_VALID )); + xjson_AddBoolToObject (result, "green", + (summary & GPGME_SIGSUM_GREEN )); + xjson_AddBoolToObject (result, "red", + (summary & GPGME_SIGSUM_RED )); + xjson_AddBoolToObject (result, "revoked", + (summary & GPGME_SIGSUM_KEY_REVOKED)); + xjson_AddBoolToObject (result, "key-expired", + (summary & GPGME_SIGSUM_KEY_EXPIRED)); + xjson_AddBoolToObject (result, "sig-expired", + (summary & GPGME_SIGSUM_SIG_EXPIRED)); + xjson_AddBoolToObject (result, "key-missing", + (summary & GPGME_SIGSUM_KEY_MISSING)); + xjson_AddBoolToObject (result, "crl-missing", + (summary & GPGME_SIGSUM_CRL_MISSING)); + xjson_AddBoolToObject (result, "crl-too-old", + (summary & GPGME_SIGSUM_CRL_TOO_OLD)); + xjson_AddBoolToObject (result, "bad-policy", + (summary & GPGME_SIGSUM_BAD_POLICY )); + xjson_AddBoolToObject (result, "sys-error", + (summary & GPGME_SIGSUM_SYS_ERROR )); + + return result; +} + + +/* Helper for summary formatting */ +static const char * +validity_to_string (gpgme_validity_t val) +{ + switch (val) + { + case GPGME_VALIDITY_UNDEFINED:return "undefined"; + case GPGME_VALIDITY_NEVER: return "never"; + case GPGME_VALIDITY_MARGINAL: return "marginal"; + case GPGME_VALIDITY_FULL: return "full"; + case GPGME_VALIDITY_ULTIMATE: return "ultimate"; + case GPGME_VALIDITY_UNKNOWN: + default: return "unknown"; + } +} + +static const char * +protocol_to_string (gpgme_protocol_t proto) +{ + switch (proto) + { + case GPGME_PROTOCOL_OpenPGP: return "OpenPGP"; + case GPGME_PROTOCOL_CMS: return "CMS"; + case GPGME_PROTOCOL_GPGCONF: return "gpgconf"; + case GPGME_PROTOCOL_ASSUAN: return "assuan"; + case GPGME_PROTOCOL_G13: return "g13"; + case GPGME_PROTOCOL_UISERVER:return "uiserver"; + case GPGME_PROTOCOL_SPAWN: return "spawn"; + default: + return "unknown"; + } +} + +/* Create a sig_notation json object */ +static cjson_t +sig_notation_to_json (gpgme_sig_notation_t not) +{ + cjson_t result = xjson_CreateObject (); + xjson_AddBoolToObject (result, "human_readable", not->human_readable); + xjson_AddBoolToObject (result, "critical", not->critical); + + xjson_AddStringToObject0 (result, "name", not->name); + xjson_AddStringToObject0 (result, "value", not->value); + + xjson_AddNumberToObject (result, "flags", not->flags); + + return result; +} + +/* Create a key_sig json object */ +static cjson_t +key_sig_to_json (gpgme_key_sig_t sig) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", sig->revoked); + xjson_AddBoolToObject (result, "expired", sig->expired); + xjson_AddBoolToObject (result, "invalid", sig->invalid); + xjson_AddBoolToObject (result, "exportable", sig->exportable); + + xjson_AddStringToObject0 (result, "pubkey_algo_name", + gpgme_pubkey_algo_name (sig->pubkey_algo)); + xjson_AddStringToObject0 (result, "keyid", sig->keyid); + xjson_AddStringToObject0 (result, "status", gpgme_strerror (sig->status)); + xjson_AddStringToObject0 (result, "name", sig->name); + xjson_AddStringToObject0 (result, "email", sig->email); + xjson_AddStringToObject0 (result, "comment", sig->comment); + + xjson_AddNumberToObject (result, "pubkey_algo", sig->pubkey_algo); + xjson_AddNumberToObject (result, "timestamp", sig->timestamp); + xjson_AddNumberToObject (result, "expires", sig->expires); + xjson_AddNumberToObject (result, "status_code", sig->status); + xjson_AddNumberToObject (result, "sig_class", sig->sig_class); + + if (sig->notations) + { + gpgme_sig_notation_t not; + cjson_t array = xjson_CreateArray (); + for (not = sig->notations; not; not = not->next) + cJSON_AddItemToArray (array, sig_notation_to_json (not)); + xjson_AddItemToObject (result, "notations", array); + } + + return result; +} + +/* Create a tofu info object */ +static cjson_t +tofu_to_json (gpgme_tofu_info_t tofu) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "description", tofu->description); + + xjson_AddNumberToObject (result, "validity", tofu->validity); + xjson_AddNumberToObject (result, "policy", tofu->policy); + xjson_AddNumberToObject (result, "signcount", tofu->signcount); + xjson_AddNumberToObject (result, "encrcount", tofu->encrcount); + xjson_AddNumberToObject (result, "signfirst", tofu->signfirst); + xjson_AddNumberToObject (result, "signlast", tofu->signlast); + xjson_AddNumberToObject (result, "encrfirst", tofu->encrfirst); + xjson_AddNumberToObject (result, "encrlast", tofu->encrlast); + + return result; +} + +/* Create a userid json object */ +static cjson_t +uid_to_json (gpgme_user_id_t uid) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", uid->revoked); + xjson_AddBoolToObject (result, "invalid", uid->invalid); + + xjson_AddStringToObject0 (result, "validity", + validity_to_string (uid->validity)); + xjson_AddStringToObject0 (result, "uid", uid->uid); + xjson_AddStringToObject0 (result, "name", uid->name); + xjson_AddStringToObject0 (result, "email", uid->email); + xjson_AddStringToObject0 (result, "comment", uid->comment); + xjson_AddStringToObject0 (result, "address", uid->address); + + xjson_AddNumberToObject (result, "origin", uid->origin); + xjson_AddNumberToObject (result, "last_update", uid->last_update); + + /* Key sigs */ + if (uid->signatures) + { + cjson_t sig_array = xjson_CreateArray (); + gpgme_key_sig_t sig; + + for (sig = uid->signatures; sig; sig = sig->next) + cJSON_AddItemToArray (sig_array, key_sig_to_json (sig)); + + xjson_AddItemToObject (result, "signatures", sig_array); + } + + /* TOFU info */ + if (uid->tofu) + { + gpgme_tofu_info_t tofu; + cjson_t array = xjson_CreateArray (); + for (tofu = uid->tofu; tofu; tofu = tofu->next) + cJSON_AddItemToArray (array, tofu_to_json (tofu)); + xjson_AddItemToObject (result, "tofu", array); + } + + return result; +} + +/* Create a subkey json object */ +static cjson_t +subkey_to_json (gpgme_subkey_t sub) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", sub->revoked); + xjson_AddBoolToObject (result, "expired", sub->expired); + xjson_AddBoolToObject (result, "disabled", sub->disabled); + xjson_AddBoolToObject (result, "invalid", sub->invalid); + xjson_AddBoolToObject (result, "can_encrypt", sub->can_encrypt); + xjson_AddBoolToObject (result, "can_sign", sub->can_sign); + xjson_AddBoolToObject (result, "can_certify", sub->can_certify); + xjson_AddBoolToObject (result, "can_authenticate", sub->can_authenticate); + xjson_AddBoolToObject (result, "secret", sub->secret); + xjson_AddBoolToObject (result, "is_qualified", sub->is_qualified); + xjson_AddBoolToObject (result, "is_cardkey", sub->is_cardkey); + xjson_AddBoolToObject (result, "is_de_vs", sub->is_de_vs); + + xjson_AddStringToObject0 (result, "pubkey_algo_name", + gpgme_pubkey_algo_name (sub->pubkey_algo)); + xjson_AddStringToObject0 (result, "pubkey_algo_string", + gpgme_pubkey_algo_string (sub)); + xjson_AddStringToObject0 (result, "keyid", sub->keyid); + xjson_AddStringToObject0 (result, "card_number", sub->card_number); + xjson_AddStringToObject0 (result, "curve", sub->curve); + xjson_AddStringToObject0 (result, "keygrip", sub->keygrip); + + xjson_AddNumberToObject (result, "pubkey_algo", sub->pubkey_algo); + xjson_AddNumberToObject (result, "length", sub->length); + xjson_AddNumberToObject (result, "timestamp", sub->timestamp); + xjson_AddNumberToObject (result, "expires", sub->expires); + + return result; +} + +/* Create a key json object */ +static cjson_t +key_to_json (gpgme_key_t key) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", key->revoked); + xjson_AddBoolToObject (result, "expired", key->expired); + xjson_AddBoolToObject (result, "disabled", key->disabled); + xjson_AddBoolToObject (result, "invalid", key->invalid); + xjson_AddBoolToObject (result, "can_encrypt", key->can_encrypt); + xjson_AddBoolToObject (result, "can_sign", key->can_sign); + xjson_AddBoolToObject (result, "can_certify", key->can_certify); + xjson_AddBoolToObject (result, "can_authenticate", key->can_authenticate); + xjson_AddBoolToObject (result, "secret", key->secret); + xjson_AddBoolToObject (result, "is_qualified", key->is_qualified); + + xjson_AddStringToObject0 (result, "protocol", + protocol_to_string (key->protocol)); + xjson_AddStringToObject0 (result, "issuer_serial", key->issuer_serial); + xjson_AddStringToObject0 (result, "issuer_name", key->issuer_name); + xjson_AddStringToObject0 (result, "fingerprint", key->fpr); + xjson_AddStringToObject0 (result, "chain_id", key->chain_id); + xjson_AddStringToObject0 (result, "owner_trust", + validity_to_string (key->owner_trust)); + + xjson_AddNumberToObject (result, "origin", key->origin); + xjson_AddNumberToObject (result, "last_update", key->last_update); + + /* Add subkeys */ + if (key->subkeys) + { + cjson_t subkey_array = xjson_CreateArray (); + gpgme_subkey_t sub; + for (sub = key->subkeys; sub; sub = sub->next) + cJSON_AddItemToArray (subkey_array, subkey_to_json (sub)); + + xjson_AddItemToObject (result, "subkeys", subkey_array); + } + + /* User Ids */ + if (key->uids) + { + cjson_t uid_array = xjson_CreateArray (); + gpgme_user_id_t uid; + for (uid = key->uids; uid; uid = uid->next) + cJSON_AddItemToArray (uid_array, uid_to_json (uid)); + + xjson_AddItemToObject (result, "userids", uid_array); + } + + return result; +} -/* Create a "data" object and the "type", "base64" and "more" flags - * from DATA and append them to RESULT. Ownership if DATA is +/* Create a signature json object */ +static cjson_t +signature_to_json (gpgme_signature_t sig) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddItemToObject (result, "summary", sigsum_to_json (sig->summary)); + + xjson_AddBoolToObject (result, "wrong_key_usage", sig->wrong_key_usage); + xjson_AddBoolToObject (result, "chain_model", sig->chain_model); + xjson_AddBoolToObject (result, "is_de_vs", sig->is_de_vs); + + xjson_AddStringToObject0 (result, "status_string", + gpgme_strerror (sig->status)); + xjson_AddStringToObject0 (result, "fingerprint", sig->fpr); + xjson_AddStringToObject0 (result, "validity_string", + validity_to_string (sig->validity)); + xjson_AddStringToObject0 (result, "pubkey_algo_name", + gpgme_pubkey_algo_name (sig->pubkey_algo)); + xjson_AddStringToObject0 (result, "hash_algo_name", + gpgme_hash_algo_name (sig->hash_algo)); + xjson_AddStringToObject0 (result, "pka_address", sig->pka_address); + + xjson_AddNumberToObject (result, "status_code", sig->status); + xjson_AddNumberToObject (result, "timestamp", sig->timestamp); + xjson_AddNumberToObject (result, "exp_timestamp", sig->exp_timestamp); + xjson_AddNumberToObject (result, "pka_trust", sig->pka_trust); + xjson_AddNumberToObject (result, "validity", sig->validity); + xjson_AddNumberToObject (result, "validity_reason", sig->validity_reason); + + if (sig->notations) + { + gpgme_sig_notation_t not; + cjson_t array = xjson_CreateArray (); + for (not = sig->notations; not; not = not->next) + cJSON_AddItemToArray (array, sig_notation_to_json (not)); + xjson_AddItemToObject (result, "notations", array); + } + + return result; +} + + +/* Create a JSON object from a gpgme_verify result */ +static cjson_t +verify_result_to_json (gpgme_verify_result_t verify_result) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "is_mime", verify_result->is_mime); + + if (verify_result->signatures) + { + cjson_t array = xjson_CreateArray (); + gpgme_signature_t sig; + + for (sig = verify_result->signatures; sig; sig = sig->next) + cJSON_AddItemToArray (array, signature_to_json (sig)); + xjson_AddItemToObject (result, "signatures", array); + } + + return result; +} + +/* Create a recipient json object */ +static cjson_t +recipient_to_json (gpgme_recipient_t recp) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "keyid", recp->keyid); + xjson_AddStringToObject0 (result, "pubkey_algo_name", + gpgme_pubkey_algo_name (recp->pubkey_algo)); + xjson_AddStringToObject0 (result, "status_string", + gpgme_strerror (recp->status)); + + xjson_AddNumberToObject (result, "status_code", recp->status); + + return result; +} + + +/* Create a JSON object from a gpgme_decrypt result */ +static cjson_t +decrypt_result_to_json (gpgme_decrypt_result_t decrypt_result) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "file_name", decrypt_result->file_name); + xjson_AddStringToObject0 (result, "symkey_algo", + decrypt_result->symkey_algo); + + xjson_AddBoolToObject (result, "wrong_key_usage", + decrypt_result->wrong_key_usage); + xjson_AddBoolToObject (result, "is_de_vs", + decrypt_result->is_de_vs); + xjson_AddBoolToObject (result, "is_mime", decrypt_result->is_mime); + xjson_AddBoolToObject (result, "legacy_cipher_nomdc", + decrypt_result->legacy_cipher_nomdc); + + if (decrypt_result->recipients) + { + cjson_t array = xjson_CreateArray (); + gpgme_recipient_t recp; + + for (recp = decrypt_result->recipients; recp; recp = recp->next) + cJSON_AddItemToArray (array, recipient_to_json (recp)); + xjson_AddItemToObject (result, "recipients", array); + } + + return result; +} + + +/* Create a JSON object from an engine_info */ +static cjson_t +engine_info_to_json (gpgme_engine_info_t info) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "protocol", + protocol_to_string (info->protocol)); + xjson_AddStringToObject0 (result, "fname", info->file_name); + xjson_AddStringToObject0 (result, "version", info->version); + xjson_AddStringToObject0 (result, "req_version", info->req_version); + xjson_AddStringToObject0 (result, "homedir", info->home_dir ? + info->home_dir : + "default"); + return result; +} + + +/* Create a JSON object from an import_status */ +static cjson_t +import_status_to_json (gpgme_import_status_t sts) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "fingerprint", sts->fpr); + xjson_AddStringToObject0 (result, "error_string", + gpgme_strerror (sts->result)); + + xjson_AddNumberToObject (result, "status", sts->status); + + return result; +} + +/* Create a JSON object from an import result */ +static cjson_t +import_result_to_json (gpgme_import_result_t imp) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddNumberToObject (result, "considered", imp->considered); + xjson_AddNumberToObject (result, "no_user_id", imp->no_user_id); + xjson_AddNumberToObject (result, "imported", imp->imported); + xjson_AddNumberToObject (result, "imported_rsa", imp->imported_rsa); + xjson_AddNumberToObject (result, "unchanged", imp->unchanged); + xjson_AddNumberToObject (result, "new_user_ids", imp->new_user_ids); + xjson_AddNumberToObject (result, "new_sub_keys", imp->new_sub_keys); + xjson_AddNumberToObject (result, "new_signatures", imp->new_signatures); + xjson_AddNumberToObject (result, "new_revocations", imp->new_revocations); + xjson_AddNumberToObject (result, "secret_read", imp->secret_read); + xjson_AddNumberToObject (result, "secret_imported", imp->secret_imported); + xjson_AddNumberToObject (result, "secret_unchanged", imp->secret_unchanged); + xjson_AddNumberToObject (result, "skipped_new_keys", imp->skipped_new_keys); + xjson_AddNumberToObject (result, "not_imported", imp->not_imported); + xjson_AddNumberToObject (result, "skipped_v3_keys", imp->skipped_v3_keys); + + + if (imp->imports) + { + cjson_t array = xjson_CreateArray (); + gpgme_import_status_t status; + + for (status = imp->imports; status; status = status->next) + cJSON_AddItemToArray (array, import_status_to_json (status)); + xjson_AddItemToObject (result, "imports", array); + } + + return result; +} + + +/* Create a JSON object from a gpgconf arg */ +static cjson_t +conf_arg_to_json (gpgme_conf_arg_t arg, gpgme_conf_type_t type) +{ + cjson_t result = xjson_CreateObject (); + int is_none = 0; + switch (type) + { + case GPGME_CONF_STRING: + case GPGME_CONF_PATHNAME: + case GPGME_CONF_LDAP_SERVER: + case GPGME_CONF_KEY_FPR: + case GPGME_CONF_PUB_KEY: + case GPGME_CONF_SEC_KEY: + case GPGME_CONF_ALIAS_LIST: + xjson_AddStringToObject0 (result, "string", arg->value.string); + break; + + case GPGME_CONF_UINT32: + xjson_AddNumberToObject (result, "number", arg->value.uint32); + break; + + case GPGME_CONF_INT32: + xjson_AddNumberToObject (result, "number", arg->value.int32); + break; + + case GPGME_CONF_NONE: + default: + is_none = 1; + break; + } + xjson_AddBoolToObject (result, "is_none", is_none); + return result; +} + + +/* Create a JSON object from a gpgconf option */ +static cjson_t +conf_opt_to_json (gpgme_conf_opt_t opt) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "name", opt->name); + xjson_AddStringToObject0 (result, "description", opt->description); + xjson_AddStringToObject0 (result, "argname", opt->argname); + xjson_AddStringToObject0 (result, "default_description", + opt->default_description); + xjson_AddStringToObject0 (result, "no_arg_description", + opt->no_arg_description); + + xjson_AddNumberToObject (result, "flags", opt->flags); + xjson_AddNumberToObject (result, "level", opt->level); + xjson_AddNumberToObject (result, "type", opt->type); + xjson_AddNumberToObject (result, "alt_type", opt->alt_type); + + if (opt->default_value) + { + cjson_t array = xjson_CreateArray (); + gpgme_conf_arg_t arg; + + for (arg = opt->default_value; arg; arg = arg->next) + cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type)); + xjson_AddItemToObject (result, "default_value", array); + } + + if (opt->no_arg_value) + { + cjson_t array = xjson_CreateArray (); + gpgme_conf_arg_t arg; + + for (arg = opt->no_arg_value; arg; arg = arg->next) + cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type)); + xjson_AddItemToObject (result, "no_arg_value", array); + } + + if (opt->value) + { + cjson_t array = xjson_CreateArray (); + gpgme_conf_arg_t arg; + + for (arg = opt->value; arg; arg = arg->next) + cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type)); + xjson_AddItemToObject (result, "value", array); + } + return result; +} + + +/* Create a JSON object from a gpgconf component*/ +static cjson_t +conf_comp_to_json (gpgme_conf_comp_t cmp) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "name", cmp->name); + xjson_AddStringToObject0 (result, "description", cmp->description); + xjson_AddStringToObject0 (result, "program_name", cmp->program_name); + + + if (cmp->options) + { + cjson_t array = xjson_CreateArray (); + gpgme_conf_opt_t opt; + + for (opt = cmp->options; opt; opt = opt->next) + cJSON_AddItemToArray (array, conf_opt_to_json (opt)); + xjson_AddItemToObject (result, "options", array); + } + + return result; +} + + +/* Create a gpgme_data from json string data named "name" + * in the request. Takes the base64 option into account. + * + * Adds an error to the "result" on error. */ +static gpg_error_t +get_string_data (cjson_t request, cjson_t result, const char *name, + gpgme_data_t *r_data) +{ + gpgme_error_t err; + int opt_base64; + cjson_t j_data; + + if ((err = get_boolean_flag (request, "base64", 0, &opt_base64))) + return err; + + /* Get the data. Note that INPUT is a shallow data object with the + * storage hold in REQUEST. */ + j_data = cJSON_GetObjectItem (request, name); + if (!j_data) + { + return gpg_error (GPG_ERR_NO_DATA); + } + if (!cjson_is_string (j_data)) + { + return gpg_error (GPG_ERR_INV_VALUE); + } + if (opt_base64) + { + err = data_from_base64_string (r_data, j_data); + if (err) + { + gpg_error_object (result, err, + "Error decoding Base-64 encoded '%s': %s", + name, gpg_strerror (err)); + return err; + } + } + else + { + err = gpgme_data_new_from_mem (r_data, j_data->valuestring, + strlen (j_data->valuestring), 0); + if (err) + { + gpg_error_object (result, err, "Error getting '%s': %s", + name, gpg_strerror (err)); + return err; + } + } + return 0; +} + + +/* Create a "data" object and the "type" and "base64" flags + * from DATA and append them to RESULT. Ownership of DATA is * transferred to this function. TYPE must be a fixed string. - * CHUNKSIZE is the chunksize requested from the caller. If BASE64 is - * -1 the need for base64 encoding is determined by the content of - * DATA, all other values are take as rtue or false. Note that - * op_getmore has similar code but works on PENDING_DATA which is set - * here. */ + * If BASE64 is -1 the need for base64 encoding is determined + * by the content of DATA, all other values are taken as true + * or false. */ static gpg_error_t -make_data_object (cjson_t result, gpgme_data_t data, size_t chunksize, +make_data_object (cjson_t result, gpgme_data_t data, const char *type, int base64) { gpg_error_t err; char *buffer; - size_t buflen; - int c; + const char *s; + size_t buflen, n; if (!base64 || base64 == -1) /* Make sure that we really have a string. */ gpgme_data_write (data, "", 1); @@ -629,58 +1469,131 @@ make_data_object (cjson_t result, gpgme_data_t data, size_t chunksize, base64 = 0; if (!buflen) log_fatal ("Appended Nul byte got lost\n"); - if (memchr (buffer, 0, buflen-1)) - { - buflen--; /* Adjust for the extra nul byte. */ - base64 = 1; - } - /* Fixme: We might want to do more advanced heuristics than to - * only look for a Nul. */ + /* Figure out if there is any Nul octet in the buffer. In that + * case we need to Base-64 the buffer. Due to problems with the + * browser's Javascript we use Base-64 also in case an UTF-8 + * character is in the buffer. This is because the chunking may + * split an UTF-8 characters and JS can't handle this. */ + for (s=buffer, n=0; n < buflen -1; s++, n++) + if (!*s || (*s & 0x80)) + { + buflen--; /* Adjust for the extra nul byte. */ + base64 = 1; + break; + } } - /* Adjust the chunksize if we need to do base64 conversion. */ - if (base64) - chunksize = (chunksize / 4) * 3; - xjson_AddStringToObject (result, "type", type); xjson_AddBoolToObject (result, "base64", base64); - if (buflen > chunksize) + if (base64) + err = add_base64_to_object (result, "data", buffer, buflen); + else + err = cjson_AddStringToObject (result, "data", buffer); + + leave: + gpgme_free (buffer); + return err; +} + + +/* Encode and chunk response. + * + * If neccessary this base64 encodes and chunks the repsonse + * for getmore so that we always return valid json independent + * of the chunksize. + * + * A chunked repsonse contains the base64 encoded chunk + * as a string and a boolean if there is still more data + * available for getmore like: + * { + * chunk: "SGVsbG8gV29ybGQK" + * more: true + * } + * + * Chunking is only done if the response is larger then the + * chunksize. + * + * caller has to xfree the return value. + */ +static char * +encode_and_chunk (cjson_t request, cjson_t response) +{ + char *data; + gpg_error_t err = 0; + size_t chunksize = 0; + char *getmore_request = NULL; + + if (opt_interactive) + data = cJSON_Print (response); + else + data = cJSON_PrintUnformatted (response); + + if (!data) { - xjson_AddBoolToObject (result, "more", 1); + err = GPG_ERR_NO_DATA; + goto leave; + } - c = buffer[chunksize]; - buffer[chunksize] = 0; - if (base64) - err = add_base64_to_object (result, "data", buffer, chunksize); - else - err = cjson_AddStringToObject (result, "data", buffer); - buffer[chunksize] = c; - if (err) - goto leave; + if (!request) + { + goto leave; + } - pending_data.buffer = buffer; - buffer = NULL; - pending_data.length = buflen; - pending_data.written = chunksize; - pending_data.type = type; - pending_data.base64 = base64; + if ((err = get_chunksize (request, &chunksize))) + { + err = GPG_ERR_INV_VALUE; + goto leave; } - else + + if (!chunksize) + goto leave; + + pending_data.buffer = data; + /* Data should already be encoded so that it does not + contain 0.*/ + pending_data.length = strlen (data); + pending_data.written = 0; + + if (gpgrt_asprintf (&getmore_request, + "{ \"op\":\"getmore\", \"chunksize\": %i }", + (int) chunksize) == -1) { - if (base64) - err = add_base64_to_object (result, "data", buffer, buflen); - else - err = cjson_AddStringToObject (result, "data", buffer); + err = gpg_error_from_syserror (); + goto leave; } - leave: - gpgme_free (buffer); - return err; + data = process_request (getmore_request); + +leave: + xfree (getmore_request); + + if (!err && !data) + { + err = GPG_ERR_GENERAL; + } + + if (err) + { + cjson_t err_obj = gpg_error_object (NULL, err, + "Encode and chunk failed: %s", + gpgme_strerror (err)); + xfree (data); + if (opt_interactive) + data = cJSON_Print (err_obj); + data = cJSON_PrintUnformatted (err_obj); + + cJSON_Delete (err_obj); + } + + return data; } +/* + * Implementation of the commands. + */ static const char hlp_encrypt[] = "op: \"encrypt\"\n" "keys: Array of strings with the fingerprints or user-ids\n" @@ -690,7 +1603,10 @@ static const char hlp_encrypt[] = "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" - "chunksize: Max number of bytes in the resulting \"data\".\n" + "signing_keys: Similar to the keys parameter for added signing.\n" + " (openpgp only)" + "file_name: The file name associated with the data.\n" + "sender: Sender info to embed in a signature.\n" "\n" "Optional boolean flags (default is false):\n" "base64: Input data is base64 encoded.\n" @@ -708,32 +1624,29 @@ static const char hlp_encrypt[] = "data: Unless armor mode is used a Base64 encoded binary\n" " ciphertext. In armor mode a string with an armored\n" " OpenPGP or a PEM message.\n" - "base64: Boolean indicating whether data is base64 encoded.\n" - "more: Optional boolean indicating that \"getmore\" is required."; + "base64: Boolean indicating whether data is base64 encoded."; static gpg_error_t op_encrypt (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; - size_t chunksize; - int opt_base64; + char **signing_patterns = NULL; int opt_mime; char *keystring = NULL; - cjson_t j_input; + char *file_name = NULL; gpgme_data_t input = NULL; gpgme_data_t output = NULL; int abool; gpgme_encrypt_flags_t encrypt_flags = 0; + gpgme_ctx_t keylist_ctx = NULL; + gpgme_key_t key = NULL; + cjson_t j_tmp = NULL; if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); - if ((err = get_chunksize (request, &chunksize))) - goto leave; - if ((err = get_boolean_flag (request, "base64", 0, &opt_base64))) - goto leave; if ((err = get_boolean_flag (request, "mime", 0, &opt_mime))) goto leave; @@ -765,81 +1678,114 @@ op_encrypt (cjson_t request, cjson_t result) if (abool) encrypt_flags |= GPGME_ENCRYPT_WANT_ADDRESS; - - /* Get the keys. */ - err = get_keys (request, &keystring); - if (err) + j_tmp = cJSON_GetObjectItem (request, "file_name"); + if (j_tmp && cjson_is_string (j_tmp)) { - /* Provide a custom error response. */ - error_object (result, "Error getting keys: %s", gpg_strerror (err)); - goto leave; + file_name = j_tmp->valuestring; } - /* Get the data. Note that INPUT is a shallow data object with the - * storage hold in REQUEST. */ - j_input = cJSON_GetObjectItem (request, "data"); - if (!j_input) + j_tmp = cJSON_GetObjectItem (request, "sender"); + if (j_tmp && cjson_is_string (j_tmp)) { - err = gpg_error (GPG_ERR_NO_DATA); - goto leave; + gpgme_set_sender (ctx, j_tmp->valuestring); } - if (!cjson_is_string (j_input)) + + /* Get the keys. */ + err = get_keys (request, "keys", &keystring); + if (err) { - err = gpg_error (GPG_ERR_INV_VALUE); + /* Provide a custom error response. */ + gpg_error_object (result, err, "Error getting keys: %s", + gpg_strerror (err)); goto leave; } - if (opt_base64) + + /* Do we have signing keys ? */ + signing_patterns = create_keylist_patterns (request, "signing_keys"); + if (signing_patterns) { - err = data_from_base64_string (&input, j_input); + keylist_ctx = create_onetime_context (protocol); + gpgme_set_keylist_mode (keylist_ctx, GPGME_KEYLIST_MODE_LOCAL); + + err = gpgme_op_keylist_ext_start (keylist_ctx, + (const char **) signing_patterns, + 1, 0); if (err) { - error_object (result, "Error decoding Base-64 encoded 'data': %s", - gpg_strerror (err)); + gpg_error_object (result, err, "Error listing keys: %s", + gpg_strerror (err)); goto leave; } - } - else - { - err = gpgme_data_new_from_mem (&input, j_input->valuestring, - strlen (j_input->valuestring), 0); - if (err) + while (!(err = gpgme_op_keylist_next (keylist_ctx, &key))) { - error_object (result, "Error getting 'data': %s", gpg_strerror (err)); - goto leave; + if ((err = gpgme_signers_add (ctx, key))) + { + gpg_error_object (result, err, "Error adding signer: %s", + gpg_strerror (err)); + goto leave; + } + gpgme_key_unref (key); + key = NULL; } + release_onetime_context (keylist_ctx); + keylist_ctx = NULL; } + + if ((err = get_string_data (request, result, "data", &input))) + goto leave; + if (opt_mime) gpgme_data_set_encoding (input, GPGME_DATA_ENCODING_MIME); + if (file_name) + { + gpgme_data_set_file_name (input, file_name); + } /* Create an output data object. */ err = gpgme_data_new (&output); if (err) { - error_object (result, "Error creating output data object: %s", - gpg_strerror (err)); + gpg_error_object (result, err, "Error creating output data object: %s", + gpg_strerror (err)); goto leave; } /* Encrypt. */ - err = gpgme_op_encrypt_ext (ctx, NULL, keystring, encrypt_flags, - input, output); + if (!signing_patterns) + { + err = gpgme_op_encrypt_ext (ctx, NULL, keystring, encrypt_flags, + input, output); + } + else + { + err = gpgme_op_encrypt_sign_ext (ctx, NULL, keystring, encrypt_flags, + input, output); + + } /* encrypt_result = gpgme_op_encrypt_result (ctx); */ if (err) { - error_object (result, "Encryption failed: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Encryption failed: %s", + gpg_strerror (err)); goto leave; } gpgme_data_release (input); input = NULL; /* We need to base64 if armoring has not been requested. */ - err = make_data_object (result, output, chunksize, + err = make_data_object (result, output, "ciphertext", !gpgme_get_armor (ctx)); output = NULL; leave: + xfree_array (signing_patterns); xfree (keystring); + release_onetime_context (keylist_ctx); + /* Reset sender in case the context is reused */ + gpgme_set_sender (ctx, NULL); + gpgme_key_unref (key); + gpgme_signers_clear (ctx); release_context (ctx); gpgme_data_release (input); gpgme_data_release (output); @@ -854,121 +1800,1275 @@ static const char hlp_decrypt[] = "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" - "chunksize: Max number of bytes in the resulting \"data\".\n" "\n" "Optional boolean flags (default is false):\n" "base64: Input data is base64 encoded.\n" "\n" "Response on success:\n" - "type: \"plaintext\"\n" - "data: The decrypted data. This may be base64 encoded.\n" - "base64: Boolean indicating whether data is base64 encoded.\n" - "mime: A Boolean indicating whether the data is a MIME object.\n" - "info: An optional object with extra information.\n" - "more: Optional boolean indicating that \"getmore\" is required."; + "type: \"plaintext\"\n" + "data: The decrypted data. This may be base64 encoded.\n" + "base64: Boolean indicating whether data is base64 encoded.\n" + "mime: deprecated - use dec_info is_mime instead\n" + "dec_info: An object with decryption information. (gpgme_decrypt_result_t)\n" + " Boolean values:\n" + " wrong_key_usage: Key should not have been used for encryption.\n" + " is_de_vs: Message was encrypted in compliance to the de-vs\n" + " mode.\n" + " is_mime: Message claims that the content is a MIME Message.\n" + " legacy_cipher_nomdc: The message was made by a legacy algorithm\n" + " without integrity protection.\n" + " String values:\n" + " file_name: The filename contained in the decrypt result.\n" + " symkey_algo: A string with the symmetric encryption algorithm and\n" + " mode using the format \"<algo>.<mode>\".\n" + " Array values:\n" + " recipients: The list of recipients (gpgme_recipient_t).\n" + " String values:\n" + " keyid: The keyid of the recipient.\n" + " pubkey_algo_name: gpgme_pubkey_algo_name of used algo.\n" + " status_string: The status code as localized gpg-error string\n" + " Number values:\n" + " status_code: The status as a number. (gpg_error_t)\n" + "info: Optional an object with verification information.\n" + " (gpgme_verify_result_t)\n" + " file_name: The filename contained in the verify result.\n" + " is_mime: The is_mime info contained in the verify result.\n" + " signatures: Array of signatures\n" + " summary: Object containing summary information.\n" + " Boolean values: (Check gpgme_sigsum_t doc for meaning)\n" + " valid\n" + " green\n" + " red\n" + " revoked\n" + " key-expired\n" + " sig-expired\n" + " key-missing\n" + " crl-missing\n" + " crl-too-old\n" + " bad-policy\n" + " sys-error\n" + " sigsum: Array of strings representing the sigsum.\n" + " Boolean values:\n" + " wrong_key_usage: Key should not have been used for signing.\n" + " chain_model: Validity has been verified using the chain model.\n" + " is_de_vs: signature is in compliance to the de-vs mode.\n" + " String values:\n" + " status_string: The status code as localized gpg-error string\n" + " fingerprint: The fingerprint of the signing key.\n" + " validity_string: The validity as string.\n" + " pubkey_algo_name: gpgme_pubkey_algo_name of used algo.\n" + " hash_algo_name: gpgme_hash_algo_name of used hash algo\n" + " pka_address: The mailbox from the PKA information.\n" + " Number values:\n" + " status_code: The status as a number. (gpg_error_t)\n" + " timestamp: Signature creation time. (secs since epoch)\n" + " exp_timestamp: Signature expiration or 0. (secs since epoch)\n" + " pka_trust: PKA status: 0 = not available, 1 = bad, 2 = okay, 3 = RFU.\n" + " validity: validity as number (gpgme_validity_t)\n" + " validity_reason: (gpg_error_t)\n" + " Array values:\n" + " notations: Notation data and policy urls (gpgme_sig_notation_t)\n" + " Boolean values:\n" + " human_readable\n" + " critical\n" + " String values:\n" + " name\n" + " value\n" + " Number values:\n" + " flags\n"; static gpg_error_t op_decrypt (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; - size_t chunksize; - int opt_base64; - cjson_t j_input; gpgme_data_t input = NULL; gpgme_data_t output = NULL; gpgme_decrypt_result_t decrypt_result; + gpgme_verify_result_t verify_result; if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); - if ((err = get_chunksize (request, &chunksize))) + + if ((err = get_string_data (request, result, "data", &input))) + goto leave; + + /* Create an output data object. */ + err = gpgme_data_new (&output); + if (err) + { + gpg_error_object (result, err, + "Error creating output data object: %s", + gpg_strerror (err)); + goto leave; + } + + /* Decrypt. */ + err = gpgme_op_decrypt_ext (ctx, GPGME_DECRYPT_VERIFY, + input, output); + decrypt_result = gpgme_op_decrypt_result (ctx); + if (err) + { + gpg_error_object (result, err, "Decryption failed: %s", + gpg_strerror (err)); + goto leave; + } + gpgme_data_release (input); + input = NULL; + + if (decrypt_result->is_mime) + xjson_AddBoolToObject (result, "mime", 1); + + xjson_AddItemToObject (result, "dec_info", + decrypt_result_to_json (decrypt_result)); + + verify_result = gpgme_op_verify_result (ctx); + if (verify_result && verify_result->signatures) + { + xjson_AddItemToObject (result, "info", + verify_result_to_json (verify_result)); + } + + err = make_data_object (result, output, "plaintext", -1); + output = NULL; + + if (err) + { + gpg_error_object (result, err, "Plaintext output failed: %s", + gpg_strerror (err)); + goto leave; + } + + leave: + release_context (ctx); + gpgme_data_release (input); + gpgme_data_release (output); + return err; +} + + + +static const char hlp_sign[] = + "op: \"sign\"\n" + "keys: Array of strings with the fingerprints of the signing key.\n" + " For a single key a String may be used instead of an array.\n" + "data: Input data. \n" + "\n" + "Optional parameters:\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "sender: The mail address of the sender.\n" + "mode: A string with the signing mode can be:\n" + " detached (default)\n" + " opaque\n" + " clearsign\n" + "\n" + "Optional boolean flags (default is false):\n" + "base64: Input data is base64 encoded.\n" + "armor: Request output in armored format.\n" + "\n" + "Response on success:\n" + "type: \"signature\"\n" + "data: Unless armor mode is used a Base64 encoded binary\n" + " signature. In armor mode a string with an armored\n" + " OpenPGP or a PEM message.\n" + "base64: Boolean indicating whether data is base64 encoded.\n"; +static gpg_error_t +op_sign (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_protocol_t protocol; + char **patterns = NULL; + gpgme_data_t input = NULL; + gpgme_data_t output = NULL; + int abool; + cjson_t j_tmp; + gpgme_sig_mode_t mode = GPGME_SIG_MODE_DETACH; + gpgme_ctx_t keylist_ctx = NULL; + gpgme_key_t key = NULL; + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + + if ((err = get_boolean_flag (request, "armor", 0, &abool))) goto leave; + gpgme_set_armor (ctx, abool); - if ((err = get_boolean_flag (request, "base64", 0, &opt_base64))) + j_tmp = cJSON_GetObjectItem (request, "mode"); + if (j_tmp && cjson_is_string (j_tmp)) + { + if (!strcmp (j_tmp->valuestring, "opaque")) + { + mode = GPGME_SIG_MODE_NORMAL; + } + else if (!strcmp (j_tmp->valuestring, "clearsign")) + { + mode = GPGME_SIG_MODE_CLEAR; + } + } + + j_tmp = cJSON_GetObjectItem (request, "sender"); + if (j_tmp && cjson_is_string (j_tmp)) + { + gpgme_set_sender (ctx, j_tmp->valuestring); + } + + patterns = create_keylist_patterns (request, "keys"); + if (!patterns) + { + gpg_error_object (result, err, "Error getting keys: %s", + gpg_strerror (gpg_error (GPG_ERR_NO_KEY))); + goto leave; + } + + /* Do a keylisting and add the keys */ + keylist_ctx = create_onetime_context (protocol); + gpgme_set_keylist_mode (keylist_ctx, GPGME_KEYLIST_MODE_LOCAL); + + err = gpgme_op_keylist_ext_start (keylist_ctx, + (const char **) patterns, 1, 0); + if (err) + { + gpg_error_object (result, err, "Error listing keys: %s", + gpg_strerror (err)); + goto leave; + } + while (!(err = gpgme_op_keylist_next (keylist_ctx, &key))) + { + if ((err = gpgme_signers_add (ctx, key))) + { + gpg_error_object (result, err, "Error adding signer: %s", + gpg_strerror (err)); + goto leave; + } + gpgme_key_unref (key); + key = NULL; + } + + if ((err = get_string_data (request, result, "data", &input))) goto leave; - /* Get the data. Note that INPUT is a shallow data object with the - * storage hold in REQUEST. */ - j_input = cJSON_GetObjectItem (request, "data"); - if (!j_input) + /* Create an output data object. */ + err = gpgme_data_new (&output); + if (err) { - err = gpg_error (GPG_ERR_NO_DATA); + gpg_error_object (result, err, "Error creating output data object: %s", + gpg_strerror (err)); goto leave; } - if (!cjson_is_string (j_input)) + + /* Sign. */ + err = gpgme_op_sign (ctx, input, output, mode); + if (err) { - err = gpg_error (GPG_ERR_INV_VALUE); + gpg_error_object (result, err, "Signing failed: %s", + gpg_strerror (err)); goto leave; } - if (opt_base64) + + gpgme_data_release (input); + input = NULL; + + /* We need to base64 if armoring has not been requested. */ + err = make_data_object (result, output, + "signature", !gpgme_get_armor (ctx)); + output = NULL; + + leave: + xfree_array (patterns); + gpgme_signers_clear (ctx); + gpgme_key_unref (key); + release_onetime_context (keylist_ctx); + release_context (ctx); + gpgme_data_release (input); + gpgme_data_release (output); + return err; +} + + + +static const char hlp_verify[] = + "op: \"verify\"\n" + "data: The data to verify.\n" + "\n" + "Optional parameters:\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "signature: A detached signature. If missing opaque is assumed.\n" + "\n" + "Optional boolean flags (default is false):\n" + "base64: Input data is base64 encoded.\n" + "\n" + "Response on success:\n" + "type: \"plaintext\"\n" + "data: The verified data. This may be base64 encoded.\n" + "base64: Boolean indicating whether data is base64 encoded.\n" + "info: An object with verification information (gpgme_verify_result_t).\n" + " is_mime: Boolean that is true if the messages claims it is MIME.\n" + " Note that this flag is not covered by the signature.)\n" + " signatures: Array of signatures\n" + " summary: Object containing summary information.\n" + " Boolean values: (Check gpgme_sigsum_t doc for meaning)\n" + " valid\n" + " green\n" + " red\n" + " revoked\n" + " key-expired\n" + " sig-expired\n" + " key-missing\n" + " crl-missing\n" + " crl-too-old\n" + " bad-policy\n" + " sys-error\n" + " sigsum: Array of strings representing the sigsum.\n" + " Boolean values:\n" + " wrong_key_usage: Key should not have been used for signing.\n" + " chain_model: Validity has been verified using the chain model.\n" + " is_de_vs: signature is in compliance to the de-vs mode.\n" + " String values:\n" + " status_string: The status code as localized gpg-error string\n" + " fingerprint: The fingerprint of the signing key.\n" + " validity_string: The validity as string.\n" + " pubkey_algo_name: gpgme_pubkey_algo_name of used algo.\n" + " hash_algo_name: gpgme_hash_algo_name of used hash algo\n" + " pka_address: The mailbox from the PKA information.\n" + " Number values:\n" + " status_code: The status as a number. (gpg_error_t)\n" + " timestamp: Signature creation time. (secs since epoch)\n" + " exp_timestamp: Signature expiration or 0. (secs since epoch)\n" + " pka_trust: PKA status: 0 = not available, 1 = bad, 2 = okay, 3 = RFU.\n" + " validity: validity as number (gpgme_validity_t)\n" + " validity_reason: (gpg_error_t)\n" + " Array values:\n" + " notations: Notation data and policy urls (gpgme_sig_notation_t)\n" + " Boolean values:\n" + " human_readable\n" + " critical\n" + " String values:\n" + " name\n" + " value\n" + " Number values:\n" + " flags\n"; +static gpg_error_t +op_verify (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_protocol_t protocol; + gpgme_data_t input = NULL; + gpgme_data_t signature = NULL; + gpgme_data_t output = NULL; + gpgme_verify_result_t verify_result; + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + + if ((err = get_string_data (request, result, "data", &input))) + goto leave; + + err = get_string_data (request, result, "signature", &signature); + /* Signature data is optional otherwise we expect opaque or clearsigned. */ + if (err && err != gpg_error (GPG_ERR_NO_DATA)) + goto leave; + + if (!signature) { - err = data_from_base64_string (&input, j_input); + /* Verify opaque or clearsigned we need an output data object. */ + err = gpgme_data_new (&output); if (err) { - error_object (result, "Error decoding Base-64 encoded 'data': %s", - gpg_strerror (err)); + gpg_error_object (result, err, + "Error creating output data object: %s", + gpg_strerror (err)); goto leave; } + err = gpgme_op_verify (ctx, input, 0, output); } else { - err = gpgme_data_new_from_mem (&input, j_input->valuestring, - strlen (j_input->valuestring), 0); + err = gpgme_op_verify (ctx, signature, input, NULL); + } + + if (err) + { + gpg_error_object (result, err, "Verify failed: %s", gpg_strerror (err)); + goto leave; + } + gpgme_data_release (input); + input = NULL; + gpgme_data_release (signature); + signature = NULL; + + verify_result = gpgme_op_verify_result (ctx); + if (verify_result && verify_result->signatures) + { + xjson_AddItemToObject (result, "info", + verify_result_to_json (verify_result)); + } + + if (output) + { + err = make_data_object (result, output, "plaintext", -1); + output = NULL; + if (err) { - error_object (result, "Error getting 'data': %s", gpg_strerror (err)); + gpg_error_object (result, err, "Plaintext output failed: %s", + gpg_strerror (err)); goto leave; } } - /* Create an output data object. */ - err = gpgme_data_new (&output); + leave: + release_context (ctx); + gpgme_data_release (input); + gpgme_data_release (output); + gpgme_data_release (signature); + return err; +} + + + +static const char hlp_version[] = + "op: \"version\"\n" + "\n" + "Response on success:\n" + "gpgme: The GPGME Version.\n" + "info: dump of engine info. containing:\n" + " protocol: The protocol.\n" + " fname: The file name.\n" + " version: The version.\n" + " req_ver: The required version.\n" + " homedir: The homedir of the engine or \"default\".\n"; +static gpg_error_t +op_version (cjson_t request, cjson_t result) +{ + gpg_error_t err = 0; + gpgme_engine_info_t ei = NULL; + cjson_t infos = xjson_CreateArray (); + + (void)request; + + if (!cJSON_AddStringToObject (result, "gpgme", gpgme_check_version (NULL))) + { + cJSON_Delete (infos); + return gpg_error_from_syserror (); + } + + if ((err = gpgme_get_engine_info (&ei))) + { + cJSON_Delete (infos); + return err; + } + + for (; ei; ei = ei->next) + cJSON_AddItemToArray (infos, engine_info_to_json (ei)); + + if (!cJSON_AddItemToObject (result, "info", infos)) + { + err = gpg_error_from_syserror (); + cJSON_Delete (infos); + return err; + } + + return 0; +} + + + +static const char hlp_keylist[] = + "op: \"keylist\"\n" + "\n" + "Optional parameters:\n" + "keys: Array of strings or fingerprints to lookup\n" + " For a single key a String may be used instead of an array.\n" + " default lists all keys.\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "\n" + "Optional boolean flags (default is false):\n" + "secret: List only secret keys.\n" + "with-secret: Add KEYLIST_MODE_WITH_SECRET.\n" + "extern: Add KEYLIST_MODE_EXTERN.\n" + "local: Add KEYLIST_MODE_LOCAL. (default mode).\n" + "sigs: Add KEYLIST_MODE_SIGS.\n" + "notations: Add KEYLIST_MODE_SIG_NOTATIONS.\n" + "tofu: Add KEYLIST_MODE_WITH_TOFU.\n" + "ephemeral: Add KEYLIST_MODE_EPHEMERAL.\n" + "validate: Add KEYLIST_MODE_VALIDATE.\n" + "locate: Add KEYLIST_MODE_LOCATE.\n" + "\n" + "Response on success:\n" + "keys: Array of keys.\n" + " Boolean values:\n" + " revoked\n" + " expired\n" + " disabled\n" + " invalid\n" + " can_encrypt\n" + " can_sign\n" + " can_certify\n" + " can_authenticate\n" + " secret\n" + " is_qualified\n" + " String values:\n" + " protocol\n" + " issuer_serial (CMS Only)\n" + " issuer_name (CMS Only)\n" + " chain_id (CMS Only)\n" + " owner_trust (OpenPGP only)\n" + " fingerprint\n" + " Number values:\n" + " last_update\n" + " origin\n" + " Array values:\n" + " subkeys\n" + " Boolean values:\n" + " revoked\n" + " expired\n" + " disabled\n" + " invalid\n" + " can_encrypt\n" + " can_sign\n" + " can_certify\n" + " can_authenticate\n" + " secret\n" + " is_qualified\n" + " is_cardkey\n" + " is_de_vs\n" + " String values:\n" + " pubkey_algo_name\n" + " pubkey_algo_string\n" + " keyid\n" + " card_number\n" + " curve\n" + " keygrip\n" + " Number values:\n" + " pubkey_algo\n" + " length\n" + " timestamp\n" + " expires\n" + " userids\n" + " Boolean values:\n" + " revoked\n" + " invalid\n" + " String values:\n" + " validity\n" + " uid\n" + " name\n" + " email\n" + " comment\n" + " address\n" + " Number values:\n" + " origin\n" + " last_update\n" + " Array values:\n" + " signatures\n" + " Boolean values:\n" + " revoked\n" + " expired\n" + " invalid\n" + " exportable\n" + " String values:\n" + " pubkey_algo_name\n" + " keyid\n" + " status\n" + " uid\n" + " name\n" + " email\n" + " comment\n" + " Number values:\n" + " pubkey_algo\n" + " timestamp\n" + " expires\n" + " status_code\n" + " sig_class\n" + " Array values:\n" + " notations\n" + " Boolean values:\n" + " human_readable\n" + " critical\n" + " String values:\n" + " name\n" + " value\n" + " Number values:\n" + " flags\n" + " tofu\n" + " String values:\n" + " description\n" + " Number values:\n" + " validity\n" + " policy\n" + " signcount\n" + " encrcount\n" + " signfirst\n" + " signlast\n" + " encrfirst\n" + " encrlast\n"; +static gpg_error_t +op_keylist (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_protocol_t protocol; + char **patterns = NULL; + int abool; + int secret_only = 0; + gpgme_keylist_mode_t mode = 0; + gpgme_key_t key = NULL; + cjson_t keyarray = xjson_CreateArray (); + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + + /* Handle the various keylist mode bools. */ + if ((err = get_boolean_flag (request, "secret", 0, &abool))) + goto leave; + if (abool) + { + mode |= GPGME_KEYLIST_MODE_WITH_SECRET; + secret_only = 1; + } + if ((err = get_boolean_flag (request, "with-secret", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_WITH_SECRET; + if ((err = get_boolean_flag (request, "extern", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_EXTERN; + + if ((err = get_boolean_flag (request, "local", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_LOCAL; + + if ((err = get_boolean_flag (request, "sigs", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_SIGS; + + if ((err = get_boolean_flag (request, "notations", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_SIG_NOTATIONS; + + if ((err = get_boolean_flag (request, "tofu", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_WITH_TOFU; + + if ((err = get_boolean_flag (request, "ephemeral", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_EPHEMERAL; + + if ((err = get_boolean_flag (request, "validate", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_VALIDATE; + + if ((err = get_boolean_flag (request, "locate", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_LOCATE; + + if (!mode) + { + /* default to local */ + mode = GPGME_KEYLIST_MODE_LOCAL; + } + + /* Get the keys. */ + patterns = create_keylist_patterns (request, "keys"); + + /* Do a keylisting and add the keys */ + gpgme_set_keylist_mode (ctx, mode); + + err = gpgme_op_keylist_ext_start (ctx, (const char **) patterns, + secret_only, 0); if (err) { - error_object (result, "Error creating output data object: %s", - gpg_strerror (err)); + gpg_error_object (result, err, "Error listing keys: %s", + gpg_strerror (err)); goto leave; } - /* Decrypt. */ - err = gpgme_op_decrypt_ext (ctx, GPGME_DECRYPT_VERIFY, - input, output); - decrypt_result = gpgme_op_decrypt_result (ctx); + while (!(err = gpgme_op_keylist_next (ctx, &key))) + { + cJSON_AddItemToArray (keyarray, key_to_json (key)); + gpgme_key_unref (key); + } + err = 0; + + if (!cJSON_AddItemToObject (result, "keys", keyarray)) + { + err = gpg_error_from_syserror (); + goto leave; + } + + leave: + xfree_array (patterns); + if (err) + { + cJSON_Delete (keyarray); + } + return err; +} + + + +static const char hlp_import[] = + "op: \"import\"\n" + "data: The data to import.\n" + "\n" + "Optional parameters:\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "\n" + "Optional boolean flags (default is false):\n" + "base64: Input data is base64 encoded.\n" + "\n" + "Response on success:\n" + "result: The import result.\n" + " Number values:\n" + " considered\n" + " no_user_id\n" + " imported\n" + " imported_rsa\n" + " unchanged\n" + " new_user_ids\n" + " new_sub_keys\n" + " new_signatures\n" + " new_revocations\n" + " secret_read\n" + " secret_imported\n" + " secret_unchanged\n" + " skipped_new_keys\n" + " not_imported\n" + " skipped_v3_keys\n" + " Array values:\n" + " imports: List of keys for which an import was attempted\n" + " String values:\n" + " fingerprint\n" + " error_string\n" + " Number values:\n" + " error_code\n" + " status\n"; +static gpg_error_t +op_import (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_data_t input = NULL; + gpgme_import_result_t import_result; + gpgme_protocol_t protocol; + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + + if ((err = get_string_data (request, result, "data", &input))) + goto leave; + + /* Import. */ + err = gpgme_op_import (ctx, input); + import_result = gpgme_op_import_result (ctx); if (err) { - error_object (result, "Decryption failed: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Import failed: %s", + gpg_strerror (err)); goto leave; } gpgme_data_release (input); input = NULL; - if (decrypt_result->is_mime) - xjson_AddBoolToObject (result, "mime", 1); - - err = make_data_object (result, output, chunksize, "plaintext", -1); - output = NULL; + xjson_AddItemToObject (result, "result", + import_result_to_json (import_result)); leave: release_context (ctx); gpgme_data_release (input); + return err; +} + + +static const char hlp_export[] = + "op: \"export\"\n" + "\n" + "Optional parameters:\n" + "keys: Array of strings or fingerprints to lookup\n" + " For a single key a String may be used instead of an array.\n" + " default exports all keys.\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "\n" + "Optional boolean flags (default is false):\n" + "armor: Request output in armored format.\n" + "extern: Add EXPORT_MODE_EXTERN.\n" + "minimal: Add EXPORT_MODE_MINIMAL.\n" + "raw: Add EXPORT_MODE_RAW.\n" + "pkcs12: Add EXPORT_MODE_PKCS12.\n" + "with-sec-fprs: Add the sec-fprs array to the result.\n" + "\n" + "Response on success:\n" + "type: \"keys\"\n" + "data: Unless armor mode is used a Base64 encoded binary.\n" + " In armor mode a string with an armored\n" + " OpenPGP or a PEM / PKCS12 key.\n" + "base64: Boolean indicating whether data is base64 encoded.\n" + "sec-fprs: Optional, only if with-secret is set. An array containing\n" + " the fingerprints of the keys in the export for which a secret\n" + " key is available"; +static gpg_error_t +op_export (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_protocol_t protocol; + char **patterns = NULL; + int abool; + int with_secret = 0; + gpgme_export_mode_t mode = 0; + gpgme_data_t output = NULL; + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + + if ((err = get_boolean_flag (request, "armor", 0, &abool))) + goto leave; + gpgme_set_armor (ctx, abool); + + /* Handle the various export mode bools. */ + if ((err = get_boolean_flag (request, "secret", 0, &abool))) + goto leave; + if (abool) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + goto leave; + } + + if ((err = get_boolean_flag (request, "extern", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_EXPORT_MODE_EXTERN; + + if ((err = get_boolean_flag (request, "minimal", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_EXPORT_MODE_MINIMAL; + + if ((err = get_boolean_flag (request, "raw", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_EXPORT_MODE_RAW; + + if ((err = get_boolean_flag (request, "pkcs12", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_EXPORT_MODE_PKCS12; + + if ((err = get_boolean_flag (request, "with-sec-fprs", 0, &abool))) + goto leave; + if (abool) + with_secret = 1; + + /* Get the export patterns. */ + patterns = create_keylist_patterns (request, "keys"); + + /* Create an output data object. */ + err = gpgme_data_new (&output); + if (err) + { + gpg_error_object (result, err, "Error creating output data object: %s", + gpg_strerror (err)); + goto leave; + } + + err = gpgme_op_export_ext (ctx, (const char **) patterns, + mode, output); + if (err) + { + gpg_error_object (result, err, "Error exporting keys: %s", + gpg_strerror (err)); + goto leave; + } + + /* We need to base64 if armoring has not been requested. */ + err = make_data_object (result, output, + "keys", !gpgme_get_armor (ctx)); + output = NULL; + + if (!err && with_secret) + { + err = add_secret_fprs ((const char **) patterns, protocol, result); + } + +leave: + xfree_array (patterns); + release_context (ctx); gpgme_data_release (output); + + return err; +} + + +static const char hlp_delete[] = + "op: \"delete\"\n" + "key: Fingerprint of the key to delete.\n" + "\n" + "Optional parameters:\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "\n" + "Response on success:\n" + "success: Boolean true.\n"; +static gpg_error_t +op_delete (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_ctx_t keylist_ctx = NULL; + gpgme_protocol_t protocol; + gpgme_key_t key = NULL; + int secret = 0; + cjson_t j_key = NULL; + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + keylist_ctx = get_context (protocol); + + if ((err = get_boolean_flag (request, "secret", 0, &secret))) + goto leave; + if (secret) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + goto leave; + } + + j_key = cJSON_GetObjectItem (request, "key"); + if (!j_key) + { + err = gpg_error (GPG_ERR_NO_KEY); + goto leave; + } + if (!cjson_is_string (j_key)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + /* Get the key */ + if ((err = gpgme_get_key (keylist_ctx, j_key->valuestring, &key, 0))) + { + gpg_error_object (result, err, "Error fetching key for delete: %s", + gpg_strerror (err)); + goto leave; + } + + err = gpgme_op_delete (ctx, key, 0); + if (err) + { + gpg_error_object (result, err, "Error deleting key: %s", + gpg_strerror (err)); + goto leave; + } + + xjson_AddBoolToObject (result, "success", 1); + +leave: + gpgme_key_unref (key); + release_context (ctx); + release_context (keylist_ctx); + + return err; +} + + +static const char hlp_config_opt[] = + "op: \"config_opt\"\n" + "component: The component of the option.\n" + "option: The name of the option.\n" + "\n" + "Response on success:\n" + "\n" + "option: Information about the option.\n" + " String values:\n" + " name: The name of the option\n" + " description: Localized description of the opt.\n" + " argname: Thhe argument name e.g. --verbose\n" + " default_description\n" + " no_arg_description\n" + " Number values:\n" + " flags: Flags for this option.\n" + " level: the level of the description. See gpgme_conf_level_t.\n" + " type: The type of the option. See gpgme_conf_type_t.\n" + " alt_type: Alternate type of the option. See gpgme_conf_type_t\n" + " Arg type values: (see desc. below)\n" + " default_value: Array of the default value.\n" + " no_arg_value: Array of the value if it is not set.\n" + " value: Array for the current value if the option is set.\n" + "\n" + "If the response is empty the option was not found\n" + ""; +static gpg_error_t +op_config_opt (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_conf_comp_t conf = NULL; + gpgme_conf_comp_t comp = NULL; + cjson_t j_tmp; + char *comp_name = NULL; + char *opt_name = NULL; + + ctx = get_context (GPGME_PROTOCOL_GPGCONF); + + j_tmp = cJSON_GetObjectItem (request, "component"); + if (!j_tmp || !cjson_is_string (j_tmp)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + comp_name = j_tmp->valuestring; + + + j_tmp = cJSON_GetObjectItem (request, "option"); + if (!j_tmp || !cjson_is_string (j_tmp)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + opt_name = j_tmp->valuestring; + + /* Load the config */ + err = gpgme_op_conf_load (ctx, &conf); + if (err) + { + goto leave; + } + + comp = conf; + for (comp = conf; comp; comp = comp->next) + { + gpgme_conf_opt_t opt = NULL; + int found = 0; + if (!comp->name || strcmp (comp->name, comp_name)) + { + /* Skip components if a single one is specified */ + continue; + } + for (opt = comp->options; opt; opt = opt->next) + { + if (!opt->name || strcmp (opt->name, opt_name)) + { + /* Skip components if a single one is specified */ + continue; + } + xjson_AddItemToObject (result, "option", conf_opt_to_json (opt)); + found = 1; + break; + } + if (found) + break; + } + +leave: + gpgme_conf_release (conf); + release_context (ctx); + + return err; +} + + +static const char hlp_config[] = + "op: \"config\"\n" + "\n" + "Optional parameters:\n" + "component: Component of entries to list.\n" + " Default: all\n" + "\n" + "Response on success:\n" + " components: Array of the component program configs.\n" + " name: The component name.\n" + " description: Description of the component.\n" + " program_name: The absolute path to the program.\n" + " options: Array of config options\n" + " String values:\n" + " name: The name of the option\n" + " description: Localized description of the opt.\n" + " argname: Thhe argument name e.g. --verbose\n" + " default_description\n" + " no_arg_description\n" + " Number values:\n" + " flags: Flags for this option.\n" + " level: the level of the description. See gpgme_conf_level_t.\n" + " type: The type of the option. See gpgme_conf_type_t.\n" + " alt_type: Alternate type of the option. See gpgme_conf_type_t\n" + " Arg type values: (see desc. below)\n" + " default_value: Array of the default value.\n" + " no_arg_value: Array of the value if it is not set.\n" + " value: Array for the current value if the option is set.\n" + "\n" + "Conf type values are an array of values that are either\n" + "of type number named \"number\" or of type string,\n" + "named \"string\".\n" + "If the type is none the bool value is_none is true.\n" + ""; +static gpg_error_t +op_config (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_conf_comp_t conf = NULL; + gpgme_conf_comp_t comp = NULL; + cjson_t j_tmp; + char *comp_name = NULL; + cjson_t j_comps = xjson_CreateArray (); + + ctx = get_context (GPGME_PROTOCOL_GPGCONF); + + j_tmp = cJSON_GetObjectItem (request, "component"); + if (j_tmp && cjson_is_string (j_tmp)) + { + comp_name = j_tmp->valuestring; + } + else if (j_tmp && !cjson_is_string (j_tmp)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + /* Load the config */ + err = gpgme_op_conf_load (ctx, &conf); + if (err) + { + goto leave; + } + + comp = conf; + for (comp = conf; comp; comp = comp->next) + { + if (comp_name && comp->name && strcmp (comp->name, comp_name)) + { + /* Skip components if a single one is specified */ + continue; + } + cJSON_AddItemToArray (j_comps, conf_comp_to_json (comp)); + } + xjson_AddItemToObject (result, "components", j_comps); + +leave: + gpgme_conf_release (conf); + release_context (ctx); + return err; } -static const char hlp_getmore[] = - "op: \"getmore\"\n" +static const char hlp_createkey[] = + "op: \"createkey\"\n" + "userid: The user id. E.g. \"Foo Bar <foo@bar.baz>\"\n" "\n" "Optional parameters:\n" - "chunksize: Max number of bytes in the \"data\" object.\n" + "algo: Algo of the key as string. See doc for gpg --quick-gen-key.\n" + " Supported values are \"default\" and \"future-default\".\n" + "expires: Seconds from now to expiry as Number. 0 means no expiry.\n" + " The default is to use a standard expiration interval.\n" "\n" "Response on success:\n" - "type: Type of the pending data\n" - "data: The next chunk of data\n" - "base64: Boolean indicating whether data is base64 encoded\n" - "more: Optional boolean requesting another \"getmore\"."; + "fingerprint: The fingerprint of the created key.\n" + "\n" + "Note: This interface does not allow key generation if the userid\n" + "of the new key already exists in the keyring.\n"; +static gpg_error_t +op_createkey (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + unsigned int flags = GPGME_CREATE_FORCE; /* Always force as the GUI should + handle checks, if required. */ + unsigned long expires = 0; + cjson_t j_tmp; + const char *algo = "default"; + const char *userid; + gpgme_genkey_result_t res; + +#ifdef GPG_AGENT_ALLOWS_KEYGEN_THROUGH_BROWSER + /* GnuPG forbids keygen through the browser socket so for + this we create an unrestricted context. + See GnuPG-Bug-Id: T4010 for more info */ + ctx = get_context (GPGME_PROTOCOL_OpenPGP); +#else + err = gpgme_new (&ctx); + if (err) + log_fatal ("error creating GPGME context: %s\n", gpg_strerror (err)); + gpgme_set_protocol (ctx, GPGME_PROTOCOL_OpenPGP); +#endif + + j_tmp = cJSON_GetObjectItem (request, "algo"); + if (j_tmp && cjson_is_string (j_tmp)) + { + algo = j_tmp->valuestring; + } + + j_tmp = cJSON_GetObjectItem (request, "userid"); + if (!j_tmp || !cjson_is_string (j_tmp)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + userid = j_tmp->valuestring; + + j_tmp = cJSON_GetObjectItem (request, "expires"); + if (j_tmp) + { + if (!cjson_is_number (j_tmp)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + expires = j_tmp->valueint; + + if (!expires) + flags |= GPGME_CREATE_NOEXPIRE; + } + + + if ((err = gpgme_op_createkey (ctx, userid, algo, 0, expires, NULL, flags))) + goto leave; + + res = gpgme_op_genkey_result (ctx); + if (!res) + { + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + xjson_AddStringToObject0 (result, "fingerprint", res->fpr); + +leave: +#ifdef GPG_AGENT_ALLOWS_KEYGEN_THROUGH_BROWSER + release_context (ctx); +#else + gpgme_release (ctx); +#endif + + return err; +} + + + +static const char hlp_getmore[] = + "op: \"getmore\"\n" + "\n" + "Response on success:\n" + "response: base64 encoded json response.\n" + "more: Another getmore is required.\n" + "base64: boolean if the response is base64 encoded.\n"; static gpg_error_t op_getmore (cjson_t request, cjson_t result) { @@ -980,20 +3080,24 @@ op_getmore (cjson_t request, cjson_t result) if ((err = get_chunksize (request, &chunksize))) goto leave; - /* Adjust the chunksize if we need to do base64 conversion. */ - if (pending_data.base64) - chunksize = (chunksize / 4) * 3; + /* For the meta data we need 41 bytes: + {"more":true,"base64":true,"response":""} */ + chunksize -= 41; + + /* Adjust the chunksize for the base64 conversion. */ + chunksize = (chunksize / 4) * 3; /* Do we have anything pending? */ if (!pending_data.buffer) { err = gpg_error (GPG_ERR_NO_DATA); - error_object (result, "Operation not possible: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Operation not possible: %s", + gpg_strerror (err)); goto leave; } - xjson_AddStringToObject (result, "type", pending_data.type); - xjson_AddBoolToObject (result, "base64", pending_data.base64); + /* We currently always use base64 encoding for simplicity. */ + xjson_AddBoolToObject (result, "base64", 1); if (pending_data.written >= pending_data.length) { @@ -1002,7 +3106,7 @@ op_getmore (cjson_t request, cjson_t result) gpgme_free (pending_data.buffer); pending_data.buffer = NULL; xjson_AddBoolToObject (result, "more", 0); - err = cjson_AddStringToObject (result, "data", ""); + err = cjson_AddStringToObject (result, "response", ""); } else { @@ -1017,21 +3121,16 @@ op_getmore (cjson_t request, cjson_t result) c = pending_data.buffer[pending_data.written + n]; pending_data.buffer[pending_data.written + n] = 0; - if (pending_data.base64) - err = add_base64_to_object (result, "data", - (pending_data.buffer - + pending_data.written), n); - else - err = cjson_AddStringToObject (result, "data", - (pending_data.buffer - + pending_data.written)); + err = add_base64_to_object (result, "response", + (pending_data.buffer + + pending_data.written), n); pending_data.buffer[pending_data.written + n] = c; if (!err) { pending_data.written += n; if (pending_data.written >= pending_data.length) { - gpgme_free (pending_data.buffer); + xfree (pending_data.buffer); pending_data.buffer = NULL; } } @@ -1051,9 +3150,27 @@ static const char hlp_help[] = "operation is not performned but a string with the documentation\n" "returned. To list all operations it is allowed to leave out \"op\" in\n" "help mode. Supported values for \"op\" are:\n\n" + " config Read configuration values.\n" + " config_opt Read a single configuration value.\n" + " decrypt Decrypt data.\n" + " delete Delete a key.\n" " encrypt Encrypt data.\n" - " getmore Retrieve remaining data.\n" - " help Help overview."; + " export Export keys.\n" + " createkey Generate a keypair (OpenPGP only).\n" + " import Import data.\n" + " keylist List keys.\n" + " sign Sign data.\n" + " verify Verify data.\n" + " version Get engine information.\n" + " getmore Retrieve remaining data if chunksize was used.\n" + " help Help overview.\n" + "\n" + "If the data needs to be transferred in smaller chunks the\n" + "property \"chunksize\" with an integer value can be added.\n" + "When \"chunksize\" is set the response (including json) will\n" + "not be larger then \"chunksize\" but might be smaller.\n" + "The chunked result will be transferred in base64 encoded chunks\n" + "using the \"getmore\" operation. See help getmore for more info."; static gpg_error_t op_help (cjson_t request, cjson_t result) { @@ -1090,10 +3207,20 @@ process_request (const char *request) gpg_error_t (*handler)(cjson_t request, cjson_t result); const char * const helpstr; } optbl[] = { - { "encrypt", op_encrypt, hlp_encrypt }, - { "decrypt", op_decrypt, hlp_decrypt }, - { "getmore", op_getmore, hlp_getmore }, - { "help", op_help, hlp_help }, + { "config", op_config, hlp_config }, + { "config_opt", op_config_opt, hlp_config_opt }, + { "encrypt", op_encrypt, hlp_encrypt }, + { "export", op_export, hlp_export }, + { "decrypt", op_decrypt, hlp_decrypt }, + { "delete", op_delete, hlp_delete }, + { "createkey", op_createkey, hlp_createkey }, + { "keylist", op_keylist, hlp_keylist }, + { "import", op_import, hlp_import }, + { "sign", op_sign, hlp_sign }, + { "verify", op_verify, hlp_verify }, + { "version", op_version, hlp_version }, + { "getmore", op_getmore, hlp_getmore }, + { "help", op_help, hlp_help }, { NULL } }; size_t erroff; @@ -1101,8 +3228,9 @@ process_request (const char *request) cjson_t j_tmp, j_op; cjson_t response; int helpmode; + int is_getmore = 0; const char *op; - char *res; + char *res = NULL; int idx; response = xjson_CreateObject (); @@ -1146,7 +3274,7 @@ process_request (const char *request) else { gpg_error_t err; - + is_getmore = optbl[idx].handler == op_getmore; /* If this is not the "getmore" command and we have any * pending data release that data. */ if (pending_data.buffer && optbl[idx].handler != op_getmore) @@ -1163,8 +3291,8 @@ process_request (const char *request) || strcmp (j_tmp->valuestring, "error")) { /* No error type response - provide a generic one. */ - error_object (response, "Operation failed: %s", - gpg_strerror (err)); + gpg_error_object (response, err, "Operation failed: %s", + gpg_strerror (err)); } xjson_AddStringToObject (response, "op", op); @@ -1178,14 +3306,37 @@ process_request (const char *request) } leave: - cJSON_Delete (json); - if (opt_interactive) - res = cJSON_Print (response); + if (is_getmore) + { + /* For getmore we bypass the encode_and_chunk. */ + if (opt_interactive) + res = cJSON_Print (response); + else + res = cJSON_PrintUnformatted (response); + } else - res = cJSON_PrintUnformatted (response); + res = encode_and_chunk (json, response); if (!res) - log_error ("Printing JSON data failed\n"); + { + cjson_t err_obj; + + log_error ("printing JSON data failed\n"); + + err_obj = error_object (NULL, "Printing JSON data failed"); + if (opt_interactive) + res = cJSON_Print (err_obj); + res = cJSON_PrintUnformatted (err_obj); + cJSON_Delete (err_obj); + } + + cJSON_Delete (json); cJSON_Delete (response); + + if (!res) + { + /* Can't happen unless we created a broken error_object above */ + return xtrystrdup ("Bug: Fatal error in process request\n"); + } return res; } @@ -1557,7 +3708,7 @@ native_messaging_repl (void) } /* Read request. */ - request = xtrymalloc (nrequest); + request = xtrymalloc (nrequest + 1); if (!request) { err = gpg_error_from_syserror (); @@ -1582,6 +3733,7 @@ native_messaging_repl (void) } else /* Process request */ { + request[n] = '\0'; /* Ensure that request has an end */ if (opt_debug) log_debug ("request='%s'\n", request); xfree (response); @@ -1620,6 +3772,10 @@ native_messaging_repl (void) log_error ("error writing request: %s\n", gpg_strerror (err)); break; } + xfree (response); + response = NULL; + xfree (request); + request = NULL; } xfree (response); @@ -1684,6 +3840,8 @@ main (int argc, char *argv[]) }; gpgrt_argparse_t pargs = { &argc, &argv}; + int log_file_set = 0; + gpgrt_set_strusage (my_strusage); #ifdef HAVE_SETLOCALE @@ -1720,12 +3878,24 @@ main (int argc, char *argv[]) if (!opt_debug) { + /* Handling is similar to GPGME_DEBUG */ const char *s = getenv ("GPGME_JSON_DEBUG"); + const char *s1; + if (s && atoi (s) > 0) - opt_debug = 1; + { + opt_debug = 1; + s1 = strchr (s, PATHSEP_C); + if (s1 && strlen (s1) > 2) + { + s1++; + log_set_file (s1); + log_file_set = 1; + } + } } - if (opt_debug) + if (opt_debug && !log_file_set) { const char *home = getenv ("HOME"); char *file = xstrconcat ("socket://", diff --git a/src/gpgme.c b/src/gpgme.c index 82d6747..2d829d9 100644 --- a/src/gpgme.c +++ b/src/gpgme.c @@ -249,6 +249,7 @@ gpgme_release (gpgme_ctx_t ctx) free (ctx->lc_messages); free (ctx->override_session_key); free (ctx->request_origin); + free (ctx->auto_key_locate); _gpgme_engine_info_release (ctx->engine_info); ctx->engine_info = NULL; DESTROY_LOCK (ctx->lock); @@ -542,6 +543,17 @@ gpgme_set_ctx_flag (gpgme_ctx_t ctx, const char *name, const char *value) { ctx->no_symkey_cache = abool; } + else if (!strcmp (name, "ignore-mdc-error")) + { + ctx->ignore_mdc_error = abool; + } + else if (!strcmp (name, "auto-key-locate")) + { + free (ctx->auto_key_locate); + ctx->auto_key_locate = strdup (value); + if (!ctx->auto_key_locate) + err = gpg_error_from_syserror (); + } else err = gpg_error (GPG_ERR_UNKNOWN_NAME); @@ -591,6 +603,14 @@ gpgme_get_ctx_flag (gpgme_ctx_t ctx, const char *name) { return ctx->no_symkey_cache? "1":""; } + else if (!strcmp (name, "ignore-mdc-error")) + { + return ctx->ignore_mdc_error? "1":""; + } + else if (!strcmp (name, "auto-key-locate")) + { + return ctx->auto_key_locate? ctx->auto_key_locate : ""; + } else return NULL; } diff --git a/src/gpgme.def b/src/gpgme.def index a01d89a..c690220 100644 --- a/src/gpgme.def +++ b/src/gpgme.def @@ -272,5 +272,7 @@ EXPORTS gpgme_op_encrypt_sign_ext @202 gpgme_op_encrypt_sign_ext_start @203 + gpgme_data_new_from_estream @204 + ; END diff --git a/src/gpgme.h.in b/src/gpgme.h.in index 49fafb9..3bf968e 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -95,6 +95,12 @@ extern "C" { #define _GPGME_DEPRECATED_OUTSIDE_GPGME(a,b) _GPGME_DEPRECATED(a,b) #endif +/* We used to use some symbols which clash with keywords in some + * languages. This macro is used to obsolete them. */ +#if defined(__cplusplus) || defined(SWIGPYTHON) +# define _GPGME_OBSOLETE_SOME_SYMBOLS 1 +#endif + /* Check for a matching _FILE_OFFSET_BITS definition. */ #if @NEED__FILE_OFFSET_BITS@ @@ -399,12 +405,15 @@ gpgme_pinentry_mode_t; #define GPGME_EXPORT_MODE_SECRET 16 #define GPGME_EXPORT_MODE_RAW 32 #define GPGME_EXPORT_MODE_PKCS12 64 +#define GPGME_EXPORT_MODE_NOUID 128 /* Experimental(!)*/ typedef unsigned int gpgme_export_mode_t; /* Flags for the audit log functions. */ +#define GPGME_AUDITLOG_DEFAULT 0 #define GPGME_AUDITLOG_HTML 1 +#define GPGME_AUDITLOG_DIAG 2 #define GPGME_AUDITLOG_WITH_HELP 128 @@ -638,7 +647,7 @@ struct _gpgme_key_sig gpgme_error_t status; /* Deprecated; use SIG_CLASS instead. */ -#ifdef __cplusplus +#ifdef _GPGME_OBSOLETE_SOME_SYMBOLS unsigned int _obsolete_class _GPGME_DEPRECATED(0,4); #else unsigned int class _GPGME_DEPRECATED_OUTSIDE_GPGME(0,4); @@ -1178,6 +1187,8 @@ gpgme_error_t gpgme_data_new_from_cbs (gpgme_data_t *dh, gpgme_error_t gpgme_data_new_from_fd (gpgme_data_t *dh, int fd); gpgme_error_t gpgme_data_new_from_stream (gpgme_data_t *dh, FILE *stream); +gpgme_error_t gpgme_data_new_from_estream (gpgme_data_t *r_dh, + gpgrt_stream_t stream); /* Return the encoding attribute of the data buffer DH */ gpgme_data_encoding_t gpgme_data_get_encoding (gpgme_data_t dh); @@ -1365,8 +1376,12 @@ struct _gpgme_op_decrypt_result /* The message claims that the content is a MIME object. */ unsigned int is_mime : 1; + /* The message was made by a legacy algorithm without any integrity + * protection. This might be an old but legitimate message. */ + unsigned int legacy_cipher_nomdc : 1; + /* Internal to GPGME, do not use. */ - int _unused : 29; + int _unused : 28; gpgme_recipient_t recipients; @@ -1458,7 +1473,7 @@ struct _gpgme_new_signature char *fpr; /* Deprecated; use SIG_CLASS instead. */ -#ifdef __cplusplus +#ifdef _GPGME_OBSOLETE_SOME_SYMBOLS unsigned int _obsolete_class_2; #else unsigned int class _GPGME_DEPRECATED_OUTSIDE_GPGME(0,4); @@ -1583,11 +1598,12 @@ struct _gpgme_op_verify_result { gpgme_signature_t signatures; - /* The original file name of the plaintext message, if - available. */ + /* The original file name of the plaintext message, if available. + * Warning: This information is not covered by the signature. */ char *file_name; /* The message claims that the content is a MIME object. */ + /* Warning: This flag is not covered by the signature. */ unsigned int is_mime : 1; /* Internal to GPGME; do not use. */ diff --git a/src/keysign.c b/src/keysign.c index c2fcabb..5e49793 100644 --- a/src/keysign.c +++ b/src/keysign.c @@ -171,7 +171,7 @@ keysign_start (gpgme_ctx_t ctx, int synchronous, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } diff --git a/src/libgpgme.vers b/src/libgpgme.vers index b49c86d..7531f54 100644 --- a/src/libgpgme.vers +++ b/src/libgpgme.vers @@ -133,6 +133,9 @@ GPGME_1.1 { gpgme_op_decrypt_ext; gpgme_op_decrypt_ext_start; + + gpgme_data_new_from_estream; + }; diff --git a/src/op-support.c b/src/op-support.c index 03f274c..9414e61 100644 --- a/src/op-support.c +++ b/src/op-support.c @@ -414,6 +414,9 @@ _gpgme_parse_failure (char *args) { char *where, *which; + if (!strncmp (args, "gpg-exit", 8)) + return 0; + where = strchr (args, ' '); if (!where) return trace_gpg_error (GPG_ERR_INV_ENGINE); @@ -425,9 +428,5 @@ _gpgme_parse_failure (char *args) if (where) *where = '\0'; - where = args; - if (!strcmp (where, "gpg-exit")) - return 0; - return atoi (which); } @@ -85,7 +85,8 @@ gpgme_error_t _gpgme_verify_status_handler (void *priv, /* From decrypt.c. */ -gpgme_error_t _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx); +gpgme_error_t _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx, + gpgme_data_t plaintext); gpgme_error_t _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code, char *args); diff --git a/src/passwd.c b/src/passwd.c index 5bd67a5..6c03002 100644 --- a/src/passwd.c +++ b/src/passwd.c @@ -151,7 +151,7 @@ passwd_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t key, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -449,7 +449,7 @@ sign_start (gpgme_ctx_t ctx, int synchronous, gpgme_data_t plain, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } diff --git a/src/w32-util.c b/src/w32-util.c index 5b02c7e..30dd081 100644 --- a/src/w32-util.c +++ b/src/w32-util.c @@ -72,6 +72,17 @@ # define F_OK 0 #endif +/* The Registry key used by GNUPG. */ +#ifdef _WIN64 +# define GNUPG_REGKEY_2 "Software\\Wow6432Node\\GNU\\GnuPG" +#else +# define GNUPG_REGKEY_2 "Software\\GNU\\GnuPG" +#endif +#ifdef _WIN64 +# define GNUPG_REGKEY_3 "Software\\Wow6432Node\\GnuPG" +#else +# define GNUPG_REGKEY_3 "Software\\GnuPG" +#endif DEFINE_STATIC_LOCK (get_path_lock); @@ -513,7 +524,7 @@ _gpgme_get_gpg_path (void) char *dir; dir = read_w32_registry_string ("HKEY_LOCAL_MACHINE", - "Software\\GNU\\GnuPG", + GNUPG_REGKEY_2, "Install Directory"); if (dir) { @@ -568,12 +579,12 @@ _gpgme_get_gpgconf_path (void) char *dir; dir = read_w32_registry_string (NULL, - "Software\\GNU\\GnuPG", + GNUPG_REGKEY_2, "Install Directory"); if (!dir) { char *tmp = read_w32_registry_string (NULL, - "Software\\GnuPG", + GNUPG_REGKEY_3, "Install Directory"); if (tmp) { @@ -596,6 +607,14 @@ _gpgme_get_gpgconf_path (void) gpgconf = find_program_at_standard_place ("GNU\\GnuPG\\gpgconf.exe"); } + /* 5. Try to find gpgconf.exe relative to us. */ + if (!gpgconf && inst_dir) + { + char *dir = _gpgme_strconcat (inst_dir, "\\..\\..\\GnuPG\\bin"); + gpgconf = find_program_in_dir (dir, name); + free (dir); + } + /* 5. Print a debug message if not found. */ if (!gpgconf) _gpgme_debug (DEBUG_ENGINE, "_gpgme_get_gpgconf_path: '%s' not found",name); |