json: Fix uninitialized key unref in op_delete
[gpgme.git] / src / gpgme.c
index 2372a06..2d829d9 100644 (file)
@@ -1,23 +1,23 @@
 /* gpgme.c - GnuPG Made Easy.
    Copyright (C) 2000 Werner Koch (dd9jn)
-   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 g10 Code GmbH
+   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2012,
+                 2014, 2015 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, write to the Free Software
-   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-   02111-1307, USA.  */
+   License along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
 
 #if HAVE_CONFIG_H
 #include <config.h>
@@ -27,7 +27,9 @@
 #include <string.h>
 #include <assert.h>
 #include <errno.h>
+#ifdef HAVE_LOCALE_H
 #include <locale.h>
+#endif
 
 #include "util.h"
 #include "context.h"
@@ -35,6 +37,8 @@
 #include "wait.h"
 #include "debug.h"
 #include "priv-io.h"
+#include "sys-util.h"
+#include "mbox-util.h"
 
 \f
 /* The default locale.  */
@@ -45,34 +49,77 @@ static char *def_lc_messages;
 \f
 gpgme_error_t _gpgme_selftest = GPG_ERR_NOT_OPERATIONAL;
 
+/* Protects all reference counters in result structures.  All other
+   accesses to a result structure are read only.  */
+DEFINE_STATIC_LOCK (result_ref_lock);
+
+\f
+/* Set the global flag NAME to VALUE.  Return 0 on success.  Note that
+   this function does not use gpgme_error and thus a non-zero return
+   value merely means "error".  Certain flags may be set before
+   gpgme_check_version is called.  See the manual for a description of
+   supported flags.  The caller must assure that this function is
+   called only by one thread at a time.  */
+int
+gpgme_set_global_flag (const char *name, const char *value)
+{
+  if (!name || !value)
+    return -1;
+  else if (!strcmp (name, "debug"))
+    return _gpgme_debug_set_debug_envvar (value);
+  else if (!strcmp (name, "disable-gpgconf"))
+    {
+      _gpgme_dirinfo_disable_gpgconf ();
+      return 0;
+    }
+  else if (!strcmp (name, "require-gnupg"))
+    return _gpgme_set_engine_minimal_version (value);
+  else if (!strcmp (name, "gpgconf-name"))
+    return _gpgme_set_default_gpgconf_name (value);
+  else if (!strcmp (name, "gpg-name"))
+    return _gpgme_set_default_gpg_name (value);
+  else if (!strcmp (name, "w32-inst-dir"))
+    return _gpgme_set_override_inst_dir (value);
+  else
+    return -1;
+}
+
+
 \f
 /* Create a new context as an environment for GPGME crypto
    operations.  */
 gpgme_error_t
 gpgme_new (gpgme_ctx_t *r_ctx)
 {
+  gpgme_error_t err;
   gpgme_ctx_t ctx;
   TRACE_BEG (DEBUG_CTX, "gpgme_new", r_ctx);
 
   if (_gpgme_selftest)
-    return TRACE_ERR (gpgme_error (_gpgme_selftest));
+    return TRACE_ERR (_gpgme_selftest);
+
+  if (!r_ctx)
+    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
 
   ctx = calloc (1, sizeof *ctx);
   if (!ctx)
-    return TRACE_ERR (gpg_error_from_errno (errno));
+    return TRACE_ERR (gpg_error_from_syserror ());
 
   INIT_LOCK (ctx->lock);
-  
-  _gpgme_engine_info_copy (&ctx->engine_info);
-  if (!ctx->engine_info)
+
+  err = _gpgme_engine_info_copy (&ctx->engine_info);
+  if (!err && !ctx->engine_info)
+    err = gpg_error (GPG_ERR_NO_ENGINE);
+  if (err)
     {
       free (ctx);
-      return TRACE_ERR (gpg_error_from_errno (errno));
+      return TRACE_ERR (err);
     }
 
   ctx->keylist_mode = GPGME_KEYLIST_MODE_LOCAL;
   ctx->include_certs = GPGME_INCLUDE_CERTS_DEFAULT;
   ctx->protocol = GPGME_PROTOCOL_OpenPGP;
+  ctx->sub_protocol = GPGME_PROTOCOL_DEFAULT;
   _gpgme_fd_table_init (&ctx->fdt);
 
   LOCK (def_lc_lock);
@@ -81,10 +128,11 @@ gpgme_new (gpgme_ctx_t *r_ctx)
       ctx->lc_ctype = strdup (def_lc_ctype);
       if (!ctx->lc_ctype)
        {
+          int saved_err = gpg_error_from_syserror ();
          UNLOCK (def_lc_lock);
          _gpgme_engine_info_release (ctx->engine_info);
          free (ctx);
-         return TRACE_ERR (gpg_error_from_errno (errno));
+         return TRACE_ERR (saved_err);
        }
     }
   else
