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