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