* engine-gpgsm.c (_gpgme_gpgsm_op_keylist): Allow NULL for
[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 #warning  version check is disabled
90   static const char *gpgsm_version = "0.0.1";
91
92   /* FIXME: Locking.  */
93   if (!gpgsm_version)
94     gpgsm_version = _gpgme_get_program_version (_gpgme_get_gpgsm_path ());
95
96   return gpgsm_version;
97 }
98
99 GpgmeError
100 _gpgme_gpgsm_check_version (void)
101 {
102     return _gpgme_compare_versions (_gpgme_gpgsm_get_version (),
103                                   NEED_GPGSM_VERSION)
104     ? 0 : mk_error (Invalid_Engine);
105 }
106
107 GpgmeError
108 _gpgme_gpgsm_new (GpgsmObject *r_gpgsm)
109 {
110   GpgmeError err = 0;
111   GpgsmObject gpgsm;
112   char *argv[] = { "gpgsm", "--server", NULL };
113   int ip[2] = { -1, -1 };
114   int op[2] = { -1, -1 };
115   int mp[2] = { -1, -1 };
116
117   *r_gpgsm = NULL;
118   gpgsm = xtrycalloc (1, sizeof *gpgsm);
119   if (!gpgsm)
120     {
121       err = mk_error (Out_Of_Core);
122       goto leave;
123     }
124
125   if (_gpgme_io_pipe (ip, 0) < 0)
126     {
127       err = mk_error (General_Error);
128       goto leave;
129     }
130   gpgsm->input_fd = ip[1];
131   fcntl (ip[1], F_SETFD, FD_CLOEXEC); /* FIXME */
132   gpgsm->input_fd_server = ip[0];
133   if (_gpgme_io_pipe (op, 1) < 0)
134     {
135       err = mk_error (General_Error);
136       goto leave;
137     }
138   gpgsm->output_fd = op[0];
139   fcntl (op[0], F_SETFD, FD_CLOEXEC); /* FIXME */
140   gpgsm->output_fd_server = op[1];
141   if (_gpgme_io_pipe (mp, 0) < 0)
142     {
143       err = mk_error (General_Error);
144       goto leave;
145     }
146   gpgsm->message_fd = mp[1];
147   fcntl (mp[1], F_SETFD, FD_CLOEXEC); /* FIXME */
148   gpgsm->message_fd_server = mp[0];
149
150   err = assuan_pipe_connect (&gpgsm->assuan_ctx,
151                              _gpgme_get_gpgsm_path (), argv);
152
153  leave:
154   if (ip[0] != -1)
155     _gpgme_io_close (ip[0]);
156   if (op[1] != -1)
157     _gpgme_io_close (op[1]);
158   if (mp[0] != -1)
159     _gpgme_io_close (mp[0]);
160
161   if (err)
162     _gpgme_gpgsm_release (gpgsm);
163   else
164     *r_gpgsm = gpgsm;
165
166   return err;
167 }
168
169 void
170 _gpgme_gpgsm_release (GpgsmObject gpgsm)
171 {
172   pid_t pid;
173
174   if (!gpgsm)
175     return;
176
177   pid = assuan_get_pid (gpgsm->assuan_ctx);
178   if (pid != -1)
179     _gpgme_remove_proc_from_wait_queue (pid);
180
181   if (gpgsm->input_fd != -1)
182     _gpgme_io_close (gpgsm->input_fd);
183   if (gpgsm->output_fd != -1)
184     _gpgme_io_close (gpgsm->output_fd);
185   if (gpgsm->message_fd != -1)
186     _gpgme_io_close (gpgsm->message_fd);
187
188   assuan_pipe_disconnect (gpgsm->assuan_ctx);
189
190   xfree (gpgsm->command);
191   xfree (gpgsm);
192 }
193
194 static AssuanError
195 gpgsm_assuan_simple_command (ASSUAN_CONTEXT ctx, char *line)
196 {
197   AssuanError err;
198
199   err = assuan_write_line (ctx, line);
200   if (err)
201     return err;
202
203   do
204     {
205       err = _assuan_read_line (ctx);
206       if (err)
207         return err;
208     }
209   while (*ctx->inbound.line == '#' || !ctx->inbound.linelen);
210   
211   if (ctx->inbound.linelen >= 2
212       && ctx->inbound.line[0] == 'O' && ctx->inbound.line[1] == 'K'
213       && (ctx->inbound.line[2] == '\0' || ctx->inbound.line[2] == ' '))
214     return 0;
215   else
216     return ASSUAN_General_Error;
217 }
218
219 #define COMMANDLINELEN 40
220 static AssuanError
221 gpgsm_set_fd (ASSUAN_CONTEXT ctx, const char *which, int fd, const char *opt)
222 {
223   char line[COMMANDLINELEN];
224
225   if (opt)
226     snprintf (line, COMMANDLINELEN, "%s FD=%i %s", which, fd, opt);
227   else
228     snprintf (line, COMMANDLINELEN, "%s FD=%i", which, fd);
229
230   return gpgsm_assuan_simple_command (ctx, line);
231 }
232
233 GpgmeError
234 _gpgme_gpgsm_op_decrypt (GpgsmObject gpgsm, GpgmeData ciph, GpgmeData plain)
235 {
236   AssuanError err;
237
238   if (!gpgsm)
239     return mk_error (Invalid_Value);
240
241   gpgsm->command = xtrystrdup ("DECRYPT");
242   if (!gpgsm->command)
243     return mk_error (Out_Of_Core);
244
245   gpgsm->input_data = ciph;
246   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
247   if (err)
248     return mk_error (General_Error);    /* FIXME */
249   gpgsm->output_data = plain;
250   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, 0);
251   if (err)
252     return mk_error (General_Error);    /* FIXME */
253   _gpgme_io_close (gpgsm->message_fd);
254   gpgsm->message_fd = -1;
255
256   return 0;
257 }
258
259 GpgmeError
260 _gpgme_gpgsm_op_delete (GpgsmObject gpgsm, GpgmeKey key, int allow_secret)
261 {
262   /* FIXME */
263   return mk_error (Not_Implemented);
264 }
265
266 static AssuanError
267 gpgsm_set_recipients (ASSUAN_CONTEXT ctx, GpgmeRecipients recp)
268 {
269   AssuanError err;
270   char *line;
271   int linelen;
272   struct user_id_s *r;
273
274   linelen = 10 + 40 + 1;        /* "RECIPIENT " + guess + '\0'.  */
275   line = xtrymalloc (10 + 40 + 1);
276   if (!line)
277     return ASSUAN_Out_Of_Core;
278   strcpy (line, "RECIPIENT ");
279   for (r = recp->list; r; r = r->next)
280     {
281       int newlen = 11 + strlen (r->name);
282       if (linelen < newlen)
283         {
284           char *newline = xtryrealloc (line, newlen);
285           if (! newline)
286             {
287               xfree (line);
288               return ASSUAN_Out_Of_Core;
289             }
290           line = newline;
291           linelen = newlen;
292         }
293       strcpy (&line[10], r->name);
294       
295       err = gpgsm_assuan_simple_command (ctx, line);
296       if (err)
297         {
298           xfree (line);
299           return err;
300         }
301     }
302   return 0;
303 }
304
305 GpgmeError
306 _gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp,
307                          GpgmeData plain, GpgmeData ciph, int use_armor)
308 {
309   AssuanError err;
310
311   if (!gpgsm)
312     return mk_error (Invalid_Value);
313
314   gpgsm->command = xtrystrdup (use_armor ? "ENCRYPT armor" : "ENCRYPT");
315   if (!gpgsm->command)
316     return mk_error (Out_Of_Core);
317
318   gpgsm->input_data = plain;
319   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
320   if (err)
321     return mk_error (General_Error);    /* FIXME */
322   gpgsm->output_data = ciph;
323   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
324                       use_armor ? "--armor" : 0);
325   if (err)
326     return mk_error (General_Error);    /* FIXME */
327   _gpgme_io_close (gpgsm->message_fd);
328   gpgsm->message_fd = -1;
329
330   err = gpgsm_set_recipients (gpgsm->assuan_ctx, recp);
331   if (err)
332     return mk_error (General_Error);
333
334   return 0;
335 }
336
337 GpgmeError
338 _gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp,
339                         GpgmeData keydata, int use_armor)
340 {
341   /* FIXME */
342   return mk_error (Not_Implemented);
343 }
344
345 GpgmeError
346 _gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor)
347 {
348   /* FIXME */
349   return mk_error (Not_Implemented);
350 }
351
352 GpgmeError
353 _gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata)
354 {
355   AssuanError err;
356
357   if (!gpgsm)
358     return mk_error (Invalid_Value);
359
360   gpgsm->command = xtrystrdup ("IMPORT");
361   if (!gpgsm->command)
362     return mk_error (Out_Of_Core);
363
364   gpgsm->input_data = keydata;
365   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
366   if (err)
367     return mk_error (General_Error);    /* FIXME */
368   _gpgme_io_close (gpgsm->output_fd);
369   gpgsm->output_fd = -1;
370   _gpgme_io_close (gpgsm->message_fd);
371   gpgsm->message_fd = -1;
372
373   return 0;
374 }
375
376 GpgmeError
377 _gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern,
378                          int secret_only, int keylist_mode)
379 {
380   char *line;
381
382   if (!pattern)
383     pattern = "";
384
385   line = xtrymalloc (9 + strlen (pattern) + 1); /* "LISTKEYS " + p + '\0'.  */
386   if (!line)
387     return mk_error (Out_Of_Core);
388   strcpy (line, "LISTKEYS ");
389   strcpy (&line[9], pattern);
390
391   _gpgme_io_close (gpgsm->input_fd);
392   gpgsm->input_fd = -1;
393   _gpgme_io_close (gpgsm->output_fd);
394   gpgsm->output_fd = -1;
395   _gpgme_io_close (gpgsm->message_fd);
396   gpgsm->message_fd = -1;
397
398   gpgsm->command = line;
399   return 0;
400 }
401
402 GpgmeError
403 _gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out,
404                       GpgmeSigMode mode, int use_armor,
405                       int use_textmode, GpgmeCtx ctx /* FIXME */)
406 {
407   AssuanError err;
408
409   if (!gpgsm)
410     return mk_error (Invalid_Value);
411
412   gpgsm->command = xtrystrdup (mode == GPGME_SIG_MODE_DETACH
413                                ? "SIGN --detach" : "SIGN");
414   if (!gpgsm->command)
415     return mk_error (Out_Of_Core);
416
417   gpgsm->input_data = in;
418   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
419   if (err)
420     return mk_error (General_Error);    /* FIXME */
421   gpgsm->output_data = out;
422   err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server,
423                       use_armor ? "--armor" : 0);
424   if (err)
425     return mk_error (General_Error);    /* FIXME */
426   _gpgme_io_close (gpgsm->message_fd);
427   gpgsm->message_fd = -1;
428
429   return 0;
430 }
431
432 GpgmeError
433 _gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern)
434 {
435   /* FIXME */
436   return mk_error (Not_Implemented);
437 }
438
439 GpgmeError
440 _gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text)
441 {
442   AssuanError err;
443
444   if (!gpgsm)
445     return mk_error (Invalid_Value);
446
447   gpgsm->command = xtrystrdup ("VERIFY");
448   if (!gpgsm->command)
449     return mk_error (Out_Of_Core);
450
451   gpgsm->input_data = sig;
452   err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, 0);
453   if (err)
454     return mk_error (General_Error);    /* FIXME */
455   gpgsm->message_data = sig;
456   err = gpgsm_set_fd (gpgsm->assuan_ctx, "MESSAGE", gpgsm->message_fd_server,
457                       0);
458   if (err)
459     return mk_error (General_Error);    /* FIXME */
460   _gpgme_io_close (gpgsm->output_fd);
461   gpgsm->output_fd = -1;
462
463   return 0;
464 }
465
466 static int
467 status_cmp (const void *ap, const void *bp)
468 {
469   const struct status_table_s *a = ap;
470   const struct status_table_s *b = bp;
471
472   return strcmp (a->name, b->name);
473 }
474
475 static int
476 gpgsm_status_handler (void *opaque, int pid, int fd)
477 {
478   int err;
479   GpgsmObject gpgsm = opaque;
480   ASSUAN_CONTEXT actx = gpgsm->assuan_ctx;
481   char *line;
482   int linelen;
483
484   do
485     {
486       assert (fd == gpgsm->assuan_ctx->inbound.fd);
487
488       err = _assuan_read_line (gpgsm->assuan_ctx);
489       line = actx->inbound.line;
490       linelen = strlen (line);
491
492       if ((linelen >= 2
493            && line[0] == 'O' && line[1] == 'K'
494            && (line[2] == '\0' || line[2] == ' '))
495           || (linelen >= 3
496               && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
497               && (line[3] == '\0' || line[3] == ' ')))
498         {
499           /* FIXME Save error somewhere.  */
500           if (gpgsm->status.fnc)
501             gpgsm->status.fnc (gpgsm->status.fnc_value, STATUS_EOF, "");
502           return 1;
503         }
504       /* FIXME: Parse the status and call the handler.  */
505       
506       if (linelen > 2
507           && line[0] == 'S' && line[1] == ' ')
508         {
509           struct status_table_s t, *r;
510           char *rest;
511           
512           rest = strchr (line + 2, ' ');
513           if (!rest)
514             rest = line + linelen; /* set to an empty string */
515           else
516             *rest++ = 0;
517
518           t.name = line + 2;
519           r = bsearch (&t, status_table, DIM(status_table) - 1,
520                        sizeof t, status_cmp);
521
522           if (r)
523             {
524               if (gpgsm->status.fnc)
525                 gpgsm->status.fnc (gpgsm->status.fnc_value, r->code, rest);
526             }
527           else
528             fprintf (stderr, "[UNKNOWN STATUS]%s %s", t.name, rest);
529         }
530     }
531   while (gpgsm->assuan_ctx->inbound.attic.linelen);
532   
533   return 0;
534 }
535
536 void
537 _gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm,
538                                  GpgStatusHandler fnc, void *fnc_value) 
539 {
540   assert (gpgsm);
541
542   gpgsm->status.fnc = fnc;
543   gpgsm->status.fnc_value = fnc_value;
544 }
545
546 GpgmeError
547 _gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque)
548 {
549   GpgmeError err = 0;
550   pid_t pid;
551
552   if (!gpgsm)
553     return mk_error (Invalid_Value);
554
555   pid = assuan_get_pid (gpgsm->assuan_ctx);
556
557   err = _gpgme_register_pipe_handler (opaque, gpgsm_status_handler, gpgsm, pid,
558                                       gpgsm->assuan_ctx->inbound.fd, 1);
559
560   if (gpgsm->input_fd != -1)
561     {
562       err = _gpgme_register_pipe_handler (opaque, _gpgme_data_outbound_handler,
563                                           gpgsm->input_data, pid,
564                                           gpgsm->input_fd, 0);
565       if (!err) /* FIXME Kludge around poll() problem.  */
566         err = _gpgme_io_set_nonblocking (gpgsm->input_fd);
567     }
568   if (!err && gpgsm->output_fd != -1)
569     err = _gpgme_register_pipe_handler (opaque, _gpgme_data_inbound_handler,
570                                         gpgsm->output_data, pid,
571                                         gpgsm->output_fd, 1);
572   if (!err && gpgsm->message_fd != -1)
573     {
574       err = _gpgme_register_pipe_handler (opaque, _gpgme_data_outbound_handler,
575                                           gpgsm->message_data, pid,
576                                           gpgsm->message_fd, 0);
577       if (!err) /* FIXME Kludge around poll() problem.  */
578         err = _gpgme_io_set_nonblocking (gpgsm->message_fd);
579     }
580
581   if (!err)
582     err = assuan_write_line (gpgsm->assuan_ctx, gpgsm->command);
583
584   return err;
585 }
586
587 #else   /* ENABLE_GPGSM */
588
589 #include <stddef.h>
590 #include "util.h"
591
592 #include "engine-gpgsm.h"
593
594 const char *
595 _gpgme_gpgsm_get_version (void)
596 {
597   return NULL;
598 }
599
600 GpgmeError
601 _gpgme_gpgsm_check_version (void)
602 {
603   return mk_error (Invalid_Engine);
604 }
605
606 GpgmeError
607 _gpgme_gpgsm_new (GpgsmObject *r_gpgsm)
608 {
609   return mk_error (Invalid_Engine);
610 }
611
612 void
613 _gpgme_gpgsm_release (GpgsmObject gpgsm)
614 {
615   return;
616 }
617
618 void
619 _gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm,
620                                  GpgStatusHandler fnc, void *fnc_value) 
621 {
622   return;
623 }
624
625 GpgmeError
626 _gpgme_gpgsm_op_decrypt (GpgsmObject gpgsm, GpgmeData ciph, GpgmeData plain)
627 {
628   return mk_error (Invalid_Engine);
629 }
630
631 GpgmeError
632 _gpgme_gpgsm_op_delete (GpgsmObject gpgsm, GpgmeKey key, int allow_secret)
633 {
634   return mk_error (Invalid_Engine);
635 }
636
637 GpgmeError
638 _gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp,
639                          GpgmeData plain, GpgmeData ciph, int use_armor)
640 {
641   return mk_error (Invalid_Engine);
642 }
643
644 GpgmeError
645 _gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp,
646                         GpgmeData keydata, int use_armor)
647 {
648   return mk_error (Invalid_Engine);
649 }
650
651 GpgmeError
652 _gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor)
653 {
654   return mk_error (Invalid_Engine);
655 }
656   
657 GpgmeError
658 _gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata)
659 {
660   return mk_error (Invalid_Engine);
661 }
662
663 GpgmeError
664 _gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern,
665                          int secret_only, int keylist_mode)
666 {
667   return mk_error (Invalid_Engine);
668 }
669
670 GpgmeError
671 _gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out,
672                       GpgmeSigMode mode, int use_armor,
673                       int use_textmode, GpgmeCtx ctx /* FIXME */)
674 {
675   return mk_error (Invalid_Engine);
676 }
677
678 GpgmeError
679 _gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern)
680 {
681   return mk_error (Invalid_Engine);
682 }
683
684 GpgmeError
685 _gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text)
686 {
687   return mk_error (Invalid_Engine);
688 }
689
690 GpgmeError
691 _gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque)
692 {
693   return mk_error (Invalid_Engine);
694 }
695
696 #endif  /* ! ENABLE_GPGSM */