Various changes.
[gnupg.git] / assuan / assuan-handler.c
1 /* assuan-handler.c - dispatch commands 
2  *      Copyright (C) 2001 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 2 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, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25
26 #include "assuan-defs.h"
27
28 #define spacep(p)  (*(p) == ' ' || *(p) == '\t')
29 #define digitp(a) ((a) >= '0' && (a) <= '9')
30
31
32 static int
33 dummy_handler (ASSUAN_CONTEXT ctx, char *line)
34 {
35   return set_error (ctx, Server_Fault, "no handler registered");
36 }
37
38
39 static int
40 std_handler_nop (ASSUAN_CONTEXT ctx, char *line)
41 {
42   return 0; /* okay */
43 }
44   
45 static int
46 std_handler_cancel (ASSUAN_CONTEXT ctx, char *line)
47 {
48   if (ctx->cancel_notify_fnc)
49     ctx->cancel_notify_fnc (ctx);
50   return set_error (ctx, Not_Implemented, NULL); 
51 }
52
53 static int
54 std_handler_option (ASSUAN_CONTEXT ctx, char *line)
55 {
56   char *key, *value, *p;
57
58   for (key=line; spacep (key); key++)
59     ;
60   if (!*key)
61     return set_error (ctx, Syntax_Error, "argument required");
62   if (*key == '=')
63     return set_error (ctx, Syntax_Error, "no option name given");
64   for (value=key; *value && !spacep (value) && *value != '='; value++)
65     ;
66   if (*value)
67     {
68       if (spacep (value))
69         *value++ = 0; /* terminate key */
70       for (; spacep (value); value++)
71         ;
72       if (*value == '=')
73         {
74           *value++ = 0; /* terminate key */
75           for (; spacep (value); value++)
76             ;
77           if (!*value)
78             return set_error (ctx, Syntax_Error, "option argument expected");
79         }
80       if (*value)
81         {
82           for (p = value + strlen(value) - 1; p > value && spacep (p); p--)
83             ;
84           if (p > value)
85             *++p = 0; /* strip trailing spaces */
86         }
87     }
88
89   if (*key == '-' && key[1] == '-' && key[2])
90     key += 2; /* the double dashes are optional */
91   if (*key == '-')
92     return set_error (ctx, Syntax_Error,
93                       "option should not begin with one dash");
94
95   if (ctx->option_handler_fnc)
96     return ctx->option_handler_fnc (ctx, key, value);
97   return 0;
98 }
99   
100 static int
101 std_handler_bye (ASSUAN_CONTEXT ctx, char *line)
102 {
103   if (ctx->bye_notify_fnc)
104     ctx->bye_notify_fnc (ctx);
105   assuan_close_input_fd (ctx);
106   assuan_close_output_fd (ctx);
107   return -1; /* pretty simple :-) */
108 }
109   
110 static int
111 std_handler_auth (ASSUAN_CONTEXT ctx, char *line)
112 {
113   return set_error (ctx, Not_Implemented, NULL); 
114 }
115   
116 static int
117 std_handler_reset (ASSUAN_CONTEXT ctx, char *line)
118 {
119   if (ctx->reset_notify_fnc)
120     ctx->reset_notify_fnc (ctx);
121   assuan_close_input_fd (ctx);
122   assuan_close_output_fd (ctx);
123   return 0;
124 }
125   
126 static int
127 std_handler_end (ASSUAN_CONTEXT ctx, char *line)
128 {
129   return set_error (ctx, Not_Implemented, NULL); 
130 }
131
132 static int
133 parse_cmd_input_output (ASSUAN_CONTEXT ctx, char *line, int *rfd)
134 {
135   char *endp;
136
137   if (strncmp (line, "FD=", 3))
138     return set_error (ctx, Syntax_Error, "FD=<n> expected");
139   line += 3;
140   if (!digitp (*line))
141     return set_error (ctx, Syntax_Error, "number required");
142   *rfd = strtoul (line, &endp, 10);
143   /* remove that argument so that a notify handler won't see it */
144   memset (line, ' ', endp? (endp-line):strlen(line));
145
146   if (*rfd == ctx->inbound.fd)
147     return set_error (ctx, Parameter_Conflict, "fd same as inbound fd");
148   if (*rfd == ctx->outbound.fd)
149     return set_error (ctx, Parameter_Conflict, "fd same as outbound fd");
150   return 0;
151 }
152
153 /* Format is INPUT FD=<n> */
154 static int
155 std_handler_input (ASSUAN_CONTEXT ctx, char *line)
156 {
157   int rc, fd;
158
159   rc = parse_cmd_input_output (ctx, line, &fd);
160   if (rc)
161     return rc;
162   ctx->input_fd = fd;
163   if (ctx->input_notify_fnc)
164     ctx->input_notify_fnc (ctx, line);
165   return 0;
166 }
167
168 /* Format is OUTPUT FD=<n> */
169 static int
170 std_handler_output (ASSUAN_CONTEXT ctx, char *line)
171 {
172   int rc, fd;
173
174   rc = parse_cmd_input_output (ctx, line, &fd);
175   if (rc)
176     return rc;
177   ctx->output_fd = fd;
178   if (ctx->output_notify_fnc)
179     ctx->output_notify_fnc (ctx, line);
180   return 0;
181 }
182
183
184
185   
186
187 /* This is a table with the standard commands and handler for them.
188    The table is used to initialize a new context and assuciate strings
189    and handlers with cmd_ids */
190 static struct {
191   const char *name;
192   int cmd_id;
193   int (*handler)(ASSUAN_CONTEXT, char *line);
194   int always; /* always initialize this command */
195 } std_cmd_table[] = {
196   { "NOP",    ASSUAN_CMD_NOP,    std_handler_nop, 1 },
197   { "CANCEL", ASSUAN_CMD_CANCEL, std_handler_cancel, 1 },
198   { "OPTION", ASSUAN_CMD_OPTION, std_handler_option, 1 },
199   { "BYE",    ASSUAN_CMD_BYE,    std_handler_bye, 1 },
200   { "AUTH",   ASSUAN_CMD_AUTH,   std_handler_auth, 1 },
201   { "RESET",  ASSUAN_CMD_RESET,  std_handler_reset, 1 },
202   { "END",    ASSUAN_CMD_END,    std_handler_end, 1 },
203
204   { "INPUT",  ASSUAN_CMD_INPUT,  std_handler_input },
205   { "OUTPUT", ASSUAN_CMD_OUTPUT, std_handler_output },
206   { "OPTION", ASSUAN_CMD_OPTION, std_handler_option, 1 },
207   { NULL }
208 };
209
210
211 /**
212  * assuan_register_command:
213  * @ctx: the server context
214  * @cmd_id: An ID value for the command
215  * @cmd_name: A string with the command name
216  * @handler: The handler function to be called
217  * 
218  * Register a handler to be used for a given command.
219  * 
220  * The @cmd_name must be %NULL or an empty string for all @cmd_ids
221  * below %ASSUAN_CMD_USER because predefined values are used.
222  * 
223  * Return value: 
224  **/
225 int
226 assuan_register_command (ASSUAN_CONTEXT ctx,
227                          int cmd_id, const char *cmd_name,
228                          int (*handler)(ASSUAN_CONTEXT, char *))
229 {
230   int i;
231
232   if (cmd_name && !*cmd_name)
233     cmd_name = NULL;
234
235   if (cmd_id < ASSUAN_CMD_USER)
236     { 
237       if (cmd_name)
238         return ASSUAN_Invalid_Value; /* must be NULL for these values*/
239
240       for (i=0; std_cmd_table[i].name; i++)
241         {
242           if (std_cmd_table[i].cmd_id == cmd_id)
243             {
244               cmd_name = std_cmd_table[i].name;
245               if (!handler)
246                 handler = std_cmd_table[i].handler;
247               break;
248             }
249         }
250       if (!std_cmd_table[i].name)
251         return ASSUAN_Invalid_Value; /* not a pre-registered one */
252     }
253   
254   if (!handler)
255     handler = dummy_handler;
256
257   if (!cmd_name)
258     return ASSUAN_Invalid_Value;
259
260 /*    fprintf (stderr, "DBG-assuan: registering %d as `%s'\n", cmd_id, cmd_name); */
261
262   if (!ctx->cmdtbl)
263     {
264       ctx->cmdtbl_size = 50;
265       ctx->cmdtbl = xtrycalloc ( ctx->cmdtbl_size, sizeof *ctx->cmdtbl);
266       if (!ctx->cmdtbl)
267         return ASSUAN_Out_Of_Core;
268       ctx->cmdtbl_used = 0;
269     }
270   else if (ctx->cmdtbl_used >= ctx->cmdtbl_size)
271     {
272       struct cmdtbl_s *x;
273
274       x = xtryrealloc ( ctx->cmdtbl, (ctx->cmdtbl_size+10) * sizeof *x);
275       if (!x)
276         return ASSUAN_Out_Of_Core;
277       ctx->cmdtbl = x;
278       ctx->cmdtbl_size += 50;
279     }
280
281   ctx->cmdtbl[ctx->cmdtbl_used].name = cmd_name;
282   ctx->cmdtbl[ctx->cmdtbl_used].cmd_id = cmd_id;
283   ctx->cmdtbl[ctx->cmdtbl_used].handler = handler;
284   ctx->cmdtbl_used++;
285   return 0;
286 }
287
288 int
289 assuan_register_bye_notify (ASSUAN_CONTEXT ctx, void (*fnc)(ASSUAN_CONTEXT))
290 {
291   if (!ctx)
292     return ASSUAN_Invalid_Value;
293   ctx->bye_notify_fnc = fnc;
294   return 0;
295 }
296
297 int
298 assuan_register_reset_notify (ASSUAN_CONTEXT ctx, void (*fnc)(ASSUAN_CONTEXT))
299 {
300   if (!ctx)
301     return ASSUAN_Invalid_Value;
302   ctx->reset_notify_fnc = fnc;
303   return 0;
304 }
305
306 int
307 assuan_register_cancel_notify (ASSUAN_CONTEXT ctx, void (*fnc)(ASSUAN_CONTEXT))
308 {
309   if (!ctx)
310     return ASSUAN_Invalid_Value;
311   ctx->cancel_notify_fnc = fnc;
312   return 0;
313 }
314
315 int
316 assuan_register_option_handler (ASSUAN_CONTEXT ctx,
317                                int (*fnc)(ASSUAN_CONTEXT,
318                                           const char*, const char*))
319 {
320   if (!ctx)
321     return ASSUAN_Invalid_Value;
322   ctx->option_handler_fnc = fnc;
323   return 0;
324 }
325
326 int
327 assuan_register_input_notify (ASSUAN_CONTEXT ctx,
328                               void (*fnc)(ASSUAN_CONTEXT, const char *))
329 {
330   if (!ctx)
331     return ASSUAN_Invalid_Value;
332   ctx->input_notify_fnc = fnc;
333   return 0;
334 }
335
336 int
337 assuan_register_output_notify (ASSUAN_CONTEXT ctx,
338                               void (*fnc)(ASSUAN_CONTEXT, const char *))
339 {
340   if (!ctx)
341     return ASSUAN_Invalid_Value;
342   ctx->output_notify_fnc = fnc;
343   return 0;
344 }
345
346
347 /* Helper to register the standards commands */
348 int
349 _assuan_register_std_commands (ASSUAN_CONTEXT ctx)
350 {
351   int i, rc;
352
353   for (i=0; std_cmd_table[i].name; i++)
354     {
355       if (std_cmd_table[i].always)
356         {
357           rc = assuan_register_command (ctx, std_cmd_table[i].cmd_id,
358                                         NULL, NULL);
359           if (rc)
360             return rc;
361         }
362     } 
363   return 0;
364 }
365
366
367 \f
368 /* Process the special data lines.  The "D " has already been removed
369    from the line.  As all handlers this function may modify the line.  */
370 static int
371 handle_data_line (ASSUAN_CONTEXT ctx, char *line, int linelen)
372 {
373   return set_error (ctx, Not_Implemented, NULL);
374 }
375
376 /* like ascii_strcasecmp but assume that B is already uppercase */
377 static int
378 my_strcasecmp (const char *a, const char *b)
379 {
380     if (a == b)
381         return 0;
382
383     for (; *a && *b; a++, b++)
384       {
385         if (((*a >= 'a' && *a <= 'z')? (*a&~0x20):*a) != *b)
386             break;
387       }
388     return *a == *b? 0 : (((*a >= 'a' && *a <= 'z')? (*a&~0x20):*a) - *b);
389 }
390
391 /* Parse the line, break out the command, find it in the command
392    table, remove leading and white spaces from the arguments, all the
393    handler with the argument line and return the error */
394 static int 
395 dispatch_command (ASSUAN_CONTEXT ctx, char *line, int linelen)
396 {
397   char *p;
398   const char *s;
399   int shift, i;
400
401   if (*line == 'D' && line[1] == ' ') /* divert to special handler */
402     return handle_data_line (ctx, line+2, linelen-2);
403
404   for (p=line; *p && *p != ' ' && *p != '\t'; p++)
405     ;
406   if (p==line)
407     return set_error (ctx, Invalid_Command, "leading white-space"); 
408   if (*p) 
409     { /* Skip over leading WS after the keyword */
410       *p++ = 0;
411       while ( *p == ' ' || *p == '\t')
412         p++;
413     }
414   shift = p - line;
415
416   for (i=0; (s=ctx->cmdtbl[i].name); i++)
417     {
418       if (!strcmp (line, s))
419         break;
420     }
421   if (!s)
422     { /* and try case insensitive */
423       for (i=0; (s=ctx->cmdtbl[i].name); i++)
424         {
425           if (!my_strcasecmp (line, s))
426             break;
427         }
428     }
429   if (!s)
430     return set_error (ctx, Unknown_Command, NULL);
431   line += shift;
432   linelen -= shift;
433
434 /*    fprintf (stderr, "DBG-assuan: processing %s `%s'\n", s, line); */
435   return ctx->cmdtbl[i].handler (ctx, line);
436 }
437
438
439
440 \f
441 static int
442 process_request (ASSUAN_CONTEXT ctx)
443 {
444   int rc;
445
446   if (ctx->in_inquire)
447     return ASSUAN_Nested_Commands;
448
449   rc = _assuan_read_line (ctx);
450   if (rc)
451     return rc;
452   if (*ctx->inbound.line == '#' || !ctx->inbound.linelen)
453     return 0; /* comment line - ignore */
454
455   ctx->outbound.data.error = 0;
456   ctx->outbound.data.linelen = 0;
457   /* dispatch command and return reply */
458   rc = dispatch_command (ctx, ctx->inbound.line, ctx->inbound.linelen);
459   /* check from data write errors */
460   if (ctx->outbound.data.fp)
461     { /* Flush the data lines */
462       fclose (ctx->outbound.data.fp);
463       ctx->outbound.data.fp = NULL;
464       if (!rc && ctx->outbound.data.error)
465         rc = ctx->outbound.data.error;
466     }
467   else /* flush any data send w/o using the data fp */
468     {
469       assuan_send_data (ctx, NULL, 0);
470       if (!rc && ctx->outbound.data.error)
471         rc = ctx->outbound.data.error;
472     }
473   /* Error handling */
474   if (!rc)
475     {
476       rc = assuan_write_line (ctx, ctx->okay_line? ctx->okay_line : "OK");
477     }
478   else if (rc == -1)
479     { /* No error checking because the peer may have already disconnect */ 
480       assuan_write_line (ctx, "OK closing connection");
481       ctx->finish_handler (ctx);
482     }
483   else 
484     {
485       char errline[256];
486
487       if (rc < 100)
488         sprintf (errline, "ERR %d server fault (%.50s)",
489                  ASSUAN_Server_Fault, assuan_strerror (rc));
490       else
491         {
492           const char *text = ctx->err_no == rc? ctx->err_str:NULL;
493
494           sprintf (errline, "ERR %d %.50s%s%.100s",
495                    rc, assuan_strerror (rc), text? " - ":"", text?text:"");
496         }
497       rc = assuan_write_line (ctx, errline);
498     }
499
500   ctx->confidential = 0;
501   if (ctx->okay_line)
502     {
503       xfree (ctx->okay_line);
504       ctx->okay_line = NULL;
505     }
506   return rc;
507 }
508
509 /**
510  * assuan_process:
511  * @ctx: assuan context
512  * 
513  * This fucntion is used to handle the assuan protocol after a
514  * connection has been established using assuan_accept().  This is the
515  * main protocol handler.
516  * 
517  * Return value: 0 on success or an error code if the assuan operation
518  * failed.  Note, that no error is returned for operational errors.
519  **/
520 int
521 assuan_process (ASSUAN_CONTEXT ctx)
522 {
523   int rc;
524
525   do {
526     rc = process_request (ctx);
527   } while (!rc);
528
529   if (rc == -1)
530     rc = 0;
531
532   return rc;
533 }
534
535
536 /**
537  * assuan_process_next:
538  * @ctx: Assuan context
539  * 
540  * Same as assuan_process() but the user has to provide the outer
541  * loop.  He should loop as long as the return code is zero and stop
542  * otherwise; -1 is regular end.
543  * 
544  * See also: assuan_get_active_fds()
545  * Return value: -1 for end of server, 0 on success or an error code
546  **/
547 int 
548 assuan_process_next (ASSUAN_CONTEXT ctx)
549 {
550   return process_request (ctx);
551 }
552
553
554 /**
555  * assuan_get_active_fds:
556  * @ctx: Assuan context
557  * @what: 0 for read fds, 1 for write fds
558  * @fdarray: Caller supplied array to store the FDs
559  * @fdarraysize: size of that array
560  * 
561  * Return all active filedescriptors for the given context.  This
562  * function can be used to select on the fds and call
563  * assuan_process_next() if there is an active one.  The first fd in
564  * the array is the one used for the command connection.
565  *
566  * Note, that write FDs are not yet supported.
567  * 
568  * Return value: number of FDs active and put into @fdarray or -1 on
569  * error which is most likely a too small fdarray.
570  **/
571 int 
572 assuan_get_active_fds (ASSUAN_CONTEXT ctx, int what,
573                        int *fdarray, int fdarraysize)
574 {
575   int n = 0;
576
577   if (!ctx || fdarraysize < 2 || what < 0 || what > 1)
578     return -1;
579
580   if (!what)
581     {
582       if (ctx->inbound.fd != -1)
583         fdarray[n++] = ctx->inbound.fd;
584     }
585   else
586     {
587       if (ctx->outbound.fd != -1)
588         fdarray[n++] = ctx->outbound.fd;
589       if (ctx->outbound.data.fp)
590         fdarray[n++] = fileno (ctx->outbound.data.fp);
591     }
592
593   return n;
594 }
595
596 /* Return a FP to be used for data output.  The FILE pointer is valid
597    until the end of a handler.  So a close is not needed.  Assuan does
598    all the buffering needed to insert the status line as well as the
599    required line wappping and quoting for data lines.
600
601    We use GNU's custom streams here.  There should be an alternative
602    implementaion for systems w/o a glibc, a simple implementation
603    could use a child process */
604 FILE *
605 assuan_get_data_fp (ASSUAN_CONTEXT ctx)
606 {
607   cookie_io_functions_t cookie_fnc;
608
609   if (ctx->outbound.data.fp)
610     return ctx->outbound.data.fp;
611   
612   cookie_fnc.read = NULL; 
613   cookie_fnc.write = _assuan_cookie_write_data;
614   cookie_fnc.seek = NULL;
615   cookie_fnc.close = _assuan_cookie_write_flush;
616
617   ctx->outbound.data.fp = fopencookie (ctx, "wb", cookie_fnc);
618   ctx->outbound.data.error = 0;
619   return ctx->outbound.data.fp;
620 }
621
622
623 /* Set the text used for the next OK reponse.  This string is
624    automatically reset to NULL after the next command. */
625 AssuanError
626 assuan_set_okay_line (ASSUAN_CONTEXT ctx, const char *line)
627 {
628   if (!ctx)
629     return ASSUAN_Invalid_Value;
630   if (!line)
631     {
632       xfree (ctx->okay_line);
633       ctx->okay_line = NULL;
634     }
635   else
636     {
637       /* FIXME: we need to use gcry_is_secure() to test whether
638          we should allocate the entire line in secure memory */
639       char *buf = xtrymalloc (3+strlen(line)+1);
640       if (!buf)
641         return ASSUAN_Out_Of_Core;
642       strcpy (buf, "OK ");
643       strcpy (buf+3, line);
644       xfree (ctx->okay_line);
645       ctx->okay_line = buf;
646     }
647   return 0;
648 }
649
650
651
652 void
653 assuan_write_status (ASSUAN_CONTEXT ctx, const char *keyword, const char *text)
654 {
655   char buffer[256];
656   char *helpbuf;
657   size_t n;
658
659   if ( !ctx || !keyword)
660     return;
661   if (!text)
662     text = "";
663
664   n = 2 + strlen (keyword) + 1 + strlen (text) + 1;
665   if (n < sizeof (buffer))
666     {
667       strcpy (buffer, "S ");
668       strcat (buffer, keyword);
669       if (*text)
670         {
671           strcat (buffer, " ");
672           strcat (buffer, text);
673         }
674       assuan_write_line (ctx, buffer);
675     }
676   else if ( (helpbuf = xtrymalloc (n)) )
677     {
678       strcpy (helpbuf, "S ");
679       strcat (helpbuf, keyword);
680       if (*text)
681         {
682           strcat (helpbuf, " ");
683           strcat (helpbuf, text);
684         }
685       assuan_write_line (ctx, helpbuf);
686       xfree (helpbuf);
687     }
688 }