* assuan.h: Prototype assuan_pipe_connect and assuan_pipe_disconnect.
[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   return set_error (ctx, Not_Implemented, NULL); 
48 }
49   
50 static int
51 std_handler_bye (ASSUAN_CONTEXT ctx, char *line)
52 {
53   return -1; /* pretty simple :-) */
54 }
55   
56 static int
57 std_handler_auth (ASSUAN_CONTEXT ctx, char *line)
58 {
59   return set_error (ctx, Not_Implemented, NULL); 
60 }
61   
62 static int
63 std_handler_reset (ASSUAN_CONTEXT ctx, char *line)
64 {
65   return set_error (ctx, Not_Implemented, NULL); 
66 }
67   
68 static int
69 std_handler_end (ASSUAN_CONTEXT ctx, char *line)
70 {
71   return set_error (ctx, Not_Implemented, NULL); 
72 }
73
74 static int
75 parse_cmd_input_output (ASSUAN_CONTEXT ctx, char *line, int *rfd)
76 {
77   char *endp;
78
79   if (strncmp (line, "FD=", 3))
80     return set_error (ctx, Syntax_Error, "FD=<n> expected");
81   line += 3;
82   if (!digitp (*line))
83     return set_error (ctx, Syntax_Error, "number required");
84   *rfd = strtoul (line, &endp, 10);
85   if (*endp)
86     return set_error (ctx, Syntax_Error, "garbage found");
87   if (*rfd == ctx->inbound.fd)
88     return set_error (ctx, Parameter_Conflict, "fd same as inbound fd");
89   if (*rfd == ctx->outbound.fd)
90     return set_error (ctx, Parameter_Conflict, "fd same as outbound fd");
91   return 0;
92 }
93
94 /* Format is INPUT FD=<n> */
95 static int
96 std_handler_input (ASSUAN_CONTEXT ctx, char *line)
97 {
98   int rc, fd;
99
100   rc = parse_cmd_input_output (ctx, line, &fd);
101   if (rc)
102     return rc;
103   ctx->input_fd = fd;
104   return 0;
105 }
106
107 /* Format is OUTPUT FD=<n> */
108 static int
109 std_handler_output (ASSUAN_CONTEXT ctx, char *line)
110 {
111   int rc, fd;
112
113   rc = parse_cmd_input_output (ctx, line, &fd);
114   if (rc)
115     return rc;
116   ctx->output_fd = fd;
117   return 0;
118 }
119
120
121
122   
123
124 /* This is a table with the standard commands and handler for them.
125    The table is used to initialize a new context and assuciate strings
126    and handlers with cmd_ids */
127 static struct {
128   const char *name;
129   int cmd_id;
130   int (*handler)(ASSUAN_CONTEXT, char *line);
131   int always; /* always initializethis command */
132 } std_cmd_table[] = {
133   { "NOP",    ASSUAN_CMD_NOP,    std_handler_nop, 1 },
134   { "CANCEL", ASSUAN_CMD_CANCEL, std_handler_cancel, 1 },
135   { "BYE",    ASSUAN_CMD_BYE,    std_handler_bye, 1 },
136   { "AUTH",   ASSUAN_CMD_AUTH,   std_handler_auth, 1 },
137   { "RESET",  ASSUAN_CMD_RESET,  std_handler_reset, 1 },
138   { "END",    ASSUAN_CMD_END,    std_handler_end, 1 },
139
140   { "INPUT",  ASSUAN_CMD_INPUT,  std_handler_input },
141   { "OUTPUT", ASSUAN_CMD_OUTPUT, std_handler_output },
142   { NULL }
143 };
144
145
146 /**
147  * assuan_register_command:
148  * @ctx: the server context
149  * @cmd_id: An ID value for the command
150  * @cmd_name: A string with the command name
151  * @handler: The handler function to be called
152  * 
153  * Register a handler to be used for a given command.
154  * 
155  * The @cmd_name must be %NULL or an empty string for all @cmd_ids
156  * below %ASSUAN_CMD_USER because predefined values are used.
157  * 
158  * Return value: 
159  **/
160 int
161 assuan_register_command (ASSUAN_CONTEXT ctx,
162                          int cmd_id, const char *cmd_name,
163                          int (*handler)(ASSUAN_CONTEXT, char *))
164 {
165   int i;
166
167   if (cmd_name && !*cmd_name)
168     cmd_name = NULL;
169
170   if (cmd_id < ASSUAN_CMD_USER)
171     { 
172       if (cmd_name)
173         return ASSUAN_Invalid_Value; /* must be NULL for these values*/
174
175       for (i=0; std_cmd_table[i].name; i++)
176         {
177           if (std_cmd_table[i].cmd_id == cmd_id)
178             {
179               cmd_name = std_cmd_table[i].name;
180               if (!handler)
181                 handler = std_cmd_table[i].handler;
182               break;
183             }
184         }
185       if (!std_cmd_table[i].name)
186         return ASSUAN_Invalid_Value; /* not a pre-registered one */
187     }
188   
189   if (!handler)
190     handler = dummy_handler;
191
192   if (!cmd_name)
193     return ASSUAN_Invalid_Value;
194
195 /*    fprintf (stderr, "DBG-assuan: registering %d as `%s'\n", cmd_id, cmd_name); */
196
197   if (!ctx->cmdtbl)
198     {
199       ctx->cmdtbl_size = 50;
200       ctx->cmdtbl = xtrycalloc ( ctx->cmdtbl_size, sizeof *ctx->cmdtbl);
201       if (!ctx->cmdtbl)
202         return ASSUAN_Out_Of_Core;
203       ctx->cmdtbl_used = 0;
204     }
205   else if (ctx->cmdtbl_used >= ctx->cmdtbl_size)
206     {
207       struct cmdtbl_s *x;
208
209       x = xtryrealloc ( ctx->cmdtbl, (ctx->cmdtbl_size+10) * sizeof *x);
210       if (!x)
211         return ASSUAN_Out_Of_Core;
212       ctx->cmdtbl = x;
213       ctx->cmdtbl_size += 50;
214     }
215
216   ctx->cmdtbl[ctx->cmdtbl_used].name = cmd_name;
217   ctx->cmdtbl[ctx->cmdtbl_used].cmd_id = cmd_id;
218   ctx->cmdtbl[ctx->cmdtbl_used].handler = handler;
219   ctx->cmdtbl_used++;
220   return 0;
221 }
222
223 /* Helper to register the standards commands */
224 int
225 _assuan_register_std_commands (ASSUAN_CONTEXT ctx)
226 {
227   int i, rc;
228
229   for (i=0; std_cmd_table[i].name; i++)
230     {
231       if (std_cmd_table[i].always)
232         {
233           rc = assuan_register_command (ctx, std_cmd_table[i].cmd_id,
234                                         NULL, NULL);
235           if (rc)
236             return rc;
237         }
238     } 
239   return 0;
240 }
241
242
243 \f
244 /* Process the special data lines.  The "D " has already been removed
245    from the line.  As all handlers this function may modify the line.  */
246 static int
247 handle_data_line (ASSUAN_CONTEXT ctx, char *line, int linelen)
248 {
249   return set_error (ctx, Not_Implemented, NULL);
250 }
251
252
253 /* Parse the line, break out the command, find it in the command
254    table, remove leading and white spaces from the arguments, all the
255    handler with the argument line and return the error */
256 static int 
257 dispatch_command (ASSUAN_CONTEXT ctx, char *line, int linelen)
258 {
259   char *p;
260   const char *s;
261   int shift, i;
262
263   if (*line == 'D' && line[1] == ' ') /* divert to special handler */
264     return handle_data_line (ctx, line+2, linelen-2);
265
266   for (p=line; *p && *p != ' ' && *p != '\t'; p++)
267     ;
268   if (p==line)
269     return set_error (ctx, Invalid_Command, "leading white-space"); 
270   if (*p) 
271     { /* Skip over leading WS after the keyword */
272       *p++ = 0;
273       while ( *p == ' ' || *p == '\t')
274         p++;
275     }
276   shift = p - line;
277
278   for (i=0; (s=ctx->cmdtbl[i].name); i++)
279     if (!strcmp (line, s))
280       break;
281   if (!s)
282     return set_error (ctx, Unknown_Command, NULL);
283   line += shift;
284   linelen -= shift;
285
286 /*    fprintf (stderr, "DBG-assuan: processing %s `%s'\n", s, line); */
287   return ctx->cmdtbl[i].handler (ctx, line);
288 }
289
290
291
292
293 /**
294  * assuan_process:
295  * @ctx: assuan context
296  * 
297  * This fucntion is used to handle the assuan protocol after a
298  * connection has been established using assuan_accept().  This is the
299  * main protocol handler.
300  * 
301  * Return value: 0 on success or an error code if the assuan operation
302  * failed.  Note, that no error is returned for operational errors.
303  **/
304 int
305 assuan_process (ASSUAN_CONTEXT ctx)
306 {
307   int rc;
308
309   do {
310     /* Read the line but skip comments */
311     do
312       {
313         rc = _assuan_read_line (ctx);
314         if (rc)
315           return rc;
316       
317 /*          fprintf (stderr, "DBG-assuan: got %d bytes `%s'\n", */
318 /*                   ctx->inbound.linelen, ctx->inbound.line); */
319       }
320     while ( *ctx->inbound.line == '#' || !ctx->inbound.linelen);
321
322     ctx->outbound.data.error = 0;
323     ctx->outbound.data.linelen = 0;
324     /* dispatch command and return reply */
325     rc = dispatch_command (ctx, ctx->inbound.line, ctx->inbound.linelen);
326     /* check from data write errors */
327     if (ctx->outbound.data.fp)
328       { /* Flush the data lines */
329         fclose (ctx->outbound.data.fp);
330         ctx->outbound.data.fp = NULL;
331         if (!rc && ctx->outbound.data.error)
332           rc = ctx->outbound.data.error;
333       }
334     /* Error handling */
335     if (!rc)
336       {
337         rc = _assuan_write_line (ctx, "OK");
338       }
339     else if (rc == -1)
340       { /* No error checking because the peer may have already disconnect */ 
341         _assuan_write_line (ctx, "OK  Bye, bye - hope to meet you again");
342       }
343     else 
344       {
345         char errline[256];
346
347         if (rc < 100)
348           sprintf (errline, "ERR %d server fault (%.50s)",
349                    ASSUAN_Server_Fault, assuan_strerror (rc));
350         else
351           {
352             const char *text = ctx->err_no == rc? ctx->err_str:NULL;
353
354             sprintf (errline, "ERR %d %.50s%s%.100s",
355                      rc, assuan_strerror (rc), text? " - ":"", text?text:"");
356           }
357         rc = _assuan_write_line (ctx, errline);
358       }
359   } while (!rc);
360
361   if (rc == -1)
362     rc = 0;
363
364   return rc;
365 }
366
367
368 /* Return a FP to be used for data output.  The FILE pointer is valid
369    until the end of a handler.  So a close is not needed.  Assuan does
370    all the buffering needed to insert the status line as well as the
371    required line wappping and quoting for data lines.
372
373    We use GNU's custom streams here.  There should be an alternative
374    implementaion for systems w/o a glibc, a simple implementation
375    could use a child process */
376 FILE *
377 assuan_get_data_fp (ASSUAN_CONTEXT ctx)
378 {
379   cookie_io_functions_t cookie_fnc;
380
381   if (ctx->outbound.data.fp)
382     return ctx->outbound.data.fp;
383   
384   cookie_fnc.read = NULL; 
385   cookie_fnc.write = _assuan_cookie_write_data;
386   cookie_fnc.seek = NULL;
387   cookie_fnc.close = _assuan_cookie_write_flush;
388
389   ctx->outbound.data.fp = fopencookie (ctx, "wb", cookie_fnc);
390   ctx->outbound.data.error = 0;
391   return ctx->outbound.data.fp;
392 }
393
394
395 void
396 assuan_write_status (ASSUAN_CONTEXT ctx, const char *keyword, const char *text)
397 {
398   char buffer[256];
399   char *helpbuf;
400   size_t n;
401
402   if ( !ctx || !keyword)
403     return;
404   if (!text)
405     text = "";
406
407   n = 2 + strlen (keyword) + 1 + strlen (text) + 1;
408   if (n < sizeof (buffer))
409     {
410       strcpy (buffer, "S ");
411       strcat (buffer, keyword);
412       if (*text)
413         {
414           strcat (buffer, " ");
415           strcat (buffer, text);
416         }
417       _assuan_write_line (ctx, buffer);
418     }
419   else if ( (helpbuf = xtrymalloc (n)) )
420     {
421       strcpy (helpbuf, "S ");
422       strcat (helpbuf, keyword);
423       if (*text)
424         {
425           strcat (helpbuf, " ");
426           strcat (helpbuf, text);
427         }
428       _assuan_write_line (ctx, helpbuf);
429       xfree (helpbuf);
430     }
431 }