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