core: Minor change of the gpgme_op_edit semantics.
[gpgme.git] / src / engine-uiserver.c
1 /* engine-uiserver.c - Uiserver engine.
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 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 Lesser General Public License as
9    published by the Free Software Foundation; either version 2.1 of
10    the License, or (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    Lesser General Public License for more details.
16
17    You should have received a copy of the GNU Lesser General Public
18    License along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20    02111-1307, USA.  */
21
22 /* Peculiar: Use special keys from email address for recipient and
23    signer (==sender).  Use no data objects with encryption for
24    prep_encrypt.  */
25
26 #if HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <stdlib.h>
31 #include <string.h>
32 #ifdef HAVE_SYS_TYPES_H
33 # include <sys/types.h>
34 #endif
35 #include <assert.h>
36 #ifdef HAVE_UNISTD_H
37 # include <unistd.h>
38 #endif
39 #include <locale.h>
40 #include <fcntl.h> /* FIXME */
41 #include <errno.h>
42
43 #include "gpgme.h"
44 #include "util.h"
45 #include "ops.h"
46 #include "wait.h"
47 #include "priv-io.h"
48 #include "sema.h"
49 #include "data.h"
50
51 #include "assuan.h"
52 #include "debug.h"
53
54 #include "engine-backend.h"
55
56 \f
57 typedef struct
58 {
59   int fd;       /* FD we talk about.  */
60   int server_fd;/* Server FD for this connection.  */
61   int dir;      /* Inbound/Outbound, maybe given implicit?  */
62   void *data;   /* Handler-specific data.  */
63   void *tag;    /* ID from the user for gpgme_remove_io_callback.  */
64   char server_fd_str[15]; /* Same as SERVER_FD but as a string.  We
65                              need this because _gpgme_io_fd2str can't
66                              be used on a closed descriptor.  */
67 } iocb_data_t;
68
69
70 struct engine_uiserver
71 {
72   assuan_context_t assuan_ctx;
73
74   int lc_ctype_set;
75   int lc_messages_set;
76   gpgme_protocol_t protocol;
77
78   iocb_data_t status_cb;
79
80   /* Input, output etc are from the servers perspective.  */
81   iocb_data_t input_cb;
82   gpgme_data_t input_helper_data;  /* Input helper data object.  */
83   void *input_helper_memory;       /* Input helper memory block.  */
84
85   iocb_data_t output_cb;
86
87   iocb_data_t message_cb;
88
89   struct
90   {
91     engine_status_handler_t fnc;
92     void *fnc_value;
93     gpgme_status_cb_t mon_cb;
94     void *mon_cb_value;
95   } status;
96
97   struct
98   {
99     engine_colon_line_handler_t fnc;
100     void *fnc_value;
101     struct
102     {
103       char *line;
104       int linesize;
105       int linelen;
106     } attic;
107     int any; /* any data line seen */
108   } colon;
109
110   gpgme_data_t inline_data;  /* Used to collect D lines.  */
111
112   struct gpgme_io_cbs io_cbs;
113 };
114
115 typedef struct engine_uiserver *engine_uiserver_t;
116
117
118 static void uiserver_io_event (void *engine,
119                             gpgme_event_io_t type, void *type_data);
120
121
122 \f
123 static char *
124 uiserver_get_version (const char *file_name)
125 {
126   (void)file_name;
127   return NULL;
128 }
129
130
131 static const char *
132 uiserver_get_req_version (void)
133 {
134   return NULL;
135 }
136
137 \f
138 static void
139 close_notify_handler (int fd, void *opaque)
140 {
141   engine_uiserver_t uiserver = opaque;
142
143   assert (fd != -1);
144   if (uiserver->status_cb.fd == fd)
145     {
146       if (uiserver->status_cb.tag)
147         (*uiserver->io_cbs.remove) (uiserver->status_cb.tag);
148       uiserver->status_cb.fd = -1;
149       uiserver->status_cb.tag = NULL;
150     }
151   else if (uiserver->input_cb.fd == fd)
152     {
153       if (uiserver->input_cb.tag)
154         (*uiserver->io_cbs.remove) (uiserver->input_cb.tag);
155       uiserver->input_cb.fd = -1;
156       uiserver->input_cb.tag = NULL;
157       if (uiserver->input_helper_data)
158         {
159           gpgme_data_release (uiserver->input_helper_data);
160           uiserver->input_helper_data = NULL;
161         }
162       if (uiserver->input_helper_memory)
163         {
164           free (uiserver->input_helper_memory);
165           uiserver->input_helper_memory = NULL;
166         }
167     }
168   else if (uiserver->output_cb.fd == fd)
169     {
170       if (uiserver->output_cb.tag)
171         (*uiserver->io_cbs.remove) (uiserver->output_cb.tag);
172       uiserver->output_cb.fd = -1;
173       uiserver->output_cb.tag = NULL;
174     }
175   else if (uiserver->message_cb.fd == fd)
176     {
177       if (uiserver->message_cb.tag)
178         (*uiserver->io_cbs.remove) (uiserver->message_cb.tag);
179       uiserver->message_cb.fd = -1;
180       uiserver->message_cb.tag = NULL;
181     }
182 }
183
184
185 /* This is the default inquiry callback.  We use it to handle the
186    Pinentry notifications.  */
187 static gpgme_error_t
188 default_inq_cb (engine_uiserver_t uiserver, const char *line)
189 {
190   (void)uiserver;
191
192   if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17]))
193     {
194       _gpgme_allow_set_foreground_window ((pid_t)strtoul (line+17, NULL, 10));
195     }
196
197   return 0;
198 }
199
200
201 static gpgme_error_t
202 uiserver_cancel (void *engine)
203 {
204   engine_uiserver_t uiserver = engine;
205
206   if (!uiserver)
207     return gpg_error (GPG_ERR_INV_VALUE);
208
209   if (uiserver->status_cb.fd != -1)
210     _gpgme_io_close (uiserver->status_cb.fd);
211   if (uiserver->input_cb.fd != -1)
212     _gpgme_io_close (uiserver->input_cb.fd);
213   if (uiserver->output_cb.fd != -1)
214     _gpgme_io_close (uiserver->output_cb.fd);
215   if (uiserver->message_cb.fd != -1)
216     _gpgme_io_close (uiserver->message_cb.fd);
217
218   if (uiserver->assuan_ctx)
219     {
220       assuan_release (uiserver->assuan_ctx);
221       uiserver->assuan_ctx = NULL;
222     }
223
224   return 0;
225 }
226
227
228 static void
229 uiserver_release (void *engine)
230 {
231   engine_uiserver_t uiserver = engine;
232
233   if (!uiserver)
234     return;
235
236   uiserver_cancel (engine);
237
238   free (uiserver->colon.attic.line);
239   free (uiserver);
240 }
241
242
243 static gpgme_error_t
244 uiserver_new (void **engine, const char *file_name, const char *home_dir,
245               const char *version)
246 {
247   gpgme_error_t err = 0;
248   engine_uiserver_t uiserver;
249   char *dft_display = NULL;
250   char dft_ttyname[64];
251   char *dft_ttytype = NULL;
252   char *optstr;
253
254   (void)home_dir;
255   (void)version; /* Not yet used.  */
256
257   uiserver = calloc (1, sizeof *uiserver);
258   if (!uiserver)
259     return gpg_error_from_syserror ();
260
261   uiserver->protocol = GPGME_PROTOCOL_DEFAULT;
262   uiserver->status_cb.fd = -1;
263   uiserver->status_cb.dir = 1;
264   uiserver->status_cb.tag = 0;
265   uiserver->status_cb.data = uiserver;
266
267   uiserver->input_cb.fd = -1;
268   uiserver->input_cb.dir = 0;
269   uiserver->input_cb.tag = 0;
270   uiserver->input_cb.server_fd = -1;
271   *uiserver->input_cb.server_fd_str = 0;
272   uiserver->output_cb.fd = -1;
273   uiserver->output_cb.dir = 1;
274   uiserver->output_cb.tag = 0;
275   uiserver->output_cb.server_fd = -1;
276   *uiserver->output_cb.server_fd_str = 0;
277   uiserver->message_cb.fd = -1;
278   uiserver->message_cb.dir = 0;
279   uiserver->message_cb.tag = 0;
280   uiserver->message_cb.server_fd = -1;
281   *uiserver->message_cb.server_fd_str = 0;
282
283   uiserver->status.fnc = 0;
284   uiserver->colon.fnc = 0;
285   uiserver->colon.attic.line = 0;
286   uiserver->colon.attic.linesize = 0;
287   uiserver->colon.attic.linelen = 0;
288   uiserver->colon.any = 0;
289
290   uiserver->inline_data = NULL;
291
292   uiserver->io_cbs.add = NULL;
293   uiserver->io_cbs.add_priv = NULL;
294   uiserver->io_cbs.remove = NULL;
295   uiserver->io_cbs.event = NULL;
296   uiserver->io_cbs.event_priv = NULL;
297
298   err = assuan_new_ext (&uiserver->assuan_ctx, GPG_ERR_SOURCE_GPGME,
299                         &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb,
300                         NULL);
301   if (err)
302     goto leave;
303   assuan_ctx_set_system_hooks (uiserver->assuan_ctx,
304                                &_gpgme_assuan_system_hooks);
305
306   err = assuan_socket_connect (uiserver->assuan_ctx,
307                                file_name ?
308                                file_name : _gpgme_get_default_uisrv_socket (),
309                                0, ASSUAN_SOCKET_SERVER_FDPASSING);
310   if (err)
311     goto leave;
312
313   err = _gpgme_getenv ("DISPLAY", &dft_display);
314   if (err)
315     goto leave;
316   if (dft_display)
317     {
318       if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
319         {
320           err = gpg_error_from_syserror ();
321           free (dft_display);
322           goto leave;
323         }
324       free (dft_display);
325
326       err = assuan_transact (uiserver->assuan_ctx, optstr, NULL, NULL, NULL,
327                              NULL, NULL, NULL);
328       free (optstr);
329       if (err)
330         goto leave;
331     }
332
333   if (isatty (1))
334     {
335       int rc;
336
337       rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
338
339       /* Even though isatty() returns 1, ttyname_r() may fail in many
340          ways, e.g., when /dev/pts is not accessible under chroot.  */
341       if (!rc)
342         {
343           if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
344             {
345               err = gpg_error_from_syserror ();
346               goto leave;
347             }
348           err = assuan_transact (uiserver->assuan_ctx, optstr, NULL, NULL, NULL,
349                                  NULL, NULL, NULL);
350           free (optstr);
351           if (err)
352             goto leave;
353
354           err = _gpgme_getenv ("TERM", &dft_ttytype);
355           if (err)
356             goto leave;
357           if (dft_ttytype)
358             {
359               if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0)
360                 {
361                   err = gpg_error_from_syserror ();
362                   free (dft_ttytype);
363                   goto leave;
364                 }
365               free (dft_ttytype);
366
367               err = assuan_transact (uiserver->assuan_ctx, optstr, NULL, NULL,
368                                      NULL, NULL, NULL, NULL);
369               free (optstr);
370               if (err)
371                 goto leave;
372             }
373         }
374     }
375
376 #ifdef HAVE_W32_SYSTEM
377   /* Under Windows we need to use AllowSetForegroundWindow.  Tell
378      uiserver to tell us when it needs it.  */
379   if (!err)
380     {
381       err = assuan_transact (uiserver->assuan_ctx, "OPTION allow-pinentry-notify",
382                              NULL, NULL, NULL, NULL, NULL, NULL);
383       if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
384         err = 0; /* This is a new feature of uiserver.  */
385     }
386 #endif /*HAVE_W32_SYSTEM*/
387
388  leave:
389   if (err)
390     uiserver_release (uiserver);
391   else
392     *engine = uiserver;
393
394   return err;
395 }
396
397
398 static gpgme_error_t
399 uiserver_set_locale (void *engine, int category, const char *value)
400 {
401   engine_uiserver_t uiserver = engine;
402   gpgme_error_t err;
403   char *optstr;
404   const char *catstr;
405
406   /* FIXME: If value is NULL, we need to reset the option to default.
407      But we can't do this.  So we error out here.  UISERVER needs support
408      for this.  */
409   if (category == LC_CTYPE)
410     {
411       catstr = "lc-ctype";
412       if (!value && uiserver->lc_ctype_set)
413         return gpg_error (GPG_ERR_INV_VALUE);
414       if (value)
415         uiserver->lc_ctype_set = 1;
416     }
417 #ifdef LC_MESSAGES
418   else if (category == LC_MESSAGES)
419     {
420       catstr = "lc-messages";
421       if (!value && uiserver->lc_messages_set)
422         return gpg_error (GPG_ERR_INV_VALUE);
423       if (value)
424         uiserver->lc_messages_set = 1;
425     }
426 #endif /* LC_MESSAGES */
427   else
428     return gpg_error (GPG_ERR_INV_VALUE);
429
430   /* FIXME: Reset value to default.  */
431   if (!value)
432     return 0;
433
434   if (asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0)
435     err = gpg_error_from_syserror ();
436   else
437     {
438       err = assuan_transact (uiserver->assuan_ctx, optstr, NULL, NULL,
439                              NULL, NULL, NULL, NULL);
440       free (optstr);
441     }
442
443   return err;
444 }
445
446
447 static gpgme_error_t
448 uiserver_set_protocol (void *engine, gpgme_protocol_t protocol)
449 {
450   engine_uiserver_t uiserver = engine;
451
452   if (protocol != GPGME_PROTOCOL_OpenPGP
453       && protocol != GPGME_PROTOCOL_CMS
454       && protocol != GPGME_PROTOCOL_DEFAULT)
455     return gpg_error (GPG_ERR_INV_VALUE);
456
457   uiserver->protocol = protocol;
458   return 0;
459 }
460
461
462 static gpgme_error_t
463 uiserver_assuan_simple_command (engine_uiserver_t uiserver, const char *cmd,
464                                 engine_status_handler_t status_fnc,
465                                 void *status_fnc_value)
466 {
467   assuan_context_t ctx = uiserver->assuan_ctx;
468   gpg_error_t err;
469   char *line;
470   size_t linelen;
471
472   err = assuan_write_line (ctx, cmd);
473   if (err)
474     return err;
475
476   do
477     {
478       err = assuan_read_line (ctx, &line, &linelen);
479       if (err)
480         return err;
481
482       if (*line == '#' || !linelen)
483         continue;
484
485       if (linelen >= 2
486           && line[0] == 'O' && line[1] == 'K'
487           && (line[2] == '\0' || line[2] == ' '))
488         return 0;
489       else if (linelen >= 4
490           && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
491           && line[3] == ' ')
492         err = atoi (&line[4]);
493       else if (linelen >= 2
494                && line[0] == 'S' && line[1] == ' ')
495         {
496           char *rest;
497           gpgme_status_code_t r;
498
499           rest = strchr (line + 2, ' ');
500           if (!rest)
501             rest = line + linelen; /* set to an empty string */
502           else
503             *(rest++) = 0;
504
505           r = _gpgme_parse_status (line + 2);
506           if (uiserver->status.mon_cb && r != GPGME_STATUS_PROGRESS)
507             {
508               /* Note that we call the monitor even if we do
509                * not know the status code (r < 0).  */
510               err = uiserver->status.mon_cb (uiserver->status.mon_cb_value,
511                                              line + 2, rest);
512             }
513
514           if (err)
515             ;
516           else if (r >= 0 && status_fnc)
517             err = status_fnc (status_fnc_value, r, rest);
518           else
519             err = gpg_error (GPG_ERR_GENERAL);
520         }
521       else
522         err = gpg_error (GPG_ERR_GENERAL);
523     }
524   while (!err);
525
526   return err;
527 }
528
529
530 typedef enum { INPUT_FD, OUTPUT_FD, MESSAGE_FD } fd_type_t;
531
532 #define COMMANDLINELEN 40
533 static gpgme_error_t
534 uiserver_set_fd (engine_uiserver_t uiserver, fd_type_t fd_type, const char *opt)
535 {
536   gpg_error_t err = 0;
537   char line[COMMANDLINELEN];
538   const char *which;
539   iocb_data_t *iocb_data;
540   int dir;
541
542   switch (fd_type)
543     {
544     case INPUT_FD:
545       which = "INPUT";
546       iocb_data = &uiserver->input_cb;
547       break;
548
549     case OUTPUT_FD:
550       which = "OUTPUT";
551       iocb_data = &uiserver->output_cb;
552       break;
553
554     case MESSAGE_FD:
555       which = "MESSAGE";
556       iocb_data = &uiserver->message_cb;
557       break;
558
559     default:
560       return gpg_error (GPG_ERR_INV_VALUE);
561     }
562
563   dir = iocb_data->dir;
564
565   /* We try to short-cut the communication by giving UISERVER direct
566      access to the file descriptor, rather than using a pipe.  */
567   iocb_data->server_fd = _gpgme_data_get_fd (iocb_data->data);
568   if (iocb_data->server_fd < 0)
569     {
570       int fds[2];
571
572       if (_gpgme_io_pipe (fds, 0) < 0)
573         return gpg_error_from_syserror ();
574
575       iocb_data->fd = dir ? fds[0] : fds[1];
576       iocb_data->server_fd = dir ? fds[1] : fds[0];
577
578       if (_gpgme_io_set_close_notify (iocb_data->fd,
579                                       close_notify_handler, uiserver))
580         {
581           err = gpg_error (GPG_ERR_GENERAL);
582           goto leave_set_fd;
583         }
584     }
585
586   err = assuan_sendfd (uiserver->assuan_ctx, iocb_data->server_fd);
587   if (err)
588     goto leave_set_fd;
589
590   _gpgme_io_close (iocb_data->server_fd);
591   iocb_data->server_fd = -1;
592
593   if (opt)
594     snprintf (line, COMMANDLINELEN, "%s FD %s", which, opt);
595   else
596     snprintf (line, COMMANDLINELEN, "%s FD", which);
597
598   err = uiserver_assuan_simple_command (uiserver, line, NULL, NULL);
599
600  leave_set_fd:
601   if (err)
602     {
603       _gpgme_io_close (iocb_data->fd);
604       iocb_data->fd = -1;
605       if (iocb_data->server_fd != -1)
606         {
607           _gpgme_io_close (iocb_data->server_fd);
608           iocb_data->server_fd = -1;
609         }
610     }
611
612   return err;
613 }
614
615
616 static const char *
617 map_data_enc (gpgme_data_t d)
618 {
619   switch (gpgme_data_get_encoding (d))
620     {
621     case GPGME_DATA_ENCODING_NONE:
622       break;
623     case GPGME_DATA_ENCODING_BINARY:
624       return "--binary";
625     case GPGME_DATA_ENCODING_BASE64:
626       return "--base64";
627     case GPGME_DATA_ENCODING_ARMOR:
628       return "--armor";
629     default:
630       break;
631     }
632   return NULL;
633 }
634
635
636 static gpgme_error_t
637 status_handler (void *opaque, int fd)
638 {
639   struct io_cb_data *data = (struct io_cb_data *) opaque;
640   engine_uiserver_t uiserver = (engine_uiserver_t) data->handler_value;
641   gpgme_error_t err = 0;
642   char *line;
643   size_t linelen;
644
645   do
646     {
647       err = assuan_read_line (uiserver->assuan_ctx, &line, &linelen);
648       if (err)
649         {
650           /* Try our best to terminate the connection friendly.  */
651           /*      assuan_write_line (uiserver->assuan_ctx, "BYE"); */
652           TRACE3 (DEBUG_CTX, "gpgme:status_handler", uiserver,
653                   "fd 0x%x: error from assuan (%d) getting status line : %s",
654                   fd, err, gpg_strerror (err));
655         }
656       else if (linelen >= 3
657                && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
658                && (line[3] == '\0' || line[3] == ' '))
659         {
660           if (line[3] == ' ')
661             err = atoi (&line[4]);
662           if (! err)
663             err = gpg_error (GPG_ERR_GENERAL);
664           TRACE2 (DEBUG_CTX, "gpgme:status_handler", uiserver,
665                   "fd 0x%x: ERR line - mapped to: %s",
666                   fd, err ? gpg_strerror (err) : "ok");
667           /* Try our best to terminate the connection friendly.  */
668           /*      assuan_write_line (uiserver->assuan_ctx, "BYE"); */
669         }
670       else if (linelen >= 2
671                && line[0] == 'O' && line[1] == 'K'
672                && (line[2] == '\0' || line[2] == ' '))
673         {
674           if (uiserver->status.fnc)
675             {
676               char emptystring[1] = {0};
677               err = uiserver->status.fnc (uiserver->status.fnc_value,
678                                           GPGME_STATUS_EOF, emptystring);
679               if (gpg_err_code (err) == GPG_ERR_FALSE)
680                 err = 0; /* Drop special error code.  */
681             }
682
683           if (!err && uiserver->colon.fnc && uiserver->colon.any)
684             {
685               /* We must tell a colon function about the EOF. We do
686                  this only when we have seen any data lines.  Note
687                  that this inlined use of colon data lines will
688                  eventually be changed into using a regular data
689                  channel. */
690               uiserver->colon.any = 0;
691               err = uiserver->colon.fnc (uiserver->colon.fnc_value, NULL);
692             }
693           TRACE2 (DEBUG_CTX, "gpgme:status_handler", uiserver,
694                   "fd 0x%x: OK line - final status: %s",
695                   fd, err ? gpg_strerror (err) : "ok");
696           _gpgme_io_close (uiserver->status_cb.fd);
697           return err;
698         }
699       else if (linelen > 2
700                && line[0] == 'D' && line[1] == ' '
701                && uiserver->colon.fnc)
702         {
703           /* We are using the colon handler even for plain inline data
704              - strange name for that function but for historic reasons
705              we keep it.  */
706           /* FIXME We can't use this for binary data because we
707              assume this is a string.  For the current usage of colon
708              output it is correct.  */
709           char *src = line + 2;
710           char *end = line + linelen;
711           char *dst;
712           char **aline = &uiserver->colon.attic.line;
713           int *alinelen = &uiserver->colon.attic.linelen;
714
715           if (uiserver->colon.attic.linesize < *alinelen + linelen + 1)
716             {
717               char *newline = realloc (*aline, *alinelen + linelen + 1);
718               if (!newline)
719                 err = gpg_error_from_syserror ();
720               else
721                 {
722                   *aline = newline;
723                   uiserver->colon.attic.linesize = *alinelen + linelen + 1;
724                 }
725             }
726           if (!err)
727             {
728               dst = *aline + *alinelen;
729
730               while (!err && src < end)
731                 {
732                   if (*src == '%' && src + 2 < end)
733                     {
734                       /* Handle escaped characters.  */
735                       ++src;
736                       *dst = _gpgme_hextobyte (src);
737                       (*alinelen)++;
738                       src += 2;
739                     }
740                   else
741                     {
742                       *dst = *src++;
743                       (*alinelen)++;
744                     }
745
746                   if (*dst == '\n')
747                     {
748                       /* Terminate the pending line, pass it to the colon
749                          handler and reset it.  */
750
751                       uiserver->colon.any = 1;
752                       if (*alinelen > 1 && *(dst - 1) == '\r')
753                         dst--;
754                       *dst = '\0';
755
756                       /* FIXME How should we handle the return code?  */
757                       err = uiserver->colon.fnc (uiserver->colon.fnc_value, *aline);
758                       if (!err)
759                         {
760                           dst = *aline;
761                           *alinelen = 0;
762                         }
763                     }
764                   else
765                     dst++;
766                 }
767             }
768           TRACE2 (DEBUG_CTX, "gpgme:status_handler", uiserver,
769                   "fd 0x%x: D line; final status: %s",
770                   fd, err? gpg_strerror (err):"ok");
771         }
772       else if (linelen > 2
773                && line[0] == 'D' && line[1] == ' '
774                && uiserver->inline_data)
775         {
776           char *src = line + 2;
777           char *end = line + linelen;
778           char *dst = src;
779           gpgme_ssize_t nwritten;
780
781           linelen = 0;
782           while (src < end)
783             {
784               if (*src == '%' && src + 2 < end)
785                 {
786                   /* Handle escaped characters.  */
787                   ++src;
788                   *dst++ = _gpgme_hextobyte (src);
789                   src += 2;
790                 }
791               else
792                 *dst++ = *src++;
793
794               linelen++;
795             }
796
797           src = line + 2;
798           while (linelen > 0)
799             {
800               nwritten = gpgme_data_write (uiserver->inline_data, src, linelen);
801               if (!nwritten || (nwritten < 0 && errno != EINTR)
802                   || nwritten > linelen)
803                 {
804                   err = gpg_error_from_syserror ();
805                   break;
806                 }
807               src += nwritten;
808               linelen -= nwritten;
809             }
810
811           TRACE2 (DEBUG_CTX, "gpgme:status_handler", uiserver,
812                   "fd 0x%x: D inlinedata; final status: %s",
813                   fd, err? gpg_strerror (err):"ok");
814         }
815       else if (linelen > 2
816                && line[0] == 'S' && line[1] == ' ')
817         {
818           char *rest;
819           gpgme_status_code_t r;
820
821           rest = strchr (line + 2, ' ');
822           if (!rest)
823             rest = line + linelen; /* set to an empty string */
824           else
825             *(rest++) = 0;
826
827           r = _gpgme_parse_status (line + 2);
828
829           if (r >= 0)
830             {
831               if (uiserver->status.fnc)
832                 {
833                   err = uiserver->status.fnc (uiserver->status.fnc_value,
834                                               r, rest);
835                   if (gpg_err_code (err) == GPG_ERR_FALSE)
836                     err = 0; /* Drop special error code.  */
837                 }
838             }
839           else
840             fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest);
841           TRACE3 (DEBUG_CTX, "gpgme:status_handler", uiserver,
842                   "fd 0x%x: S line (%s) - final status: %s",
843                   fd, line+2, err? gpg_strerror (err):"ok");
844         }
845       else if (linelen >= 7
846                && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
847                && line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
848                && line[6] == 'E'
849                && (line[7] == '\0' || line[7] == ' '))
850         {
851           char *keyword = line+7;
852
853           while (*keyword == ' ')
854             keyword++;;
855           default_inq_cb (uiserver, keyword);
856           assuan_write_line (uiserver->assuan_ctx, "END");
857         }
858
859     }
860   while (!err && assuan_pending_line (uiserver->assuan_ctx));
861
862   return err;
863 }
864
865
866 static gpgme_error_t
867 add_io_cb (engine_uiserver_t uiserver, iocb_data_t *iocbd, gpgme_io_cb_t handler)
868 {
869   gpgme_error_t err;
870
871   TRACE_BEG2 (DEBUG_ENGINE, "engine-uiserver:add_io_cb", uiserver,
872               "fd %d, dir %d", iocbd->fd, iocbd->dir);
873   err = (*uiserver->io_cbs.add) (uiserver->io_cbs.add_priv,
874                               iocbd->fd, iocbd->dir,
875                               handler, iocbd->data, &iocbd->tag);
876   if (err)
877     return TRACE_ERR (err);
878   if (!iocbd->dir)
879     /* FIXME Kludge around poll() problem.  */
880     err = _gpgme_io_set_nonblocking (iocbd->fd);
881   return TRACE_ERR (err);
882 }
883
884
885 static gpgme_error_t
886 start (engine_uiserver_t uiserver, const char *command)
887 {
888   gpgme_error_t err;
889   int fdlist[5];
890   int nfds;
891
892   /* We need to know the fd used by assuan for reads.  We do this by
893      using the assumption that the first returned fd from
894      assuan_get_active_fds() is always this one.  */
895   nfds = assuan_get_active_fds (uiserver->assuan_ctx, 0 /* read fds */,
896                                 fdlist, DIM (fdlist));
897   if (nfds < 1)
898     return gpg_error (GPG_ERR_GENERAL); /* FIXME */
899
900   /* We "duplicate" the file descriptor, so we can close it here (we
901      can't close fdlist[0], as that is closed by libassuan, and
902      closing it here might cause libassuan to close some unrelated FD
903      later).  Alternatively, we could special case status_fd and
904      register/unregister it manually as needed, but this increases
905      code duplication and is more complicated as we can not use the
906      close notifications etc.  A third alternative would be to let
907      Assuan know that we closed the FD, but that complicates the
908      Assuan interface.  */
909
910   uiserver->status_cb.fd = _gpgme_io_dup (fdlist[0]);
911   if (uiserver->status_cb.fd < 0)
912     return gpg_error_from_syserror ();
913
914   if (_gpgme_io_set_close_notify (uiserver->status_cb.fd,
915                                   close_notify_handler, uiserver))
916     {
917       _gpgme_io_close (uiserver->status_cb.fd);
918       uiserver->status_cb.fd = -1;
919       return gpg_error (GPG_ERR_GENERAL);
920     }
921
922   err = add_io_cb (uiserver, &uiserver->status_cb, status_handler);
923   if (!err && uiserver->input_cb.fd != -1)
924     err = add_io_cb (uiserver, &uiserver->input_cb, _gpgme_data_outbound_handler);
925   if (!err && uiserver->output_cb.fd != -1)
926     err = add_io_cb (uiserver, &uiserver->output_cb, _gpgme_data_inbound_handler);
927   if (!err && uiserver->message_cb.fd != -1)
928     err = add_io_cb (uiserver, &uiserver->message_cb, _gpgme_data_outbound_handler);
929
930   if (!err)
931     err = assuan_write_line (uiserver->assuan_ctx, command);
932
933   if (!err)
934     uiserver_io_event (uiserver, GPGME_EVENT_START, NULL);
935
936   return err;
937 }
938
939
940 static gpgme_error_t
941 uiserver_reset (void *engine)
942 {
943   engine_uiserver_t uiserver = engine;
944
945   /* We must send a reset because we need to reset the list of
946      signers.  Note that RESET does not reset OPTION commands. */
947   return uiserver_assuan_simple_command (uiserver, "RESET", NULL, NULL);
948 }
949
950
951 static gpgme_error_t
952 _uiserver_decrypt (void *engine, int verify,
953                    gpgme_data_t ciph, gpgme_data_t plain)
954 {
955   engine_uiserver_t uiserver = engine;
956   gpgme_error_t err;
957   const char *protocol;
958   char *cmd;
959
960   if (!uiserver)
961     return gpg_error (GPG_ERR_INV_VALUE);
962   if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT)
963     protocol = "";
964   else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP)
965     protocol = " --protocol=OpenPGP";
966   else if (uiserver->protocol == GPGME_PROTOCOL_CMS)
967     protocol = " --protocol=CMS";
968   else
969     return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
970
971   if (asprintf (&cmd, "DECRYPT%s%s", protocol,
972                 verify ? "" : " --no-verify") < 0)
973     return gpg_error_from_syserror ();
974
975   uiserver->input_cb.data = ciph;
976   err = uiserver_set_fd (uiserver, INPUT_FD,
977                          map_data_enc (uiserver->input_cb.data));
978   if (err)
979     {
980       free (cmd);
981       return gpg_error (GPG_ERR_GENERAL);       /* FIXME */
982     }
983   uiserver->output_cb.data = plain;
984   err = uiserver_set_fd (uiserver, OUTPUT_FD, 0);
985   if (err)
986     {
987       free (cmd);
988       return gpg_error (GPG_ERR_GENERAL);       /* FIXME */
989     }
990   uiserver->inline_data = NULL;
991
992   err = start (engine, cmd);
993   free (cmd);
994   return err;
995 }
996
997
998 static gpgme_error_t
999 uiserver_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain)
1000 {
1001   return _uiserver_decrypt (engine, 0, ciph, plain);
1002 }
1003
1004
1005 static gpgme_error_t
1006 uiserver_decrypt_verify (void *engine, gpgme_data_t ciph, gpgme_data_t plain)
1007 {
1008   return _uiserver_decrypt (engine, 1, ciph, plain);
1009 }
1010
1011
1012 static gpgme_error_t
1013 set_recipients (engine_uiserver_t uiserver, gpgme_key_t recp[])
1014 {
1015   gpgme_error_t err = 0;
1016   char *line;
1017   int linelen;
1018   int invalid_recipients = 0;
1019   int i;
1020
1021   linelen = 10 + 40 + 1;        /* "RECIPIENT " + guess + '\0'.  */
1022   line = malloc (10 + 40 + 1);
1023   if (!line)
1024     return gpg_error_from_syserror ();
1025   strcpy (line, "RECIPIENT ");
1026   for (i=0; !err && recp[i]; i++)
1027     {
1028       char *uid;
1029       int newlen;
1030
1031       /* We use only the first user ID of the key.  */
1032       if (!recp[i]->uids || !(uid=recp[i]->uids->uid) || !*uid)
1033         {
1034           invalid_recipients++;
1035           continue;
1036         }
1037
1038       newlen = 11 + strlen (uid);
1039       if (linelen < newlen)
1040         {
1041           char *newline = realloc (line, newlen);
1042           if (! newline)
1043             {
1044               int saved_err = gpg_error_from_syserror ();
1045               free (line);
1046               return saved_err;
1047             }
1048           line = newline;
1049           linelen = newlen;
1050         }
1051       /* FIXME: need to do proper escaping  */
1052       strcpy (&line[10], uid);
1053
1054       err = uiserver_assuan_simple_command (uiserver, line,
1055                                             uiserver->status.fnc,
1056                                             uiserver->status.fnc_value);
1057       /* FIXME: This might requires more work.  */
1058       if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
1059         invalid_recipients++;
1060       else if (err)
1061         {
1062           free (line);
1063           return err;
1064         }
1065     }
1066   free (line);
1067   return gpg_error (invalid_recipients
1068                     ? GPG_ERR_UNUSABLE_PUBKEY : GPG_ERR_NO_ERROR);
1069 }
1070
1071
1072 static gpgme_error_t
1073 uiserver_encrypt (void *engine, gpgme_key_t recp[], gpgme_encrypt_flags_t flags,
1074                   gpgme_data_t plain, gpgme_data_t ciph, int use_armor)
1075 {
1076   engine_uiserver_t uiserver = engine;
1077   gpgme_error_t err;
1078   const char *protocol;
1079   char *cmd;
1080
1081   if (!uiserver)
1082     return gpg_error (GPG_ERR_INV_VALUE);
1083   if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT)
1084     protocol = "";
1085   else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP)
1086     protocol = " --protocol=OpenPGP";
1087   else if (uiserver->protocol == GPGME_PROTOCOL_CMS)
1088     protocol = " --protocol=CMS";
1089   else
1090     return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
1091
1092   if (flags & GPGME_ENCRYPT_PREPARE)
1093     {
1094       if (!recp || plain || ciph)
1095         return gpg_error (GPG_ERR_INV_VALUE);
1096
1097       if (asprintf (&cmd, "PREP_ENCRYPT%s%s", protocol,
1098                     (flags & GPGME_ENCRYPT_EXPECT_SIGN)
1099                     ? " --expect-sign" : "") < 0)
1100         return gpg_error_from_syserror ();
1101     }
1102   else
1103     {
1104       if (!plain || !ciph)
1105         return gpg_error (GPG_ERR_INV_VALUE);
1106
1107       if (asprintf (&cmd, "ENCRYPT%s", protocol) < 0)
1108         return gpg_error_from_syserror ();
1109     }
1110
1111   if (plain)
1112     {
1113       uiserver->input_cb.data = plain;
1114       err = uiserver_set_fd (uiserver, INPUT_FD,
1115                              map_data_enc (uiserver->input_cb.data));
1116       if (err)
1117         {
1118           free (cmd);
1119           return err;
1120         }
1121     }
1122
1123   if (ciph)
1124     {
1125       uiserver->output_cb.data = ciph;
1126       err = uiserver_set_fd (uiserver, OUTPUT_FD, use_armor ? "--armor"
1127                              : map_data_enc (uiserver->output_cb.data));
1128       if (err)
1129         {
1130           free (cmd);
1131           return err;
1132         }
1133     }
1134
1135   uiserver->inline_data = NULL;
1136
1137   if (recp)
1138     {
1139       err = set_recipients (uiserver, recp);
1140       if (err)
1141         {
1142           free (cmd);
1143           return err;
1144         }
1145     }
1146
1147   err = start (uiserver, cmd);
1148   free (cmd);
1149   return err;
1150 }
1151
1152
1153 static gpgme_error_t
1154 uiserver_sign (void *engine, gpgme_data_t in, gpgme_data_t out,
1155                gpgme_sig_mode_t mode, int use_armor, int use_textmode,
1156                int include_certs, gpgme_ctx_t ctx /* FIXME */)
1157 {
1158   engine_uiserver_t uiserver = engine;
1159   gpgme_error_t err = 0;
1160   const char *protocol;
1161   char *cmd;
1162   gpgme_key_t key;
1163
1164   (void)use_textmode;
1165   (void)include_certs;
1166
1167   if (!uiserver || !in || !out)
1168     return gpg_error (GPG_ERR_INV_VALUE);
1169   if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT)
1170     protocol = "";
1171   else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP)
1172     protocol = " --protocol=OpenPGP";
1173   else if (uiserver->protocol == GPGME_PROTOCOL_CMS)
1174     protocol = " --protocol=CMS";
1175   else
1176     return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
1177
1178   if (asprintf (&cmd, "SIGN%s%s", protocol,
1179                 (mode == GPGME_SIG_MODE_DETACH) ? " --detached" : "") < 0)
1180     return gpg_error_from_syserror ();
1181
1182   key = gpgme_signers_enum (ctx, 0);
1183   if (key)
1184     {
1185       const char *s = NULL;
1186
1187       if (key && key->uids)
1188         s = key->uids->email;
1189
1190       if (s && strlen (s) < 80)
1191         {
1192           char buf[100];
1193
1194           strcpy (stpcpy (buf, "SENDER --info "), s);
1195           err = uiserver_assuan_simple_command (uiserver, buf,
1196                                                 uiserver->status.fnc,
1197                                                 uiserver->status.fnc_value);
1198         }
1199       else
1200         err = gpg_error (GPG_ERR_INV_VALUE);
1201       gpgme_key_unref (key);
1202       if (err)
1203       {
1204         free (cmd);
1205         return err;
1206       }
1207   }
1208
1209   uiserver->input_cb.data = in;
1210   err = uiserver_set_fd (uiserver, INPUT_FD,
1211                          map_data_enc (uiserver->input_cb.data));
1212   if (err)
1213     {
1214       free (cmd);
1215       return err;
1216     }
1217   uiserver->output_cb.data = out;
1218   err = uiserver_set_fd (uiserver, OUTPUT_FD, use_armor ? "--armor"
1219                          : map_data_enc (uiserver->output_cb.data));
1220   if (err)
1221     {
1222       free (cmd);
1223       return err;
1224     }
1225   uiserver->inline_data = NULL;
1226
1227   err = start (uiserver, cmd);
1228   free (cmd);
1229   return err;
1230 }
1231
1232
1233 /* FIXME: Missing a way to specify --silent.  */
1234 static gpgme_error_t
1235 uiserver_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text,
1236               gpgme_data_t plaintext)
1237 {
1238   engine_uiserver_t uiserver = engine;
1239   gpgme_error_t err;
1240   const char *protocol;
1241   char *cmd;
1242
1243   if (!uiserver)
1244     return gpg_error (GPG_ERR_INV_VALUE);
1245   if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT)
1246     protocol = "";
1247   else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP)
1248     protocol = " --protocol=OpenPGP";
1249   else if (uiserver->protocol == GPGME_PROTOCOL_CMS)
1250     protocol = " --protocol=CMS";
1251   else
1252     return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
1253
1254   if (asprintf (&cmd, "VERIFY%s", protocol) < 0)
1255     return gpg_error_from_syserror ();
1256
1257   uiserver->input_cb.data = sig;
1258   err = uiserver_set_fd (uiserver, INPUT_FD,
1259                          map_data_enc (uiserver->input_cb.data));
1260   if (err)
1261     {
1262       free (cmd);
1263       return err;
1264     }
1265   if (plaintext)
1266     {
1267       /* Normal or cleartext signature.  */
1268       uiserver->output_cb.data = plaintext;
1269       err = uiserver_set_fd (uiserver, OUTPUT_FD, 0);
1270     }
1271   else
1272     {
1273       /* Detached signature.  */
1274       uiserver->message_cb.data = signed_text;
1275       err = uiserver_set_fd (uiserver, MESSAGE_FD, 0);
1276     }
1277   uiserver->inline_data = NULL;
1278
1279   if (!err)
1280     err = start (uiserver, cmd);
1281
1282   free (cmd);
1283   return err;
1284 }
1285
1286
1287 /* This sets a status callback for monitoring status lines before they
1288  * are passed to a caller set handler.  */
1289 static void
1290 uiserver_set_status_cb (void *engine, gpgme_status_cb_t cb, void *cb_value)
1291 {
1292   engine_uiserver_t uiserver = engine;
1293
1294   uiserver->status.mon_cb = cb;
1295   uiserver->status.mon_cb_value = cb_value;
1296 }
1297
1298
1299 static void
1300 uiserver_set_status_handler (void *engine, engine_status_handler_t fnc,
1301                           void *fnc_value)
1302 {
1303   engine_uiserver_t uiserver = engine;
1304
1305   uiserver->status.fnc = fnc;
1306   uiserver->status.fnc_value = fnc_value;
1307 }
1308
1309
1310 static gpgme_error_t
1311 uiserver_set_colon_line_handler (void *engine, engine_colon_line_handler_t fnc,
1312                               void *fnc_value)
1313 {
1314   engine_uiserver_t uiserver = engine;
1315
1316   uiserver->colon.fnc = fnc;
1317   uiserver->colon.fnc_value = fnc_value;
1318   uiserver->colon.any = 0;
1319   return 0;
1320 }
1321
1322
1323 static void
1324 uiserver_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
1325 {
1326   engine_uiserver_t uiserver = engine;
1327   uiserver->io_cbs = *io_cbs;
1328 }
1329
1330
1331 static void
1332 uiserver_io_event (void *engine, gpgme_event_io_t type, void *type_data)
1333 {
1334   engine_uiserver_t uiserver = engine;
1335
1336   TRACE3 (DEBUG_ENGINE, "gpgme:uiserver_io_event", uiserver,
1337           "event %p, type %d, type_data %p",
1338           uiserver->io_cbs.event, type, type_data);
1339   if (uiserver->io_cbs.event)
1340     (*uiserver->io_cbs.event) (uiserver->io_cbs.event_priv, type, type_data);
1341 }
1342
1343
1344 struct engine_ops _gpgme_engine_ops_uiserver =
1345   {
1346     /* Static functions.  */
1347     _gpgme_get_default_uisrv_socket,
1348     NULL,
1349     uiserver_get_version,
1350     uiserver_get_req_version,
1351     uiserver_new,
1352
1353     /* Member functions.  */
1354     uiserver_release,
1355     uiserver_reset,
1356     uiserver_set_status_cb,
1357     uiserver_set_status_handler,
1358     NULL,               /* set_command_handler */
1359     uiserver_set_colon_line_handler,
1360     uiserver_set_locale,
1361     uiserver_set_protocol,
1362     uiserver_decrypt,
1363     uiserver_decrypt_verify,
1364     NULL,               /* delete */
1365     NULL,               /* edit */
1366     uiserver_encrypt,
1367     NULL,               /* encrypt_sign */
1368     NULL,               /* export */
1369     NULL,               /* export_ext */
1370     NULL,               /* genkey */
1371     NULL,               /* import */
1372     NULL,               /* keylist */
1373     NULL,               /* keylist_ext */
1374     NULL,               /* keysign */
1375     NULL,               /* tofu_policy */
1376     uiserver_sign,
1377     NULL,               /* trustlist */
1378     uiserver_verify,
1379     NULL,               /* getauditlog */
1380     NULL,               /* opassuan_transact */
1381     NULL,               /* conf_load */
1382     NULL,               /* conf_save */
1383     uiserver_set_io_cbs,
1384     uiserver_io_event,
1385     uiserver_cancel,
1386     NULL,               /* cancel_op */
1387     NULL,               /* passwd */
1388     NULL,                /* set_pinentry_mode */
1389     NULL                /* opspawn */
1390   };