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