g13: First chunk of code to support dm-crypt.
[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.";
165 static gpg_error_t
166 cmd_device (assuan_context_t ctx, char *line)
167 {
168   ctrl_t ctrl = assuan_get_pointer (ctx);
169   gpg_error_t err = 0;
170   tab_item_t ti;
171   estream_t fp = NULL;
172
173   /* strcpy (line, "/dev/sdb1"); /\* FIXME *\/ */
174   line = skip_options (line);
175
176   /* Always close an open device stream of this session. */
177   xfree (ctrl->server_local->devicename);
178   ctrl->server_local->devicename = NULL;
179   es_fclose (ctrl->server_local->devicefp);
180   ctrl->server_local->devicefp = NULL;
181
182   /* Are we allowed to use the given device?  */
183   for (ti=ctrl->client.tab; ti; ti = ti->next)
184     if (!strcmp (line, ti->blockdev))
185       break;
186   if (!ti)
187     {
188       set_error (GPG_ERR_EACCES, "device not configured for user");
189       goto leave;
190     }
191
192   ctrl->server_local->devicename = xtrystrdup (line);
193   if (!ctrl->server_local->devicename)
194     {
195       err = gpg_error_from_syserror ();
196       goto leave;
197     }
198
199
200   /* Check whether we have permissions to open the device and keep an
201      FD open.  */
202   fp = es_fopen (ctrl->server_local->devicename, "rb");
203   if (!fp)
204     {
205       err = gpg_error_from_syserror ();
206       log_error ("error opening '%s': %s\n",
207                  ctrl->server_local->devicename, gpg_strerror (err));
208       goto leave;
209     }
210
211   es_fclose (ctrl->server_local->devicefp);
212   ctrl->server_local->devicefp = fp;
213   fp = NULL;
214   ctrl->devti = ti;
215
216  leave:
217   es_fclose (fp);
218   if (err)
219     {
220       xfree (ctrl->server_local->devicename);
221       ctrl->server_local->devicename = NULL;
222       ctrl->devti = NULL;
223     }
224   return leave_cmd (ctx, err);
225 }
226
227
228 static const char hlp_create[] =
229   "CREATE <type>\n"
230   "\n"
231   "Create a new encrypted partition on the current device.\n"
232   "<type> must be \"dm-crypt\" for now.";
233 static gpg_error_t
234 cmd_create (assuan_context_t ctx, char *line)
235 {
236   ctrl_t ctrl = assuan_get_pointer (ctx);
237   gpg_error_t err = 0;
238
239   line = skip_options (line);
240
241   if (strcmp (line, "dm-crypt"))
242     {
243       err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
244       goto leave;
245     }
246
247   if (!ctrl->server_local->devicename
248       || !ctrl->server_local->devicefp
249       || !ctrl->devti)
250     {
251       err = set_error (GPG_ERR_ENOENT, "No device has been set");
252       goto leave;
253     }
254
255   err = sh_is_empty_partition (ctrl->server_local->devicename);
256   if (err)
257     {
258       assuan_set_error (ctx, err, "Partition is not empty");
259       goto leave;
260     }
261
262   err = sh_dmcrypt_create_container (ctrl,
263                                      ctrl->server_local->devicename,
264                                      ctrl->server_local->devicefp);
265
266
267
268
269  leave:
270   return leave_cmd (ctx, err);
271 }
272
273
274
275 static const char hlp_getinfo[] =
276   "GETINFO <what>\n"
277   "\n"
278   "Multipurpose function to return a variety of information.\n"
279   "Supported values for WHAT are:\n"
280   "\n"
281   "  version     - Return the version of the program.\n"
282   "  pid         - Return the process id of the server.\n"
283   "  showtab     - Show the table for the user.";
284 static gpg_error_t
285 cmd_getinfo (assuan_context_t ctx, char *line)
286 {
287   ctrl_t ctrl = assuan_get_pointer (ctx);
288   gpg_error_t err = 0;
289   char *buf;
290
291   if (!strcmp (line, "version"))
292     {
293       const char *s = PACKAGE_VERSION;
294       err = assuan_send_data (ctx, s, strlen (s));
295     }
296   else if (!strcmp (line, "pid"))
297     {
298       char numbuf[50];
299
300       snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
301       err = assuan_send_data (ctx, numbuf, strlen (numbuf));
302     }
303   else if (!strncmp (line, "getsz", 5))
304     {
305       unsigned long long nblocks;
306       err = sh_blockdev_getsz (line+6, &nblocks);
307       if (!err)
308         log_debug ("getsz=%llu\n", nblocks);
309     }
310   else if (!strcmp (line, "showtab"))
311     {
312       tab_item_t ti;
313
314       for (ti=ctrl->client.tab; !err && ti; ti = ti->next)
315         {
316           buf = es_bsprintf ("%s %s%s %s %s%s\n",
317                              ctrl->client.uname,
318                              *ti->blockdev=='/'? "":"partuuid=",
319                              ti->blockdev,
320                              ti->label? ti->label : "-",
321                              ti->mountpoint? " ":"",
322                              ti->mountpoint? ti->mountpoint:"");
323           if (!buf)
324             err = gpg_error_from_syserror ();
325           else
326             {
327               err = assuan_send_data (ctx, buf, strlen (buf));
328               if (!err)
329                 err = assuan_send_data (ctx, NULL, 0); /* Flush  */
330             }
331           xfree (buf);
332         }
333     }
334   else
335     err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
336
337   return leave_cmd (ctx, err);
338 }
339
340
341 /* This command handler is used for all commands if this process has
342    not been started as expected.  */
343 static gpg_error_t
344 fail_command (assuan_context_t ctx, char *line)
345 {
346   gpg_error_t err;
347   const char *name = assuan_get_command_name (ctx);
348
349   (void)line;
350
351   if (!name)
352     name = "?";
353
354   err = set_error_fail_cmd ();
355   log_error ("command '%s' failed: %s\n", name, gpg_strerror (err));
356   return err;
357 }
358
359
360 /* Tell the Assuan library about our commands.  */
361 static int
362 register_commands (assuan_context_t ctx, int fail_all)
363 {
364   static struct {
365     const char *name;
366     assuan_handler_t handler;
367     const char * const help;
368   } table[] =  {
369     { "DEVICE",        cmd_device, hlp_device },
370     { "CREATE",        cmd_create, hlp_create },
371     { "INPUT",         NULL },
372     { "OUTPUT",        NULL },
373     { "GETINFO",       cmd_getinfo, hlp_getinfo },
374     { NULL }
375   };
376   gpg_error_t err;
377   int i;
378
379   for (i=0; table[i].name; i++)
380     {
381       err = assuan_register_command (ctx, table[i].name,
382                                      fail_all ? fail_command : table[i].handler,
383                                      table[i].help);
384       if (err)
385         return err;
386     }
387   return 0;
388 }
389
390
391 /* Startup the server.  */
392 gpg_error_t
393 syshelp_server (ctrl_t ctrl)
394 {
395   gpg_error_t err;
396   assuan_fd_t filedes[2];
397   assuan_context_t ctx = NULL;
398
399   /* We use a pipe based server so that we can work from scripts.
400      assuan_init_pipe_server will automagically detect when we are
401      called with a socketpair and ignore FILEDES in this case. */
402   filedes[0] = assuan_fdopen (0);
403   filedes[1] = assuan_fdopen (1);
404   err = assuan_new (&ctx);
405   if (err)
406     {
407       log_error ("failed to allocate an Assuan context: %s\n",
408                  gpg_strerror (err));
409       goto leave;
410     }
411
412   err = assuan_init_pipe_server (ctx, filedes);
413   if (err)
414     {
415       log_error ("failed to initialize the server: %s\n", gpg_strerror (err));
416       goto leave;
417     }
418
419   err = register_commands (ctx, 0 /*FIXME:ctrl->fail_all_cmds*/);
420   if (err)
421     {
422       log_error ("failed to the register commands with Assuan: %s\n",
423                  gpg_strerror (err));
424       goto leave;
425     }
426
427   assuan_set_pointer (ctx, ctrl);
428
429   {
430     char *tmp = xtryasprintf ("G13-syshelp %s ready to serve requests "
431                               "from %lu(%s)",
432                               PACKAGE_VERSION,
433                               (unsigned long)ctrl->client.uid,
434                               ctrl->client.uname);
435     if (tmp)
436       {
437         assuan_set_hello_line (ctx, tmp);
438         xfree (tmp);
439       }
440   }
441
442   assuan_register_reset_notify (ctx, reset_notify);
443   assuan_register_option_handler (ctx, option_handler);
444
445   ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
446   if (!ctrl->server_local)
447     {
448       err = gpg_error_from_syserror ();
449       goto leave;
450     }
451   ctrl->server_local->assuan_ctx = ctx;
452
453   while ( !(err = assuan_accept (ctx)) )
454     {
455       err = assuan_process (ctx);
456       if (err)
457         log_info ("Assuan processing failed: %s\n", gpg_strerror (err));
458     }
459   if (err == -1)
460     err = 0;
461   else
462     log_info ("Assuan accept problem: %s\n", gpg_strerror (err));
463
464  leave:
465   reset_notify (ctx, NULL);  /* Release all items hold by SERVER_LOCAL.  */
466   if (ctrl->server_local)
467     {
468       xfree (ctrl->server_local);
469       ctrl->server_local = NULL;
470     }
471
472   assuan_release (ctx);
473   return err;
474 }
475
476
477 gpg_error_t
478 sh_encrypt_keyblob (ctrl_t ctrl, const void *keyblob, size_t keybloblen,
479                     char **r_enckeyblob, size_t *r_enckeybloblen)
480 {
481   assuan_context_t ctx = ctrl->server_local->assuan_ctx;
482   gpg_error_t err;
483   unsigned char *enckeyblob;
484   size_t enckeybloblen;
485
486   *r_enckeyblob = NULL;
487
488   /* Send the plaintext.  */
489   err = g13_status (ctrl, STATUS_PLAINTEXT_FOLLOWS, NULL);
490   if (err)
491     return err;
492   assuan_begin_confidential (ctx);
493   err = assuan_send_data (ctx, keyblob, keybloblen);
494   if (!err)
495     err = assuan_send_data (ctx, NULL, 0);
496   assuan_end_confidential (ctx);
497   if (!err)
498     err = assuan_write_line (ctx, "END");
499   if (err)
500     {
501       log_error (_("error sending data: %s\n"), gpg_strerror (err));
502       return err;
503     }
504
505   /* Inquire the ciphertext.  */
506   err = assuan_inquire (ctx, "ENCKEYBLOB",
507                         &enckeyblob, &enckeybloblen, 16 * 1024);
508   if (err)
509     {
510       log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
511       return err;
512     }
513
514   *r_enckeyblob = enckeyblob;
515   *r_enckeybloblen = enckeybloblen;
516   return 0;
517 }
518
519
520 /* Send a status line with status ID NO.  The arguments are a list of
521    strings terminated by a NULL argument.  */
522 gpg_error_t
523 g13_status (ctrl_t ctrl, int no, ...)
524 {
525   gpg_error_t err = 0;
526   va_list arg_ptr;
527   const char *text;
528
529   va_start (arg_ptr, no);
530
531   if (1)
532     {
533       assuan_context_t ctx = ctrl->server_local->assuan_ctx;
534       char buf[950], *p;
535       size_t n;
536
537       p = buf;
538       n = 0;
539       while ( (text = va_arg (arg_ptr, const char *)) )
540         {
541           if (n)
542             {
543               *p++ = ' ';
544               n++;
545             }
546           for ( ; *text && n < DIM (buf)-2; n++)
547             *p++ = *text++;
548         }
549       *p = 0;
550       err = assuan_write_status (ctx, get_status_string (no), buf);
551     }
552
553   va_end (arg_ptr);
554   return err;
555 }