Line data Source code
1 : /* assuan-handler.c - dispatch commands
2 : Copyright (C) 2001, 2002, 2003, 2007, 2009,
3 : 2011 Free Software Foundation, Inc.
4 :
5 : This file is part of Assuan.
6 :
7 : Assuan 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 : Assuan 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, see <http://www.gnu.org/licenses/>.
19 : */
20 :
21 : #ifdef HAVE_CONFIG_H
22 : #include <config.h>
23 : #endif
24 :
25 : #include <stdlib.h>
26 : #include <stdio.h>
27 : #include <string.h>
28 : #include <errno.h>
29 :
30 : #include "assuan-defs.h"
31 : #include "debug.h"
32 :
33 :
34 : #define spacep(p) (*(p) == ' ' || *(p) == '\t')
35 : #define digitp(a) ((a) >= '0' && (a) <= '9')
36 :
37 : static int my_strcasecmp (const char *a, const char *b);
38 :
39 :
40 : #define PROCESS_DONE(ctx, rc) \
41 : ((ctx)->in_process_next ? assuan_process_done ((ctx), (rc)) : (rc))
42 :
43 : static gpg_error_t
44 0 : dummy_handler (assuan_context_t ctx, char *line)
45 : {
46 0 : return
47 0 : PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASSUAN_SERVER_FAULT,
48 : "no handler registered"));
49 : }
50 :
51 :
52 : static const char std_help_nop[] =
53 : "NOP\n"
54 : "\n"
55 : "No operation. Returns OK without any action.";
56 : static gpg_error_t
57 0 : std_handler_nop (assuan_context_t ctx, char *line)
58 : {
59 0 : return PROCESS_DONE (ctx, 0); /* okay */
60 : }
61 :
62 : static const char std_help_cancel[] =
63 : "CANCEL\n"
64 : "\n"
65 : "Run the server's cancel handler if one has been registered.";
66 : static gpg_error_t
67 0 : std_handler_cancel (assuan_context_t ctx, char *line)
68 : {
69 0 : if (ctx->cancel_notify_fnc)
70 : /* Return value ignored. */
71 0 : ctx->cancel_notify_fnc (ctx, line);
72 0 : return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_NOT_IMPLEMENTED, NULL));
73 : }
74 :
75 : static const char std_help_option[] =
76 : "OPTION <NAME> [ [=] <VALUE> ]\n"
77 : "\n"
78 : "Set option <NAME> to configure server operation. Leading and\n"
79 : "trailing spaces around <NAME> and <VALUE> are allowed but should be\n"
80 : "ignored. For compatibility reasons, <NAME> may be prefixed with two\n"
81 : "dashes. The use of the equal sign is optional but suggested if\n"
82 : "<VALUE> is given.";
83 : static gpg_error_t
84 0 : std_handler_option (assuan_context_t ctx, char *line)
85 : {
86 : char *key, *value, *p;
87 :
88 0 : for (key=line; spacep (key); key++)
89 : ;
90 0 : if (!*key)
91 0 : return
92 0 : PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_SYNTAX, "argument required"));
93 0 : if (*key == '=')
94 0 : return
95 0 : PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_SYNTAX,
96 : "no option name given"));
97 0 : for (value=key; *value && !spacep (value) && *value != '='; value++)
98 : ;
99 0 : if (*value)
100 : {
101 0 : if (spacep (value))
102 0 : *value++ = 0; /* terminate key */
103 0 : for (; spacep (value); value++)
104 : ;
105 0 : if (*value == '=')
106 : {
107 0 : *value++ = 0; /* terminate key */
108 0 : for (; spacep (value); value++)
109 : ;
110 0 : if (!*value)
111 0 : return
112 0 : PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_SYNTAX,
113 : "option argument expected"));
114 : }
115 0 : if (*value)
116 : {
117 0 : for (p = value + strlen(value) - 1; p > value && spacep (p); p--)
118 : ;
119 0 : if (p > value)
120 0 : *++p = 0; /* strip trailing spaces */
121 : }
122 : }
123 :
124 0 : if (*key == '-' && key[1] == '-' && key[2])
125 0 : key += 2; /* the double dashes are optional */
126 0 : if (*key == '-')
127 0 : return PROCESS_DONE (ctx,
128 : set_error (ctx, GPG_ERR_ASS_SYNTAX,
129 : "option should not begin with one dash"));
130 :
131 0 : if (ctx->option_handler_fnc)
132 0 : return PROCESS_DONE (ctx, ctx->option_handler_fnc (ctx, key, value));
133 0 : return PROCESS_DONE (ctx, 0);
134 : }
135 :
136 : static const char std_help_bye[] =
137 : "BYE\n"
138 : "\n"
139 : "Close the connection. The server will reply with OK.";
140 : static gpg_error_t
141 2 : std_handler_bye (assuan_context_t ctx, char *line)
142 : {
143 2 : if (ctx->bye_notify_fnc)
144 : /* Return value ignored. */
145 0 : ctx->bye_notify_fnc (ctx, line);
146 2 : assuan_close_input_fd (ctx);
147 2 : assuan_close_output_fd (ctx);
148 : /* pretty simple :-) */
149 2 : ctx->process_complete = 1;
150 2 : return PROCESS_DONE (ctx, 0);
151 : }
152 :
153 : static const char std_help_auth[] =
154 : "AUTH\n"
155 : "\n"
156 : "Reserved for future extensions.";
157 : static gpg_error_t
158 0 : std_handler_auth (assuan_context_t ctx, char *line)
159 : {
160 0 : return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_NOT_IMPLEMENTED, NULL));
161 : }
162 :
163 : static const char std_help_reset[] =
164 : "RESET\n"
165 : "\n"
166 : "Reset the connection but not any existing authentication. The server\n"
167 : "should release all resources associated with the connection.";
168 : static gpg_error_t
169 0 : std_handler_reset (assuan_context_t ctx, char *line)
170 : {
171 0 : gpg_error_t err = 0;
172 :
173 0 : if (ctx->reset_notify_fnc)
174 0 : err = ctx->reset_notify_fnc (ctx, line);
175 0 : if (! err)
176 : {
177 0 : assuan_close_input_fd (ctx);
178 0 : assuan_close_output_fd (ctx);
179 0 : _assuan_uds_close_fds (ctx);
180 : }
181 0 : return PROCESS_DONE (ctx, err);
182 : }
183 :
184 : static const char std_help_help[] =
185 : "HELP [<COMMAND>]\n"
186 : "\n"
187 : "Lists all commands that the server understands as comment lines on\n"
188 : "the status channel. If <COMMAND> is given, list detailed help for\n"
189 : "that command.";
190 : static gpg_error_t
191 0 : std_handler_help (assuan_context_t ctx, char *line)
192 : {
193 : unsigned int i;
194 : char buf[ASSUAN_LINELENGTH];
195 : const char *helpstr;
196 : size_t n;
197 :
198 0 : n = strcspn (line, " \t\n");
199 0 : if (!n)
200 : {
201 : /* Print all commands. If a help string is available and that
202 : starts with the command name, print the first line of the
203 : help string. */
204 0 : for (i = 0; i < ctx->cmdtbl_used; i++)
205 : {
206 0 : n = strlen (ctx->cmdtbl[i].name);
207 0 : helpstr = ctx->cmdtbl[i].helpstr;
208 0 : if (helpstr
209 0 : && !strncmp (ctx->cmdtbl[i].name, helpstr, n)
210 0 : && (!helpstr[n] || helpstr[n] == '\n' || helpstr[n] == ' ')
211 0 : && (n = strcspn (helpstr, "\n")) )
212 0 : snprintf (buf, sizeof (buf), "# %.*s", (int)n, helpstr);
213 : else
214 0 : snprintf (buf, sizeof (buf), "# %s", ctx->cmdtbl[i].name);
215 0 : buf[ASSUAN_LINELENGTH - 1] = '\0';
216 0 : assuan_write_line (ctx, buf);
217 : }
218 : }
219 : else
220 : {
221 : /* Print the help for the given command. */
222 0 : int c = line[n];
223 0 : line[n] = 0;
224 0 : for (i=0; ctx->cmdtbl[i].name; i++)
225 0 : if (!my_strcasecmp (line, ctx->cmdtbl[i].name))
226 0 : break;
227 0 : line[n] = c;
228 0 : if (!ctx->cmdtbl[i].name)
229 0 : return PROCESS_DONE (ctx, set_error (ctx,GPG_ERR_UNKNOWN_COMMAND,NULL));
230 0 : helpstr = ctx->cmdtbl[i].helpstr;
231 0 : if (!helpstr)
232 0 : return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_NOT_FOUND, NULL));
233 : do
234 : {
235 0 : n = strcspn (helpstr, "\n");
236 0 : snprintf (buf, sizeof (buf), "# %.*s", (int)n, helpstr);
237 0 : helpstr += n;
238 0 : if (*helpstr == '\n')
239 0 : helpstr++;
240 0 : buf[ASSUAN_LINELENGTH - 1] = '\0';
241 0 : assuan_write_line (ctx, buf);
242 : }
243 0 : while (*helpstr);
244 : }
245 :
246 0 : return PROCESS_DONE (ctx, 0);
247 : }
248 :
249 : static const char std_help_end[] =
250 : "END\n"
251 : "\n"
252 : "Used by a client to mark the end of raw data.";
253 : static gpg_error_t
254 0 : std_handler_end (assuan_context_t ctx, char *line)
255 : {
256 0 : return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_NOT_IMPLEMENTED, NULL));
257 : }
258 :
259 :
260 : gpg_error_t
261 6 : assuan_command_parse_fd (assuan_context_t ctx, char *line, assuan_fd_t *rfd)
262 : {
263 : char *endp;
264 :
265 6 : if ((strncmp (line, "FD", 2) && strncmp (line, "fd", 2))
266 6 : || (line[2] != '=' && line[2] != '\0' && !spacep(&line[2])))
267 0 : return set_error (ctx, GPG_ERR_ASS_SYNTAX, "FD[=<n>] expected");
268 6 : line += 2;
269 6 : if (*line == '=')
270 : {
271 0 : line ++;
272 0 : if (!digitp (*line))
273 0 : return set_error (ctx, GPG_ERR_ASS_SYNTAX, "number required");
274 : #if HAVE_W64_SYSTEM
275 : *rfd = (void*)strtoull (line, &endp, 10);
276 : #elif HAVE_W32_SYSTEM
277 : *rfd = (void*)strtoul (line, &endp, 10);
278 : #else
279 0 : *rfd = strtoul (line, &endp, 10);
280 : #endif
281 : /* Remove that argument so that a notify handler won't see it. */
282 0 : memset (line, ' ', endp? (endp-line):strlen(line));
283 :
284 0 : if (*rfd == ctx->inbound.fd)
285 0 : return set_error (ctx, GPG_ERR_ASS_PARAMETER, "fd same as inbound fd");
286 0 : if (*rfd == ctx->outbound.fd)
287 0 : return set_error (ctx, GPG_ERR_ASS_PARAMETER, "fd same as outbound fd");
288 0 : return 0;
289 : }
290 : else
291 : /* Our peer has sent the file descriptor. */
292 6 : return assuan_receivefd (ctx, rfd);
293 : }
294 :
295 :
296 : static const char std_help_input[] =
297 : "INPUT FD[=<N>]\n"
298 : "\n"
299 : "Used by a client to pass an input file descriptor to the server.\n"
300 : "The server opens <N> as a local file descriptor. Without <N>, the\n"
301 : "server opens the file descriptor just sent by the client using\n"
302 : "assuan_sendfd.";
303 : static gpg_error_t
304 6 : std_handler_input (assuan_context_t ctx, char *line)
305 : {
306 : gpg_error_t rc;
307 : assuan_fd_t fd, oldfd;
308 :
309 6 : rc = assuan_command_parse_fd (ctx, line, &fd);
310 6 : if (rc)
311 0 : return PROCESS_DONE (ctx, rc);
312 :
313 : #ifdef HAVE_W32CE_SYSTEM
314 : oldfd = fd;
315 : fd = _assuan_w32ce_finish_pipe ((int)fd, 0);
316 : if (fd == INVALID_HANDLE_VALUE)
317 : return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_PARAMETER,
318 : "rvid conversion failed"));
319 : TRACE2 (ctx, ASSUAN_LOG_SYSIO, "std_handler_input", ctx,
320 : "turned RVID 0x%x into handle 0x%x", oldfd, fd);
321 : #endif
322 :
323 6 : if (ctx->input_notify_fnc)
324 : {
325 0 : oldfd = ctx->input_fd;
326 0 : ctx->input_fd = fd;
327 0 : rc = ctx->input_notify_fnc (ctx, line);
328 0 : if (rc)
329 0 : ctx->input_fd = oldfd;
330 : }
331 6 : else if (!rc)
332 6 : ctx->input_fd = fd;
333 6 : return PROCESS_DONE (ctx, rc);
334 : }
335 :
336 :
337 : static const char std_help_output[] =
338 : "OUTPUT FD[=<N>]\n"
339 : "\n"
340 : "Used by a client to pass an output file descriptor to the server.\n"
341 : "The server opens <N> as a local file descriptor. Without <N>, the\n"
342 : "server opens the file descriptor just sent by the client using\n"
343 : "assuan_sendfd.";
344 : static gpg_error_t
345 0 : std_handler_output (assuan_context_t ctx, char *line)
346 : {
347 : gpg_error_t rc;
348 : assuan_fd_t fd, oldfd;
349 :
350 0 : rc = assuan_command_parse_fd (ctx, line, &fd);
351 0 : if (rc)
352 0 : return PROCESS_DONE (ctx, rc);
353 :
354 : #ifdef HAVE_W32CE_SYSTEM
355 : oldfd = fd;
356 : fd = _assuan_w32ce_finish_pipe ((int)fd, 1);
357 : if (fd == INVALID_HANDLE_VALUE)
358 : return PROCESS_DONE (ctx, set_error (ctx, gpg_err_code_from_syserror (),
359 : "rvid conversion failed"));
360 : TRACE2 (ctx, ASSUAN_LOG_SYSIO, "std_handler_output", ctx,
361 : "turned RVID 0x%x into handle 0x%x", oldfd, fd);
362 : #endif
363 :
364 0 : if (ctx->output_notify_fnc)
365 : {
366 0 : oldfd = ctx->output_fd;
367 0 : ctx->output_fd = fd;
368 0 : rc = ctx->output_notify_fnc (ctx, line);
369 0 : if (rc)
370 0 : ctx->output_fd = oldfd;
371 : }
372 0 : else if (!rc)
373 0 : ctx->output_fd = fd;
374 0 : return PROCESS_DONE (ctx, rc);
375 : }
376 :
377 :
378 : /* This is a table with the standard commands and handler for them.
379 : The table is used to initialize a new context and associate strings
380 : with default handlers */
381 : static struct {
382 : const char *name;
383 : gpg_error_t (*handler)(assuan_context_t, char *line);
384 : const char *help;
385 : int always; /* always initialize this command */
386 : } std_cmd_table[] = {
387 : { "NOP", std_handler_nop, std_help_nop, 1 },
388 : { "CANCEL", std_handler_cancel, std_help_cancel, 1 },
389 : { "OPTION", std_handler_option, std_help_option, 1 },
390 : { "BYE", std_handler_bye, std_help_bye, 1 },
391 : { "AUTH", std_handler_auth, std_help_auth, 1 },
392 : { "RESET", std_handler_reset, std_help_reset, 1 },
393 : { "END", std_handler_end, std_help_end, 1 },
394 : { "HELP", std_handler_help, std_help_help, 1 },
395 :
396 : { "INPUT", std_handler_input, std_help_input, 0 },
397 : { "OUTPUT", std_handler_output, std_help_output, 0 },
398 : { } };
399 :
400 :
401 : /**
402 : * assuan_register_command:
403 : * @ctx: the server context
404 : * @cmd_name: A string with the command name
405 : * @handler: The handler function to be called or NULL to use a default
406 : * handler.
407 : * HELPSTRING
408 : *
409 : * Register a handler to be used for a given command. Note that
410 : * several default handlers are already regsitered with a new context.
411 : * This function however allows to override them.
412 : *
413 : * Return value: 0 on success or an error code
414 : **/
415 : gpg_error_t
416 23 : assuan_register_command (assuan_context_t ctx, const char *cmd_name,
417 : assuan_handler_t handler, const char *help_string)
418 : {
419 23 : int i, cmd_index = -1;
420 : const char *s;
421 :
422 23 : if (cmd_name && !*cmd_name)
423 0 : cmd_name = NULL;
424 :
425 23 : if (!cmd_name)
426 0 : return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
427 :
428 23 : if (!handler)
429 : { /* find a default handler. */
430 20 : for (i=0; (s=std_cmd_table[i].name) && strcmp (cmd_name, s); i++)
431 : ;
432 20 : if (!s)
433 : { /* Try again but case insensitive. */
434 0 : for (i=0; (s=std_cmd_table[i].name)
435 0 : && my_strcasecmp (cmd_name, s); i++)
436 : ;
437 : }
438 20 : if (s)
439 20 : handler = std_cmd_table[i].handler;
440 20 : if (!handler)
441 0 : handler = dummy_handler; /* Last resort is the dummy handler. */
442 : }
443 :
444 23 : if (!ctx->cmdtbl)
445 : {
446 2 : ctx->cmdtbl_size = 50;
447 2 : ctx->cmdtbl = _assuan_calloc (ctx, ctx->cmdtbl_size, sizeof *ctx->cmdtbl);
448 2 : if (!ctx->cmdtbl)
449 0 : return _assuan_error (ctx, gpg_err_code_from_syserror ());
450 2 : ctx->cmdtbl_used = 0;
451 : }
452 21 : else if (ctx->cmdtbl_used >= ctx->cmdtbl_size)
453 : {
454 : struct cmdtbl_s *x;
455 :
456 0 : x = _assuan_realloc (ctx, ctx->cmdtbl, (ctx->cmdtbl_size+10) * sizeof *x);
457 0 : if (!x)
458 0 : return _assuan_error (ctx, gpg_err_code_from_syserror ());
459 0 : ctx->cmdtbl = x;
460 0 : ctx->cmdtbl_size += 50;
461 : }
462 :
463 144 : for (i=0; i<ctx->cmdtbl_used; i++)
464 : {
465 121 : if (!my_strcasecmp (cmd_name, ctx->cmdtbl[i].name))
466 : {
467 0 : cmd_index = i;
468 0 : break;
469 : }
470 : }
471 :
472 23 : if (cmd_index == -1)
473 23 : cmd_index = ctx->cmdtbl_used++;
474 :
475 23 : ctx->cmdtbl[cmd_index].name = cmd_name;
476 23 : ctx->cmdtbl[cmd_index].handler = handler;
477 23 : ctx->cmdtbl[cmd_index].helpstr = help_string;
478 23 : return 0;
479 : }
480 :
481 : /* Return the name of the command currently processed by a handler.
482 : The string returned is valid until the next call to an assuan
483 : function on the same context. Returns NULL if no handler is
484 : executed or the command is not known. */
485 : const char *
486 0 : assuan_get_command_name (assuan_context_t ctx)
487 : {
488 0 : return ctx? ctx->current_cmd_name : NULL;
489 : }
490 :
491 : gpg_error_t
492 0 : assuan_register_pre_cmd_notify (assuan_context_t ctx,
493 : gpg_error_t (*fnc)(assuan_context_t,
494 : const char *cmd))
495 : {
496 0 : if (!ctx)
497 0 : return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
498 0 : ctx->pre_cmd_notify_fnc = fnc;
499 0 : return 0;
500 : }
501 :
502 : gpg_error_t
503 0 : assuan_register_post_cmd_notify (assuan_context_t ctx,
504 : void (*fnc)(assuan_context_t, gpg_error_t))
505 : {
506 0 : if (!ctx)
507 0 : return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
508 0 : ctx->post_cmd_notify_fnc = fnc;
509 0 : return 0;
510 : }
511 :
512 : gpg_error_t
513 0 : assuan_register_bye_notify (assuan_context_t ctx, assuan_handler_t fnc)
514 : {
515 0 : if (!ctx)
516 0 : return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
517 0 : ctx->bye_notify_fnc = fnc;
518 0 : return 0;
519 : }
520 :
521 : gpg_error_t
522 0 : assuan_register_reset_notify (assuan_context_t ctx, assuan_handler_t fnc)
523 : {
524 0 : if (!ctx)
525 0 : return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
526 0 : ctx->reset_notify_fnc = fnc;
527 0 : return 0;
528 : }
529 :
530 : gpg_error_t
531 0 : assuan_register_cancel_notify (assuan_context_t ctx, assuan_handler_t fnc)
532 : {
533 0 : if (!ctx)
534 0 : return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
535 0 : ctx->cancel_notify_fnc = fnc;
536 0 : return 0;
537 : }
538 :
539 : gpg_error_t
540 0 : assuan_register_option_handler (assuan_context_t ctx,
541 : gpg_error_t (*fnc)(assuan_context_t,
542 : const char*, const char*))
543 : {
544 0 : if (!ctx)
545 0 : return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
546 0 : ctx->option_handler_fnc = fnc;
547 0 : return 0;
548 : }
549 :
550 : gpg_error_t
551 0 : assuan_register_input_notify (assuan_context_t ctx, assuan_handler_t fnc)
552 : {
553 0 : if (!ctx)
554 0 : return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
555 0 : ctx->input_notify_fnc = fnc;
556 0 : return 0;
557 : }
558 :
559 : gpg_error_t
560 0 : assuan_register_output_notify (assuan_context_t ctx, assuan_handler_t fnc)
561 : {
562 0 : if (!ctx)
563 0 : return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
564 0 : ctx->output_notify_fnc = fnc;
565 0 : return 0;
566 : }
567 :
568 :
569 : /* Helper to register the standards commands */
570 : gpg_error_t
571 2 : _assuan_register_std_commands (assuan_context_t ctx)
572 : {
573 : gpg_error_t rc;
574 : int i;
575 :
576 22 : for (i = 0; std_cmd_table[i].name; i++)
577 : {
578 20 : if (std_cmd_table[i].always)
579 : {
580 16 : rc = assuan_register_command (ctx, std_cmd_table[i].name, NULL, NULL);
581 16 : if (rc)
582 0 : return rc;
583 : }
584 : }
585 2 : return 0;
586 : }
587 :
588 :
589 :
590 : /* Process the special data lines. The "D " has already been removed
591 : from the line. As all handlers this function may modify the line. */
592 : static gpg_error_t
593 0 : handle_data_line (assuan_context_t ctx, char *line, int linelen)
594 : {
595 0 : return set_error (ctx, GPG_ERR_NOT_IMPLEMENTED, NULL);
596 : }
597 :
598 : /* like ascii_strcasecmp but assume that B is already uppercase */
599 : static int
600 121 : my_strcasecmp (const char *a, const char *b)
601 : {
602 121 : if (a == b)
603 0 : return 0;
604 :
605 127 : for (; *a && *b; a++, b++)
606 : {
607 127 : if (((*a >= 'a' && *a <= 'z')? (*a&~0x20):*a) != *b)
608 121 : break;
609 : }
610 121 : return *a == *b? 0 : (((*a >= 'a' && *a <= 'z')? (*a&~0x20):*a) - *b);
611 : }
612 :
613 :
614 : /* Parse the line, break out the command, find it in the command
615 : table, remove leading and white spaces from the arguments, call the
616 : handler with the argument line and return the error. */
617 : static gpg_error_t
618 15 : dispatch_command (assuan_context_t ctx, char *line, int linelen)
619 : {
620 : gpg_error_t err;
621 : char *p;
622 : const char *s;
623 : int shift, i;
624 :
625 : /* Note that as this function is invoked by assuan_process_next as
626 : well, we need to hide non-critical errors with PROCESS_DONE. */
627 :
628 15 : if (*line == 'D' && line[1] == ' ') /* divert to special handler */
629 : /* FIXME: Depending on the final implementation of
630 : handle_data_line, this may be wrong here. For example, if a
631 : user callback is invoked, and that callback is responsible for
632 : calling assuan_process_done, then this is wrong. */
633 0 : return PROCESS_DONE (ctx, handle_data_line (ctx, line+2, linelen-2));
634 :
635 15 : for (p=line; *p && *p != ' ' && *p != '\t'; p++)
636 : ;
637 15 : if (p==line)
638 0 : return PROCESS_DONE
639 : (ctx, set_error (ctx, GPG_ERR_ASS_SYNTAX, "leading white-space"));
640 15 : if (*p)
641 : { /* Skip over leading WS after the keyword */
642 7 : *p++ = 0;
643 14 : while ( *p == ' ' || *p == '\t')
644 0 : p++;
645 : }
646 15 : shift = p - line;
647 :
648 131 : for (i=0; (s=ctx->cmdtbl[i].name); i++)
649 : {
650 131 : if (!strcmp (line, s))
651 15 : break;
652 : }
653 15 : if (!s)
654 : { /* and try case insensitive */
655 0 : for (i=0; (s=ctx->cmdtbl[i].name); i++)
656 : {
657 0 : if (!my_strcasecmp (line, s))
658 0 : break;
659 : }
660 : }
661 15 : if (!s)
662 0 : return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_UNKNOWN_CMD, NULL));
663 15 : line += shift;
664 : /* linelen -= shift; -- not needed. */
665 :
666 15 : if (ctx->pre_cmd_notify_fnc) {
667 0 : err = ctx->pre_cmd_notify_fnc(ctx, ctx->cmdtbl[i].name);
668 :
669 0 : if (err)
670 0 : return PROCESS_DONE(ctx, err);
671 : }
672 :
673 : /* fprintf (stderr, "DBG-assuan: processing %s `%s'\n", s, line); */
674 15 : ctx->current_cmd_name = ctx->cmdtbl[i].name;
675 15 : err = ctx->cmdtbl[i].handler (ctx, line);
676 15 : ctx->current_cmd_name = NULL;
677 15 : return err;
678 : }
679 :
680 :
681 : /* Call this to acknowledge the current command. */
682 : gpg_error_t
683 15 : assuan_process_done (assuan_context_t ctx, gpg_error_t rc)
684 : {
685 15 : if (!ctx->in_command)
686 0 : return _assuan_error (ctx, GPG_ERR_ASS_GENERAL);
687 :
688 15 : if (ctx->flags.force_close)
689 0 : ctx->process_complete = 1;
690 :
691 15 : ctx->in_command = 0;
692 :
693 : /* Check for data write errors. */
694 15 : if (ctx->outbound.data.fp)
695 : {
696 : /* Flush the data lines. */
697 0 : fclose (ctx->outbound.data.fp);
698 0 : ctx->outbound.data.fp = NULL;
699 0 : if (!rc && ctx->outbound.data.error)
700 0 : rc = ctx->outbound.data.error;
701 : }
702 : else
703 : {
704 : /* Flush any data send without using the data FP. */
705 15 : assuan_send_data (ctx, NULL, 0);
706 15 : if (!rc && ctx->outbound.data.error)
707 0 : rc = ctx->outbound.data.error;
708 : }
709 :
710 : /* Error handling. */
711 15 : if (!rc)
712 : {
713 15 : if (ctx->process_complete)
714 : {
715 : /* No error checking because the peer may have already
716 : disconnect. */
717 2 : assuan_write_line (ctx, "OK closing connection");
718 2 : ctx->finish_handler (ctx);
719 : }
720 : else
721 13 : rc = assuan_write_line (ctx, ctx->okay_line ? ctx->okay_line : "OK");
722 : }
723 : else
724 : {
725 : char errline[300];
726 0 : const char *text = ctx->err_no == rc ? ctx->err_str : NULL;
727 : char ebuf[50];
728 :
729 0 : if (ctx->flags.force_close)
730 0 : text = "[closing connection]";
731 :
732 0 : gpg_strerror_r (rc, ebuf, sizeof (ebuf));
733 0 : snprintf (errline, sizeof errline, "ERR %d %.50s <%.30s>%s%.100s",
734 : rc, ebuf, gpg_strsource (rc),
735 : text? " - ":"", text?text:"");
736 :
737 0 : rc = assuan_write_line (ctx, errline);
738 :
739 0 : if (ctx->flags.force_close)
740 0 : ctx->finish_handler (ctx);
741 : }
742 :
743 15 : if (ctx->post_cmd_notify_fnc)
744 0 : ctx->post_cmd_notify_fnc (ctx, rc);
745 :
746 15 : ctx->flags.confidential = 0;
747 15 : if (ctx->okay_line)
748 : {
749 0 : _assuan_free (ctx, ctx->okay_line);
750 0 : ctx->okay_line = NULL;
751 : }
752 :
753 15 : return rc;
754 : }
755 :
756 :
757 : static gpg_error_t
758 0 : process_next (assuan_context_t ctx)
759 : {
760 : gpg_error_t rc;
761 :
762 : /* What the next thing to do is depends on the current state.
763 : However, we will always first read the next line. The client is
764 : required to write full lines without blocking long after starting
765 : a partial line. */
766 0 : rc = _assuan_read_line (ctx);
767 0 : if (_assuan_error_is_eagain (ctx, rc))
768 0 : return 0;
769 0 : if (gpg_err_code (rc) == GPG_ERR_EOF)
770 : {
771 0 : ctx->process_complete = 1;
772 0 : return 0;
773 : }
774 0 : if (rc)
775 0 : return rc;
776 0 : if (*ctx->inbound.line == '#' || !ctx->inbound.linelen)
777 : /* Comment lines are ignored. */
778 0 : return 0;
779 :
780 : /* Now we have a line that really means something. It could be one
781 : of the following things: First, if we are not in a command
782 : already, it is the next command to dispatch. Second, if we are
783 : in a command, it can only be the response to an INQUIRE
784 : reply. */
785 :
786 0 : if (!ctx->in_command)
787 : {
788 0 : ctx->in_command = 1;
789 :
790 0 : ctx->outbound.data.error = 0;
791 0 : ctx->outbound.data.linelen = 0;
792 : /* Dispatch command and return reply. */
793 0 : ctx->in_process_next = 1;
794 0 : rc = dispatch_command (ctx, ctx->inbound.line, ctx->inbound.linelen);
795 0 : ctx->in_process_next = 0;
796 : }
797 0 : else if (ctx->in_inquire)
798 : {
799 : /* FIXME: Pick up the continuation. */
800 0 : rc = _assuan_inquire_ext_cb (ctx);
801 : }
802 : else
803 : {
804 : /* Should not happen. The client is sending data while we are
805 : in a command and not waiting for an inquire. We log an error
806 : and discard it. */
807 0 : TRACE0 (ctx, ASSUAN_LOG_DATA, "process_next", ctx,
808 : "unexpected client data");
809 0 : rc = 0;
810 : }
811 :
812 0 : return rc;
813 : }
814 :
815 :
816 : /* This function should be invoked when the assuan connected FD is
817 : ready for reading. If the equivalent to EWOULDBLOCK is returned
818 : (this should be done by the command handler), assuan_process_next
819 : should be invoked the next time the connected FD is readable.
820 : Eventually, the caller will finish by invoking assuan_process_done.
821 : DONE is set to 1 if the connection has ended. */
822 : gpg_error_t
823 0 : assuan_process_next (assuan_context_t ctx, int *done)
824 : {
825 : gpg_error_t rc;
826 :
827 0 : if (done)
828 0 : *done = 0;
829 0 : ctx->process_complete = 0;
830 : do
831 : {
832 0 : rc = process_next (ctx);
833 : }
834 0 : while (!rc && !ctx->process_complete && assuan_pending_line (ctx));
835 :
836 0 : if (done)
837 0 : *done = !!ctx->process_complete;
838 :
839 0 : return rc;
840 : }
841 :
842 :
843 :
844 : static gpg_error_t
845 21 : process_request (assuan_context_t ctx)
846 : {
847 : gpg_error_t rc;
848 :
849 21 : if (ctx->in_inquire)
850 0 : return _assuan_error (ctx, GPG_ERR_ASS_NESTED_COMMANDS);
851 :
852 : do
853 : {
854 21 : rc = _assuan_read_line (ctx);
855 : }
856 21 : while (_assuan_error_is_eagain (ctx, rc));
857 21 : if (gpg_err_code (rc) == GPG_ERR_EOF)
858 : {
859 0 : ctx->process_complete = 1;
860 0 : return 0;
861 : }
862 21 : if (rc)
863 0 : return rc;
864 21 : if (*ctx->inbound.line == '#' || !ctx->inbound.linelen)
865 6 : return 0; /* comment line - ignore */
866 :
867 15 : ctx->in_command = 1;
868 15 : ctx->outbound.data.error = 0;
869 15 : ctx->outbound.data.linelen = 0;
870 : /* dispatch command and return reply */
871 15 : rc = dispatch_command (ctx, ctx->inbound.line, ctx->inbound.linelen);
872 :
873 15 : return assuan_process_done (ctx, rc);
874 : }
875 :
876 : /**
877 : * assuan_process:
878 : * @ctx: assuan context
879 : *
880 : * This function is used to handle the assuan protocol after a
881 : * connection has been established using assuan_accept(). This is the
882 : * main protocol handler.
883 : *
884 : * Return value: 0 on success or an error code if the assuan operation
885 : * failed. Note, that no error is returned for operational errors.
886 : **/
887 : gpg_error_t
888 2 : assuan_process (assuan_context_t ctx)
889 : {
890 : gpg_error_t rc;
891 :
892 2 : ctx->process_complete = 0;
893 : do {
894 21 : rc = process_request (ctx);
895 21 : } while (!rc && !ctx->process_complete);
896 :
897 2 : return rc;
898 : }
899 :
900 :
901 : /**
902 : * assuan_get_active_fds:
903 : * @ctx: Assuan context
904 : * @what: 0 for read fds, 1 for write fds
905 : * @fdarray: Caller supplied array to store the FDs
906 : * @fdarraysize: size of that array
907 : *
908 : * Return all active filedescriptors for the given context. This
909 : * function can be used to select on the fds and call
910 : * assuan_process_next() if there is an active one. The first fd in
911 : * the array is the one used for the command connection.
912 : *
913 : * Note, that write FDs are not yet supported.
914 : *
915 : * Return value: number of FDs active and put into @fdarray or -1 on
916 : * error which is most likely a too small fdarray.
917 : **/
918 : int
919 0 : assuan_get_active_fds (assuan_context_t ctx, int what,
920 : assuan_fd_t *fdarray, int fdarraysize)
921 : {
922 0 : int n = 0;
923 :
924 0 : if (!ctx || fdarraysize < 2 || what < 0 || what > 1)
925 0 : return -1;
926 :
927 0 : if (!what)
928 : {
929 0 : if (ctx->inbound.fd != ASSUAN_INVALID_FD)
930 0 : fdarray[n++] = ctx->inbound.fd;
931 : }
932 : else
933 : {
934 0 : if (ctx->outbound.fd != ASSUAN_INVALID_FD)
935 0 : fdarray[n++] = ctx->outbound.fd;
936 0 : if (ctx->outbound.data.fp)
937 : #if defined(HAVE_W32CE_SYSTEM)
938 : fdarray[n++] = (void*)fileno (ctx->outbound.data.fp);
939 : #elif defined(HAVE_W32_SYSTEM)
940 : fdarray[n++] = (void*)_get_osfhandle (fileno (ctx->outbound.data.fp));
941 : #else
942 0 : fdarray[n++] = fileno (ctx->outbound.data.fp);
943 : #endif
944 : }
945 :
946 0 : return n;
947 : }
948 :
949 :
950 : /* Two simple wrappers to make the expected function types match. */
951 : #ifdef HAVE_FUNOPEN
952 : static int
953 : fun1_cookie_write (void *cookie, const char *buffer, int orig_size)
954 : {
955 : return _assuan_cookie_write_data (cookie, buffer, orig_size);
956 : }
957 : #endif /*HAVE_FUNOPEN*/
958 : #ifdef HAVE_FOPENCOOKIE
959 : static ssize_t
960 0 : fun2_cookie_write (void *cookie, const char *buffer, size_t orig_size)
961 : {
962 0 : return _assuan_cookie_write_data (cookie, buffer, orig_size);
963 : }
964 : #endif /*HAVE_FOPENCOOKIE*/
965 :
966 : /* Return a FP to be used for data output. The FILE pointer is valid
967 : until the end of a handler. So a close is not needed. Assuan does
968 : all the buffering needed to insert the status line as well as the
969 : required line wappping and quoting for data lines.
970 :
971 : We use GNU's custom streams here. There should be an alternative
972 : implementaion for systems w/o a glibc, a simple implementation
973 : could use a child process */
974 : FILE *
975 0 : assuan_get_data_fp (assuan_context_t ctx)
976 : {
977 : #if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
978 0 : if (ctx->outbound.data.fp)
979 0 : return ctx->outbound.data.fp;
980 :
981 : #ifdef HAVE_FUNOPEN
982 : ctx->outbound.data.fp = funopen (ctx, 0, fun1_cookie_write,
983 : 0, _assuan_cookie_write_flush);
984 : #else
985 0 : ctx->outbound.data.fp = funopen (ctx, 0, fun2_cookie_write,
986 : 0, _assuan_cookie_write_flush);
987 : #endif
988 :
989 0 : ctx->outbound.data.error = 0;
990 0 : return ctx->outbound.data.fp;
991 : #else
992 : gpg_err_set_errno (ENOSYS);
993 : return NULL;
994 : #endif
995 : }
996 :
997 :
998 : /* Set the text used for the next OK response. This string is
999 : automatically reset to NULL after the next command. */
1000 : gpg_error_t
1001 0 : assuan_set_okay_line (assuan_context_t ctx, const char *line)
1002 : {
1003 0 : if (!ctx)
1004 0 : return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
1005 0 : if (!line)
1006 : {
1007 0 : _assuan_free (ctx, ctx->okay_line);
1008 0 : ctx->okay_line = NULL;
1009 : }
1010 : else
1011 : {
1012 : /* FIXME: we need to use gcry_is_secure() to test whether
1013 : we should allocate the entire line in secure memory */
1014 0 : char *buf = _assuan_malloc (ctx, 3 + strlen(line) + 1);
1015 0 : if (!buf)
1016 0 : return _assuan_error (ctx, gpg_err_code_from_syserror ());
1017 0 : strcpy (buf, "OK ");
1018 0 : strcpy (buf+3, line);
1019 0 : _assuan_free (ctx, ctx->okay_line);
1020 0 : ctx->okay_line = buf;
1021 : }
1022 0 : return 0;
1023 : }
1024 :
1025 :
1026 :
1027 : gpg_error_t
1028 0 : assuan_write_status (assuan_context_t ctx,
1029 : const char *keyword, const char *text)
1030 : {
1031 : char buffer[256];
1032 : char *helpbuf;
1033 : size_t n;
1034 : gpg_error_t ae;
1035 :
1036 0 : if ( !ctx || !keyword)
1037 0 : return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
1038 0 : if (!text)
1039 0 : text = "";
1040 :
1041 0 : n = 2 + strlen (keyword) + 1 + strlen (text) + 1;
1042 0 : if (n < sizeof (buffer))
1043 : {
1044 0 : strcpy (buffer, "S ");
1045 0 : strcat (buffer, keyword);
1046 0 : if (*text)
1047 : {
1048 0 : strcat (buffer, " ");
1049 0 : strcat (buffer, text);
1050 : }
1051 0 : ae = assuan_write_line (ctx, buffer);
1052 : }
1053 0 : else if ( (helpbuf = _assuan_malloc (ctx, n)) )
1054 : {
1055 0 : strcpy (helpbuf, "S ");
1056 0 : strcat (helpbuf, keyword);
1057 0 : if (*text)
1058 : {
1059 0 : strcat (helpbuf, " ");
1060 0 : strcat (helpbuf, text);
1061 : }
1062 0 : ae = assuan_write_line (ctx, helpbuf);
1063 0 : _assuan_free (ctx, helpbuf);
1064 : }
1065 : else
1066 0 : ae = 0;
1067 0 : return ae;
1068 : }
|