2001-12-05 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / engine-gpgsm.c
1 /* engine-gpgsm.c -  GpgSM engine
2  *      Copyright (C) 2000 Werner Koch (dd9jn)
3  *      Copyright (C) 2001 g10 Code GmbH
4  *
5  * This file is part of GPGME.
6  *
7  * GPGME is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * GPGME is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
20  */
21
22 #if HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 /* FIXME: Correct check?  */
27 #ifdef GPGSM_PATH
28 #define ENABLE_GPGSM 1
29 #endif
30
31 #ifdef ENABLE_GPGSM
32
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include <assert.h>
37 #include <fcntl.h> /* FIXME */
38
39 /* FIXME */
40 #include "../assuan/assuan-defs.h"
41 #undef xtrymalloc
42 #undef xtrycalloc
43 #undef xtryrealloc
44 #undef xfree
45
46 #include "rungpg.h"
47 #include "status-table.h"
48
49 #include "gpgme.h"
50 #include "util.h"
51 #include "types.h"
52 #include "ops.h"
53 #include "wait.h"
54 #include "io.h"
55 #include "key.h"
56
57 #include "engine-gpgsm.h"
58
59 #include "assuan.h"
60
61 struct gpgsm_object_s
62 {
63   ASSUAN_CONTEXT assuan_ctx;
64
65   /* Input, output etc are from the servers perspective.  */
66   int input_fd;
67   int input_fd_server;
68   GpgmeData input_data;
69   int output_fd;
70   int output_fd_server;
71   GpgmeData output_data;
72   int message_fd;
73   int message_fd_server;
74   GpgmeData message_data;
75
76   char *command;
77
78   struct
79   {
80     GpgStatusHandler fnc;
81     void *fnc_value;
82   } status;
83   
84 };
85
86 const char *
87 _gpgme_gpgsm_get_version (void)
88 {
89   static const char *gpgsm_version;
90
91   /* FIXME: Locking.  */
92   if (!gpgsm_version)
93     gpgsm_version = _gpgme_get_program_version (_gpgme_get_gpgsm_path ());
94
95   return gpgsm_version;
96 }
97
98 GpgmeError
99 _gpgme_gpgsm_check_version (void)
100 {
101   return _gpgme_compare_versions (_gpgme_gpgsm_get_version (),
102                                   NEED_GPGSM_VERSION)
103     ? 0 : mk_error (Invalid_Engine);
104 }
105
106 GpgmeError
107 _gpgme_gpgsm_new (GpgsmObject *r_gpgsm)
108 {
109   GpgmeError err = 0;
110   GpgsmObject gpgsm;
111   char *argv[] = { "gpgsm", "--server", NULL };
112   int ip[2] = { -1, -1 };
113   int op[2] = { -1, -1 };
114   int mp[2] = { -1, -1 };
115
116   *r_gpgsm = NULL;
117   gpgsm = xtrycalloc (1, sizeof *gpgsm);
118   if (!gpgsm)
119     {
120       err = mk_error (Out_Of_Core);
121       goto leave;
122     }
123
124   if (_gpgme_io_pipe (ip, 0) < 0)
125     {
126       err = mk_error (General_Error);
127       goto leave;
128     }
129   gpgsm->input_fd = ip[1];
130   fcntl (ip[1], F_SETFD, FD_CLOEXEC); /* FIXME */
131   gpgsm->input_fd_server = ip[0];
132   if (_gpgme_io_pipe (op, 1) < 0)
133     {
134       err = mk_error (General_Error);
135       goto leave;
136     }
137   gpgsm->output_fd = op[0];
138   fcntl (op[0], F_SETFD, FD_CLOEXEC); /* FIXME */
139   gpgsm->output_fd_server = op[1];
140   if (_gpgme_io_pipe (mp, 0) < 0)
141     {
142       err = mk_error (General_Error);
143       goto leave;
144     }
145   gpgsm->message_fd = mp[1];
146   fcntl (mp[1], F_SETFD, FD_CLOEXEC); /* FIXME */
147   gpgsm->message_fd_server = mp[0];
148
149   err = assuan_pipe_connect (&gpgsm->assuan_ctx,
150                              _gpgme_get_gpgsm_path (), argv);
151
152  leave:
153   if (ip[0] != -1)
154     _gpgme_io_close (ip[0]);
155   if (op[1] != -1)
156     _gpgme_io_close (op[1]);
157   if (mp[0] != -1)
158     _gpgme_io_close (mp[0]);
159
160   if (err)
161     _gpgme_gpgsm_release (gpgsm);
162   else
163     *r_gpgsm = gpgsm;
164
165   return err;
166 }
167
168 void
169 _gpgme_gpgsm_release (GpgsmObject gpgsm)
170 {
171   pid_t pid;
172
173   if (!gpgsm)
174     return;
175
176   pid = assuan_get_pid (gpgsm->assuan_ctx);
177   if (pid != -1)
178     _gpgme_remove_proc_from_wait_queue (pid);
179
180   if (gpgsm->input_fd != -1)
181     _gpgme_io_close (gpgsm->input_fd);
182   if (gpgsm->output_fd != -1)
183     _gpgme_io_close (gpgsm->output_fd);
184   if (gpgsm->message_fd != -1)
185     _gpgme_io_close (gpgsm->message_fd);
186
187   assuan_pipe_disconnect (gpgsm->assuan_ctx);
188
189   xfree (gpgsm->command);
190   xfree (gpgsm);
191 }
192
193 static AssuanError
194 gpgsm_assuan_simple_command (ASSUAN_CONTEXT ctx, char *line)
195 {
196   AssuanError err;
197
198   err = _assuan_write_line (ctx, line);
199   if (err)
200     return err;
201
202   do
203     {
204       err = _assuan_read_line (ctx);
205       if (err)
206         return err;
207     }
208   while (*ctx->inbound.line == '#' || !ctx->inbound.linelen);
209   
210   if (ctx->inbound.linelen >= 2
211       && ctx->inbound.line[0] == 'O' && ctx->inbound.line[1] == 'K'
212       && (ctx->inbound.line[2] == '\0' || ctx->inbound.line[2] == ' '))
213     return 0;
214   else
215     return ASSUAN_General_Error;
216 }
217
218 #define COMMANDLINELEN 40
219 static AssuanError
220 gpgsm_set_fd (ASSUAN_CONTEXT ctx, const char *which, int fd, const char *opt)
221 {
222   char line[COMMANDLINELEN];
223
224   if (opt)
225     snprintf (line, COMMANDLINELEN, "%s FD=%i %s", which, fd, opt);
226   else
227     snprintf (line, COMMANDLINELEN, "%s FD=%i", which, fd);
228
229   return gpgsm_assuan_simple_command (ctx, line);
230 }
231
232 GpgmeError
233 _gpgme_gpgsm_op_decrypt (GpgsmObject gpgsm, GpgmeData ciph, GpgmeData plain)
234 {
235   AssuanError err;
236
237   if (!gpgsm)
238     return mk_error (Invalid_Value);
239
240   gpgsm->command = xtrystrdup ("DECRYPT");
241   if (!gpgsm->command)
242     return mk_error (Out_Of_Core);
243
244   gpgsm->input_data = ciph;
245   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
246   if (err)
247     return mk_error (General_Error);    /* FIXME */
248   gpgsm->output_data = plain;
249   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, 0);
250   if (err)
251     return mk_error (General_Error);    /* FIXME */
252   _gpgme_io_close (gpgsm->message_fd);
253   gpgsm->message_fd = -1;
254
255   return 0;
256 }
257
258 GpgmeError
259 _gpgme_gpgsm_op_delete (GpgsmObject gpgsm, GpgmeKey key, int allow_secret)
260 {
261   /* FIXME */
262   return mk_error (Not_Implemented);
263 }
264
265 static AssuanError
266 gpgsm_set_recipients (ASSUAN_CONTEXT ctx, GpgmeRecipients recp)
267 {
268   AssuanError err;
269   char *line;
270   int linelen;
271   struct user_id_s *r;
272
273   linelen = 10 + 40 + 1;        /* "RECIPIENT " + guess + '\0'.  */
274   line = xtrymalloc (10 + 40 + 1);
275   if (!line)
276     return ASSUAN_Out_Of_Core;
277   strcpy (line, "RECIPIENT ");
278   for (r = recp->list; r; r = r->next)
279     {
280       int newlen = 11 + strlen (r->name);
281       if (linelen < newlen)
282         {
283           char *newline = xtryrealloc (line, newlen);
284           if (! newline)
285             {
286               xfree (line);
287               return ASSUAN_Out_Of_Core;
288             }
289           line = newline;
290           linelen = newlen;
291         }
292       strcpy (&line[10], r->name);
293       
294       err = gpgsm_assuan_simple_command (ctx, line);
295       if (err)
296         {
297           xfree (line);
298           return err;
299         }
300     }
301   return 0;
302 }
303
304 GpgmeError
305 _gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp,
306                          GpgmeData plain, GpgmeData ciph, int use_armor)
307 {
308   AssuanError err;
309
310   if (!gpgsm)
311     return mk_error (Invalid_Value);
312
313   gpgsm->command = xtrystrdup (use_armor ? "ENCRYPT armor" : "ENCRYPT");
314   if (!gpgsm->command)
315     return mk_error (Out_Of_Core);
316
317   gpgsm->input_data = plain;
318   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
319   if (err)
320     return mk_error (General_Error);    /* FIXME */
321   gpgsm->output_data = ciph;
322   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
323                       use_armor ? "--armor" : 0);
324   if (err)
325     return mk_error (General_Error);    /* FIXME */
326   _gpgme_io_close (gpgsm->message_fd);
327   gpgsm->message_fd = -1;
328
329   err = gpgsm_set_recipients (gpgsm->assuan_ctx, recp);
330   if (err)
331     return mk_error (General_Error);
332
333   return 0;
334 }
335
336 GpgmeError
337 _gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp,
338                         GpgmeData keydata, int use_armor)
339 {
340   /* FIXME */
341   return mk_error (Not_Implemented);
342 }
343
344 GpgmeError
345 _gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor)
346 {
347   /* FIXME */
348   return mk_error (Not_Implemented);
349 }
350
351 GpgmeError
352 _gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata)
353 {
354   AssuanError err;
355
356   if (!gpgsm)
357     return mk_error (Invalid_Value);
358
359   gpgsm->command = xtrystrdup ("IMPORT");
360   if (!gpgsm->command)
361     return mk_error (Out_Of_Core);
362
363   gpgsm->input_data = keydata;
364   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
365   if (err)
366     return mk_error (General_Error);    /* FIXME */
367   _gpgme_io_close (gpgsm->output_fd);
368   gpgsm->output_fd = -1;
369   _gpgme_io_close (gpgsm->message_fd);
370   gpgsm->message_fd = -1;
371
372   return 0;
373 }
374
375 GpgmeError
376 _gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern,
377                          int secret_only, int keylist_mode)
378 {
379   char *line;
380
381   line = xtrymalloc (9 + strlen (pattern) + 1); /* "LISTKEYS " + p + '\0'.  */
382   if (!line)
383     return mk_error (Out_Of_Core);
384   strcpy (line, "LISTKEYS ");
385   strcpy (&line[9], pattern);
386
387   _gpgme_io_close (gpgsm->input_fd);
388   gpgsm->input_fd = -1;
389   _gpgme_io_close (gpgsm->output_fd);
390   gpgsm->output_fd = -1;
391   _gpgme_io_close (gpgsm->message_fd);
392   gpgsm->message_fd = -1;
393
394   gpgsm->command = line;
395   return 0;
396 }
397
398 GpgmeError
399 _gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out,
400                       GpgmeSigMode mode, int use_armor,
401                       int use_textmode, GpgmeCtx ctx /* FIXME */)
402 {
403   AssuanError err;
404
405   if (!gpgsm)
406     return mk_error (Invalid_Value);
407
408   gpgsm->command = xtrystrdup (mode == GPGME_SIG_MODE_DETACH
409                                ? "SIGN --detach" : "SIGN");
410   if (!gpgsm->command)
411     return mk_error (Out_Of_Core);
412
413   gpgsm->input_data = in;
414   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
415   if (err)
416     return mk_error (General_Error);    /* FIXME */
417   gpgsm->output_data = out;
418   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
419                       use_armor ? "--armor" : 0);
420   if (err)
421     return mk_error (General_Error);    /* FIXME */
422   _gpgme_io_close (gpgsm->message_fd);
423   gpgsm->message_fd = -1;
424
425   return 0;
426 }
427
428 GpgmeError
429 _gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern)
430 {
431   /* FIXME */
432   return mk_error (Not_Implemented);
433 }
434
435 GpgmeError
436 _gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text)
437 {
438   AssuanError err;
439
440   if (!gpgsm)
441     return mk_error (Invalid_Value);
442
443   gpgsm->command = xtrystrdup ("VERIFY");
444   if (!gpgsm->command)
445     return mk_error (Out_Of_Core);
446
447   gpgsm->input_data = sig;
448   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
449   if (err)
450     return mk_error (General_Error);    /* FIXME */
451   gpgsm->message_data = sig;
452   err = gpgsm_set_fd (gpgsm->assuan_ctx, "MESSAGE", gpgsm->message_fd_server,
453                       0);
454   if (err)
455     return mk_error (General_Error);    /* FIXME */
456   _gpgme_io_close (gpgsm->output_fd);
457   gpgsm->output_fd = -1;
458
459   return 0;
460 }
461
462 static int
463 status_cmp (const void *ap, const void *bp)
464 {
465   const struct status_table_s *a = ap;
466   const struct status_table_s *b = bp;
467
468   return strcmp (a->name, b->name);
469 }
470
471 static int
472 gpgsm_status_handler (void *opaque, int pid, int fd)
473 {
474   int err;
475   GpgsmObject gpgsm = opaque;
476   ASSUAN_CONTEXT actx = gpgsm->assuan_ctx;
477   char *line;
478   int linelen;
479   char *next_line;
480
481   assert (fd == gpgsm->assuan_ctx->inbound.fd);
482
483   err = _assuan_read_line (gpgsm->assuan_ctx);
484
485   /* Assuan can currently return more than one line at once.  */
486   line = actx->inbound.line;
487
488   while (line)
489     {
490       next_line = strchr (line, '\n');
491       if (next_line)
492         *next_line++ = 0;
493       linelen = strlen (line);
494
495       if (line[0] == '#' || !linelen)
496         return 0;  /* FIXME */
497
498       if ((linelen >= 2
499            && line[0] == 'O' && line[1] == 'K'
500            && (line[2] == '\0' || line[2] == ' '))
501           || (linelen >= 3
502               && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
503               && (line[3] == '\0' || line[3] == ' ')))
504         {
505           /* FIXME Save error somewhere.  */
506           if (gpgsm->status.fnc)
507             gpgsm->status.fnc (gpgsm->status.fnc_value, STATUS_EOF, "");
508           return 1;
509         }
510       /* FIXME: Parse the status and call the handler.  */
511       
512       if (linelen > 2
513           && line[0] == 'S' && line[1] == ' ')
514         {
515           struct status_table_s t, *r;
516           char *rest;
517           
518           rest = strchr (line + 2, ' ');
519           if (!rest)
520             rest = line + linelen; /* set to an empty string */
521           else
522             *rest++ = 0;
523
524           t.name = line + 2;
525           r = bsearch (&t, status_table, DIM(status_table) - 1,
526                        sizeof t, status_cmp);
527
528           if (r)
529             {
530               if (gpgsm->status.fnc)
531                 gpgsm->status.fnc (gpgsm->status.fnc_value, r->code, rest);
532             }
533           else
534             fprintf (stderr, "[UNKNOWN STATUS]%s %s", t.name, rest);
535         }
536       line = next_line;
537     }
538
539   return 0;
540 }
541
542 void
543 _gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm,
544                                  GpgStatusHandler fnc, void *fnc_value) 
545 {
546   assert (gpgsm);
547
548   gpgsm->status.fnc = fnc;
549   gpgsm->status.fnc_value = fnc_value;
550 }
551
552 GpgmeError
553 _gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque)
554 {
555   GpgmeError err = 0;
556   pid_t pid;
557
558   if (!gpgsm)
559     return mk_error (Invalid_Value);
560
561   pid = assuan_get_pid (gpgsm->assuan_ctx);
562
563   err = _gpgme_register_pipe_handler (opaque, gpgsm_status_handler, gpgsm, pid,
564                                       gpgsm->assuan_ctx->inbound.fd, 1);
565
566   if (gpgsm->input_fd != -1)
567     {
568       err = _gpgme_register_pipe_handler (opaque, _gpgme_data_outbound_handler,
569                                           gpgsm->input_data, pid,
570                                           gpgsm->input_fd, 0);
571       if (!err) /* FIXME Kludge around poll() problem.  */
572         err = _gpgme_io_set_nonblocking (gpgsm->input_fd);
573     }
574   if (!err && gpgsm->output_fd != -1)
575     err = _gpgme_register_pipe_handler (opaque, _gpgme_data_inbound_handler,
576                                         gpgsm->output_data, pid,
577                                         gpgsm->output_fd, 1);
578   if (!err && gpgsm->message_fd != -1)
579     {
580       err = _gpgme_register_pipe_handler (opaque, _gpgme_data_outbound_handler,
581                                           gpgsm->message_data, pid,
582                                           gpgsm->message_fd, 0);
583       if (!err) /* FIXME Kludge around poll() problem.  */
584         err = _gpgme_io_set_nonblocking (gpgsm->message_fd);
585     }
586
587   if (!err)
588     err = _assuan_write_line (gpgsm->assuan_ctx, gpgsm->command);
589
590   return err;
591 }
592
593 #else   /* ENABLE_GPGSM */
594
595 #include <stddef.h>
596 #include "util.h"
597
598 #include "engine-gpgsm.h"
599
600 const char *
601 _gpgme_gpgsm_get_version (void)
602 {
603   return NULL;
604 }
605
606 GpgmeError
607 _gpgme_gpgsm_check_version (void)
608 {
609   return mk_error (Invalid_Engine);
610 }
611
612 GpgmeError
613 _gpgme_gpgsm_new (GpgsmObject *r_gpgsm)
614 {
615   return mk_error (Invalid_Engine);
616 }
617
618 void
619 _gpgme_gpgsm_release (GpgsmObject gpgsm)
620 {
621   return;
622 }
623
624 void
625 _gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm,
626                                  GpgStatusHandler fnc, void *fnc_value) 
627 {
628   return;
629 }
630
631 GpgmeError
632 _gpgme_gpgsm_op_decrypt (GpgsmObject gpgsm, GpgmeData ciph, GpgmeData plain)
633 {
634   return mk_error (Invalid_Engine);
635 }
636
637 GpgmeError
638 _gpgme_gpgsm_op_delete (GpgsmObject gpgsm, GpgmeKey key, int allow_secret)
639 {
640   return mk_error (Invalid_Engine);
641 }
642
643 GpgmeError
644 _gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp,
645                          GpgmeData plain, GpgmeData ciph, int use_armor)
646 {
647   return mk_error (Invalid_Engine);
648 }
649
650 GpgmeError
651 _gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp,
652                         GpgmeData keydata, int use_armor)
653 {
654   return mk_error (Invalid_Engine);
655 }
656
657 GpgmeError
658 _gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor)
659 {
660   return mk_error (Invalid_Engine);
661 }
662   
663 GpgmeError
664 _gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata)
665 {
666   return mk_error (Invalid_Engine);
667 }
668
669 GpgmeError
670 _gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern,
671                          int secret_only, int keylist_mode)
672 {
673   return mk_error (Invalid_Engine);
674 }
675
676 GpgmeError
677 _gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out,
678                       GpgmeSigMode mode, int use_armor,
679                       int use_textmode, GpgmeCtx ctx /* FIXME */)
680 {
681   return mk_error (Invalid_Engine);
682 }
683
684 GpgmeError
685 _gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern)
686 {
687   return mk_error (Invalid_Engine);
688 }
689
690 GpgmeError
691 _gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text)
692 {
693   return mk_error (Invalid_Engine);
694 }
695
696 GpgmeError
697 _gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque)
698 {
699   return mk_error (Invalid_Engine);
700 }
701
702 #endif  /* ! ENABLE_GPGSM */