@@ -95,12 +143,13 @@ gpgme_new (gpgme_ctx_t *r_ctx)
       ctx->lc_messages = strdup (def_lc_messages);
       if (!ctx->lc_messages)
        {
+          int saved_err = gpg_error_from_syserror ();
          UNLOCK (def_lc_lock);
          if (ctx->lc_ctype)
            free (ctx->lc_ctype);
          _gpgme_engine_info_release (ctx->engine_info);
          free (ctx);
-         return TRACE_ERR (gpg_error_from_errno (errno));
+         return TRACE_ERR (saved_err);
        }
     }
   else
@@ -114,17 +163,32 @@ gpgme_new (gpgme_ctx_t *r_ctx)
 
 
 gpgme_error_t
-_gpgme_cancel_with_err (gpgme_ctx_t ctx, gpg_error_t ctx_err)
+_gpgme_cancel_with_err (gpgme_ctx_t ctx, gpg_error_t ctx_err,
+                       gpg_error_t op_err)
 {
   gpgme_error_t err;
-  TRACE_BEG1 (DEBUG_CTX, "_gpgme_cancel_with_err", ctx, "ctx_err=%i",
-             ctx_err);
+  struct gpgme_io_event_done_data data;
 
-  err = _gpgme_engine_cancel (ctx->engine);
-  if (err)
-    return TRACE_ERR (err);
+  TRACE_BEG2 (DEBUG_CTX, "_gpgme_cancel_with_err", ctx, "ctx_err=%i, op_err=%i",
+             ctx_err, op_err);
+
+  if (ctx_err)
+    {
+      err = _gpgme_engine_cancel (ctx->engine);
+      if (err)
+       return TRACE_ERR (err);
+    }
+  else
+    {
+      err = _gpgme_engine_cancel_op (ctx->engine);
+      if (err)
+       return TRACE_ERR (err);
+    }
 
-  _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &ctx_err);
+  data.err = ctx_err;
+  data.op_err = op_err;
+
+  _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &data);
 
   return TRACE_ERR (0);
 }
@@ -134,7 +198,16 @@ _gpgme_cancel_with_err (gpgme_ctx_t ctx, gpg_error_t ctx_err)
 gpgme_error_t
 gpgme_cancel (gpgme_ctx_t ctx)
 {
-  return _gpgme_cancel_with_err (ctx, gpg_error (GPG_ERR_CANCELED));
+  gpg_error_t err;
+
+  TRACE_BEG (DEBUG_CTX, "gpgme_cancel", ctx);
+
+  if (!ctx)
+    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
+
+  err = _gpgme_cancel_with_err (ctx, gpg_error (GPG_ERR_CANCELED), 0);
+
+  return TRACE_ERR (err);
 }
 
 
@@ -144,6 +217,9 @@ gpgme_cancel_async (gpgme_ctx_t ctx)
 {
   TRACE_BEG (DEBUG_CTX, "gpgme_cancel_async", ctx);
 
+  if (!ctx)
+    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
+
   LOCK (ctx->lock);
   ctx->canceled = 1;
   UNLOCK (ctx->lock);
@@ -158,18 +234,24 @@ gpgme_release (gpgme_ctx_t ctx)
 {
   TRACE (DEBUG_CTX, "gpgme_release", ctx);
 
+  if (!ctx)
+    return;
+
   _gpgme_engine_release (ctx->engine);
+  ctx->engine = NULL;
   _gpgme_fd_table_deinit (&ctx->fdt);
   _gpgme_release_result (ctx);
-  gpgme_signers_clear (ctx);
-  gpgme_sig_notation_clear (ctx);
-  if (ctx->signers)
-    free (ctx->signers);
-  if (ctx->lc_ctype)
-    free (ctx->lc_ctype);
-  if (ctx->lc_messages)
-    free (ctx->lc_messages);
+  _gpgme_signers_clear (ctx);
+  _gpgme_sig_notation_clear (ctx);
+  free (ctx->sender);
+  free (ctx->signers);
+  free (ctx->lc_ctype);
+  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);
   free (ctx);
 }
