2009-10-20 Marcus Brinkmann <marcus@g10code.com>
[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, NULL);
308     }
309   runner_release (runner);
310   encfs_handler_cleanup (parm);
311   return err;
312 }
313
314
315
316
317 \f
318 /* See be_get_detached_name for a description.  Note that the
319    dispatcher code makes sure that NULL is stored at R_NAME before
320    calling us. */
321 gpg_error_t
322 be_encfs_get_detached_name (const char *fname, char **r_name, int *r_isdir)
323 {
324   char *result;
325
326   if (!fname || !*fname)
327     return gpg_error (GPG_ERR_INV_ARG);
328
329   result = strconcat (fname, ".d", NULL);
330   if (!result)
331     return gpg_error_from_syserror ();
332   *r_name = result;
333   *r_isdir = 1;
334   return 0;
335 }
336
337
338 /* Create a new session key and append it as a tuple to the memory
339    buffer MB.  
340
341    The EncFS daemon takes a passphrase from stdin and internally
342    mangles it by means of some KDF from OpenSSL.  We want to store a
343    binary key but we need to make sure that certain characters are not
344    used because the EncFS utility reads it from stdin and obviously
345    acts on some of the characters.  This we replace CR (in case of an
346    MSDOS version of EncFS), LF (the delimiter used by EncFS) and Nul
347    (because it is unlikely to work).  We use 32 bytes (256 bit)
348    because that is sufficient for the largest cipher (AES-256) and in
349    addition gives enough margin for a possible entropy degradation by
350    the KDF.  */
351 gpg_error_t
352 be_encfs_create_new_keys (membuf_t *mb)
353 {
354   char *buffer;
355   int i, j;
356
357   /* Allocate a buffer of 32 bytes plus 8 spare bytes we may need to
358      replace the unwanted values.  */
359   buffer = xtrymalloc_secure (32+8);
360   if (!buffer)
361     return gpg_error_from_syserror ();
362
363   /* Randomize the buffer.  STRONG random should be enough as it is a
364      good compromise between security and performance.  The
365      anticipated usage of this tool is the quite often creation of new
366      containers and thus this should not deplete the system's entropy
367      tool too much.  */ 
368   gcry_randomize (buffer, 32+8, GCRY_STRONG_RANDOM);
369   for (i=j=0; i < 32; i++)
370     {
371       if (buffer[i] == '\r' || buffer[i] == '\n' || buffer[i] == 0 )
372         {
373           /* Replace.  */
374           if (j == 8)
375             {
376               /* Need to get more random.  */
377               gcry_randomize (buffer+32, 8, GCRY_STRONG_RANDOM);
378               j = 0;
379             }
380           buffer[i] = buffer[32+j];
381           j++;
382         }
383     }
384
385   /* Store the key.  */
386   append_tuple (mb, KEYBLOB_TAG_ENCKEY, buffer, 32);
387
388   /* Free the temporary buffer.  */
389   wipememory (buffer, 32+8);  /*  A failsafe extra wiping.  */
390   xfree (buffer);
391
392   return 0;
393 }
394
395
396 /* Create the container described by the filename FNAME and the keyblob
397    information in TUPLES. */
398 gpg_error_t
399 be_encfs_create_container (ctrl_t ctrl, const char *fname, tupledesc_t tuples,
400                            unsigned int *r_id)
401 {
402   gpg_error_t err;
403   int dummy;
404   char *containername = NULL;
405   char *mountpoint = NULL;
406
407   err = be_encfs_get_detached_name (fname, &containername, &dummy);
408   if (err)
409     goto leave;
410
411   mountpoint = xtrystrdup ("/tmp/.#g13_XXXXXX");
412   if (!mountpoint)
413     {
414       err = gpg_error_from_syserror ();
415       goto leave;
416     }
417   if (!mkdtemp (mountpoint))
418     {
419       err = gpg_error_from_syserror ();
420       log_error (_("can't create directory `%s': %s\n"),
421                  "/tmp/g13-XXXXXX", gpg_strerror (err));
422       goto leave;
423     }
424
425   err = run_encfs_tool (ctrl, ENCFS_CMD_CREATE, containername, mountpoint,
426                         tuples, r_id);
427   
428   /* In any case remove the temporary mount point.  */
429   if (rmdir (mountpoint))
430     log_error ("error removing temporary mount point `%s': %s\n",
431                mountpoint, gpg_strerror (gpg_error_from_syserror ()));
432
433
434  leave:
435   xfree (containername);
436   xfree (mountpoint);
437   return err;
438 }
439
440
441 /* Mount the container described by the filename FNAME and the keyblob
442    information in TUPLES.  On success the runner id is stored at R_ID. */
443 gpg_error_t
444 be_encfs_mount_container (ctrl_t ctrl, 
445                           const char *fname, const char *mountpoint,
446                           tupledesc_t tuples, unsigned int *r_id)
447 {
448   gpg_error_t err;
449   int dummy;
450   char *containername = NULL;
451
452   if (!mountpoint)
453     {
454       log_error ("the encfs backend requires an explicit mountpoint\n");
455       err = gpg_error (GPG_ERR_NOT_SUPPORTED);
456       goto leave;
457     }
458
459   err = be_encfs_get_detached_name (fname, &containername, &dummy);
460   if (err)
461     goto leave;
462
463   err = run_encfs_tool (ctrl, ENCFS_CMD_MOUNT, containername, mountpoint,
464                         tuples, r_id);
465   
466  leave:
467   xfree (containername);
468   return err;
469 }