chiark / gitweb /
Import gnupg2_2.1.17.orig.tar.bz2
[gnupg2.git] / g13 / be-encfs.c
1 /* be-encfs.c - The EncFS based backend
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 <https://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <unistd.h>
26 #include <assert.h>
27
28 #include "g13.h"
29 #include "i18n.h"
30 #include "keyblob.h"
31 #include "be-encfs.h"
32 #include "runner.h"
33 #include "../common/sysutils.h"
34 #include "../common/exechelp.h"
35
36
37 /* Command values used to run the encfs tool.  */
38 enum encfs_cmds
39   {
40     ENCFS_CMD_CREATE,
41     ENCFS_CMD_MOUNT,
42     ENCFS_CMD_UMOUNT
43   };
44
45
46 /* An object to keep the private state of the encfs tool.  It is
47    released by encfs_handler_cleanup.  */
48 struct encfs_parm_s
49 {
50   enum encfs_cmds cmd;  /* The current command. */
51   tupledesc_t tuples;   /* NULL or the tuples object.  */
52   char *mountpoint;     /* The mountpoint.  */
53 };
54 typedef struct encfs_parm_s *encfs_parm_t;
55
56
57 static gpg_error_t
58 send_cmd_bin (runner_t runner, const void *data, size_t datalen)
59 {
60   return runner_send_line (runner, data, datalen);
61 }
62
63
64 static gpg_error_t
65 send_cmd (runner_t runner, const char *string)
66 {
67   log_debug ("sending command  -->%s<--\n", string);
68   return send_cmd_bin (runner, string, strlen (string));
69 }
70
71
72
73 static void
74 run_umount_helper (const char *mountpoint)
75 {
76   gpg_error_t err;
77   const char pgmname[] = FUSERMOUNT;
78   const char *args[3];
79
80   args[0] = "-u";
81   args[1] = mountpoint;
82   args[2] = NULL;
83
84   err = gnupg_spawn_process_detached (pgmname, args, NULL);
85   if (err)
86     log_error ("failed to run '%s': %s\n",
87                pgmname, gpg_strerror (err));
88 }
89
90
91 /* Handle one line of the encfs tool's output.  This function is
92    allowed to modify the content of BUFFER.  */
93 static gpg_error_t
94 handle_status_line (runner_t runner, const char *line,
95                     enum encfs_cmds cmd, tupledesc_t tuples)
96 {
97   gpg_error_t err;
98
99   /* Check that encfs understands our new options.  */
100   if (!strncmp (line, "$STATUS$", 8))
101     {
102       for (line +=8; *line && spacep (line); line++)
103         ;
104       log_info ("got status '%s'\n", line);
105       if (!strcmp (line, "fuse_main_start"))
106         {
107           /* Send a special error code back to let the caller know
108              that everything has been setup by encfs.  */
109           err = gpg_error (GPG_ERR_UNFINISHED);
110         }
111       else
112         err = 0;
113     }
114   else if (!strncmp (line, "$PROMPT$", 8))
115     {
116       for (line +=8; *line && spacep (line); line++)
117         ;
118       log_info ("got prompt '%s'\n", line);
119       if (!strcmp (line, "create_root_dir"))
120         err = send_cmd (runner, cmd == ENCFS_CMD_CREATE? "y":"n");
121       else if (!strcmp (line, "create_mount_point"))
122         err = send_cmd (runner, "y");
123       else if (!strcmp (line, "passwd")
124                || !strcmp (line, "new_passwd"))
125         {
126           if (tuples)
127             {
128               size_t n;
129               const void *value;
130
131               value = find_tuple (tuples, KEYBLOB_TAG_ENCKEY, &n);
132               if (!value)
133                 err = gpg_error (GPG_ERR_INV_SESSION_KEY);
134               else if ((err = send_cmd_bin (runner, value, n)))
135                 {
136                   if (gpg_err_code (err) == GPG_ERR_BUG
137                       && gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
138                     err = gpg_error (GPG_ERR_INV_SESSION_KEY);
139                 }
140             }
141           else
142             err = gpg_error (GPG_ERR_NO_DATA);
143         }
144       else
145         err = send_cmd (runner, ""); /* Default to send an empty line.  */
146     }
147   else if (strstr (line, "encfs: unrecognized option '"))
148     err = gpg_error (GPG_ERR_INV_ENGINE);
149   else
150     err = 0;
151
152   return err;
153 }
154
155
156 /* The main processing function as used by the runner.  */
157 static gpg_error_t
158 encfs_handler (void *opaque, runner_t runner, const char *status_line)
159 {
160   encfs_parm_t parm = opaque;
161   gpg_error_t err;
162
163   if (!parm || !runner)
164     return gpg_error (GPG_ERR_BUG);
165   if (!status_line)
166     {
167       /* Runner requested internal flushing - nothing to do here. */
168       return 0;
169     }
170
171   err = handle_status_line (runner, status_line, parm->cmd, parm->tuples);
172   if (gpg_err_code (err) == GPG_ERR_UNFINISHED
173       && gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
174     {
175       err = 0;
176       /* No more need for the tuples.  */
177       destroy_tupledesc (parm->tuples);
178       parm->tuples = NULL;
179
180       if (parm->cmd == ENCFS_CMD_CREATE)
181         {
182           /* The encfs tool keeps on running after creation of the
183              container.  We don't want that and thus need to stop the
184              encfs process. */
185           run_umount_helper (parm->mountpoint);
186           /* In case the umount helper does not work we try to kill
187              the engine.  FIXME: We should figure out how to make
188              fusermount work.  */
189           runner_cancel (runner);
190         }
191     }
192
193   return err;
194 }
195
196
197 /* Called by the runner to cleanup the private data. */
198 static void
199 encfs_handler_cleanup (void *opaque)
200 {
201   encfs_parm_t parm = opaque;
202
203   if (!parm)
204     return;
205
206   destroy_tupledesc (parm->tuples);
207   xfree (parm->mountpoint);
208   xfree (parm);
209 }
210
211
212 /* Run the encfs tool.  */
213 static gpg_error_t
214 run_encfs_tool (ctrl_t ctrl, enum encfs_cmds cmd,
215                 const char *rawdir, const char *mountpoint, tupledesc_t tuples,
216                 unsigned int *r_id)
217 {
218   gpg_error_t err;
219   encfs_parm_t parm;
220   runner_t runner = NULL;
221   int outbound[2] = { -1, -1 };
222   int inbound[2]  = { -1, -1 };
223   const char *pgmname;
224   const char *argv[10];
225   pid_t pid = (pid_t)(-1);
226   int idx;
227
228   (void)ctrl;
229
230   parm = xtrycalloc (1, sizeof *parm);
231   if (!parm)
232     {
233       err = gpg_error_from_syserror ();
234       goto leave;
235     }
236   parm->cmd = cmd;
237   parm->tuples = ref_tupledesc (tuples);
238   parm->mountpoint = xtrystrdup (mountpoint);
239   if (!parm->mountpoint)
240     {
241       err = gpg_error_from_syserror ();
242       goto leave;
243     }
244
245   err = runner_new (&runner, "encfs");
246   if (err)
247     goto leave;
248
249   err = gnupg_create_inbound_pipe (inbound, NULL, 0);
250   if (!err)
251     err = gnupg_create_outbound_pipe (outbound, NULL, 0);
252   if (err)
253     {
254       log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
255       goto leave;
256     }
257
258   pgmname = ENCFS;
259   idx = 0;
260   argv[idx++] = "-f";
261   if (opt.verbose)
262     argv[idx++] = "-v";
263   argv[idx++] = "--stdinpass";
264   argv[idx++] = "--annotate";
265   argv[idx++] = rawdir;
266   argv[idx++] = mountpoint;
267   argv[idx++] = NULL;
268   assert (idx <= DIM (argv));
269
270   err = gnupg_spawn_process_fd (pgmname, argv,
271                                 outbound[0], -1, inbound[1], &pid);
272   if (err)
273     {
274       log_error ("error spawning '%s': %s\n", pgmname, gpg_strerror (err));
275       goto leave;
276     }
277   close (outbound[0]); outbound[0] = -1;
278   close ( inbound[1]);  inbound[1] = -1;
279
280   runner_set_fds (runner, inbound[0], outbound[1]);
281   inbound[0] = -1;  /* Now owned by RUNNER.  */
282   outbound[1] = -1; /* Now owned by RUNNER.  */
283
284   runner_set_handler (runner, encfs_handler, encfs_handler_cleanup, parm);
285   parm = NULL; /* Now owned by RUNNER.  */
286
287   runner_set_pid (runner, pid);
288   pid = (pid_t)(-1); /* The process is now owned by RUNNER.  */
289
290   err = runner_spawn (runner);
291   if (err)
292     goto leave;
293
294   *r_id = runner_get_rid (runner);
295   log_info ("running '%s' in the background\n", pgmname);
296
297  leave:
298   if (inbound[0] != -1)
299     close (inbound[0]);
300   if (inbound[1] != -1)
301     close (inbound[1]);
302   if (outbound[0] != -1)
303     close (outbound[0]);
304   if (outbound[1] != -1)
305     close (outbound[1]);
306   if (pid != (pid_t)(-1))
307     {
308       gnupg_wait_process (pgmname, pid, 1, NULL);
309       gnupg_release_process (pid);
310     }
311   runner_release (runner);
312   encfs_handler_cleanup (parm);
313   return err;
314 }
315
316
317
318
319 \f
320 /* See be_get_detached_name for a description.  Note that the
321    dispatcher code makes sure that NULL is stored at R_NAME before
322    calling us. */
323 gpg_error_t
324 be_encfs_get_detached_name (const char *fname, char **r_name, int *r_isdir)
325 {
326   char *result;
327
328   if (!fname || !*fname)
329     return gpg_error (GPG_ERR_INV_ARG);
330
331   result = strconcat (fname, ".d", NULL);
332   if (!result)
333     return gpg_error_from_syserror ();
334   *r_name = result;
335   *r_isdir = 1;
336   return 0;
337 }
338
339
340 /* Create a new session key and append it as a tuple to the memory
341    buffer MB.
342
343    The EncFS daemon takes a passphrase from stdin and internally
344    mangles it by means of some KDF from OpenSSL.  We want to store a
345    binary key but we need to make sure that certain characters are not
346    used because the EncFS utility reads it from stdin and obviously
347    acts on some of the characters.  This we replace CR (in case of an
348    MSDOS version of EncFS), LF (the delimiter used by EncFS) and Nul
349    (because it is unlikely to work).  We use 32 bytes (256 bit)
350    because that is sufficient for the largest cipher (AES-256) and in
351    addition gives enough margin for a possible entropy degradation by
352    the KDF.  */
353 gpg_error_t
354 be_encfs_create_new_keys (membuf_t *mb)
355 {
356   char *buffer;
357   int i, j;
358
359   /* Allocate a buffer of 32 bytes plus 8 spare bytes we may need to
360      replace the unwanted values.  */
361   buffer = xtrymalloc_secure (32+8);
362   if (!buffer)
363     return gpg_error_from_syserror ();
364
365   /* Randomize the buffer.  STRONG random should be enough as it is a
366      good compromise between security and performance.  The
367      anticipated usage of this tool is the quite often creation of new
368      containers and thus this should not deplete the system's entropy
369      tool too much.  */
370   gcry_randomize (buffer, 32+8, GCRY_STRONG_RANDOM);
371   for (i=j=0; i < 32; i++)
372     {
373       if (buffer[i] == '\r' || buffer[i] == '\n' || buffer[i] == 0 )
374         {
375           /* Replace.  */
376           if (j == 8)
377             {
378               /* Need to get more random.  */
379               gcry_randomize (buffer+32, 8, GCRY_STRONG_RANDOM);
380               j = 0;
381             }
382           buffer[i] = buffer[32+j];
383           j++;
384         }
385     }
386
387   /* Store the key.  */
388   append_tuple (mb, KEYBLOB_TAG_ENCKEY, buffer, 32);
389
390   /* Free the temporary buffer.  */
391   wipememory (buffer, 32+8);  /*  A failsafe extra wiping.  */
392   xfree (buffer);
393
394   return 0;
395 }
396
397
398 /* Create the container described by the filename FNAME and the keyblob
399    information in TUPLES. */
400 gpg_error_t
401 be_encfs_create_container (ctrl_t ctrl, const char *fname, tupledesc_t tuples,
402                            unsigned int *r_id)
403 {
404   gpg_error_t err;
405   int dummy;
406   char *containername = NULL;
407   char *mountpoint = NULL;
408
409   err = be_encfs_get_detached_name (fname, &containername, &dummy);
410   if (err)
411     goto leave;
412
413   mountpoint = xtrystrdup ("/tmp/.#g13_XXXXXX");
414   if (!mountpoint)
415     {
416       err = gpg_error_from_syserror ();
417       goto leave;
418     }
419   if (!gnupg_mkdtemp (mountpoint))
420     {
421       err = gpg_error_from_syserror ();
422       log_error (_("can't create directory '%s': %s\n"),
423                  "/tmp/.#g13_XXXXXX", gpg_strerror (err));
424       goto leave;
425     }
426
427   err = run_encfs_tool (ctrl, ENCFS_CMD_CREATE, containername, mountpoint,
428                         tuples, r_id);
429
430   /* In any case remove the temporary mount point.  */
431   if (rmdir (mountpoint))
432     log_error ("error removing temporary mount point '%s': %s\n",
433                mountpoint, gpg_strerror (gpg_error_from_syserror ()));
434
435
436  leave:
437   xfree (containername);
438   xfree (mountpoint);
439   return err;
440 }
441
442
443 /* Mount the container described by the filename FNAME and the keyblob
444    information in TUPLES.  On success the runner id is stored at R_ID. */
445 gpg_error_t
446 be_encfs_mount_container (ctrl_t ctrl,
447                           const char *fname, const char *mountpoint,
448                           tupledesc_t tuples, unsigned int *r_id)
449 {
450   gpg_error_t err;
451   int dummy;
452   char *containername = NULL;
453
454   if (!mountpoint)
455     {
456       log_error ("the encfs backend requires an explicit mountpoint\n");
457       err = gpg_error (GPG_ERR_NOT_SUPPORTED);
458       goto leave;
459     }
460
461   err = be_encfs_get_detached_name (fname, &containername, &dummy);
462   if (err)
463     goto leave;
464
465   err = run_encfs_tool (ctrl, ENCFS_CMD_MOUNT, containername, mountpoint,
466                         tuples, r_id);
467
468  leave:
469   xfree (containername);
470   return err;
471 }