@@ -178,29 +260,44 @@ gpgme_release (gpgme_ctx_t ctx)
 void
 gpgme_result_ref (void *result)
 {
-  struct ctx_op_data *data = result - sizeof (struct ctx_op_data);
+  struct ctx_op_data *data;
 
   if (! result)
     return;
 
+  data = (void*)((char*)result - sizeof (struct ctx_op_data));
+
+  assert (data->magic == CTX_OP_DATA_MAGIC);
+
+  LOCK (result_ref_lock);
   data->references++;
+  UNLOCK (result_ref_lock);
 }
 
 
 void
 gpgme_result_unref (void *result)
 {
-  struct ctx_op_data *data = result - sizeof (struct ctx_op_data);
+  struct ctx_op_data *data;
 
   if (! result)
     return;
 
-  if (--data->references == 0)
+  data = (void*)((char*)result - sizeof (struct ctx_op_data));
+
+  assert (data->magic == CTX_OP_DATA_MAGIC);
+
+  LOCK (result_ref_lock);
+  if (--data->references)
     {
-      if (data->cleanup)
-       (*data->cleanup) (data->hook);
-      free (data);
+      UNLOCK (result_ref_lock);
+      return;
     }
+  UNLOCK (result_ref_lock);
+
+  if (data->cleanup)
+    (*data->cleanup) (data->hook);
+  free (data);
 }
 
 
@@ -225,11 +322,18 @@ gpgme_set_protocol (gpgme_ctx_t ctx, gpgme_protocol_t protocol)
 {
   TRACE_BEG2 (DEBUG_CTX, "gpgme_set_protocol", ctx, "protocol=%i (%s)",
              protocol, gpgme_get_protocol_name (protocol)
-             ? gpgme_get_protocol_name (protocol) : "unknown");
+             ? gpgme_get_protocol_name (protocol) : "invalid");
 
   if (protocol != GPGME_PROTOCOL_OpenPGP
       && protocol != GPGME_PROTOCOL_CMS
-      && protocol != GPGME_PROTOCOL_ASSUAN)
+      && protocol != GPGME_PROTOCOL_GPGCONF
+      && protocol != GPGME_PROTOCOL_ASSUAN
+      && protocol != GPGME_PROTOCOL_G13
+      && protocol != GPGME_PROTOCOL_UISERVER
+      && protocol != GPGME_PROTOCOL_SPAWN)
+    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
+
+  if (!ctx)
     return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
 
   if (ctx->protocol != protocol)
@@ -254,11 +358,39 @@ gpgme_get_protocol (gpgme_ctx_t ctx)
   TRACE2 (DEBUG_CTX, "gpgme_get_protocol", ctx,
          "ctx->protocol=%i (%s)", ctx->protocol,
          gpgme_get_protocol_name (ctx->protocol)
-         ? gpgme_get_protocol_name (ctx->protocol) : "unknown");
+         ? gpgme_get_protocol_name (ctx->protocol) : "invalid");
+
   return ctx->protocol;
 }
 
 
+gpgme_error_t
+gpgme_set_sub_protocol (gpgme_ctx_t ctx, gpgme_protocol_t protocol)
+{
+  TRACE2 (DEBUG_CTX, "gpgme_set_sub_protocol", ctx, "protocol=%i (%s)",
+         protocol, gpgme_get_protocol_name (protocol)
+         ? gpgme_get_protocol_name (protocol) : "invalid");
+
+  if (!ctx)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  ctx->sub_protocol = protocol;
+  return 0;
+}
+
+
+gpgme_protocol_t
+gpgme_get_sub_protocol (gpgme_ctx_t ctx)
+{
+  TRACE2 (DEBUG_CTX, "gpgme_get_sub_protocol", ctx,
+         "ctx->sub_protocol=%i (%s)", ctx->sub_protocol,
+         gpgme_get_protocol_name (ctx->sub_protocol)
+         ? gpgme_get_protocol_name (ctx->sub_protocol) : "invalid");
+
+  return ctx->sub_protocol;
+}
+
+
 const char *
 gpgme_get_protocol_name (gpgme_protocol_t protocol)
 {
@@ -270,9 +402,24 @@ gpgme_get_protocol_name (gpgme_protocol_t protocol)
     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";
+
+    case GPGME_PROTOCOL_DEFAULT:
+      return "default";
+
     case GPGME_PROTOCOL_UNKNOWN:
       return "unknown";
 
@@ -281,13 +428,53 @@ gpgme_get_protocol_name (gpgme_protocol_t protocol)
     }
 }
 
