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