2009-10-26 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / src / engine-g13.c
1 /* engine-g13.c - G13 engine.
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 g10 Code GmbH
4  
5    This file is part of GPGME.
6
7    GPGME is free software; you can redistribute it and/or modify it
8    under the terms of the GNU Lesser General Public License as
9    published by the Free Software Foundation; either version 2.1 of
10    the License, or (at your option) any later version.
11    
12    GPGME is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Lesser General Public License for more details.
16    
17    You should have received a copy of the GNU Lesser General Public
18    License along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20    02111-1307, USA.  */
21
22 #if HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <assert.h>
30 #include <unistd.h>
31 #include <locale.h>
32 #include <fcntl.h> /* FIXME */
33 #include <errno.h>
34
35 #include "gpgme.h"
36 #include "util.h"
37 #include "ops.h"
38 #include "wait.h"
39 #include "priv-io.h"
40 #include "sema.h"
41
42 #include "assuan.h"
43 #include "debug.h"
44
45 #include "engine-backend.h"
46
47 \f
48 typedef struct
49 {
50   int fd;       /* FD we talk about.  */
51   int server_fd;/* Server FD for this connection.  */
52   int dir;      /* Inbound/Outbound, maybe given implicit?  */
53   void *data;   /* Handler-specific data.  */
54   void *tag;    /* ID from the user for gpgme_remove_io_callback.  */
55   char server_fd_str[15]; /* Same as SERVER_FD but as a string.  We
56                              need this because _gpgme_io_fd2str can't
57                              be used on a closed descriptor.  */
58 } iocb_data_t;
59
60
61 struct engine_g13
62 {
63   assuan_context_t assuan_ctx;
64
65   int lc_ctype_set;
66   int lc_messages_set;
67
68   iocb_data_t status_cb;
69
70   struct gpgme_io_cbs io_cbs;
71
72   /* Internal callbacks.  */
73   engine_assuan_result_cb_t result_cb;
74   void *result_cb_value; 
75
76   /* User provided callbacks.  */
77   struct {
78     gpgme_assuan_data_cb_t data_cb;
79     void *data_cb_value;
80
81     gpgme_assuan_inquire_cb_t inq_cb;
82     void *inq_cb_value;
83
84     gpgme_assuan_status_cb_t status_cb;
85     void *status_cb_value;
86   } user;
87 };
88
89 typedef struct engine_g13 *engine_g13_t;
90
91
92 static void g13_io_event (void *engine, 
93                             gpgme_event_io_t type, void *type_data);
94
95
96 \f
97 static char *
98 g13_get_version (const char *file_name)
99 {
100   return _gpgme_get_program_version (file_name ? file_name
101                                      : _gpgme_get_g13_path ());
102 }
103
104
105 static const char *
106 g13_get_req_version (void)
107 {
108   return NEED_G13_VERSION;
109 }
110
111 \f
112 static void
113 close_notify_handler (int fd, void *opaque)
114 {
115   engine_g13_t g13 = opaque;
116
117   assert (fd != -1);
118   if (g13->status_cb.fd == fd)
119     {
120       if (g13->status_cb.tag)
121         (*g13->io_cbs.remove) (g13->status_cb.tag);
122       g13->status_cb.fd = -1;
123       g13->status_cb.tag = NULL;
124     }
125 }
126
127
128 /* This is the default inquiry callback.  We use it to handle the
129    Pinentry notifications.  */
130 static gpgme_error_t
131 default_inq_cb (engine_g13_t g13, const char *keyword, const char *args)
132 {
133   gpg_error_t err;
134
135   if (!strcmp (keyword, "PINENTRY_LAUNCHED"))
136     {
137       _gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10));
138     }
139
140   if (g13->user.inq_cb)
141     {
142       gpgme_data_t data = NULL;
143
144       err = g13->user.inq_cb (g13->user.inq_cb_value,
145                                 keyword, args, &data);
146       if (!err && data)
147         {
148           /* FIXME: Returning data is not yet implemented.  However we
149              need to allow the caller to cleanup his data object.
150              Thus we run the callback in finish mode immediately.  */
151           err = g13->user.inq_cb (g13->user.inq_cb_value,
152                                   NULL, NULL, &data);
153         }
154     }
155   else
156     err = 0;
157
158   return err;
159 }
160
161
162 static gpgme_error_t
163 g13_cancel (void *engine)
164 {
165   engine_g13_t g13 = engine;
166
167   if (!g13)
168     return gpg_error (GPG_ERR_INV_VALUE);
169
170   if (g13->status_cb.fd != -1)
171     _gpgme_io_close (g13->status_cb.fd);
172
173   if (g13->assuan_ctx)
174     {
175       assuan_release (g13->assuan_ctx);
176       g13->assuan_ctx = NULL;
177     }
178
179   return 0;
180 }
181
182
183 static gpgme_error_t
184 g13_cancel_op (void *engine)
185 {
186   engine_g13_t g13 = engine;
187
188   if (!g13)
189     return gpg_error (GPG_ERR_INV_VALUE);
190
191   if (g13->status_cb.fd != -1)
192     _gpgme_io_close (g13->status_cb.fd);
193
194   return 0;
195 }
196
197
198 static void
199 g13_release (void *engine)
200 {
201   engine_g13_t g13 = engine;
202
203   if (!g13)
204     return;
205
206   g13_cancel (engine);
207
208   free (g13);
209 }
210
211
212 static gpgme_error_t
213 g13_new (void **engine, const char *file_name, const char *home_dir)
214 {
215   gpgme_error_t err = 0;
216   engine_g13_t g13;
217   int argc;
218   char *argv[5];
219   char *dft_display = NULL;
220   char dft_ttyname[64];
221   char *dft_ttytype = NULL;
222   char *optstr;
223
224   g13 = calloc (1, sizeof *g13);
225   if (!g13)
226     return gpg_error_from_errno (errno);
227
228   g13->status_cb.fd = -1;
229   g13->status_cb.dir = 1;
230   g13->status_cb.tag = 0;
231   g13->status_cb.data = g13;
232
233   err = assuan_new (&g13->assuan_ctx);
234   if (err)
235     goto leave;
236
237   argc = 0;
238   argv[argc++] = "g13";
239   if (home_dir)
240     {
241       argv[argc++] = "--homedir";
242       argv[argc++] = home_dir;
243     }
244   argv[argc++] = "--server";
245   argv[argc++] = NULL;
246
247   err = assuan_new_ext (&g13->assuan_ctx, GPG_ERR_SOURCE_GPGME,
248                         &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb,
249                         NULL);
250   if (err)
251     goto leave;
252   assuan_ctx_set_system_hooks (g13->assuan_ctx, &_gpgme_assuan_system_hooks);
253
254 #if USE_DESCRIPTOR_PASSING
255   err = assuan_pipe_connect_ext
256     (g13->assuan_ctx, file_name ? file_name : _gpgme_get_g13_path (),
257      argv, NULL, NULL, NULL, 1);
258 #else
259   err = assuan_pipe_connect
260     (g13->assuan_ctx, file_name ? file_name : _gpgme_get_g13_path (),
261      argv, NULL);
262 #endif
263   if (err)
264     goto leave;
265
266   err = _gpgme_getenv ("DISPLAY", &dft_display);
267   if (err)
268     goto leave;
269   if (dft_display)
270     {
271       if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
272         {
273           free (dft_display);
274           err = gpg_error_from_errno (errno);
275           goto leave;
276         }
277       free (dft_display);
278
279       err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL,
280                              NULL, NULL, NULL);
281       free (optstr);
282       if (err)
283         goto leave;
284     }
285
286   if (isatty (1))
287     {
288       int rc;
289
290       rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
291       if (rc)
292         {
293           err = gpg_error_from_errno (rc);
294           goto leave;
295         }
296       else
297         {
298           if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
299             {
300               err = gpg_error_from_errno (errno);
301               goto leave;
302             }
303           err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL,
304                                  NULL, NULL, NULL);
305           free (optstr);
306           if (err)
307             goto leave;
308
309           err = _gpgme_getenv ("TERM", &dft_ttytype);
310           if (err)
311             goto leave;
312           if (dft_ttytype)
313             {
314               if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0)
315                 {
316                   free (dft_ttytype);
317                   err = gpg_error_from_errno (errno);
318                   goto leave;
319                 }
320               free (dft_ttytype);
321
322               err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL,
323                                      NULL, NULL, NULL, NULL);
324               free (optstr);
325               if (err)
326                 goto leave;
327             }
328         }
329     }
330
331 #ifdef HAVE_W32_SYSTEM
332   /* Under Windows we need to use AllowSetForegroundWindow.  Tell
333      g13 to tell us when it needs it.  */
334   if (!err)
335     {
336       err = assuan_transact (g13->assuan_ctx, "OPTION allow-pinentry-notify",
337                              NULL, NULL, NULL, NULL, NULL, NULL);
338       if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
339         err = 0; /* This is a new feature of g13.  */
340     }
341 #endif /*HAVE_W32_SYSTEM*/
342
343  leave:
344
345   if (err)
346     g13_release (g13);
347   else
348     *engine = g13;
349
350   return err;
351 }
352
353
354 static gpgme_error_t
355 g13_set_locale (void *engine, int category, const char *value)
356 {
357   engine_g13_t g13 = engine;
358   gpgme_error_t err;
359   char *optstr;
360   char *catstr;
361
362   /* FIXME: If value is NULL, we need to reset the option to default.
363      But we can't do this.  So we error out here.  G13 needs support
364      for this.  */
365   if (category == LC_CTYPE)
366     {
367       catstr = "lc-ctype";
368       if (!value && g13->lc_ctype_set)
369         return gpg_error (GPG_ERR_INV_VALUE);
370       if (value)
371         g13->lc_ctype_set = 1;
372     }
373 #ifdef LC_MESSAGES
374   else if (category == LC_MESSAGES)
375     {
376       catstr = "lc-messages";
377       if (!value && g13->lc_messages_set)
378         return gpg_error (GPG_ERR_INV_VALUE);
379       if (value)
380         g13->lc_messages_set = 1;
381     }
382 #endif /* LC_MESSAGES */
383   else
384     return gpg_error (GPG_ERR_INV_VALUE);
385
386   /* FIXME: Reset value to default.  */
387   if (!value) 
388     return 0;
389
390   if (asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0)
391     err = gpg_error_from_errno (errno);
392   else
393     {
394       err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL,
395                              NULL, NULL, NULL, NULL);
396       free (optstr);
397     }
398
399   return err;
400 }
401
402
403 static gpgme_error_t
404 g13_assuan_simple_command (assuan_context_t ctx, char *cmd,
405                              engine_status_handler_t status_fnc,
406                              void *status_fnc_value)
407 {
408   gpg_error_t err;
409   char *line;
410   size_t linelen;
411
412   err = assuan_write_line (ctx, cmd);
413   if (err)
414     return err;
415
416   do
417     {
418       err = assuan_read_line (ctx, &line, &linelen);
419       if (err)
420         return err;
421
422       if (*line == '#' || !linelen)
423         continue;
424
425       if (linelen >= 2
426           && line[0] == 'O' && line[1] == 'K'
427           && (line[2] == '\0' || line[2] == ' '))
428         return 0;
429       else if (linelen >= 4
430           && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
431           && line[3] == ' ')
432         err = atoi (&line[4]);
433       else if (linelen >= 2
434                && line[0] == 'S' && line[1] == ' ')
435         {
436           char *rest;
437
438           rest = strchr (line + 2, ' ');
439           if (!rest)
440             rest = line + linelen; /* set to an empty string */
441           else
442             *(rest++) = 0;
443
444           /* Nothing to do with status lines.  */
445         }
446       else
447         err = gpg_error (GPG_ERR_GENERAL);
448     }
449   while (!err);
450
451   return err;
452 }
453
454
455 static gpgme_error_t
456 status_handler (void *opaque, int fd)
457 {
458   struct io_cb_data *data = (struct io_cb_data *) opaque;
459   engine_g13_t g13 = (engine_g13_t) data->handler_value;
460   gpgme_error_t err = 0;
461   char *line;
462   size_t linelen;
463
464   do
465     {
466       err = assuan_read_line (g13->assuan_ctx, &line, &linelen);
467       if (err)
468         {
469           /* Try our best to terminate the connection friendly.  */
470           /*      assuan_write_line (g13->assuan_ctx, "BYE"); */
471           TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13,
472                   "fd 0x%x: error reading assuan line: %s",
473                   fd, gpg_strerror (err));
474         }
475       else if (linelen >= 3
476                && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
477                && (line[3] == '\0' || line[3] == ' '))
478         {
479           if (line[3] == ' ')
480             err = atoi (&line[4]);
481           if (! err)
482             err = gpg_error (GPG_ERR_GENERAL);
483           TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13,
484                   "fd 0x%x: ERR line: %s",
485                   fd, err ? gpg_strerror (err) : "ok");
486           
487           /* In g13, command execution errors are not fatal, as we use
488              a session based protocol.  */
489           if (g13->result_cb)
490             err = g13->result_cb (g13->result_cb_value, err);
491           else
492             err = 0;
493           if (!err)
494             {
495               _gpgme_io_close (g13->status_cb.fd);
496               return 0;
497             }
498         }
499       else if (linelen >= 2
500                && line[0] == 'O' && line[1] == 'K'
501                && (line[2] == '\0' || line[2] == ' '))
502         {
503           TRACE1 (DEBUG_CTX, "gpgme:status_handler", g13,
504                   "fd 0x%x: OK line", fd);
505           if (g13->result_cb)
506             err = g13->result_cb (g13->result_cb_value, 0);
507           else
508             err = 0;
509           if (!err)
510             {
511               _gpgme_io_close (g13->status_cb.fd);
512               return 0;
513             }
514         }
515       else if (linelen > 2
516                && line[0] == 'D' && line[1] == ' ')
517         {
518           /* We are using the colon handler even for plain inline data
519              - strange name for that function but for historic reasons
520              we keep it.  */
521           /* FIXME We can't use this for binary data because we
522              assume this is a string.  For the current usage of colon
523              output it is correct.  */
524           char *src = line + 2;
525           char *end = line + linelen;
526           char *dst = src;
527
528           linelen = 0;
529           while (src < end)
530             {
531               if (*src == '%' && src + 2 < end)
532                 {
533                   /* Handle escaped characters.  */
534                   ++src;
535                   *dst++ = _gpgme_hextobyte (src);
536                   src += 2;
537                 }
538               else
539                 *dst++ = *src++;
540
541               linelen++;
542             }
543
544           src = line + 2;
545           if (linelen && g13->user.data_cb)
546             err = g13->user.data_cb (g13->user.data_cb_value,
547                                        src, linelen);
548           else
549             err = 0;
550
551           TRACE2 (DEBUG_CTX, "gpgme:g13_status_handler", g13,
552                   "fd 0x%x: D inlinedata; status from cb: %s",
553                   fd, (g13->user.data_cb ?
554                        (err? gpg_strerror (err):"ok"):"no callback"));
555
556         }
557       else if (linelen > 2
558                && line[0] == 'S' && line[1] == ' ')
559         {
560           char *src;
561           char *args;
562
563           src = line + 2;
564           while (*src == ' ')
565             src++;
566           
567           args = strchr (line + 2, ' ');
568           if (!args)
569             args = line + linelen; /* set to an empty string */
570           else
571             *(args++) = 0;
572
573           while (*args == ' ')
574             args++;
575
576           if (g13->user.status_cb)
577             err = g13->user.status_cb (g13->user.status_cb_value,
578                                        src, args);
579           else
580             err = 0;
581
582           TRACE3 (DEBUG_CTX, "gpgme:g13_status_handler", g13,
583                   "fd 0x%x: S line (%s) - status from cb: %s",
584                   fd, line+2, (g13->user.status_cb ?
585                                (err? gpg_strerror (err):"ok"):"no callback"));
586         }
587       else if (linelen >= 7
588                && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
589                && line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
590                && line[6] == 'E' 
591                && (line[7] == '\0' || line[7] == ' '))
592         {
593           char *src;
594           char *args;
595           
596           for (src=line+7; *src == ' '; src++)
597             ;
598
599           args = strchr (src, ' ');
600           if (!args)
601             args = line + linelen; /* Let it point to an empty string.  */
602           else
603             *(args++) = 0;
604
605           while (*args == ' ')
606             args++;
607
608           err = default_inq_cb (g13, src, args);
609           if (!err) 
610             {
611               /* Flush and send END.  */
612               err = assuan_send_data (g13->assuan_ctx, NULL, 0);
613             }
614           else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED)
615             {
616               /* Flush and send CANcel.  */
617               err = assuan_send_data (g13->assuan_ctx, NULL, 1);
618             }
619           assuan_write_line (g13->assuan_ctx, "END");
620         }
621     }
622   while (!err && assuan_pending_line (g13->assuan_ctx));
623   
624   return err;
625 }
626   
627
628 static gpgme_error_t
629 add_io_cb (engine_g13_t g13, iocb_data_t *iocbd, gpgme_io_cb_t handler)
630 {
631   gpgme_error_t err;
632
633   TRACE_BEG2 (DEBUG_ENGINE, "engine-g13:add_io_cb", g13,
634               "fd %d, dir %d", iocbd->fd, iocbd->dir);
635   err = (*g13->io_cbs.add) (g13->io_cbs.add_priv,
636                               iocbd->fd, iocbd->dir,
637                               handler, iocbd->data, &iocbd->tag);
638   if (err)
639     return TRACE_ERR (err);
640   if (!iocbd->dir)
641     /* FIXME Kludge around poll() problem.  */
642     err = _gpgme_io_set_nonblocking (iocbd->fd);
643   return TRACE_ERR (err);
644 }
645
646
647 static gpgme_error_t
648 start (engine_g13_t g13, const char *command)
649 {
650   gpgme_error_t err;
651   int fdlist[5];
652   int nfds;
653
654   /* We need to know the fd used by assuan for reads.  We do this by
655      using the assumption that the first returned fd from
656      assuan_get_active_fds() is always this one.  */
657   nfds = assuan_get_active_fds (g13->assuan_ctx, 0 /* read fds */,
658                                 fdlist, DIM (fdlist));
659   if (nfds < 1)
660     return gpg_error (GPG_ERR_GENERAL); /* FIXME */
661
662   /* We "duplicate" the file descriptor, so we can close it here (we
663      can't close fdlist[0], as that is closed by libassuan, and
664      closing it here might cause libassuan to close some unrelated FD
665      later).  Alternatively, we could special case status_fd and
666      register/unregister it manually as needed, but this increases
667      code duplication and is more complicated as we can not use the
668      close notifications etc.  A third alternative would be to let
669      Assuan know that we closed the FD, but that complicates the
670      Assuan interface.  */
671
672   g13->status_cb.fd = _gpgme_io_dup (fdlist[0]);
673   if (g13->status_cb.fd < 0)
674     return gpg_error_from_syserror ();
675
676   if (_gpgme_io_set_close_notify (g13->status_cb.fd,
677                                   close_notify_handler, g13))
678     {
679       _gpgme_io_close (g13->status_cb.fd);
680       g13->status_cb.fd = -1;
681       return gpg_error (GPG_ERR_GENERAL);
682     }
683
684   err = add_io_cb (g13, &g13->status_cb, status_handler);
685   if (!err)
686     err = assuan_write_line (g13->assuan_ctx, command);
687
688   if (!err)
689     g13_io_event (g13, GPGME_EVENT_START, NULL);
690
691   return err;
692 }
693
694
695 #if USE_DESCRIPTOR_PASSING
696 static gpgme_error_t
697 g13_reset (void *engine)
698 {
699   engine_g13_t g13 = engine;
700
701   /* We must send a reset because we need to reset the list of
702      signers.  Note that RESET does not reset OPTION commands. */
703   return g13_assuan_simple_command (g13->assuan_ctx, "RESET", NULL, NULL);
704 }
705 #endif
706
707
708 static gpgme_error_t
709 g13_transact (void *engine,
710                 const char *command,
711                 engine_assuan_result_cb_t result_cb,
712                 void *result_cb_value,
713                 gpgme_assuan_data_cb_t data_cb,
714                 void *data_cb_value,
715                 gpgme_assuan_inquire_cb_t inq_cb,
716                 void *inq_cb_value,
717                 gpgme_assuan_status_cb_t status_cb,
718                 void *status_cb_value)
719 {
720   engine_g13_t g13 = engine;
721   gpgme_error_t err;
722
723   if (!g13 || !command || !*command)
724     return gpg_error (GPG_ERR_INV_VALUE);
725
726   g13->result_cb = result_cb;
727   g13->result_cb_value = result_cb_value;
728   g13->user.data_cb = data_cb;
729   g13->user.data_cb_value = data_cb_value;
730   g13->user.inq_cb = inq_cb;
731   g13->user.inq_cb_value = inq_cb_value;
732   g13->user.status_cb = status_cb;
733   g13->user.status_cb_value = status_cb_value;
734
735   err = start (g13, command);
736   return err;
737 }
738
739
740
741 static void
742 g13_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
743 {
744   engine_g13_t g13 = engine;
745   g13->io_cbs = *io_cbs;
746 }
747
748
749 static void
750 g13_io_event (void *engine, gpgme_event_io_t type, void *type_data)
751 {
752   engine_g13_t g13 = engine;
753
754   TRACE3 (DEBUG_ENGINE, "gpgme:g13_io_event", g13,
755           "event %p, type %d, type_data %p",
756           g13->io_cbs.event, type, type_data);
757   if (g13->io_cbs.event)
758     (*g13->io_cbs.event) (g13->io_cbs.event_priv, type, type_data);
759 }
760
761
762 struct engine_ops _gpgme_engine_ops_g13 =
763   {
764     /* Static functions.  */
765     _gpgme_get_g13_path,
766     NULL,
767     g13_get_version,
768     g13_get_req_version,
769     g13_new,
770
771     /* Member functions.  */
772     g13_release,
773 #if USE_DESCRIPTOR_PASSING
774     g13_reset,
775 #else
776     NULL,                       /* reset */
777 #endif
778     NULL,               /* set_status_handler */
779     NULL,               /* set_command_handler */
780     NULL,               /* set_colon_line_handler */
781     g13_set_locale,
782     NULL,               /* decrypt */
783     NULL,               /* delete */
784     NULL,               /* edit */
785     NULL,               /* encrypt */
786     NULL,               /* encrypt_sign */
787     NULL,               /* export */
788     NULL,               /* export_ext */
789     NULL,               /* genkey */
790     NULL,               /* import */
791     NULL,               /* keylist */
792     NULL,               /* keylist_ext */
793     NULL,               /* sign */
794     NULL,               /* trustlist */
795     NULL,               /* verify */
796     NULL,               /* getauditlog */
797     g13_transact,
798     NULL,               /* conf_load */
799     NULL,               /* conf_save */
800     g13_set_io_cbs,
801     g13_io_event,
802     g13_cancel,
803     g13_cancel_op,
804   };