diff options
| author | Marcus Brinkmann <[email protected]> | 2009-10-22 16:44:07 +0000 | 
|---|---|---|
| committer | Marcus Brinkmann <[email protected]> | 2009-10-22 16:44:07 +0000 | 
| commit | a6f3857128220cc413434d5fb56cdd7f9a890c4c (patch) | |
| tree | 50478606bd9a1f87a61e8df224f8c4260ad2e5a2 /src/engine-g13.c | |
| parent | Really add file. (diff) | |
| download | gpgme-a6f3857128220cc413434d5fb56cdd7f9a890c4c.tar.gz gpgme-a6f3857128220cc413434d5fb56cdd7f9a890c4c.zip | |
2009-10-22  Marcus Brinkmann  <[email protected]>
	* configure.ac: Add support for G13.
src/
2009-10-22  Marcus Brinkmann  <[email protected]>
	* Makefile.am: Remove @NETLIBS@ from LIBADDs.
	(g13_components): New variable.
	(main_sources): Add $(g13_components).
	* g13.c, engine-g13.c: New files.
	* engine.c (engine_ops): Check for assuan for assuan engine, add
	g13 engine.
	* util.h (_gpgme_get_g13_path, _gpgme_encode_percent_string): New
	prototypes.
	* conversion.c (_gpgme_encode_percent_string): New function.
	* gpgme.h.in (gpgme_protocol_t): Add GPGME_PROTOCOL_G13.
	(struct _gpgme_op_g13_result, gpgme_g13_result_t): New types.
	(gpgme_op_g13_mount): New function.
	* gpgme.def, libgpgme.vers: Add gpgme_op_g13_mount.
	* gpgme.c (gpgme_set_protocol): Allow GPGME_PROTOCOL_G13.
	(gpgme_get_protocol_name): Add GPGME_PROTOCOL_G13.
	* posix-util.c (_gpgme_get_g13_path): New function.
	* w32-util.c (_gpgme_get_g13_path): New function.
	* engine-backend.h (_gpgme_engine_ops_g13): New declaration.
