Blame src/engine-assuan.c

Packit d7e8d0
/* engine-assuan.c - Low-level Assuan protocol engine
Packit d7e8d0
 * Copyright (C) 2009 g10 Code GmbH
Packit d7e8d0
 *
Packit d7e8d0
 * This file is part of GPGME.
Packit d7e8d0
 *
Packit d7e8d0
 * GPGME is free software; you can redistribute it and/or modify it
Packit d7e8d0
 * under the terms of the GNU Lesser General Public License as
Packit d7e8d0
 * published by the Free Software Foundation; either version 2.1 of
Packit d7e8d0
 * the License, or (at your option) any later version.
Packit d7e8d0
 *
Packit d7e8d0
 * GPGME is distributed in the hope that it will be useful, but
Packit d7e8d0
 * WITHOUT ANY WARRANTY; without even the implied warranty of
Packit d7e8d0
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit d7e8d0
 * Lesser General Public License for more details.
Packit d7e8d0
 *
Packit d7e8d0
 * You should have received a copy of the GNU Lesser General Public
Packit d7e8d0
 * License along with this program; if not, see <https://www.gnu.org/licenses/>.
Packit d7e8d0
 */
Packit d7e8d0
Packit d7e8d0
/*
Packit d7e8d0
   Note: This engine requires a modern Assuan server which uses
Packit d7e8d0
   gpg-error codes.  In particular there is no backward compatible
Packit d7e8d0
   mapping of old Assuan error codes implemented.
Packit d7e8d0
*/
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
#if HAVE_CONFIG_H
Packit d7e8d0
#include <config.h>
Packit d7e8d0
#endif
Packit d7e8d0
Packit d7e8d0
#include <stdlib.h>
Packit d7e8d0
#include <string.h>
Packit d7e8d0
#ifdef HAVE_SYS_TYPES_H
Packit d7e8d0
# include <sys/types.h>
Packit d7e8d0
#endif
Packit d7e8d0
#include <assert.h>
Packit d7e8d0
#ifdef HAVE_UNISTD_H
Packit d7e8d0
# include <unistd.h>
Packit d7e8d0
#endif
Packit d7e8d0
#ifdef HAVE_LOCALE_H
Packit d7e8d0
#include <locale.h>
Packit d7e8d0
#endif
Packit d7e8d0
#include <errno.h>
Packit d7e8d0
Packit d7e8d0
#include "gpgme.h"
Packit d7e8d0
#include "util.h"
Packit d7e8d0
#include "ops.h"
Packit d7e8d0
#include "wait.h"
Packit d7e8d0
#include "priv-io.h"
Packit d7e8d0
#include "sema.h"
Packit d7e8d0
Packit d7e8d0
#include "assuan.h"
Packit d7e8d0
#include "debug.h"
Packit d7e8d0
Packit d7e8d0
#include "engine-backend.h"
Packit d7e8d0
Packit d7e8d0

