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