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