core: Minor change of the gpgme_op_edit semantics.
[gpgme.git] / src / engine-gpgsm.c
1 /* engine-gpgsm.c - GpgSM engine.
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009,
4                  2010 g10 Code GmbH
5
6    This file is part of GPGME.
7
8    GPGME is free software; you can redistribute it and/or modify it
9    under the terms of the GNU Lesser General Public License as
10    published by the Free Software Foundation; either version 2.1 of
11    the License, or (at your option) any later version.
12
13    GPGME is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Lesser General Public License for more details.
17
18    You should have received a copy of the GNU Lesser General Public
19    License along with this program; if not, write to the Free Software
20    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
21    02111-1307, USA.  */
22
23 #if HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <stdlib.h>
28 #include <string.h>
29 #ifdef HAVE_SYS_TYPES_H
30 # include <sys/types.h>
31 #endif
32 #include <assert.h>
33 #ifdef HAVE_UNISTD_H
34 # include <unistd.h>
35 #endif
36 #ifdef HAVE_LOCALE_H
37 #include <locale.h>
38 #endif
39 #include <fcntl.h> /* FIXME */
40 #include <errno.h>
41
42 #include "gpgme.h"
43 #include "util.h"
44 #include "ops.h"
45 #include "wait.h"
46 #include "priv-io.h"
47 #include "sema.h"
48 #include "data.h"
49
50 #include "assuan.h"
51 #include "debug.h"
52
53 #include "engine-backend.h"
54
55 \f
56 typedef struct
57 {
58   int fd;       /* FD we talk about.  */
59   int server_fd;/* Server FD for this connection.  */
60   int dir;      /* Inbound/Outbound, maybe given implicit?  */
61   void *data;   /* Handler-specific data.  */
62   void *tag;    /* ID from the user for gpgme_remove_io_callback.  */
63   char server_fd_str[15]; /* Same as SERVER_FD but as a string.  We
64                              need this because _gpgme_io_fd2str can't
65                              be used on a closed descriptor.  */
66 } iocb_data_t;
67
68
69 struct engine_gpgsm
70 {
71   assuan_context_t assuan_ctx;
72
73   int lc_ctype_set;
74   int lc_messages_set;
75
76   iocb_data_t status_cb;
77
78   /* Input, output etc are from the servers perspective.  */
79   iocb_data_t input_cb;
80   gpgme_data_t input_helper_data;  /* Input helper data object.  */
81   void *input_helper_memory;       /* Input helper memory block.  */
82
83   iocb_data_t output_cb;
84
85   iocb_data_t message_cb;
86
87   struct
88   {
89     engine_status_handler_t fnc;
90     void *fnc_value;
91     gpgme_status_cb_t mon_cb;
92     void *mon_cb_value;
93   } status;
94
95   struct
96   {
97     engine_colon_line_handler_t fnc;
98     void *fnc_value;
99     struct
100     {
101       char *line;
102       int linesize;
103       int linelen;
104     } attic;
105     int any; /* any data line seen */
106   } colon;
107
108   gpgme_data_t inline_data;  /* Used to collect D lines.  */
109
110   struct gpgme_io_cbs io_cbs;
111 };
112
113 typedef struct engine_gpgsm *engine_gpgsm_t;
114
115
116 static void gpgsm_io_event (void *engine,
117                             gpgme_event_io_t type, void *type_data);
118
119
120 \f
121 static char *
122 gpgsm_get_version (const char *file_name)
123 {
124   return _gpgme_get_program_version (file_name ? file_name
125                                      : _gpgme_get_default_gpgsm_name ());
126 }
127
128
129 static const char *
130 gpgsm_get_req_version (void)
131 {
132   return "2.0.4";
133 }
134
135 \f
136 static void
137 close_notify_handler (int fd, void *opaque)
138 {
139   engine_gpgsm_t gpgsm = opaque;
140
141   assert (fd != -1);
142   if (gpgsm->status_cb.fd == fd)
143     {
144       if (gpgsm->status_cb.tag)
145         (*gpgsm->io_cbs.remove) (gpgsm->status_cb.tag);
146       gpgsm->status_cb.fd = -1;
147       gpgsm->status_cb.tag = NULL;
148     }
149   else if (gpgsm->input_cb.fd == fd)
150     {
151       if (gpgsm->input_cb.tag)
152         (*gpgsm->io_cbs.remove) (gpgsm->input_cb.tag);
153       gpgsm->input_cb.fd = -1;
154       gpgsm->input_cb.tag = NULL;
155       if (gpgsm->input_helper_data)
156         {
157           gpgme_data_release (gpgsm->input_helper_data);
158           gpgsm->input_helper_data = NULL;
159         }
160       if (gpgsm->input_helper_memory)
161         {
162           free (gpgsm->input_helper_memory);
163           gpgsm->input_helper_memory = NULL;
164         }
165     }
166   else if (gpgsm->output_cb.fd == fd)
167     {
168       if (gpgsm->output_cb.tag)
169         (*gpgsm->io_cbs.remove) (gpgsm->output_cb.tag);
170       gpgsm->output_cb.fd = -1;
171       gpgsm->output_cb.tag = NULL;
172     }
173   else if (gpgsm->message_cb.fd == fd)
174     {
175       if (gpgsm->message_cb.tag)
176         (*gpgsm->io_cbs.remove) (gpgsm->message_cb.tag);
177       gpgsm->message_cb.fd = -1;
178       gpgsm->message_cb.tag = NULL;
179     }
180 }
181
182
183 /* This is the default inquiry callback.  We use it to handle the
184    Pinentry notifications.  */
185 static gpgme_error_t
186 default_inq_cb (engine_gpgsm_t gpgsm, const char *line)
187 {
188   (void)gpgsm;
189
190   if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17]))
191     {
192       _gpgme_allow_set_foreground_window ((pid_t)strtoul (line+17, NULL, 10));
193     }
194
195   return 0;
196 }
197
198
199 static gpgme_error_t
200 gpgsm_cancel (void *engine)
201 {
202   engine_gpgsm_t gpgsm = engine;
203
204   if (!gpgsm)
205     return gpg_error (GPG_ERR_INV_VALUE);
206
207   if (gpgsm->status_cb.fd != -1)
208     _gpgme_io_close (gpgsm->status_cb.fd);
209   if (gpgsm->input_cb.fd != -1)
210     _gpgme_io_close (gpgsm->input_cb.fd);
211   if (gpgsm->output_cb.fd != -1)
212     _gpgme_io_close (gpgsm->output_cb.fd);
213   if (gpgsm->message_cb.fd != -1)
214     _gpgme_io_close (gpgsm->message_cb.fd);
215
216   if (gpgsm->assuan_ctx)
217     {
218       assuan_release (gpgsm->assuan_ctx);
219       gpgsm->assuan_ctx = NULL;
220     }
221
222   return 0;
223 }
224
225
226 static void
227 gpgsm_release (void *engine)
228 {
229   engine_gpgsm_t gpgsm = engine;
230
231   if (!gpgsm)
232     return;
233
234   gpgsm_cancel (engine);
235
236   free (gpgsm->colon.attic.line);
237   free (gpgsm);
238 }
239
240
241 static gpgme_error_t
242 gpgsm_new (void **engine, const char *file_name, const char *home_dir,
243            const char *version)
244 {
245   gpgme_error_t err = 0;
246   engine_gpgsm_t gpgsm;
247   const char *pgmname;
248   const char *argv[5];
249   int argc;
250 #if !USE_DESCRIPTOR_PASSING
251   int fds[2];
252   int child_fds[4];
253 #endif
254   char *dft_display = NULL;
255   char dft_ttyname[64];
256   char *dft_ttytype = NULL;
257   char *optstr;
258
259   (void)version; /* Not yet used.  */
260
261   gpgsm = calloc (1, sizeof *gpgsm);
262   if (!gpgsm)
263     return gpg_error_from_syserror ();
264
265   gpgsm->status_cb.fd = -1;
266   gpgsm->status_cb.dir = 1;
267   gpgsm->status_cb.tag = 0;
268   gpgsm->status_cb.data = gpgsm;
269
270   gpgsm->input_cb.fd = -1;
271   gpgsm->input_cb.dir = 0;
272   gpgsm->input_cb.tag = 0;
273   gpgsm->input_cb.server_fd = -1;
274   *gpgsm->input_cb.server_fd_str = 0;
275   gpgsm->output_cb.fd = -1;
276   gpgsm->output_cb.dir = 1;
277   gpgsm->output_cb.tag = 0;
278   gpgsm->output_cb.server_fd = -1;
279   *gpgsm->output_cb.server_fd_str = 0;
280   gpgsm->message_cb.fd = -1;
281   gpgsm->message_cb.dir = 0;
282   gpgsm->message_cb.tag = 0;
283   gpgsm->message_cb.server_fd = -1;
284   *gpgsm->message_cb.server_fd_str = 0;
285
286   gpgsm->status.fnc = 0;
287   gpgsm->colon.fnc = 0;
288   gpgsm->colon.attic.line = 0;
289   gpgsm->colon.attic.linesize = 0;
290   gpgsm->colon.attic.linelen = 0;
291   gpgsm->colon.any = 0;
292
293   gpgsm->inline_data = NULL;
294
295   gpgsm->io_cbs.add = NULL;
296   gpgsm->io_cbs.add_priv = NULL;
297   gpgsm->io_cbs.remove = NULL;
298   gpgsm->io_cbs.event = NULL;
299   gpgsm->io_cbs.event_priv = NULL;
300
301 #if !USE_DESCRIPTOR_PASSING
302   if (_gpgme_io_pipe (fds, 0) < 0)
303     {
304       err = gpg_error_from_syserror ();
305       goto leave;
306     }
307   gpgsm->input_cb.fd = fds[1];
308   gpgsm->input_cb.server_fd = fds[0];
309
310   if (_gpgme_io_pipe (fds, 1) < 0)
311     {
312       err = gpg_error_from_syserror ();
313       goto leave;
314     }
315   gpgsm->output_cb.fd = fds[0];
316   gpgsm->output_cb.server_fd = fds[1];
317
318   if (_gpgme_io_pipe (fds, 0) < 0)
319     {
320       err = gpg_error_from_syserror ();
321       goto leave;
322     }
323   gpgsm->message_cb.fd = fds[1];
324   gpgsm->message_cb.server_fd = fds[0];
325
326   child_fds[0] = gpgsm->input_cb.server_fd;
327   child_fds[1] = gpgsm->output_cb.server_fd;
328   child_fds[2] = gpgsm->message_cb.server_fd;
329   child_fds[3] = -1;
330 #endif
331
332   pgmname = file_name ? file_name : _gpgme_get_default_gpgsm_name ();
333
334   argc = 0;
335   argv[argc++] = _gpgme_get_basename (pgmname);
336   if (home_dir)
337     {
338       argv[argc++] = "--homedir";
339       argv[argc++] = home_dir;
340     }
341   argv[argc++] = "--server";
342   argv[argc++] = NULL;
343
344   err = assuan_new_ext (&gpgsm->assuan_ctx, GPG_ERR_SOURCE_GPGME,
345                         &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb,
346                         NULL);
347   if (err)
348     goto leave;
349   assuan_ctx_set_system_hooks (gpgsm->assuan_ctx, &_gpgme_assuan_system_hooks);
350
351 #if USE_DESCRIPTOR_PASSING
352   err = assuan_pipe_connect (gpgsm->assuan_ctx, pgmname, argv,
353                              NULL, NULL, NULL, ASSUAN_PIPE_CONNECT_FDPASSING);
354 #else
355   {
356     assuan_fd_t achild_fds[4];
357     int i;
358
359     /* For now... */
360     for (i = 0; i < 4; i++)
361       achild_fds[i] = (assuan_fd_t) child_fds[i];
362
363     err = assuan_pipe_connect (gpgsm->assuan_ctx, pgmname, argv,
364                                achild_fds, NULL, NULL, 0);
365
366     /* For now... */
367     for (i = 0; i < 4; i++)
368       child_fds[i] = (int) achild_fds[i];
369   }
370
371   /* On Windows, handles are inserted in the spawned process with
372      DuplicateHandle, and child_fds contains the server-local names
373      for the inserted handles when assuan_pipe_connect returns.  */
374   if (!err)
375     {
376       /* Note: We don't use _gpgme_io_fd2str here.  On W32 the
377          returned handles are real W32 system handles, not whatever
378          GPGME uses internally (which may be a system handle, a C
379          library handle or a GLib/Qt channel.  Confusing, yes, but
380          remember these are server-local names, so they are not part
381          of GPGME at all.  */
382       snprintf (gpgsm->input_cb.server_fd_str,
383                 sizeof gpgsm->input_cb.server_fd_str, "%d", child_fds[0]);
384       snprintf (gpgsm->output_cb.server_fd_str,
385                 sizeof gpgsm->output_cb.server_fd_str, "%d", child_fds[1]);
386       snprintf (gpgsm->message_cb.server_fd_str,
387                 sizeof gpgsm->message_cb.server_fd_str, "%d", child_fds[2]);
388     }
389 #endif
390   if (err)
391     goto leave;
392
393   err = _gpgme_getenv ("DISPLAY", &dft_display);
394   if (err)
395     goto leave;
396   if (dft_display)
397     {
398       if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
399         {
400           free (dft_display);
401           err = gpg_error_from_syserror ();
402           goto leave;
403         }
404       free (dft_display);
405
406       err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL,
407                              NULL, NULL, NULL);
408       free (optstr);
409       if (err)
410         goto leave;
411     }
412
413   if (isatty (1))
414     {
415       int rc;
416
417       rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
418
419       /* Even though isatty() returns 1, ttyname_r() may fail in many
420          ways, e.g., when /dev/pts is not accessible under chroot.  */
421       if (!rc)
422         {
423           if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
424             {
425               err = gpg_error_from_syserror ();
426               goto leave;
427             }
428           err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL,
429                                  NULL, NULL, NULL);
430           free (optstr);
431           if (err)
432             goto leave;
433
434           err = _gpgme_getenv ("TERM", &dft_ttytype);
435           if (err)
436             goto leave;
437           if (dft_ttytype)
438             {
439               if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0)
440                 {
441                   free (dft_ttytype);
442                   err = gpg_error_from_syserror ();
443                   goto leave;
444                 }
445               free (dft_ttytype);
446
447               err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL,
448                                      NULL, NULL, NULL, NULL);
449               free (optstr);
450               if (err)
451                 goto leave;
452             }
453         }
454     }
455
456   /* Ask gpgsm to enable the audit log support.  */
457   if (!err)
458     {
459       err = assuan_transact (gpgsm->assuan_ctx, "OPTION enable-audit-log=1",
460                              NULL, NULL, NULL, NULL, NULL, NULL);
461       if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
462         err = 0; /* This is an optional feature of gpgsm.  */
463     }
464
465
466 #ifdef HAVE_W32_SYSTEM
467   /* Under Windows we need to use AllowSetForegroundWindow.  Tell
468      gpgsm to tell us when it needs it.  */
469   if (!err)
470     {
471       err = assuan_transact (gpgsm->assuan_ctx, "OPTION allow-pinentry-notify",
472                              NULL, NULL, NULL, NULL, NULL, NULL);
473       if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
474         err = 0; /* This is a new feature of gpgsm.  */
475     }
476 #endif /*HAVE_W32_SYSTEM*/
477
478 #if !USE_DESCRIPTOR_PASSING
479   if (!err
480       && (_gpgme_io_set_close_notify (gpgsm->input_cb.fd,
481                                       close_notify_handler, gpgsm)
482           || _gpgme_io_set_close_notify (gpgsm->output_cb.fd,
483                                          close_notify_handler, gpgsm)
484           || _gpgme_io_set_close_notify (gpgsm->message_cb.fd,
485                                          close_notify_handler, gpgsm)))
486     {
487       err = gpg_error (GPG_ERR_GENERAL);
488       goto leave;
489     }
490 #endif
491
492  leave:
493   /* Close the server ends of the pipes (because of this, we must use
494      the stored server_fd_str in the function start).  Our ends are
495      closed in gpgsm_release().  */
496 #if !USE_DESCRIPTOR_PASSING
497   if (gpgsm->input_cb.server_fd != -1)
498     _gpgme_io_close (gpgsm->input_cb.server_fd);
499   if (gpgsm->output_cb.server_fd != -1)
500     _gpgme_io_close (gpgsm->output_cb.server_fd);
501   if (gpgsm->message_cb.server_fd != -1)
502     _gpgme_io_close (gpgsm->message_cb.server_fd);
503 #endif
504
505   if (err)
506     gpgsm_release (gpgsm);
507   else
508     *engine = gpgsm;
509
510   return err;
511 }
512
513
514 static gpgme_error_t
515 gpgsm_set_locale (void *engine, int category, const char *value)
516 {
517   engine_gpgsm_t gpgsm = engine;
518   gpgme_error_t err;
519   char *optstr;
520   const char *catstr;
521
522   /* FIXME: If value is NULL, we need to reset the option to default.
523      But we can't do this.  So we error out here.  GPGSM needs support
524      for this.  */
525   if (0)
526     ;
527 #ifdef LC_CTYPE
528   else if (category == LC_CTYPE)
529     {
530       catstr = "lc-ctype";
531       if (!value && gpgsm->lc_ctype_set)
532         return gpg_error (GPG_ERR_INV_VALUE);
533       if (value)
534         gpgsm->lc_ctype_set = 1;
535     }
536 #endif
537 #ifdef LC_MESSAGES
538   else if (category == LC_MESSAGES)
539     {
540       catstr = "lc-messages";
541       if (!value && gpgsm->lc_messages_set)
542         return gpg_error (GPG_ERR_INV_VALUE);
543       if (value)
544         gpgsm->lc_messages_set = 1;
545     }
546 #endif /* LC_MESSAGES */
547   else
548     return gpg_error (GPG_ERR_INV_VALUE);
549
550   /* FIXME: Reset value to default.  */
551   if (!value)
552     return 0;
553
554   if (asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0)
555     err = gpg_error_from_syserror ();
556   else
557     {
558       err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL,
559                              NULL, NULL, NULL, NULL);
560       free (optstr);
561     }
562
563   return err;
564 }
565
566
567 static gpgme_error_t
568 gpgsm_assuan_simple_command (engine_gpgsm_t gpgsm, const char *cmd,
569                              engine_status_handler_t status_fnc,
570                              void *status_fnc_value)
571 {
572   assuan_context_t ctx = gpgsm->assuan_ctx;
573   gpg_error_t err, cb_err;
574   char *line;
575   size_t linelen;
576
577   err = assuan_write_line (ctx, cmd);
578   if (err)
579     return err;
580
581   cb_err = 0;
582   do
583     {
584       err = assuan_read_line (ctx, &line, &linelen);
585       if (err)
586         return err;
587
588       if (*line == '#' || !linelen)
589         continue;
590
591       if (linelen >= 2
592           && line[0] == 'O' && line[1] == 'K'
593           && (line[2] == '\0' || line[2] == ' '))
594         return cb_err;
595       else if (linelen >= 4
596           && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
597           && line[3] == ' ')
598         {
599           /* We prefer a callback generated error because that one is
600              more related to gpgme and thus probably more important
601              than the error returned by the engine.  */
602           err = cb_err? cb_err : atoi (&line[4]);
603         }
604       else if (linelen >= 2
605                && line[0] == 'S' && line[1] == ' ')
606         {
607           /* After an error from a status callback we skip all further
608              status lines.  */
609           if (!cb_err)
610             {
611               char *rest;
612               gpgme_status_code_t r;
613
614               rest = strchr (line + 2, ' ');
615               if (!rest)
616                 rest = line + linelen; /* set to an empty string */
617               else
618                 *(rest++) = 0;
619
620               r = _gpgme_parse_status (line + 2);
621               if (gpgsm->status.mon_cb && r != GPGME_STATUS_PROGRESS)
622                 {
623                   /* Note that we call the monitor even if we do
624                    * not know the status code (r < 0).  */
625                   cb_err = gpgsm->status.mon_cb (gpgsm->status.mon_cb_value,
626                                                  line + 2, rest);
627                 }
628
629               if (r >= 0 && status_fnc && !cb_err)
630                 cb_err = status_fnc (status_fnc_value, r, rest);
631             }
632         }
633       else
634         {
635           /* Invalid line or INQUIRY.  We can't do anything else than
636              to stop.  As with ERR we prefer a status callback
637              generated error code, though.  */
638           err = cb_err ? cb_err : gpg_error (GPG_ERR_GENERAL);
639         }
640     }
641   while (!err);
642
643   return err;
644 }
645
646
647 typedef enum { INPUT_FD, OUTPUT_FD, MESSAGE_FD } fd_type_t;
648
649 static void
650 gpgsm_clear_fd (engine_gpgsm_t gpgsm, fd_type_t fd_type)
651 {
652 #if !USE_DESCRIPTOR_PASSING
653   switch (fd_type)
654     {
655     case INPUT_FD:
656       _gpgme_io_close (gpgsm->input_cb.fd);
657       break;
658     case OUTPUT_FD:
659       _gpgme_io_close (gpgsm->output_cb.fd);
660       break;
661     case MESSAGE_FD:
662       _gpgme_io_close (gpgsm->message_cb.fd);
663       break;
664     }
665 #else
666   (void)gpgsm;
667   (void)fd_type;
668 #endif
669 }
670
671 #define COMMANDLINELEN 40
672 static gpgme_error_t
673 gpgsm_set_fd (engine_gpgsm_t gpgsm, fd_type_t fd_type, const char *opt)
674 {
675   gpg_error_t err = 0;
676   char line[COMMANDLINELEN];
677   const char *which;
678   iocb_data_t *iocb_data;
679 #if USE_DESCRIPTOR_PASSING
680   int dir;
681 #endif
682
683   switch (fd_type)
684     {
685     case INPUT_FD:
686       which = "INPUT";
687       iocb_data = &gpgsm->input_cb;
688       break;
689
690     case OUTPUT_FD:
691       which = "OUTPUT";
692       iocb_data = &gpgsm->output_cb;
693       break;
694
695     case MESSAGE_FD:
696       which = "MESSAGE";
697       iocb_data = &gpgsm->message_cb;
698       break;
699
700     default:
701       return gpg_error (GPG_ERR_INV_VALUE);
702     }
703
704 #if USE_DESCRIPTOR_PASSING
705   dir = iocb_data->dir;
706   /* We try to short-cut the communication by giving GPGSM direct
707      access to the file descriptor, rather than using a pipe.  */
708   iocb_data->server_fd = _gpgme_data_get_fd (iocb_data->data);
709   if (iocb_data->server_fd < 0)
710     {
711       int fds[2];
712
713       if (_gpgme_io_pipe (fds, dir) < 0)
714         return gpg_error_from_syserror ();
715
716       iocb_data->fd = dir ? fds[0] : fds[1];
717       iocb_data->server_fd = dir ? fds[1] : fds[0];
718
719       if (_gpgme_io_set_close_notify (iocb_data->fd,
720                                       close_notify_handler, gpgsm))
721         {
722           err = gpg_error (GPG_ERR_GENERAL);
723           goto leave_set_fd;
724         }
725     }
726
727   err = assuan_sendfd (gpgsm->assuan_ctx, iocb_data->server_fd);
728   if (err)
729     goto leave_set_fd;
730
731   _gpgme_io_close (iocb_data->server_fd);
732   iocb_data->server_fd = -1;
733
734   if (opt)
735     snprintf (line, COMMANDLINELEN, "%s FD %s", which, opt);
736   else
737     snprintf (line, COMMANDLINELEN, "%s FD", which);
738 #else
739   if (opt)
740     snprintf (line, COMMANDLINELEN, "%s FD=%s %s",
741               which, iocb_data->server_fd_str, opt);
742   else
743     snprintf (line, COMMANDLINELEN, "%s FD=%s",
744               which, iocb_data->server_fd_str);
745 #endif
746
747   err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL);
748
749 #if USE_DESCRIPTOR_PASSING
750  leave_set_fd:
751   if (err)
752     {
753       _gpgme_io_close (iocb_data->fd);
754       iocb_data->fd = -1;
755       if (iocb_data->server_fd != -1)
756         {
757           _gpgme_io_close (iocb_data->server_fd);
758           iocb_data->server_fd = -1;
759         }
760     }
761 #endif
762
763   return err;
764 }
765
766
767 static const char *
768 map_data_enc (gpgme_data_t d)
769 {
770   switch (gpgme_data_get_encoding (d))
771     {
772     case GPGME_DATA_ENCODING_NONE:
773       break;
774     case GPGME_DATA_ENCODING_BINARY:
775       return "--binary";
776     case GPGME_DATA_ENCODING_BASE64:
777       return "--base64";
778     case GPGME_DATA_ENCODING_ARMOR:
779       return "--armor";
780     default:
781       break;
782     }
783   return NULL;
784 }
785
786
787 static gpgme_error_t
788 status_handler (void *opaque, int fd)
789 {
790   struct io_cb_data *data = (struct io_cb_data *) opaque;
791   engine_gpgsm_t gpgsm = (engine_gpgsm_t) data->handler_value;
792   gpgme_error_t err = 0;
793   char *line;
794   size_t linelen;
795
796   do
797     {
798       err = assuan_read_line (gpgsm->assuan_ctx, &line, &linelen);
799       if (err)
800         {
801           /* Try our best to terminate the connection friendly.  */
802           /*      assuan_write_line (gpgsm->assuan_ctx, "BYE"); */
803           TRACE3 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
804                   "fd 0x%x: error from assuan (%d) getting status line : %s",
805                   fd, err, gpg_strerror (err));
806         }
807       else if (linelen >= 3
808                && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
809                && (line[3] == '\0' || line[3] == ' '))
810         {
811           if (line[3] == ' ')
812             err = atoi (&line[4]);
813           if (! err)
814             err = gpg_error (GPG_ERR_GENERAL);
815           TRACE2 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
816                   "fd 0x%x: ERR line - mapped to: %s",
817                   fd, err ? gpg_strerror (err) : "ok");
818           /* Try our best to terminate the connection friendly.  */
819           /*      assuan_write_line (gpgsm->assuan_ctx, "BYE"); */
820         }
821       else if (linelen >= 2
822                && line[0] == 'O' && line[1] == 'K'
823                && (line[2] == '\0' || line[2] == ' '))
824         {
825           if (gpgsm->status.fnc)
826             {
827               char emptystring[1] = {0};
828               err = gpgsm->status.fnc (gpgsm->status.fnc_value,
829                                        GPGME_STATUS_EOF, emptystring);
830               if (gpg_err_code (err) == GPG_ERR_FALSE)
831                 err = 0; /* Drop special error code.  */
832             }
833
834           if (!err && gpgsm->colon.fnc && gpgsm->colon.any)
835             {
836               /* We must tell a colon function about the EOF. We do
837                  this only when we have seen any data lines.  Note
838                  that this inlined use of colon data lines will
839                  eventually be changed into using a regular data
840                  channel. */
841               gpgsm->colon.any = 0;
842               err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, NULL);
843             }
844           TRACE2 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
845                   "fd 0x%x: OK line - final status: %s",
846                   fd, err ? gpg_strerror (err) : "ok");
847           _gpgme_io_close (gpgsm->status_cb.fd);
848           return err;
849         }
850       else if (linelen > 2
851                && line[0] == 'D' && line[1] == ' '
852                && gpgsm->colon.fnc)
853         {
854           /* We are using the colon handler even for plain inline data
855              - strange name for that function but for historic reasons
856              we keep it.  */
857           /* FIXME We can't use this for binary data because we
858              assume this is a string.  For the current usage of colon
859              output it is correct.  */
860           char *src = line + 2;
861           char *end = line + linelen;
862           char *dst;
863           char **aline = &gpgsm->colon.attic.line;
864           int *alinelen = &gpgsm->colon.attic.linelen;
865
866           if (gpgsm->colon.attic.linesize < *alinelen + linelen + 1)
867             {
868               char *newline = realloc (*aline, *alinelen + linelen + 1);
869               if (!newline)
870                 err = gpg_error_from_syserror ();
871               else
872                 {
873                   *aline = newline;
874                   gpgsm->colon.attic.linesize = *alinelen + linelen + 1;
875                 }
876             }
877           if (!err)
878             {
879               dst = *aline + *alinelen;
880
881               while (!err && src < end)
882                 {
883                   if (*src == '%' && src + 2 < end)
884                     {
885                       /* Handle escaped characters.  */
886                       ++src;
887                       *dst = _gpgme_hextobyte (src);
888                       (*alinelen)++;
889                       src += 2;
890                     }
891                   else
892                     {
893                       *dst = *src++;
894                       (*alinelen)++;
895                     }
896
897                   if (*dst == '\n')
898                     {
899                       /* Terminate the pending line, pass it to the colon
900                          handler and reset it.  */
901
902                       gpgsm->colon.any = 1;
903                       if (*alinelen > 1 && *(dst - 1) == '\r')
904                         dst--;
905                       *dst = '\0';
906
907                       /* FIXME How should we handle the return code?  */
908                       err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, *aline);
909                       if (!err)
910                         {
911                           dst = *aline;
912                           *alinelen = 0;
913                         }
914                     }
915                   else
916                     dst++;
917                 }
918             }
919           TRACE2 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
920                   "fd 0x%x: D line; final status: %s",
921                   fd, err? gpg_strerror (err):"ok");
922         }
923       else if (linelen > 2
924                && line[0] == 'D' && line[1] == ' '
925                && gpgsm->inline_data)
926         {
927           char *src = line + 2;
928           char *end = line + linelen;
929           char *dst = src;
930           gpgme_ssize_t nwritten;
931
932           linelen = 0;
933           while (src < end)
934             {
935               if (*src == '%' && src + 2 < end)
936                 {
937                   /* Handle escaped characters.  */
938                   ++src;
939                   *dst++ = _gpgme_hextobyte (src);
940                   src += 2;
941                 }
942               else
943                 *dst++ = *src++;
944
945               linelen++;
946             }
947
948           src = line + 2;
949           while (linelen > 0)
950             {
951               nwritten = gpgme_data_write (gpgsm->inline_data, src, linelen);
952               if (!nwritten || (nwritten < 0 && errno != EINTR)
953                   || nwritten > linelen)
954                 {
955                   err = gpg_error_from_syserror ();
956                   break;
957                 }
958               src += nwritten;
959               linelen -= nwritten;
960             }
961
962           TRACE2 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
963                   "fd 0x%x: D inlinedata; final status: %s",
964                   fd, err? gpg_strerror (err):"ok");
965         }
966       else if (linelen > 2
967                && line[0] == 'S' && line[1] == ' ')
968         {
969           char *rest;
970           gpgme_status_code_t r;
971
972           rest = strchr (line + 2, ' ');
973           if (!rest)
974             rest = line + linelen; /* set to an empty string */
975           else
976             *(rest++) = 0;
977
978           r = _gpgme_parse_status (line + 2);
979
980           if (r >= 0)
981             {
982               if (gpgsm->status.fnc)
983                 {
984                   err = gpgsm->status.fnc (gpgsm->status.fnc_value, r, rest);
985                   if (gpg_err_code (err) == GPG_ERR_FALSE)
986                     err = 0; /* Drop special error code.  */
987                 }
988             }
989           else
990             fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest);
991           TRACE3 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
992                   "fd 0x%x: S line (%s) - final status: %s",
993                   fd, line+2, err? gpg_strerror (err):"ok");
994         }
995       else if (linelen >= 7
996                && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
997                && line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
998                && line[6] == 'E'
999                && (line[7] == '\0' || line[7] == ' '))
1000         {
1001           char *keyword = line+7;
1002
1003           while (*keyword == ' ')
1004             keyword++;;
1005           default_inq_cb (gpgsm, keyword);
1006           assuan_write_line (gpgsm->assuan_ctx, "END");
1007         }
1008
1009     }
1010   while (!err && assuan_pending_line (gpgsm->assuan_ctx));
1011
1012   return err;
1013 }
1014
1015
1016 static gpgme_error_t
1017 add_io_cb (engine_gpgsm_t gpgsm, iocb_data_t *iocbd, gpgme_io_cb_t handler)
1018 {
1019   gpgme_error_t err;
1020
1021   TRACE_BEG2 (DEBUG_ENGINE, "engine-gpgsm:add_io_cb", gpgsm,
1022               "fd %d, dir %d", iocbd->fd, iocbd->dir);
1023   err = (*gpgsm->io_cbs.add) (gpgsm->io_cbs.add_priv,
1024                               iocbd->fd, iocbd->dir,
1025                               handler, iocbd->data, &iocbd->tag);
1026   if (err)
1027     return TRACE_ERR (err);
1028   if (!iocbd->dir)
1029     /* FIXME Kludge around poll() problem.  */
1030     err = _gpgme_io_set_nonblocking (iocbd->fd);
1031   return TRACE_ERR (err);
1032 }
1033
1034
1035 static gpgme_error_t
1036 start (engine_gpgsm_t gpgsm, const char *command)
1037 {
1038   gpgme_error_t err;
1039   assuan_fd_t afdlist[5];
1040   int fdlist[5];
1041   int nfds;
1042   int i;
1043
1044   /* We need to know the fd used by assuan for reads.  We do this by
1045      using the assumption that the first returned fd from
1046      assuan_get_active_fds() is always this one.  */
1047   nfds = assuan_get_active_fds (gpgsm->assuan_ctx, 0 /* read fds */,
1048                                 afdlist, DIM (afdlist));
1049   if (nfds < 1)
1050     return gpg_error (GPG_ERR_GENERAL); /* FIXME */
1051   /* For now... */
1052   for (i = 0; i < nfds; i++)
1053     fdlist[i] = (int) afdlist[i];
1054
1055   /* We "duplicate" the file descriptor, so we can close it here (we
1056      can't close fdlist[0], as that is closed by libassuan, and
1057      closing it here might cause libassuan to close some unrelated FD
1058      later).  Alternatively, we could special case status_fd and
1059      register/unregister it manually as needed, but this increases
1060      code duplication and is more complicated as we can not use the
1061      close notifications etc.  A third alternative would be to let
1062      Assuan know that we closed the FD, but that complicates the
1063      Assuan interface.  */
1064
1065   gpgsm->status_cb.fd = _gpgme_io_dup (fdlist[0]);
1066   if (gpgsm->status_cb.fd < 0)
1067     return gpg_error_from_syserror ();
1068
1069   if (_gpgme_io_set_close_notify (gpgsm->status_cb.fd,
1070                                   close_notify_handler, gpgsm))
1071     {
1072       _gpgme_io_close (gpgsm->status_cb.fd);
1073       gpgsm->status_cb.fd = -1;
1074       return gpg_error (GPG_ERR_GENERAL);
1075     }
1076
1077   err = add_io_cb (gpgsm, &gpgsm->status_cb, status_handler);
1078   if (!err && gpgsm->input_cb.fd != -1)
1079     err = add_io_cb (gpgsm, &gpgsm->input_cb, _gpgme_data_outbound_handler);
1080   if (!err && gpgsm->output_cb.fd != -1)
1081     err = add_io_cb (gpgsm, &gpgsm->output_cb, _gpgme_data_inbound_handler);
1082   if (!err && gpgsm->message_cb.fd != -1)
1083     err = add_io_cb (gpgsm, &gpgsm->message_cb, _gpgme_data_outbound_handler);
1084
1085   if (!err)
1086     err = assuan_write_line (gpgsm->assuan_ctx, command);
1087
1088   if (!err)
1089     gpgsm_io_event (gpgsm, GPGME_EVENT_START, NULL);
1090
1091   return err;
1092 }
1093
1094
1095 #if USE_DESCRIPTOR_PASSING
1096 static gpgme_error_t
1097 gpgsm_reset (void *engine)
1098 {
1099   engine_gpgsm_t gpgsm = engine;
1100
1101   /* IF we have an active connection we must send a reset because we
1102      need to reset the list of signers.  Note that RESET does not
1103      reset OPTION commands. */
1104   return (gpgsm->assuan_ctx
1105           ? gpgsm_assuan_simple_command (gpgsm, "RESET", NULL, NULL)
1106           : 0);
1107 }
1108 #endif
1109
1110
1111
1112 static gpgme_error_t
1113 gpgsm_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain)
1114 {
1115   engine_gpgsm_t gpgsm = engine;
1116   gpgme_error_t err;
1117
1118   if (!gpgsm)
1119     return gpg_error (GPG_ERR_INV_VALUE);
1120
1121   gpgsm->input_cb.data = ciph;
1122   err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
1123   if (err)
1124     return gpg_error (GPG_ERR_GENERAL); /* FIXME */
1125   gpgsm->output_cb.data = plain;
1126   err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0);
1127   if (err)
1128     return gpg_error (GPG_ERR_GENERAL); /* FIXME */
1129   gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1130   gpgsm->inline_data = NULL;
1131
1132   err = start (engine, "DECRYPT");
1133   return err;
1134 }
1135
1136
1137 static gpgme_error_t
1138 gpgsm_delete (void *engine, gpgme_key_t key, int allow_secret)
1139 {
1140   engine_gpgsm_t gpgsm = engine;
1141   gpgme_error_t err;
1142   char *fpr = key->subkeys ? key->subkeys->fpr : NULL;
1143   char *linep = fpr;
1144   char *line;
1145   int length = 8;       /* "DELKEYS " */
1146
1147   (void)allow_secret;
1148
1149   if (!fpr)
1150     return gpg_error (GPG_ERR_INV_VALUE);
1151
1152   while (*linep)
1153     {
1154       length++;
1155       if (*linep == '%' || *linep == ' ' || *linep == '+')
1156         length += 2;
1157       linep++;
1158     }
1159   length++;
1160
1161   line = malloc (length);
1162   if (!line)
1163     return gpg_error_from_syserror ();
1164
1165   strcpy (line, "DELKEYS ");
1166   linep = &line[8];
1167
1168   while (*fpr)
1169     {
1170       switch (*fpr)
1171         {
1172         case '%':
1173           *(linep++) = '%';
1174           *(linep++) = '2';
1175           *(linep++) = '5';
1176           break;
1177         case ' ':
1178           *(linep++) = '%';
1179           *(linep++) = '2';
1180           *(linep++) = '0';
1181           break;
1182         case '+':
1183           *(linep++) = '%';
1184           *(linep++) = '2';
1185           *(linep++) = 'B';
1186           break;
1187         default:
1188           *(linep++) = *fpr;
1189           break;
1190         }
1191       fpr++;
1192     }
1193   *linep = '\0';
1194
1195   gpgsm_clear_fd (gpgsm, OUTPUT_FD);
1196   gpgsm_clear_fd (gpgsm, INPUT_FD);
1197   gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1198   gpgsm->inline_data = NULL;
1199
1200   err = start (gpgsm, line);
1201   free (line);
1202
1203   return err;
1204 }
1205
1206
1207 static gpgme_error_t
1208 set_recipients (engine_gpgsm_t gpgsm, gpgme_key_t recp[])
1209 {
1210   gpgme_error_t err = 0;
1211   char *line;
1212   int linelen;
1213   int invalid_recipients = 0;
1214   int i;
1215
1216   linelen = 10 + 40 + 1;        /* "RECIPIENT " + guess + '\0'.  */
1217   line = malloc (10 + 40 + 1);
1218   if (!line)
1219     return gpg_error_from_syserror ();
1220   strcpy (line, "RECIPIENT ");
1221   for (i =0; !err && recp[i]; i++)
1222     {
1223       char *fpr;
1224       int newlen;
1225
1226       if (!recp[i]->subkeys || !recp[i]->subkeys->fpr)
1227         {
1228           invalid_recipients++;
1229           continue;
1230         }
1231       fpr = recp[i]->subkeys->fpr;
1232
1233       newlen = 11 + strlen (fpr);
1234       if (linelen < newlen)
1235         {
1236           char *newline = realloc (line, newlen);
1237           if (! newline)
1238             {
1239               int saved_err = gpg_error_from_syserror ();
1240               free (line);
1241               return saved_err;
1242             }
1243           line = newline;
1244           linelen = newlen;
1245         }
1246       strcpy (&line[10], fpr);
1247
1248       err = gpgsm_assuan_simple_command (gpgsm, line, gpgsm->status.fnc,
1249                                          gpgsm->status.fnc_value);
1250       /* FIXME: This requires more work.  */
1251       if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
1252         invalid_recipients++;
1253       else if (err)
1254         {
1255           free (line);
1256           return err;
1257         }
1258     }
1259   free (line);
1260   return gpg_error (invalid_recipients
1261                     ? GPG_ERR_UNUSABLE_PUBKEY : GPG_ERR_NO_ERROR);
1262 }
1263
1264
1265 static gpgme_error_t
1266 gpgsm_encrypt (void *engine, gpgme_key_t recp[], gpgme_encrypt_flags_t flags,
1267                gpgme_data_t plain, gpgme_data_t ciph, int use_armor)
1268 {
1269   engine_gpgsm_t gpgsm = engine;
1270   gpgme_error_t err;
1271
1272   if (!gpgsm)
1273     return gpg_error (GPG_ERR_INV_VALUE);
1274   if (!recp)
1275     return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
1276
1277   if (flags & GPGME_ENCRYPT_NO_ENCRYPT_TO)
1278     {
1279       err = gpgsm_assuan_simple_command (gpgsm,
1280                                          "OPTION no-encrypt-to", NULL, NULL);
1281       if (err)
1282         return err;
1283     }
1284
1285   gpgsm->input_cb.data = plain;
1286   err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
1287   if (err)
1288     return err;
1289   gpgsm->output_cb.data = ciph;
1290   err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
1291                       : map_data_enc (gpgsm->output_cb.data));
1292   if (err)
1293     return err;
1294   gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1295   gpgsm->inline_data = NULL;
1296
1297   err = set_recipients (gpgsm, recp);
1298
1299   if (!err)
1300     err = start (gpgsm, "ENCRYPT");
1301
1302   return err;
1303 }
1304
1305
1306 static gpgme_error_t
1307 gpgsm_export (void *engine, const char *pattern, gpgme_export_mode_t mode,
1308               gpgme_data_t keydata, int use_armor)
1309 {
1310   engine_gpgsm_t gpgsm = engine;
1311   gpgme_error_t err = 0;
1312   char *cmd;
1313
1314   if (!gpgsm)
1315     return gpg_error (GPG_ERR_INV_VALUE);
1316
1317   if (!pattern)
1318     pattern = "";
1319
1320   cmd = malloc (7 + 9 + 9 + strlen (pattern) + 1);
1321   if (!cmd)
1322     return gpg_error_from_syserror ();
1323
1324   strcpy (cmd, "EXPORT ");
1325   if ((mode & GPGME_EXPORT_MODE_SECRET))
1326     {
1327       strcat (cmd, "--secret ");
1328       if ((mode & GPGME_EXPORT_MODE_RAW))
1329         strcat (cmd, "--raw ");
1330       else if ((mode & GPGME_EXPORT_MODE_PKCS12))
1331         strcat (cmd, "--pkcs12 ");
1332     }
1333   strcat (cmd, pattern);
1334
1335   gpgsm->output_cb.data = keydata;
1336   err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
1337                       : map_data_enc (gpgsm->output_cb.data));
1338   if (err)
1339     return err;
1340   gpgsm_clear_fd (gpgsm, INPUT_FD);
1341   gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1342   gpgsm->inline_data = NULL;
1343
1344   err = start (gpgsm, cmd);
1345   free (cmd);
1346   return err;
1347 }
1348
1349
1350 static gpgme_error_t
1351 gpgsm_export_ext (void *engine, const char *pattern[], gpgme_export_mode_t mode,
1352                   gpgme_data_t keydata, int use_armor)
1353 {
1354   engine_gpgsm_t gpgsm = engine;
1355   gpgme_error_t err = 0;
1356   char *line;
1357   /* Length is "EXPORT " + "--secret " + "--pkcs12 " + p + '\0'.  */
1358   int length = 7 + 9 + 9 + 1;
1359   char *linep;
1360
1361   if (!gpgsm)
1362     return gpg_error (GPG_ERR_INV_VALUE);
1363
1364   if (pattern && *pattern)
1365     {
1366       const char **pat = pattern;
1367
1368       while (*pat)
1369         {
1370           const char *patlet = *pat;
1371
1372           while (*patlet)
1373             {
1374               length++;
1375               if (*patlet == '%' || *patlet == ' ' || *patlet == '+')
1376                 length += 2;
1377               patlet++;
1378             }
1379           pat++;
1380           length++;
1381         }
1382     }
1383   line = malloc (length);
1384   if (!line)
1385     return gpg_error_from_syserror ();
1386
1387   strcpy (line, "EXPORT ");
1388   if ((mode & GPGME_EXPORT_MODE_SECRET))
1389     {
1390       strcat (line, "--secret ");
1391       if ((mode & GPGME_EXPORT_MODE_RAW))
1392         strcat (line, "--raw ");
1393       else if ((mode & GPGME_EXPORT_MODE_PKCS12))
1394         strcat (line, "--pkcs12 ");
1395     }
1396   linep = &line[strlen (line)];
1397
1398   if (pattern && *pattern)
1399     {
1400       while (*pattern)
1401         {
1402           const char *patlet = *pattern;
1403
1404           while (*patlet)
1405             {
1406               switch (*patlet)
1407                 {
1408                 case '%':
1409                   *(linep++) = '%';
1410                   *(linep++) = '2';
1411                   *(linep++) = '5';
1412                   break;
1413                 case ' ':
1414                   *(linep++) = '%';
1415                   *(linep++) = '2';
1416                   *(linep++) = '0';
1417                   break;
1418                 case '+':
1419                   *(linep++) = '%';
1420                   *(linep++) = '2';
1421                   *(linep++) = 'B';
1422                   break;
1423                 default:
1424                   *(linep++) = *patlet;
1425                   break;
1426                 }
1427               patlet++;
1428             }
1429           pattern++;
1430           if (*pattern)
1431             *linep++ = ' ';
1432         }
1433     }
1434   *linep = '\0';
1435
1436   gpgsm->output_cb.data = keydata;
1437   err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
1438                       : map_data_enc (gpgsm->output_cb.data));
1439   if (err)
1440     return err;
1441   gpgsm_clear_fd (gpgsm, INPUT_FD);
1442   gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1443   gpgsm->inline_data = NULL;
1444
1445   err = start (gpgsm, line);
1446   free (line);
1447   return err;
1448 }
1449
1450
1451 static gpgme_error_t
1452 gpgsm_genkey (void *engine,
1453               const char *userid, const char *algo,
1454               unsigned long reserved, unsigned long expires,
1455               gpgme_key_t key, unsigned int flags,
1456               gpgme_data_t help_data, unsigned int extraflags,
1457               gpgme_data_t pubkey, gpgme_data_t seckey)
1458 {
1459   engine_gpgsm_t gpgsm = engine;
1460   gpgme_error_t err;
1461
1462   (void)reserved;
1463
1464   if (!gpgsm)
1465     return gpg_error (GPG_ERR_INV_VALUE);
1466
1467   if (help_data)
1468     {
1469       if (!pubkey || seckey)
1470         return gpg_error (GPG_ERR_INV_VALUE);
1471
1472       gpgsm->input_cb.data = help_data;
1473       err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
1474       if (err)
1475         return err;
1476       gpgsm->output_cb.data = pubkey;
1477       err = gpgsm_set_fd (gpgsm, OUTPUT_FD,
1478                           (extraflags & GENKEY_EXTRAFLAG_ARMOR)? "--armor"
1479                           : map_data_enc (gpgsm->output_cb.data));
1480       if (err)
1481         return err;
1482       gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1483       gpgsm->inline_data = NULL;
1484
1485       err = start (gpgsm, "GENKEY");
1486       return err;
1487     }
1488
1489   (void)userid;
1490   (void)algo;
1491   (void)expires;
1492   (void)key;
1493   (void)flags;
1494
1495   /* The new interface has not yet been implemented,  */
1496   return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
1497 }
1498
1499
1500 static gpgme_error_t
1501 gpgsm_import (void *engine, gpgme_data_t keydata, gpgme_key_t *keyarray)
1502 {
1503   engine_gpgsm_t gpgsm = engine;
1504   gpgme_error_t err;
1505   gpgme_data_encoding_t dataenc;
1506   int idx;
1507
1508   if (!gpgsm)
1509     return gpg_error (GPG_ERR_INV_VALUE);
1510
1511   if (keydata && keyarray)
1512     return gpg_error (GPG_ERR_INV_VALUE); /* Only one is allowed.  */
1513
1514   dataenc = gpgme_data_get_encoding (keydata);
1515
1516   if (keyarray)
1517     {
1518       size_t buflen;
1519       char *buffer, *p;
1520
1521       /* Fist check whether the engine already features the
1522          --re-import option.  */
1523       err = gpgsm_assuan_simple_command
1524         (gpgsm, "GETINFO cmd_has_option IMPORT re-import", NULL, NULL);
1525       if (err)
1526         return gpg_error (GPG_ERR_NOT_SUPPORTED);
1527
1528       /* Create an internal data object with a list of all
1529          fingerprints.  The data object and its memory (to avoid an
1530          extra copy by gpgme_data_new_from_mem) are stored in two
1531          variables which are released by the close_notify_handler.  */
1532       for (idx=0, buflen=0; keyarray[idx]; idx++)
1533         {
1534           if (keyarray[idx]->protocol == GPGME_PROTOCOL_CMS
1535               && keyarray[idx]->subkeys
1536               && keyarray[idx]->subkeys->fpr
1537               && *keyarray[idx]->subkeys->fpr)
1538             buflen += strlen (keyarray[idx]->subkeys->fpr) + 1;
1539         }
1540       /* Allocate a bufer with extra space for the trailing Nul
1541          introduced by the use of stpcpy.  */
1542       buffer = malloc (buflen+1);
1543       if (!buffer)
1544         return gpg_error_from_syserror ();
1545       for (idx=0, p = buffer; keyarray[idx]; idx++)
1546         {
1547           if (keyarray[idx]->protocol == GPGME_PROTOCOL_CMS
1548               && keyarray[idx]->subkeys
1549               && keyarray[idx]->subkeys->fpr
1550               && *keyarray[idx]->subkeys->fpr)
1551             p = stpcpy (stpcpy (p, keyarray[idx]->subkeys->fpr), "\n");
1552         }
1553
1554       err = gpgme_data_new_from_mem (&gpgsm->input_helper_data,
1555                                      buffer, buflen, 0);
1556       if (err)
1557         {
1558           free (buffer);
1559           return err;
1560         }
1561       gpgsm->input_helper_memory = buffer;
1562
1563       gpgsm->input_cb.data = gpgsm->input_helper_data;
1564       err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
1565       if (err)
1566         {
1567           gpgme_data_release (gpgsm->input_helper_data);
1568           gpgsm->input_helper_data = NULL;
1569           free (gpgsm->input_helper_memory);
1570           gpgsm->input_helper_memory = NULL;
1571           return err;
1572         }
1573       gpgsm_clear_fd (gpgsm, OUTPUT_FD);
1574       gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1575       gpgsm->inline_data = NULL;
1576
1577       return start (gpgsm, "IMPORT --re-import");
1578     }
1579   else if (dataenc == GPGME_DATA_ENCODING_URL
1580            || dataenc == GPGME_DATA_ENCODING_URL0
1581            || dataenc == GPGME_DATA_ENCODING_URLESC)
1582     {
1583       return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
1584     }
1585   else
1586     {
1587       gpgsm->input_cb.data = keydata;
1588       err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
1589       if (err)
1590         return err;
1591       gpgsm_clear_fd (gpgsm, OUTPUT_FD);
1592       gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1593       gpgsm->inline_data = NULL;
1594
1595       return start (gpgsm, "IMPORT");
1596     }
1597 }
1598
1599
1600 static gpgme_error_t
1601 gpgsm_keylist (void *engine, const char *pattern, int secret_only,
1602                gpgme_keylist_mode_t mode, int engine_flags)
1603 {
1604   engine_gpgsm_t gpgsm = engine;
1605   char *line;
1606   gpgme_error_t err;
1607   int list_mode = 0;
1608
1609   if (mode & GPGME_KEYLIST_MODE_LOCAL)
1610     list_mode |= 1;
1611   if (mode & GPGME_KEYLIST_MODE_EXTERN)
1612     list_mode |= 2;
1613
1614   if (!pattern)
1615     pattern = "";
1616
1617   /* Hack to make sure that the agent is started.  Only if the agent
1618      has been started an application may connect to the agent via
1619      GPGME_PROTOCOL_ASSUAN - for example to look for smartcards.  We
1620      do this only if a secret key listing has been requested.  In
1621      general this is not needed because a secret key listing starts
1622      the agent.  However on a fresh installation no public keys are
1623      available and thus there is no need for gpgsm to ask the agent
1624      whether a secret key exists for the public key.  */
1625   if (secret_only || (mode & GPGME_KEYLIST_MODE_WITH_SECRET))
1626     gpgsm_assuan_simple_command (gpgsm, "GETINFO agent-check", NULL, NULL);
1627
1628   /* Always send list-mode option because RESET does not reset it.  */
1629   if (asprintf (&line, "OPTION list-mode=%d", (list_mode & 3)) < 0)
1630     return gpg_error_from_syserror ();
1631   err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL);
1632   free (line);
1633   if (err)
1634     return err;
1635
1636
1637   /* Always send key validation because RESET does not reset it.  */
1638
1639   /* Use the validation mode if requested.  We don't check for an error
1640      yet because this is a pretty fresh gpgsm features. */
1641   gpgsm_assuan_simple_command (gpgsm,
1642                                (mode & GPGME_KEYLIST_MODE_VALIDATE)?
1643                                "OPTION with-validation=1":
1644                                "OPTION with-validation=0" ,
1645                                NULL, NULL);
1646   /* Include the ephemeral keys if requested.  We don't check for an error
1647      yet because this is a pretty fresh gpgsm features. */
1648   gpgsm_assuan_simple_command (gpgsm,
1649                                (mode & GPGME_KEYLIST_MODE_EPHEMERAL)?
1650                                "OPTION with-ephemeral-keys=1":
1651                                "OPTION with-ephemeral-keys=0" ,
1652                                NULL, NULL);
1653   gpgsm_assuan_simple_command (gpgsm,
1654                                (mode & GPGME_KEYLIST_MODE_WITH_SECRET)?
1655                                "OPTION with-secret=1":
1656                                "OPTION with-secret=0" ,
1657                                NULL, NULL);
1658   gpgsm_assuan_simple_command (gpgsm,
1659                                (engine_flags & GPGME_ENGINE_FLAG_OFFLINE)?
1660                                "OPTION offline=1":
1661                                "OPTION offline=0" ,
1662                                NULL, NULL);
1663
1664
1665   /* Length is "LISTSECRETKEYS " + p + '\0'.  */
1666   line = malloc (15 + strlen (pattern) + 1);
1667   if (!line)
1668     return gpg_error_from_syserror ();
1669   if (secret_only)
1670     {
1671       strcpy (line, "LISTSECRETKEYS ");
1672       strcpy (&line[15], pattern);
1673     }
1674   else
1675     {
1676       strcpy (line, "LISTKEYS ");
1677       strcpy (&line[9], pattern);
1678     }
1679
1680   gpgsm_clear_fd (gpgsm, INPUT_FD);
1681   gpgsm_clear_fd (gpgsm, OUTPUT_FD);
1682   gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1683   gpgsm->inline_data = NULL;
1684
1685   err = start (gpgsm, line);
1686   free (line);
1687   return err;
1688 }
1689
1690
1691 static gpgme_error_t
1692 gpgsm_keylist_ext (void *engine, const char *pattern[], int secret_only,
1693                    int reserved, gpgme_keylist_mode_t mode, int engine_flags)
1694 {
1695   engine_gpgsm_t gpgsm = engine;
1696   char *line;
1697   gpgme_error_t err;
1698   /* Length is "LISTSECRETKEYS " + p + '\0'.  */
1699   int length = 15 + 1;
1700   char *linep;
1701   int any_pattern = 0;
1702   int list_mode = 0;
1703
1704   if (reserved)
1705     return gpg_error (GPG_ERR_INV_VALUE);
1706
1707   if (mode & GPGME_KEYLIST_MODE_LOCAL)
1708     list_mode |= 1;
1709   if (mode & GPGME_KEYLIST_MODE_EXTERN)
1710     list_mode |= 2;
1711
1712   /* Always send list-mode option because RESET does not reset it.  */
1713   if (asprintf (&line, "OPTION list-mode=%d", (list_mode & 3)) < 0)
1714     return gpg_error_from_syserror ();
1715   err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL);
1716   free (line);
1717   if (err)
1718     return err;
1719
1720   /* Always send key validation because RESET does not reset it.  */
1721   /* Use the validation mode if required.  We don't check for an error
1722      yet because this is a pretty fresh gpgsm features. */
1723   gpgsm_assuan_simple_command (gpgsm,
1724                                (mode & GPGME_KEYLIST_MODE_VALIDATE)?
1725                                "OPTION with-validation=1":
1726                                "OPTION with-validation=0" ,
1727                                NULL, NULL);
1728   gpgsm_assuan_simple_command (gpgsm,
1729                                (mode & GPGME_KEYLIST_MODE_WITH_SECRET)?
1730                                "OPTION with-secret=1":
1731                                "OPTION with-secret=0" ,
1732                                NULL, NULL);
1733   gpgsm_assuan_simple_command (gpgsm,
1734                                (engine_flags & GPGME_ENGINE_FLAG_OFFLINE)?
1735                                "OPTION offline=1":
1736                                "OPTION offline=0" ,
1737                                NULL, NULL);
1738
1739   if (pattern && *pattern)
1740     {
1741       const char **pat = pattern;
1742
1743       while (*pat)
1744         {
1745           const char *patlet = *pat;
1746
1747           while (*patlet)
1748             {
1749               length++;
1750               if (*patlet == '%' || *patlet == ' ' || *patlet == '+')
1751                 length += 2;
1752               patlet++;
1753             }
1754           pat++;
1755           length++;
1756         }
1757     }
1758   line = malloc (length);
1759   if (!line)
1760     return gpg_error_from_syserror ();
1761   if (secret_only)
1762     {
1763       strcpy (line, "LISTSECRETKEYS ");
1764       linep = &line[15];
1765     }
1766   else
1767     {
1768       strcpy (line, "LISTKEYS ");
1769       linep = &line[9];
1770     }
1771
1772   if (pattern && *pattern)
1773     {
1774       while (*pattern)
1775         {
1776           const char *patlet = *pattern;
1777
1778           while (*patlet)
1779             {
1780               switch (*patlet)
1781                 {
1782                 case '%':
1783                   *(linep++) = '%';
1784                   *(linep++) = '2';
1785                   *(linep++) = '5';
1786                   break;
1787                 case ' ':
1788                   *(linep++) = '%';
1789                   *(linep++) = '2';
1790                   *(linep++) = '0';
1791                   break;
1792                 case '+':
1793                   *(linep++) = '%';
1794                   *(linep++) = '2';
1795                   *(linep++) = 'B';
1796                   break;
1797                 default:
1798                   *(linep++) = *patlet;
1799                   break;
1800                 }
1801               patlet++;
1802             }
1803           any_pattern = 1;
1804           *linep++ = ' ';
1805           pattern++;
1806         }
1807     }
1808   if (any_pattern)
1809     linep--;
1810   *linep = '\0';
1811
1812   gpgsm_clear_fd (gpgsm, INPUT_FD);
1813   gpgsm_clear_fd (gpgsm, OUTPUT_FD);
1814   gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1815   gpgsm->inline_data = NULL;
1816
1817   err = start (gpgsm, line);
1818   free (line);
1819   return err;
1820 }
1821
1822
1823 static gpgme_error_t
1824 gpgsm_sign (void *engine, gpgme_data_t in, gpgme_data_t out,
1825             gpgme_sig_mode_t mode, int use_armor, int use_textmode,
1826             int include_certs, gpgme_ctx_t ctx /* FIXME */)
1827 {
1828   engine_gpgsm_t gpgsm = engine;
1829   gpgme_error_t err;
1830   char *assuan_cmd;
1831   int i;
1832   gpgme_key_t key;
1833
1834   (void)use_textmode;
1835
1836   if (!gpgsm)
1837     return gpg_error (GPG_ERR_INV_VALUE);
1838
1839   /* FIXME: This does not work as RESET does not reset it so we can't
1840      revert back to default.  */
1841   if (include_certs != GPGME_INCLUDE_CERTS_DEFAULT)
1842     {
1843       /* FIXME: Make sure that if we run multiple operations, that we
1844          can reset any previously set value in case the default is
1845          requested.  */
1846
1847       if (asprintf (&assuan_cmd, "OPTION include-certs %i", include_certs) < 0)
1848         return gpg_error_from_syserror ();
1849       err = gpgsm_assuan_simple_command (gpgsm, assuan_cmd, NULL, NULL);
1850       free (assuan_cmd);
1851       if (err)
1852         return err;
1853     }
1854
1855   for (i = 0; (key = gpgme_signers_enum (ctx, i)); i++)
1856     {
1857       const char *s = key->subkeys ? key->subkeys->fpr : NULL;
1858       if (s && strlen (s) < 80)
1859         {
1860           char buf[100];
1861
1862           strcpy (stpcpy (buf, "SIGNER "), s);
1863           err = gpgsm_assuan_simple_command (gpgsm, buf,
1864                                              gpgsm->status.fnc,
1865                                              gpgsm->status.fnc_value);
1866         }
1867       else
1868         err = gpg_error (GPG_ERR_INV_VALUE);
1869       gpgme_key_unref (key);
1870       if (err)
1871         return err;
1872     }
1873
1874   gpgsm->input_cb.data = in;
1875   err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
1876   if (err)
1877     return err;
1878   gpgsm->output_cb.data = out;
1879   err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
1880                       : map_data_enc (gpgsm->output_cb.data));
1881   if (err)
1882     return err;
1883   gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1884   gpgsm->inline_data = NULL;
1885
1886   err = start (gpgsm, mode == GPGME_SIG_MODE_DETACH
1887                ? "SIGN --detached" : "SIGN");
1888   return err;
1889 }
1890
1891
1892 static gpgme_error_t
1893 gpgsm_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text,
1894               gpgme_data_t plaintext)
1895 {
1896   engine_gpgsm_t gpgsm = engine;
1897   gpgme_error_t err;
1898
1899   if (!gpgsm)
1900     return gpg_error (GPG_ERR_INV_VALUE);
1901
1902   gpgsm->input_cb.data = sig;
1903   err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
1904   if (err)
1905     return err;
1906   if (plaintext)
1907     {
1908       /* Normal or cleartext signature.  */
1909       gpgsm->output_cb.data = plaintext;
1910       err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0);
1911       gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1912     }
1913   else
1914     {
1915       /* Detached signature.  */
1916       gpgsm->message_cb.data = signed_text;
1917       err = gpgsm_set_fd (gpgsm, MESSAGE_FD, 0);
1918       gpgsm_clear_fd (gpgsm, OUTPUT_FD);
1919     }
1920   gpgsm->inline_data = NULL;
1921
1922   if (!err)
1923     err = start (gpgsm, "VERIFY");
1924
1925   return err;
1926 }
1927
1928
1929 /* Send the GETAUDITLOG command.  The result is saved to a gpgme data
1930    object.  */
1931 static gpgme_error_t
1932 gpgsm_getauditlog (void *engine, gpgme_data_t output, unsigned int flags)
1933 {
1934   engine_gpgsm_t gpgsm = engine;
1935   gpgme_error_t err = 0;
1936
1937   if (!gpgsm || !output)
1938     return gpg_error (GPG_ERR_INV_VALUE);
1939
1940 #if USE_DESCRIPTOR_PASSING
1941   gpgsm->output_cb.data = output;
1942   err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0);
1943   if (err)
1944     return err;
1945
1946   gpgsm_clear_fd (gpgsm, INPUT_FD);
1947   gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1948   gpgsm->inline_data = NULL;
1949 # define CMD  "GETAUDITLOG"
1950 #else
1951   gpgsm_clear_fd (gpgsm, OUTPUT_FD);
1952   gpgsm_clear_fd (gpgsm, INPUT_FD);
1953   gpgsm_clear_fd (gpgsm, MESSAGE_FD);
1954   gpgsm->inline_data = output;
1955 # define CMD  "GETAUDITLOG --data"
1956 #endif
1957
1958   err = start (gpgsm, (flags & GPGME_AUDITLOG_HTML)? CMD " --html" : CMD);
1959
1960   return err;
1961 }
1962
1963
1964 /* This sets a status callback for monitoring status lines before they
1965  * are passed to a caller set handler.  */
1966 static void
1967 gpgsm_set_status_cb (void *engine, gpgme_status_cb_t cb, void *cb_value)
1968 {
1969   engine_gpgsm_t gpgsm = engine;
1970
1971   gpgsm->status.mon_cb = cb;
1972   gpgsm->status.mon_cb_value = cb_value;
1973 }
1974
1975
1976 static void
1977 gpgsm_set_status_handler (void *engine, engine_status_handler_t fnc,
1978                           void *fnc_value)
1979 {
1980   engine_gpgsm_t gpgsm = engine;
1981
1982   gpgsm->status.fnc = fnc;
1983   gpgsm->status.fnc_value = fnc_value;
1984 }
1985
1986
1987 static gpgme_error_t
1988 gpgsm_set_colon_line_handler (void *engine, engine_colon_line_handler_t fnc,
1989                               void *fnc_value)
1990 {
1991   engine_gpgsm_t gpgsm = engine;
1992
1993   gpgsm->colon.fnc = fnc;
1994   gpgsm->colon.fnc_value = fnc_value;
1995   gpgsm->colon.any = 0;
1996   return 0;
1997 }
1998
1999
2000 static void
2001 gpgsm_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
2002 {
2003   engine_gpgsm_t gpgsm = engine;
2004   gpgsm->io_cbs = *io_cbs;
2005 }
2006
2007
2008 static void
2009 gpgsm_io_event (void *engine, gpgme_event_io_t type, void *type_data)
2010 {
2011   engine_gpgsm_t gpgsm = engine;
2012
2013   TRACE3 (DEBUG_ENGINE, "gpgme:gpgsm_io_event", gpgsm,
2014           "event %p, type %d, type_data %p",
2015           gpgsm->io_cbs.event, type, type_data);
2016   if (gpgsm->io_cbs.event)
2017     (*gpgsm->io_cbs.event) (gpgsm->io_cbs.event_priv, type, type_data);
2018 }
2019
2020
2021 static gpgme_error_t
2022 gpgsm_passwd (void *engine, gpgme_key_t key, unsigned int flags)
2023 {
2024   engine_gpgsm_t gpgsm = engine;
2025   gpgme_error_t err;
2026   char *line;
2027
2028   (void)flags;
2029
2030   if (!key || !key->subkeys || !key->subkeys->fpr)
2031     return gpg_error (GPG_ERR_INV_CERT_OBJ);
2032
2033   if (asprintf (&line, "PASSWD -- %s", key->subkeys->fpr) < 0)
2034     return gpg_error_from_syserror ();
2035
2036   gpgsm_clear_fd (gpgsm, OUTPUT_FD);
2037   gpgsm_clear_fd (gpgsm, INPUT_FD);
2038   gpgsm_clear_fd (gpgsm, MESSAGE_FD);
2039   gpgsm->inline_data = NULL;
2040
2041   err = start (gpgsm, line);
2042   free (line);
2043
2044   return err;
2045 }
2046
2047
2048
2049 struct engine_ops _gpgme_engine_ops_gpgsm =
2050   {
2051     /* Static functions.  */
2052     _gpgme_get_default_gpgsm_name,
2053     NULL,
2054     gpgsm_get_version,
2055     gpgsm_get_req_version,
2056     gpgsm_new,
2057
2058     /* Member functions.  */
2059     gpgsm_release,
2060 #if USE_DESCRIPTOR_PASSING
2061     gpgsm_reset,
2062 #else
2063     NULL,                       /* reset */
2064 #endif
2065     gpgsm_set_status_cb,
2066     gpgsm_set_status_handler,
2067     NULL,               /* set_command_handler */
2068     gpgsm_set_colon_line_handler,
2069     gpgsm_set_locale,
2070     NULL,               /* set_protocol */
2071     gpgsm_decrypt,
2072     gpgsm_decrypt,
2073     gpgsm_delete,       /* decrypt_verify */
2074     NULL,               /* edit */
2075     gpgsm_encrypt,
2076     NULL,               /* encrypt_sign */
2077     gpgsm_export,
2078     gpgsm_export_ext,
2079     gpgsm_genkey,
2080     gpgsm_import,
2081     gpgsm_keylist,
2082     gpgsm_keylist_ext,
2083     NULL,               /* keysign */
2084     NULL,               /* tofu_policy */
2085     gpgsm_sign,
2086     NULL,               /* trustlist */
2087     gpgsm_verify,
2088     gpgsm_getauditlog,
2089     NULL,               /* opassuan_transact */
2090     NULL,               /* conf_load */
2091     NULL,               /* conf_save */
2092     gpgsm_set_io_cbs,
2093     gpgsm_io_event,
2094     gpgsm_cancel,
2095     NULL,               /* cancel_op */
2096     gpgsm_passwd,
2097     NULL,               /* set_pinentry_mode */
2098     NULL                /* opspawn */
2099   };