* engine-gpgsm.c (_gpgme_gpgsm_set_colon_line_handler): New.
[gpgme.git] / gpgme / engine-gpgsm.c
1 /* engine-gpgsm.c -  GpgSM engine
2  *      Copyright (C) 2000 Werner Koch (dd9jn)
3  *      Copyright (C) 2001 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 <fcntl.h> /* FIXME */
38
39 #include "rungpg.h"
40 #include "status-table.h"
41
42 #include "gpgme.h"
43 #include "util.h"
44 #include "types.h"
45 #include "ops.h"
46 #include "wait.h"
47 #include "io.h"
48 #include "key.h"
49
50 #include "engine-gpgsm.h"
51
52 #include "assuan.h"
53
54 #define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
55                      *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
56 #define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
57
58
59 struct gpgsm_object_s
60 {
61   ASSUAN_CONTEXT assuan_ctx;
62
63   /* Input, output etc are from the servers perspective.  */
64   int input_fd;
65   int input_fd_server;
66   GpgmeData input_data;
67   int output_fd;
68   int output_fd_server;
69   GpgmeData output_data;
70   int message_fd;
71   int message_fd_server;
72   GpgmeData message_data;
73
74   char *command;
75
76   struct
77   {
78     GpgStatusHandler fnc;
79     void *fnc_value;
80   } status;
81
82   struct
83   {
84     GpgColonLineHandler fnc;
85     void *fnc_value;
86   } colon;
87   
88 };
89
90 const char *
91 _gpgme_gpgsm_get_version (void)
92 {
93   static const char *gpgsm_version;
94
95   /* FIXME: Locking.  */
96   if (!gpgsm_version)
97     gpgsm_version = _gpgme_get_program_version (_gpgme_get_gpgsm_path ());
98
99   return gpgsm_version;
100 }
101
102 GpgmeError
103 _gpgme_gpgsm_check_version (void)
104 {
105     return _gpgme_compare_versions (_gpgme_gpgsm_get_version (),
106                                   NEED_GPGSM_VERSION)
107     ? 0 : mk_error (Invalid_Engine);
108 }
109
110 GpgmeError
111 _gpgme_gpgsm_new (GpgsmObject *r_gpgsm)
112 {
113   GpgmeError err = 0;
114   GpgsmObject gpgsm;
115   char *argv[] = { "gpgsm", "--server", NULL };
116   int ip[2] = { -1, -1 };
117   int op[2] = { -1, -1 };
118   int mp[2] = { -1, -1 };
119
120   *r_gpgsm = NULL;
121   gpgsm = xtrycalloc (1, sizeof *gpgsm);
122   if (!gpgsm)
123     {
124       err = mk_error (Out_Of_Core);
125       goto leave;
126     }
127
128   if (_gpgme_io_pipe (ip, 0) < 0)
129     {
130       err = mk_error (General_Error);
131       goto leave;
132     }
133   gpgsm->input_fd = ip[1];
134   fcntl (ip[1], F_SETFD, FD_CLOEXEC); /* FIXME */
135   gpgsm->input_fd_server = ip[0];
136   if (_gpgme_io_pipe (op, 1) < 0)
137     {
138       err = mk_error (General_Error);
139       goto leave;
140     }
141   gpgsm->output_fd = op[0];
142   fcntl (op[0], F_SETFD, FD_CLOEXEC); /* FIXME */
143   gpgsm->output_fd_server = op[1];
144   if (_gpgme_io_pipe (mp, 0) < 0)
145     {
146       err = mk_error (General_Error);
147       goto leave;
148     }
149   gpgsm->message_fd = mp[1];
150   fcntl (mp[1], F_SETFD, FD_CLOEXEC); /* FIXME */
151   gpgsm->message_fd_server = mp[0];
152
153   err = assuan_pipe_connect (&gpgsm->assuan_ctx,
154                              _gpgme_get_gpgsm_path (), argv);
155
156  leave:
157   if (ip[0] != -1)
158     _gpgme_io_close (ip[0]);
159   if (op[1] != -1)
160     _gpgme_io_close (op[1]);
161   if (mp[0] != -1)
162     _gpgme_io_close (mp[0]);
163
164   if (err)
165     _gpgme_gpgsm_release (gpgsm);
166   else
167     *r_gpgsm = gpgsm;
168
169   return err;
170 }
171
172 void
173 _gpgme_gpgsm_release (GpgsmObject gpgsm)
174 {
175   pid_t pid;
176
177   if (!gpgsm)
178     return;
179
180   pid = assuan_get_pid (gpgsm->assuan_ctx);
181   if (pid != -1)
182     _gpgme_remove_proc_from_wait_queue (pid);
183
184   if (gpgsm->input_fd != -1)
185     _gpgme_io_close (gpgsm->input_fd);
186   if (gpgsm->output_fd != -1)
187     _gpgme_io_close (gpgsm->output_fd);
188   if (gpgsm->message_fd != -1)
189     _gpgme_io_close (gpgsm->message_fd);
190
191   assuan_pipe_disconnect (gpgsm->assuan_ctx);
192
193   xfree (gpgsm->command);
194   xfree (gpgsm);
195 }
196
197 static AssuanError
198 gpgsm_assuan_simple_command (ASSUAN_CONTEXT ctx, char *cmd)
199 {
200   AssuanError err;
201   char *line;
202   size_t linelen;
203
204   err = assuan_write_line (ctx, cmd);
205   if (err)
206     return err;
207
208   do
209     {
210       err = assuan_read_line (ctx, &line, &linelen);
211       if (err)
212         return err;
213     }
214   while (*line == '#' || !linelen);
215   
216   if (linelen >= 2
217       && line[0] == 'O' && line[1] == 'K'
218       && (line[2] == '\0' || line[2] == ' '))
219     return 0;
220   else
221     return ASSUAN_General_Error;
222 }
223
224 #define COMMANDLINELEN 40
225 static AssuanError
226 gpgsm_set_fd (ASSUAN_CONTEXT ctx, const char *which, int fd, const char *opt)
227 {
228   char line[COMMANDLINELEN];
229
230   if (opt)
231     snprintf (line, COMMANDLINELEN, "%s FD=%i %s", which, fd, opt);
232   else
233     snprintf (line, COMMANDLINELEN, "%s FD=%i", which, fd);
234
235   return gpgsm_assuan_simple_command (ctx, line);
236 }
237
238 GpgmeError
239 _gpgme_gpgsm_op_decrypt (GpgsmObject gpgsm, GpgmeData ciph, GpgmeData plain)
240 {
241   AssuanError err;
242
243   if (!gpgsm)
244     return mk_error (Invalid_Value);
245
246   gpgsm->command = xtrystrdup ("DECRYPT");
247   if (!gpgsm->command)
248     return mk_error (Out_Of_Core);
249
250   gpgsm->input_data = ciph;
251   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
252   if (err)
253     return mk_error (General_Error);    /* FIXME */
254   gpgsm->output_data = plain;
255   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, 0);
256   if (err)
257     return mk_error (General_Error);    /* FIXME */
258   _gpgme_io_close (gpgsm->message_fd);
259   gpgsm->message_fd = -1;
260
261   return 0;
262 }
263
264 GpgmeError
265 _gpgme_gpgsm_op_delete (GpgsmObject gpgsm, GpgmeKey key, int allow_secret)
266 {
267   /* FIXME */
268   return mk_error (Not_Implemented);
269 }
270
271 static AssuanError
272 gpgsm_set_recipients (ASSUAN_CONTEXT ctx, GpgmeRecipients recp)
273 {
274   AssuanError err;
275   char *line;
276   int linelen;
277   struct user_id_s *r;
278
279   linelen = 10 + 40 + 1;        /* "RECIPIENT " + guess + '\0'.  */
280   line = xtrymalloc (10 + 40 + 1);
281   if (!line)
282     return ASSUAN_Out_Of_Core;
283   strcpy (line, "RECIPIENT ");
284   for (r = recp->list; r; r = r->next)
285     {
286       int newlen = 11 + strlen (r->name);
287       if (linelen < newlen)
288         {
289           char *newline = xtryrealloc (line, newlen);
290           if (! newline)
291             {
292               xfree (line);
293               return ASSUAN_Out_Of_Core;
294             }
295           line = newline;
296           linelen = newlen;
297         }
298       strcpy (&line[10], r->name);
299       
300       err = gpgsm_assuan_simple_command (ctx, line);
301       if (err)
302         {
303           xfree (line);
304           return err;
305         }
306     }
307   return 0;
308 }
309
310 GpgmeError
311 _gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp,
312                          GpgmeData plain, GpgmeData ciph, int use_armor)
313 {
314   AssuanError err;
315
316   if (!gpgsm)
317     return mk_error (Invalid_Value);
318
319   gpgsm->command = xtrystrdup (use_armor ? "ENCRYPT armor" : "ENCRYPT");
320   if (!gpgsm->command)
321     return mk_error (Out_Of_Core);
322
323   gpgsm->input_data = plain;
324   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
325   if (err)
326     return mk_error (General_Error);    /* FIXME */
327   gpgsm->output_data = ciph;
328   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
329                       use_armor ? "--armor" : 0);
330   if (err)
331     return mk_error (General_Error);    /* FIXME */
332   _gpgme_io_close (gpgsm->message_fd);
333   gpgsm->message_fd = -1;
334
335   err = gpgsm_set_recipients (gpgsm->assuan_ctx, recp);
336   if (err)
337     return mk_error (General_Error);
338
339   return 0;
340 }
341
342 GpgmeError
343 _gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp,
344                         GpgmeData keydata, int use_armor)
345 {
346   /* FIXME */
347   return mk_error (Not_Implemented);
348 }
349
350 GpgmeError
351 _gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor)
352 {
353   /* FIXME */
354   return mk_error (Not_Implemented);
355 }
356
357 GpgmeError
358 _gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata)
359 {
360   AssuanError err;
361
362   if (!gpgsm)
363     return mk_error (Invalid_Value);
364
365   gpgsm->command = xtrystrdup ("IMPORT");
366   if (!gpgsm->command)
367     return mk_error (Out_Of_Core);
368
369   gpgsm->input_data = keydata;
370   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
371   if (err)
372     return mk_error (General_Error);    /* FIXME */
373   _gpgme_io_close (gpgsm->output_fd);
374   gpgsm->output_fd = -1;
375   _gpgme_io_close (gpgsm->message_fd);
376   gpgsm->message_fd = -1;
377
378   return 0;
379 }
380
381 GpgmeError
382 _gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern,
383                          int secret_only, int keylist_mode)
384 {
385   char *line;
386
387   if (!pattern)
388     pattern = "";
389
390   line = xtrymalloc (9 + strlen (pattern) + 1); /* "LISTKEYS " + p + '\0'.  */
391   if (!line)
392     return mk_error (Out_Of_Core);
393   strcpy (line, "LISTKEYS ");
394   strcpy (&line[9], pattern);
395
396   _gpgme_io_close (gpgsm->input_fd);
397   gpgsm->input_fd = -1;
398   _gpgme_io_close (gpgsm->output_fd);
399   gpgsm->output_fd = -1;
400   _gpgme_io_close (gpgsm->message_fd);
401   gpgsm->message_fd = -1;
402
403   gpgsm->command = line;
404   return 0;
405 }
406
407 GpgmeError
408 _gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out,
409                       GpgmeSigMode mode, int use_armor,
410                       int use_textmode, GpgmeCtx ctx /* FIXME */)
411 {
412   AssuanError err;
413
414   if (!gpgsm)
415     return mk_error (Invalid_Value);
416
417   gpgsm->command = xtrystrdup (mode == GPGME_SIG_MODE_DETACH
418                                ? "SIGN --detach" : "SIGN");
419   if (!gpgsm->command)
420     return mk_error (Out_Of_Core);
421
422   gpgsm->input_data = in;
423   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
424   if (err)
425     return mk_error (General_Error);    /* FIXME */
426   gpgsm->output_data = out;
427   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
428                       use_armor ? "--armor" : 0);
429   if (err)
430     return mk_error (General_Error);    /* FIXME */
431   _gpgme_io_close (gpgsm->message_fd);
432   gpgsm->message_fd = -1;
433
434   return 0;
435 }
436
437 GpgmeError
438 _gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern)
439 {
440   /* FIXME */
441   return mk_error (Not_Implemented);
442 }
443
444 GpgmeError
445 _gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text)
446 {
447   AssuanError err;
448
449   if (!gpgsm)
450     return mk_error (Invalid_Value);
451
452   gpgsm->command = xtrystrdup ("VERIFY");
453   if (!gpgsm->command)
454     return mk_error (Out_Of_Core);
455
456   gpgsm->input_data = sig;
457   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
458   if (err)
459     return mk_error (General_Error);    /* FIXME */
460   gpgsm->message_data = sig;
461   err = gpgsm_set_fd (gpgsm->assuan_ctx, "MESSAGE", gpgsm->message_fd_server,
462                       0);
463   if (err)
464     return mk_error (General_Error);    /* FIXME */
465   _gpgme_io_close (gpgsm->output_fd);
466   gpgsm->output_fd = -1;
467
468   return 0;
469 }
470
471 static int
472 status_cmp (const void *ap, const void *bp)
473 {
474   const struct status_table_s *a = ap;
475   const struct status_table_s *b = bp;
476
477   return strcmp (a->name, b->name);
478 }
479
480 static int
481 gpgsm_status_handler (void *opaque, int pid, int fd)
482 {
483   int err;
484   GpgsmObject gpgsm = opaque;
485   char *line;
486   size_t linelen;
487
488   do
489     {
490       err = assuan_read_line (gpgsm->assuan_ctx, &line, &linelen);
491
492       if (err
493           || (linelen >= 2
494               && line[0] == 'O' && line[1] == 'K'
495               && (line[2] == '\0' || line[2] == ' '))
496           || (linelen >= 3
497               && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
498               && (line[3] == '\0' || line[3] == ' ')))
499         {
500           /* FIXME Save error somewhere.  */
501           if (gpgsm->status.fnc)
502             gpgsm->status.fnc (gpgsm->status.fnc_value, STATUS_EOF, "");
503           return 1;
504         }
505
506       if (linelen > 2
507           && line[0] == 'D' && line[1] == ' '
508           && gpgsm->colon.fnc )
509         {
510           unsigned char *s, *d;
511
512           line += 2;
513           linelen -= 2;
514           /* Hmmm, we are using the colon handler even for plain
515              inline data - strange name for that fucntion but for
516              historic reasons we keep it */
517           for (s=d=line; linelen; linelen--)
518             {
519               if (*s == '%' && linelen > 2)
520                 { /* handle escaping */
521                   s++;
522                   *d++ = xtoi_2 (s);
523                   s += 2;
524                   linelen -= 2;
525                 }
526               else
527                 *d++ = *s++;
528             }
529           *d = 0; /* add a hidden string terminator */
530
531           /* fixme: should we handle the return code? */
532           /* fixme: Hmmm: we can't use this for binary data because we
533              assume this is a string.  For the current usage of colon
534              output it is correct */
535           /* FIXME: we need extra bufferring to pass colon lines line
536              by line */
537           gpgsm->colon.fnc (gpgsm->colon.fnc_value, line);
538         }
539       else if (linelen > 2
540           && line[0] == 'S' && line[1] == ' ')
541         {
542           struct status_table_s t, *r;
543           char *rest;
544           
545           rest = strchr (line + 2, ' ');
546           if (!rest)
547             rest = line + linelen; /* set to an empty string */
548           else
549             *rest++ = 0;
550
551           t.name = line + 2;
552           r = bsearch (&t, status_table, DIM(status_table) - 1,
553                        sizeof t, status_cmp);
554
555           if (r)
556             {
557               if (gpgsm->status.fnc)
558                 gpgsm->status.fnc (gpgsm->status.fnc_value, r->code, rest);
559             }
560           else
561             fprintf (stderr, "[UNKNOWN STATUS]%s %s", t.name, rest);
562         }
563     }
564   while (assuan_pending_line (gpgsm->assuan_ctx));
565   
566   return 0;
567 }
568
569 void
570 _gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm,
571                                  GpgStatusHandler fnc, void *fnc_value) 
572 {
573   assert (gpgsm);
574
575   gpgsm->status.fnc = fnc;
576   gpgsm->status.fnc_value = fnc_value;
577 }
578
579 void
580 _gpgme_gpgsm_set_colon_line_handler (GpgsmObject gpgsm,
581                                      GpgColonLineHandler fnc, void *fnc_value) 
582 {
583   assert (gpgsm);
584
585   gpgsm->colon.fnc = fnc;
586   gpgsm->colon.fnc_value = fnc_value;
587 }
588
589 GpgmeError
590 _gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque)
591 {
592   GpgmeError err = 0;
593   pid_t pid;
594   int fdlist[5];
595   int nfds;
596
597   if (!gpgsm)
598     return mk_error (Invalid_Value);
599
600   pid = assuan_get_pid (gpgsm->assuan_ctx);
601
602   /* We need to know the fd used by assuan for reads.  We do this by
603      using the assumption that the first returned fd from
604      assuan_get_active_fds() is always this one. */
605   nfds = assuan_get_active_fds (gpgsm->assuan_ctx, 0 /* read fds */,
606                                 fdlist, DIM (fdlist));
607   if (nfds < 1)
608     return mk_error (General_Error);
609   err = _gpgme_register_pipe_handler (opaque, gpgsm_status_handler, gpgsm, pid,
610                                       fdlist[0], 1);
611
612
613   if (gpgsm->input_fd != -1)
614     {
615       err = _gpgme_register_pipe_handler (opaque, _gpgme_data_outbound_handler,
616                                           gpgsm->input_data, pid,
617                                           gpgsm->input_fd, 0);
618       if (!err) /* FIXME Kludge around poll() problem.  */
619         err = _gpgme_io_set_nonblocking (gpgsm->input_fd);
620     }
621   if (!err && gpgsm->output_fd != -1)
622     err = _gpgme_register_pipe_handler (opaque, _gpgme_data_inbound_handler,
623                                         gpgsm->output_data, pid,
624                                         gpgsm->output_fd, 1);
625   if (!err && gpgsm->message_fd != -1)
626     {
627       err = _gpgme_register_pipe_handler (opaque, _gpgme_data_outbound_handler,
628                                           gpgsm->message_data, pid,
629                                           gpgsm->message_fd, 0);
630       if (!err) /* FIXME Kludge around poll() problem.  */
631         err = _gpgme_io_set_nonblocking (gpgsm->message_fd);
632     }
633
634   if (!err)
635     err = assuan_write_line (gpgsm->assuan_ctx, gpgsm->command);
636
637   return err;
638 }
639
640 #else   /* ENABLE_GPGSM */
641
642 #include <stddef.h>
643 #include "util.h"
644
645 #include "engine-gpgsm.h"
646
647 const char *
648 _gpgme_gpgsm_get_version (void)
649 {
650   return NULL;
651 }
652
653 GpgmeError
654 _gpgme_gpgsm_check_version (void)
655 {
656   return mk_error (Invalid_Engine);
657 }
658
659 GpgmeError
660 _gpgme_gpgsm_new (GpgsmObject *r_gpgsm)
661 {
662   return mk_error (Invalid_Engine);
663 }
664
665 void
666 _gpgme_gpgsm_release (GpgsmObject gpgsm)
667 {
668   return;
669 }
670
671 void
672 _gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm,
673                                  GpgStatusHandler fnc, void *fnc_value) 
674 {
675   return;
676 }
677
678 GpgmeError
679 _gpgme_gpgsm_op_decrypt (GpgsmObject gpgsm, GpgmeData ciph, GpgmeData plain)
680 {
681   return mk_error (Invalid_Engine);
682 }
683
684 GpgmeError
685 _gpgme_gpgsm_op_delete (GpgsmObject gpgsm, GpgmeKey key, int allow_secret)
686 {
687   return mk_error (Invalid_Engine);
688 }
689
690 GpgmeError
691 _gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp,
692                          GpgmeData plain, GpgmeData ciph, int use_armor)
693 {
694   return mk_error (Invalid_Engine);
695 }
696
697 GpgmeError
698 _gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp,
699                         GpgmeData keydata, int use_armor)
700 {
701   return mk_error (Invalid_Engine);
702 }
703
704 GpgmeError
705 _gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor)
706 {
707   return mk_error (Invalid_Engine);
708 }
709   
710 GpgmeError
711 _gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata)
712 {
713   return mk_error (Invalid_Engine);
714 }
715
716 GpgmeError
717 _gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern,
718                          int secret_only, int keylist_mode)
719 {
720   return mk_error (Invalid_Engine);
721 }
722
723 GpgmeError
724 _gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out,
725                       GpgmeSigMode mode, int use_armor,
726                       int use_textmode, GpgmeCtx ctx /* FIXME */)
727 {
728   return mk_error (Invalid_Engine);
729 }
730
731 GpgmeError
732 _gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern)
733 {
734   return mk_error (Invalid_Engine);
735 }
736
737 GpgmeError
738 _gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text)
739 {
740   return mk_error (Invalid_Engine);
741 }
742
743 GpgmeError
744 _gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque)
745 {
746   return mk_error (Invalid_Engine);
747 }
748
749 #endif  /* ! ENABLE_GPGSM */