Fixed a gpgconf problem under Windows.
[gpgme.git] / gpgme / engine-gpgconf.c
1 /* engine-gpgconf.c - gpg-conf engine.
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2008 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 #include <sys/types.h>
29 #include <assert.h>
30 #include <unistd.h>
31 #include <locale.h>
32 #include <fcntl.h> /* FIXME */
33 #include <errno.h>
34
35 #include "gpgme.h"
36 #include "util.h"
37 #include "ops.h"
38 #include "wait.h"
39 #include "priv-io.h"
40 #include "sema.h"
41
42 #include "assuan.h"
43 #include "debug.h"
44
45 #include "engine-backend.h"
46
47 \f
48 struct engine_gpgconf
49 {
50   char *file_name;
51   char *home_dir;
52 };
53
54 typedef struct engine_gpgconf *engine_gpgconf_t;
55
56 \f
57 static char *
58 gpgconf_get_version (const char *file_name)
59 {
60   return _gpgme_get_program_version (file_name ? file_name
61                                      : _gpgme_get_gpgconf_path ());
62 }
63
64
65 static const char *
66 gpgconf_get_req_version (void)
67 {
68   return NEED_GPGCONF_VERSION;
69 }
70
71 \f
72 static void
73 gpgconf_release (void *engine)
74 {
75   engine_gpgconf_t gpgconf = engine;
76
77   if (!gpgconf)
78     return;
79
80   if (gpgconf->file_name)
81     free (gpgconf->file_name);
82   if (gpgconf->home_dir)
83     free (gpgconf->home_dir);
84
85   free (gpgconf);
86 }
87
88
89 static gpgme_error_t
90 gpgconf_new (void **engine, const char *file_name, const char *home_dir)
91 {
92   gpgme_error_t err = 0;
93   engine_gpgconf_t gpgconf;
94
95   gpgconf = calloc (1, sizeof *gpgconf);
96   if (!gpgconf)
97     return gpg_error_from_errno (errno);
98
99   gpgconf->file_name = strdup (file_name ? file_name
100                                : _gpgme_get_gpgconf_path ());
101   if (!gpgconf->file_name)
102     err = gpg_error_from_syserror ();
103
104   if (!err && home_dir)
105     {
106       gpgconf->home_dir = strdup (home_dir);
107       if (!gpgconf->home_dir)
108         err = gpg_error_from_syserror ();
109     }
110
111   if (err)
112     gpgconf_release (gpgconf);
113   else
114     *engine = gpgconf;
115
116   return err;
117 }
118
119 \f
120 static void
121 release_arg (gpgme_conf_arg_t arg, gpgme_conf_type_t alt_type)
122 {
123   while (arg)
124     {
125       gpgme_conf_arg_t next = arg->next;
126
127       if (alt_type == GPGME_CONF_STRING)
128         free (arg->value.string);
129       free (arg);
130       arg = next;
131     }
132 }
133
134
135 static void
136 release_opt (gpgme_conf_opt_t opt)
137 {
138   if (opt->name)
139     free (opt->name);
140   if (opt->description)
141     free (opt->description);
142   if (opt->argname)
143     free (opt->argname);
144
145   release_arg (opt->default_value, opt->alt_type);
146   if (opt->default_description)
147     free (opt->default_description);
148   
149   release_arg (opt->no_arg_value, opt->alt_type);
150   release_arg (opt->value, opt->alt_type);
151   release_arg (opt->new_value, opt->alt_type);
152
153   free (opt);
154 }
155
156
157 static void
158 release_comp (gpgme_conf_comp_t comp)
159 {
160   gpgme_conf_opt_t opt;
161
162   if (comp->name)
163     free (comp->name);
164   if (comp->description)
165     free (comp->description);
166   if (comp->program_name)
167     free (comp->program_name);
168
169   opt = comp->options;
170   while (opt)
171     {
172       gpgme_conf_opt_t next = opt->next;
173       release_opt (opt);
174       opt = next;
175     }
176
177   free (comp);
178 }
179
180
181 static void
182 gpgconf_config_release (gpgme_conf_comp_t conf)
183 {
184   while (conf)
185     {
186       gpgme_conf_comp_t next = conf->next;
187       release_comp (conf);
188       conf = next;
189     }
190 }
191
192
193 static gpgme_error_t
194 gpgconf_read (void *engine, char *arg1, char *arg2,
195               gpgme_error_t (*cb) (void *hook, char *line),
196               void *hook)
197 {
198   struct engine_gpgconf *gpgconf = engine;
199   gpgme_error_t err = 0;
200 #define LINELENGTH 1024
201   char linebuf[LINELENGTH] = "";
202   int linelen = 0;
203   char *argv[] = { NULL /* file_name */, arg1, arg2, 0 };
204   int rp[2];
205   struct spawn_fd_item_s pfd[] = { {0, -1}, {-1, -1} };
206   struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */}, {-1, -1} };
207   int status;
208   int nread;
209   char *mark = NULL;
210
211   /* FIXME: Deal with engine->home_dir.  */
212
213   /* _gpgme_engine_new guarantees that this is not NULL.  */
214   argv[0] = gpgconf->file_name;
215   
216   if (_gpgme_io_pipe (rp, 1) < 0)
217     return gpg_error_from_syserror ();
218
219   pfd[0].fd = rp[1];
220   cfd[0].fd = rp[1];
221
222   status = _gpgme_io_spawn (gpgconf->file_name, argv, cfd, pfd, NULL);
223   if (status < 0)
224     {
225       _gpgme_io_close (rp[0]);
226       _gpgme_io_close (rp[1]);
227       return gpg_error_from_syserror ();
228     }
229
230   do
231     {
232       nread = _gpgme_io_read (rp[0], 
233                               linebuf + linelen, LINELENGTH - linelen - 1);
234       if (nread > 0)
235         {
236           char *line;
237           const char *lastmark = NULL;
238           size_t nused;
239
240           linelen += nread;
241           linebuf[linelen] = '\0';
242
243           for (line=linebuf; (mark = strchr (line, '\n')); line = mark+1 )
244             {
245               lastmark = mark;
246               if (mark > line && mark[-1] == '\r')
247                 mark--;
248               *mark = '\0';
249
250               /* Got a full line.  Due to the CR removal code (which
251                  occurs only on Windows) we might be one-off and thus
252                  would see empty lines.  Don't pass them to the
253                  callback. */
254               err = *line? (*cb) (hook, line) : NULL;
255               if (err)
256                 goto leave;
257             }
258
259           nused = lastmark? (lastmark + 1 - linebuf) : 0;
260           memmove (linebuf, linebuf + nused, nused);
261           linelen -= nused;
262         }
263     }
264   while (nread > 0 && linelen < LINELENGTH - 1);
265   
266   if (!err && nread < 0)
267     err = gpg_error_from_syserror ();
268   if (!err && nread > 0)
269     err = gpg_error (GPG_ERR_LINE_TOO_LONG);
270
271  leave:
272   _gpgme_io_close (rp[0]);
273
274   return err;
275 }
276
277
278 static gpgme_error_t
279 gpgconf_config_load_cb (void *hook, char *line)
280 {
281   gpgme_conf_comp_t *comp_p = hook;
282   gpgme_conf_comp_t comp = *comp_p;
283 #define NR_FIELDS 16
284   char *field[NR_FIELDS];
285   int fields = 0;
286
287   while (line && fields < NR_FIELDS)
288     {
289       field[fields++] = line;
290       line = strchr (line, ':');
291       if (line)
292         *(line++) = '\0';
293     }
294
295   /* We require at least the first 3 fields.  */
296   if (fields < 2)
297     return gpg_error (GPG_ERR_INV_ENGINE);
298
299   /* Find the pointer to the new component in the list.  */
300   while (comp && comp->next)
301     comp = comp->next;
302   if (comp)
303     comp_p = &comp->next;
304
305   comp = calloc (1, sizeof (*comp));
306   if (!comp)
307     return gpg_error_from_syserror ();
308   /* Prepare return value.  */
309   comp->_last_opt_p = &comp->options;
310   *comp_p = comp;
311
312   comp->name = strdup (field[0]);
313   if (!comp->name)
314     return gpg_error_from_syserror ();
315
316   comp->description = strdup (field[1]);
317   if (!comp->description)
318     return gpg_error_from_syserror ();
319
320   if (fields >= 3)
321     {
322       comp->program_name = strdup (field[2]);
323       if (!comp->program_name)
324         return gpg_error_from_syserror ();
325     }
326
327   return 0;
328 }
329
330
331 static gpgme_error_t
332 gpgconf_parse_option (gpgme_conf_opt_t opt,
333                       gpgme_conf_arg_t *arg_p, char *line)
334 {
335   gpgme_error_t err;
336   char *mark;
337
338   if (!line[0])
339     return 0;
340
341   mark = strchr (line, ',');
342   if (mark)
343     *mark = '\0';
344
345   while (line)
346     {
347       gpgme_conf_arg_t arg = calloc (1, sizeof (*arg));
348       if (!arg)
349         return gpg_error_from_syserror ();
350       *arg_p = arg;
351       arg_p = &arg->next;
352
353       if (*line == '\0')
354         arg->no_arg = 1;
355       else
356         {
357           switch (opt->alt_type)
358             {
359               /* arg->value.count is an alias for arg->value.uint32.  */
360             case GPGME_CONF_NONE:
361             case GPGME_CONF_UINT32:
362               arg->value.uint32 = strtoul (line, NULL, 0);
363               break;
364               
365             case GPGME_CONF_INT32:
366               arg->value.uint32 = strtol (line, NULL, 0);
367               break;
368               
369             case GPGME_CONF_STRING:
370             case GPGME_CONF_PATHNAME:
371             case GPGME_CONF_LDAP_SERVER:
372               /* Skip quote character.  */
373               line++;
374               
375               err = _gpgme_decode_percent_string (line, &arg->value.string,
376                                                   0, 0);
377               if (err)
378                 return err;
379               break;
380             }
381         }
382
383       /* Find beginning of next value.  */
384       if (mark++ && *mark)
385         line = mark;
386       else
387         line = NULL;
388     }
389
390   return 0;
391 }
392
393
394 static gpgme_error_t
395 gpgconf_config_load_cb2 (void *hook, char *line)
396 {
397   gpgme_error_t err;
398   gpgme_conf_comp_t comp = hook;
399   gpgme_conf_opt_t *opt_p = comp->_last_opt_p;
400   gpgme_conf_opt_t opt;
401 #define NR_FIELDS 16
402   char *field[NR_FIELDS];
403   int fields = 0;
404
405   while (line && fields < NR_FIELDS)
406     {
407       field[fields++] = line;
408       line = strchr (line, ':');
409       if (line)
410         *(line++) = '\0';
411     }
412
413   /* We require at least the first 10 fields.  */
414   if (fields < 10)
415     return gpg_error (GPG_ERR_INV_ENGINE);
416
417   opt = calloc (1, sizeof (*opt));
418   if (!opt)
419     return gpg_error_from_syserror ();
420
421   comp->_last_opt_p = &opt->next;
422   *opt_p = opt;
423
424   if (field[0][0])
425     {
426       opt->name = strdup (field[0]);
427       if (!opt->name)
428         return gpg_error_from_syserror ();
429     }
430
431   opt->flags = strtoul (field[1], NULL, 0);
432
433   opt->level = strtoul (field[2], NULL, 0);
434
435   if (field[3][0])
436     {
437       opt->description = strdup (field[3]);
438       if (!opt->description)
439         return gpg_error_from_syserror ();
440     }
441
442   opt->type = strtoul (field[4], NULL, 0);
443
444   opt->alt_type = strtoul (field[5], NULL, 0);
445
446   if (field[6][0])
447     {
448       opt->argname = strdup (field[6]);
449       if (!opt->argname)
450         return gpg_error_from_syserror ();
451     }
452
453   if (opt->flags & GPGME_CONF_DEFAULT)
454     {
455       err = gpgconf_parse_option (opt, &opt->default_value, field[7]);
456       if (err)
457         return err;
458     }
459   else if ((opt->flags & GPGME_CONF_DEFAULT_DESC) && field[7][0])
460     {
461       opt->default_description = strdup (field[7]);
462       if (!opt->default_description)
463         return gpg_error_from_syserror ();
464     }
465
466   if (opt->flags & GPGME_CONF_NO_ARG_DESC)
467     {
468       opt->no_arg_description = strdup (field[8]);
469       if (!opt->no_arg_description)
470         return gpg_error_from_syserror ();
471     }
472   else
473     {
474       err = gpgconf_parse_option (opt, &opt->no_arg_value, field[8]);
475       if (err)
476         return err;
477     }
478
479   err = gpgconf_parse_option (opt, &opt->value, field[9]);
480   if (err)
481     return err;
482
483   return 0;
484 }
485
486
487 static gpgme_error_t
488 gpgconf_conf_load (void *engine, gpgme_conf_comp_t *comp_p)
489 {
490   gpgme_error_t err;
491   gpgme_conf_comp_t comp = NULL;
492   gpgme_conf_comp_t cur_comp;
493
494   *comp_p = NULL;
495
496   err = gpgconf_read (engine, "--list-components", NULL,
497                       gpgconf_config_load_cb, &comp);
498   if (err)
499     {
500       gpgconf_release (comp);
501       return err;
502     }
503
504   cur_comp = comp;
505   while (!err && cur_comp)
506     {
507       err = gpgconf_read (engine, "--list-options", cur_comp->name,
508                           gpgconf_config_load_cb2, cur_comp);
509       cur_comp = cur_comp->next;
510     }
511
512   if (err)
513     {
514       gpgconf_release (comp);
515       return err;
516     }
517
518   *comp_p = comp;
519   return 0;
520 }
521
522
523 \f
524 gpgme_error_t
525 _gpgme_conf_arg_new (gpgme_conf_arg_t *arg_p,
526                      gpgme_conf_type_t type, void *value)
527 {
528   gpgme_conf_arg_t arg;
529
530   arg = calloc (1, sizeof (*arg));
531   if (!arg)
532     return gpg_error_from_syserror ();
533
534   if (!value)
535     arg->no_arg = 1;
536   else
537     {
538       switch (type)
539         {
540         case GPGME_CONF_NONE:
541         case GPGME_CONF_UINT32:
542           arg->value.uint32 = *((unsigned int *) value);
543           break;
544           
545         case GPGME_CONF_INT32:
546           arg->value.int32 = *((int *) value);
547           break;
548           
549         case GPGME_CONF_STRING:
550         case GPGME_CONF_PATHNAME:
551         case GPGME_CONF_LDAP_SERVER:
552           arg->value.string = strdup (value);
553           if (!arg->value.string)
554             {
555               free (arg);
556               return gpg_error_from_syserror ();
557             }
558           break;
559           
560         default:
561           free (arg);
562           return gpg_error (GPG_ERR_INV_VALUE);
563         }
564     }
565
566   *arg_p = arg;
567   return 0;
568 }
569
570
571 void
572 _gpgme_conf_arg_release (gpgme_conf_arg_t arg, gpgme_conf_type_t type)
573 {
574   switch (type)
575     {
576     case GPGME_CONF_NONE:
577     case GPGME_CONF_UINT32:
578     case GPGME_CONF_INT32:
579     case GPGME_CONF_STRING:
580     default:
581       break;
582        
583     case GPGME_CONF_PATHNAME:
584     case GPGME_CONF_LDAP_SERVER:
585       type = GPGME_CONF_STRING;
586       break;
587     }
588
589   release_arg (arg, type);
590 }
591
592
593 gpgme_error_t
594 _gpgme_conf_opt_change (gpgme_conf_opt_t opt, int reset, gpgme_conf_arg_t arg)
595 {
596   if (opt->new_value)
597     release_arg (opt->new_value, opt->alt_type);
598
599   if (reset)
600     {
601       opt->new_value = NULL;
602       opt->change_value = 0;
603     }
604   else
605     {
606       opt->new_value = arg;
607       opt->change_value = 1;
608     }
609   return 0;
610 }
611
612 \f
613 /* FIXME: Major problem: We don't get errors from gpgconf.  */
614
615 static gpgme_error_t
616 gpgconf_write (void *engine, char *arg1, char *arg2, gpgme_data_t conf)
617 {
618   struct engine_gpgconf *gpgconf = engine;
619   gpgme_error_t err = 0;
620 #define BUFLEN 1024
621   char buf[BUFLEN];
622   int buflen = 0;
623   char *argv[] = { NULL /* file_name */, arg1, arg2, 0 };
624   int rp[2];
625   struct spawn_fd_item_s pfd[] = { {1, -1}, {-1, -1} };
626   struct spawn_fd_item_s cfd[] = { {-1, 0 /* STDIN_FILENO */}, {-1, -1} };
627   int status;
628   int nwrite;
629
630   /* FIXME: Deal with engine->home_dir.  */
631
632   /* _gpgme_engine_new guarantees that this is not NULL.  */
633   argv[0] = gpgconf->file_name;
634   argv[0] = "/nowhere/path-needs-to-be-fixed/gpgconf";
635
636   if (_gpgme_io_pipe (rp, 0) < 0)
637     return gpg_error_from_syserror ();
638
639   pfd[0].fd = rp[0];
640   cfd[0].fd = rp[0];
641
642   status = _gpgme_io_spawn (gpgconf->file_name, argv, cfd, pfd, NULL);
643   if (status < 0)
644     {
645       _gpgme_io_close (rp[0]);
646       _gpgme_io_close (rp[1]);
647       return gpg_error_from_syserror ();
648     }
649
650   for (;;)
651     {
652       if (buflen == 0)
653         {
654           do
655             {
656               buflen = gpgme_data_read (conf, buf, BUFLEN);
657             }
658           while (buflen < 0 && errno == EAGAIN);
659
660           if (buflen < 0)
661             {
662               err = gpg_error_from_syserror ();
663               _gpgme_io_close (rp[1]);
664               return err;
665             }
666           else if (buflen == 0)
667             {
668               /* All is written.  */
669               _gpgme_io_close (rp[1]);
670               return 0;
671             }
672         }
673
674       do
675         {
676           nwrite = _gpgme_io_write (rp[1], buf, buflen);
677         }
678       while (nwrite < 0 && errno == EAGAIN);
679
680       if (nwrite > 0)
681         {
682           buflen -= nwrite;
683           if (buflen > 0)
684             memmove (&buf[0], &buf[nwrite], buflen);
685         }
686       else if (nwrite < 0)
687         {
688           _gpgme_io_close (rp[1]);
689           return gpg_error_from_syserror ();
690         }
691     }
692
693   return 0;
694 }
695
696
697 static gpgme_error_t
698 arg_to_data (gpgme_data_t conf, gpgme_conf_opt_t option, gpgme_conf_arg_t arg)
699 {
700   gpgme_error_t err = 0;
701   int amt = 0;
702   char buf[16];
703
704   while (amt >= 0 && arg)
705     {
706       switch (option->alt_type)
707         {
708         case GPGME_CONF_NONE:
709         case GPGME_CONF_UINT32:
710         default:
711           snprintf (buf, sizeof (buf), "%u", arg->value.uint32);
712           buf[sizeof (buf) - 1] = '\0';
713           amt = gpgme_data_write (conf, buf, strlen (buf));
714           break;
715           
716         case GPGME_CONF_INT32:
717           snprintf (buf, sizeof (buf), "%i", arg->value.uint32);
718           buf[sizeof (buf) - 1] = '\0';
719           amt = gpgme_data_write (conf, buf, strlen (buf));
720           break;
721           
722         case GPGME_CONF_STRING:
723         case GPGME_CONF_PATHNAME:
724         case GPGME_CONF_LDAP_SERVER:
725           /* One quote character, and three times to allow
726              for percent escaping.  */
727           {
728             char *ptr = arg->value.string;
729             amt = gpgme_data_write (conf, "\"", 1);
730             if (amt < 0)
731               break;
732
733             while (!err && *ptr)
734               {
735                 switch (*ptr)
736                   {
737                   case '%':
738                     amt = gpgme_data_write (conf, "%25", 3);
739                     break;
740
741                   case ':':
742                     amt = gpgme_data_write (conf, "%3a", 3);
743                     break;
744
745                   case ',':
746                     amt = gpgme_data_write (conf, "%2c", 3);
747                     break;
748
749                   default:
750                     amt = gpgme_data_write (conf, ptr, 1);
751                   }
752                 ptr++;
753               }
754           }
755           break;
756         }
757
758       if (amt < 0)
759         break;
760
761       arg = arg->next;
762       /* Comma separator.  */
763       if (arg)
764         amt = gpgme_data_write (conf, ",", 1);
765     }
766
767   if (amt < 0)
768     return gpg_error_from_syserror ();
769   
770   return 0;
771 }
772
773
774 static gpgme_error_t
775 gpgconf_conf_save (void *engine, gpgme_conf_comp_t comp)
776 {
777   gpgme_error_t err;
778   int amt = 0;
779   /* We use a data object to store the new configuration.  */
780   gpgme_data_t conf;
781   gpgme_conf_opt_t option;
782   int something_changed = 0;
783
784   err = gpgme_data_new (&conf);
785   if (err)
786     return err;
787
788   option = comp->options;
789   while (!err && amt >= 0 && option)
790     {
791       if (option->change_value)
792         {
793           unsigned int flags = 0;
794           char buf[16];
795
796           something_changed = 1;
797
798           amt = gpgme_data_write (conf, option->name, strlen (option->name));
799           if (amt >= 0)
800             amt = gpgme_data_write (conf, ":", 1);
801           if (amt < 0)
802             break;
803
804           if (!option->new_value)
805             flags |= GPGME_CONF_DEFAULT;
806           snprintf (buf, sizeof (buf), "%u", flags);
807           buf[sizeof (buf) - 1] = '\0';
808
809           amt = gpgme_data_write (conf, buf, strlen (buf));
810           if (amt >= 0)
811             amt = gpgme_data_write (conf, ":", 1);
812           if (amt < 0)
813             break;
814
815           if (option->new_value)
816             {
817               err = arg_to_data (conf, option, option->new_value);
818               if (err)
819                 break;
820             }
821           amt = gpgme_data_write (conf, "\n", 1);
822         }
823       option = option->next;
824     }
825   if (!err && amt < 0)
826     err = gpg_error_from_syserror ();
827   if (err || !something_changed)
828     goto bail;
829
830   err = gpgme_data_seek (conf, 0, SEEK_SET);
831   if (err)
832     goto bail;
833
834   err = gpgconf_write (engine, "--change-options", comp->name, conf);
835  bail:
836   gpgme_data_release (conf);
837   return err;
838 }
839
840
841 static void
842 gpgconf_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
843 {
844   /* Nothing to do.  */
845 }
846
847 \f
848 /* Currently, we do not use the engine interface for the various
849    operations.  */
850 void
851 _gpgme_conf_release (gpgme_conf_comp_t conf)
852 {
853   gpgconf_config_release (conf);
854 }
855
856 \f
857 struct engine_ops _gpgme_engine_ops_gpgconf =
858   {
859     /* Static functions.  */
860     _gpgme_get_gpgconf_path,
861     gpgconf_get_version,
862     gpgconf_get_req_version,
863     gpgconf_new,
864
865     /* Member functions.  */
866     gpgconf_release,
867     NULL,               /* reset */
868     NULL,               /* set_status_handler */
869     NULL,               /* set_command_handler */
870     NULL,               /* set_colon_line_handler */
871     NULL,               /* set_locale */
872     NULL,               /* decrypt */
873     NULL,               /* delete */
874     NULL,               /* edit */
875     NULL,               /* encrypt */
876     NULL,               /* encrypt_sign */
877     NULL,               /* export */
878     NULL,               /* export_ext */
879     NULL,               /* genkey */
880     NULL,               /* import */
881     NULL,               /* keylist */
882     NULL,               /* keylist_ext */
883     NULL,               /* sign */
884     NULL,               /* trustlist */
885     NULL,               /* verify */
886     NULL,               /* getauditlog */
887     gpgconf_conf_load,
888     gpgconf_conf_save,
889     gpgconf_set_io_cbs,
890     NULL,               /* io_event */
891     NULL                /* cancel */
892   };