Diffstat (limited to '')
| -rw-r--r-- | src/engine-g13.c | 796 | 
1 files changed, 796 insertions, 0 deletions
| diff --git a/src/engine-g13.c b/src/engine-g13.c new file mode 100644 index 00000000..f957691a --- /dev/null +++ b/src/engine-g13.c @@ -0,0 +1,796 @@ +/* engine-g13.c - G13 engine. +   Copyright (C) 2000 Werner Koch (dd9jn) +   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 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.  */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <assert.h> +#include <unistd.h> +#include <locale.h> +#include <fcntl.h> /* FIXME */ +#include <errno.h> + +#include "gpgme.h" +#include "util.h" +#include "ops.h" +#include "wait.h" +#include "priv-io.h" +#include "sema.h" + +#include "assuan.h" +#include "debug.h" + +#include "engine-backend.h" + + +typedef struct +{ +  int fd;	/* FD we talk about.  */ +  int server_fd;/* Server FD for this connection.  */ +  int dir;	/* Inbound/Outbound, maybe given implicit?  */ +  void *data;	/* Handler-specific data.  */ +  void *tag;	/* ID from the user for gpgme_remove_io_callback.  */ +  char server_fd_str[15]; /* Same as SERVER_FD but as a string.  We +                             need this because _gpgme_io_fd2str can't +                             be used on a closed descriptor.  */ +} iocb_data_t; + + +struct engine_g13 +{ +  assuan_context_t assuan_ctx; + +  int lc_ctype_set; +  int lc_messages_set; + +  iocb_data_t status_cb; + +  struct gpgme_io_cbs io_cbs; + +  /* Internal callbacks.  */ +  engine_assuan_result_cb_t result_cb; +  void *result_cb_value;  + +  /* User provided callbacks.  */ +  struct { +    gpgme_assuan_data_cb_t data_cb; +    void *data_cb_value; + +    gpgme_assuan_inquire_cb_t inq_cb; +    void *inq_cb_value; + +    gpgme_assuan_status_cb_t status_cb; +    void *status_cb_value; +  } user; +}; + +typedef struct engine_g13 *engine_g13_t; + + +static void g13_io_event (void *engine,  +                            gpgme_event_io_t type, void *type_data); + + + +static char * +g13_get_version (const char *file_name) +{ +  return _gpgme_get_program_version (file_name ? file_name +				     : _gpgme_get_g13_path ()); +} + + +static const char * +g13_get_req_version (void) +{ +  return NEED_G13_VERSION; +} + + +static void +close_notify_handler (int fd, void *opaque) +{ +  engine_g13_t g13 = opaque; + +  assert (fd != -1); +  if (g13->status_cb.fd == fd) +    { +      if (g13->status_cb.tag) +	(*g13->io_cbs.remove) (g13->status_cb.tag); +      g13->status_cb.fd = -1; +      g13->status_cb.tag = NULL; +    } +} + + +/* This is the default inquiry callback.  We use it to handle the +   Pinentry notifications.  */ +static gpgme_error_t +default_inq_cb (engine_g13_t g13, const char *keyword, const char *args) +{ +  gpg_error_t err; + +  if (!strcmp (keyword, "PINENTRY_LAUNCHED")) +    { +      _gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10)); +    } + +  if (g13->user.inq_cb) +    { +      gpgme_data_t data = NULL; + +      err = g13->user.inq_cb (g13->user.inq_cb_value, +                                keyword, args, &data); +      if (!err && data) +        { +          /* FIXME: Returning data is not yet implemented.  However we +             need to allow the caller to cleanup his data object. +             Thus we run the callback in finish mode immediately.  */ +          err = g13->user.inq_cb (g13->user.inq_cb_value, +				  NULL, NULL, &data); +        } +    } +  else +    err = 0; + +  return err; +} + + +static gpgme_error_t +g13_cancel (void *engine) +{ +  engine_g13_t g13 = engine; + +  if (!g13) +    return gpg_error (GPG_ERR_INV_VALUE); + +  if (g13->status_cb.fd != -1) +    _gpgme_io_close (g13->status_cb.fd); + +  if (g13->assuan_ctx) +    { +      assuan_release (g13->assuan_ctx); +      g13->assuan_ctx = NULL; +    } + +  return 0; +} + + +static void +g13_release (void *engine) +{ +  engine_g13_t g13 = engine; + +  if (!g13) +    return; + +  g13_cancel (engine); + +  free (g13); +} + + +static gpgme_error_t +g13_new (void **engine, const char *file_name, const char *home_dir) +{ +  gpgme_error_t err = 0; +  engine_g13_t g13; +  int argc; +  char *argv[5]; +  char *dft_display = NULL; +  char dft_ttyname[64]; +  char *dft_ttytype = NULL; +  char *optstr; + +  g13 = calloc (1, sizeof *g13); +  if (!g13) +    return gpg_error_from_errno (errno); + +  g13->status_cb.fd = -1; +  g13->status_cb.dir = 1; +  g13->status_cb.tag = 0; +  g13->status_cb.data = g13; + +  err = assuan_new (&g13->assuan_ctx); +  if (err) +    goto leave; + +  argc = 0; +  argv[argc++] = "g13"; +  if (home_dir) +    { +      argv[argc++] = "--homedir"; +      argv[argc++] = home_dir; +    } +  argv[argc++] = "--server"; +  argv[argc++] = NULL; + +  err = assuan_new_ext (&g13->assuan_ctx, GPG_ERR_SOURCE_GPGME, +			&_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb, +			NULL); +  if (err) +    goto leave; +  assuan_ctx_set_system_hooks (g13->assuan_ctx, &_gpgme_assuan_system_hooks); + +#if USE_DESCRIPTOR_PASSING +  err = assuan_pipe_connect_ext +    (g13->assuan_ctx, file_name ? file_name : _gpgme_get_g13_path (), +     argv, NULL, NULL, NULL, 1); +#else +  err = assuan_pipe_connect +    (g13->assuan_ctx, file_name ? file_name : _gpgme_get_g13_path (), +     argv, NULL); +#endif +  if (err) +    goto leave; + +  err = _gpgme_getenv ("DISPLAY", &dft_display); +  if (err) +    goto leave; +  if (dft_display) +    { +      if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0) +        { +	  free (dft_display); +	  err = gpg_error_from_errno (errno); +	  goto leave; +	} +      free (dft_display); + +      err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL, +			     NULL, NULL, NULL); +      free (optstr); +      if (err) +	goto leave; +    } + +  if (isatty (1)) +    { +      int rc; + +      rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname)); +      if (rc) +	{ +	  err = gpg_error_from_errno (rc); +	  goto leave; +	} +      else +	{ +	  if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0) +	    { +	      err = gpg_error_from_errno (errno); +	      goto leave; +	    } +	  err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL, +				 NULL, NULL, NULL); +	  free (optstr); +	  if (err) +	    goto leave; + +	  err = _gpgme_getenv ("TERM", &dft_ttytype); +	  if (err) +	    goto leave; +	  if (dft_ttytype) +	    { +	      if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0) +		{ +		  free (dft_ttytype); +		  err = gpg_error_from_errno (errno); +		  goto leave; +		} +	      free (dft_ttytype); + +	      err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, +				     NULL, NULL, NULL, NULL); +	      free (optstr); +	      if (err) +		goto leave; +	    } +	} +    } + +#ifdef HAVE_W32_SYSTEM +  /* Under Windows we need to use AllowSetForegroundWindow.  Tell +     g13 to tell us when it needs it.  */ +  if (!err) +    { +      err = assuan_transact (g13->assuan_ctx, "OPTION allow-pinentry-notify", +                             NULL, NULL, NULL, NULL, NULL, NULL); +      if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) +        err = 0; /* This is a new feature of g13.  */ +    } +#endif /*HAVE_W32_SYSTEM*/ + + leave: + +  if (err) +    g13_release (g13); +  else +    *engine = g13; + +  return err; +} + + +static gpgme_error_t +g13_set_locale (void *engine, int category, const char *value) +{ +  engine_g13_t g13 = engine; +  gpgme_error_t err; +  char *optstr; +  char *catstr; + +  /* FIXME: If value is NULL, we need to reset the option to default. +     But we can't do this.  So we error out here.  G13 needs support +     for this.  */ +  if (category == LC_CTYPE) +    { +      catstr = "lc-ctype"; +      if (!value && g13->lc_ctype_set) +	return gpg_error (GPG_ERR_INV_VALUE); +      if (value) +	g13->lc_ctype_set = 1; +    } +#ifdef LC_MESSAGES +  else if (category == LC_MESSAGES) +    { +      catstr = "lc-messages"; +      if (!value && g13->lc_messages_set) +	return gpg_error (GPG_ERR_INV_VALUE); +      if (value) +	g13->lc_messages_set = 1; +    } +#endif /* LC_MESSAGES */ +  else +    return gpg_error (GPG_ERR_INV_VALUE); + +  /* FIXME: Reset value to default.  */ +  if (!value)  +    return 0; + +  if (asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0) +    err = gpg_error_from_errno (errno); +  else +    { +      err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, +			     NULL, NULL, NULL, NULL); +      free (optstr); +    } + +  return err; +} + + +/* Forward declaration.  */ +static gpgme_status_code_t parse_status (const char *name); + +static gpgme_error_t +g13_assuan_simple_command (assuan_context_t ctx, char *cmd, +			     engine_status_handler_t status_fnc, +			     void *status_fnc_value) +{ +  gpg_error_t err; +  char *line; +  size_t linelen; + +  err = assuan_write_line (ctx, cmd); +  if (err) +    return err; + +  do +    { +      err = assuan_read_line (ctx, &line, &linelen); +      if (err) +	return err; + +      if (*line == '#' || !linelen) +	continue; + +      if (linelen >= 2 +	  && line[0] == 'O' && line[1] == 'K' +	  && (line[2] == '\0' || line[2] == ' ')) +	return 0; +      else if (linelen >= 4 +	  && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' +	  && line[3] == ' ') +	err = atoi (&line[4]); +      else if (linelen >= 2 +	       && line[0] == 'S' && line[1] == ' ') +	{ +	  char *rest; +	  gpgme_status_code_t r; + +	  rest = strchr (line + 2, ' '); +	  if (!rest) +	    rest = line + linelen; /* set to an empty string */ +	  else +	    *(rest++) = 0; + +	  r = parse_status (line + 2); + +	  if (r >= 0 && status_fnc) +	    err = status_fnc (status_fnc_value, r, rest); +	  else +	    err = gpg_error (GPG_ERR_GENERAL); +	} +      else +	err = gpg_error (GPG_ERR_GENERAL); +    } +  while (!err); + +  return err; +} + + +static gpgme_error_t +status_handler (void *opaque, int fd) +{ +  gpgme_error_t err = 0; +  engine_g13_t g13 = opaque; +  char *line; +  size_t linelen; + +  do +    { +      err = assuan_read_line (g13->assuan_ctx, &line, &linelen); +      if (err) +	{ +	  /* Try our best to terminate the connection friendly.  */ +	  /*	  assuan_write_line (g13->assuan_ctx, "BYE"); */ +          TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13, +		  "fd 0x%x: error reading assuan line: %s", +                  fd, gpg_strerror (err)); +	} +      else if (linelen >= 3 +	       && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' +	       && (line[3] == '\0' || line[3] == ' ')) +	{ +	  if (line[3] == ' ') +	    err = atoi (&line[4]); +	  if (! err) +	    err = gpg_error (GPG_ERR_GENERAL); +          TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13, +		  "fd 0x%x: ERR line: %s", +                  fd, err ? gpg_strerror (err) : "ok"); +	   +	  /* In g13, command execution errors are not fatal, as we use +	     a session based protocol.  */ +          if (g13->result_cb) +            err = g13->result_cb (g13->result_cb_value, err); +          else +            err = 0; +          if (!err) +            { +              _gpgme_io_close (g13->status_cb.fd); +              return 0; +	    } +	} +      else if (linelen >= 2 +	       && line[0] == 'O' && line[1] == 'K' +	       && (line[2] == '\0' || line[2] == ' ')) +	{ +          TRACE1 (DEBUG_CTX, "gpgme:status_handler", g13, +		  "fd 0x%x: OK line", fd); +          if (g13->result_cb) +            err = g13->result_cb (g13->result_cb_value, 0); +          else +            err = 0; +	  if (!err) +            { +              _gpgme_io_close (g13->status_cb.fd); +              return 0; +            } +	} +      else if (linelen > 2 +	       && line[0] == 'D' && line[1] == ' ') +        { +	  /* We are using the colon handler even for plain inline data +             - strange name for that function but for historic reasons +             we keep it.  */ +          /* FIXME We can't use this for binary data because we +             assume this is a string.  For the current usage of colon +             output it is correct.  */ +          char *src = line + 2; +	  char *end = line + linelen; +	  char *dst = src; + +          linelen = 0; +          while (src < end) +            { +              if (*src == '%' && src + 2 < end) +                { +                  /* Handle escaped characters.  */ +                  ++src; +                  *dst++ = _gpgme_hextobyte (src); +                  src += 2; +                } +              else +                *dst++ = *src++; + +              linelen++; +            } + +          src = line + 2; +          if (linelen && g13->user.data_cb) +            err = g13->user.data_cb (g13->user.data_cb_value, +                                       src, linelen); +          else +            err = 0; + +          TRACE2 (DEBUG_CTX, "gpgme:g13_status_handler", g13, +		  "fd 0x%x: D inlinedata; status from cb: %s", +                  fd, (g13->user.data_cb ? +                       (err? gpg_strerror (err):"ok"):"no callback")); + +        } +      else if (linelen > 2 +	       && line[0] == 'S' && line[1] == ' ') +	{ +	  char *src; +	  char *args; + +	  src = line + 2; +          while (*src == ' ') +            src++; +	   +	  args = strchr (line + 2, ' '); +	  if (!args) +	    args = line + linelen; /* set to an empty string */ +	  else +	    *(args++) = 0; + +          while (*args == ' ') +            args++; + +          if (g13->user.status_cb) +            err = g13->user.status_cb (g13->user.status_cb_value, +				       src, args); +          else +            err = 0; + +          TRACE3 (DEBUG_CTX, "gpgme:g13_status_handler", g13, +		  "fd 0x%x: S line (%s) - status from cb: %s", +                  fd, line+2, (g13->user.status_cb ? +                               (err? gpg_strerror (err):"ok"):"no callback")); +	} +      else if (linelen >= 7 +               && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' +               && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' +               && line[6] == 'E'  +               && (line[7] == '\0' || line[7] == ' ')) +        { +          char *src; +	  char *args; +	   +          for (src=line+7; *src == ' '; src++) +            ; + +	  args = strchr (src, ' '); +	  if (!args) +	    args = line + linelen; /* Let it point to an empty string.  */ +	  else +	    *(args++) = 0; + +          while (*args == ' ') +            args++; + +          err = default_inq_cb (g13, src, args); +          if (!err)  +            { +              /* Flush and send END.  */ +              err = assuan_send_data (g13->assuan_ctx, NULL, 0); +            } +          else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED) +            { +              /* Flush and send CANcel.  */ +              err = assuan_send_data (g13->assuan_ctx, NULL, 1); +            } +          assuan_write_line (g13->assuan_ctx, "END"); +        } +    } +  while (!err && assuan_pending_line (g13->assuan_ctx)); +   +  return err; +} +   + +static gpgme_error_t +add_io_cb (engine_g13_t g13, iocb_data_t *iocbd, gpgme_io_cb_t handler) +{ +  gpgme_error_t err; + +  TRACE_BEG2 (DEBUG_ENGINE, "engine-g13:add_io_cb", g13, +              "fd %d, dir %d", iocbd->fd, iocbd->dir); +  err = (*g13->io_cbs.add) (g13->io_cbs.add_priv, +			      iocbd->fd, iocbd->dir, +			      handler, iocbd->data, &iocbd->tag); +  if (err) +    return TRACE_ERR (err); +  if (!iocbd->dir) +    /* FIXME Kludge around poll() problem.  */ +    err = _gpgme_io_set_nonblocking (iocbd->fd); +  return TRACE_ERR (err); +} + + +static gpgme_error_t +start (engine_g13_t g13, const char *command) +{ +  gpgme_error_t err; +  int fdlist[5]; +  int nfds; + +  /* We need to know the fd used by assuan for reads.  We do this by +     using the assumption that the first returned fd from +     assuan_get_active_fds() is always this one.  */ +  nfds = assuan_get_active_fds (g13->assuan_ctx, 0 /* read fds */, +                                fdlist, DIM (fdlist)); +  if (nfds < 1) +    return gpg_error (GPG_ERR_GENERAL);	/* FIXME */ + +  /* We "duplicate" the file descriptor, so we can close it here (we +     can't close fdlist[0], as that is closed by libassuan, and +     closing it here might cause libassuan to close some unrelated FD +     later).  Alternatively, we could special case status_fd and +     register/unregister it manually as needed, but this increases +     code duplication and is more complicated as we can not use the +     close notifications etc.  A third alternative would be to let +     Assuan know that we closed the FD, but that complicates the +     Assuan interface.  */ + +  g13->status_cb.fd = _gpgme_io_dup (fdlist[0]); +  if (g13->status_cb.fd < 0) +    return gpg_error_from_syserror (); + +  if (_gpgme_io_set_close_notify (g13->status_cb.fd, +				  close_notify_handler, g13)) +    { +      _gpgme_io_close (g13->status_cb.fd); +      g13->status_cb.fd = -1; +      return gpg_error (GPG_ERR_GENERAL); +    } + +  err = add_io_cb (g13, &g13->status_cb, status_handler); +  if (!err) +    err = assuan_write_line (g13->assuan_ctx, command); + +  if (!err) +    g13_io_event (g13, GPGME_EVENT_START, NULL); + +  return err; +} + + +#if USE_DESCRIPTOR_PASSING +static gpgme_error_t +g13_reset (void *engine) +{ +  engine_g13_t g13 = engine; + +  /* We must send a reset because we need to reset the list of +     signers.  Note that RESET does not reset OPTION commands. */ +  return g13_assuan_simple_command (g13->assuan_ctx, "RESET", NULL, NULL); +} +#endif + + +static gpgme_error_t +g13_transact (void *engine, +                const char *command, +                engine_assuan_result_cb_t result_cb, +                void *result_cb_value, +                gpgme_assuan_data_cb_t data_cb, +                void *data_cb_value, +                gpgme_assuan_inquire_cb_t inq_cb, +                void *inq_cb_value, +                gpgme_assuan_status_cb_t status_cb, +                void *status_cb_value) +{ +  engine_g13_t g13 = engine; +  gpgme_error_t err; + +  if (!g13 || !command || !*command) +    return gpg_error (GPG_ERR_INV_VALUE); + +  g13->result_cb = result_cb; +  g13->result_cb_value = result_cb_value; +  g13->user.data_cb = data_cb; +  g13->user.data_cb_value = data_cb_value; +  g13->user.inq_cb = inq_cb; +  g13->user.inq_cb_value = inq_cb_value; +  g13->user.status_cb = status_cb; +  g13->user.status_cb_value = status_cb_value; + +  err = start (g13, command); +  return err; +} + + + +static void +g13_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) +{ +  engine_g13_t g13 = engine; +  g13->io_cbs = *io_cbs; +} + + +static void +g13_io_event (void *engine, gpgme_event_io_t type, void *type_data) +{ +  engine_g13_t g13 = engine; + +  TRACE3 (DEBUG_ENGINE, "gpgme:g13_io_event", g13, +          "event %p, type %d, type_data %p", +          g13->io_cbs.event, type, type_data); +  if (g13->io_cbs.event) +    (*g13->io_cbs.event) (g13->io_cbs.event_priv, type, type_data); +} + + +struct engine_ops _gpgme_engine_ops_g13 = +  { +    /* Static functions.  */ +    _gpgme_get_g13_path, +    NULL, +    g13_get_version, +    g13_get_req_version, +    g13_new, + +    /* Member functions.  */ +    g13_release, +#if USE_DESCRIPTOR_PASSING +    g13_reset, +#else +    NULL,			/* reset */ +#endif +    NULL,               /* set_status_handler */ +    NULL,		/* set_command_handler */ +    NULL,               /* set_colon_line_handler */ +    g13_set_locale, +    NULL,               /* decrypt */ +    NULL,               /* delete */ +    NULL,		/* edit */ +    NULL,               /* encrypt */ +    NULL,		/* encrypt_sign */ +    NULL,               /* export */ +    NULL,               /* export_ext */ +    NULL,               /* genkey */ +    NULL,               /* import */ +    NULL,               /* keylist */ +    NULL,               /* keylist_ext */ +    NULL,               /* sign */ +    NULL,		/* trustlist */ +    NULL,               /* verify */ +    NULL,               /* getauditlog */ +    g13_transact, +    NULL,		/* conf_load */ +    NULL,		/* conf_save */ +    g13_set_io_cbs, +    g13_io_event, +    g13_cancel +  }; | 
