First take on the low-level assuan interface.
[gpgme.git] / src / engine-assuan.c
1 /* engine-assuan.c - Low-level Assuan protocol engine
2  * Copyright (C) 2009 g10 Code GmbH
3  *
4  * This file is part of GPGME.
5  *
6  * GPGME is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * GPGME is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /*
21    Note: This engine requires a modern Assuan server which uses
22    gpg-error codes.  In particular there is no backward compatible
23    mapping of old Assuan error codes implemented.
24 */
25
26
27 #if HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/types.h>
34 #include <assert.h>
35 #include <unistd.h>
36 #include <locale.h>
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
46 #include "assuan.h"
47 #include "debug.h"
48
49 #include "engine-backend.h"
50
51 \f
52 typedef struct
53 {
54   int fd;       /* FD we talk about.  */
55   int server_fd;/* Server FD for this connection.  */
56   int dir;      /* Inbound/Outbound, maybe given implicit?  */
57   void *data;   /* Handler-specific data.  */
58   void *tag;    /* ID from the user for gpgme_remove_io_callback.  */
59 } iocb_data_t;
60
61 /* Engine instance data.  */
62 struct engine_llass
63 {
64   assuan_context_t assuan_ctx;
65
66   int lc_ctype_set;
67   int lc_messages_set;
68
69   iocb_data_t status_cb;
70
71   struct gpgme_io_cbs io_cbs;
72
73   /* Internal callbacks.  */
74   engine_assuan_result_cb_t result_cb;
75   void *result_cb_value; 
76
77   /* User provided callbacks.  */
78   struct {
79     gpgme_assuan_data_cb_t data_cb;
80     void *data_cb_value;
81
82     gpgme_assuan_inquire_cb_t inq_cb;
83     void *inq_cb_value;
84
85     gpgme_assuan_status_cb_t status_cb;
86     void *status_cb_value;
87   } user;
88
89   /* Option flags.  */
90   struct {
91     int gpg_agent:1;  /* Assume this is a gpg-agent connection.  */
92   } opt;
93
94 };
95 typedef struct engine_llass *engine_llass_t;
96
97 /* Helper to pass data to a callback.  */
98 struct _gpgme_assuan_sendfnc_ctx
99 {
100   assuan_context_t assuan_ctx;
101 };
102
103
104
105 /* Prototypes.  */
106 static void llass_io_event (void *engine,
107                             gpgme_event_io_t type, void *type_data);
108
109
110
111
112 \f
113 /* return the default home directory.  */
114 static const char *
115 llass_get_home_dir (void)
116 {
117   /* For this engine the home directory is not a filename but a string
118      used to convey options.  The exclamation mark is a marker to show
119      that this is not a directory name. Options are strings delimited
120      by a space.  The only option defined for now is GPG_AGENT to
121      enable GPG_AGENT specific commands to send to the server at
122      connection startup.  */
123   return "!GPG_AGENT";
124 }
125
126 static char *
127 llass_get_version (const char *file_name)
128 {
129   return strdup ("1.0");
130 }
131
132
133 static const char *
134 llass_get_req_version (void)
135 {
136   return "1.0";
137 }
138
139 \f
140 static void
141 close_notify_handler (int fd, void *opaque)
142 {
143   engine_llass_t llass = opaque;
144
145   assert (fd != -1);
146   if (llass->status_cb.fd == fd)
147     {
148       if (llass->status_cb.tag)
149         llass->io_cbs.remove (llass->status_cb.tag);
150       llass->status_cb.fd = -1;
151       llass->status_cb.tag = NULL;
152     }
153 }
154
155
156
157 static gpgme_error_t
158 llass_cancel (void *engine)
159 {
160   engine_llass_t llass = engine;
161
162   if (!llass)
163     return gpg_error (GPG_ERR_INV_VALUE);
164
165   if (llass->status_cb.fd != -1)
166     _gpgme_io_close (llass->status_cb.fd);
167
168   if (llass->assuan_ctx)
169     {
170       assuan_disconnect (llass->assuan_ctx);
171       llass->assuan_ctx = NULL;
172     }
173
174   return 0;
175 }
176
177
178 static void
179 llass_release (void *engine)
180 {
181   engine_llass_t llass = engine;
182
183   if (!llass)
184     return;
185
186   llass_cancel (engine);
187
188   free (llass);
189 }
190
191
192 /* Create a new instance. If HOME_DIR is NULL standard options for use
193    with gpg-agent are issued.  */  
194 static gpgme_error_t
195 llass_new (void **engine, const char *file_name, const char *home_dir)
196 {
197   gpgme_error_t err = 0;
198   engine_llass_t llass;
199   char *optstr;
200
201   llass = calloc (1, sizeof *llass);
202   if (!llass)
203     return gpg_error_from_syserror ();
204
205   llass->status_cb.fd = -1;
206   llass->status_cb.dir = 1;
207   llass->status_cb.tag = 0;
208   llass->status_cb.data = llass;
209
210   /* Parse_options.  */
211   if (home_dir && *home_dir == '!')
212     {
213       home_dir++;
214       /* Very simple parser only working for the one option we support.  */
215       if (!strncmp (home_dir, "GPG_AGENT", 9) 
216           && (!home_dir[9] || home_dir[9] == ' '))
217         llass->opt.gpg_agent = 1;
218     }
219
220   err = assuan_socket_connect (&llass->assuan_ctx, file_name, 0);
221   if (err)
222     goto leave;
223
224   if (llass->opt.gpg_agent)
225     {
226       char *dft_display = NULL;
227
228       err = _gpgme_getenv ("DISPLAY", &dft_display);
229       if (err)
230         goto leave;
231       if (dft_display)
232         {
233           if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
234             {
235               err = gpg_error_from_syserror ();
236               free (dft_display);
237               goto leave;
238             }
239           free (dft_display);
240
241           err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL,
242                                  NULL, NULL, NULL);
243           free (optstr);
244           if (err)
245             goto leave;
246         }
247     }
248
249   if (llass->opt.gpg_agent && isatty (1))
250     {
251       int rc;
252       char dft_ttyname[64];
253       char *dft_ttytype = NULL;
254
255       rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
256       if (rc)
257         {
258           err = gpg_error_from_errno (rc);
259           goto leave;
260         }
261       else
262         {
263           if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
264             {
265               err = gpg_error_from_syserror ();
266               goto leave;
267             }
268           err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL,
269                                  NULL, NULL, NULL);
270           free (optstr);
271           if (err)
272             goto leave;
273
274           err = _gpgme_getenv ("TERM", &dft_ttytype);
275           if (err)
276             goto leave;
277           if (dft_ttytype)
278             {
279               if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0)
280                 {
281                   err = gpg_error_from_syserror ();
282                   free (dft_ttytype);
283                   goto leave;
284                 }
285               free (dft_ttytype);
286               
287               err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL,
288                                      NULL, NULL, NULL, NULL);
289               free (optstr);
290               if (err)
291                 goto leave;
292             }
293         }
294     }
295
296
297 #ifdef HAVE_W32_SYSTEM
298   /* Under Windows we need to use AllowSetForegroundWindow.  Tell
299      llass to tell us when it needs it.  */
300   if (!err && llass->opt.gpg_agent)
301     {
302       err = assuan_transact (llass->assuan_ctx, "OPTION allow-pinentry-notify",
303                              NULL, NULL, NULL, NULL, NULL, NULL);
304       if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
305         err = 0; /* This work only with recent gpg-agents.  */
306     }
307 #endif /*HAVE_W32_SYSTEM*/
308
309
310  leave:
311   /* Close the server ends of the pipes (because of this, we must use
312      the stored server_fd_str in the function start).  Our ends are
313      closed in llass_release().  */
314
315   if (err)
316     llass_release (llass);
317   else
318     *engine = llass;
319
320   return err;
321 }
322
323
324 static gpgme_error_t
325 llass_set_locale (void *engine, int category, const char *value)
326 {
327   gpgme_error_t err;
328   engine_llass_t llass = engine;
329   char *optstr;
330   char *catstr;
331
332   if (!llass->opt.gpg_agent)
333     return 0;
334
335   /* FIXME: If value is NULL, we need to reset the option to default.
336      But we can't do this.  So we error out here.  gpg-agent needs
337      support for this.  */
338   if (category == LC_CTYPE)
339     {
340       catstr = "lc-ctype";
341       if (!value && llass->lc_ctype_set)
342         return gpg_error (GPG_ERR_INV_VALUE);
343       if (value)
344         llass->lc_ctype_set = 1;
345     }
346 #ifdef LC_MESSAGES
347   else if (category == LC_MESSAGES)
348     {
349       catstr = "lc-messages";
350       if (!value && llass->lc_messages_set)
351         return gpg_error (GPG_ERR_INV_VALUE);
352       if (value)
353         llass->lc_messages_set = 1;
354     }
355 #endif /* LC_MESSAGES */
356   else
357     return gpg_error (GPG_ERR_INV_VALUE);
358
359   /* FIXME: Reset value to default.  */
360   if (!value)
361     return 0;
362
363   if (asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0)
364     err = gpg_error_from_errno (errno);
365   else
366     {
367       err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL,
368                              NULL, NULL, NULL, NULL);
369       free (optstr);
370     }
371   return err;
372 }
373
374
375
376 static gpgme_error_t
377 inquire_cb_sendfnc (gpgme_assuan_sendfnc_ctx_t ctx,
378                     const void *data, size_t datalen)
379 {
380   if (data && datalen)
381     return assuan_send_data (ctx->assuan_ctx, data, datalen);
382   else
383     return 0;  /* Don't allow an inquire to send a flush.  */
384 }
385
386
387 /* This is the inquiry callback.  It handles stuff which ee need to
388    handle here and passes everything on to the user callback.  */
389 static gpgme_error_t
390 inquire_cb (engine_llass_t llass, const char *keyword, const char *args)
391 {
392   gpg_error_t err;
393
394   if (llass->opt.gpg_agent && !strcmp (keyword, "PINENTRY_LAUNCHED"))
395     {
396       _gpgme_allow_set_foregound_window ((pid_t)strtoul (args, NULL, 10));
397     }
398
399   if (llass->user.inq_cb)
400     {
401       struct _gpgme_assuan_sendfnc_ctx sendfnc_ctx;
402
403       sendfnc_ctx.assuan_ctx = llass->assuan_ctx;
404       err = llass->user.inq_cb (llass->user.inq_cb_value,
405                                 keyword, args,
406                                 inquire_cb_sendfnc, &sendfnc_ctx);
407     }
408   else
409     err = 0;
410
411   return err;
412 }
413
414
415 static gpgme_error_t
416 llass_status_handler (void *opaque, int fd)
417 {
418   gpgme_error_t err = 0;
419   engine_llass_t llass = opaque;
420   char *line;
421   size_t linelen;
422
423   do
424     {
425       err = assuan_read_line (llass->assuan_ctx, &line, &linelen);
426       if (err)
427         {
428           TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
429                   "fd 0x%x: error reading assuan line: %s",
430                   fd, gpg_strerror (err));
431         }
432       else if (linelen >= 2 && line[0] == 'D' && line[1] == ' ')
433         {
434           char *src = line + 2;
435           char *end = line + linelen;
436           char *dst = src;
437
438           linelen = 0;
439           while (src < end)
440             {
441               if (*src == '%' && src + 2 < end)
442                 {
443                   /* Handle escaped characters.  */
444                   ++src;
445                   *dst++ = _gpgme_hextobyte (src);
446                   src += 2;
447                 }
448               else
449                 *dst++ = *src++;
450
451               linelen++;
452             }
453
454           src = line + 2;
455           if (linelen && llass->user.data_cb)
456             err = llass->user.data_cb (llass->user.data_cb_value,
457                                        src, linelen);
458           else
459             err = 0;
460
461           TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
462                   "fd 0x%x: D inlinedata; status from cb: %s",
463                   fd, (llass->user.data_cb ?
464                        (err? gpg_strerror (err):"ok"):"no callback"));
465         }
466       else if (linelen >= 3
467                && line[0] == 'E' && line[1] == 'N' && line[2] == 'D'
468                && (line[3] == '\0' || line[3] == ' '))
469         {
470           /* END received.  Tell the data callback.  */
471           if (llass->user.data_cb)
472             err = llass->user.data_cb (llass->user.data_cb_value, NULL, 0);
473           else
474             err = 0;
475
476           TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
477                   "fd 0x%x: END line; status from cb: %s",
478                   fd, (llass->user.data_cb ?
479                        (err? gpg_strerror (err):"ok"):"no callback"));
480         }
481       else if (linelen > 2 && line[0] == 'S' && line[1] == ' ')
482         {
483           char *args;
484           char *src;
485
486           for (src=line+2; *src == ' '; src++)
487             ;
488
489           args = strchr (src, ' ');
490           if (!args)
491             args = line + linelen; /* Let it point to an empty string.  */
492           else
493             *(args++) = 0;
494
495           while (*args == ' ')
496             args++;
497
498           if (llass->user.status_cb)
499             err = llass->user.status_cb (llass->user.status_cb_value,
500                                          src, args);
501           else
502             err = 0;
503
504           TRACE3 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
505                   "fd 0x%x: S line (%s) - status from cb: %s",
506                   fd, line+2, (llass->user.status_cb ?
507                                (err? gpg_strerror (err):"ok"):"no callback"));
508         }
509       else if (linelen >= 7
510                && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
511                && line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
512                && line[6] == 'E'
513                && (line[7] == '\0' || line[7] == ' '))
514         {
515           char *src;
516           char *args;
517
518           for (src=line+7; *src == ' '; src++)
519             ;
520
521           args = strchr (src, ' ');
522           if (!args)
523             args = line + linelen; /* Let it point to an empty string.  */
524           else
525             *(args++) = 0;
526
527           while (*args == ' ')
528             args++;
529
530           err = inquire_cb (llass, src, args);
531           if (!err) /* Flush and send END.  */
532             err = assuan_send_data (llass->assuan_ctx, NULL, 0);
533         }
534       else if (linelen >= 3
535                && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
536                && (line[3] == '\0' || line[3] == ' '))
537         {
538           if (line[3] == ' ')
539             err = atoi (line+4);
540           else
541             err = gpg_error (GPG_ERR_GENERAL);
542           TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
543                   "fd 0x%x: ERR line: %s",
544                   fd, err ? gpg_strerror (err) : "ok");
545           if (llass->result_cb)
546             err = llass->result_cb (llass->result_cb_value, err);
547           else
548             err = 0;
549           if (!err)
550             {
551               _gpgme_io_close (llass->status_cb.fd);
552               return 0;
553             }
554         }
555       else if (linelen >= 2
556                && line[0] == 'O' && line[1] == 'K'
557                && (line[2] == '\0' || line[2] == ' '))
558         {
559           TRACE1 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
560                   "fd 0x%x: OK line", fd);
561           if (llass->result_cb)
562             err = llass->result_cb (llass->result_cb_value, 0);
563           else
564             err = 0;
565           if (!err)
566             {
567               _gpgme_io_close (llass->status_cb.fd);
568               return 0;
569             }
570         }
571       else
572         {
573           /* Comment line or invalid line.  */
574         }
575
576     }
577   while (!err && assuan_pending_line (llass->assuan_ctx));
578
579   return err;
580 }
581
582
583 static gpgme_error_t
584 add_io_cb (engine_llass_t llass, iocb_data_t *iocbd, gpgme_io_cb_t handler)
585 {
586   gpgme_error_t err;
587
588   TRACE_BEG2 (DEBUG_ENGINE, "engine-assuan:add_io_cb", llass,
589               "fd %d, dir %d", iocbd->fd, iocbd->dir);
590   err = (*llass->io_cbs.add) (llass->io_cbs.add_priv,
591                               iocbd->fd, iocbd->dir,
592                               handler, iocbd->data, &iocbd->tag);
593   if (err)
594     return TRACE_ERR (err);
595   if (!iocbd->dir)
596     /* FIXME Kludge around poll() problem.  */
597     err = _gpgme_io_set_nonblocking (iocbd->fd);
598   return TRACE_ERR (err);
599 }
600
601
602 static gpgme_error_t
603 start (engine_llass_t llass, const char *command)
604 {
605   gpgme_error_t err;
606   int fdlist[5];
607   int nfds;
608
609   /* We need to know the fd used by assuan for reads.  We do this by
610      using the assumption that the first returned fd from
611      assuan_get_active_fds() is always this one.  */
612   nfds = assuan_get_active_fds (llass->assuan_ctx, 0 /* read fds */,
613                                 fdlist, DIM (fdlist));
614   if (nfds < 1)
615     return gpg_error (GPG_ERR_GENERAL); /* FIXME */
616
617   /* We "duplicate" the file descriptor, so we can close it here (we
618      can't close fdlist[0], as that is closed by libassuan, and
619      closing it here might cause libassuan to close some unrelated FD
620      later).  Alternatively, we could special case status_fd and
621      register/unregister it manually as needed, but this increases
622      code duplication and is more complicated as we can not use the
623      close notifications etc.  A third alternative would be to let
624      Assuan know that we closed the FD, but that complicates the
625      Assuan interface.  */
626
627   llass->status_cb.fd = _gpgme_io_dup (fdlist[0]);
628   if (llass->status_cb.fd < 0)
629     return gpg_error_from_syserror ();
630
631   if (_gpgme_io_set_close_notify (llass->status_cb.fd,
632                                   close_notify_handler, llass))
633     {
634       _gpgme_io_close (llass->status_cb.fd);
635       llass->status_cb.fd = -1;
636       return gpg_error (GPG_ERR_GENERAL);
637     }
638
639   err = add_io_cb (llass, &llass->status_cb, llass_status_handler);
640   if (!err)
641     err = assuan_write_line (llass->assuan_ctx, command);
642
643   /* FIXME: If *command == '#' no answer is expected.  */
644
645   if (!err)
646     llass_io_event (llass, GPGME_EVENT_START, NULL);
647
648   return err;
649 }
650
651
652
653 static gpgme_error_t
654 llass_transact (void *engine,
655                 const char *command,
656                 engine_assuan_result_cb_t result_cb,
657                 void *result_cb_value,
658                 gpgme_assuan_data_cb_t data_cb,
659                 void *data_cb_value,
660                 gpgme_assuan_inquire_cb_t inq_cb,
661                 void *inq_cb_value,
662                 gpgme_assuan_status_cb_t status_cb,
663                 void *status_cb_value)
664 {
665   engine_llass_t llass = engine;
666   gpgme_error_t err;
667
668   if (!llass || !command || !*command)
669     return gpg_error (GPG_ERR_INV_VALUE);
670
671   llass->result_cb = result_cb;
672   llass->result_cb_value = result_cb_value;
673   llass->user.data_cb = data_cb;
674   llass->user.data_cb_value = data_cb_value;
675   llass->user.inq_cb = inq_cb;
676   llass->user.inq_cb_value = inq_cb_value;
677   llass->user.status_cb = status_cb;
678   llass->user.status_cb_value = status_cb_value;
679
680   err = start (llass, command);
681   return err;
682 }
683
684
685
686 static void
687 llass_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
688 {
689   engine_llass_t llass = engine;
690   llass->io_cbs = *io_cbs;
691 }
692
693
694 static void
695 llass_io_event (void *engine, gpgme_event_io_t type, void *type_data)
696 {
697   engine_llass_t llass = engine;
698
699   TRACE3 (DEBUG_ENGINE, "gpgme:llass_io_event", llass,
700           "event %p, type %d, type_data %p",
701           llass->io_cbs.event, type, type_data);
702   if (llass->io_cbs.event)
703     (*llass->io_cbs.event) (llass->io_cbs.event_priv, type, type_data);
704 }
705
706
707 struct engine_ops _gpgme_engine_ops_assuan =
708   {
709     /* Static functions.  */
710     _gpgme_get_default_agent_socket,
711     llass_get_home_dir,
712     llass_get_version,
713     llass_get_req_version,
714     llass_new,
715
716     /* Member functions.  */
717     llass_release,
718     NULL,               /* reset */
719     NULL,               /* set_status_handler */
720     NULL,               /* set_command_handler */
721     NULL,               /* set_colon_line_handler */
722     llass_set_locale,
723     NULL,               /* decrypt */
724     NULL,               /* delete */
725     NULL,               /* edit */
726     NULL,               /* encrypt */
727     NULL,               /* encrypt_sign */
728     NULL,               /* export */
729     NULL,               /* export_ext */
730     NULL,               /* genkey */
731     NULL,               /* import */
732     NULL,               /* keylist */
733     NULL,               /* keylist_ext */
734     NULL,               /* sign */
735     NULL,               /* trustlist */
736     NULL,               /* verify */
737     NULL,               /* getauditlog */
738     llass_transact,     /* opassuan_transact */
739     NULL,               /* conf_load */
740     NULL,               /* conf_save */
741     llass_set_io_cbs,
742     llass_io_event,
743     llass_cancel
744   };