Packit d7e8d0
typedef struct
Packit d7e8d0
{
Packit d7e8d0
  int fd;	/* FD we talk about.  */
Packit d7e8d0
  int server_fd;/* Server FD for this connection.  */
Packit d7e8d0
  int dir;	/* Inbound/Outbound, maybe given implicit?  */
Packit d7e8d0
  void *data;	/* Handler-specific data.  */
Packit d7e8d0
  void *tag;	/* ID from the user for gpgme_remove_io_callback.  */
Packit d7e8d0
} iocb_data_t;
Packit d7e8d0
Packit d7e8d0
/* Engine instance data.  */
Packit d7e8d0
struct engine_llass
Packit d7e8d0
{
Packit d7e8d0
  assuan_context_t assuan_ctx;
Packit d7e8d0
Packit d7e8d0
  int lc_ctype_set;
Packit d7e8d0
  int lc_messages_set;
Packit d7e8d0
Packit d7e8d0
  iocb_data_t status_cb;
Packit d7e8d0
Packit d7e8d0
  struct gpgme_io_cbs io_cbs;
Packit d7e8d0
Packit d7e8d0
  /* Hack for old opassuan.c interface, see there the result struct.  */
Packit d7e8d0
  gpg_error_t last_op_err;
Packit d7e8d0
Packit d7e8d0
  /* User provided callbacks.  */
Packit d7e8d0
  struct {
Packit d7e8d0
    gpgme_assuan_data_cb_t data_cb;
Packit d7e8d0
    void *data_cb_value;
Packit d7e8d0
Packit d7e8d0
    gpgme_assuan_inquire_cb_t inq_cb;
Packit d7e8d0
    void *inq_cb_value;
Packit d7e8d0
Packit d7e8d0
    gpgme_assuan_status_cb_t status_cb;
Packit d7e8d0
    void *status_cb_value;
Packit d7e8d0
  } user;
Packit d7e8d0
Packit d7e8d0
  /* Option flags.  */
Packit d7e8d0
  struct {
Packit d7e8d0
    int gpg_agent:1;  /* Assume this is a gpg-agent connection.  */
Packit d7e8d0
  } opt;
Packit d7e8d0
Packit d7e8d0
};
Packit d7e8d0
typedef struct engine_llass *engine_llass_t;
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
gpg_error_t _gpgme_engine_assuan_last_op_err (void *engine)
Packit d7e8d0
{
Packit d7e8d0
  engine_llass_t llass = engine;
Packit d7e8d0
  return llass->last_op_err;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
/* Prototypes.  */
Packit d7e8d0
static void llass_io_event (void *engine,
Packit d7e8d0
                            gpgme_event_io_t type, void *type_data);
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0

Packit d7e8d0
/* return the default home directory.  */
Packit d7e8d0
static const char *
Packit d7e8d0
llass_get_home_dir (void)
Packit d7e8d0
{
Packit d7e8d0
  /* For this engine the home directory is not a filename but a string
Packit d7e8d0
     used to convey options.  The exclamation mark is a marker to show
Packit d7e8d0
     that this is not a directory name. Options are strings delimited
Packit d7e8d0
     by a space.  The only option defined for now is GPG_AGENT to
Packit d7e8d0
     enable GPG_AGENT specific commands to send to the server at
Packit d7e8d0
     connection startup.  */
Packit d7e8d0
  return "!GPG_AGENT";
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
static char *
Packit d7e8d0
llass_get_version (const char *file_name)
Packit d7e8d0
{
Packit d7e8d0
  (void)file_name;
Packit d7e8d0
  return NULL;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
static const char *
Packit d7e8d0
llass_get_req_version (void)
Packit d7e8d0
{
Packit d7e8d0
  return NULL;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0

Packit d7e8d0
static void
Packit d7e8d0
close_notify_handler (int fd, void *opaque)
Packit d7e8d0
{
Packit d7e8d0
  engine_llass_t llass = opaque;
Packit d7e8d0
Packit d7e8d0
  assert (fd != -1);
Packit d7e8d0
  if (llass->status_cb.fd == fd)
Packit d7e8d0
    {
Packit d7e8d0
      if (llass->status_cb.tag)
Packit d7e8d0
	llass->io_cbs.remove (llass->status_cb.tag);
Packit d7e8d0
      llass->status_cb.fd = -1;
Packit d7e8d0
      llass->status_cb.tag = NULL;
Packit d7e8d0
    }
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
static gpgme_error_t
Packit d7e8d0
llass_cancel (void *engine)
Packit d7e8d0
{
Packit d7e8d0
  engine_llass_t llass = engine;
Packit d7e8d0
Packit d7e8d0
  if (!llass)
Packit d7e8d0
    return gpg_error (GPG_ERR_INV_VALUE);
Packit d7e8d0
Packit d7e8d0
  if (llass->status_cb.fd != -1)
Packit d7e8d0
    _gpgme_io_close (llass->status_cb.fd);
Packit d7e8d0
Packit d7e8d0
  if (llass->assuan_ctx)
Packit d7e8d0
    {
Packit d7e8d0
      assuan_release (llass->assuan_ctx);
Packit d7e8d0
      llass->assuan_ctx = NULL;
Packit d7e8d0
    }
Packit d7e8d0
Packit d7e8d0
  return 0;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
static gpgme_error_t
Packit d7e8d0
llass_cancel_op (void *engine)
Packit d7e8d0
{
Packit d7e8d0
  engine_llass_t llass = engine;
Packit d7e8d0
Packit d7e8d0
  if (!llass)
Packit d7e8d0
    return gpg_error (GPG_ERR_INV_VALUE);
Packit d7e8d0
Packit d7e8d0
  if (llass->status_cb.fd != -1)
Packit d7e8d0
    _gpgme_io_close (llass->status_cb.fd);
Packit d7e8d0
Packit d7e8d0
  return 0;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
static void
Packit d7e8d0
llass_release (void *engine)
Packit d7e8d0
{
Packit d7e8d0
  engine_llass_t llass = engine;
Packit d7e8d0
Packit d7e8d0
  if (!llass)
Packit d7e8d0
    return;
Packit d7e8d0
Packit d7e8d0
  llass_cancel (engine);
Packit d7e8d0
Packit d7e8d0
  free (llass);
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
/* Create a new instance. If HOME_DIR is NULL standard options for use
Packit d7e8d0
   with gpg-agent are issued.  */
Packit d7e8d0
static gpgme_error_t
Packit d7e8d0
llass_new (void **engine, const char *file_name, const char *home_dir,
Packit d7e8d0
           const char *version)
Packit d7e8d0
{
Packit d7e8d0
  gpgme_error_t err = 0;
Packit d7e8d0
  engine_llass_t llass;
Packit d7e8d0
  char *optstr;
Packit d7e8d0
  char *env_tty = NULL;
Packit d7e8d0
Packit d7e8d0
  (void)version; /* Not yet used.  */
Packit d7e8d0
Packit d7e8d0
  llass = calloc (1, sizeof *llass);
Packit d7e8d0
  if (!llass)
Packit d7e8d0
    return gpg_error_from_syserror ();
Packit d7e8d0
Packit d7e8d0
  llass->status_cb.fd = -1;
Packit d7e8d0
  llass->status_cb.dir = 1;
Packit d7e8d0
  llass->status_cb.tag = 0;
Packit d7e8d0
  llass->status_cb.data = llass;
Packit d7e8d0
Packit d7e8d0
  /* Parse_options.  */
Packit d7e8d0
  if (home_dir && *home_dir == '!')
Packit d7e8d0
    {
Packit d7e8d0
      home_dir++;
Packit d7e8d0
      /* Very simple parser only working for the one option we support.  */
Packit d7e8d0
      /* Note that wk promised to write a regression test if this
Packit d7e8d0
         parser will be extended.  */
Packit d7e8d0
      if (!strncmp (home_dir, "GPG_AGENT", 9)
Packit d7e8d0
          && (!home_dir[9] || home_dir[9] == ' '))
Packit d7e8d0
        llass->opt.gpg_agent = 1;
Packit d7e8d0
    }
Packit d7e8d0
Packit d7e8d0
  err = assuan_new_ext (&llass->assuan_ctx, GPG_ERR_SOURCE_GPGME,
Packit d7e8d0
			&_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb,
Packit d7e8d0
			NULL);
Packit d7e8d0
  if (err)
Packit d7e8d0
    goto leave;
Packit d7e8d0
  assuan_ctx_set_system_hooks (llass->assuan_ctx, &_gpgme_assuan_system_hooks);
Packit d7e8d0
  assuan_set_flag (llass->assuan_ctx, ASSUAN_CONVEY_COMMENTS, 1);
Packit d7e8d0
Packit d7e8d0
  err = assuan_socket_connect (llass->assuan_ctx, file_name, 0, 0);
Packit d7e8d0
  if (err)
Packit d7e8d0
    goto leave;
Packit d7e8d0
Packit d7e8d0
  if (llass->opt.gpg_agent)
Packit d7e8d0
    {
Packit d7e8d0
      char *dft_display = NULL;
Packit d7e8d0
Packit d7e8d0
      err = _gpgme_getenv ("DISPLAY", &dft_display);
Packit d7e8d0
      if (err)
Packit d7e8d0
        goto leave;
Packit d7e8d0
      if (dft_display)
Packit d7e8d0
        {
Packit d7e8d0
          if (gpgrt_asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
Packit d7e8d0
            {
Packit d7e8d0
              err = gpg_error_from_syserror ();
Packit d7e8d0
              free (dft_display);
Packit d7e8d0
              goto leave;
Packit d7e8d0
            }
Packit d7e8d0
          free (dft_display);
Packit d7e8d0
Packit d7e8d0
          err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL,
Packit d7e8d0
                                 NULL, NULL, NULL);
Packit d7e8d0
          gpgrt_free (optstr);
Packit d7e8d0
          if (err)
Packit d7e8d0
            goto leave;
Packit d7e8d0
        }
Packit d7e8d0
    }
Packit d7e8d0
Packit d7e8d0
  if (llass->opt.gpg_agent)
Packit d7e8d0
    err = _gpgme_getenv ("GPG_TTY", &env_tty);
Packit d7e8d0
Packit d7e8d0
  if (llass->opt.gpg_agent && (isatty (1) || env_tty || err))
Packit d7e8d0
    {
Packit d7e8d0
      int rc = 0;
Packit d7e8d0
      char dft_ttyname[64];
Packit d7e8d0
      char *dft_ttytype = NULL;
Packit d7e8d0
Packit d7e8d0
      if (err)
Packit d7e8d0
        goto leave;
Packit d7e8d0
      else if (env_tty)
Packit d7e8d0
        {
Packit d7e8d0
          snprintf (dft_ttyname, sizeof (dft_ttyname), "%s", env_tty);
Packit d7e8d0
          free (env_tty);
Packit d7e8d0
        }
Packit d7e8d0
      else
Packit d7e8d0
        rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
Packit d7e8d0
Packit d7e8d0
      /* Even though isatty() returns 1, ttyname_r() may fail in many
Packit d7e8d0
	 ways, e.g., when /dev/pts is not accessible under chroot.  */
Packit d7e8d0
      if (!rc)
Packit d7e8d0
	{
Packit d7e8d0
	  if (gpgrt_asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
Packit d7e8d0
	    {
Packit d7e8d0
	      err = gpg_error_from_syserror ();
Packit d7e8d0
	      goto leave;
Packit d7e8d0
	    }
Packit d7e8d0
	  err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL,
Packit d7e8d0
				 NULL, NULL, NULL);
Packit d7e8d0
	  gpgrt_free (optstr);
Packit d7e8d0
	  if (err)
Packit d7e8d0
            goto leave;
Packit d7e8d0
Packit d7e8d0
	  err = _gpgme_getenv ("TERM", &dft_ttytype);
Packit d7e8d0
	  if (err)
Packit d7e8d0
	    goto leave;
Packit d7e8d0
	  if (dft_ttytype)
Packit d7e8d0
	    {
Packit d7e8d0
	      if (gpgrt_asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype)< 0)
Packit d7e8d0
		{
Packit d7e8d0
		  err = gpg_error_from_syserror ();
Packit d7e8d0
		  free (dft_ttytype);
Packit d7e8d0
		  goto leave;
Packit d7e8d0
		}
Packit d7e8d0
	      free (dft_ttytype);
Packit d7e8d0
Packit d7e8d0
	      err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL,
Packit d7e8d0
				     NULL, NULL, NULL, NULL);
Packit d7e8d0
	      gpgrt_free (optstr);
Packit d7e8d0
	      if (err)
Packit d7e8d0
                goto leave;
Packit d7e8d0
	    }
Packit d7e8d0
	}
Packit d7e8d0
    }
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
#ifdef HAVE_W32_SYSTEM
Packit d7e8d0
  /* Under Windows we need to use AllowSetForegroundWindow.  Tell
Packit d7e8d0
     llass to tell us when it needs it.  */
Packit d7e8d0
  if (!err && llass->opt.gpg_agent)
Packit d7e8d0
    {
Packit d7e8d0
      err = assuan_transact (llass->assuan_ctx, "OPTION allow-pinentry-notify",
Packit d7e8d0
                             NULL, NULL, NULL, NULL, NULL, NULL);
Packit d7e8d0
      if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
Packit d7e8d0
        err = 0; /* This work only with recent gpg-agents.  */
Packit d7e8d0
    }
Packit d7e8d0
#endif /*HAVE_W32_SYSTEM*/
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
 leave:
Packit d7e8d0
  /* Close the server ends of the pipes (because of this, we must use
Packit d7e8d0
     the stored server_fd_str in the function start).  Our ends are
Packit d7e8d0
     closed in llass_release().  */
Packit d7e8d0
Packit d7e8d0
  if (err)
Packit d7e8d0
    llass_release (llass);
Packit d7e8d0
  else
Packit d7e8d0
    *engine = llass;
Packit d7e8d0
Packit d7e8d0
  return err;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
static gpgme_error_t
Packit d7e8d0
llass_set_locale (void *engine, int category, const char *value)
Packit d7e8d0
{
Packit d7e8d0
  gpgme_error_t err;
Packit d7e8d0
  engine_llass_t llass = engine;
Packit d7e8d0
  char *optstr;
Packit d7e8d0
  const char *catstr;
Packit d7e8d0
Packit d7e8d0
  if (!llass->opt.gpg_agent)
Packit d7e8d0
    return 0;
Packit d7e8d0
Packit d7e8d0
  /* FIXME: If value is NULL, we need to reset the option to default.
Packit d7e8d0
     But we can't do this.  So we error out here.  gpg-agent needs
Packit d7e8d0
     support for this.  */
Packit d7e8d0
  if (0)
Packit d7e8d0
    ;
Packit d7e8d0
#ifdef LC_CTYPE
Packit d7e8d0
  else if (category == LC_CTYPE)
Packit d7e8d0
    {
Packit d7e8d0
      catstr = "lc-ctype";
Packit d7e8d0
      if (!value && llass->lc_ctype_set)
Packit d7e8d0
	return gpg_error (GPG_ERR_INV_VALUE);
Packit d7e8d0
      if (value)
Packit d7e8d0
	llass->lc_ctype_set = 1;
Packit d7e8d0
    }
Packit d7e8d0
#endif
Packit d7e8d0
#ifdef LC_MESSAGES
Packit d7e8d0
  else if (category == LC_MESSAGES)
Packit d7e8d0
    {
Packit d7e8d0
      catstr = "lc-messages";
Packit d7e8d0
      if (!value && llass->lc_messages_set)
Packit d7e8d0
	return gpg_error (GPG_ERR_INV_VALUE);
Packit d7e8d0
      if (value)
Packit d7e8d0
	llass->lc_messages_set = 1;
Packit d7e8d0
    }
Packit d7e8d0
#endif /* LC_MESSAGES */
Packit d7e8d0
  else
Packit d7e8d0
    return gpg_error (GPG_ERR_INV_VALUE);
Packit d7e8d0
Packit d7e8d0
  /* FIXME: Reset value to default.  */
Packit d7e8d0
  if (!value)
Packit d7e8d0
    return 0;
Packit d7e8d0
Packit d7e8d0
  if (gpgrt_asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0)
Packit d7e8d0
    err = gpg_error_from_syserror ();
Packit d7e8d0
  else
Packit d7e8d0
    {
Packit d7e8d0
      err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL,
Packit d7e8d0
			     NULL, NULL, NULL, NULL);
Packit d7e8d0
      gpgrt_free (optstr);
Packit d7e8d0
    }
Packit d7e8d0
  return err;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
/* This is the inquiry callback.  It handles stuff which ee need to
Packit d7e8d0
   handle here and passes everything on to the user callback.  */
Packit d7e8d0
static gpgme_error_t
Packit d7e8d0
inquire_cb (engine_llass_t llass, const char *keyword, const char *args)
Packit d7e8d0
{
Packit d7e8d0
  gpg_error_t err;
Packit d7e8d0
Packit d7e8d0
  if (llass->opt.gpg_agent && !strcmp (keyword, "PINENTRY_LAUNCHED"))
Packit d7e8d0
    {
Packit d7e8d0
      _gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10));
Packit d7e8d0
    }
Packit d7e8d0
Packit d7e8d0
  if (llass->user.inq_cb)
Packit d7e8d0
    {
Packit d7e8d0
      gpgme_data_t data = NULL;
Packit d7e8d0
Packit d7e8d0
      err = llass->user.inq_cb (llass->user.inq_cb_value,
Packit d7e8d0
                                keyword, args, &data);
Packit d7e8d0
      if (!err && data)
Packit d7e8d0
        {
Packit d7e8d0
          /* FIXME: Returning data is not yet implemented.  However we
Packit d7e8d0
             need to allow the caller to cleanup his data object.
Packit d7e8d0
             Thus we run the callback in finish mode immediately.  */
Packit d7e8d0
          err = llass->user.inq_cb (llass->user.inq_cb_value,
Packit d7e8d0
                                    NULL, NULL, &data);
Packit d7e8d0
        }
Packit d7e8d0
    }
Packit d7e8d0
  else
Packit d7e8d0
    err = 0;
Packit d7e8d0
Packit d7e8d0
  return err;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
static gpgme_error_t
Packit d7e8d0
llass_status_handler (void *opaque, int fd)
Packit d7e8d0
{
Packit d7e8d0
  struct io_cb_data *data = (struct io_cb_data *) opaque;
Packit d7e8d0
  engine_llass_t llass = (engine_llass_t) data->handler_value;
Packit d7e8d0
  gpgme_error_t err = 0;
Packit d7e8d0
  char *line;
Packit d7e8d0
  size_t linelen;
Packit d7e8d0
Packit d7e8d0
  do
Packit d7e8d0
    {
Packit d7e8d0
      err = assuan_read_line (llass->assuan_ctx, &line, &linelen);
Packit d7e8d0
      if (err)
Packit d7e8d0
	{
Packit d7e8d0
	  /* Reading a full line may not be possible when
Packit d7e8d0
	     communicating over a socket in nonblocking mode.  In this
Packit d7e8d0
	     case, we are done for now.  */
Packit d7e8d0
	  if (gpg_err_code (err) == GPG_ERR_EAGAIN)
Packit d7e8d0
	    {
Packit d7e8d0
	      TRACE1 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
Packit d7e8d0
		      "fd 0x%x: EAGAIN reading assuan line (ignored)", fd);
Packit d7e8d0
	      err = 0;
Packit d7e8d0
	      continue;
Packit d7e8d0
	    }
Packit d7e8d0
Packit d7e8d0
	  TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
Packit d7e8d0
		  "fd 0x%x: error reading assuan line: %s",
Packit d7e8d0
                  fd, gpg_strerror (err));
Packit d7e8d0
	}
Packit d7e8d0
      else if (linelen >= 2 && line[0] == 'D' && line[1] == ' ')
Packit d7e8d0
        {
Packit d7e8d0
          char *src = line + 2;
Packit d7e8d0
	  char *end = line + linelen;
Packit d7e8d0
	  char *dst = src;
Packit d7e8d0
Packit d7e8d0
          linelen = 0;
Packit d7e8d0
          while (src < end)
Packit d7e8d0
            {
Packit d7e8d0
              if (*src == '%' && src + 2 < end)
Packit d7e8d0
                {
Packit d7e8d0
                  /* Handle escaped characters.  */
Packit d7e8d0
                  ++src;
Packit d7e8d0
                  *dst++ = _gpgme_hextobyte (src);
Packit d7e8d0
                  src += 2;
Packit d7e8d0
                }
Packit d7e8d0
              else
Packit d7e8d0
                *dst++ = *src++;
Packit d7e8d0
Packit d7e8d0
              linelen++;
Packit d7e8d0
            }
Packit d7e8d0
Packit d7e8d0
          src = line + 2;
Packit d7e8d0
          if (linelen && llass->user.data_cb)
Packit d7e8d0
            err = llass->user.data_cb (llass->user.data_cb_value,
Packit d7e8d0
                                       src, linelen);
Packit d7e8d0
Packit d7e8d0
          TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
Packit d7e8d0
		  "fd 0x%x: D inlinedata; status from cb: %s",
Packit d7e8d0
                  fd, (llass->user.data_cb ?
Packit d7e8d0
                       (err? gpg_strerror (err):"ok"):"no callback"));
Packit d7e8d0
        }
Packit d7e8d0
      else if (linelen >= 3
Packit d7e8d0
               && line[0] == 'E' && line[1] == 'N' && line[2] == 'D'
Packit d7e8d0
               && (line[3] == '\0' || line[3] == ' '))
Packit d7e8d0
        {
Packit d7e8d0
          /* END received.  Tell the data callback.  */
Packit d7e8d0
          if (llass->user.data_cb)
Packit d7e8d0
            err = llass->user.data_cb (llass->user.data_cb_value, NULL, 0);
Packit d7e8d0
Packit d7e8d0
          TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
Packit d7e8d0
		  "fd 0x%x: END line; status from cb: %s",
Packit d7e8d0
                  fd, (llass->user.data_cb ?
Packit d7e8d0
                       (err? gpg_strerror (err):"ok"):"no callback"));
Packit d7e8d0
        }
Packit d7e8d0
      else if (linelen > 2 && line[0] == 'S' && line[1] == ' ')
Packit d7e8d0
	{
Packit d7e8d0
	  char *args;
Packit d7e8d0
          char *src;
Packit d7e8d0
Packit d7e8d0
          for (src=line+2; *src == ' '; src++)
Packit d7e8d0
            ;
Packit d7e8d0
Packit d7e8d0
	  args = strchr (src, ' ');
Packit d7e8d0
	  if (!args)
Packit d7e8d0
	    args = line + linelen; /* Let it point to an empty string.  */
Packit d7e8d0
	  else
Packit d7e8d0
	    *(args++) = 0;
Packit d7e8d0
Packit d7e8d0
          while (*args == ' ')
Packit d7e8d0
            args++;
Packit d7e8d0
Packit d7e8d0
          if (llass->user.status_cb)
Packit d7e8d0
            err = llass->user.status_cb (llass->user.status_cb_value,
Packit d7e8d0
                                         src, args);
Packit d7e8d0
Packit d7e8d0
          TRACE3 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
Packit d7e8d0
		  "fd 0x%x: S line (%s) - status from cb: %s",
Packit d7e8d0
                  fd, line+2, (llass->user.status_cb ?
Packit d7e8d0
                               (err? gpg_strerror (err):"ok"):"no callback"));
Packit d7e8d0
	}
Packit d7e8d0
      else if (linelen >= 7
Packit d7e8d0
               && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
Packit d7e8d0
               && line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
Packit d7e8d0
               && line[6] == 'E'
Packit d7e8d0
               && (line[7] == '\0' || line[7] == ' '))
Packit d7e8d0
        {
Packit d7e8d0
          char *src;
Packit d7e8d0
	  char *args;
Packit d7e8d0
Packit d7e8d0
          for (src=line+7; *src == ' '; src++)
Packit d7e8d0
            ;
Packit d7e8d0
Packit d7e8d0
	  args = strchr (src, ' ');
Packit d7e8d0
	  if (!args)
Packit d7e8d0
	    args = line + linelen; /* Let it point to an empty string.  */
Packit d7e8d0
	  else
Packit d7e8d0
	    *(args++) = 0;
Packit d7e8d0
Packit d7e8d0
          while (*args == ' ')
Packit d7e8d0
            args++;
Packit d7e8d0
Packit d7e8d0
          err = inquire_cb (llass, src, args);
Packit d7e8d0
          if (!err)
Packit d7e8d0
            {
Packit d7e8d0
              /* Flush and send END.  */
Packit d7e8d0
              err = assuan_send_data (llass->assuan_ctx, NULL, 0);
Packit d7e8d0
            }
Packit d7e8d0
          else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED)
Packit d7e8d0
            {
Packit d7e8d0
              /* Flush and send CANcel.  */
Packit d7e8d0
              err = assuan_send_data (llass->assuan_ctx, NULL, 1);
Packit d7e8d0
            }
Packit d7e8d0
        }
Packit d7e8d0
      else if (linelen >= 3
Packit d7e8d0
	       && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
Packit d7e8d0
	       && (line[3] == '\0' || line[3] == ' '))
Packit d7e8d0
	{
Packit d7e8d0
	  if (line[3] == ' ')
Packit d7e8d0
	    err = atoi (line+4);
Packit d7e8d0
	  else
Packit d7e8d0
	    err = gpg_error (GPG_ERR_GENERAL);
Packit d7e8d0
          TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
Packit d7e8d0
		  "fd 0x%x: ERR line: %s",
Packit d7e8d0
                  fd, err ? gpg_strerror (err) : "ok");
Packit d7e8d0
Packit d7e8d0
	  /* Command execution errors are not fatal, as we use
Packit d7e8d0
	     a session based protocol.  */
Packit d7e8d0
	  data->op_err = err;
Packit d7e8d0
	  llass->last_op_err = err;
Packit d7e8d0
Packit d7e8d0
	  /* The caller will do the rest (namely, call cancel_op,
Packit d7e8d0
	     which closes status_fd).  */
Packit d7e8d0
	  return 0;
Packit d7e8d0
	}
Packit d7e8d0
      else if (linelen >= 2
Packit d7e8d0
	       && line[0] == 'O' && line[1] == 'K'
Packit d7e8d0
	       && (line[2] == '\0' || line[2] == ' '))
Packit d7e8d0
	{
Packit d7e8d0
          TRACE1 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
Packit d7e8d0
		  "fd 0x%x: OK line", fd);
Packit d7e8d0
Packit d7e8d0
	  llass->last_op_err = 0;
Packit d7e8d0
Packit d7e8d0
	  _gpgme_io_close (llass->status_cb.fd);
Packit d7e8d0
	  return 0;
Packit d7e8d0
	}
