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   xfree (line);
339   return 0;
340 }
341
342 GpgmeError
343 _gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp,
344                          GpgmeData plain, GpgmeData ciph, int use_armor)
345 {
346   AssuanError err;
347
348   if (!gpgsm)
349     return mk_error (Invalid_Value);
350
351   gpgsm->command = xtrystrdup ("ENCRYPT");
352   if (!gpgsm->command)
353     return mk_error (Out_Of_Core);
354
355   gpgsm->input_data = plain;
356   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
357   if (err)
358     return mk_error (General_Error);    /* FIXME */
359   gpgsm->output_data = ciph;
360   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
361                       use_armor ? "--armor" : 0);
362   if (err)
363     return mk_error (General_Error);    /* FIXME */
364   _gpgme_io_close (gpgsm->message_fd);
365
366   err = gpgsm_set_recipients (gpgsm->assuan_ctx, recp);
367   if (err)
368     return mk_error (General_Error);
369
370   return 0;
371 }
372
373 GpgmeError
374 _gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp,
375                         GpgmeData keydata, int use_armor)
376 {
377   /* FIXME */
378   return mk_error (Not_Implemented);
379 }
380
381 GpgmeError
382 _gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor)
383 {
384   /* FIXME */
385   return mk_error (Not_Implemented);
386 }
387
388 GpgmeError
389 _gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata)
390 {
391   AssuanError err;
392
393   if (!gpgsm)
394     return mk_error (Invalid_Value);
395
396   gpgsm->command = xtrystrdup ("IMPORT");
397   if (!gpgsm->command)
398     return mk_error (Out_Of_Core);
399
400   gpgsm->input_data = keydata;
401   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
402   if (err)
403     return mk_error (General_Error);    /* FIXME */
404   _gpgme_io_close (gpgsm->output_fd);
405   _gpgme_io_close (gpgsm->message_fd);
406
407   return 0;
408 }
409
410 GpgmeError
411 _gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern,
412                          int secret_only, int keylist_mode)
413 {
414   char *line;
415
416   if (!pattern)
417     pattern = "";
418
419   line = xtrymalloc (9 + strlen (pattern) + 1); /* "LISTKEYS " + p + '\0'.  */
420   if (!line)
421     return mk_error (Out_Of_Core);
422   strcpy (line, "LISTKEYS ");
423   strcpy (&line[9], pattern);
424
425   _gpgme_io_close (gpgsm->input_fd);
426   _gpgme_io_close (gpgsm->output_fd);
427   _gpgme_io_close (gpgsm->message_fd);
428
429   gpgsm->command = line;
430   return 0;
431 }
432
433 GpgmeError
434 _gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out,
435                       GpgmeSigMode mode, int use_armor,
436                       int use_textmode, GpgmeCtx ctx /* FIXME */)
437 {
438   AssuanError err;
439
440   if (!gpgsm)
441     return mk_error (Invalid_Value);
442
443   gpgsm->command = xtrystrdup (mode == GPGME_SIG_MODE_DETACH
444                                ? "SIGN --detach" : "SIGN");
445   if (!gpgsm->command)
446     return mk_error (Out_Of_Core);
447
448   gpgsm->input_data = in;
449   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
450   if (err)
451     return mk_error (General_Error);    /* FIXME */
452   gpgsm->output_data = out;
453   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
454                       use_armor ? "--armor" : 0);
455   if (err)
456     return mk_error (General_Error);    /* FIXME */
457   _gpgme_io_close (gpgsm->message_fd);
458
459   return 0;
460 }
461
462 GpgmeError
463 _gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern)
464 {
465   /* FIXME */
466   return mk_error (Not_Implemented);
467 }
468
469 GpgmeError
470 _gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text)
471 {
472   AssuanError err;
473
474   if (!gpgsm)
475     return mk_error (Invalid_Value);
476
477   gpgsm->command = xtrystrdup ("VERIFY");
478   if (!gpgsm->command)
479     return mk_error (Out_Of_Core);
480
481   gpgsm->input_data = sig;
482   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
483   if (err)
484     return mk_error (General_Error);    /* FIXME */
485   gpgsm->message_data = sig;
486   err = gpgsm_set_fd (gpgsm->assuan_ctx, "MESSAGE", gpgsm->message_fd_server,
487                       0);
488   if (err)
489     return mk_error (General_Error);    /* FIXME */
490   _gpgme_io_close (gpgsm->output_fd);
491
492   return 0;
493 }
494
495 static int
496 status_cmp (const void *ap, const void *bp)
497 {
498   const struct status_table_s *a = ap;
499   const struct status_table_s *b = bp;
500
501   return strcmp (a->name, b->name);
502 }
503
504 static int
505 gpgsm_status_handler (void *opaque, int pid, int fd)
506 {
507   int err;
508   GpgsmObject gpgsm = opaque;
509   char *line;
510   size_t linelen;
511
512   do
513     {
514       err = assuan_read_line (gpgsm->assuan_ctx, &line, &linelen);
515
516       if (err
517           || (linelen >= 2
518               && line[0] == 'O' && line[1] == 'K'
519               && (line[2] == '\0' || line[2] == ' '))
520           || (linelen >= 3
521               && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
522               && (line[3] == '\0' || line[3] == ' ')))
523         {
524           /* FIXME Save error somewhere.  */
525           if (gpgsm->status.fnc)
526             gpgsm->status.fnc (gpgsm->status.fnc_value, STATUS_EOF, "");
527           return 1;
528         }
529
530       if (linelen > 2
531           && line[0] == 'D' && line[1] == ' '
532           && gpgsm->colon.fnc )
533         {
534           unsigned char *s, *d;
535
536           line += 2;
537           linelen -= 2;
538           /* Hmmm, we are using the colon handler even for plain
539              inline data - strange name for that fucntion but for
540              historic reasons we keep it */
541           for (s=d=line; linelen; linelen--)
542             {
543               if (*s == '%' && linelen > 2)
544                 { /* handle escaping */
545                   s++;
546                   *d++ = xtoi_2 (s);
547                   s += 2;
548                   linelen -= 2;
549                 }
550               else
551                 *d++ = *s++;
552             }
553           *d = 0; /* add a hidden string terminator */
554
555           /* fixme: should we handle the return code? */
556           /* fixme: Hmmm: we can't use this for binary data because we
557              assume this is a string.  For the current usage of colon
558              output it is correct */
559           /* FIXME: we need extra bufferring to pass colon lines line
560              by line */
561           gpgsm->colon.fnc (gpgsm->colon.fnc_value, line);
562         }
563       else if (linelen > 2
564           && line[0] == 'S' && line[1] == ' ')
565         {
566           struct status_table_s t, *r;
567           char *rest;
568           
569           rest = strchr (line + 2, ' ');
570           if (!rest)
571             rest = line + linelen; /* set to an empty string */
572           else
573             *rest++ = 0;
574
575           t.name = line + 2;
576           r = bsearch (&t, status_table, DIM(status_table) - 1,
577                        sizeof t, status_cmp);
578
579           if (r)
580             {
581               if (gpgsm->status.fnc)
582                 gpgsm->status.fnc (gpgsm->status.fnc_value, r->code, rest);
583             }
584           else
585             fprintf (stderr, "[UNKNOWN STATUS]%s %s", t.name, rest);
586         }
587     }
588   while (assuan_pending_line (gpgsm->assuan_ctx));
589   
590   return 0;
591 }
592
593 void
594 _gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm,
595                                  GpgStatusHandler fnc, void *fnc_value) 
596 {
597   assert (gpgsm);
598
599   gpgsm->status.fnc = fnc;
600   gpgsm->status.fnc_value = fnc_value;
601 }
602
603 void
604 _gpgme_gpgsm_set_colon_line_handler (GpgsmObject gpgsm,
605                                      GpgColonLineHandler fnc, void *fnc_value) 
606 {
607   assert (gpgsm);
608
609   gpgsm->colon.fnc = fnc;
610   gpgsm->colon.fnc_value = fnc_value;
611 }
612
613 GpgmeError
614 _gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque)
615 {
616   GpgmeError err = 0;
617   pid_t pid;
618   int fdlist[5];
619   int nfds;
620
621   if (!gpgsm)
622     return mk_error (Invalid_Value);
623
624   pid = assuan_get_pid (gpgsm->assuan_ctx);
625
626   /* We need to know the fd used by assuan for reads.  We do this by
627      using the assumption that the first returned fd from
628      assuan_get_active_fds() is always this one. */
629   nfds = assuan_get_active_fds (gpgsm->assuan_ctx, 0 /* read fds */,
630                                 fdlist, DIM (fdlist));
631   if (nfds < 1)
632     return mk_error (General_Error);
633   err = _gpgme_register_pipe_handler (opaque, gpgsm_status_handler, gpgsm, pid,
634                                       fdlist[0], 1);
635
636
637   if (gpgsm->input_fd != -1)
638     {
639       err = _gpgme_register_pipe_handler (opaque, _gpgme_data_outbound_handler,
640                                           gpgsm->input_data, pid,
641                                           gpgsm->input_fd, 0);
642       if (!err) /* FIXME Kludge around poll() problem.  */
643         err = _gpgme_io_set_nonblocking (gpgsm->input_fd);
644     }
645   if (!err && gpgsm->output_fd != -1)
646     err = _gpgme_register_pipe_handler (opaque, _gpgme_data_inbound_handler,
647                                         gpgsm->output_data, pid,
648                                         gpgsm->output_fd, 1);
649   if (!err && gpgsm->message_fd != -1)
650     {
651       err = _gpgme_register_pipe_handler (opaque, _gpgme_data_outbound_handler,
652                                           gpgsm->message_data, pid,
653                                           gpgsm->message_fd, 0);
654       if (!err) /* FIXME Kludge around poll() problem.  */
655         err = _gpgme_io_set_nonblocking (gpgsm->message_fd);
656     }
657
658   if (!err)
659     err = assuan_write_line (gpgsm->assuan_ctx, gpgsm->command);
660
661   return err;
662 }
663
664 #else   /* ENABLE_GPGSM */
665
666 #include <stddef.h>
667 #include "util.h"
668
669 #include "engine-gpgsm.h"
670
671 const char *
672 _gpgme_gpgsm_get_version (void)
673 {
674   return NULL;
675 }
676
677 GpgmeError
678 _gpgme_gpgsm_check_version (void)
679 {
680   return mk_error (Invalid_Engine);
681 }
682
683 GpgmeError
684 _gpgme_gpgsm_new (GpgsmObject *r_gpgsm)
685 {
686   return mk_error (Invalid_Engine);
687 }
688
689 void
690 _gpgme_gpgsm_release (GpgsmObject gpgsm)
691 {
692   return;
693 }
694
695 void
696 _gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm,
697                                  GpgStatusHandler fnc, void *fnc_value) 
698 {
699   return;
700 }
701
702 GpgmeError
703 _gpgme_gpgsm_op_decrypt (GpgsmObject gpgsm, GpgmeData ciph, GpgmeData plain)
704 {
705   return mk_error (Invalid_Engine);
706 }
707
708 GpgmeError
709 _gpgme_gpgsm_op_delete (GpgsmObject gpgsm, GpgmeKey key, int allow_secret)
710 {
711   return mk_error (Invalid_Engine);
712 }
713
714 GpgmeError
715 _gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp,
716                          GpgmeData plain, GpgmeData ciph, int use_armor)
717 {
718   return mk_error (Invalid_Engine);
719 }
720
721 GpgmeError
722 _gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp,
723                         GpgmeData keydata, int use_armor)
724 {
725   return mk_error (Invalid_Engine);
726 }
727
728 GpgmeError
729 _gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor)
730 {
731   return mk_error (Invalid_Engine);
732 }
733   
734 GpgmeError
735 _gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata)
736 {
737   return mk_error (Invalid_Engine);
738 }
739
740 GpgmeError
741 _gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern,
742                          int secret_only, int keylist_mode)
743 {
744   return mk_error (Invalid_Engine);
745 }
746
747 GpgmeError
748 _gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out,
749                       GpgmeSigMode mode, int use_armor,
750                       int use_textmode, GpgmeCtx ctx /* FIXME */)
751 {
752   return mk_error (Invalid_Engine);
753 }
754
755 GpgmeError
756 _gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern)
757 {
758   return mk_error (Invalid_Engine);
759 }
760
761 GpgmeError
762 _gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text)
763 {
764   return mk_error (Invalid_Engine);
765 }
766
767 GpgmeError
768 _gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque)
769 {
770   return mk_error (Invalid_Engine);
771 }
772
773 #endif  /* ! ENABLE_GPGSM */