ede2c9cf98d73d3ae01342711f43ecfdee48db03
[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.  */
251               err = (*cb) (hook, line);
252               if (err)
253                 goto leave;
254             }
255
256           nused = lastmark? (lastmark + 1 - linebuf) : 0;
257           memmove (linebuf, linebuf + nused, nused);
258           linelen -= nused;
259         }
260     }
261   while (nread > 0 && linelen < LINELENGTH - 1);
262   
263   if (!err && nread < 0)
264     err = gpg_error_from_syserror ();
265   if (!err && nread > 0)
266     err = gpg_error (GPG_ERR_LINE_TOO_LONG);
267
268  leave:
269   _gpgme_io_close (rp[0]);
270
271   return err;
272 }
273
274
275 static gpgme_error_t
276 gpgconf_config_load_cb (void *hook, char *line)
277 {
278   gpgme_conf_comp_t *comp_p = hook;
279   gpgme_conf_comp_t comp = *comp_p;
280 #define NR_FIELDS 16
281   char *field[NR_FIELDS];
282   int fields = 0;
283
284   while (line && fields < NR_FIELDS)
285     {
286       field[fields++] = line;
287       line = strchr (line, ':');
288       if (line)
289         *(line++) = '\0';
290     }
291
292   /* We require at least the first 3 fields.  */
293   if (fields < 2)
294     return gpg_error (GPG_ERR_INV_ENGINE);
295
296   /* Find the pointer to the new component in the list.  */
297   while (comp && comp->next)
298     comp = comp->next;
299   if (comp)
300     comp_p = &comp->next;
301
302   comp = calloc (1, sizeof (*comp));
303   if (!comp)
304     return gpg_error_from_syserror ();
305   /* Prepare return value.  */
306   comp->_last_opt_p = &comp->options;
307   *comp_p = comp;
308
309   comp->name = strdup (field[0]);
310   if (!comp->name)
311     return gpg_error_from_syserror ();
312
313   comp->description = strdup (field[1]);
314   if (!comp->description)
315     return gpg_error_from_syserror ();
316
317   if (fields >= 3)
318     {
319       comp->program_name = strdup (field[2]);
320       if (!comp->program_name)
321         return gpg_error_from_syserror ();
322     }
323
324   return 0;
325 }
326
327
328 static gpgme_error_t
329 gpgconf_parse_option (gpgme_conf_opt_t opt,
330                       gpgme_conf_arg_t *arg_p, char *line)
331 {
332   gpgme_error_t err;
333   char *mark;
334
335   if (!line[0])
336     return 0;
337
338   mark = strchr (line, ',');
339   if (mark)
340     *mark = '\0';
341
342   while (line)
343     {
344       gpgme_conf_arg_t arg = calloc (1, sizeof (*arg));
345       if (!arg)
346         return gpg_error_from_syserror ();
347       *arg_p = arg;
348       arg_p = &arg->next;
349
350       if (*line == '\0')
351         arg->no_arg = 1;
352       else
353         {
354           switch (opt->alt_type)
355             {
356               /* arg->value.count is an alias for arg->value.uint32.  */
357             case GPGME_CONF_NONE:
358             case GPGME_CONF_UINT32:
359               arg->value.uint32 = strtoul (line, NULL, 0);
360               break;
361               
362             case GPGME_CONF_INT32:
363               arg->value.uint32 = strtol (line, NULL, 0);
364               break;
365               
366             case GPGME_CONF_STRING:
367             case GPGME_CONF_PATHNAME:
368             case GPGME_CONF_LDAP_SERVER:
369               /* Skip quote character.  */
370               line++;
371               
372               err = _gpgme_decode_percent_string (line, &arg->value.string,
373                                                   0, 0);
374               if (err)
375                 return err;
376               break;
377             }
378         }
379
380       /* Find beginning of next value.  */
381       if (mark++ && *mark)
382         line = mark;
383       else
384         line = NULL;
385     }
386
387   return 0;
388 }
389
390
391 static gpgme_error_t
392 gpgconf_config_load_cb2 (void *hook, char *line)
393 {
394   gpgme_error_t err;
395   gpgme_conf_comp_t comp = hook;
396   gpgme_conf_opt_t *opt_p = comp->_last_opt_p;
397   gpgme_conf_opt_t opt;
398 #define NR_FIELDS 16
399   char *field[NR_FIELDS];
400   int fields = 0;
401
402   while (line && fields < NR_FIELDS)
403     {
404       field[fields++] = line;
405       line = strchr (line, ':');
406       if (line)
407         *(line++) = '\0';
408     }
409
410   /* We require at least the first 10 fields.  */
411   if (fields < 10)
412     return gpg_error (GPG_ERR_INV_ENGINE);
413
414   opt = calloc (1, sizeof (*opt));
415   if (!opt)
416     return gpg_error_from_syserror ();
417
418   comp->_last_opt_p = &opt->next;
419   *opt_p = opt;
420
421   if (field[0][0])
422     {
423       opt->name = strdup (field[0]);
424       if (!opt->name)
425         return gpg_error_from_syserror ();
426     }
427
428   opt->flags = strtoul (field[1], NULL, 0);
429
430   opt->level = strtoul (field[2], NULL, 0);
431
432   if (field[3][0])
433     {
434       opt->description = strdup (field[3]);
435       if (!opt->description)
436         return gpg_error_from_syserror ();
437     }
438
439   opt->type = strtoul (field[4], NULL, 0);
440
441   opt->alt_type = strtoul (field[5], NULL, 0);
442
443   if (field[6][0])
444     {
445       opt->argname = strdup (field[6]);
446       if (!opt->argname)
447         return gpg_error_from_syserror ();
448     }
449
450   if (opt->flags & GPGME_CONF_DEFAULT)
451     {
452       err = gpgconf_parse_option (opt, &opt->default_value, field[7]);
453       if (err)
454         return err;
455     }
456   else if ((opt->flags & GPGME_CONF_DEFAULT_DESC) && field[7][0])
457     {
458       opt->default_description = strdup (field[7]);
459       if (!opt->default_description)
460         return gpg_error_from_syserror ();
461     }
462
463   if (opt->flags & GPGME_CONF_NO_ARG_DESC)
464     {
465       opt->no_arg_description = strdup (field[8]);
466       if (!opt->no_arg_description)
467         return gpg_error_from_syserror ();
468     }
469   else
470     {
471       err = gpgconf_parse_option (opt, &opt->no_arg_value, field[8]);
472       if (err)
473         return err;
474     }
475
476   err = gpgconf_parse_option (opt, &opt->value, field[9]);
477   if (err)
478     return err;
479
480   return 0;
481 }
482
483
484 static gpgme_error_t
485 gpgconf_conf_load (void *engine, gpgme_conf_comp_t *comp_p)
486 {
487   gpgme_error_t err;
488   gpgme_conf_comp_t comp = NULL;
489   gpgme_conf_comp_t cur_comp;
490
491   *comp_p = NULL;
492
493   err = gpgconf_read (engine, "--list-components", NULL,
494                       gpgconf_config_load_cb, &comp);
495   if (err)
496     {
497       gpgconf_release (comp);
498       return err;
499     }
500
501   cur_comp = comp;
502   while (!err && cur_comp)
503     {
504       err = gpgconf_read (engine, "--list-options", cur_comp->name,
505                           gpgconf_config_load_cb2, cur_comp);
506       cur_comp = cur_comp->next;
507     }
508
509   if (err)
510     {
511       gpgconf_release (comp);
512       return err;
513     }
514
515   *comp_p = comp;
516   return 0;
517 }
518
519
520 \f
521 gpgme_error_t
522 _gpgme_conf_arg_new (gpgme_conf_arg_t *arg_p,
523                      gpgme_conf_type_t type, void *value)
524 {
525   gpgme_conf_arg_t arg;
526
527   arg = calloc (1, sizeof (*arg));
528   if (!arg)
529     return gpg_error_from_syserror ();
530
531   if (!value)
532     arg->no_arg = 1;
533   else
534     {
535       switch (type)
536         {
537         case GPGME_CONF_NONE:
538         case GPGME_CONF_UINT32:
539           arg->value.uint32 = *((unsigned int *) value);
540           break;
541           
542         case GPGME_CONF_INT32:
543           arg->value.int32 = *((int *) value);
544           break;
545           
546         case GPGME_CONF_STRING:
547         case GPGME_CONF_PATHNAME:
548         case GPGME_CONF_LDAP_SERVER:
549           arg->value.string = strdup (value);
550           if (!arg->value.string)
551             {
552               free (arg);
553               return gpg_error_from_syserror ();
554             }
555           break;
556           
557         default:
558           free (arg);
559           return gpg_error (GPG_ERR_INV_VALUE);
560         }
561     }
562
563   *arg_p = arg;
564   return 0;
565 }
566
567
568 void
569 _gpgme_conf_arg_release (gpgme_conf_arg_t arg, gpgme_conf_type_t type)
570 {
571   switch (type)
572     {
573     case GPGME_CONF_NONE:
574     case GPGME_CONF_UINT32:
575     case GPGME_CONF_INT32:
576     case GPGME_CONF_STRING:
577     default:
578       break;
579        
580     case GPGME_CONF_PATHNAME:
581     case GPGME_CONF_LDAP_SERVER:
582       type = GPGME_CONF_STRING;
583       break;
584     }
585
586   release_arg (arg, type);
587 }
588
589
590 gpgme_error_t
591 _gpgme_conf_opt_change (gpgme_conf_opt_t opt, int reset, gpgme_conf_arg_t arg)
592 {
593   if (opt->new_value)
594     release_arg (opt->new_value, opt->alt_type);
595
596   if (reset)
597     {
598       opt->new_value = NULL;
599       opt->change_value = 0;
600     }
601   else
602     {
603       opt->new_value = arg;
604       opt->change_value = 1;
605     }
606   return 0;
607 }
608
609 \f
610 /* FIXME: Major problem: We don't get errors from gpgconf.  */
611
612 static gpgme_error_t
613 gpgconf_write (void *engine, char *arg1, char *arg2, gpgme_data_t conf)
614 {
615   struct engine_gpgconf *gpgconf = engine;
616   gpgme_error_t err = 0;
617 #define BUFLEN 1024
618   char buf[BUFLEN];
619   int buflen = 0;
620   char *argv[] = { NULL /* file_name */, arg1, arg2, 0 };
621   int rp[2];
622   struct spawn_fd_item_s pfd[] = { {1, -1}, {-1, -1} };
623   struct spawn_fd_item_s cfd[] = { {-1, 0 /* STDIN_FILENO */}, {-1, -1} };
624   int status;
625   int nwrite;
626
627   /* FIXME: Deal with engine->home_dir.  */
628
629   /* _gpgme_engine_new guarantees that this is not NULL.  */
630   argv[0] = gpgconf->file_name;
631   argv[0] = "/nowhere/path-needs-to-be-fixed/gpgconf";
632
633   if (_gpgme_io_pipe (rp, 0) < 0)
634     return gpg_error_from_syserror ();
635
636   pfd[0].fd = rp[0];
637   cfd[0].fd = rp[0];
638
639   status = _gpgme_io_spawn (gpgconf->file_name, argv, cfd, pfd, NULL);
640   if (status < 0)
641     {
642       _gpgme_io_close (rp[0]);
643       _gpgme_io_close (rp[1]);
644       return gpg_error_from_syserror ();
645     }
646
647   for (;;)
648     {
649       if (buflen == 0)
650         {
651           do
652             {
653               buflen = gpgme_data_read (conf, buf, BUFLEN);
654             }
655           while (buflen < 0 && errno == EAGAIN);
656
657           if (buflen < 0)
658             {
659               err = gpg_error_from_syserror ();
660               _gpgme_io_close (rp[1]);
661               return err;
662             }
663           else if (buflen == 0)
664             {
665               /* All is written.  */
666               _gpgme_io_close (rp[1]);
667               return 0;
668             }
669         }
670
671       do
672         {
673           nwrite = _gpgme_io_write (rp[1], buf, buflen);
674         }
675       while (nwrite < 0 && errno == EAGAIN);
676
677       if (nwrite > 0)
678         {
679           buflen -= nwrite;
680           if (buflen > 0)
681             memmove (&buf[0], &buf[nwrite], buflen);
682         }
683       else if (nwrite < 0)
684         {
685           _gpgme_io_close (rp[1]);
686           return gpg_error_from_syserror ();
687         }
688     }
689
690   return 0;
691 }
692
693
694 static gpgme_error_t
695 arg_to_data (gpgme_data_t conf, gpgme_conf_opt_t option, gpgme_conf_arg_t arg)
696 {
697   gpgme_error_t err = 0;
698   int amt = 0;
699   char buf[16];
700
701   while (amt >= 0 && arg)
702     {
703       switch (option->alt_type)
704         {
705         case GPGME_CONF_NONE:
706         case GPGME_CONF_UINT32:
707         default:
708           snprintf (buf, sizeof (buf), "%u", arg->value.uint32);
709           buf[sizeof (buf) - 1] = '\0';
710           amt = gpgme_data_write (conf, buf, strlen (buf));
711           break;
712           
713         case GPGME_CONF_INT32:
714           snprintf (buf, sizeof (buf), "%i", arg->value.uint32);
715           buf[sizeof (buf) - 1] = '\0';
716           amt = gpgme_data_write (conf, buf, strlen (buf));
717           break;
718           
719         case GPGME_CONF_STRING:
720         case GPGME_CONF_PATHNAME:
721         case GPGME_CONF_LDAP_SERVER:
722           /* One quote character, and three times to allow
723              for percent escaping.  */
724           {
725             char *ptr = arg->value.string;
726             amt = gpgme_data_write (conf, "\"", 1);
727             if (amt < 0)
728               break;
729
730             while (!err && *ptr)
731               {
732                 switch (*ptr)
733                   {
734                   case '%':
735                     amt = gpgme_data_write (conf, "%25", 3);
736                     break;
737
738                   case ':':
739                     amt = gpgme_data_write (conf, "%3a", 3);
740                     break;
741
742                   case ',':
743                     amt = gpgme_data_write (conf, "%2c", 3);
744                     break;
745
746                   default:
747                     amt = gpgme_data_write (conf, ptr, 1);
748                   }
749                 ptr++;
750               }
751           }
752           break;
753         }
754
755       if (amt < 0)
756         break;
757
758       arg = arg->next;
759       /* Comma separator.  */
760       if (arg)
761         amt = gpgme_data_write (conf, ",", 1);
762     }
763
764   if (amt < 0)
765     return gpg_error_from_syserror ();
766   
767   return 0;
768 }
769
770
771 static gpgme_error_t
772 gpgconf_conf_save (void *engine, gpgme_conf_comp_t comp)
773 {
774   gpgme_error_t err;
775   int amt = 0;
776   /* We use a data object to store the new configuration.  */
777   gpgme_data_t conf;
778   gpgme_conf_opt_t option;
779   int something_changed = 0;
780
781   err = gpgme_data_new (&conf);
782   if (err)
783     return err;
784
785   option = comp->options;
786   while (!err && amt >= 0 && option)
787     {
788       if (option->change_value)
789         {
790           unsigned int flags = 0;
791           char buf[16];
792
793           something_changed = 1;
794
795           amt = gpgme_data_write (conf, option->name, strlen (option->name));
796           if (amt >= 0)
797             amt = gpgme_data_write (conf, ":", 1);
798           if (amt < 0)
799             break;
800
801           if (!option->new_value)
802             flags |= GPGME_CONF_DEFAULT;
803           snprintf (buf, sizeof (buf), "%u", flags);
804           buf[sizeof (buf) - 1] = '\0';
805
806           amt = gpgme_data_write (conf, buf, strlen (buf));
807           if (amt >= 0)
808             amt = gpgme_data_write (conf, ":", 1);
809           if (amt < 0)
810             break;
811
812           if (option->new_value)
813             {
814               err = arg_to_data (conf, option, option->new_value);
815               if (err)
816                 break;
817             }
818           amt = gpgme_data_write (conf, "\n", 1);
819         }
820       option = option->next;
821     }
822   if (!err && amt < 0)
823     err = gpg_error_from_syserror ();
824   if (err || !something_changed)
825     goto bail;
826
827   err = gpgme_data_seek (conf, 0, SEEK_SET);
828   if (err)
829     goto bail;
830
831   err = gpgconf_write (engine, "--change-options", comp->name, conf);
832  bail:
833   gpgme_data_release (conf);
834   return err;
835 }
836
837
838 static void
839 gpgconf_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
840 {
841   /* Nothing to do.  */
842 }
843
844 \f
845 /* Currently, we do not use the engine interface for the various
846    operations.  */
847 void
848 _gpgme_conf_release (gpgme_conf_comp_t conf)
849 {
850   gpgconf_config_release (conf);
851 }
852
853 \f
854 struct engine_ops _gpgme_engine_ops_gpgconf =
855   {
856     /* Static functions.  */
857     _gpgme_get_gpgconf_path,
858     gpgconf_get_version,
859     gpgconf_get_req_version,
860     gpgconf_new,
861
862     /* Member functions.  */
863     gpgconf_release,
864     NULL,               /* reset */
865     NULL,               /* set_status_handler */
866     NULL,               /* set_command_handler */
867     NULL,               /* set_colon_line_handler */
868     NULL,               /* set_locale */
869     NULL,               /* decrypt */
870     NULL,               /* delete */
871     NULL,               /* edit */
872     NULL,               /* encrypt */
873     NULL,               /* encrypt_sign */
874     NULL,               /* export */
875     NULL,               /* export_ext */
876     NULL,               /* genkey */
877     NULL,               /* import */
878     NULL,               /* keylist */
879     NULL,               /* keylist_ext */
880     NULL,               /* sign */
881     NULL,               /* trustlist */
882     NULL,               /* verify */
883     NULL,               /* getauditlog */
884     gpgconf_conf_load,
885     gpgconf_conf_save,
886     gpgconf_set_io_cbs,
887     NULL,               /* io_event */
888     NULL                /* cancel */
889   };