+
+/* Store the sender's address in the context.  ADDRESS is addr-spec of
+ * mailbox but my also be a complete mailbox, in which case this
+ * function extracts the addr-spec from it.  Returns 0 on success or
+ * an error code if no valid addr-spec could be extracted from
+ * ADDRESS.  */
+gpgme_error_t
+gpgme_set_sender (gpgme_ctx_t ctx, const char *address)
+{
+  char *p = NULL;
+
+  TRACE_BEG1 (DEBUG_CTX, "gpgme_set_sender", ctx, "sender='%s'",
+              address?address:"(null)");
+
+  if (!ctx || (address && !(p = _gpgme_mailbox_from_userid (address))))
+    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
+
+  free (ctx->sender);
+  ctx->sender = p;
+  return TRACE_ERR (0);
+}
+
+
+/* Return the sender's address (addr-spec part) from the context or
+ * NULL if none was set.  The returned value is valid as long as the
+ * CTX is valid and gpgme_set_sender has not been used.  */
+const char *
+gpgme_get_sender (gpgme_ctx_t ctx)
+{
+  TRACE1 (DEBUG_CTX, "gpgme_get_sender", ctx, "sender='%s'",
+          ctx?ctx->sender:"");
+
+  return ctx->sender;
+}
+
+
 /* Enable or disable the use of an ascii armor for all output.  */
 void
 gpgme_set_armor (gpgme_ctx_t ctx, int use_armor)
 {
   TRACE2 (DEBUG_CTX, "gpgme_set_armor", ctx, "use_armor=%i (%s)",
          use_armor, use_armor ? "yes" : "no");
-  ctx->use_armor = use_armor;
+
+  if (!ctx)
+    return;
+
+  ctx->use_armor = !!use_armor;
 }
 
 
@@ -301,6 +488,134 @@ gpgme_get_armor (gpgme_ctx_t ctx)
 }
 
 
