g13: Add commands --suspend and --remove.
[gnupg.git] / g13 / sh-cmd.c
1 /* sh-cmd.c - The Assuan server for g13-syshelp
2  * Copyright (C) 2015 Werner Koch
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-syshelp.h"
29 #include <assuan.h>
30 #include "i18n.h"
31 #include "keyblob.h"
32
33
34 /* Local data for this server module.  A pointer to this is stored in
35    the CTRL object of each connection.  */
36 struct server_local_s
37 {
38   /* The Assuan contect we are working on.  */
39   assuan_context_t assuan_ctx;
40
41   /* The malloced name of the device.  */
42   char *devicename;
43
44   /* A stream open for read of the device set by the DEVICE command or
45      NULL if no DEVICE command has been used.  */
46   estream_t devicefp;
47 };
48
49
50
51 \f
52 /* Local prototypes.  */
53
54
55
56 \f
57 /*
58    Helper functions.
59  */
60
61 /* Set an error and a description.  */
62 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
63 #define set_error_fail_cmd() set_error (GPG_ERR_NOT_INITIALIZED, \
64                                         "not called via userv or unknown user")
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   (void)key;
129   (void)value;
130
131   if (ctrl->fail_all_cmds)
132     err = set_error_fail_cmd ();
133   else
134     err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
135
136   return err;
137 }
138
139
140 /* The handler for an Assuan RESET command.  */
141 static gpg_error_t
142 reset_notify (assuan_context_t ctx, char *line)
143 {
144   ctrl_t ctrl = assuan_get_pointer (ctx);
145
146   (void)line;
147
148   xfree (ctrl->server_local->devicename);
149   ctrl->server_local->devicename = NULL;
150   es_fclose (ctrl->server_local->devicefp);
151   ctrl->server_local->devicefp = NULL;
152   ctrl->devti = NULL;
153
154   assuan_close_input_fd (ctx);
155   assuan_close_output_fd (ctx);
156   return 0;
157 }
158
159
160 static const char hlp_device[] =
161   "DEVICE <name>\n"
162   "\n"
163   "Set the device used by further commands.\n"
164   "A device name or a PARTUUID string may be used.\n"
165   "Access to that device (by the g13 system) is locked\n"
166   "until a new DEVICE command or end of this process\n";
167 static gpg_error_t
168 cmd_device (assuan_context_t ctx, char *line)
169 {
170   ctrl_t ctrl = assuan_get_pointer (ctx);
171   gpg_error_t err = 0;
172   tab_item_t ti;
173   estream_t fp = NULL;
174
175   line = skip_options (line);
176
177 /* # warning hardwired to /dev/sdb1 ! */
178 /*   if (strcmp (line, "/dev/sdb1")) */
179 /*     { */
180 /*       err = gpg_error (GPG_ERR_ENOENT); */
181 /*       goto leave; */
182 /*     } */
183
184   /* Always close an open device stream of this session. */
185   xfree (ctrl->server_local->devicename);
186   ctrl->server_local->devicename = NULL;
187   es_fclose (ctrl->server_local->devicefp);
188   ctrl->server_local->devicefp = NULL;
189
190   /* Are we allowed to use the given device?  */
191   for (ti=ctrl->client.tab; ti; ti = ti->next)
192     if (!strcmp (line, ti->blockdev))
193       break;
194   if (!ti)
195     {
196       err = set_error (GPG_ERR_EACCES, "device not configured for user");
197       goto leave;
198     }
199
200   ctrl->server_local->devicename = xtrystrdup (line);
201   if (!ctrl->server_local->devicename)
202     {
203       err = gpg_error_from_syserror ();
204       goto leave;
205     }
206
207
208   /* Check whether we have permissions to open the device and keep an
209      FD open.  */
210   fp = es_fopen (ctrl->server_local->devicename, "rb");
211   if (!fp)
212     {
213       err = gpg_error_from_syserror ();
214       log_error ("error opening '%s': %s\n",
215                  ctrl->server_local->devicename, gpg_strerror (err));
216       goto leave;
217     }
218
219   es_fclose (ctrl->server_local->devicefp);
220   ctrl->server_local->devicefp = fp;
221   fp = NULL;
222   ctrl->devti = ti;
223
224   /* Fixme: Take some kind of lock.  */
225
226  leave:
227   es_fclose (fp);
228   if (err)
229     {
230       xfree (ctrl->server_local->devicename);
231       ctrl->server_local->devicename = NULL;
232       ctrl->devti = NULL;
233     }
234   return leave_cmd (ctx, err);
235 }
236
237
238 static const char hlp_create[] =
239   "CREATE <type>\n"
240   "\n"
241   "Create a new encrypted partition on the current device.\n"
242   "<type> must be \"dm-crypt\" for now.";
243 static gpg_error_t
244 cmd_create (assuan_context_t ctx, char *line)
245 {
246   ctrl_t ctrl = assuan_get_pointer (ctx);
247   gpg_error_t err = 0;
248   estream_t fp = NULL;
249
250   line = skip_options (line);
251   if (strcmp (line, "dm-crypt"))
252     {
253       err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
254       goto leave;
255     }
256
257   if (!ctrl->server_local->devicename
258       || !ctrl->server_local->devicefp
259       || !ctrl->devti)
260     {
261       err = set_error (GPG_ERR_ENOENT, "No device has been set");
262       goto leave;
263     }
264
265   err = sh_is_empty_partition (ctrl->server_local->devicename);
266   if (err)
267     {
268       if (gpg_err_code (err) == GPG_ERR_FALSE)
269         err = gpg_error (GPG_ERR_CONFLICT);
270       err = assuan_set_error (ctx, err, "Partition is not empty");
271       goto leave;
272     }
273
274   /* We need a writeable stream to create the container.  */
275   fp = es_fopen (ctrl->server_local->devicename, "r+b");
276   if (!fp)
277     {
278       err = gpg_error_from_syserror ();
279       log_error ("error opening '%s': %s\n",
280                  ctrl->server_local->devicename, gpg_strerror (err));
281       goto leave;
282     }
283   if (es_setvbuf (fp, NULL, _IONBF, 0))
284     {
285       err = gpg_error_from_syserror ();
286       log_error ("error setting '%s' to _IONBF: %s\n",
287                  ctrl->server_local->devicename, gpg_strerror (err));
288       goto leave;
289     }
290
291   err = sh_dmcrypt_create_container (ctrl,
292                                      ctrl->server_local->devicename,
293                                      fp);
294   if (es_fclose (fp))
295     {
296       gpg_error_t err2 = gpg_error_from_syserror ();
297       log_error ("error closing '%s': %s\n",
298                  ctrl->server_local->devicename, gpg_strerror (err2));
299       if (!err)
300         err = err2;
301     }
302   fp = NULL;
303
304  leave:
305   es_fclose (fp);
306   return leave_cmd (ctx, err);
307 }
308
309
310 static const char hlp_mount[] =
311   "MOUNT <type>\n"
312   "\n"
313   "Mount an encrypted partition on the current device.\n"
314   "<type> must be \"dm-crypt\" for now.";
315 static gpg_error_t
316 cmd_mount (assuan_context_t ctx, char *line)
317 {
318   ctrl_t ctrl = assuan_get_pointer (ctx);
319   gpg_error_t err = 0;
320   unsigned char *keyblob = NULL;
321   size_t keybloblen;
322   tupledesc_t tuples = NULL;
323
324   line = skip_options (line);
325
326   if (strcmp (line, "dm-crypt"))
327     {
328       err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
329       goto leave;
330     }
331
332   if (!ctrl->server_local->devicename
333       || !ctrl->server_local->devicefp
334       || !ctrl->devti)
335     {
336       err = set_error (GPG_ERR_ENOENT, "No device has been set");
337       goto leave;
338     }
339
340   err = sh_is_empty_partition (ctrl->server_local->devicename);
341   if (!err)
342     {
343       err = gpg_error (GPG_ERR_ENODEV);
344       assuan_set_error (ctx, err, "Partition is empty");
345       goto leave;
346     }
347   err = 0;
348
349   /* We expect that the client already decrypted the keyblob.
350    * Eventually we should move reading of the keyblob to here and ask
351    * the client to decrypt it.  */
352   assuan_begin_confidential (ctx);
353   err = assuan_inquire (ctx, "KEYBLOB",
354                         &keyblob, &keybloblen, 4 * 1024);
355   assuan_end_confidential (ctx);
356   if (err)
357     {
358       log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
359       goto leave;
360     }
361   err = create_tupledesc (&tuples, keyblob, keybloblen);
362   if (!err)
363     keyblob = NULL;
364   else
365     {
366       if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
367         log_error ("unknown keyblob version received\n");
368       goto leave;
369     }
370
371   err = sh_dmcrypt_mount_container (ctrl,
372                                     ctrl->server_local->devicename,
373                                     tuples);
374
375  leave:
376   xfree (tuples);
377   destroy_tupledesc (tuples);
378   return leave_cmd (ctx, err);
379 }
380
381
382 static const char hlp_suspend[] =
383   "SUSPEND <type>\n"
384   "\n"
385   "Suspend an encrypted partition and wipe the key.\n"
386   "<type> must be \"dm-crypt\" for now.";
387 static gpg_error_t
388 cmd_suspend (assuan_context_t ctx, char *line)
389 {
390   ctrl_t ctrl = assuan_get_pointer (ctx);
391   gpg_error_t err = 0;
392
393   line = skip_options (line);
394
395   if (strcmp (line, "dm-crypt"))
396     {
397       err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
398       goto leave;
399     }
400
401   if (!ctrl->server_local->devicename
402       || !ctrl->server_local->devicefp
403       || !ctrl->devti)
404     {
405       err = set_error (GPG_ERR_ENOENT, "No device has been set");
406       goto leave;
407     }
408
409   err = sh_is_empty_partition (ctrl->server_local->devicename);
410   if (!err)
411     {
412       err = gpg_error (GPG_ERR_ENODEV);
413       assuan_set_error (ctx, err, "Partition is empty");
414       goto leave;
415     }
416   err = 0;
417
418   err = sh_dmcrypt_suspend_container (ctrl, ctrl->server_local->devicename);
419
420  leave:
421   return leave_cmd (ctx, err);
422 }
423
424
425 static const char hlp_resume[] =
426   "RESUME <type>\n"
427   "\n"
428   "Resume an encrypted partition and set the key.\n"
429   "<type> must be \"dm-crypt\" for now.";
430 static gpg_error_t
431 cmd_resume (assuan_context_t ctx, char *line)
432 {
433   ctrl_t ctrl = assuan_get_pointer (ctx);
434   gpg_error_t err = 0;
435   unsigned char *keyblob = NULL;
436   size_t keybloblen;
437   tupledesc_t tuples = NULL;
438
439   line = skip_options (line);
440
441   if (strcmp (line, "dm-crypt"))
442     {
443       err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
444       goto leave;
445     }
446
447   if (!ctrl->server_local->devicename
448       || !ctrl->server_local->devicefp
449       || !ctrl->devti)
450     {
451       err = set_error (GPG_ERR_ENOENT, "No device has been set");
452       goto leave;
453     }
454
455   err = sh_is_empty_partition (ctrl->server_local->devicename);
456   if (!err)
457     {
458       err = gpg_error (GPG_ERR_ENODEV);
459       assuan_set_error (ctx, err, "Partition is empty");
460       goto leave;
461     }
462   err = 0;
463
464   /* We expect that the client already decrypted the keyblob.
465    * Eventually we should move reading of the keyblob to here and ask
466    * the client to decrypt it.  */
467   assuan_begin_confidential (ctx);
468   err = assuan_inquire (ctx, "KEYBLOB",
469                         &keyblob, &keybloblen, 4 * 1024);
470   assuan_end_confidential (ctx);
471   if (err)
472     {
473       log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
474       goto leave;
475     }
476   err = create_tupledesc (&tuples, keyblob, keybloblen);
477   if (!err)
478     keyblob = NULL;
479   else
480     {
481       if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
482         log_error ("unknown keyblob version received\n");
483       goto leave;
484     }
485
486   err = sh_dmcrypt_resume_container (ctrl,
487                                      ctrl->server_local->devicename,
488                                      tuples);
489
490  leave:
491   xfree (tuples);
492   destroy_tupledesc (tuples);
493   return leave_cmd (ctx, err);
494 }
495
496
497 static const char hlp_getinfo[] =
498   "GETINFO <what>\n"
499   "\n"
500   "Multipurpose function to return a variety of information.\n"
501   "Supported values for WHAT are:\n"
502   "\n"
503   "  version     - Return the version of the program.\n"
504   "  pid         - Return the process id of the server.\n"
505   "  showtab     - Show the table for the user.";
506 static gpg_error_t
507 cmd_getinfo (assuan_context_t ctx, char *line)
508 {
509   ctrl_t ctrl = assuan_get_pointer (ctx);
510   gpg_error_t err = 0;
511   char *buf;
512
513   if (!strcmp (line, "version"))
514     {
515       const char *s = PACKAGE_VERSION;
516       err = assuan_send_data (ctx, s, strlen (s));
517     }
518   else if (!strcmp (line, "pid"))
519     {
520       char numbuf[50];
521
522       snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
523       err = assuan_send_data (ctx, numbuf, strlen (numbuf));
524     }
525   else if (!strncmp (line, "getsz", 5))
526     {
527       unsigned long long nblocks;
528       err = sh_blockdev_getsz (line+6, &nblocks);
529       if (!err)
530         log_debug ("getsz=%llu\n", nblocks);
531     }
532   else if (!strcmp (line, "showtab"))
533     {
534       tab_item_t ti;
535
536       for (ti=ctrl->client.tab; !err && ti; ti = ti->next)
537         {
538           buf = es_bsprintf ("%s %s%s %s %s%s\n",
539                              ctrl->client.uname,
540                              *ti->blockdev=='/'? "":"partuuid=",
541                              ti->blockdev,
542                              ti->label? ti->label : "-",
543                              ti->mountpoint? " ":"",
544                              ti->mountpoint? ti->mountpoint:"");
545           if (!buf)
546             err = gpg_error_from_syserror ();
547           else
548             {
549               err = assuan_send_data (ctx, buf, strlen (buf));
550               if (!err)
551                 err = assuan_send_data (ctx, NULL, 0); /* Flush  */
552             }
553           xfree (buf);
554         }
555     }
556   else
557     err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
558
559   return leave_cmd (ctx, err);
560 }
561
562
563 /* This command handler is used for all commands if this process has
564    not been started as expected.  */
565 static gpg_error_t
566 fail_command (assuan_context_t ctx, char *line)
567 {
568   gpg_error_t err;
569   const char *name = assuan_get_command_name (ctx);
570
571   (void)line;
572
573   if (!name)
574     name = "?";
575
576   err = set_error_fail_cmd ();
577   log_error ("command '%s' failed: %s\n", name, gpg_strerror (err));
578   return err;
579 }
580
581
582 /* Tell the Assuan library about our commands.  */
583 static int
584 register_commands (assuan_context_t ctx, int fail_all)
585 {
586   static struct {
587     const char *name;
588     assuan_handler_t handler;
589     const char * const help;
590   } table[] =  {
591     { "DEVICE",        cmd_device, hlp_device },
592     { "CREATE",        cmd_create, hlp_create },
593     { "MOUNT",         cmd_mount,  hlp_mount  },
594     { "SUSPEND",       cmd_suspend,hlp_suspend},
595     { "RESUME",        cmd_resume, hlp_resume },
596     { "INPUT",         NULL },
597     { "OUTPUT",        NULL },
598     { "GETINFO",       cmd_getinfo, hlp_getinfo },
599     { NULL }
600   };
601   gpg_error_t err;
602   int i;
603
604   for (i=0; table[i].name; i++)
605     {
606       err = assuan_register_command (ctx, table[i].name,
607                                      fail_all ? fail_command : table[i].handler,
608                                      table[i].help);
609       if (err)
610         return err;
611     }
612   return 0;
613 }
614
615
616 /* Startup the server.  */
617 gpg_error_t
618 syshelp_server (ctrl_t ctrl)
619 {
620   gpg_error_t err;
621   assuan_fd_t filedes[2];
622   assuan_context_t ctx = NULL;
623
624   /* We use a pipe based server so that we can work from scripts.
625      assuan_init_pipe_server will automagically detect when we are
626      called with a socketpair and ignore FILEDES in this case. */
627   filedes[0] = assuan_fdopen (0);
628   filedes[1] = assuan_fdopen (1);
629   err = assuan_new (&ctx);
630   if (err)
631     {
632       log_error ("failed to allocate an Assuan context: %s\n",
633                  gpg_strerror (err));
634       goto leave;
635     }
636
637   err = assuan_init_pipe_server (ctx, filedes);
638   if (err)
639     {
640       log_error ("failed to initialize the server: %s\n", gpg_strerror (err));
641       goto leave;
642     }
643
644   err = register_commands (ctx, 0 /*FIXME:ctrl->fail_all_cmds*/);
645   if (err)
646     {
647       log_error ("failed to the register commands with Assuan: %s\n",
648                  gpg_strerror (err));
649       goto leave;
650     }
651
652   assuan_set_pointer (ctx, ctrl);
653
654   {
655     char *tmp = xtryasprintf ("G13-syshelp %s ready to serve requests "
656                               "from %lu(%s)",
657                               PACKAGE_VERSION,
658                               (unsigned long)ctrl->client.uid,
659                               ctrl->client.uname);
660     if (tmp)
661       {
662         assuan_set_hello_line (ctx, tmp);
663         xfree (tmp);
664       }
665   }
666
667   assuan_register_reset_notify (ctx, reset_notify);
668   assuan_register_option_handler (ctx, option_handler);
669
670   ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
671   if (!ctrl->server_local)
672     {
673       err = gpg_error_from_syserror ();
674       goto leave;
675     }
676   ctrl->server_local->assuan_ctx = ctx;
677
678   while ( !(err = assuan_accept (ctx)) )
679     {
680       err = assuan_process (ctx);
681       if (err)
682         log_info ("Assuan processing failed: %s\n", gpg_strerror (err));
683     }
684   if (err == -1)
685     err = 0;
686   else
687     log_info ("Assuan accept problem: %s\n", gpg_strerror (err));
688
689  leave:
690   reset_notify (ctx, NULL);  /* Release all items hold by SERVER_LOCAL.  */
691   if (ctrl->server_local)
692     {
693       xfree (ctrl->server_local);
694       ctrl->server_local = NULL;
695     }
696
697   assuan_release (ctx);
698   return err;
699 }
700
701
702 gpg_error_t
703 sh_encrypt_keyblob (ctrl_t ctrl, const void *keyblob, size_t keybloblen,
704                     char **r_enckeyblob, size_t *r_enckeybloblen)
705 {
706   assuan_context_t ctx = ctrl->server_local->assuan_ctx;
707   gpg_error_t err;
708   unsigned char *enckeyblob;
709   size_t enckeybloblen;
710
711   *r_enckeyblob = NULL;
712
713   /* Send the plaintext.  */
714   err = g13_status (ctrl, STATUS_PLAINTEXT_FOLLOWS, NULL);
715   if (err)
716     return err;
717   assuan_begin_confidential (ctx);
718   err = assuan_send_data (ctx, keyblob, keybloblen);
719   if (!err)
720     err = assuan_send_data (ctx, NULL, 0);
721   assuan_end_confidential (ctx);
722   if (!err)
723     err = assuan_write_line (ctx, "END");
724   if (err)
725     {
726       log_error (_("error sending data: %s\n"), gpg_strerror (err));
727       return err;
728     }
729
730   /* Inquire the ciphertext.  */
731   err = assuan_inquire (ctx, "ENCKEYBLOB",
732                         &enckeyblob, &enckeybloblen, 16 * 1024);
733   if (err)
734     {
735       log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
736       return err;
737     }
738
739   *r_enckeyblob = enckeyblob;
740   *r_enckeybloblen = enckeybloblen;
741   return 0;
742 }
743
744
745 /* Send a status line with status ID NO.  The arguments are a list of
746    strings terminated by a NULL argument.  */
747 gpg_error_t
748 g13_status (ctrl_t ctrl, int no, ...)
749 {
750   gpg_error_t err = 0;
751   va_list arg_ptr;
752   const char *text;
753
754   va_start (arg_ptr, no);
755
756   if (1)
757     {
758       assuan_context_t ctx = ctrl->server_local->assuan_ctx;
759       char buf[950], *p;
760       size_t n;
761
762       p = buf;
763       n = 0;
764       while ( (text = va_arg (arg_ptr, const char *)) )
765         {
766           if (n)
767             {
768               *p++ = ' ';
769               n++;
770             }
771           for ( ; *text && n < DIM (buf)-2; n++)
772             *p++ = *text++;
773         }
774       *p = 0;
775       err = assuan_write_status (ctx, get_status_string (no), buf);
776     }
777
778   va_end (arg_ptr);
779   return err;
780 }