Packit d7e8d0
      else
Packit d7e8d0
        {
Packit d7e8d0
          /* Comment line or invalid line.  */
Packit d7e8d0
        }
Packit d7e8d0
Packit d7e8d0
    }
Packit d7e8d0
  while (!err && assuan_pending_line (llass->assuan_ctx));
Packit d7e8d0
Packit d7e8d0
  return err;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
static gpgme_error_t
Packit d7e8d0
add_io_cb (engine_llass_t llass, iocb_data_t *iocbd, gpgme_io_cb_t handler)
Packit d7e8d0
{
Packit d7e8d0
  gpgme_error_t err;
Packit d7e8d0
Packit d7e8d0
  TRACE_BEG2 (DEBUG_ENGINE, "engine-assuan:add_io_cb", llass,
Packit d7e8d0
              "fd %d, dir %d", iocbd->fd, iocbd->dir);
Packit d7e8d0
  err = (*llass->io_cbs.add) (llass->io_cbs.add_priv,
Packit d7e8d0
			      iocbd->fd, iocbd->dir,
Packit d7e8d0
			      handler, iocbd->data, &iocbd->tag);
Packit d7e8d0
  if (err)
Packit d7e8d0
    return TRACE_ERR (err);
Packit d7e8d0
  if (!iocbd->dir)
Packit d7e8d0
    /* FIXME Kludge around poll() problem.  */
Packit d7e8d0
    err = _gpgme_io_set_nonblocking (iocbd->fd);
Packit d7e8d0
  return TRACE_ERR (err);
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
static gpgme_error_t
Packit d7e8d0
start (engine_llass_t llass, const char *command)
Packit d7e8d0
{
Packit d7e8d0
  gpgme_error_t err;
Packit d7e8d0
  assuan_fd_t afdlist[5];
Packit d7e8d0
  int fdlist[5];
Packit d7e8d0
  int nfds;
Packit d7e8d0
  int i;
Packit d7e8d0
Packit d7e8d0
  /* We need to know the fd used by assuan for reads.  We do this by
Packit d7e8d0
     using the assumption that the first returned fd from
Packit d7e8d0
     assuan_get_active_fds() is always this one.  */
Packit d7e8d0
  nfds = assuan_get_active_fds (llass->assuan_ctx, 0 /* read fds */,
Packit d7e8d0
                                afdlist, DIM (afdlist));
Packit d7e8d0
  if (nfds < 1)
Packit d7e8d0
    return gpg_error (GPG_ERR_GENERAL);	/* FIXME */
Packit d7e8d0
  /* For now... */
Packit d7e8d0
  for (i = 0; i < nfds; i++)
Packit d7e8d0
    fdlist[i] = (int) afdlist[i];
Packit d7e8d0
Packit d7e8d0
  /* We "duplicate" the file descriptor, so we can close it here (we
Packit d7e8d0
     can't close fdlist[0], as that is closed by libassuan, and
Packit d7e8d0
     closing it here might cause libassuan to close some unrelated FD
Packit d7e8d0
     later).  Alternatively, we could special case status_fd and
Packit d7e8d0
     register/unregister it manually as needed, but this increases
Packit d7e8d0
     code duplication and is more complicated as we can not use the
Packit d7e8d0
     close notifications etc.  A third alternative would be to let
Packit d7e8d0
     Assuan know that we closed the FD, but that complicates the
Packit d7e8d0
     Assuan interface.  */
Packit d7e8d0
Packit d7e8d0
  llass->status_cb.fd = _gpgme_io_dup (fdlist[0]);
Packit d7e8d0
  if (llass->status_cb.fd < 0)
Packit d7e8d0
    return gpg_error_from_syserror ();
Packit d7e8d0
Packit d7e8d0
  if (_gpgme_io_set_close_notify (llass->status_cb.fd,
Packit d7e8d0
				  close_notify_handler, llass))
Packit d7e8d0
    {
Packit d7e8d0
      _gpgme_io_close (llass->status_cb.fd);
Packit d7e8d0
      llass->status_cb.fd = -1;
Packit d7e8d0
      return gpg_error (GPG_ERR_GENERAL);
Packit d7e8d0
    }
Packit d7e8d0
Packit d7e8d0
  err = add_io_cb (llass, &llass->status_cb, llass_status_handler);
Packit d7e8d0
  if (!err)
Packit d7e8d0
    err = assuan_write_line (llass->assuan_ctx, command);
Packit d7e8d0
Packit d7e8d0
  /* FIXME: If *command == '#' no answer is expected.  */
Packit d7e8d0
Packit d7e8d0
  if (!err)
Packit d7e8d0
    llass_io_event (llass, GPGME_EVENT_START, NULL);
Packit d7e8d0
Packit d7e8d0
  return err;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
static gpgme_error_t
Packit d7e8d0
llass_transact (void *engine,
Packit d7e8d0
                const char *command,
Packit d7e8d0
                gpgme_assuan_data_cb_t data_cb,
Packit d7e8d0
                void *data_cb_value,
Packit d7e8d0
                gpgme_assuan_inquire_cb_t inq_cb,
Packit d7e8d0
                void *inq_cb_value,
Packit d7e8d0
                gpgme_assuan_status_cb_t status_cb,
Packit d7e8d0
                void *status_cb_value)
Packit d7e8d0
{
Packit d7e8d0
  engine_llass_t llass = engine;
Packit d7e8d0
  gpgme_error_t err;
Packit d7e8d0
Packit d7e8d0
  if (!llass || !command || !*command)
Packit d7e8d0
    return gpg_error (GPG_ERR_INV_VALUE);
Packit d7e8d0
Packit d7e8d0
  llass->user.data_cb = data_cb;
Packit d7e8d0
  llass->user.data_cb_value = data_cb_value;
Packit d7e8d0
  llass->user.inq_cb = inq_cb;
Packit d7e8d0
  llass->user.inq_cb_value = inq_cb_value;
Packit d7e8d0
  llass->user.status_cb = status_cb;
Packit d7e8d0
  llass->user.status_cb_value = status_cb_value;
Packit d7e8d0
Packit d7e8d0
  err = start (llass, command);
Packit d7e8d0
  return err;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
static void
Packit d7e8d0
llass_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
Packit d7e8d0
{
Packit d7e8d0
  engine_llass_t llass = engine;
Packit d7e8d0
  llass->io_cbs = *io_cbs;
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
static void
Packit d7e8d0
llass_io_event (void *engine, gpgme_event_io_t type, void *type_data)
Packit d7e8d0
{
Packit d7e8d0
  engine_llass_t llass = engine;
Packit d7e8d0
Packit d7e8d0
  TRACE3 (DEBUG_ENGINE, "gpgme:llass_io_event", llass,
Packit d7e8d0
          "event %p, type %d, type_data %p",
Packit d7e8d0
          llass->io_cbs.event, type, type_data);
Packit d7e8d0
  if (llass->io_cbs.event)
Packit d7e8d0
    (*llass->io_cbs.event) (llass->io_cbs.event_priv, type, type_data);
Packit d7e8d0
}
Packit d7e8d0
Packit d7e8d0
Packit d7e8d0
struct engine_ops _gpgme_engine_ops_assuan =
Packit d7e8d0
  {
Packit d7e8d0
    /* Static functions.  */
Packit d7e8d0
    _gpgme_get_default_agent_socket,
Packit d7e8d0
    llass_get_home_dir,
Packit d7e8d0
    llass_get_version,
Packit d7e8d0
    llass_get_req_version,
Packit d7e8d0
    llass_new,
Packit d7e8d0
Packit d7e8d0
    /* Member functions.  */
Packit d7e8d0
    llass_release,
Packit d7e8d0
    NULL,		/* reset */
Packit d7e8d0
    NULL,               /* set_status_cb */
Packit d7e8d0
    NULL,               /* set_status_handler */
Packit d7e8d0
    NULL,		/* set_command_handler */
Packit d7e8d0
    NULL,               /* set_colon_line_handler */
Packit d7e8d0
    llass_set_locale,
Packit d7e8d0
    NULL,		/* set_protocol */
Packit d7e8d0
    NULL,               /* decrypt */
Packit d7e8d0
    NULL,               /* delete */
Packit d7e8d0
    NULL,		/* edit */
Packit d7e8d0
    NULL,               /* encrypt */
Packit d7e8d0
    NULL,		/* encrypt_sign */
Packit d7e8d0
    NULL,               /* export */
Packit d7e8d0
    NULL,               /* export_ext */
Packit d7e8d0
    NULL,               /* genkey */
Packit d7e8d0
    NULL,               /* import */
Packit d7e8d0
    NULL,               /* keylist */
Packit d7e8d0
    NULL,               /* keylist_ext */
Packit d7e8d0
    NULL,               /* keylist_data */
Packit d7e8d0
    NULL,               /* keysign */
Packit d7e8d0
    NULL,               /* tofu_policy */
Packit d7e8d0
    NULL,               /* sign */
Packit d7e8d0
    NULL,		/* trustlist */
Packit d7e8d0
    NULL,               /* verify */
Packit d7e8d0
    NULL,               /* getauditlog */
Packit d7e8d0
    llass_transact,     /* opassuan_transact */
Packit d7e8d0
    NULL,		/* conf_load */
Packit d7e8d0
    NULL,		/* conf_save */
Packit d7e8d0
    NULL,		/* conf_dir */
Packit d7e8d0
    NULL,               /* query_swdb */
Packit d7e8d0
    llass_set_io_cbs,
Packit d7e8d0
    llass_io_event,
Packit d7e8d0
    llass_cancel,
Packit d7e8d0
    llass_cancel_op,
Packit d7e8d0
    NULL,               /* passwd */
Packit d7e8d0
    NULL,               /* set_pinentry_mode */
Packit d7e8d0
    NULL                /* opspawn */
Packit d7e8d0
  };