+/* Set the flag NAME for CTX to VALUE.  Please consult the manual for
+ * a description of the flags.
+ */
+gpgme_error_t
+gpgme_set_ctx_flag (gpgme_ctx_t ctx, const char *name, const char *value)
+{
+  gpgme_error_t err = 0;
+  int abool;
+
+  TRACE2 (DEBUG_CTX, "gpgme_set_ctx_flag", ctx,
+          "name='%s' value='%s'",
+         name? name:"(null)", value?value:"(null)");
+
+  abool = (value && *value)? !!atoi (value) : 0;
+
+  if (!ctx || !name || !value)
+    err = gpg_error (GPG_ERR_INV_VALUE);
+  else if (!strcmp (name, "redraw"))
+    {
+      ctx->redraw_suggested = abool;
+    }
+  else if (!strcmp (name, "full-status"))
+    {
+      ctx->full_status = abool;
+    }
+  else if (!strcmp (name, "raw-description"))
+    {
+      ctx->raw_description = abool;
+    }
+  else if (!strcmp (name, "export-session-key"))
+    {
+      ctx->export_session_keys = abool;
+    }
+  else if (!strcmp (name, "override-session-key"))
+    {
+      free (ctx->override_session_key);
+      ctx->override_session_key = strdup (value);
+      if (!ctx->override_session_key)
+        err = gpg_error_from_syserror ();
+    }
+  else if (!strcmp (name, "auto-key-retrieve"))
+    {
+      ctx->auto_key_retrieve = abool;
+    }
+  else if (!strcmp (name, "request-origin"))
+    {
+      free (ctx->request_origin);
+      ctx->request_origin = strdup (value);
+      if (!ctx->request_origin)
+        err = gpg_error_from_syserror ();
+    }
+  else if (!strcmp (name, "no-symkey-cache"))
+    {
+      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);
+
+  return err;
+}
+
+
+/* Get the context flag named NAME.  See gpgme_set_ctx_flag for a list
+ * of valid names.  If the NAME is unknown NULL is returned.  For a
+ * boolean flag an empty string is returned for False and the string
+ * "1" for True; thus either atoi or a simple string test can be
+ * used.  */
+const char *
+gpgme_get_ctx_flag (gpgme_ctx_t ctx, const char *name)
+{
+  if (!ctx || !name)
+    return NULL;
+  else if (!strcmp (name, "redraw"))
+    {
+      return ctx->redraw_suggested? "1":"";
+    }
+  else if (!strcmp (name, "full-status"))
+    {
+      return ctx->full_status? "1":"";
+    }
+  else if (!strcmp (name, "raw-description"))
+    {
+      return ctx->raw_description? "1":"";
+    }
+  else if (!strcmp (name, "export-session-key"))
+    {
+      return ctx->export_session_keys? "1":"";
+    }
+  else if (!strcmp (name, "override-session-key"))
+    {
+      return ctx->override_session_key? ctx->override_session_key : "";
+    }
+  else if (!strcmp (name, "auto-key-retrieve"))
+    {
+      return ctx->auto_key_retrieve? "1":"";
+    }
+  else if (!strcmp (name, "request-origin"))
+    {
+      return ctx->request_origin? ctx->request_origin : "";
+    }
+  else if (!strcmp (name, "no-symkey-cache"))
+    {
+      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;
+}
+
+
 /* Enable or disable the use of the special textmode.  Textmode is for
   example used for the RFC2015 signatures; note that the updated RFC
   3156 mandates that the MUA does some preparations so that textmode
@@ -310,7 +625,11 @@ gpgme_set_textmode (gpgme_ctx_t ctx, int use_textmode)
 {
   TRACE2 (DEBUG_CTX, "gpgme_set_textmode", ctx, "use_textmode=%i (%s)",
          use_textmode, use_textmode ? "yes" : "no");
-  ctx->use_textmode = use_textmode;
+
+  if (!ctx)
+    return;
+
+  ctx->use_textmode = !!use_textmode;
 }
 
 /* Return the state of the textmode flag.  */
@@ -323,12 +642,39 @@ gpgme_get_textmode (gpgme_ctx_t ctx)
 }
 
 
+/* Enable offline mode for this context. In offline mode dirmngr
+  will be disabled. */
+void
+gpgme_set_offline (gpgme_ctx_t ctx, int offline)
+{
+  TRACE2 (DEBUG_CTX, "gpgme_set_offline", ctx, "offline=%i (%s)",
+          offline, offline ? "yes" : "no");
+
+  if (!ctx)
+    return;
+
+  ctx->offline = !!offline;
+}
+
+/* Return the state of the offline flag.  */
+int
+gpgme_get_offline (gpgme_ctx_t ctx)
+{
+  TRACE2 (DEBUG_CTX, "gpgme_get_offline", ctx, "ctx->offline=%i (%s)",
+          ctx->offline, ctx->offline ? "yes" : "no");
+  return ctx->offline;
+}
+
+
 /* Set the number of certifications to include in an S/MIME message.
    The default is GPGME_INCLUDE_CERTS_DEFAULT.  -1 means all certs,
    and -2 means all certs except the root cert.  */
 void
 gpgme_set_include_certs (gpgme_ctx_t ctx, int nr_of_certs)
 {
+  if (!ctx)
+    return;
+
   if (nr_of_certs == GPGME_INCLUDE_CERTS_DEFAULT)
     ctx->include_certs = GPGME_INCLUDE_CERTS_DEFAULT;
   else if (nr_of_certs < -2)
@@ -361,6 +707,9 @@ gpgme_set_keylist_mode (gpgme_ctx_t ctx, gpgme_keylist_mode_t mode)
   TRACE1 (DEBUG_CTX, "gpgme_set_keylist_mode", ctx, "keylist_mode=0x%x",
          mode);
 
+  if (!ctx)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
   ctx->keylist_mode = mode;
   return 0;
 }
@@ -376,6 +725,43 @@ gpgme_get_keylist_mode (gpgme_ctx_t ctx)
 }
 
 
