Merge branch 'master' into justus/pyme3
[gpgme.git] / src / engine-g13.c
1 /* engine-g13.c - G13 engine.
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 g10 Code GmbH
4
5    This file is part of GPGME.
6
7    GPGME is free software; you can redistribute it and/or modify it
8    under the terms of the GNU Lesser General Public License as
9    published by the Free Software Foundation; either version 2.1 of
10    the License, or (at your option) any later version.
11
12    GPGME is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Lesser General Public License for more details.
16
17    You should have received a copy of the GNU Lesser General Public
18    License along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20    02111-1307, USA.  */
21
22 #if HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdlib.h>
27 #include <string.h>
28 #ifdef HAVE_SYS_TYPES_H
29 # include <sys/types.h>
30 #endif
31 #include <assert.h>
32 #ifdef HAVE_UNISTD_H
33 # include <unistd.h>
34 #endif
35 #ifdef HAVE_LOCALE_H
36 #include <locale.h>
37 #endif
38 #include <fcntl.h> /* FIXME */
39 #include <errno.h>
40
41 #include "gpgme.h"
42 #include "util.h"
43 #include "ops.h"
44 #include "wait.h"
45 #include "priv-io.h"
46 #include "sema.h"
47
48 #include "assuan.h"
49 #include "debug.h"
50
51 #include "engine-backend.h"
52
53 \f
54 typedef struct
55 {
56   int fd;       /* FD we talk about.  */
57   int server_fd;/* Server FD for this connection.  */
58   int dir;      /* Inbound/Outbound, maybe given implicit?  */
59   void *data;   /* Handler-specific data.  */
60   void *tag;    /* ID from the user for gpgme_remove_io_callback.  */
61   char server_fd_str[15]; /* Same as SERVER_FD but as a string.  We
62                              need this because _gpgme_io_fd2str can't
63                              be used on a closed descriptor.  */
64 } iocb_data_t;
65
66
67 struct engine_g13
68 {
69   assuan_context_t assuan_ctx;
70
71   int lc_ctype_set;
72   int lc_messages_set;
73
74   iocb_data_t status_cb;
75
76   struct gpgme_io_cbs io_cbs;
77
78   /* User provided callbacks.  */
79   struct {
80     gpgme_assuan_data_cb_t data_cb;
81     void *data_cb_value;
82
83     gpgme_assuan_inquire_cb_t inq_cb;
84     void *inq_cb_value;
85
86     gpgme_assuan_status_cb_t status_cb;
87     void *status_cb_value;
88   } user;
89 };
90
91 typedef struct engine_g13 *engine_g13_t;
92
93
94 static void g13_io_event (void *engine,
95                             gpgme_event_io_t type, void *type_data);
96
97
98 \f
99 static char *
100 g13_get_version (const char *file_name)
101 {
102   return _gpgme_get_program_version (file_name ? file_name
103                                      : _gpgme_get_default_g13_name ());
104 }
105
106
107 static const char *
108 g13_get_req_version (void)
109 {
110   return "2.1.0";
111 }
112
113 \f
114 static void
115 close_notify_handler (int fd, void *opaque)
116 {
117   engine_g13_t g13 = opaque;
118
119   assert (fd != -1);
120   if (g13->status_cb.fd == fd)
121     {
122       if (g13->status_cb.tag)
123         (*g13->io_cbs.remove) (g13->status_cb.tag);
124       g13->status_cb.fd = -1;
125       g13->status_cb.tag = NULL;
126     }
127 }
128
129
130 /* This is the default inquiry callback.  We use it to handle the
131    Pinentry notifications.  */
132 static gpgme_error_t
133 default_inq_cb (engine_g13_t g13, const char *keyword, const char *args)
134 {
135   gpg_error_t err;
136
137   if (!strcmp (keyword, "PINENTRY_LAUNCHED"))
138     {
139       _gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10));
140     }
141
142   if (g13->user.inq_cb)
143     {
144       gpgme_data_t data = NULL;
145
146       err = g13->user.inq_cb (g13->user.inq_cb_value,
147                                 keyword, args, &data);
148       if (!err && data)
149         {
150           /* FIXME: Returning data is not yet implemented.  However we
151              need to allow the caller to cleanup his data object.
152              Thus we run the callback in finish mode immediately.  */
153           err = g13->user.inq_cb (g13->user.inq_cb_value,
154                                   NULL, NULL, &data);
155         }
156     }
157   else
158     err = 0;
159
160   return err;
161 }
162
163
164 static gpgme_error_t
165 g13_cancel (void *engine)
166 {
167   engine_g13_t g13 = engine;
168
169   if (!g13)
170     return gpg_error (GPG_ERR_INV_VALUE);
171
172   if (g13->status_cb.fd != -1)
173     _gpgme_io_close (g13->status_cb.fd);
174
175   if (g13->assuan_ctx)
176     {
177       assuan_release (g13->assuan_ctx);
178       g13->assuan_ctx = NULL;
179     }
180
181   return 0;
182 }
183
184
185 static gpgme_error_t
186 g13_cancel_op (void *engine)
187 {
188   engine_g13_t g13 = engine;
189
190   if (!g13)
191     return gpg_error (GPG_ERR_INV_VALUE);
192
193   if (g13->status_cb.fd != -1)
194     _gpgme_io_close (g13->status_cb.fd);
195
196   return 0;
197 }
198
199
200 static void
201 g13_release (void *engine)
202 {
203   engine_g13_t g13 = engine;
204
205   if (!g13)
206     return;
207
208   g13_cancel (engine);
209
210   free (g13);
211 }
212
213
214 static gpgme_error_t
215 g13_new (void **engine, const char *file_name, const char *home_dir)
216 {
217   gpgme_error_t err = 0;
218   engine_g13_t g13;
219   const char *pgmname;
220   int argc;
221   const char *argv[5];
222   char *dft_display = NULL;
223   char dft_ttyname[64];
224   char *dft_ttytype = NULL;
225   char *optstr;
226
227   g13 = calloc (1, sizeof *g13);
228   if (!g13)
229     return gpg_error_from_syserror ();
230
231   g13->status_cb.fd = -1;
232   g13->status_cb.dir = 1;
233   g13->status_cb.tag = 0;
234   g13->status_cb.data = g13;
235
236   pgmname = file_name ? file_name : _gpgme_get_default_g13_name ();
237   argc = 0;
238   argv[argc++] = _gpgme_get_basename (pgmname);
239   if (home_dir)
240     {
241       argv[argc++] = "--homedir";
242       argv[argc++] = home_dir;
243     }
244   argv[argc++] = "--server";
245   argv[argc++] = NULL;
246
247   err = assuan_new_ext (&g13->assuan_ctx, GPG_ERR_SOURCE_GPGME,
248                         &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb,
249                         NULL);
250   if (err)
251     goto leave;
252   assuan_ctx_set_system_hooks (g13->assuan_ctx, &_gpgme_assuan_system_hooks);
253
254 #if USE_DESCRIPTOR_PASSING
255   err = assuan_pipe_connect (g13->assuan_ctx, pgmname, argv,
256                              NULL, NULL, NULL, ASSUAN_PIPE_CONNECT_FDPASSING);
257 #else
258   err = assuan_pipe_connect (g13->assuan_ctx, pgmname, argv,
259                              NULL, NULL, NULL, 0);
260 #endif
261   if (err)
262     goto leave;
263
264   err = _gpgme_getenv ("DISPLAY", &dft_display);
265   if (err)
266     goto leave;
267   if (dft_display)
268     {
269       if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
270         {
271           free (dft_display);
272           err = gpg_error_from_syserror ();
273           goto leave;
274         }
275       free (dft_display);
276
277       err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL,
278                              NULL, NULL, NULL);
279       free (optstr);
280       if (err)
281         goto leave;
282     }
283
284   if (isatty (1))
285     {
286       int rc;
287
288       rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
289
290       /* Even though isatty() returns 1, ttyname_r() may fail in many
291          ways, e.g., when /dev/pts is not accessible under chroot.  */
292       if (!rc)
293         {
294           if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
295             {
296               err = gpg_error_from_syserror ();
297               goto leave;
298             }
299           err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL,
300                                  NULL, NULL, NULL);
301           free (optstr);
302           if (err)
303             goto leave;
304
305           err = _gpgme_getenv ("TERM", &dft_ttytype);
306           if (err)
307             goto leave;
308           if (dft_ttytype)
309             {
310               if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0)
311                 {
312                   free (dft_ttytype);
313                   err = gpg_error_from_syserror ();
314                   goto leave;
315                 }
316               free (dft_ttytype);
317
318               err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL,
319                                      NULL, NULL, NULL, NULL);
320               free (optstr);
321               if (err)
322                 goto leave;
323             }
324         }
325     }
326
327 #ifdef HAVE_W32_SYSTEM
328   /* Under Windows we need to use AllowSetForegroundWindow.  Tell
329      g13 to tell us when it needs it.  */
330   if (!err)
331     {
332       err = assuan_transact (g13->assuan_ctx, "OPTION allow-pinentry-notify",
333                              NULL, NULL, NULL, NULL, NULL, NULL);
334       if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
335         err = 0; /* This is a new feature of g13.  */
336     }
337 #endif /*HAVE_W32_SYSTEM*/
338
339  leave:
340
341   if (err)
342     g13_release (g13);
343   else
344     *engine = g13;
345
346   return err;
347 }
348
349
350 static gpgme_error_t
351 g13_set_locale (void *engine, int category, const char *value)
352 {
353   engine_g13_t g13 = engine;
354   gpgme_error_t err;
355   char *optstr;
356   char *catstr;
357
358   /* FIXME: If value is NULL, we need to reset the option to default.
359      But we can't do this.  So we error out here.  G13 needs support
360      for this.  */
361   if (0)
362     ;
363 #ifdef LC_CTYPE
364   else if (category == LC_CTYPE)
365     {
366       catstr = "lc-ctype";
367       if (!value && g13->lc_ctype_set)
368         return gpg_error (GPG_ERR_INV_VALUE);
369       if (value)
370         g13->lc_ctype_set = 1;
371     }
372 #endif
373 #ifdef LC_MESSAGES
374   else if (category == LC_MESSAGES)
375     {
376       catstr = "lc-messages";
377       if (!value && g13->lc_messages_set)
378         return gpg_error (GPG_ERR_INV_VALUE);
379       if (value)
380         g13->lc_messages_set = 1;
381     }
382 #endif /* LC_MESSAGES */
383   else
384     return gpg_error (GPG_ERR_INV_VALUE);
385
386   /* FIXME: Reset value to default.  */
387   if (!value)
388     return 0;
389
390   if (asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0)
391     err = gpg_error_from_syserror ();
392   else
393     {
394       err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL,
395                              NULL, NULL, NULL, NULL);
396       free (optstr);
397     }
398
399   return err;
400 }
401
402
403 #if USE_DESCRIPTOR_PASSING
404 static gpgme_error_t
405 g13_assuan_simple_command (assuan_context_t ctx, char *cmd,
406                            engine_status_handler_t status_fnc,
407                            void *status_fnc_value)
408 {
409   gpg_error_t err;
410   char *line;
411   size_t linelen;
412
413   err = assuan_write_line (ctx, cmd);
414   if (err)
415     return err;
416
417   do
418     {
419       err = assuan_read_line (ctx, &line, &linelen);
420       if (err)
421         return err;
422
423       if (*line == '#' || !linelen)
424         continue;
425
426       if (linelen >= 2
427           && line[0] == 'O' && line[1] == 'K'
428           && (line[2] == '\0' || line[2] == ' '))
429         return 0;
430       else if (linelen >= 4
431           && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
432           && line[3] == ' ')
433         err = atoi (&line[4]);
434       else if (linelen >= 2
435                && line[0] == 'S' && line[1] == ' ')
436         {
437           char *rest;
438
439           rest = strchr (line + 2, ' ');
440           if (!rest)
441             rest = line + linelen; /* set to an empty string */
442           else
443             *(rest++) = 0;
444
445           /* Nothing to do with status lines.  */
446         }
447       else
448         err = gpg_error (GPG_ERR_GENERAL);
449     }
450   while (!err);
451
452   return err;
453 }
454 #endif
455
456
457 static gpgme_error_t
458 status_handler (void *opaque, int fd)
459 {
460   struct io_cb_data *data = (struct io_cb_data *) opaque;
461   engine_g13_t g13 = (engine_g13_t) data->handler_value;
462   gpgme_error_t err = 0;
463   char *line;
464   size_t linelen;
465
466   do
467     {
468       err = assuan_read_line (g13->assuan_ctx, &line, &linelen);
469       if (err)
470         {
471           /* Try our best to terminate the connection friendly.  */
472           /*      assuan_write_line (g13->assuan_ctx, "BYE"); */
473           TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13,
474                   "fd 0x%x: error reading assuan line: %s",
475                   fd, gpg_strerror (err));
476         }
477       else if (linelen >= 3
478                && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
479                && (line[3] == '\0' || line[3] == ' '))
480         {
481           if (line[3] == ' ')
482             err = atoi (&line[4]);
483           if (! err)
484             err = gpg_error (GPG_ERR_GENERAL);
485           TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13,
486                   "fd 0x%x: ERR line: %s",
487                   fd, err ? gpg_strerror (err) : "ok");
488
489           /* Command execution errors are not fatal, as we use
490              a session based protocol.  */
491           data->op_err = err;
492
493           /* The caller will do the rest (namely, call cancel_op,
494              which closes status_fd).  */
495           return 0;
496         }
497       else if (linelen >= 2
498                && line[0] == 'O' && line[1] == 'K'
499                && (line[2] == '\0' || line[2] == ' '))
500         {
501           TRACE1 (DEBUG_CTX, "gpgme:status_handler", g13,
502                   "fd 0x%x: OK line", fd);
503
504           _gpgme_io_close (g13->status_cb.fd);
505           return 0;
506         }
507       else if (linelen > 2
508                && line[0] == 'D' && line[1] == ' ')
509         {
510           /* We are using the colon handler even for plain inline data
511              - strange name for that function but for historic reasons
512              we keep it.  */
513           /* FIXME We can't use this for binary data because we
514              assume this is a string.  For the current usage of colon
515              output it is correct.  */
516           char *src = line + 2;
517           char *end = line + linelen;
518           char *dst = src;
519
520           linelen = 0;
521           while (src < end)
522             {
523               if (*src == '%' && src + 2 < end)
524                 {
525                   /* Handle escaped characters.  */
526                   ++src;
527                   *dst++ = _gpgme_hextobyte (src);
528                   src += 2;
529                 }
530               else
531                 *dst++ = *src++;
532
533               linelen++;
534             }
535
536           src = line + 2;
537           if (linelen && g13->user.data_cb)
538             err = g13->user.data_cb (g13->user.data_cb_value,
539                                        src, linelen);
540           else
541             err = 0;
542
543           TRACE2 (DEBUG_CTX, "gpgme:g13_status_handler", g13,
544                   "fd 0x%x: D inlinedata; status from cb: %s",
545                   fd, (g13->user.data_cb ?
546                        (err? gpg_strerror (err):"ok"):"no callback"));
547
548         }
549       else if (linelen > 2
550                && line[0] == 'S' && line[1] == ' ')
551         {
552           char *src;
553           char *args;
554
555           src = line + 2;
556           while (*src == ' ')
557             src++;
558
559           args = strchr (line + 2, ' ');
560           if (!args)
561             args = line + linelen; /* set to an empty string */
562           else
563             *(args++) = 0;
564
565           while (*args == ' ')
566             args++;
567
568           if (g13->user.status_cb)
569             err = g13->user.status_cb (g13->user.status_cb_value,
570                                        src, args);
571           else
572             err = 0;
573
574           TRACE3 (DEBUG_CTX, "gpgme:g13_status_handler", g13,
575                   "fd 0x%x: S line (%s) - status from cb: %s",
576                   fd, line+2, (g13->user.status_cb ?
577                                (err? gpg_strerror (err):"ok"):"no callback"));
578         }
579       else if (linelen >= 7
580                && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
581                && line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
582                && line[6] == 'E'
583                && (line[7] == '\0' || line[7] == ' '))
584         {
585           char *src;
586           char *args;
587
588           for (src=line+7; *src == ' '; src++)
589             ;
590
591           args = strchr (src, ' ');
592           if (!args)
593             args = line + linelen; /* Let it point to an empty string.  */
594           else
595             *(args++) = 0;
596
597           while (*args == ' ')
598             args++;
599
600           err = default_inq_cb (g13, src, args);
601           if (!err)
602             {
603               /* Flush and send END.  */
604               err = assuan_send_data (g13->assuan_ctx, NULL, 0);
605             }
606           else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED)
607             {
608               /* Flush and send CANcel.  */
609               err = assuan_send_data (g13->assuan_ctx, NULL, 1);
610             }
611           assuan_write_line (g13->assuan_ctx, "END");
612         }
613     }
614   while (!err && assuan_pending_line (g13->assuan_ctx));
615
616   return err;
617 }
618
619
620 static gpgme_error_t
621 add_io_cb (engine_g13_t g13, iocb_data_t *iocbd, gpgme_io_cb_t handler)
622 {
623   gpgme_error_t err;
624
625   TRACE_BEG2 (DEBUG_ENGINE, "engine-g13:add_io_cb", g13,
626               "fd %d, dir %d", iocbd->fd, iocbd->dir);
627   err = (*g13->io_cbs.add) (g13->io_cbs.add_priv,
628                               iocbd->fd, iocbd->dir,
629                               handler, iocbd->data, &iocbd->tag);
630   if (err)
631     return TRACE_ERR (err);
632   if (!iocbd->dir)
633     /* FIXME Kludge around poll() problem.  */
634     err = _gpgme_io_set_nonblocking (iocbd->fd);
635   return TRACE_ERR (err);
636 }
637
638
639 static gpgme_error_t
640 start (engine_g13_t g13, const char *command)
641 {
642   gpgme_error_t err;
643   assuan_fd_t afdlist[5];
644   int fdlist[5];
645   int nfds;
646   int i;
647
648   /* We need to know the fd used by assuan for reads.  We do this by
649      using the assumption that the first returned fd from
650      assuan_get_active_fds() is always this one.  */
651   nfds = assuan_get_active_fds (g13->assuan_ctx, 0 /* read fds */,
652                                 afdlist, DIM (afdlist));
653   if (nfds < 1)
654     return gpg_error (GPG_ERR_GENERAL); /* FIXME */
655   /* For now... */
656   for (i = 0; i < nfds; i++)
657     fdlist[i] = (int) afdlist[i];
658
659   /* We "duplicate" the file descriptor, so we can close it here (we
660      can't close fdlist[0], as that is closed by libassuan, and
661      closing it here might cause libassuan to close some unrelated FD
662      later).  Alternatively, we could special case status_fd and
663      register/unregister it manually as needed, but this increases
664      code duplication and is more complicated as we can not use the
665      close notifications etc.  A third alternative would be to let
666      Assuan know that we closed the FD, but that complicates the
667      Assuan interface.  */
668
669   g13->status_cb.fd = _gpgme_io_dup (fdlist[0]);
670   if (g13->status_cb.fd < 0)
671     return gpg_error_from_syserror ();
672
673   if (_gpgme_io_set_close_notify (g13->status_cb.fd,
674                                   close_notify_handler, g13))
675     {
676       _gpgme_io_close (g13->status_cb.fd);
677       g13->status_cb.fd = -1;
678       return gpg_error (GPG_ERR_GENERAL);
679     }
680
681   err = add_io_cb (g13, &g13->status_cb, status_handler);
682   if (!err)
683     err = assuan_write_line (g13->assuan_ctx, command);
684
685   if (!err)
686     g13_io_event (g13, GPGME_EVENT_START, NULL);
687
688   return err;
689 }
690
691
692 #if USE_DESCRIPTOR_PASSING
693 static gpgme_error_t
694 g13_reset (void *engine)
695 {
696   engine_g13_t g13 = engine;
697
698   /* We must send a reset because we need to reset the list of
699      signers.  Note that RESET does not reset OPTION commands. */
700   return g13_assuan_simple_command (g13->assuan_ctx, "RESET", NULL, NULL);
701 }
702 #endif
703
704
705 static gpgme_error_t
706 g13_transact (void *engine,
707                 const char *command,
708                 gpgme_assuan_data_cb_t data_cb,
709                 void *data_cb_value,
710                 gpgme_assuan_inquire_cb_t inq_cb,
711                 void *inq_cb_value,
712                 gpgme_assuan_status_cb_t status_cb,
713                 void *status_cb_value)
714 {
715   engine_g13_t g13 = engine;
716   gpgme_error_t err;
717
718   if (!g13 || !command || !*command)
719     return gpg_error (GPG_ERR_INV_VALUE);
720
721   g13->user.data_cb = data_cb;
722   g13->user.data_cb_value = data_cb_value;
723   g13->user.inq_cb = inq_cb;
724   g13->user.inq_cb_value = inq_cb_value;
725   g13->user.status_cb = status_cb;
726   g13->user.status_cb_value = status_cb_value;
727
728   err = start (g13, command);
729   return err;
730 }
731
732
733
734 static void
735 g13_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
736 {
737   engine_g13_t g13 = engine;
738   g13->io_cbs = *io_cbs;
739 }
740
741
742 static void
743 g13_io_event (void *engine, gpgme_event_io_t type, void *type_data)
744 {
745   engine_g13_t g13 = engine;
746
747   TRACE3 (DEBUG_ENGINE, "gpgme:g13_io_event", g13,
748           "event %p, type %d, type_data %p",
749           g13->io_cbs.event, type, type_data);
750   if (g13->io_cbs.event)
751     (*g13->io_cbs.event) (g13->io_cbs.event_priv, type, type_data);
752 }
753
754
755 struct engine_ops _gpgme_engine_ops_g13 =
756   {
757     /* Static functions.  */
758     _gpgme_get_default_g13_name,
759     NULL,
760     g13_get_version,
761     g13_get_req_version,
762     g13_new,
763
764     /* Member functions.  */
765     g13_release,
766 #if USE_DESCRIPTOR_PASSING
767     g13_reset,
768 #else
769     NULL,                       /* reset */
770 #endif
771     NULL,               /* set_status_handler */
772     NULL,               /* set_command_handler */
773     NULL,               /* set_colon_line_handler */
774     g13_set_locale,
775     NULL,               /* set_protocol */
776     NULL,               /* decrypt */
777     NULL,               /* decrypt_verify */
778     NULL,               /* delete */
779     NULL,               /* edit */
780     NULL,               /* encrypt */
781     NULL,               /* encrypt_sign */
782     NULL,               /* export */
783     NULL,               /* export_ext */
784     NULL,               /* genkey */
785     NULL,               /* import */
786     NULL,               /* keylist */
787     NULL,               /* keylist_ext */
788     NULL,               /* sign */
789     NULL,               /* trustlist */
790     NULL,               /* verify */
791     NULL,               /* getauditlog */
792     g13_transact,
793     NULL,               /* conf_load */
794     NULL,               /* conf_save */
795     g13_set_io_cbs,
796     g13_io_event,
797     g13_cancel,
798     g13_cancel_op,
799     NULL,               /* passwd */
800     NULL,               /* set_pinentry_mode */
801     NULL                /* opspawn */
802   };