Set DISPLAY environment variable if --display option
[pinentry-qt.git] / 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 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27
28 #include "assuan-defs.h"
29
30 #define spacep(p)  (*(p) == ' ' || *(p) == '\t')
31 #define digitp(a) ((a) >= '0' && (a) <= '9')
32
33
34 static int
35 dummy_handler (ASSUAN_CONTEXT ctx, char *line)
36 {
37   return set_error (ctx, Server_Fault, "no handler registered");
38 }
39
40
41 static int
42 std_handler_nop (ASSUAN_CONTEXT ctx, char *line)
43 {
44   return 0; /* okay */
45 }
46   
47 static int
48 std_handler_cancel (ASSUAN_CONTEXT ctx, char *line)
49 {
50   if (ctx->cancel_notify_fnc)
51     ctx->cancel_notify_fnc (ctx);
52   return set_error (ctx, Not_Implemented, NULL); 
53 }
54
55 static int
56 std_handler_option (ASSUAN_CONTEXT ctx, char *line)
57 {
58   char *key, *value, *p;
59
60   for (key=line; spacep (key); key++)
61     ;
62   if (!*key)
63     return set_error (ctx, Syntax_Error, "argument required");
64   if (*key == '=')
65     return set_error (ctx, Syntax_Error, "no option name given");
66   for (value=key; *value && !spacep (value) && *value != '='; value++)
67     ;
68   if (*value)
69     {
70       if (spacep (value))
71         *value++ = 0; /* terminate key */
72       for (; spacep (value); value++)
73         ;
74       if (*value == '=')
75         {
76           *value++ = 0; /* terminate key */
77           for (; spacep (value); value++)
78             ;
79           if (!*value)
80             return set_error (ctx, Syntax_Error, "option argument expected");
81         }
82       if (*value)
83         {
84           for (p = value + strlen(value) - 1; p > value && spacep (p); p--)
85             ;
86           if (p > value)
87             *++p = 0; /* strip trailing spaces */
88         }
89     }
90
91   if (*key == '-' && key[1] == '-' && key[2])
92     key += 2; /* the double dashes are optional */
93   if (*key == '-')
94     return set_error (ctx, Syntax_Error,
95                       "option should not begin with one dash");
96
97   if (ctx->option_handler_fnc)
98     return ctx->option_handler_fnc (ctx, key, value);
99   return 0;
100 }
101   
102 static int
103 std_handler_bye (ASSUAN_CONTEXT ctx, char *line)
104 {
105   if (ctx->bye_notify_fnc)
106     ctx->bye_notify_fnc (ctx);
107   assuan_close_input_fd (ctx);
108   assuan_close_output_fd (ctx);
109   return -1; /* pretty simple :-) */
110 }
111   
112 static int
113 std_handler_auth (ASSUAN_CONTEXT ctx, char *line)
114 {
115   return set_error (ctx, Not_Implemented, NULL); 
116 }
117   
118 static int
119 std_handler_reset (ASSUAN_CONTEXT ctx, char *line)
120 {
121   if (ctx->reset_notify_fnc)
122     ctx->reset_notify_fnc (ctx);
123   assuan_close_input_fd (ctx);
124   assuan_close_output_fd (ctx);
125   return 0;
126 }
127   
128 static int
129 std_handler_end (ASSUAN_CONTEXT ctx, char *line)
130 {
131   return set_error (ctx, Not_Implemented, NULL); 
132 }
133
134 static int
135 parse_cmd_input_output (ASSUAN_CONTEXT ctx, char *line, int *rfd)
136 {
137   char *endp;
138
139   if (strncmp (line, "FD=", 3))
140     return set_error (ctx, Syntax_Error, "FD=<n> expected");
141   line += 3;
142   if (!digitp (*line))
143     return set_error (ctx, Syntax_Error, "number required");
144   *rfd = strtoul (line, &endp, 10);
145   /* remove that argument so that a notify handler won't see it */
146   memset (line, ' ', endp? (endp-line):strlen(line));
147
148   if (*rfd == ctx->inbound.fd)
149     return set_error (ctx, Parameter_Conflict, "fd same as inbound fd");
150   if (*rfd == ctx->outbound.fd)
151     return set_error (ctx, Parameter_Conflict, "fd same as outbound fd");
152   return 0;
153 }
154
155 /* Format is INPUT FD=<n> */
156 static int
157 std_handler_input (ASSUAN_CONTEXT ctx, char *line)
158 {
159   int rc, fd;
160
161   rc = parse_cmd_input_output (ctx, line, &fd);
162   if (rc)
163     return rc;
164   ctx->input_fd = fd;
165   if (ctx->input_notify_fnc)
166     ctx->input_notify_fnc (ctx, line);
167   return 0;
168 }
169
170 /* Format is OUTPUT FD=<n> */
171 static int
172 std_handler_output (ASSUAN_CONTEXT ctx, char *line)
173 {
174   int rc, fd;
175
176   rc = parse_cmd_input_output (ctx, line, &fd);
177   if (rc)
178     return rc;
179   ctx->output_fd = fd;
180   if (ctx->output_notify_fnc)
181     ctx->output_notify_fnc (ctx, line);
182   return 0;
183 }
184
185
186
187   
188
189 /* This is a table with the standard commands and handler for them.
190    The table is used to initialize a new context and assuciate strings
191    and handlers with cmd_ids */
192 static struct {
193   const char *name;
194   int cmd_id;
195   int (*handler)(ASSUAN_CONTEXT, char *line);
196   int always; /* always initialize this command */
197 } std_cmd_table[] = {
198   { "NOP",    ASSUAN_CMD_NOP,    std_handler_nop, 1 },
199   { "CANCEL", ASSUAN_CMD_CANCEL, std_handler_cancel, 1 },
200   { "OPTION", ASSUAN_CMD_OPTION, std_handler_option, 1 },
201   { "BYE",    ASSUAN_CMD_BYE,    std_handler_bye, 1 },
202   { "AUTH",   ASSUAN_CMD_AUTH,   std_handler_auth, 1 },
203   { "RESET",  ASSUAN_CMD_RESET,  std_handler_reset, 1 },
204   { "END",    ASSUAN_CMD_END,    std_handler_end, 1 },
205
206   { "INPUT",  ASSUAN_CMD_INPUT,  std_handler_input },
207   { "OUTPUT", ASSUAN_CMD_OUTPUT, std_handler_output },
208   { "OPTION", ASSUAN_CMD_OPTION, std_handler_option, 1 },
209   { NULL }
210 };
211
212
213 /**
214  * assuan_register_command:
215  * @ctx: the server context
216  * @cmd_id: An ID value for the command
217  * @cmd_name: A string with the command name
218  * @handler: The handler function to be called
219  * 
220  * Register a handler to be used for a given command.
221  * 
222  * The @cmd_name must be %NULL or an empty string for all @cmd_ids
223  * below %ASSUAN_CMD_USER because predefined values are used.
224  * 
225  * Return value: 
226  **/
227 int
228 assuan_register_command (ASSUAN_CONTEXT ctx,
229                          int cmd_id, const char *cmd_name,
230                          int (*handler)(ASSUAN_CONTEXT, char *))
231 {
232   int i;
233
234   if (cmd_name && !*cmd_name)
235     cmd_name = NULL;
236
237   if (cmd_id < ASSUAN_CMD_USER)
238     { 
239       if (cmd_name)
240         return ASSUAN_Invalid_Value; /* must be NULL for these values*/
241
242       for (i=0; std_cmd_table[i].name; i++)
243         {
244           if (std_cmd_table[i].cmd_id == cmd_id)
245             {
246               cmd_name = std_cmd_table[i].name;
247               if (!handler)
248                 handler = std_cmd_table[i].handler;
249               break;
250             }
251         }
252       if (!std_cmd_table[i].name)
253         return ASSUAN_Invalid_Value; /* not a pre-registered one */
254     }
255   
256   if (!handler)
257     handler = dummy_handler;
258
259   if (!cmd_name)
260     return ASSUAN_Invalid_Value;
261
262 /*    fprintf (stderr, "DBG-assuan: registering %d as `%s'\n", cmd_id, cmd_name); */
263
264   if (!ctx->cmdtbl)
265     {
266       ctx->cmdtbl_size = 50;
267       ctx->cmdtbl = xtrycalloc ( ctx->cmdtbl_size, sizeof *ctx->cmdtbl);
268       if (!ctx->cmdtbl)
269         return ASSUAN_Out_Of_Core;
270       ctx->cmdtbl_used = 0;
271     }
272   else if (ctx->cmdtbl_used >= ctx->cmdtbl_size)
273     {
274       struct cmdtbl_s *x;
275
276       x = xtryrealloc ( ctx->cmdtbl, (ctx->cmdtbl_size+10) * sizeof *x);
277       if (!x)
278         return ASSUAN_Out_Of_Core;
279       ctx->cmdtbl = x;
280       ctx->cmdtbl_size += 50;
281     }
282
283   ctx->cmdtbl[ctx->cmdtbl_used].name = cmd_name;
284   ctx->cmdtbl[ctx->cmdtbl_used].cmd_id = cmd_id;
285   ctx->cmdtbl[ctx->cmdtbl_used].handler = handler;
286   ctx->cmdtbl_used++;
287   return 0;
288 }
289
290 int
291 assuan_register_bye_notify (ASSUAN_CONTEXT ctx, void (*fnc)(ASSUAN_CONTEXT))
292 {
293   if (!ctx)
294     return ASSUAN_Invalid_Value;
295   ctx->bye_notify_fnc = fnc;
296   return 0;
297 }
298
299 int
300 assuan_register_reset_notify (ASSUAN_CONTEXT ctx, void (*fnc)(ASSUAN_CONTEXT))
301 {
302   if (!ctx)
303     return ASSUAN_Invalid_Value;
304   ctx->reset_notify_fnc = fnc;
305   return 0;
306 }
307
308 int
309 assuan_register_cancel_notify (ASSUAN_CONTEXT ctx, void (*fnc)(ASSUAN_CONTEXT))
310 {
311   if (!ctx)
312     return ASSUAN_Invalid_Value;
313   ctx->cancel_notify_fnc = fnc;
314   return 0;
315 }
316
317 int
318 assuan_register_option_handler (ASSUAN_CONTEXT ctx,
319                                int (*fnc)(ASSUAN_CONTEXT,
320                                           const char*, const char*))
321 {
322   if (!ctx)
323     return ASSUAN_Invalid_Value;
324   ctx->option_handler_fnc = fnc;
325   return 0;
326 }
327
328 int
329 assuan_register_input_notify (ASSUAN_CONTEXT ctx,
330                               void (*fnc)(ASSUAN_CONTEXT, const char *))
331 {
332   if (!ctx)
333     return ASSUAN_Invalid_Value;
334   ctx->input_notify_fnc = fnc;
335   return 0;
336 }
337
338 int
339 assuan_register_output_notify (ASSUAN_CONTEXT ctx,
340                               void (*fnc)(ASSUAN_CONTEXT, const char *))
341 {
342   if (!ctx)
343     return ASSUAN_Invalid_Value;
344   ctx->output_notify_fnc = fnc;
345   return 0;
346 }
347
348
349 /* Helper to register the standards commands */
350 int
351 _assuan_register_std_commands (ASSUAN_CONTEXT ctx)
352 {
353   int i, rc;
354
355   for (i=0; std_cmd_table[i].name; i++)
356     {
357       if (std_cmd_table[i].always)
358         {
359           rc = assuan_register_command (ctx, std_cmd_table[i].cmd_id,
360                                         NULL, 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, all 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, Syntax_Error, "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[256];
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           sprintf (errline, "ERR %d %.50s%s%.100s",
497                    rc, assuan_strerror (rc), text? " - ":"", text?text:"");
498         }
499       rc = assuan_write_line (ctx, errline);
500     }
501
502   ctx->confidential = 0;
503   if (ctx->okay_line)
504     {
505       xfree (ctx->okay_line);
506       ctx->okay_line = NULL;
507     }
508   return rc;
509 }
510
511 /**
512  * assuan_process:
513  * @ctx: assuan context
514  * 
515  * This fucntion is used to handle the assuan protocol after a
516  * connection has been established using assuan_accept().  This is the
517  * main protocol handler.
518  * 
519  * Return value: 0 on success or an error code if the assuan operation
520  * failed.  Note, that no error is returned for operational errors.
521  **/
522 int
523 assuan_process (ASSUAN_CONTEXT ctx)
524 {
525   int rc;
526
527   do {
528     rc = process_request (ctx);
529   } while (!rc);
530
531   if (rc == -1)
532     rc = 0;
533
534   return rc;
535 }
536
537
538 /**
539  * assuan_process_next:
540  * @ctx: Assuan context
541  * 
542  * Same as assuan_process() but the user has to provide the outer
543  * loop.  He should loop as long as the return code is zero and stop
544  * otherwise; -1 is regular end.
545  * 
546  * See also: assuan_get_active_fds()
547  * Return value: -1 for end of server, 0 on success or an error code
548  **/
549 int 
550 assuan_process_next (ASSUAN_CONTEXT ctx)
551 {
552   return process_request (ctx);
553 }
554
555
556 /**
557  * assuan_get_active_fds:
558  * @ctx: Assuan context
559  * @what: 0 for read fds, 1 for write fds
560  * @fdarray: Caller supplied array to store the FDs
561  * @fdarraysize: size of that array
562  * 
563  * Return all active filedescriptors for the given context.  This
564  * function can be used to select on the fds and call
565  * assuan_process_next() if there is an active one.  The first fd in
566  * the array is the one used for the command connection.
567  *
568  * Note, that write FDs are not yet supported.
569  * 
570  * Return value: number of FDs active and put into @fdarray or -1 on
571  * error which is most likely a too small fdarray.
572  **/
573 int 
574 assuan_get_active_fds (ASSUAN_CONTEXT ctx, int what,
575                        int *fdarray, int fdarraysize)
576 {
577   int n = 0;
578
579   if (!ctx || fdarraysize < 2 || what < 0 || what > 1)
580     return -1;
581
582   if (!what)
583     {
584       if (ctx->inbound.fd != -1)
585         fdarray[n++] = ctx->inbound.fd;
586     }
587   else
588     {
589       if (ctx->outbound.fd != -1)
590         fdarray[n++] = ctx->outbound.fd;
591       if (ctx->outbound.data.fp)
592         fdarray[n++] = fileno (ctx->outbound.data.fp);
593     }
594
595   return n;
596 }
597
598
599 /* Set the text used for the next OK reponse.  This string is
600    automatically reset to NULL after the next command. */
601 AssuanError
602 assuan_set_okay_line (ASSUAN_CONTEXT ctx, const char *line)
603 {
604   if (!ctx)
605     return ASSUAN_Invalid_Value;
606   if (!line)
607     {
608       xfree (ctx->okay_line);
609       ctx->okay_line = NULL;
610     }
611   else
612     {
613       /* FIXME: we need to use gcry_is_secure() to test whether
614          we should allocate the entire line in secure memory */
615       char *buf = xtrymalloc (3+strlen(line)+1);
616       if (!buf)
617         return ASSUAN_Out_Of_Core;
618       strcpy (buf, "OK ");
619       strcpy (buf+3, line);
620       xfree (ctx->okay_line);
621       ctx->okay_line = buf;
622     }
623   return 0;
624 }
625
626
627
628 void
629 assuan_write_status (ASSUAN_CONTEXT ctx, const char *keyword, const char *text)
630 {
631   char buffer[256];
632   char *helpbuf;
633   size_t n;
634
635   if ( !ctx || !keyword)
636     return;
637   if (!text)
638     text = "";
639
640   n = 2 + strlen (keyword) + 1 + strlen (text) + 1;
641   if (n < sizeof (buffer))
642     {
643       strcpy (buffer, "S ");
644       strcat (buffer, keyword);
645       if (*text)
646         {
647           strcat (buffer, " ");
648           strcat (buffer, text);
649         }
650       assuan_write_line (ctx, buffer);
651     }
652   else if ( (helpbuf = xtrymalloc (n)) )
653     {
654       strcpy (helpbuf, "S ");
655       strcat (helpbuf, keyword);
656       if (*text)
657         {
658           strcat (helpbuf, " ");
659           strcat (helpbuf, text);
660         }
661       assuan_write_line (ctx, helpbuf);
662       xfree (helpbuf);
663     }
664 }