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 "ops.h"
36 #include "wait.h"
37 #include "io.h"
38 #include "key.h"
39 #include "sema.h"
40
41 #include "assuan.h"
42 #include "status-table.h"
43
44 #include "engine-backend.h"
45
46
47 #define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
48                      *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
49 #define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
50
51
52 typedef struct
53 {
54   int fd;       /* FD we talk about.  */
55   int dir;      /* Inbound/Outbound, maybe given implicit?  */
56   void *data;   /* Handler-specific data.  */
57   void *tag;    /* ID from the user for gpgme_remove_io_callback.  */
58 } iocb_data_t;
59
60
61 struct gpgsm_object_s
62 {
63   ASSUAN_CONTEXT assuan_ctx;
64
65   iocb_data_t status_cb;
66
67   /* Input, output etc are from the servers perspective.  */
68   iocb_data_t input_cb;
69   int input_fd_server;
70
71   iocb_data_t output_cb;
72   int output_fd_server;
73
74   iocb_data_t message_cb;
75   int message_fd_server;
76
77   struct
78   {
79     EngineStatusHandler fnc;
80     void *fnc_value;
81   } status;
82
83   struct
84   {
85     EngineColonLineHandler fnc;
86     void *fnc_value;
87     struct
88     {
89       unsigned char *line;
90       int linesize;
91       int linelen;
92     } attic;
93     int any; /* any data line seen */
94   } colon; 
95
96   struct GpgmeIOCbs io_cbs;
97 };
98
99 typedef struct gpgsm_object_s *GpgsmObject;
100
101 \f
102 static const char *
103 gpgsm_get_version (void)
104 {
105   static const char *gpgsm_version;
106   DEFINE_STATIC_LOCK (gpgsm_version_lock);
107
108   LOCK (gpgsm_version_lock);
109   if (!gpgsm_version)
110     gpgsm_version = _gpgme_get_program_version (_gpgme_get_gpgsm_path ());
111   UNLOCK (gpgsm_version_lock);
112
113   return gpgsm_version;
114 }
115
116
117 static const char *
118 gpgsm_get_req_version (void)
119 {
120   return NEED_GPGSM_VERSION;
121 }
122
123
124 static void
125 close_notify_handler (int fd, void *opaque)
126 {
127   GpgsmObject gpgsm = opaque;
128
129   assert (fd != -1);
130   if (gpgsm->status_cb.fd == fd)
131     {
132       if (gpgsm->status_cb.tag)
133         (*gpgsm->io_cbs.remove) (gpgsm->status_cb.tag);
134       gpgsm->status_cb.fd = -1;
135     }
136   else if (gpgsm->input_cb.fd == fd)
137     {
138       if (gpgsm->input_cb.tag)
139         (*gpgsm->io_cbs.remove) (gpgsm->input_cb.tag);
140       gpgsm->input_cb.fd = -1;
141     }
142   else if (gpgsm->output_cb.fd == fd)
143     {
144       if (gpgsm->output_cb.tag)
145         (*gpgsm->io_cbs.remove) (gpgsm->output_cb.tag);
146       gpgsm->output_cb.fd = -1;
147     }
148   else if (gpgsm->message_cb.fd == fd)
149     {
150       if (gpgsm->message_cb.tag)
151         (*gpgsm->io_cbs.remove) (gpgsm->message_cb.tag);
152       gpgsm->message_cb.fd = -1;
153     }
154 }
155
156
157 static GpgmeError
158 map_assuan_error (AssuanError err)
159 {
160   switch (err)
161     {
162     case ASSUAN_No_Error:
163       return GPGME_No_Error;
164     case ASSUAN_General_Error:
165       return GPGME_General_Error;
166     case ASSUAN_Out_Of_Core:
167       return GPGME_Out_Of_Core;
168     case ASSUAN_Invalid_Value:
169       return GPGME_Invalid_Value;
170     case ASSUAN_Read_Error:
171       return GPGME_Read_Error;
172     case ASSUAN_Write_Error:
173       return GPGME_Write_Error;
174
175     case ASSUAN_Timeout:
176     case ASSUAN_Problem_Starting_Server:
177     case ASSUAN_Not_A_Server:
178     case ASSUAN_Not_A_Client:
179     case ASSUAN_Nested_Commands:
180     case ASSUAN_Invalid_Response:
181     case ASSUAN_No_Data_Callback:
182     case ASSUAN_No_Inquire_Callback:
183     case ASSUAN_Connect_Failed:
184     case ASSUAN_Accept_Failed:
185       return GPGME_General_Error;
186
187       /* The following error codes are meant as status codes.  */
188     case ASSUAN_Not_Implemented:
189       return GPGME_Not_Implemented;
190     case ASSUAN_Canceled:
191       return GPGME_Canceled;
192     case ASSUAN_Unsupported_Algorithm:
193       return GPGME_Not_Implemented;  /* XXX Argh.  */
194       
195     case ASSUAN_No_Data_Available:
196       return GPGME_EOF;
197       
198       /* These are errors internal to GPGME.  */
199     case ASSUAN_No_Input:
200     case ASSUAN_No_Output:
201     case ASSUAN_Invalid_Command:
202     case ASSUAN_Unknown_Command:
203     case ASSUAN_Syntax_Error:
204     case ASSUAN_Parameter_Error:
205     case ASSUAN_Parameter_Conflict:
206     case ASSUAN_Line_Too_Long:
207     case ASSUAN_Line_Not_Terminated:
208     case ASSUAN_Invalid_Data:
209     case ASSUAN_Unexpected_Command:
210     case ASSUAN_Too_Much_Data:
211     case ASSUAN_Inquire_Unknown:
212     case ASSUAN_Inquire_Error:
213     case ASSUAN_Invalid_Option:
214     case ASSUAN_Invalid_Index:
215     case ASSUAN_Unexpected_Status:
216     case ASSUAN_Unexpected_Data:
217     case ASSUAN_Invalid_Status:
218     case ASSUAN_Not_Confirmed:
219       return GPGME_General_Error;
220
221       /* These are errors in the server.  */
222     case ASSUAN_Server_Fault:
223     case ASSUAN_Server_Resource_Problem:
224     case ASSUAN_Server_IO_Error:
225     case ASSUAN_Server_Bug:
226     case ASSUAN_No_Agent:
227     case ASSUAN_Agent_Error:
228       return GPGME_Invalid_Engine;  /* XXX:  Need something more useful.  */
229
230     case ASSUAN_Bad_Certificate:
231     case ASSUAN_Bad_Certificate_Path:
232     case ASSUAN_Missing_Certificate:
233     case ASSUAN_No_Public_Key:
234     case ASSUAN_No_Secret_Key:
235     case ASSUAN_Invalid_Name:
236     case ASSUAN_Card_Error:     /* XXX: Oh well.  */
237     case ASSUAN_Invalid_Card:   /* XXX: Oh well.  */
238     case ASSUAN_No_PKCS15_App:  /* XXX: Oh well.  */
239     case ASSUAN_Card_Not_Present:       /* XXX: Oh well.  */
240     case ASSUAN_Invalid_Id:     /* XXX: Oh well.  */
241       return GPGME_Invalid_Key;
242
243     case ASSUAN_Bad_Signature:
244       return GPGME_Invalid_Key;  /* XXX: This is wrong.  */
245
246     case ASSUAN_Cert_Revoked:
247     case ASSUAN_No_CRL_For_Cert:
248     case ASSUAN_CRL_Too_Old:
249     case ASSUAN_Not_Trusted:
250       return GPGME_Invalid_Key;  /* XXX Some more details would be good.  */
251
252     default:
253       return GPGME_General_Error;
254     }
255 }
256
257
258 static void
259 gpgsm_release (void *engine)
260 {
261   GpgsmObject gpgsm = engine;
262
263   if (!gpgsm)
264     return;
265
266   if (gpgsm->status_cb.fd != -1)
267     _gpgme_io_close (gpgsm->status_cb.fd);
268   if (gpgsm->input_cb.fd != -1)
269     _gpgme_io_close (gpgsm->input_cb.fd);
270   if (gpgsm->output_cb.fd != -1)
271     _gpgme_io_close (gpgsm->output_cb.fd);
272   if (gpgsm->message_cb.fd != -1)
273     _gpgme_io_close (gpgsm->message_cb.fd);
274
275   assuan_disconnect (gpgsm->assuan_ctx);
276
277   free (gpgsm->colon.attic.line);
278   free (gpgsm);
279 }
280
281
282 static GpgmeError
283 gpgsm_new (void **engine)
284 {
285   GpgmeError err = 0;
286   GpgsmObject gpgsm;
287   char *argv[3];
288   int fds[2];
289   int child_fds[4];
290   char *dft_display = NULL;
291   char *dft_ttyname = NULL;
292   char *dft_ttytype = NULL;
293   char *old_lc = NULL;
294   char *dft_lc = NULL;
295   char *optstr;
296   int fdlist[5];
297   int nfds;
298
299   gpgsm = calloc (1, sizeof *gpgsm);
300   if (!gpgsm)
301     {
302       err = GPGME_Out_Of_Core;
303       return err;
304     }
305
306   gpgsm->status_cb.fd = -1;
307   gpgsm->status_cb.tag = 0;
308
309   gpgsm->input_cb.fd = -1;
310   gpgsm->input_cb.tag = 0;
311   gpgsm->input_fd_server = -1;
312   gpgsm->output_cb.fd = -1;
313   gpgsm->output_cb.tag = 0;
314   gpgsm->output_fd_server = -1;
315   gpgsm->message_cb.fd = -1;
316   gpgsm->message_cb.tag = 0;
317   gpgsm->message_fd_server = -1;
318
319   gpgsm->status.fnc = 0;
320   gpgsm->colon.fnc = 0;
321   gpgsm->colon.attic.line = 0;
322   gpgsm->colon.attic.linesize = 0;
323   gpgsm->colon.attic.linelen = 0;
324   gpgsm->colon.any = 0;
325
326   gpgsm->io_cbs.add = NULL;
327   gpgsm->io_cbs.add_priv = NULL;
328   gpgsm->io_cbs.remove = NULL;
329   gpgsm->io_cbs.event = NULL;
330   gpgsm->io_cbs.event_priv = NULL;
331
332   if (_gpgme_io_pipe (fds, 0) < 0)
333     {
334       err = GPGME_Pipe_Error;
335       goto leave;
336     }
337   gpgsm->input_cb.fd = fds[1];
338   gpgsm->input_cb.dir = 0;
339   gpgsm->input_fd_server = fds[0];
340
341   if (_gpgme_io_pipe (fds, 1) < 0)
342     {
343       err = GPGME_Pipe_Error;
344       goto leave;
345     }
346   gpgsm->output_cb.fd = fds[0];
347   gpgsm->output_cb.dir = 1;
348   gpgsm->output_fd_server = fds[1];
349
350   if (_gpgme_io_pipe (fds, 0) < 0)
351     {
352       err = GPGME_Pipe_Error;
353       goto leave;
354     }
355   gpgsm->message_cb.fd = fds[1];
356   gpgsm->message_cb.dir = 0;
357   gpgsm->message_fd_server = fds[0];
358
359   child_fds[0] = gpgsm->input_fd_server;
360   child_fds[1] = gpgsm->output_fd_server;
361   child_fds[2] = gpgsm->message_fd_server;
362   child_fds[3] = -1;
363
364   argv[0] = "gpgsm";
365   argv[1] = "--server";
366   argv[2] = NULL;
367
368   err = assuan_pipe_connect2 (&gpgsm->assuan_ctx,
369                               _gpgme_get_gpgsm_path (), argv, child_fds,
370                               1 /* dup stderr to /dev/null */);
371
372   /* We need to know the fd used by assuan for reads.  We do this by
373      using the assumption that the first returned fd from
374      assuan_get_active_fds() is always this one.  */
375   nfds = assuan_get_active_fds (gpgsm->assuan_ctx, 0 /* read fds */,
376                                 fdlist, DIM (fdlist));
377   if (nfds < 1)
378     {
379       err = GPGME_General_Error;  /* FIXME */
380       goto leave;
381     }
382   /* We duplicate the file descriptor, so we can close it without
383      disturbing assuan.  Alternatively, we could special case
384      status_fd and register/unregister it manually as needed, but this
385      increases code duplication and is more complicated as we can not
386      use the close notifications etc.  */
387   gpgsm->status_cb.fd = dup (fdlist[0]);
388   if (gpgsm->status_cb.fd < 0)
389     {
390       err = GPGME_General_Error;        /* FIXME */
391       goto leave;
392     }
393   gpgsm->status_cb.dir = 1;
394   gpgsm->status_cb.data = gpgsm;
395
396   dft_display = getenv ("DISPLAY");
397   if (dft_display)
398     {
399       if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
400         {
401           err = GPGME_Out_Of_Core;
402           goto leave;
403         }
404       err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL,
405                              NULL, NULL, NULL);
406       free (optstr);
407       if (err)
408         {
409           err = map_assuan_error (err);
410           goto leave;
411         }
412     }
413   dft_ttyname = ttyname (1);
414   if (dft_ttyname)
415     {
416       if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
417         {
418           err = GPGME_Out_Of_Core;
419           goto leave;
420         }
421       err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
422                              NULL);
423       free (optstr);
424       if (err)
425         {
426           err = map_assuan_error (err);
427           goto leave;
428         }
429
430       dft_ttytype = getenv ("TERM");
431       if (dft_ttytype)
432         {
433           if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0)
434             {
435               err = GPGME_Out_Of_Core;
436               goto leave;
437             }
438           err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
439                                  NULL);
440           free (optstr);
441           if (err)
442             {
443               err = map_assuan_error (err);
444               goto leave;
445             }
446         }
447
448       old_lc = setlocale (LC_CTYPE, NULL);
449       if (old_lc)
450         {
451           old_lc = strdup (old_lc);
452           if (!old_lc)
453             {
454               err = GPGME_Out_Of_Core;
455               goto leave;
456             }
457         }
458       dft_lc = setlocale (LC_CTYPE, "");
459       if (dft_lc)
460         {
461           if (asprintf (&optstr, "OPTION lc-ctype=%s", dft_lc) < 0)
462             err = GPGME_Out_Of_Core;
463           else
464             {
465               err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL,
466                                      NULL, NULL, NULL, NULL);
467               free (optstr);
468               if (err)
469                 err = map_assuan_error (err);
470             }
471         }
472       if (old_lc)
473         {
474           setlocale (LC_CTYPE, old_lc);
475           free (old_lc);
476         }
477       if (err)
478         goto leave;
479
480
481       old_lc = setlocale (LC_MESSAGES, NULL);
482       if (old_lc)
483         {
484           old_lc = strdup (old_lc);
485           if (!old_lc)
486             {
487               err = GPGME_Out_Of_Core;
488               goto leave;
489             }
490         }
491       dft_lc = setlocale (LC_MESSAGES, "");
492       if (dft_lc)
493         {
494           if (asprintf (&optstr, "OPTION lc-messages=%s", dft_lc) < 0)
495             err = GPGME_Out_Of_Core;
496           else
497             {
498               err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
499                                      NULL);
500               free (optstr);
501               if (err)
502                 err = map_assuan_error (err);
503             }
504         }
505       if (old_lc)
506         {
507           setlocale (LC_MESSAGES, old_lc);
508           free (old_lc);
509         }
510       if (err)
511         goto leave;
512     }
513
514   if (!err &&
515       (_gpgme_io_set_close_notify (gpgsm->status_cb.fd,
516                                    close_notify_handler, gpgsm)
517        || _gpgme_io_set_close_notify (gpgsm->input_cb.fd,
518                                    close_notify_handler, gpgsm)
519        || _gpgme_io_set_close_notify (gpgsm->output_cb.fd,
520                                       close_notify_handler, gpgsm)
521        || _gpgme_io_set_close_notify (gpgsm->message_cb.fd,
522                                       close_notify_handler, gpgsm)))
523     {
524       err = GPGME_General_Error;
525       goto leave;
526     }
527       
528  leave:
529   /* Close the server ends of the pipes.  Our ends are closed in
530      gpgsm_release().  */
531   if (gpgsm->input_fd_server != -1)
532     _gpgme_io_close (gpgsm->input_fd_server);
533   if (gpgsm->output_fd_server != -1)
534     _gpgme_io_close (gpgsm->output_fd_server);
535   if (gpgsm->message_fd_server != -1)
536     _gpgme_io_close (gpgsm->message_fd_server);
537
538   if (err)
539     gpgsm_release (gpgsm);
540   else
541     *engine = gpgsm;
542
543   return err;
544 }
545
546
547 /* Forward declaration.  */
548 static GpgmeStatusCode parse_status (const char *name);
549
550 static GpgmeError
551 gpgsm_assuan_simple_command (ASSUAN_CONTEXT ctx, char *cmd, EngineStatusHandler status_fnc,
552                              void *status_fnc_value)
553 {
554   AssuanError err;
555   char *line;
556   size_t linelen;
557
558   err = assuan_write_line (ctx, cmd);
559   if (err)
560     return map_assuan_error (err);
561
562   do
563     {
564       err = assuan_read_line (ctx, &line, &linelen);
565       if (err)
566         return map_assuan_error (err);
567
568       if (*line == '#' || !linelen)
569         continue;
570
571       if (linelen >= 2
572           && line[0] == 'O' && line[1] == 'K'
573           && (line[2] == '\0' || line[2] == ' '))
574         return 0;
575       else if (linelen >= 4
576           && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
577           && line[3] == ' ')
578         err = map_assuan_error (atoi (&line[4]));
579       else if (linelen >= 2
580                && line[0] == 'S' && line[1] == ' ')
581         {
582           char *rest;
583           GpgmeStatusCode r;
584
585           rest = strchr (line + 2, ' ');
586           if (!rest)
587             rest = line + linelen; /* set to an empty string */
588           else
589             *(rest++) = 0;
590
591           r = parse_status (line + 2);
592
593           if (r >= 0 && status_fnc)
594             status_fnc (status_fnc_value, r, rest);
595           else
596             err = GPGME_General_Error;
597         }
598       else
599         err = GPGME_General_Error;
600     }
601   while (!err);
602
603   return err;
604 }
605
606
607 #define COMMANDLINELEN 40
608 static GpgmeError
609 gpgsm_set_fd (ASSUAN_CONTEXT ctx, const char *which, int fd, const char *opt)
610 {
611   char line[COMMANDLINELEN];
612
613   if (opt)
614     snprintf (line, COMMANDLINELEN, "%s FD=%i %s", which, fd, opt);
615   else
616     snprintf (line, COMMANDLINELEN, "%s FD=%i", which, fd);
617
618   return gpgsm_assuan_simple_command (ctx, line, NULL, NULL);
619 }
620
621
622 static const char *
623 map_input_enc (GpgmeData d)
624 {
625   switch (gpgme_data_get_encoding (d))
626     {
627     case GPGME_DATA_ENCODING_NONE:
628       break;
629     case GPGME_DATA_ENCODING_BINARY:
630       return "--binary";
631     case GPGME_DATA_ENCODING_BASE64:
632       return "--base64";
633     case GPGME_DATA_ENCODING_ARMOR:
634       return "--armor";
635     default:
636       break;
637     }
638   return NULL;
639 }
640
641
642 static int
643 status_cmp (const void *ap, const void *bp)
644 {
645   const struct status_table_s *a = ap;
646   const struct status_table_s *b = bp;
647
648   return strcmp (a->name, b->name);
649 }
650
651
652 static GpgmeStatusCode
653 parse_status (const char *name)
654 {
655   struct status_table_s t, *r;
656   t.name = name;
657   r = bsearch (&t, status_table, DIM(status_table) - 1,
658                sizeof t, status_cmp);
659   return r ? r->code : -1;
660 }
661
662
663 static GpgmeError
664 status_handler (void *opaque, int fd)
665 {
666   AssuanError assuan_err;
667   GpgmeError err = 0;
668   GpgsmObject gpgsm = opaque;
669   char *line;
670   size_t linelen;
671
672   do
673     {
674       assuan_err = assuan_read_line (gpgsm->assuan_ctx, &line, &linelen);
675       if (assuan_err)
676         {
677           /* Try our best to terminate the connection friendly.  */
678           assuan_write_line (gpgsm->assuan_ctx, "BYE");
679           err = map_assuan_error (assuan_err);
680         }
681       else if (linelen >= 3
682                && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
683                && (line[3] == '\0' || line[3] == ' '))
684         {
685           if (line[3] == ' ')
686             err = map_assuan_error (atoi (&line[4]));
687           else
688             err = GPGME_General_Error;
689         }
690       else if (linelen >= 2
691                && line[0] == 'O' && line[1] == 'K'
692                && (line[2] == '\0' || line[2] == ' '))
693         {
694           if (gpgsm->status.fnc)
695             err = gpgsm->status.fnc (gpgsm->status.fnc_value,
696                                      GPGME_STATUS_EOF, "");
697           
698           if (!err && gpgsm->colon.fnc && gpgsm->colon.any )
699             {
700               /* We must tell a colon function about the EOF. We do
701                  this only when we have seen any data lines.  Note
702                  that this inlined use of colon data lines will
703                  eventually be changed into using a regular data
704                  channel. */
705               gpgsm->colon.any = 0;
706               err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, NULL);
707             }
708           _gpgme_io_close (gpgsm->status_cb.fd);
709           return err;
710         }
711       else if (linelen > 2
712                && line[0] == 'D' && line[1] == ' '
713                && gpgsm->colon.fnc)
714         {
715           /* We are using the colon handler even for plain inline data
716              - strange name for that function but for historic reasons
717              we keep it.  */
718           /* FIXME We can't use this for binary data because we
719              assume this is a string.  For the current usage of colon
720              output it is correct.  */
721           unsigned char *src = line + 2;
722           unsigned char *end = line + linelen;
723           unsigned char *dst;
724           unsigned char **aline = &gpgsm->colon.attic.line;
725           int *alinelen = &gpgsm->colon.attic.linelen;
726
727           if (gpgsm->colon.attic.linesize
728               < *alinelen + linelen + 1)
729             {
730               unsigned char *newline = realloc (*aline,
731                                                 *alinelen + linelen + 1);
732               if (!newline)
733                 err = GPGME_Out_Of_Core;
734               else
735                 {
736                   *aline = newline;
737                   gpgsm->colon.attic.linesize += linelen + 1;
738                 }
739             }
740           if (!err)
741             {
742               dst = *aline + *alinelen;
743
744               while (!err && src < end)
745                 {
746                   if (*src == '%' && src + 2 < end)
747                     {
748                       /* Handle escaped characters.  */
749                       ++src;
750                       *dst = xtoi_2 (src);
751                       (*alinelen)++;
752                       src += 2;
753                     }
754                   else
755                     {
756                       *dst = *src++;
757                       (*alinelen)++;
758                     }
759                   
760                   if (*dst == '\n')
761                     {
762                       /* Terminate the pending line, pass it to the colon
763                          handler and reset it.  */
764                       
765                       gpgsm->colon.any = 1;
766                       if (*alinelen > 1 && *(dst - 1) == '\r')
767                         dst--;
768                       *dst = '\0';
769
770                       /* FIXME How should we handle the return code?  */
771                       err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, *aline);
772                       if (!err)
773                         {
774                           dst = *aline;
775                           *alinelen = 0;
776                         }
777                     }
778                   else
779                     dst++;
780                 }
781             }
782         }
783       else if (linelen > 2
784                && line[0] == 'S' && line[1] == ' ')
785         {
786           char *rest;
787           GpgmeStatusCode r;
788           
789           rest = strchr (line + 2, ' ');
790           if (!rest)
791             rest = line + linelen; /* set to an empty string */
792           else
793             *(rest++) = 0;
794
795           r = parse_status (line + 2);
796
797           if (r >= 0)
798             {
799               if (gpgsm->status.fnc)
800                 err = gpgsm->status.fnc (gpgsm->status.fnc_value, r, rest);
801             }
802           else
803             fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest);
804         }
805     }
806   while (!err && assuan_pending_line (gpgsm->assuan_ctx));
807           
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, EngineStatusHandler 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, EngineColonLineHandler 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   };