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