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