1 /* assuan-handler.c - dispatch commands
2 * Copyright (C) 2001, 2002, 2003, 2007 Free Software Foundation, Inc.
4 * This file is part of Assuan.
6 * Assuan is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as
8 * published by the Free Software Foundation; either version 2.1 of
9 * the License, or (at your option) any later version.
11 * Assuan is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this program; if not, see <http://www.gnu.org/licenses/>.
26 #include "assuan-defs.h"
30 #define spacep(p) (*(p) == ' ' || *(p) == '\t')
31 #define digitp(a) ((a) >= '0' && (a) <= '9')
33 static int my_strcasecmp (const char *a
, const char *b
);
36 #define PROCESS_DONE(ctx, rc) \
37 ((ctx)->in_process_next ? assuan_process_done ((ctx), (rc)) : (rc))
40 dummy_handler (assuan_context_t ctx
, char *line
)
43 PROCESS_DONE (ctx
, set_error (ctx
, Server_Fault
, "no handler registered"));
48 std_handler_nop (assuan_context_t ctx
, char *line
)
50 return PROCESS_DONE (ctx
, 0); /* okay */
54 std_handler_cancel (assuan_context_t ctx
, char *line
)
56 if (ctx
->cancel_notify_fnc
)
57 ctx
->cancel_notify_fnc (ctx
);
58 return PROCESS_DONE (ctx
, set_error (ctx
, Not_Implemented
, NULL
));
62 std_handler_option (assuan_context_t ctx
, char *line
)
64 char *key
, *value
, *p
;
66 for (key
=line
; spacep (key
); key
++)
70 PROCESS_DONE (ctx
, set_error (ctx
, Syntax_Error
, "argument required"));
73 PROCESS_DONE (ctx
, set_error (ctx
, Syntax_Error
,
74 "no option name given"));
75 for (value
=key
; *value
&& !spacep (value
) && *value
!= '='; value
++)
80 *value
++ = 0; /* terminate key */
81 for (; spacep (value
); value
++)
85 *value
++ = 0; /* terminate key */
86 for (; spacep (value
); value
++)
90 PROCESS_DONE (ctx
, set_error (ctx
, Syntax_Error
,
91 "option argument expected"));
95 for (p
= value
+ strlen(value
) - 1; p
> value
&& spacep (p
); p
--)
98 *++p
= 0; /* strip trailing spaces */
102 if (*key
== '-' && key
[1] == '-' && key
[2])
103 key
+= 2; /* the double dashes are optional */
105 return PROCESS_DONE (ctx
,
106 set_error (ctx
, Syntax_Error
,
107 "option should not begin with one dash"));
109 if (ctx
->option_handler_fnc
)
110 return PROCESS_DONE (ctx
, ctx
->option_handler_fnc (ctx
, key
, value
));
111 return PROCESS_DONE (ctx
, 0);
115 std_handler_bye (assuan_context_t ctx
, char *line
)
117 if (ctx
->bye_notify_fnc
)
118 ctx
->bye_notify_fnc (ctx
);
119 assuan_close_input_fd (ctx
);
120 assuan_close_output_fd (ctx
);
121 return PROCESS_DONE (ctx
, _assuan_error (-1)); /* pretty simple :-) */
125 std_handler_auth (assuan_context_t ctx
, char *line
)
127 return PROCESS_DONE (ctx
, set_error (ctx
, Not_Implemented
, NULL
));
131 std_handler_reset (assuan_context_t ctx
, char *line
)
133 if (ctx
->reset_notify_fnc
)
134 ctx
->reset_notify_fnc (ctx
);
135 assuan_close_input_fd (ctx
);
136 assuan_close_output_fd (ctx
);
137 _assuan_uds_close_fds (ctx
);
138 return PROCESS_DONE (ctx
, 0);
142 std_handler_help (assuan_context_t ctx
, char *line
)
145 char buf
[ASSUAN_LINELENGTH
];
147 for (i
= 0; i
< ctx
->cmdtbl_used
; i
++)
149 snprintf (buf
, sizeof (buf
), "# %s", ctx
->cmdtbl
[i
].name
);
150 buf
[ASSUAN_LINELENGTH
- 1] = '\0';
151 assuan_write_line (ctx
, buf
);
154 return PROCESS_DONE (ctx
, 0);
159 std_handler_end (assuan_context_t ctx
, char *line
)
161 return PROCESS_DONE (ctx
, set_error (ctx
, Not_Implemented
, NULL
));
166 assuan_command_parse_fd (assuan_context_t ctx
, char *line
, assuan_fd_t
*rfd
)
170 if ((strncmp (line
, "FD", 2) && strncmp (line
, "fd", 2))
171 || (line
[2] != '=' && line
[2] != '\0' && !spacep(&line
[2])))
172 return set_error (ctx
, Syntax_Error
, "FD[=<n>] expected");
178 return set_error (ctx
, Syntax_Error
, "number required");
179 #ifdef HAVE_W32_SYSTEM
180 /* Fixme: For a W32/64bit system we will need to change the cast
181 and the conversion fucntion. */
182 *rfd
= (void*)strtoul (line
, &endp
, 10);
184 *rfd
= strtoul (line
, &endp
, 10);
186 /* Remove that argument so that a notify handler won't see it. */
187 memset (line
, ' ', endp
? (endp
-line
):strlen(line
));
189 if (*rfd
== ctx
->inbound
.fd
)
190 return set_error (ctx
, Parameter_Conflict
, "fd same as inbound fd");
191 if (*rfd
== ctx
->outbound
.fd
)
192 return set_error (ctx
, Parameter_Conflict
, "fd same as outbound fd");
196 /* Our peer has sent the file descriptor. */
197 return assuan_receivefd (ctx
, rfd
);
201 /* Format is INPUT FD=<n> */
203 std_handler_input (assuan_context_t ctx
, char *line
)
208 rc
= assuan_command_parse_fd (ctx
, line
, &fd
);
210 return PROCESS_DONE (ctx
, rc
);
212 if (ctx
->input_notify_fnc
)
213 ctx
->input_notify_fnc (ctx
, line
);
214 return PROCESS_DONE (ctx
, 0);
217 /* Format is OUTPUT FD=<n> */
219 std_handler_output (assuan_context_t ctx
, char *line
)
224 rc
= assuan_command_parse_fd (ctx
, line
, &fd
);
226 return PROCESS_DONE (ctx
, rc
);
228 if (ctx
->output_notify_fnc
)
229 ctx
->output_notify_fnc (ctx
, line
);
230 return PROCESS_DONE (ctx
, 0);
237 /* This is a table with the standard commands and handler for them.
238 The table is used to initialize a new context and associate strings
239 with default handlers */
242 int (*handler
)(assuan_context_t
, char *line
);
243 int always
; /* always initialize this command */
244 } std_cmd_table
[] = {
245 { "NOP", std_handler_nop
, 1 },
246 { "CANCEL", std_handler_cancel
, 1 },
247 { "OPTION", std_handler_option
, 1 },
248 { "BYE", std_handler_bye
, 1 },
249 { "AUTH", std_handler_auth
, 1 },
250 { "RESET", std_handler_reset
, 1 },
251 { "END", std_handler_end
, 1 },
252 { "HELP", std_handler_help
, 1 },
254 { "INPUT", std_handler_input
, 0 },
255 { "OUTPUT", std_handler_output
, 0 },
256 { "OPTION", std_handler_option
, 1 },
262 * assuan_register_command:
263 * @ctx: the server context
264 * @cmd_name: A string with the command name
265 * @handler: The handler function to be called or NULL to use a default
268 * Register a handler to be used for a given command. Note that
269 * several default handlers are already regsitered with a new context.
270 * This function however allows to override them.
272 * Return value: 0 on success or an error code
275 assuan_register_command (assuan_context_t ctx
,
276 const char *cmd_name
,
277 int (*handler
)(assuan_context_t
, char *))
282 if (cmd_name
&& !*cmd_name
)
286 return _assuan_error (ASSUAN_Invalid_Value
);
289 { /* find a default handler. */
290 for (i
=0; (s
=std_cmd_table
[i
].name
) && strcmp (cmd_name
, s
); i
++)
293 { /* Try again but case insensitive. */
294 for (i
=0; (s
=std_cmd_table
[i
].name
)
295 && my_strcasecmp (cmd_name
, s
); i
++)
299 handler
= std_cmd_table
[i
].handler
;
301 handler
= dummy_handler
; /* Last resort is the dummy handler. */
306 ctx
->cmdtbl_size
= 50;
307 ctx
->cmdtbl
= xtrycalloc ( ctx
->cmdtbl_size
, sizeof *ctx
->cmdtbl
);
309 return _assuan_error (ASSUAN_Out_Of_Core
);
310 ctx
->cmdtbl_used
= 0;
312 else if (ctx
->cmdtbl_used
>= ctx
->cmdtbl_size
)
316 x
= xtryrealloc ( ctx
->cmdtbl
, (ctx
->cmdtbl_size
+10) * sizeof *x
);
318 return _assuan_error (ASSUAN_Out_Of_Core
);
320 ctx
->cmdtbl_size
+= 50;
323 ctx
->cmdtbl
[ctx
->cmdtbl_used
].name
= cmd_name
;
324 ctx
->cmdtbl
[ctx
->cmdtbl_used
].handler
= handler
;
330 assuan_register_post_cmd_notify (assuan_context_t ctx
,
331 void (*fnc
)(assuan_context_t
, int))
334 return _assuan_error (ASSUAN_Invalid_Value
);
335 ctx
->post_cmd_notify_fnc
= fnc
;
340 assuan_register_bye_notify (assuan_context_t ctx
,
341 void (*fnc
)(assuan_context_t
))
344 return _assuan_error (ASSUAN_Invalid_Value
);
345 ctx
->bye_notify_fnc
= fnc
;
350 assuan_register_reset_notify (assuan_context_t ctx
,
351 void (*fnc
)(assuan_context_t
))
354 return _assuan_error (ASSUAN_Invalid_Value
);
355 ctx
->reset_notify_fnc
= fnc
;
360 assuan_register_cancel_notify (assuan_context_t ctx
,
361 void (*fnc
)(assuan_context_t
))
364 return _assuan_error (ASSUAN_Invalid_Value
);
365 ctx
->cancel_notify_fnc
= fnc
;
370 assuan_register_option_handler (assuan_context_t ctx
,
371 int (*fnc
)(assuan_context_t
,
372 const char*, const char*))
375 return _assuan_error (ASSUAN_Invalid_Value
);
376 ctx
->option_handler_fnc
= fnc
;
381 assuan_register_input_notify (assuan_context_t ctx
,
382 void (*fnc
)(assuan_context_t
, const char *))
385 return _assuan_error (ASSUAN_Invalid_Value
);
386 ctx
->input_notify_fnc
= fnc
;
391 assuan_register_output_notify (assuan_context_t ctx
,
392 void (*fnc
)(assuan_context_t
, const char *))
395 return _assuan_error (ASSUAN_Invalid_Value
);
396 ctx
->output_notify_fnc
= fnc
;
401 /* Helper to register the standards commands */
403 _assuan_register_std_commands (assuan_context_t ctx
)
407 for (i
=0; std_cmd_table
[i
].name
; i
++)
409 if (std_cmd_table
[i
].always
)
411 rc
= assuan_register_command (ctx
, std_cmd_table
[i
].name
, NULL
);
421 /* Process the special data lines. The "D " has already been removed
422 from the line. As all handlers this function may modify the line. */
424 handle_data_line (assuan_context_t ctx
, char *line
, int linelen
)
426 return set_error (ctx
, Not_Implemented
, NULL
);
429 /* like ascii_strcasecmp but assume that B is already uppercase */
431 my_strcasecmp (const char *a
, const char *b
)
436 for (; *a
&& *b
; a
++, b
++)
438 if (((*a
>= 'a' && *a
<= 'z')? (*a
&~0x20):*a
) != *b
)
441 return *a
== *b
? 0 : (((*a
>= 'a' && *a
<= 'z')? (*a
&~0x20):*a
) - *b
);
445 /* Parse the line, break out the command, find it in the command
446 table, remove leading and white spaces from the arguments, call the
447 handler with the argument line and return the error. */
449 dispatch_command (assuan_context_t ctx
, char *line
, int linelen
)
455 /* Note that as this function is invoked by assuan_process_next as
456 well, we need to hide non-critical errors with PROCESS_DONE. */
458 if (*line
== 'D' && line
[1] == ' ') /* divert to special handler */
459 /* FIXME: Depending on the final implementation of
460 handle_data_line, this may be wrong here. For example, if a
461 user callback is invoked, and that callback is responsible for
462 calling assuan_process_done, then this is wrong. */
463 return PROCESS_DONE (ctx
, handle_data_line (ctx
, line
+2, linelen
-2));
465 for (p
=line
; *p
&& *p
!= ' ' && *p
!= '\t'; p
++)
469 (ctx
, set_error (ctx
, Syntax_Error
, "leading white-space"));
471 { /* Skip over leading WS after the keyword */
473 while ( *p
== ' ' || *p
== '\t')
478 for (i
=0; (s
=ctx
->cmdtbl
[i
].name
); i
++)
480 if (!strcmp (line
, s
))
484 { /* and try case insensitive */
485 for (i
=0; (s
=ctx
->cmdtbl
[i
].name
); i
++)
487 if (!my_strcasecmp (line
, s
))
492 return PROCESS_DONE (ctx
, set_error (ctx
, Unknown_Command
, NULL
));
496 /* fprintf (stderr, "DBG-assuan: processing %s `%s'\n", s, line); */
497 return ctx
->cmdtbl
[i
].handler (ctx
, line
);
501 /* Call this to acknowledge the current command. */
503 assuan_process_done (assuan_context_t ctx
, int rc
)
505 if (!ctx
->in_command
)
506 return _assuan_error (ASSUAN_General_Error
);
510 /* Check for data write errors. */
511 if (ctx
->outbound
.data
.fp
)
513 /* Flush the data lines. */
514 fclose (ctx
->outbound
.data
.fp
);
515 ctx
->outbound
.data
.fp
= NULL
;
516 if (!rc
&& ctx
->outbound
.data
.error
)
517 rc
= ctx
->outbound
.data
.error
;
521 /* Flush any data send without using the data FP. */
522 assuan_send_data (ctx
, NULL
, 0);
523 if (!rc
&& ctx
->outbound
.data
.error
)
524 rc
= ctx
->outbound
.data
.error
;
527 /* Error handling. */
530 rc
= assuan_write_line (ctx
, ctx
->okay_line
? ctx
->okay_line
: "OK");
532 else if (err_is_eof (rc
))
533 { /* No error checking because the peer may have already disconnect. */
534 assuan_write_line (ctx
, "OK closing connection");
535 ctx
->finish_handler (ctx
);
542 sprintf (errline
, "ERR %d server fault (%.50s)",
543 _assuan_error (ASSUAN_Server_Fault
), assuan_strerror (rc
));
546 const char *text
= ctx
->err_no
== rc
? ctx
->err_str
:NULL
;
548 #if defined(HAVE_W32_SYSTEM)
549 unsigned int source
, code
;
553 source
= ((rc
>> 24) & 0xff);
554 code
= (rc
& 0x00ffffff);
556 && !_assuan_gpg_strerror_r (rc
, ebuf
, sizeof ebuf
)
557 && (esrc
=_assuan_gpg_strsource (rc
)))
559 /* Assume this is an libgpg-error. */
560 sprintf (errline
, "ERR %d %.50s <%.30s>%s%.100s",
562 text
? " - ":"", text
?text
:"");
565 #elif defined(__GNUC__) && defined(__ELF__)
566 /* If we have weak symbol support we try to use the error
567 strings from libgpg-error without creating a dependency.
568 They are used for debugging purposes only, so there is no
569 problem if they are not available. We need to make sure
570 that we are using ELF because only this guarantees that
571 weak symbol support is available in case GNU ld is not
572 used. It seems that old gcc versions don't implement the
573 weak attribute properly but it works with the weak
576 unsigned int source
, code
;
578 int gpg_strerror_r (unsigned int err
, char *buf
, size_t buflen
)
579 __attribute__ ((weak
));
580 const char *gpg_strsource (unsigned int err
)
581 __attribute__ ((weak
));
583 #pragma weak gpg_strerror_r
584 #pragma weak gpg_strsource
587 source
= ((rc
>> 24) & 0xff);
588 code
= (rc
& 0x00ffffff);
589 if (source
&& gpg_strsource
&& gpg_strerror_r
)
591 /* Assume this is an libgpg-error. */
594 gpg_strerror_r (rc
, ebuf
, sizeof ebuf
);
595 sprintf (errline
, "ERR %d %.50s <%.30s>%s%.100s",
599 text
? " - ":"", text
?text
:"");
602 #endif /* __GNUC__ && __ELF__ */
603 sprintf (errline
, "ERR %d %.50s%s%.100s",
604 rc
, assuan_strerror (rc
), text
? " - ":"", text
?text
:"");
606 rc
= assuan_write_line (ctx
, errline
);
609 if (ctx
->post_cmd_notify_fnc
)
610 ctx
->post_cmd_notify_fnc (ctx
, rc
);
612 ctx
->confidential
= 0;
615 xfree (ctx
->okay_line
);
616 ctx
->okay_line
= NULL
;
624 process_next (assuan_context_t ctx
)
628 /* What the next thing to do is depends on the current state.
629 However, we will always first read the next line. The client is
630 required to write full lines without blocking long after starting
632 rc
= _assuan_read_line (ctx
);
633 if (_assuan_error_is_eagain (rc
))
637 if (*ctx
->inbound
.line
== '#' || !ctx
->inbound
.linelen
)
638 /* Comment lines are ignored. */
641 /* Now we have a line that really means something. It could be one
642 of the following things: First, if we are not in a command
643 already, it is the next command to dispatch. Second, if we are
644 in a command, it can only be the response to an INQUIRE
647 if (!ctx
->in_command
)
651 ctx
->outbound
.data
.error
= 0;
652 ctx
->outbound
.data
.linelen
= 0;
653 /* Dispatch command and return reply. */
654 ctx
->in_process_next
= 1;
655 rc
= dispatch_command (ctx
, ctx
->inbound
.line
, ctx
->inbound
.linelen
);
656 ctx
->in_process_next
= 0;
658 else if (ctx
->in_inquire
)
660 /* FIXME: Pick up the continuation. */
661 rc
= _assuan_inquire_ext_cb (ctx
);
665 /* Should not happen. The client is sending data while we are
666 in a command and not waiting for an inquire. We log an error
668 _assuan_log_printf ("unexpected client data\n");
676 /* This function should be invoked when the assuan connected FD is
677 ready for reading. If the equivalent to EWOULDBLOCK is returned
678 (this should be done by the command handler), assuan_process_next
679 should be invoked the next time the connected FD is readable.
680 Eventually, the caller will finish by invoking
681 assuan_process_done. */
683 assuan_process_next (assuan_context_t ctx
)
689 rc
= process_next (ctx
);
691 while (!rc
&& assuan_pending_line (ctx
));
699 process_request (assuan_context_t ctx
)
704 return _assuan_error (ASSUAN_Nested_Commands
);
708 rc
= _assuan_read_line (ctx
);
710 while (_assuan_error_is_eagain (rc
));
713 if (*ctx
->inbound
.line
== '#' || !ctx
->inbound
.linelen
)
714 return 0; /* comment line - ignore */
717 ctx
->outbound
.data
.error
= 0;
718 ctx
->outbound
.data
.linelen
= 0;
719 /* dispatch command and return reply */
720 rc
= dispatch_command (ctx
, ctx
->inbound
.line
, ctx
->inbound
.linelen
);
722 return assuan_process_done (ctx
, rc
);
727 * @ctx: assuan context
729 * This function is used to handle the assuan protocol after a
730 * connection has been established using assuan_accept(). This is the
731 * main protocol handler.
733 * Return value: 0 on success or an error code if the assuan operation
734 * failed. Note, that no error is returned for operational errors.
737 assuan_process (assuan_context_t ctx
)
742 rc
= process_request (ctx
);
753 * assuan_get_active_fds:
754 * @ctx: Assuan context
755 * @what: 0 for read fds, 1 for write fds
756 * @fdarray: Caller supplied array to store the FDs
757 * @fdarraysize: size of that array
759 * Return all active filedescriptors for the given context. This
760 * function can be used to select on the fds and call
761 * assuan_process_next() if there is an active one. The first fd in
762 * the array is the one used for the command connection.
764 * Note, that write FDs are not yet supported.
766 * Return value: number of FDs active and put into @fdarray or -1 on
767 * error which is most likely a too small fdarray.
770 assuan_get_active_fds (assuan_context_t ctx
, int what
,
771 assuan_fd_t
*fdarray
, int fdarraysize
)
775 if (!ctx
|| fdarraysize
< 2 || what
< 0 || what
> 1)
780 if (ctx
->inbound
.fd
!= ASSUAN_INVALID_FD
)
781 fdarray
[n
++] = ctx
->inbound
.fd
;
785 if (ctx
->outbound
.fd
!= ASSUAN_INVALID_FD
)
786 fdarray
[n
++] = ctx
->outbound
.fd
;
787 if (ctx
->outbound
.data
.fp
)
788 #ifdef HAVE_W32_SYSTEM
789 fdarray
[n
++] = (void*)_get_osfhandle (fileno (ctx
->outbound
.data
.fp
));
791 fdarray
[n
++] = fileno (ctx
->outbound
.data
.fp
);
799 /* Two simple wrappers to make the expected function types match. */
802 fun1_cookie_write (void *cookie
, const char *buffer
, int orig_size
)
804 return _assuan_cookie_write_data (cookie
, buffer
, orig_size
);
806 #endif /*HAVE_FUNOPEN*/
807 #ifdef HAVE_FOPENCOOKIE
809 fun2_cookie_write (void *cookie
, const char *buffer
, size_t orig_size
)
811 return _assuan_cookie_write_data (cookie
, buffer
, orig_size
);
813 #endif /*HAVE_FOPENCOOKIE*/
815 /* Return a FP to be used for data output. The FILE pointer is valid
816 until the end of a handler. So a close is not needed. Assuan does
817 all the buffering needed to insert the status line as well as the
818 required line wappping and quoting for data lines.
820 We use GNU's custom streams here. There should be an alternative
821 implementaion for systems w/o a glibc, a simple implementation
822 could use a child process */
824 assuan_get_data_fp (assuan_context_t ctx
)
826 #if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
827 if (ctx
->outbound
.data
.fp
)
828 return ctx
->outbound
.data
.fp
;
831 ctx
->outbound
.data
.fp
= funopen (ctx
, 0, fun1_cookie_write
,
832 0, _assuan_cookie_write_flush
);
834 ctx
->outbound
.data
.fp
= funopen (ctx
, 0, fun2_cookie_write
,
835 0, _assuan_cookie_write_flush
);
838 ctx
->outbound
.data
.error
= 0;
839 return ctx
->outbound
.data
.fp
;
847 /* Set the text used for the next OK reponse. This string is
848 automatically reset to NULL after the next command. */
850 assuan_set_okay_line (assuan_context_t ctx
, const char *line
)
853 return _assuan_error (ASSUAN_Invalid_Value
);
856 xfree (ctx
->okay_line
);
857 ctx
->okay_line
= NULL
;
861 /* FIXME: we need to use gcry_is_secure() to test whether
862 we should allocate the entire line in secure memory */
863 char *buf
= xtrymalloc (3+strlen(line
)+1);
865 return _assuan_error (ASSUAN_Out_Of_Core
);
867 strcpy (buf
+3, line
);
868 xfree (ctx
->okay_line
);
869 ctx
->okay_line
= buf
;
877 assuan_write_status (assuan_context_t ctx
,
878 const char *keyword
, const char *text
)
885 if ( !ctx
|| !keyword
)
886 return _assuan_error (ASSUAN_Invalid_Value
);
890 n
= 2 + strlen (keyword
) + 1 + strlen (text
) + 1;
891 if (n
< sizeof (buffer
))
893 strcpy (buffer
, "S ");
894 strcat (buffer
, keyword
);
897 strcat (buffer
, " ");
898 strcat (buffer
, text
);
900 ae
= assuan_write_line (ctx
, buffer
);
902 else if ( (helpbuf
= xtrymalloc (n
)) )
904 strcpy (helpbuf
, "S ");
905 strcat (helpbuf
, keyword
);
908 strcat (helpbuf
, " ");
909 strcat (helpbuf
, text
);
911 ae
= assuan_write_line (ctx
, helpbuf
);