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