chiark / gitweb /
Replace use of variable-length-arrays.
[gnupg2.git] / g10 / server.c
1 /* server.c - server mode for gpg
2  * Copyright (C) 2006, 2008  Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <https://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <errno.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <stdarg.h>
26 #include <ctype.h>
27 #include <unistd.h>
28
29
30 #include "gpg.h"
31 #include <assuan.h>
32 #include "util.h"
33 #include "i18n.h"
34 #include "options.h"
35 #include "../common/server-help.h"
36 #include "../common/sysutils.h"
37 #include "status.h"
38
39
40 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
41
42
43 /* Data used to associate an Assuan context with local server data.  */
44 struct server_local_s
45 {
46   /* Our current Assuan context. */
47   assuan_context_t assuan_ctx;
48   /* File descriptor as set by the MESSAGE command. */
49   gnupg_fd_t message_fd;
50
51   /* List of prepared recipients.  */
52   pk_list_t recplist;
53
54   /* Set if pinentry notifications should be passed back to the
55      client. */
56   int allow_pinentry_notify;
57 };
58
59
60 \f
61 /* Helper to close the message fd if it is open. */
62 static void
63 close_message_fd (ctrl_t ctrl)
64 {
65   if (ctrl->server_local->message_fd != GNUPG_INVALID_FD)
66     {
67       assuan_sock_close (ctrl->server_local->message_fd);
68       ctrl->server_local->message_fd = GNUPG_INVALID_FD;
69     }
70 }
71
72 \f
73 /* Called by libassuan for Assuan options.  See the Assuan manual for
74    details. */
75 static gpg_error_t
76 option_handler (assuan_context_t ctx, const char *key, const char *value)
77 {
78   ctrl_t ctrl = assuan_get_pointer (ctx);
79
80   (void)value;
81
82   /* Fixme: Implement the tty and locale args. */
83   if (!strcmp (key, "display"))
84     {
85     }
86   else if (!strcmp (key, "ttyname"))
87     {
88     }
89   else if (!strcmp (key, "ttytype"))
90     {
91     }
92   else if (!strcmp (key, "lc-ctype"))
93     {
94     }
95   else if (!strcmp (key, "lc-messages"))
96     {
97     }
98   else if (!strcmp (key, "xauthority"))
99     {
100     }
101   else if (!strcmp (key, "pinentry_user_data"))
102     {
103     }
104   else if (!strcmp (key, "list-mode"))
105     {
106       /* This is for now a dummy option. */
107     }
108   else if (!strcmp (key, "allow-pinentry-notify"))
109     {
110       ctrl->server_local->allow_pinentry_notify = 1;
111     }
112   else
113     return gpg_error (GPG_ERR_UNKNOWN_OPTION);
114
115   return 0;
116 }
117
118
119 /* Called by libassuan for RESET commands. */
120 static gpg_error_t
121 reset_notify (assuan_context_t ctx, char *line)
122 {
123   ctrl_t ctrl = assuan_get_pointer (ctx);
124
125   (void)line;
126
127   release_pk_list (ctrl->server_local->recplist);
128   ctrl->server_local->recplist = NULL;
129
130   close_message_fd (ctrl);
131   assuan_close_input_fd (ctx);
132   assuan_close_output_fd (ctx);
133   return 0;
134 }
135
136
137 /* Called by libassuan for INPUT commands. */
138 static gpg_error_t
139 input_notify (assuan_context_t ctx, char *line)
140 {
141 /*   ctrl_t ctrl = assuan_get_pointer (ctx); */
142
143   (void)ctx;
144
145   if (strstr (line, "--armor"))
146     ; /* FIXME */
147   else if (strstr (line, "--base64"))
148     ; /* FIXME */
149   else if (strstr (line, "--binary"))
150     ;
151   else
152     {
153       /* FIXME (autodetect encoding) */
154     }
155   return 0;
156 }
157
158
159 /* Called by libassuan for OUTPUT commands. */
160 static gpg_error_t
161 output_notify (assuan_context_t ctx, char *line)
162 {
163 /*   ctrl_t ctrl = assuan_get_pointer (ctx); */
164
165   (void)ctx;
166
167   if (strstr (line, "--armor"))
168     ; /* FIXME */
169   else if (strstr (line, "--base64"))
170     {
171       /* FIXME */
172     }
173   return 0;
174 }
175
176
177
178 \f
179 /*  RECIPIENT [--hidden] <userID>
180     RECIPIENT [--hidden] --file <filename>
181
182    Set the recipient for the encryption.  <userID> should be the
183    internal representation of the key; the server may accept any other
184    way of specification.  If this is a valid and trusted recipient the
185    server does respond with OK, otherwise the return is an ERR with
186    the reason why the recipient can't be used, the encryption will
187    then not be done for this recipient.  If the policy is not to
188    encrypt at all if not all recipients are valid, the client has to
189    take care of this.  All RECIPIENT commands are cumulative until a
190    RESET or an successful ENCRYPT command.  */
191 static gpg_error_t
192 cmd_recipient (assuan_context_t ctx, char *line)
193 {
194   ctrl_t ctrl = assuan_get_pointer (ctx);
195   gpg_error_t err;
196   int hidden, file;
197
198   hidden = has_option (line,"--hidden");
199   file = has_option (line,"--file");
200   line = skip_options (line);
201
202   /* FIXME: Expand groups
203   if (opt.grouplist)
204     remusr = expand_group (rcpts);
205   else
206     remusr = rcpts;
207   */
208
209   err = find_and_check_key (ctrl, line, PUBKEY_USAGE_ENC, hidden, file,
210                             &ctrl->server_local->recplist);
211
212   if (err)
213     log_error ("command '%s' failed: %s\n", "RECIPIENT", gpg_strerror (err));
214   return err;
215 }
216
217
218 \f
219 /*  SIGNER <userID>
220
221    Set the signer's keys for the signature creation.  <userID> should
222    be the internal representation of the key; the server may accept
223    any other way of specification.  If this is a valid and usable
224    signing key the server does respond with OK, otherwise it returns
225    an ERR with the reason why the key can't be used, the signing will
226    then not be done for this key.  If the policy is not to sign at all
227    if not all signer keys are valid, the client has to take care of
228    this.  All SIGNER commands are cumulative until a RESET but they
229    are *not* reset by an SIGN command because it can be expected that
230    set of signers are used for more than one sign operation.
231
232    Note that this command returns an INV_RECP status which is a bit
233    strange, but they are very similar.  */
234 static gpg_error_t
235 cmd_signer (assuan_context_t ctx, char *line)
236 {
237   (void)ctx;
238   (void)line;
239   return gpg_error (GPG_ERR_NOT_SUPPORTED);
240 }
241
242
243 \f
244 /*  ENCRYPT
245
246    Do the actual encryption process.  Takes the plaintext from the
247    INPUT command, writes the ciphertext to the file descriptor set
248    with the OUTPUT command, take the recipients from all the
249    recipients set so far with RECIPIENTS.
250
251    If this command fails the clients should try to delete all output
252    currently done or otherwise mark it as invalid.  GPG does ensure
253    that there won't be any security problem with leftover data on the
254    output in this case.
255
256    In most cases this command won't fail because most necessary checks
257    have been done while setting the recipients.  However some checks
258    can only be done right here and thus error may occur anyway (for
259    example, no recipients at all).
260
261    The input, output and message pipes are closed after this
262    command.  */
263 static gpg_error_t
264 cmd_encrypt (assuan_context_t ctx, char *line)
265 {
266   ctrl_t ctrl = assuan_get_pointer (ctx);
267   gpg_error_t err;
268   int inp_fd, out_fd;
269
270   (void)line; /* LINE is not used.  */
271
272   if ( !ctrl->server_local->recplist )
273     {
274       write_status_text (STATUS_NO_RECP, "0");
275       err = gpg_error (GPG_ERR_NO_USER_ID);
276       goto leave;
277     }
278
279   inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
280   if (inp_fd == -1)
281     {
282       err = set_error (GPG_ERR_ASS_NO_INPUT, NULL);
283       goto leave;
284     }
285   out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
286   if (out_fd == -1)
287     {
288       err = set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
289       goto leave;
290     }
291
292
293   /* FIXME: GPGSM does this here: Add all encrypt-to marked recipients
294      from the default list. */
295
296   /* fixme: err = ctrl->audit? 0 : start_audit_session (ctrl);*/
297
298   err = encrypt_crypt (ctrl, inp_fd, NULL, NULL, 0,
299                        ctrl->server_local->recplist,
300                        out_fd);
301
302  leave:
303   /* Release the recipient list on success.  */
304   if (!err)
305     {
306       release_pk_list (ctrl->server_local->recplist);
307       ctrl->server_local->recplist = NULL;
308     }
309
310   /* Close and reset the fds. */
311   close_message_fd (ctrl);
312   assuan_close_input_fd (ctx);
313   assuan_close_output_fd (ctx);
314
315   if (err)
316     log_error ("command '%s' failed: %s\n", "ENCRYPT", gpg_strerror (err));
317   return err;
318 }
319
320
321 \f
322 /*  DECRYPT
323
324     This performs the decrypt operation.  */
325 static gpg_error_t
326 cmd_decrypt (assuan_context_t ctx, char *line)
327 {
328   ctrl_t ctrl = assuan_get_pointer (ctx);
329   gpg_error_t err;
330   int inp_fd, out_fd;
331
332   (void)line; /* LINE is not used.  */
333
334   inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
335   if (inp_fd == -1)
336     return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
337   out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
338   if (out_fd == -1)
339     return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
340
341   glo_ctrl.lasterr = 0;
342   err = decrypt_message_fd (ctrl, inp_fd, out_fd);
343   if (!err)
344     err = glo_ctrl.lasterr;
345
346   /* Close and reset the fds. */
347   close_message_fd (ctrl);
348   assuan_close_input_fd (ctx);
349   assuan_close_output_fd (ctx);
350
351   if (err)
352     log_error ("command '%s' failed: %s\n", "DECRYPT", gpg_strerror (err));
353   return err;
354 }
355
356
357 \f
358 /*  VERIFY
359
360    This does a verify operation on the message send to the input-FD.
361    The result is written out using status lines.  If an output FD was
362    given, the signed text will be written to that.
363
364    If the signature is a detached one, the server will inquire about
365    the signed material and the client must provide it.
366  */
367 static gpg_error_t
368 cmd_verify (assuan_context_t ctx, char *line)
369 {
370   int rc;
371 #ifdef HAVE_W32_SYSTEM
372   (void)ctx;
373   (void)line;
374   rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
375 #else
376   ctrl_t ctrl = assuan_get_pointer (ctx);
377   gnupg_fd_t fd = assuan_get_input_fd (ctx);
378   gnupg_fd_t out_fd = assuan_get_output_fd (ctx);
379   estream_t out_fp = NULL;
380
381   /* FIXME: Revamp this code it is nearly to 3 years old and was only
382      intended as a quick test.  */
383
384   (void)line;
385
386   if (fd == GNUPG_INVALID_FD)
387     return gpg_error (GPG_ERR_ASS_NO_INPUT);
388
389   if (out_fd != GNUPG_INVALID_FD)
390     {
391       es_syshd_t syshd;
392
393 #ifdef HAVE_W32_SYSTEM
394       syshd.type = ES_SYSHD_HANDLE;
395       syshd.u.handle = out_fd;
396 #else
397       syshd.type = ES_SYSHD_FD;
398       syshd.u.fd = out_fd;
399 #endif
400       out_fp = es_sysopen_nc (&syshd, "w");
401       if (!out_fp)
402         return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
403     }
404
405   log_debug ("WARNING: The server mode is WORK "
406              "IN PROGRESS and not ready for use\n");
407
408   rc = gpg_verify (ctrl, fd, ctrl->server_local->message_fd, out_fp);
409
410   es_fclose (out_fp);
411   close_message_fd (ctrl);
412   assuan_close_input_fd (ctx);
413   assuan_close_output_fd (ctx);
414 #endif
415
416   if (rc)
417     log_error ("command '%s' failed: %s\n", "VERIFY", gpg_strerror (rc));
418   return rc;
419 }
420
421
422 \f
423 /*  SIGN [--detached]
424
425    Sign the data set with the INPUT command and write it to the sink
426    set by OUTPUT.  With "--detached" specified, a detached signature
427    is created.  */
428 static gpg_error_t
429 cmd_sign (assuan_context_t ctx, char *line)
430 {
431   (void)ctx;
432   (void)line;
433   return gpg_error (GPG_ERR_NOT_SUPPORTED);
434 }
435
436
437 \f
438 /*  IMPORT
439
440   Import keys as read from the input-fd, return status message for
441   each imported one.  The import checks the validity of the key.  */
442 static gpg_error_t
443 cmd_import (assuan_context_t ctx, char *line)
444 {
445   (void)ctx;
446   (void)line;
447   return gpg_error (GPG_ERR_NOT_SUPPORTED);
448 }
449
450
451 \f
452 /*  EXPORT [--data [--armor|--base64]] [--] pattern
453
454    Similar to the --export command line command, this command exports
455    public keys matching PATTERN.  The output is send to the output fd
456    unless the --data option has been used in which case the output
457    gets send inline using regular data lines.  The options "--armor"
458    and "--base" ospecify an output format if "--data" has been used.
459    Recall that in general the output format is set with the OUTPUT
460    command.
461  */
462 static gpg_error_t
463 cmd_export (assuan_context_t ctx, char *line)
464 {
465   (void)ctx;
466   (void)line;
467   return gpg_error (GPG_ERR_NOT_SUPPORTED);
468 }
469
470
471 \f
472 /*  DELKEYS
473
474     Fixme
475 */
476 static gpg_error_t
477 cmd_delkeys (assuan_context_t ctx, char *line)
478 {
479   (void)ctx;
480   (void)line;
481   return gpg_error (GPG_ERR_NOT_SUPPORTED);
482 }
483
484
485 \f
486 /*  MESSAGE FD[=<n>]
487
488    Set the file descriptor to read a message which is used with
489    detached signatures.  */
490 static gpg_error_t
491 cmd_message (assuan_context_t ctx, char *line)
492 {
493   int rc;
494   gnupg_fd_t fd;
495   ctrl_t ctrl = assuan_get_pointer (ctx);
496
497   rc = assuan_command_parse_fd (ctx, line, &fd);
498   if (rc)
499     return rc;
500   if (fd == GNUPG_INVALID_FD)
501     return gpg_error (GPG_ERR_ASS_NO_INPUT);
502   ctrl->server_local->message_fd = fd;
503   return 0;
504 }
505
506
507 \f
508 /* LISTKEYS [<patterns>]
509    LISTSECRETKEYS [<patterns>]
510
511    fixme
512 */
513 static gpg_error_t
514 do_listkeys (assuan_context_t ctx, char *line, int mode)
515 {
516   (void)ctx;
517   (void)line;
518   (void)mode;
519
520   return gpg_error (GPG_ERR_NOT_SUPPORTED);
521 }
522
523
524 static gpg_error_t
525 cmd_listkeys (assuan_context_t ctx, char *line)
526 {
527   return do_listkeys (ctx, line, 3);
528 }
529
530
531 static gpg_error_t
532 cmd_listsecretkeys (assuan_context_t ctx, char *line)
533 {
534   return do_listkeys (ctx, line, 2);
535 }
536
537
538 \f
539 /* GENKEY
540
541    Read the parameters in native format from the input fd and create a
542    new OpenPGP key.
543  */
544 static gpg_error_t
545 cmd_genkey (assuan_context_t ctx, char *line)
546 {
547   (void)ctx;
548   (void)line;
549   return gpg_error (GPG_ERR_NOT_SUPPORTED);
550 }
551
552
553 /* GETINFO <what>
554
555    Multipurpose function to return a variety of information.
556    Supported values for WHAT are:
557
558      version     - Return the version of the program.
559      pid         - Return the process id of the server.
560
561  */
562 static gpg_error_t
563 cmd_getinfo (assuan_context_t ctx, char *line)
564 {
565   int rc;
566
567   if (!strcmp (line, "version"))
568     {
569       const char *s = VERSION;
570       rc = assuan_send_data (ctx, s, strlen (s));
571     }
572   else if (!strcmp (line, "pid"))
573     {
574       char numbuf[50];
575
576       snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
577       rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
578     }
579   else
580     rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
581   return rc;
582 }
583
584 static const char hlp_passwd[] =
585   "PASSWD <userID>\n"
586   "\n"
587   "Change the passphrase of the secret key for USERID.";
588 static gpg_error_t
589 cmd_passwd (assuan_context_t ctx, char *line)
590 {
591   /* ctrl_t ctrl = assuan_get_pointer (ctx); */
592   gpg_error_t err;
593
594   (void)ctx;
595   (void)line;
596   /* line = skip_options (line); */
597
598   err = gpg_error (GPG_ERR_NOT_SUPPORTED);
599
600   return err;
601 }
602
603
604
605 \f
606 /* Helper to register our commands with libassuan. */
607 static int
608 register_commands (assuan_context_t ctx)
609 {
610   static struct
611   {
612     const char *name;
613     assuan_handler_t handler;
614     const char * const help;
615   } table[] = {
616     { "RECIPIENT",     cmd_recipient },
617     { "SIGNER",        cmd_signer    },
618     { "ENCRYPT",       cmd_encrypt   },
619     { "DECRYPT",       cmd_decrypt   },
620     { "VERIFY",        cmd_verify    },
621     { "SIGN",          cmd_sign      },
622     { "IMPORT",        cmd_import    },
623     { "EXPORT",        cmd_export    },
624     { "INPUT",         NULL          },
625     { "OUTPUT",        NULL          },
626     { "MESSAGE",       cmd_message   },
627     { "LISTKEYS",      cmd_listkeys  },
628     { "LISTSECRETKEYS",cmd_listsecretkeys },
629     { "GENKEY",        cmd_genkey    },
630     { "DELKEYS",       cmd_delkeys   },
631     { "GETINFO",       cmd_getinfo   },
632     { "PASSWD",        cmd_passwd,  hlp_passwd},
633     { NULL }
634   };
635   int i, rc;
636
637   for (i=0; table[i].name; i++)
638     {
639       rc = assuan_register_command (ctx, table[i].name,
640                                     table[i].handler, table[i].help);
641       if (rc)
642         return rc;
643     }
644   return 0;
645 }
646
647
648
649 \f
650 /* Startup the server.  CTRL must have been allocated by the caller
651    and set to the default values. */
652 int
653 gpg_server (ctrl_t ctrl)
654 {
655   int rc;
656 #ifndef HAVE_W32_SYSTEM
657   int filedes[2];
658 #endif
659   assuan_context_t ctx = NULL;
660   static const char hello[] = ("GNU Privacy Guard's OpenPGP server "
661                                VERSION " ready");
662
663   /* We use a pipe based server so that we can work from scripts.
664      assuan_init_pipe_server will automagically detect when we are
665      called with a socketpair and ignore FILEDES in this case.  */
666 #ifndef HAVE_W32_SYSTEM
667   filedes[0] = assuan_fdopen (0);
668   filedes[1] = assuan_fdopen (1);
669 #endif
670   rc = assuan_new (&ctx);
671   if (rc)
672     {
673       log_error ("failed to allocate the assuan context: %s\n",
674                  gpg_strerror (rc));
675       goto leave;
676     }
677
678 #ifdef HAVE_W32_SYSTEM
679   rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
680 #else
681   rc = assuan_init_pipe_server (ctx, filedes);
682 #endif
683   if (rc)
684     {
685       log_error ("failed to initialize the server: %s\n", gpg_strerror (rc));
686       goto leave;
687     }
688
689   rc = register_commands (ctx);
690   if (rc)
691     {
692       log_error ("failed to the register commands with Assuan: %s\n",
693                  gpg_strerror(rc));
694       goto leave;
695     }
696
697   assuan_set_pointer (ctx, ctrl);
698   if (opt.verbose || opt.debug)
699     {
700       char *tmp;
701
702       tmp = xtryasprintf ("Home: %s\n"
703                           "Config: %s\n"
704                           "%s",
705                           gnupg_homedir (),
706                           "fixme: need config filename",
707                           hello);
708       if (tmp)
709         {
710           assuan_set_hello_line (ctx, tmp);
711           xfree (tmp);
712         }
713     }
714   else
715     assuan_set_hello_line (ctx, hello);
716   assuan_register_reset_notify (ctx, reset_notify);
717   assuan_register_input_notify (ctx, input_notify);
718   assuan_register_output_notify (ctx, output_notify);
719   assuan_register_option_handler (ctx, option_handler);
720
721   ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
722   if (!ctrl->server_local)
723     {
724       rc = gpg_error_from_syserror ();
725       goto leave;
726     }
727   ctrl->server_local->assuan_ctx = ctx;
728   ctrl->server_local->message_fd = GNUPG_INVALID_FD;
729
730   for (;;)
731     {
732       rc = assuan_accept (ctx);
733       if (rc == -1)
734         {
735           rc = 0;
736           break;
737         }
738       else if (rc)
739         {
740           log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
741           break;
742         }
743
744       rc = assuan_process (ctx);
745       if (rc)
746         {
747           log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
748           continue;
749         }
750     }
751
752  leave:
753   if (ctrl->server_local)
754     {
755       release_pk_list (ctrl->server_local->recplist);
756
757       xfree (ctrl->server_local);
758       ctrl->server_local = NULL;
759     }
760   assuan_release (ctx);
761   return rc;
762 }
763
764
765 /* Helper to notify the client about Pinentry events.  Because that
766    might disturb some older clients, this is only done when enabled
767    via an option.  If it is not enabled we tell Windows to allow
768    setting the foreground window right here.  Returns an gpg error
769    code. */
770 gpg_error_t
771 gpg_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line)
772 {
773   if (opt.verbose)
774     {
775       char *linecopy = xtrystrdup (line);
776       char *fields[4];
777
778       if (linecopy
779           && split_fields (linecopy, fields, DIM (fields)) >= 4
780           && !strcmp (fields[0], "PINENTRY_LAUNCHED"))
781         log_info (_("pinentry launched (pid %s, flavor %s, version %s)\n"),
782                   fields[1], fields[2], fields[3]);
783
784       xfree (linecopy);
785     }
786
787   if (!ctrl || !ctrl->server_local
788       || !ctrl->server_local->allow_pinentry_notify)
789     {
790       gnupg_allow_set_foregound_window ((pid_t)strtoul (line+17, NULL, 10));
791       /* Client might be interested in that event - send as status line.  */
792       if (!strncmp (line, "PINENTRY_LAUNCHED", 17)
793           && (line[17]==' '||!line[17]))
794         {
795           for (line += 17; *line && spacep (line); line++)
796             ;
797           write_status_text (STATUS_PINENTRY_LAUNCHED, line);
798         }
799       return 0;
800     }
801   return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
802 }