Reworked the server commands.
[gnupg.git] / g13 / server.c
1 /* server.c - The G13 Assuan server
2  * Copyright (C) 2009 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 3 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, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdarg.h>
25 #include <errno.h>
26 #include <assert.h>
27
28 #include "g13.h"
29 #include <assuan.h>
30 #include "i18n.h"
31 #include "keyblob.h"
32 #include "./server.h"
33 #include "./mount.h"
34
35
36 /* Local data for this server module.  A pointer to this is stored in
37    the CTRL object of each connection.  */
38 struct server_local_s 
39 {
40   /* The Assuan contect we are working on.  */
41   assuan_context_t assuan_ctx;
42
43   char *containername;  /* Malloced active containername.  */
44
45 };
46
47
48
49 \f
50 /* Local prototypes.  */
51 static int command_has_option (const char *cmd, const char *cmdopt);
52
53
54
55 \f
56 /*
57    Helper functions. 
58  */
59
60 /* Set an error and a description.  */
61 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
62
63
64 /* Skip over options.  Blanks after the options are also removed.  */
65 static char *
66 skip_options (const char *line)
67 {
68   while (spacep (line))
69     line++;
70   while ( *line == '-' && line[1] == '-' )
71     {
72       while (*line && !spacep (line))
73         line++;
74       while (spacep (line))
75         line++;
76     }
77   return (char*)line;
78 }
79
80
81 /* Check whether the option NAME appears in LINE.  */
82 /* static int */
83 /* has_option (const char *line, const char *name) */
84 /* { */
85 /*   const char *s; */
86 /*   int n = strlen (name); */
87
88 /*   s = strstr (line, name); */
89 /*   if (s && s >= skip_options (line)) */
90 /*     return 0; */
91 /*   return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); */
92 /* } */
93
94
95 /* Helper to print a message while leaving a command.  */
96 static gpg_error_t
97 leave_cmd (assuan_context_t ctx, gpg_error_t err)
98 {
99   if (err)
100     {
101       const char *name = assuan_get_command_name (ctx);
102       if (!name)
103         name = "?";
104       if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
105         log_error ("command '%s' failed: %s\n", name,
106                    gpg_strerror (err));
107       else
108         log_error ("command '%s' failed: %s <%s>\n", name,
109                    gpg_strerror (err), gpg_strsource (err));
110     }
111   return err;
112 }
113
114
115
116 \f
117 /* The handler for Assuan OPTION commands.  */
118 static gpg_error_t
119 option_handler (assuan_context_t ctx, const char *key, const char *value)
120 {
121   ctrl_t ctrl = assuan_get_pointer (ctx);
122   gpg_error_t err = 0;
123
124   (void)ctrl;
125
126   if (!strcmp (key, "putenv"))
127     {
128       /* Change the session's environment to be used for the
129          Pinentry.  Valid values are:
130           <NAME>            Delete envvar NAME
131           <KEY>=            Set envvar NAME to the empty string
132           <KEY>=<VALUE>     Set envvar NAME to VALUE
133       */
134       err = session_env_putenv (opt.session_env, value);
135     }
136   else if (!strcmp (key, "display"))
137     {
138       err = session_env_setenv (opt.session_env, "DISPLAY", value);
139     }
140   else if (!strcmp (key, "ttyname"))
141     {
142       err = session_env_setenv (opt.session_env, "GPG_TTY", value);
143     }
144   else if (!strcmp (key, "ttytype"))
145     {
146       err = session_env_setenv (opt.session_env, "TERM", value);
147     }
148   else if (!strcmp (key, "lc-ctype"))
149     {
150       xfree (opt.lc_ctype);
151       opt.lc_ctype = xtrystrdup (value);
152       if (!opt.lc_ctype)
153         err = gpg_error_from_syserror ();
154     }
155   else if (!strcmp (key, "lc-messages"))
156     {
157       xfree (opt.lc_messages);
158       opt.lc_messages = xtrystrdup (value);
159       if (!opt.lc_messages)
160         err = gpg_error_from_syserror ();
161     }
162   else if (!strcmp (key, "xauthority"))
163     {
164       err = session_env_setenv (opt.session_env, "XAUTHORITY", value);
165     }
166   else if (!strcmp (key, "pinentry-user-data"))
167     {
168       err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value);
169     }
170   else if (!strcmp (key, "enable-audit-log"))
171     {
172       /* This is not yet used.  */
173       /* int i = *value? atoi (value) : 0; */
174       /* ctrl->server_local->enable_audit_log = i; */
175     }
176   else if (!strcmp (key, "allow-pinentry-notify"))
177     {
178       ; /* We always allow it.  */
179     }
180   else
181     err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
182
183   return err;
184 }
185
186
187 /* The handler for an Assuan RESET command.  */
188 static void
189 reset_notify (assuan_context_t ctx)
190 {
191   ctrl_t ctrl = assuan_get_pointer (ctx);
192
193   xfree (ctrl->server_local->containername);
194   ctrl->server_local->containername = NULL;
195
196   assuan_close_input_fd (ctx);
197   assuan_close_output_fd (ctx);
198 }
199
200
201
202 /* OPEN [options] <filename>
203
204    Open the container FILENAME.  FILENAME must be percent-plus
205    escaped.  A quick check to see whether this is a suitable G13
206    container file is done.  However no cryptographic check or any
207    other check is done.  This command is used to define the target for
208    further commands.  The filename is reset with the RESET command,
209    another OPEN or the CREATE command.
210  */
211 static gpg_error_t
212 cmd_open (assuan_context_t ctx, char *line)
213 {
214   ctrl_t ctrl = assuan_get_pointer (ctx);
215   gpg_error_t err = 0;
216   char *p, *pend;
217   size_t len;
218
219   /* In any case reset the active container.  */
220   xfree (ctrl->server_local->containername);
221   ctrl->server_local->containername = NULL;
222
223   /* Parse the line.  */
224   line = skip_options (line);
225   for (p=line; *p && !spacep (p); p++)
226     ;
227   pend = p;
228   while (spacep(p))
229     p++;
230   if (*p || pend == line)
231     {
232       err = gpg_error (GPG_ERR_ASS_SYNTAX);
233       goto leave;
234     }
235   *pend = 0;
236
237   /* Unescape the line and check for embedded Nul bytes.  */
238   len = percent_plus_unescape_inplace (line, 0);
239   line[len] = 0;
240   if (!len || memchr (line, 0, len))
241     {
242       err = gpg_error (GPG_ERR_INV_NAME);
243       goto leave;
244     }
245
246   /* Do a basic check.  */
247   err = g13_is_container (ctrl, line);
248   if (err)
249     goto leave;
250
251   /* Store the filename.  */
252   ctrl->server_local->containername = xtrystrdup (line);
253   if (!ctrl->server_local->containername)
254     err = gpg_error_from_syserror ();
255   
256   
257  leave:
258   return leave_cmd (ctx, err);
259 }
260
261
262 /* MOUNT [options] [<mountpoint>]
263
264    Mount the currently open file onto MOUNTPOINT.  If MOUNTPOINT is
265    not given the system picks an unused mountpoint.  MOUNTPOINT must
266    be percent-plus escaped to allow for arbitrary names.
267  */
268 static gpg_error_t
269 cmd_mount (assuan_context_t ctx, char *line)
270 {
271   ctrl_t ctrl = assuan_get_pointer (ctx);
272   gpg_error_t err = 0;
273   char *p, *pend;
274   size_t len;
275
276   line = skip_options (line);
277   for (p=line; *p && !spacep (p); p++)
278     ;
279   pend = p;
280   while (spacep(p))
281     p++;
282   if (*p)
283     {
284       err = gpg_error (GPG_ERR_ASS_SYNTAX);
285       goto leave;
286     }
287   *pend = 0;
288
289   /* Unescape the line and check for embedded Nul bytes.  */
290   len = percent_plus_unescape_inplace (line, 0);
291   line[len] = 0;
292   if (memchr (line, 0, len))
293     {
294       err = gpg_error (GPG_ERR_INV_NAME);
295       goto leave;
296     }
297
298   if (!ctrl->server_local->containername)
299     {
300       err = gpg_error (GPG_ERR_MISSING_ACTION);
301       goto leave;
302     }
303
304   /* Perform the mount.  */
305   err = g13_mount_container (ctrl, ctrl->server_local->containername, 
306                              *line? line : NULL);
307
308  leave:
309   return leave_cmd (ctx, err);
310 }
311
312
313 /* UMOUNT [options] [<mountpoint>]
314
315    Unmount the currently open file or the one opened at MOUNTPOINT.
316    MOUNTPOINT must be percent-plus escaped.
317  */
318 static gpg_error_t
319 cmd_umount (assuan_context_t ctx, char *line)
320 {
321   ctrl_t ctrl = assuan_get_pointer (ctx);
322   gpg_error_t err = 0;
323   char *p, *pend;
324   size_t len;
325
326   line = skip_options (line);
327   for (p=line; *p && !spacep (p); p++)
328     ;
329   pend = p;
330   while (spacep(p))
331     p++;
332   if (*p)
333     {
334       err = gpg_error (GPG_ERR_ASS_SYNTAX);
335       goto leave;
336     }
337   *pend = 0;
338
339   /* Unescape the line and check for embedded Nul bytes.  */
340   len = percent_plus_unescape_inplace (line, 0);
341   line[len] = 0;
342   if (memchr (line, 0, len))
343     {
344       err = gpg_error (GPG_ERR_INV_NAME);
345       goto leave;
346     }
347
348   /* Perform the unmount.  */
349   err = g13_umount_container (ctrl, ctrl->server_local->containername, 
350                               *line? line : NULL);
351
352  leave:
353   return leave_cmd (ctx, err);
354 }
355
356
357
358 /* RECIPIENT <userID>
359
360    FIXME - description. 
361    All RECIPIENT commands are cumulative until a RESET or an
362    successful CREATE command.
363  */
364 static gpg_error_t
365 cmd_recipient (assuan_context_t ctx, char *line)
366 {
367   ctrl_t ctrl = assuan_get_pointer (ctx);
368   gpg_error_t err;
369
370   (void)ctrl;
371   err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
372   /* err = gpgsm_add_to_certlist (ctrl, line, 0, */
373   /*                              &ctrl->server_local->recplist, 0); */
374   /* if (err) */
375   /*   { */
376   /*     gpgsm_status2 (ctrl, STATUS_INV_RECP, */
377   /*                    get_inv_recpsgnr_code (rc), line, NULL); */
378   /*   } */
379
380   return leave_cmd (ctx, err);
381 }
382
383
384 /* SIGNER <userID>
385
386    FIXME - description. 
387  */
388 static gpg_error_t
389 cmd_signer (assuan_context_t ctx, char *line)
390 {
391   ctrl_t ctrl = assuan_get_pointer (ctx);
392   gpg_error_t err;
393
394   (void)ctrl;
395
396   err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
397   return leave_cmd (ctx, err);
398 }
399
400
401 /* CREATE [options] filename
402
403    Create a new container.  On success the OPEN command is done
404    implictly for the new container.
405  */
406 static gpg_error_t
407 cmd_create (assuan_context_t ctx, char *line)
408 {
409   ctrl_t ctrl = assuan_get_pointer (ctx);
410   gpg_error_t err;
411
412   (void)ctrl;
413
414   /* First we close the active container.  */
415   xfree (ctrl->server_local->containername);
416   ctrl->server_local->containername = NULL;
417
418
419
420   err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
421   return leave_cmd (ctx, err);
422 }
423
424
425 /* GETINFO <what>
426
427    Multipurpose function to return a variety of information.
428    Supported values for WHAT are:
429
430      version     - Return the version of the program.
431      pid         - Return the process id of the server.
432      cmd_has_option CMD OPT
433                  - Returns OK if the command CMD implements the option OPT.
434
435  */
436 static gpg_error_t
437 cmd_getinfo (assuan_context_t ctx, char *line)
438 {
439   gpg_error_t err = 0;
440
441   if (!strcmp (line, "version"))
442     {
443       const char *s = PACKAGE_VERSION;
444       err = assuan_send_data (ctx, s, strlen (s));
445     }
446   else if (!strcmp (line, "pid"))
447     {
448       char numbuf[50];
449
450       snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
451       err = assuan_send_data (ctx, numbuf, strlen (numbuf));
452     }
453   else if (!strncmp (line, "cmd_has_option", 14)
454            && (line[14] == ' ' || line[14] == '\t' || !line[14]))
455     {
456       char *cmd, *cmdopt;
457       line += 14;
458       while (*line == ' ' || *line == '\t')
459         line++;
460       if (!*line)
461         err = gpg_error (GPG_ERR_MISSING_VALUE);
462       else
463         {
464           cmd = line;
465           while (*line && (*line != ' ' && *line != '\t'))
466             line++;
467           if (!*line)
468             err = gpg_error (GPG_ERR_MISSING_VALUE);
469           else
470             {
471               *line++ = 0;
472               while (*line == ' ' || *line == '\t')
473                 line++;
474               if (!*line)
475                 err = gpg_error (GPG_ERR_MISSING_VALUE);
476               else
477                 {
478                   cmdopt = line;
479                   if (!command_has_option (cmd, cmdopt))
480                     err = gpg_error (GPG_ERR_GENERAL);
481                 }
482             }
483         }
484     }
485   else
486     err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
487
488   return leave_cmd (ctx, err);
489 }
490
491
492 \f
493 /* Return true if the command CMD implements the option CMDOPT.  */
494 static int
495 command_has_option (const char *cmd, const char *cmdopt)
496 {
497   (void)cmd;
498   (void)cmdopt;
499       
500   return 0;
501 }
502
503
504 /* Tell the Assuan library about our commands.  */
505 static int
506 register_commands (assuan_context_t ctx)
507 {
508   static struct {
509     const char *name;
510     gpg_error_t (*handler)(assuan_context_t, char *line);
511   } table[] =  {
512     { "OPEN",          cmd_open },
513     { "MOUNT",         cmd_mount },
514     { "UMOUNT",        cmd_umount },
515     { "RECIPIENT",     cmd_recipient },
516     { "SIGNER",        cmd_signer },
517     { "CREATE",        cmd_create },
518     { "INPUT",         NULL }, 
519     { "OUTPUT",        NULL }, 
520     { "GETINFO",       cmd_getinfo },
521     { NULL }
522   };
523   gpg_error_t err;
524   int i;
525
526   for (i=0; table[i].name; i++)
527     {
528       err = assuan_register_command (ctx, table[i].name, table[i].handler);
529       if (err)
530         return err;
531     } 
532   return 0;
533 }
534
535
536 /* Startup the server. DEFAULT_RECPLIST is the list of recipients as
537    set from the command line or config file.  We only require those
538    marked as encrypt-to. */
539 gpg_error_t
540 g13_server (ctrl_t ctrl)
541 {
542   gpg_error_t err;
543   int filedes[2];
544   assuan_context_t ctx = NULL;
545   static const char hello[] = ("GNU Privacy Guard's G13 server "
546                                PACKAGE_VERSION " ready");
547
548   /* We use a pipe based server so that we can work from scripts.
549      assuan_init_pipe_server will automagically detect when we are
550      called with a socketpair and ignore FIELDES in this case. */
551   filedes[0] = 0;
552   filedes[1] = 1;
553   err = assuan_new (&ctx);
554   if (err)
555     {
556       log_error ("failed to allocate an Assuan context: %s\n",
557                  gpg_strerror (err));
558       goto leave;
559     }
560
561   err = assuan_init_pipe_server (ctx, filedes);
562   if (err)
563     {
564       log_error ("failed to initialize the server: %s\n", gpg_strerror (err));
565       goto leave;
566     }
567
568   err = register_commands (ctx);
569   if (err)
570     {
571       log_error ("failed to the register commands with Assuan: %s\n",
572                  gpg_strerror (err));
573       goto leave;
574     }
575
576   assuan_set_pointer (ctx, ctrl);
577
578   if (opt.verbose || opt.debug)
579     {
580       char *tmp = NULL;
581       const char *s1 = getenv ("GPG_AGENT_INFO");
582
583       tmp = xtryasprintf ("Home: %s\n"
584                           "Config: %s\n"
585                           "AgentInfo: %s\n"
586                           "%s",
587                           opt.homedir,
588                           opt.config_filename,
589                           s1?s1:"[not set]",
590                           hello);
591       if (tmp)
592         {
593           assuan_set_hello_line (ctx, tmp);
594           xfree (tmp);
595         }
596     }
597   else
598     assuan_set_hello_line (ctx, hello);
599
600   assuan_register_reset_notify (ctx, reset_notify);
601   assuan_register_option_handler (ctx, option_handler);
602
603   ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
604   if (!ctrl->server_local)
605     {
606       err = gpg_error_from_syserror ();
607       goto leave;
608     }
609   ctrl->server_local->assuan_ctx = ctx;
610
611   if (DBG_ASSUAN)
612     assuan_set_log_stream (ctx, log_get_stream ());
613
614   while ( !(err = assuan_accept (ctx)) )
615     {
616       err = assuan_process (ctx);
617       if (err)
618         log_info ("Assuan processing failed: %s\n", gpg_strerror (err));
619     }
620   if (err == -1)
621     err = 0;
622   else
623     log_info ("Assuan accept problem: %s\n", gpg_strerror (err));
624   
625  leave:
626   if (ctrl->server_local)
627     {
628       xfree (ctrl->server_local);
629       ctrl->server_local = NULL;
630     }
631
632   assuan_release (ctx);
633   return err;
634 }
635
636
637
638 /* gpg_error_t */
639 /* gpgsm_status2 (ctrl_t ctrl, int no, ...) */
640 /* { */
641 /*   gpg_error_t err = 0; */
642 /*   va_list arg_ptr; */
643 /*   const char *text; */
644
645 /*   va_start (arg_ptr, no); */
646
647 /*   if (ctrl->no_server && ctrl->status_fd == -1) */
648 /*     ; /\* No status wanted. *\/ */
649 /*   else if (ctrl->no_server) */
650 /*     { */
651 /*       if (!statusfp) */
652 /*         { */
653 /*           if (ctrl->status_fd == 1) */
654 /*             statusfp = stdout; */
655 /*           else if (ctrl->status_fd == 2) */
656 /*             statusfp = stderr; */
657 /*           else */
658 /*             statusfp = fdopen (ctrl->status_fd, "w"); */
659       
660 /*           if (!statusfp) */
661 /*             { */
662 /*               log_fatal ("can't open fd %d for status output: %s\n", */
663 /*                          ctrl->status_fd, strerror(errno)); */
664 /*             } */
665 /*         } */
666       
667 /*       fputs ("[GNUPG:] ", statusfp); */
668 /*       fputs (get_status_string (no), statusfp); */
669     
670 /*       while ( (text = va_arg (arg_ptr, const char*) )) */
671 /*         { */
672 /*           putc ( ' ', statusfp ); */
673 /*           for (; *text; text++)  */
674 /*             { */
675 /*               if (*text == '\n') */
676 /*                 fputs ( "\\n", statusfp ); */
677 /*               else if (*text == '\r') */
678 /*                 fputs ( "\\r", statusfp ); */
679 /*               else  */
680 /*                 putc ( *(const byte *)text,  statusfp ); */
681 /*             } */
682 /*         } */
683 /*       putc ('\n', statusfp); */
684 /*       fflush (statusfp); */
685 /*     } */
686 /*   else  */
687 /*     { */
688 /*       assuan_context_t ctx = ctrl->server_local->assuan_ctx; */
689 /*       char buf[950], *p; */
690 /*       size_t n; */
691
692 /*       p = buf;  */
693 /*       n = 0; */
694 /*       while ( (text = va_arg (arg_ptr, const char *)) ) */
695 /*         { */
696 /*           if (n) */
697 /*             { */
698 /*               *p++ = ' '; */
699 /*               n++; */
700 /*             } */
701 /*           for ( ; *text && n < DIM (buf)-2; n++) */
702 /*             *p++ = *text++; */
703 /*         } */
704 /*       *p = 0; */
705 /*       err = assuan_write_status (ctx, get_status_string (no), buf); */
706 /*     } */
707
708 /*   va_end (arg_ptr); */
709 /*   return err; */
710 /* } */
711
712 /* gpg_error_t */
713 /* gpgsm_status (ctrl_t ctrl, int no, const char *text) */
714 /* { */
715 /*   return gpgsm_status2 (ctrl, no, text, NULL); */
716 /* } */
717
718 /* gpg_error_t */
719 /* gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, */
720 /*                             gpg_err_code_t ec) */
721 /* { */
722 /*   char buf[30]; */
723
724 /*   sprintf (buf, "%u", (unsigned int)ec); */
725 /*   if (text) */
726 /*     return gpgsm_status2 (ctrl, no, text, buf, NULL); */
727 /*   else */
728 /*     return gpgsm_status2 (ctrl, no, buf, NULL); */
729 /* } */
730
731
732 /* Helper to notify the client about Pinentry events.  Returns an gpg
733    error code. */
734 gpg_error_t
735 g13_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line)
736 {
737   if (!ctrl || !ctrl->server_local)
738     return 0;
739   return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
740 }
741
742
743
744