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