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