+/* Set the pinentry mode for CTX to MODE. */
+gpgme_error_t
+gpgme_set_pinentry_mode (gpgme_ctx_t ctx, gpgme_pinentry_mode_t mode)
+{
+  TRACE1 (DEBUG_CTX, "gpgme_set_pinentry_mode", ctx, "pinentry_mode=%u",
+         (unsigned int)mode);
+
+  if (!ctx)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  switch (mode)
+    {
+    case GPGME_PINENTRY_MODE_DEFAULT:
+    case GPGME_PINENTRY_MODE_ASK:
+    case GPGME_PINENTRY_MODE_CANCEL:
+    case GPGME_PINENTRY_MODE_ERROR:
+    case GPGME_PINENTRY_MODE_LOOPBACK:
+      break;
+    default:
+      return gpg_error (GPG_ERR_INV_VALUE);
+    }
+
+  ctx->pinentry_mode = mode;
+  return 0;
+}
+
+
+/* Get the pinentry mode of CTX.  */
+gpgme_pinentry_mode_t
+gpgme_get_pinentry_mode (gpgme_ctx_t ctx)
+{
+  TRACE1 (DEBUG_CTX, "gpgme_get_pinentry_mode", ctx,
+         "ctx->pinentry_mode=%u", (unsigned int)ctx->pinentry_mode);
+  return ctx->pinentry_mode;
+}
+
+
 /* This function sets a callback function to be used to pass a
    passphrase to gpg.  */
 void
