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