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