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