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