* engine-gpgsm.c (_gpgme_gpgsm_set_io_cbs) [ENABLE_GPGSM]: Fixed
[gpgme.git] / gpgme / engine-gpgsm.c
1 /* engine-gpgsm.c -  GpgSM engine
2  *      Copyright (C) 2000 Werner Koch (dd9jn)
3  *      Copyright (C) 2001, 2002 g10 Code GmbH
4  *
5  * This file is part of GPGME.
6  *
7  * GPGME is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * GPGME is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
20  */
21
22 #if HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 /* FIXME: Correct check?  */
27 #ifdef GPGSM_PATH
28 #define ENABLE_GPGSM 1
29 #endif
30
31 #ifdef ENABLE_GPGSM
32
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include <assert.h>
37 #include <unistd.h>
38 #include <locale.h>
39 #include <fcntl.h> /* FIXME */
40
41 #include "rungpg.h"
42 #include "status-table.h"
43
44 #include "gpgme.h"
45 #include "util.h"
46 #include "types.h"
47 #include "ops.h"
48 #include "wait.h"
49 #include "io.h"
50 #include "key.h"
51 #include "sema.h"
52
53 #include "engine-gpgsm.h"
54
55 #include "assuan.h"
56
57 #define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
58                      *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
59 #define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
60
61
62
63 typedef struct
64 {
65   int fd;       /* FD we talk about.  */
66   int dir;      /* Inbound/Outbound, maybe given implicit?  */
67   void *data;   /* Handler-specific data.  */
68   void *tag;    /* ID from the user for gpgme_remove_io_callback.  */
69 } iocb_data_t;
70
71 struct gpgsm_object_s
72 {
73   ASSUAN_CONTEXT assuan_ctx;
74
75   iocb_data_t status_cb;
76
77   /* Input, output etc are from the servers perspective.  */
78   iocb_data_t input_cb;
79   int input_fd_server;
80
81   iocb_data_t output_cb;
82   int output_fd_server;
83
84   iocb_data_t message_cb;
85   int message_fd_server;
86
87   char *command;
88
89   struct
90   {
91     GpgStatusHandler fnc;
92     void *fnc_value;
93   } status;
94
95   struct
96   {
97     GpgColonLineHandler fnc;
98     void *fnc_value;
99     struct
100     {
101       unsigned char *line;
102       int linesize;
103       int linelen;
104     } attic;
105   } colon; 
106
107   struct GpgmeIOCbs io_cbs;
108 };
109
110
111 const char *
112 _gpgme_gpgsm_get_version (void)
113 {
114   static const char *gpgsm_version;
115   DEFINE_STATIC_LOCK (gpgsm_version_lock);
116
117   LOCK (gpgsm_version_lock);
118   if (!gpgsm_version)
119     gpgsm_version = _gpgme_get_program_version (_gpgme_get_gpgsm_path ());
120   UNLOCK (gpgsm_version_lock);
121
122   return gpgsm_version;
123 }
124
125
126 GpgmeError
127 _gpgme_gpgsm_check_version (void)
128 {
129     return _gpgme_compare_versions (_gpgme_gpgsm_get_version (),
130                                   NEED_GPGSM_VERSION)
131     ? 0 : mk_error (Invalid_Engine);
132 }
133
134
135 static void
136 close_notify_handler (int fd, void *opaque)
137 {
138   GpgsmObject gpgsm = opaque;
139   int possibly_done = 0;
140
141   assert (fd != -1);
142   if (gpgsm->status_cb.fd == fd)
143     {
144       if (gpgsm->status_cb.tag)
145         {
146           (*gpgsm->io_cbs.remove) (gpgsm->status_cb.tag);
147           possibly_done = 1;
148         }
149       gpgsm->status_cb.fd = -1;
150     }
151   else if (gpgsm->input_cb.fd == fd)
152     {
153       if (gpgsm->input_cb.tag)
154         {
155           (*gpgsm->io_cbs.remove) (gpgsm->input_cb.tag);
156           possibly_done = 1;
157         }
158       gpgsm->input_cb.fd = -1;
159     }
160   else if (gpgsm->output_cb.fd == fd)
161     {
162       if (gpgsm->output_cb.tag)
163         {      
164           (*gpgsm->io_cbs.remove) (gpgsm->output_cb.tag);
165           possibly_done = 1;
166         }
167       gpgsm->output_cb.fd = -1;
168     }
169   else if (gpgsm->message_cb.fd == fd)
170     {
171       if (gpgsm->message_cb.tag)
172         {
173           (*gpgsm->io_cbs.remove) (gpgsm->message_cb.tag);
174           possibly_done = 1;
175         }
176       gpgsm->message_cb.fd = -1;
177     }
178   if (possibly_done && gpgsm->io_cbs.event
179       && gpgsm->status_cb.fd == -1 && gpgsm->input_cb.fd == -1
180       && gpgsm->output_cb.fd == -1 && gpgsm->message_cb.fd == -1)
181     (*gpgsm->io_cbs.event) (gpgsm->io_cbs.event_priv, GPGME_EVENT_DONE, NULL);
182 }
183
184
185 static GpgmeError
186 map_assuan_error (AssuanError err)
187 {
188   switch (err)
189     {
190     case ASSUAN_No_Error:
191       return mk_error (No_Error);
192     case ASSUAN_General_Error:
193       return mk_error (General_Error);
194     case ASSUAN_Out_Of_Core:
195       return mk_error (Out_Of_Core);
196     case ASSUAN_Invalid_Value:
197       return mk_error (Invalid_Value);
198     case ASSUAN_Read_Error:
199       return mk_error (Read_Error);
200     case ASSUAN_Write_Error:
201       return mk_error (Write_Error);
202
203     case ASSUAN_Timeout:
204     case ASSUAN_Problem_Starting_Server:
205     case ASSUAN_Not_A_Server:
206     case ASSUAN_Not_A_Client:
207     case ASSUAN_Nested_Commands:
208     case ASSUAN_Invalid_Response:
209     case ASSUAN_No_Data_Callback:
210     case ASSUAN_No_Inquire_Callback:
211     case ASSUAN_Connect_Failed:
212     case ASSUAN_Accept_Failed:
213       return mk_error (General_Error);
214
215       /* The following error codes are meant as status codes.  */
216     case ASSUAN_Not_Implemented:
217       return mk_error (Not_Implemented);
218     case ASSUAN_Canceled:
219       return mk_error (Canceled);
220     case ASSUAN_Unsupported_Algorithm:
221       return mk_error (Not_Implemented);  /* XXX Argh.  */
222       
223       /* These are errors internal to GPGME.  */
224     case ASSUAN_No_Data_Available:
225     case ASSUAN_No_Input:
226     case ASSUAN_No_Output:
227     case ASSUAN_Invalid_Command:
228     case ASSUAN_Unknown_Command:
229     case ASSUAN_Syntax_Error:
230     case ASSUAN_Parameter_Error:
231     case ASSUAN_Parameter_Conflict:
232     case ASSUAN_Line_Too_Long:
233     case ASSUAN_Line_Not_Terminated:
234     case ASSUAN_Invalid_Data:
235     case ASSUAN_Unexpected_Command:
236     case ASSUAN_Too_Much_Data:
237     case ASSUAN_Inquire_Unknown:
238     case ASSUAN_Inquire_Error:
239     case ASSUAN_Invalid_Option:
240     case ASSUAN_Invalid_Index:
241     case ASSUAN_Unexpected_Status:
242     case ASSUAN_Unexpected_Data:
243     case ASSUAN_Invalid_Status:
244     case ASSUAN_Not_Confirmed:
245       return mk_error (General_Error);
246
247       /* These are errors in the server.  */
248     case ASSUAN_Server_Fault:
249     case ASSUAN_Server_Resource_Problem:
250     case ASSUAN_Server_IO_Error:
251     case ASSUAN_Server_Bug:
252     case ASSUAN_No_Agent:
253     case ASSUAN_Agent_Error:
254       return mk_error (Invalid_Engine);  /* XXX:  Need something more useful.  */
255
256     case ASSUAN_Bad_Certificate:
257     case ASSUAN_Bad_Certificate_Path:
258     case ASSUAN_Missing_Certificate:
259     case ASSUAN_No_Public_Key:
260     case ASSUAN_No_Secret_Key:
261     case ASSUAN_Invalid_Name:
262     case ASSUAN_Card_Error:     /* XXX: Oh well.  */
263     case ASSUAN_Invalid_Card:   /* XXX: Oh well.  */
264     case ASSUAN_No_PKCS15_App:  /* XXX: Oh well.  */
265     case ASSUAN_Card_Not_Present:       /* XXX: Oh well.  */
266     case ASSUAN_Invalid_Id:     /* XXX: Oh well.  */
267       return mk_error (Invalid_Key);
268
269     case ASSUAN_Bad_Signature:
270       return mk_error (Invalid_Key);  /* XXX: This is wrong.  */
271
272     case ASSUAN_Cert_Revoked:
273     case ASSUAN_No_CRL_For_Cert:
274     case ASSUAN_CRL_Too_Old:
275     case ASSUAN_Not_Trusted:
276       return mk_error (Invalid_Key);  /* XXX Some more details would be good.  */
277
278     default:
279       return mk_error (General_Error);
280     }
281 }
282
283
284 GpgmeError
285 _gpgme_gpgsm_new (GpgsmObject *r_gpgsm)
286 {
287   GpgmeError err = 0;
288   GpgsmObject gpgsm;
289   char *argv[3];
290   int fds[2];
291   int child_fds[4];
292   char *dft_display = NULL;
293   char *dft_ttyname = NULL;
294   char *dft_ttytype = NULL;
295   char *old_lc = NULL;
296   char *dft_lc = NULL;
297   char *optstr;
298   int fdlist[5];
299   int nfds;
300
301   *r_gpgsm = NULL;
302   gpgsm = xtrycalloc (1, sizeof *gpgsm);
303   if (!gpgsm)
304     {
305       err = mk_error (Out_Of_Core);
306       return err;
307     }
308
309   gpgsm->status_cb.fd = -1;
310   gpgsm->status_cb.tag = 0;
311
312   gpgsm->input_cb.fd = -1;
313   gpgsm->input_cb.tag = 0;
314   gpgsm->input_fd_server = -1;
315   gpgsm->output_cb.fd = -1;
316   gpgsm->output_cb.tag = 0;
317   gpgsm->output_fd_server = -1;
318   gpgsm->message_cb.fd = -1;
319   gpgsm->message_cb.tag = 0;
320   gpgsm->message_fd_server = -1;
321
322   gpgsm->status.fnc = 0;
323   gpgsm->colon.fnc = 0;
324   gpgsm->colon.attic.line = 0;
325   gpgsm->colon.attic.linesize = 0;
326   gpgsm->colon.attic.linelen = 0;
327
328   gpgsm->io_cbs.add = NULL;
329   gpgsm->io_cbs.add_priv = NULL;
330   gpgsm->io_cbs.remove = NULL;
331   gpgsm->io_cbs.event = NULL;
332   gpgsm->io_cbs.event_priv = NULL;
333
334   if (_gpgme_io_pipe (fds, 0) < 0)
335     {
336       err = mk_error (Pipe_Error);
337       goto leave;
338     }
339   gpgsm->input_cb.fd = fds[1];
340   gpgsm->input_cb.dir = 0;
341   gpgsm->input_fd_server = fds[0];
342
343   if (_gpgme_io_pipe (fds, 1) < 0)
344     {
345       err = mk_error (Pipe_Error);
346       goto leave;
347     }
348   gpgsm->output_cb.fd = fds[0];
349   gpgsm->output_cb.dir = 1;
350   gpgsm->output_fd_server = fds[1];
351
352   if (_gpgme_io_pipe (fds, 0) < 0)
353     {
354       err = mk_error (Pipe_Error);
355       goto leave;
356     }
357   gpgsm->message_cb.fd = fds[1];
358   gpgsm->message_cb.dir = 0;
359   gpgsm->message_fd_server = fds[0];
360
361   child_fds[0] = gpgsm->input_fd_server;
362   child_fds[1] = gpgsm->output_fd_server;
363   child_fds[2] = gpgsm->message_fd_server;
364   child_fds[3] = -1;
365
366   argv[0] = "gpgsm";
367   argv[1] = "--server";
368   argv[2] = NULL;
369
370   err = assuan_pipe_connect2 (&gpgsm->assuan_ctx,
371                               _gpgme_get_gpgsm_path (), argv, child_fds,
372                               1 /* dup stderr to /dev/null */);
373
374   /* We need to know the fd used by assuan for reads.  We do this by
375      using the assumption that the first returned fd from
376      assuan_get_active_fds() is always this one.  */
377   nfds = assuan_get_active_fds (gpgsm->assuan_ctx, 0 /* read fds */,
378                                 fdlist, DIM (fdlist));
379   if (nfds < 1)
380     {
381       err = mk_error (General_Error);  /* FIXME */
382       goto leave;
383     }
384   /* We duplicate the file descriptor, so we can close it without
385      disturbing assuan.  Alternatively, we could special case
386      status_fd and register/unregister it manually as needed, but this
387      increases code duplication and is more complicated as we can not
388      use the close notifications etc.  */
389   gpgsm->status_cb.fd = dup (fdlist[0]);
390   if (gpgsm->status_cb.fd < 0)
391     {
392       err = mk_error (General_Error);   /* FIXME */
393       goto leave;
394     }
395   gpgsm->status_cb.dir = 1;
396   gpgsm->status_cb.data = gpgsm;
397
398   dft_display = getenv ("DISPLAY");
399   if (dft_display)
400     {
401       if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
402         {
403           err = mk_error (Out_Of_Core);
404           goto leave;
405         }
406       err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL,
407                              NULL, NULL, NULL);
408       free (optstr);
409       if (err)
410         {
411           err = map_assuan_error (err);
412           goto leave;
413         }
414     }
415   dft_ttyname = ttyname (1);
416   if (dft_ttyname)
417     {
418       if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
419         {
420           err = mk_error (Out_Of_Core);
421           goto leave;
422         }
423       err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
424                              NULL);
425       free (optstr);
426       if (err)
427         {
428           err = map_assuan_error (err);
429           goto leave;
430         }
431
432       dft_ttytype = getenv ("TERM");
433       if (dft_ttytype)
434         {
435           if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0)
436             {
437               err = mk_error (Out_Of_Core);
438               goto leave;
439             }
440           err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
441                                  NULL);
442           free (optstr);
443           if (err)
444             {
445               err = map_assuan_error (err);
446               goto leave;
447             }
448         }
449       old_lc = setlocale (LC_CTYPE, NULL);
450       dft_lc = setlocale (LC_CTYPE, "");
451       if (dft_lc)
452         {
453           if (asprintf (&optstr, "OPTION lc-ctype=%s", dft_lc) < 0)
454             err = mk_error (Out_Of_Core);
455           else
456             {
457               err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
458                                      NULL);
459               free (optstr);
460               if (err)
461                 err = map_assuan_error (err);
462             }
463         }
464       if (old_lc)
465         setlocale (LC_CTYPE, old_lc);
466       if (err)
467         goto leave;
468
469       old_lc = setlocale (LC_MESSAGES, NULL);
470       dft_lc = setlocale (LC_MESSAGES, "");
471       if (dft_lc)
472         {
473           if (asprintf (&optstr, "OPTION lc-messages=%s", dft_lc) < 0)
474             err = mk_error (Out_Of_Core);
475           else
476             {
477               err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
478                                      NULL);
479               free (optstr);
480               if (err)
481                 err = map_assuan_error (err);
482             }
483         }
484       if (old_lc)
485         setlocale (LC_MESSAGES, old_lc);
486       if (err)
487         goto leave;
488     }
489
490   if (!err &&
491       (_gpgme_io_set_close_notify (gpgsm->status_cb.fd,
492                                    close_notify_handler, gpgsm)
493        || _gpgme_io_set_close_notify (gpgsm->input_cb.fd,
494                                    close_notify_handler, gpgsm)
495        || _gpgme_io_set_close_notify (gpgsm->output_cb.fd,
496                                       close_notify_handler, gpgsm)
497        || _gpgme_io_set_close_notify (gpgsm->message_cb.fd,
498                                       close_notify_handler, gpgsm)))
499     {
500       err = mk_error (General_Error);
501       goto leave;
502     }
503       
504  leave:
505   /* Close the server ends of the pipes.  Our ends are closed in
506      _gpgme_gpgsm_release.  */
507   if (gpgsm->input_fd_server != -1)
508     _gpgme_io_close (gpgsm->input_fd_server);
509   if (gpgsm->output_fd_server != -1)
510     _gpgme_io_close (gpgsm->output_fd_server);
511   if (gpgsm->message_fd_server != -1)
512     _gpgme_io_close (gpgsm->message_fd_server);
513
514   if (err)
515     _gpgme_gpgsm_release (gpgsm);
516   else
517     *r_gpgsm = gpgsm;
518
519   return err;
520 }
521
522
523 void
524 _gpgme_gpgsm_release (GpgsmObject gpgsm)
525 {
526   if (!gpgsm)
527     return;
528
529   if (gpgsm->status_cb.fd != -1)
530     _gpgme_io_close (gpgsm->status_cb.fd);
531   if (gpgsm->input_cb.fd != -1)
532     _gpgme_io_close (gpgsm->input_cb.fd);
533   if (gpgsm->output_cb.fd != -1)
534     _gpgme_io_close (gpgsm->output_cb.fd);
535   if (gpgsm->message_cb.fd != -1)
536     _gpgme_io_close (gpgsm->message_cb.fd);
537
538   assuan_disconnect (gpgsm->assuan_ctx);
539
540   xfree (gpgsm->colon.attic.line);
541   xfree (gpgsm->command);
542   xfree (gpgsm);
543 }
544
545 /* Forward declaration.  */
546 static GpgStatusCode parse_status (const char *name);
547
548 static GpgmeError
549 gpgsm_assuan_simple_command (ASSUAN_CONTEXT ctx, char *cmd, GpgStatusHandler status_fnc,
550                              void *status_fnc_value)
551 {
552   AssuanError err;
553   char *line;
554   size_t linelen;
555
556   err = assuan_write_line (ctx, cmd);
557   if (err)
558     return map_assuan_error (err);
559
560   do
561     {
562       err = assuan_read_line (ctx, &line, &linelen);
563       if (err)
564         return map_assuan_error (err);
565
566       if (*line == '#' || !linelen)
567         continue;
568
569       if (linelen >= 2
570           && line[0] == 'O' && line[1] == 'K'
571           && (line[2] == '\0' || line[2] == ' '))
572         return 0;
573       else if (linelen >= 4
574           && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
575           && line[3] == ' ')
576         err = map_assuan_error (atoi (&line[4]));
577       else if (linelen >= 2
578                && line[0] == 'S' && line[1] == ' ')
579         {
580           char *rest;
581           GpgStatusCode r;
582
583           rest = strchr (line + 2, ' ');
584           if (!rest)
585             rest = line + linelen; /* set to an empty string */
586           else
587             *(rest++) = 0;
588
589           r = parse_status (line + 2);
590
591           if (r >= 0 && status_fnc)
592             status_fnc (status_fnc_value, r, rest);
593           else
594             err = mk_error (General_Error);
595         }
596       else
597         err = mk_error (General_Error);
598     }
599   while (!err);
600
601   return err;
602 }
603
604
605 #define COMMANDLINELEN 40
606 static GpgmeError
607 gpgsm_set_fd (ASSUAN_CONTEXT ctx, const char *which, int fd, const char *opt)
608 {
609   char line[COMMANDLINELEN];
610
611   if (opt)
612     snprintf (line, COMMANDLINELEN, "%s FD=%i %s", which, fd, opt);
613   else
614     snprintf (line, COMMANDLINELEN, "%s FD=%i", which, fd);
615
616   return gpgsm_assuan_simple_command (ctx, line, NULL, NULL);
617 }
618
619
620 static const char *
621 map_input_enc (GpgmeData d)
622 {
623   switch (gpgme_data_get_encoding (d))
624     {
625     case GPGME_DATA_ENCODING_NONE:
626       break;
627     case GPGME_DATA_ENCODING_BINARY:
628       return "--binary";
629     case GPGME_DATA_ENCODING_BASE64:
630       return "--base64";
631     case GPGME_DATA_ENCODING_ARMOR:
632       return "--armor";
633     default:
634       break;
635     }
636   return NULL;
637 }
638
639
640 GpgmeError
641 _gpgme_gpgsm_op_decrypt (GpgsmObject gpgsm, GpgmeData ciph, GpgmeData plain)
642 {
643   GpgmeError err;
644
645   if (!gpgsm)
646     return mk_error (Invalid_Value);
647
648   gpgsm->command = xtrystrdup ("DECRYPT");
649   if (!gpgsm->command)
650     return mk_error (Out_Of_Core);
651
652   gpgsm->input_cb.data = ciph;
653   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 
654                       map_input_enc (gpgsm->input_cb.data));
655   if (err)
656     return mk_error (General_Error);    /* FIXME */
657   gpgsm->output_cb.data = plain;
658   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, 0);
659   if (err)
660     return mk_error (General_Error);    /* FIXME */
661   _gpgme_io_close (gpgsm->message_cb.fd);
662
663   return 0;
664 }
665
666
667 GpgmeError
668 _gpgme_gpgsm_op_delete (GpgsmObject gpgsm, GpgmeKey key, int allow_secret)
669 {
670   /* FIXME */
671   return mk_error (Not_Implemented);
672 }
673
674
675 static GpgmeError
676 gpgsm_set_recipients (GpgsmObject gpgsm, GpgmeRecipients recp)
677 {
678   GpgmeError err;
679   ASSUAN_CONTEXT ctx = gpgsm->assuan_ctx;
680   char *line;
681   int linelen;
682   struct user_id_s *r;
683   int valid_recipients = 0;
684
685   linelen = 10 + 40 + 1;        /* "RECIPIENT " + guess + '\0'.  */
686   line = xtrymalloc (10 + 40 + 1);
687   if (!line)
688     return mk_error (Out_Of_Core);
689   strcpy (line, "RECIPIENT ");
690   for (r = recp->list; r; r = r->next)
691     {
692       int newlen = 11 + strlen (r->name);
693       if (linelen < newlen)
694         {
695           char *newline = xtryrealloc (line, newlen);
696           if (! newline)
697             {
698               xfree (line);
699               return mk_error (Out_Of_Core);
700             }
701           line = newline;
702           linelen = newlen;
703         }
704       strcpy (&line[10], r->name);
705       
706       err = gpgsm_assuan_simple_command (ctx, line, gpgsm->status.fnc,
707                                          gpgsm->status.fnc_value);
708       if (!err)
709         valid_recipients = 1;
710       else if (err != GPGME_Invalid_Key)
711         {
712           xfree (line);
713           return err;
714         }
715     }
716   xfree (line);
717   if (!valid_recipients && gpgsm->status.fnc)
718     gpgsm->status.fnc (gpgsm->status.fnc_value, STATUS_NO_RECP, "");
719   return 0;
720 }
721
722
723 GpgmeError
724 _gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp,
725                          GpgmeData plain, GpgmeData ciph, int use_armor)
726 {
727   GpgmeError err;
728
729   if (!gpgsm)
730     return mk_error (Invalid_Value);
731   if (!recp)
732     return mk_error (Not_Implemented);
733
734   gpgsm->command = xtrystrdup ("ENCRYPT");
735   if (!gpgsm->command)
736     return mk_error (Out_Of_Core);
737
738   gpgsm->input_cb.data = plain;
739   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server,
740                       map_input_enc (gpgsm->input_cb.data));
741   if (err)
742     return err;
743   gpgsm->output_cb.data = ciph;
744   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
745                       use_armor ? "--armor" : 0);
746   if (err)
747     return err;
748   _gpgme_io_close (gpgsm->message_cb.fd);
749
750   err = gpgsm_set_recipients (gpgsm, recp);
751   if (err)
752     return err;
753
754   return 0;
755 }
756
757
758 GpgmeError
759 _gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp,
760                         GpgmeData keydata, int use_armor)
761 {
762   GpgmeError err = 0;
763   char *cmd = NULL;
764   int cmdi;
765   int cmdlen = 32;
766
767   if (!gpgsm)
768     return mk_error (Invalid_Value);
769
770   cmd = malloc (cmdlen);
771   if (!cmd)
772     return mk_error (Out_Of_Core);
773   strcpy (cmd, "EXPORT");
774   cmdi = 6;
775
776   if (recp)
777     {
778       void *ec;
779       const char *s;
780
781       err = gpgme_recipients_enum_open (recp, &ec);
782       while (!err && (s = gpgme_recipients_enum_read (recp, &ec)))
783         {
784           int slen = strlen (s);
785           /* New string is old string + ' ' + s + '\0'.  */
786           if (cmdlen < cmdi + 1 + slen + 1)
787             {
788               char *newcmd = xtryrealloc (cmd, cmdlen * 2);
789               if (!newcmd)
790                 {
791                   xfree (cmd);
792                   return mk_error (Out_Of_Core);
793                 }
794               cmd = newcmd;
795               cmdlen *= 2;
796             }
797           cmd[cmdi++] = ' ';
798           strcpy (cmd + cmdi, s);
799           cmdi += slen;
800         }
801       if (!err)
802         err = gpgme_recipients_enum_close (recp, &ec);
803       if (err)
804         return err;
805     }
806
807   gpgsm->command = cmd;
808
809   gpgsm->output_cb.data = keydata;
810   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
811                       use_armor ? "--armor" : 0);
812   if (err)
813     return err;
814   _gpgme_io_close (gpgsm->input_cb.fd);
815   _gpgme_io_close (gpgsm->message_cb.fd);
816
817   return 0;
818 }
819
820
821 GpgmeError
822 _gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor,
823                         GpgmeData pubkey, GpgmeData seckey)
824 {
825   GpgmeError err;
826
827   if (!gpgsm || !pubkey || seckey)
828     return mk_error (Invalid_Value);
829
830   gpgsm->command = xtrystrdup ("GENKEY");
831   if (!gpgsm->command)
832     return mk_error (Out_Of_Core);
833
834   gpgsm->input_cb.data = help_data;
835   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server,
836                       map_input_enc (gpgsm->input_cb.data));
837   if (err)
838     return err;
839   gpgsm->output_cb.data = pubkey;
840   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
841                       use_armor ? "--armor" : 0);
842   if (err)
843     return err;
844   _gpgme_io_close (gpgsm->message_cb.fd);
845
846   return 0;
847 }
848
849
850 GpgmeError
851 _gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata)
852 {
853   GpgmeError err;
854
855   if (!gpgsm)
856     return mk_error (Invalid_Value);
857
858   gpgsm->command = xtrystrdup ("IMPORT");
859   if (!gpgsm->command)
860     return mk_error (Out_Of_Core);
861
862   gpgsm->input_cb.data = keydata;
863   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server,
864                       map_input_enc (gpgsm->input_cb.data));
865   if (err)
866     return err;
867   _gpgme_io_close (gpgsm->output_cb.fd);
868   _gpgme_io_close (gpgsm->message_cb.fd);
869
870   return 0;
871 }
872
873
874 GpgmeError
875 _gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern,
876                          int secret_only, int keylist_mode)
877 {
878   char *line;
879   GpgmeError err;
880
881   if (!pattern)
882     pattern = "";
883
884   if (asprintf (&line, "OPTION list-mode=%d", (keylist_mode & 3)) < 0)
885     return mk_error (Out_Of_Core);
886   err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, line, NULL, NULL);
887   free (line);
888   if (err)
889     return err;
890
891   /* Length is "LISTSECRETKEYS " + p + '\0'.  */
892   line = xtrymalloc (15 + strlen (pattern) + 1);
893   if (!line)
894     return mk_error (Out_Of_Core);
895   if (secret_only)
896     {
897       strcpy (line, "LISTSECRETKEYS ");
898       strcpy (&line[15], pattern);
899     }
900   else
901     {
902       strcpy (line, "LISTKEYS ");
903       strcpy (&line[9], pattern);
904     }
905
906   _gpgme_io_close (gpgsm->input_cb.fd);
907   _gpgme_io_close (gpgsm->output_cb.fd);
908   _gpgme_io_close (gpgsm->message_cb.fd);
909
910   gpgsm->command = line;
911   return 0;
912 }
913
914
915 GpgmeError
916 _gpgme_gpgsm_op_keylist_ext (GpgsmObject gpgsm, const char *pattern[],
917                              int secret_only, int reserved, int keylist_mode)
918 {
919   char *line;
920   GpgmeError err;
921   /* Length is "LISTSECRETKEYS " + p + '\0'.  */
922   int length = 15 + 1;
923   char *linep;
924   
925   if (reserved)
926     return mk_error (Invalid_Value);
927
928   if (asprintf (&line, "OPTION list-mode=%d", (keylist_mode & 3)) < 0)
929     return mk_error (Out_Of_Core);
930   err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, line, NULL, NULL);
931   free (line);
932   if (err)
933     return err;
934
935   if (pattern && *pattern)
936     {
937       const char **pat = pattern;
938
939       while (*pat)
940         {
941           const char *patlet = *pat;
942
943           while (*patlet)
944             {
945               length++;
946               if (*patlet == '%' || *patlet == ' ' || *patlet == '+')
947                 length += 2;
948               patlet++;
949             }
950           pat++;
951           /* This will allocate one byte more than necessary.  */
952           length++;
953         }
954     }
955   line = xtrymalloc (length);
956   if (!line)
957     return mk_error (Out_Of_Core);
958   if (secret_only)
959     {
960       strcpy (line, "LISTSECRETKEYS ");
961       linep = &line[15];
962     }
963   else
964     {
965       strcpy (line, "LISTKEYS ");
966       linep = &line[9];
967     }
968
969   if (pattern && *pattern)
970     {
971       while (*pattern)
972         {
973           const char *patlet = *pattern;
974
975           while (*patlet)
976             {
977               switch (*patlet)
978                 {
979                 case '%':
980                   *(linep++) = '%';
981                   *(linep++) = '2';
982                   *(linep++) = '5';
983                   break;
984                 case ' ':
985                   *(linep++) = '%';
986                   *(linep++) = '2';
987                   *(linep++) = '0';
988                   break;
989                 case '+':
990                   *(linep++) = '%';
991                   *(linep++) = '2';
992                   *(linep++) = 'B';
993                   break;
994                 default:
995                   *(linep++) = *patlet;
996                   break;
997                 }
998               patlet++;
999             }
1000           pattern++;
1001         }
1002     }
1003   *linep = '\0';
1004
1005   _gpgme_io_close (gpgsm->input_cb.fd);
1006   _gpgme_io_close (gpgsm->output_cb.fd);
1007   _gpgme_io_close (gpgsm->message_cb.fd);
1008
1009   gpgsm->command = line;
1010   return 0;
1011 }
1012
1013
1014 GpgmeError
1015 _gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out,
1016                       GpgmeSigMode mode, int use_armor,
1017                       int use_textmode, int include_certs,
1018                       GpgmeCtx ctx /* FIXME */)
1019 {
1020   GpgmeError err;
1021   char *assuan_cmd;
1022
1023   if (!gpgsm)
1024     return mk_error (Invalid_Value);
1025
1026   gpgsm->command = xtrystrdup (mode == GPGME_SIG_MODE_DETACH
1027                                ? "SIGN --detached" : "SIGN");
1028   if (!gpgsm->command)
1029     return mk_error (Out_Of_Core);
1030
1031   if (asprintf (&assuan_cmd, "OPTION include-certs %i", include_certs) < 0)
1032     return mk_error (Out_Of_Core);
1033   err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, assuan_cmd, NULL, NULL);
1034   free (assuan_cmd);
1035   if (err)
1036     return err;
1037
1038   gpgsm->input_cb.data = in;
1039   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server,
1040                       map_input_enc (gpgsm->input_cb.data));
1041   if (err)
1042     return err;
1043   gpgsm->output_cb.data = out;
1044   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
1045                       use_armor ? "--armor" : 0);
1046   if (err)
1047     return err;
1048   _gpgme_io_close (gpgsm->message_cb.fd);
1049
1050   return 0;
1051 }
1052
1053
1054 GpgmeError
1055 _gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern)
1056 {
1057   /* FIXME */
1058   return mk_error (Not_Implemented);
1059 }
1060
1061
1062 GpgmeError
1063 _gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text)
1064 {
1065   GpgmeError err;
1066
1067   if (!gpgsm)
1068     return mk_error (Invalid_Value);
1069
1070   gpgsm->command = xtrystrdup ("VERIFY");
1071   if (!gpgsm->command)
1072     return mk_error (Out_Of_Core);
1073
1074   gpgsm->input_cb.data = sig;
1075   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server,
1076                       map_input_enc (gpgsm->input_cb.data));
1077   if (err)
1078     return err;
1079   if (_gpgme_data_get_mode (text) == GPGME_DATA_MODE_IN)
1080     {
1081       /* Normal or cleartext signature.  */
1082       gpgsm->output_cb.data = text;
1083       err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
1084                           0);
1085       _gpgme_io_close (gpgsm->message_cb.fd);
1086     }
1087   else
1088     {
1089       /* Detached signature.  */
1090       gpgsm->message_cb.data = text;
1091       err = gpgsm_set_fd (gpgsm->assuan_ctx, "MESSAGE",
1092                           gpgsm->message_fd_server, 0);
1093       _gpgme_io_close (gpgsm->output_cb.fd);
1094     }
1095   if (err)
1096     return err;
1097
1098   return 0;
1099 }
1100
1101
1102 static int
1103 status_cmp (const void *ap, const void *bp)
1104 {
1105   const struct status_table_s *a = ap;
1106   const struct status_table_s *b = bp;
1107
1108   return strcmp (a->name, b->name);
1109 }
1110
1111
1112 static GpgStatusCode
1113 parse_status (const char *name)
1114 {
1115   struct status_table_s t, *r;
1116   t.name = name;
1117   r = bsearch (&t, status_table, DIM(status_table) - 1,
1118                sizeof t, status_cmp);
1119   return r ? r->code : -1;
1120 }
1121
1122
1123 static void
1124 gpgsm_status_handler (void *opaque, int fd)
1125 {
1126   AssuanError err;
1127   GpgsmObject gpgsm = opaque;
1128   char *line;
1129   size_t linelen;
1130
1131   do
1132     {
1133       err = assuan_read_line (gpgsm->assuan_ctx, &line, &linelen);
1134
1135       if (err
1136           || (linelen >= 2
1137               && line[0] == 'O' && line[1] == 'K'
1138               && (line[2] == '\0' || line[2] == ' '))
1139           || (linelen >= 3
1140               && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
1141               && (line[3] == '\0' || line[3] == ' ')))
1142         {
1143           /* XXX: If an error occured, find out what happened, then
1144              save the error value before running the status handler
1145              (so it takes precedence).  */
1146           if (!err && line[0] == 'E' && line[3] == ' ')
1147             {
1148               err = map_assuan_error (atoi (&line[4]));
1149               if (!err)
1150                 err = mk_error (General_Error);
1151             }
1152           if (err)
1153             {
1154               /* XXX Kludge ahead.  We really, really, really must not
1155                  make use of status.fnc_value.  */
1156               GpgmeCtx ctx = (GpgmeCtx) gpgsm->status.fnc_value;
1157               if (!ctx->error)
1158                 ctx->error = err;
1159             }
1160
1161           if (gpgsm->status.fnc)
1162             gpgsm->status.fnc (gpgsm->status.fnc_value, STATUS_EOF, "");
1163
1164           /* XXX: Try our best to terminate the connection.  */
1165           if (err)
1166             assuan_write_line (gpgsm->assuan_ctx, "BYE");
1167
1168           _gpgme_io_close (gpgsm->status_cb.fd);
1169           return;
1170         }
1171
1172       if (linelen > 2
1173           && line[0] == 'D' && line[1] == ' '
1174           && gpgsm->colon.fnc)
1175         {
1176           /* We are using the colon handler even for plain inline data
1177              - strange name for that function but for historic reasons
1178              we keep it.  */
1179           /* FIXME We can't use this for binary data because we
1180              assume this is a string.  For the current usage of colon
1181              output it is correct.  */
1182           unsigned char *src = line + 2;
1183           unsigned char *end = line + linelen;
1184           unsigned char *dst;
1185           unsigned char **aline = &gpgsm->colon.attic.line;
1186           int *alinelen = &gpgsm->colon.attic.linelen;
1187
1188           if (gpgsm->colon.attic.linesize
1189               < *alinelen + linelen + 1)
1190             {
1191               unsigned char *newline = xtryrealloc (*aline,
1192                                                     *alinelen + linelen + 1);
1193               if (!newline)
1194                 {
1195                   _gpgme_io_close (gpgsm->status_cb.fd);
1196                   return;
1197                 }
1198               *aline = newline;
1199               gpgsm->colon.attic.linesize += linelen + 1;
1200             }
1201
1202           dst = *aline + *alinelen;
1203
1204           while (src < end)
1205             {
1206               if (*src == '%' && src + 2 < end)
1207                 {
1208                   /* Handle escaped characters.  */
1209                   ++src;
1210                   *dst = xtoi_2 (src);
1211                   (*alinelen)++;
1212                   src += 2;
1213                 }
1214               else
1215                 {
1216                   *dst = *src++;
1217                   (*alinelen)++;
1218                 }
1219
1220               if (*dst == '\n')
1221                 {
1222                   /* Terminate the pending line, pass it to the colon
1223                      handler and reset it.  */
1224
1225                   if (*alinelen > 1 && *(dst - 1) == '\r')
1226                     dst--;
1227                   *dst = '\0';
1228
1229                   /* FIXME How should we handle the return code? */
1230                   gpgsm->colon.fnc (gpgsm->colon.fnc_value, *aline);
1231                   dst = *aline;
1232                   *alinelen = 0;
1233                 }
1234               else
1235                 dst++;
1236             }
1237         }
1238       else if (linelen > 2
1239           && line[0] == 'S' && line[1] == ' ')
1240         {
1241           char *rest;
1242           GpgStatusCode r;
1243
1244           rest = strchr (line + 2, ' ');
1245           if (!rest)
1246             rest = line + linelen; /* set to an empty string */
1247           else
1248             *(rest++) = 0;
1249
1250           r = parse_status (line + 2);
1251
1252           if (r >= 0)
1253             {
1254               if (gpgsm->status.fnc)
1255                 gpgsm->status.fnc (gpgsm->status.fnc_value, r, rest);
1256             }
1257           else
1258             fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest);
1259         }
1260     }
1261   while (assuan_pending_line (gpgsm->assuan_ctx));
1262 }
1263
1264
1265 void
1266 _gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm,
1267                                  GpgStatusHandler fnc, void *fnc_value) 
1268 {
1269   assert (gpgsm);
1270
1271   gpgsm->status.fnc = fnc;
1272   gpgsm->status.fnc_value = fnc_value;
1273 }
1274
1275
1276 void
1277 _gpgme_gpgsm_set_colon_line_handler (GpgsmObject gpgsm,
1278                                      GpgColonLineHandler fnc, void *fnc_value) 
1279 {
1280   assert (gpgsm);
1281
1282   gpgsm->colon.fnc = fnc;
1283   gpgsm->colon.fnc_value = fnc_value;
1284 }
1285
1286
1287 static GpgmeError
1288 _gpgme_gpgsm_add_io_cb (GpgsmObject gpgsm, iocb_data_t *iocbd,
1289                         GpgmeIOCb handler)
1290 {
1291   GpgmeError err = 0;
1292
1293   iocbd->tag = (*gpgsm->io_cbs.add) (gpgsm->io_cbs.add_priv,
1294                                      iocbd->fd, iocbd->dir,
1295                                      handler, iocbd->data);
1296   if (!iocbd->tag)
1297     err = mk_error (General_Error);
1298   if (!err && !iocbd->dir)
1299     /* FIXME Kludge around poll() problem.  */
1300     err = _gpgme_io_set_nonblocking (iocbd->fd);
1301   return err;
1302 }
1303
1304 GpgmeError
1305 _gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque)
1306 {
1307   GpgmeError err = 0;
1308   pid_t pid;
1309
1310   if (!gpgsm)
1311     return mk_error (Invalid_Value);
1312
1313   pid = assuan_get_pid (gpgsm->assuan_ctx);
1314
1315   err = _gpgme_gpgsm_add_io_cb (gpgsm, &gpgsm->status_cb,
1316                                 gpgsm_status_handler);
1317   if (gpgsm->input_cb.fd != -1)
1318     err = _gpgme_gpgsm_add_io_cb (gpgsm, &gpgsm->input_cb,
1319                                   _gpgme_data_outbound_handler);
1320   if (!err && gpgsm->output_cb.fd != -1)
1321     err = _gpgme_gpgsm_add_io_cb (gpgsm, &gpgsm->output_cb,
1322                                   _gpgme_data_inbound_handler);
1323   if (!err && gpgsm->message_cb.fd != -1)
1324     err = _gpgme_gpgsm_add_io_cb (gpgsm, &gpgsm->message_cb,
1325                                   _gpgme_data_outbound_handler);
1326
1327   if (!err)
1328     err = assuan_write_line (gpgsm->assuan_ctx, gpgsm->command);
1329
1330   return err;
1331 }
1332
1333 void
1334 _gpgme_gpgsm_set_io_cbs (GpgsmObject gpgsm, struct GpgmeIOCbs *io_cbs)
1335 {
1336   gpgsm->io_cbs = *io_cbs;
1337 }
1338
1339
1340 #else   /* ENABLE_GPGSM */
1341
1342
1343 #include <stddef.h>
1344 #include "util.h"
1345
1346 #include "engine-gpgsm.h"
1347
1348
1349 const char *
1350 _gpgme_gpgsm_get_version (void)
1351 {
1352   return NULL;
1353 }
1354
1355
1356 GpgmeError
1357 _gpgme_gpgsm_check_version (void)
1358 {
1359   return mk_error (Invalid_Engine);
1360 }
1361
1362
1363 GpgmeError
1364 _gpgme_gpgsm_new (GpgsmObject *r_gpgsm)
1365 {
1366   return mk_error (Invalid_Engine);
1367 }
1368
1369
1370 void
1371 _gpgme_gpgsm_release (GpgsmObject gpgsm)
1372 {
1373   return;
1374 }
1375
1376
1377 void
1378 _gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm,
1379                                  GpgStatusHandler fnc, void *fnc_value) 
1380 {
1381   return;
1382 }
1383
1384
1385 GpgmeError
1386 _gpgme_gpgsm_op_decrypt (GpgsmObject gpgsm, GpgmeData ciph, GpgmeData plain)
1387 {
1388   return mk_error (Invalid_Engine);
1389 }
1390
1391
1392 GpgmeError
1393 _gpgme_gpgsm_op_delete (GpgsmObject gpgsm, GpgmeKey key, int allow_secret)
1394 {
1395   return mk_error (Invalid_Engine);
1396 }
1397
1398
1399 GpgmeError
1400 _gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp,
1401                          GpgmeData plain, GpgmeData ciph, int use_armor)
1402 {
1403   return mk_error (Invalid_Engine);
1404 }
1405
1406
1407 GpgmeError
1408 _gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp,
1409                         GpgmeData keydata, int use_armor)
1410 {
1411   return mk_error (Invalid_Engine);
1412 }
1413
1414
1415 GpgmeError
1416 _gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor,
1417                         GpgmeData pubkey, GpgmeData seckey)
1418 {
1419   return mk_error (Invalid_Engine);
1420 }
1421   
1422
1423 GpgmeError
1424 _gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata)
1425 {
1426   return mk_error (Invalid_Engine);
1427 }
1428
1429
1430 GpgmeError
1431 _gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern,
1432                          int secret_only, int keylist_mode)
1433 {
1434   return mk_error (Invalid_Engine);
1435 }
1436
1437
1438 GpgmeError
1439 _gpgme_gpgsm_op_keylist_ext (GpgsmObject gpgsm, const char *pattern[],
1440                              int secret_only, int reserved, int keylist_mode)
1441 {
1442   return mk_error (Invalid_Engine);
1443 }
1444
1445 GpgmeError
1446 _gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out,
1447                       GpgmeSigMode mode, int use_armor,
1448                       int use_textmode, int include_certs,
1449                       GpgmeCtx ctx /* FIXME */)
1450 {
1451   return mk_error (Invalid_Engine);
1452 }
1453
1454
1455 GpgmeError
1456 _gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern)
1457 {
1458   return mk_error (Invalid_Engine);
1459 }
1460
1461
1462 GpgmeError
1463 _gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text)
1464 {
1465   return mk_error (Invalid_Engine);
1466 }
1467
1468
1469 void
1470 _gpgme_gpgsm_set_colon_line_handler (GpgsmObject gpgsm,
1471                                      GpgColonLineHandler fnc, void *fnc_value) 
1472 {
1473 }
1474
1475
1476 GpgmeError
1477 _gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque)
1478 {
1479   return mk_error (Invalid_Engine);
1480 }
1481
1482 void
1483 _gpgme_gpgsm_set_io_cbs (GpgsmObject gpgsm, struct GpgmeIOCbs *io_cbs)
1484 {
1485 }
1486
1487 #endif  /* ! ENABLE_GPGSM */