@@ -384,6 +770,10 @@ gpgme_set_passphrase_cb (gpgme_ctx_t ctx, gpgme_passphrase_cb_t cb,
 {
   TRACE2 (DEBUG_CTX, "gpgme_set_passphrase_cb", ctx,
          "passphrase_cb=%p/%p", cb, cb_value);
+
+  if (!ctx)
+    return;
+
   ctx->passphrase_cb = cb;
   ctx->passphrase_cb_value = cb_value;
 }
@@ -412,6 +802,10 @@ gpgme_set_progress_cb (gpgme_ctx_t ctx, gpgme_progress_cb_t cb, void *cb_value)
 {
   TRACE2 (DEBUG_CTX, "gpgme_set_progress_cb", ctx, "progress_cb=%p/%p",
          cb, cb_value);
+
+  if (!ctx)
+    return;
+
   ctx->progress_cb = cb;
   ctx->progress_cb_value = cb_value;
 }
@@ -432,10 +826,54 @@ gpgme_get_progress_cb (gpgme_ctx_t ctx, gpgme_progress_cb_t *r_cb,
 }
 
 
+/* This function sets a callback function to be used as a status
+   message forwarder.  */
+void
+gpgme_set_status_cb (gpgme_ctx_t ctx, gpgme_status_cb_t cb, void *cb_value)
+{
+  TRACE2 (DEBUG_CTX, "gpgme_set_status_cb", ctx, "status_cb=%p/%p",
+         cb, cb_value);
+
+  if (!ctx)
+    return;
+
+  ctx->status_cb = cb;
+  ctx->status_cb_value = cb_value;
+}
+
+
+/* This function returns the callback function to be used as a
+   status message forwarder.  */
+void
+gpgme_get_status_cb (gpgme_ctx_t ctx, gpgme_status_cb_t *r_cb,
+                      void **r_cb_value)
+{
+  TRACE2 (DEBUG_CTX, "gpgme_get_status_cb", ctx, "ctx->status_cb=%p/%p",
+         ctx ? ctx->status_cb : NULL, ctx ? ctx->status_cb_value : NULL);
+
+  if (r_cb)
+    *r_cb = NULL;
+
+  if (r_cb_value)
+    *r_cb_value = NULL;
+
+  if (!ctx || !ctx->status_cb)
+    return;
+
+  if (r_cb)
+    *r_cb = ctx->status_cb;
+  if (r_cb_value)
+    *r_cb_value = ctx->status_cb_value;
+}
+
+
 /* Set the I/O callback functions for CTX to IO_CBS.  */
 void
 gpgme_set_io_cbs (gpgme_ctx_t ctx, gpgme_io_cbs_t io_cbs)
 {
+  if (!ctx)
+    return;
+
   if (io_cbs)
     {
       TRACE6 (DEBUG_CTX, "gpgme_set_io_cbs", ctx,
@@ -459,28 +897,57 @@ gpgme_set_io_cbs (gpgme_ctx_t ctx, gpgme_io_cbs_t io_cbs)
 
 /* This function provides access to the internal read function; it is
    normally not used.  */
-ssize_t
+gpgme_ssize_t
 gpgme_io_read (int fd, void *buffer, size_t count)
 {
   int ret;
+  TRACE_BEG2 (DEBUG_GLOBAL, "gpgme_io_read", fd,
+             "buffer=%p, count=%u", buffer, count);
 
   ret = _gpgme_io_read (fd, buffer, count);
 
-  return ret;
+  return TRACE_SYSRES (ret);
 }
 
 
 /* This function provides access to the internal write function.  It
    is to be used by user callbacks to return data to gpgme.  See
    gpgme_passphrase_cb_t and gpgme_edit_cb_t.  */
-ssize_t
+gpgme_ssize_t
 gpgme_io_write (int fd, const void *buffer, size_t count)
 {
   int ret;
+  TRACE_BEG2 (DEBUG_GLOBAL, "gpgme_io_write", fd,
+             "buffer=%p, count=%u", buffer, count);
 
   ret = _gpgme_io_write (fd, buffer, count);
 
-  return ret;
+  return TRACE_SYSRES (ret);
+}
+
+/* This function provides access to the internal write function.  It
+   is to be used by user callbacks to return data to gpgme.  See
+   gpgme_passphrase_cb_t and gpgme_edit_cb_t.  Note that this is a
+   variant of gpgme_io_write which guarantees that all COUNT bytes are
+   written or an error is return.  Returns: 0 on success or -1 on
+   error and the sets errno. */
+int
+gpgme_io_writen (int fd, const void *buffer_arg, size_t count)
+{
+  const char *buffer = buffer_arg;
+  int ret = 0;
+  TRACE_BEG2 (DEBUG_GLOBAL, "gpgme_io_writen", fd,
+             "buffer=%p, count=%u", buffer, count);
+  while (count)
+    {
+      ret = _gpgme_io_write (fd, buffer, count);
+      if (ret < 0)
+        break;
+      buffer += ret;
+      count -= ret;
+      ret = 0;
+    }
+  return TRACE_SYSRES (ret);
 }
 
 
@@ -518,21 +985,23 @@ gpgme_set_locale (gpgme_ctx_t ctx, int category, const char *value)
         failed = 1;                                            \
     }
 
+#ifdef LC_CTYPE
   PREPARE_ONE_LOCALE (ctype, CTYPE);
+#endif
 #ifdef LC_MESSAGES
   PREPARE_ONE_LOCALE (messages, MESSAGES);
 #endif
 
   if (failed)
     {
-      int saved_errno = errno;
+      int saved_err = gpg_error_from_syserror ();
 
       if (new_lc_ctype)
        free (new_lc_ctype);
       if (new_lc_messages)
        free (new_lc_messages);
 
-      return TRACE_ERR (gpg_error_from_errno (saved_errno));
+      return TRACE_ERR (saved_err);
     }
 
 #define SET_ONE_LOCALE(lcat, ucat)                     \
@@ -554,7 +1023,9 @@ gpgme_set_locale (gpgme_ctx_t ctx, int category, const char *value)
 
   if (!ctx)
     LOCK (def_lc_lock);
+#ifdef LC_CTYPE
   SET_ONE_LOCALE (ctype, CTYPE);
+#endif
 #ifdef LC_MESSAGES
   SET_ONE_LOCALE (messages, MESSAGES);
 #endif
@@ -590,7 +1061,10 @@ gpgme_ctx_set_engine_info (gpgme_ctx_t ctx, gpgme_protocol_t proto,
              ? gpgme_get_protocol_name (proto) : "unknown",
              file_name ? file_name : "(default)",
              home_dir ? home_dir : "(default)");
-             
+
+  if (!ctx)
+    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
+
   /* Shut down the engine when changing engine info.  */
   if (ctx->engine)
     {
@@ -606,10 +1080,9 @@ gpgme_ctx_set_engine_info (gpgme_ctx_t ctx, gpgme_protocol_t proto,
 \f
 /* Clear all notation data from the context.  */
 void
-gpgme_sig_notation_clear (gpgme_ctx_t ctx)
+_gpgme_sig_notation_clear (gpgme_ctx_t ctx)
 {
   gpgme_sig_notation_t notation;
-  TRACE (DEBUG_CTX, "gpgme_sig_notation_clear", ctx);
 
   if (!ctx)
     return;
@@ -624,6 +1097,17 @@ gpgme_sig_notation_clear (gpgme_ctx_t ctx)
   ctx->sig_notations = NULL;
 }
 
+void
+gpgme_sig_notation_clear (gpgme_ctx_t ctx)
+{
+  TRACE (DEBUG_CTX, "gpgme_sig_notation_clear", ctx);
+
+  if (!ctx)
+    return;
+
+  _gpgme_sig_notation_clear (ctx);
+}
+
 
 /* Add the human-readable notation data with name NAME and value VALUE
    to the context CTX, using the flags FLAGS.  If NAME is NULL, then
@@ -642,7 +1126,7 @@ gpgme_sig_notation_add (gpgme_ctx_t ctx, const char *name,
              "name=%s, value=%s, flags=0x%x",
              name ? name : "(null)", value ? value : "(null)",
              flags);
-  
+
   if (!ctx)
     return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
 
@@ -679,33 +1163,71 @@ gpgme_sig_notation_get (gpgme_ctx_t ctx)
 
   return ctx->sig_notations;
 }
-  
+
+
 \f
-const char *
-gpgme_pubkey_algo_name (gpgme_pubkey_algo_t algo)
+/* Return a public key algorithm string made of the algorithm and size
+   or the curve name.  May return NULL on error.  Caller must free the
+   result using gpgme_free.  */
+char *
+gpgme_pubkey_algo_string (gpgme_subkey_t subkey)
 {
-  switch (algo)
+  const char *prefix = NULL;
+  char *result;
+
+  if (!subkey)
     {
-    case GPGME_PK_RSA:
-      return "RSA";
+      gpg_err_set_errno (EINVAL);
+      return NULL;
+    }
 
+  switch (subkey->pubkey_algo)
+    {
+    case GPGME_PK_RSA:
     case GPGME_PK_RSA_E:
-      return "RSA-E";
-
-    case GPGME_PK_RSA_S:
-      return "RSA-S";
+    case GPGME_PK_RSA_S: prefix = "rsa"; break;
+    case GPGME_PK_ELG_E: prefix = "elg"; break;
+    case GPGME_PK_DSA:  prefix = "dsa"; break;
+    case GPGME_PK_ELG:   prefix = "xxx"; break;
+    case GPGME_PK_ECC:
+    case GPGME_PK_ECDH:
+    case GPGME_PK_ECDSA:
+    case GPGME_PK_EDDSA: prefix = "";    break;
+    }
 
-    case GPGME_PK_ELG_E:
-      return "ELG-E";
+  if (prefix && *prefix)
+    {
+      char buffer[40];
+      snprintf (buffer, sizeof buffer, "%s%u", prefix, subkey->length);
+      result = strdup (buffer);
+    }
+  else if (prefix && subkey->curve && *subkey->curve)
+    result = strdup (subkey->curve);
+  else if (prefix)
+    result =  strdup ("E_error");
+  else
+    result = strdup  ("unknown");
 
-    case GPGME_PK_DSA:
-      return "DSA";
+  return result;
+}
 
-    case GPGME_PK_ELG:
-      return "ELG";
 
-    default:
-      return NULL;
+const char *
+gpgme_pubkey_algo_name (gpgme_pubkey_algo_t algo)
+{
+  switch (algo)
+    {
+    case GPGME_PK_RSA:   return "RSA";
+    case GPGME_PK_RSA_E: return "RSA-E";
+    case GPGME_PK_RSA_S: return "RSA-S";
+    case GPGME_PK_ELG_E: return "ELG-E";
+    case GPGME_PK_DSA:   return "DSA";
+    case GPGME_PK_ECC:   return "ECC";
+    case GPGME_PK_ELG:   return "ELG";
+    case GPGME_PK_ECDSA: return "ECDSA";
+    case GPGME_PK_ECDH:  return "ECDH";
+    case GPGME_PK_EDDSA: return "EdDSA";
+    default:             return NULL;
     }
 }
 
@@ -742,6 +1264,9 @@ gpgme_hash_algo_name (gpgme_hash_algo_t algo)
     case GPGME_MD_SHA512:
       return "SHA512";
 
+    case GPGME_MD_SHA224:
+      return "SHA224";
+
     case GPGME_MD_MD4:
       return "MD4";