mirror of
git://git.gnupg.org/gnupg.git
synced 2024-10-31 20:08:43 +01:00
5d991e333a
* common/exechelp-posix.c (gnupg_create_inbound_pipe): Add args 'r_fp' and 'nonblock'. (gnupg_create_outbound_pipe): Ditto. * common/exechelp-w32.c (gnupg_create_inbound_pipe): Add non yet functional args 'r_fp' and 'nonblock'. (gnupg_create_outbound_pipe): Ditto. * common/exechelp-w32ce.c (gnupg_create_inbound_pipe): Ditto. (gnupg_create_outbound_pipe): Ditto. -- Signed-off-by: Werner Koch <wk@gnupg.org>
472 lines
12 KiB
C
472 lines
12 KiB
C
/* be-encfs.c - The EncFS based backend
|
||
* Copyright (C) 2009 Free Software Foundation, Inc.
|
||
*
|
||
* This file is part of GnuPG.
|
||
*
|
||
* GnuPG is free software; you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation; either version 3 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* GnuPG is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include <config.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <errno.h>
|
||
#include <unistd.h>
|
||
#include <assert.h>
|
||
|
||
#include "g13.h"
|
||
#include "i18n.h"
|
||
#include "keyblob.h"
|
||
#include "be-encfs.h"
|
||
#include "runner.h"
|
||
#include "../common/sysutils.h"
|
||
#include "../common/exechelp.h"
|
||
|
||
|
||
/* Command values used to run the encfs tool. */
|
||
enum encfs_cmds
|
||
{
|
||
ENCFS_CMD_CREATE,
|
||
ENCFS_CMD_MOUNT,
|
||
ENCFS_CMD_UMOUNT
|
||
};
|
||
|
||
|
||
/* An object to keep the private state of the encfs tool. It is
|
||
released by encfs_handler_cleanup. */
|
||
struct encfs_parm_s
|
||
{
|
||
enum encfs_cmds cmd; /* The current command. */
|
||
tupledesc_t tuples; /* NULL or the tuples object. */
|
||
char *mountpoint; /* The mountpoint. */
|
||
};
|
||
typedef struct encfs_parm_s *encfs_parm_t;
|
||
|
||
|
||
static gpg_error_t
|
||
send_cmd_bin (runner_t runner, const void *data, size_t datalen)
|
||
{
|
||
return runner_send_line (runner, data, datalen);
|
||
}
|
||
|
||
|
||
static gpg_error_t
|
||
send_cmd (runner_t runner, const char *string)
|
||
{
|
||
log_debug ("sending command -->%s<--\n", string);
|
||
return send_cmd_bin (runner, string, strlen (string));
|
||
}
|
||
|
||
|
||
|
||
static void
|
||
run_umount_helper (const char *mountpoint)
|
||
{
|
||
gpg_error_t err;
|
||
const char pgmname[] = FUSERMOUNT;
|
||
const char *args[3];
|
||
|
||
args[0] = "-u";
|
||
args[1] = mountpoint;
|
||
args[2] = NULL;
|
||
|
||
err = gnupg_spawn_process_detached (pgmname, args, NULL);
|
||
if (err)
|
||
log_error ("failed to run '%s': %s\n",
|
||
pgmname, gpg_strerror (err));
|
||
}
|
||
|
||
|
||
/* Handle one line of the encfs tool's output. This function is
|
||
allowed to modify the content of BUFFER. */
|
||
static gpg_error_t
|
||
handle_status_line (runner_t runner, const char *line,
|
||
enum encfs_cmds cmd, tupledesc_t tuples)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
/* Check that encfs understands our new options. */
|
||
if (!strncmp (line, "$STATUS$", 8))
|
||
{
|
||
for (line +=8; *line && spacep (line); line++)
|
||
;
|
||
log_info ("got status '%s'\n", line);
|
||
if (!strcmp (line, "fuse_main_start"))
|
||
{
|
||
/* Send a special error code back to let the caller know
|
||
that everything has been setup by encfs. */
|
||
err = gpg_error (GPG_ERR_UNFINISHED);
|
||
}
|
||
else
|
||
err = 0;
|
||
}
|
||
else if (!strncmp (line, "$PROMPT$", 8))
|
||
{
|
||
for (line +=8; *line && spacep (line); line++)
|
||
;
|
||
log_info ("got prompt '%s'\n", line);
|
||
if (!strcmp (line, "create_root_dir"))
|
||
err = send_cmd (runner, cmd == ENCFS_CMD_CREATE? "y":"n");
|
||
else if (!strcmp (line, "create_mount_point"))
|
||
err = send_cmd (runner, "y");
|
||
else if (!strcmp (line, "passwd")
|
||
|| !strcmp (line, "new_passwd"))
|
||
{
|
||
if (tuples)
|
||
{
|
||
size_t n;
|
||
const void *value;
|
||
|
||
value = find_tuple (tuples, KEYBLOB_TAG_ENCKEY, &n);
|
||
if (!value)
|
||
err = gpg_error (GPG_ERR_INV_SESSION_KEY);
|
||
else if ((err = send_cmd_bin (runner, value, n)))
|
||
{
|
||
if (gpg_err_code (err) == GPG_ERR_BUG
|
||
&& gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
|
||
err = gpg_error (GPG_ERR_INV_SESSION_KEY);
|
||
}
|
||
}
|
||
else
|
||
err = gpg_error (GPG_ERR_NO_DATA);
|
||
}
|
||
else
|
||
err = send_cmd (runner, ""); /* Default to send an empty line. */
|
||
}
|
||
else if (strstr (line, "encfs: unrecognized option '"))
|
||
err = gpg_error (GPG_ERR_INV_ENGINE);
|
||
else
|
||
err = 0;
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* The main processing function as used by the runner. */
|
||
static gpg_error_t
|
||
encfs_handler (void *opaque, runner_t runner, const char *status_line)
|
||
{
|
||
encfs_parm_t parm = opaque;
|
||
gpg_error_t err;
|
||
|
||
if (!parm || !runner)
|
||
return gpg_error (GPG_ERR_BUG);
|
||
if (!status_line)
|
||
{
|
||
/* Runner requested internal flushing - nothing to do here. */
|
||
return 0;
|
||
}
|
||
|
||
err = handle_status_line (runner, status_line, parm->cmd, parm->tuples);
|
||
if (gpg_err_code (err) == GPG_ERR_UNFINISHED
|
||
&& gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
|
||
{
|
||
err = 0;
|
||
/* No more need for the tuples. */
|
||
destroy_tupledesc (parm->tuples);
|
||
parm->tuples = NULL;
|
||
|
||
if (parm->cmd == ENCFS_CMD_CREATE)
|
||
{
|
||
/* The encfs tool keeps on running after creation of the
|
||
container. We don't want that and thus need to stop the
|
||
encfs process. */
|
||
run_umount_helper (parm->mountpoint);
|
||
/* In case the umount helper does not work we try to kill
|
||
the engine. FIXME: We should figure out how to make
|
||
fusermount work. */
|
||
runner_cancel (runner);
|
||
}
|
||
}
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Called by the runner to cleanup the private data. */
|
||
static void
|
||
encfs_handler_cleanup (void *opaque)
|
||
{
|
||
encfs_parm_t parm = opaque;
|
||
|
||
if (!parm)
|
||
return;
|
||
|
||
destroy_tupledesc (parm->tuples);
|
||
xfree (parm->mountpoint);
|
||
xfree (parm);
|
||
}
|
||
|
||
|
||
/* Run the encfs tool. */
|
||
static gpg_error_t
|
||
run_encfs_tool (ctrl_t ctrl, enum encfs_cmds cmd,
|
||
const char *rawdir, const char *mountpoint, tupledesc_t tuples,
|
||
unsigned int *r_id)
|
||
{
|
||
gpg_error_t err;
|
||
encfs_parm_t parm;
|
||
runner_t runner = NULL;
|
||
int outbound[2] = { -1, -1 };
|
||
int inbound[2] = { -1, -1 };
|
||
const char *pgmname;
|
||
const char *argv[10];
|
||
pid_t pid = (pid_t)(-1);
|
||
int idx;
|
||
|
||
(void)ctrl;
|
||
|
||
parm = xtrycalloc (1, sizeof *parm);
|
||
if (!parm)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto leave;
|
||
}
|
||
parm->cmd = cmd;
|
||
parm->tuples = ref_tupledesc (tuples);
|
||
parm->mountpoint = xtrystrdup (mountpoint);
|
||
if (!parm->mountpoint)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto leave;
|
||
}
|
||
|
||
err = runner_new (&runner, "encfs");
|
||
if (err)
|
||
goto leave;
|
||
|
||
err = gnupg_create_inbound_pipe (inbound, NULL, 0);
|
||
if (!err)
|
||
err = gnupg_create_outbound_pipe (outbound, NULL, 0);
|
||
if (err)
|
||
{
|
||
log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
|
||
goto leave;
|
||
}
|
||
|
||
pgmname = ENCFS;
|
||
idx = 0;
|
||
argv[idx++] = "-f";
|
||
if (opt.verbose)
|
||
argv[idx++] = "-v";
|
||
argv[idx++] = "--stdinpass";
|
||
argv[idx++] = "--annotate";
|
||
argv[idx++] = rawdir;
|
||
argv[idx++] = mountpoint;
|
||
argv[idx++] = NULL;
|
||
assert (idx <= DIM (argv));
|
||
|
||
err = gnupg_spawn_process_fd (pgmname, argv,
|
||
outbound[0], -1, inbound[1], &pid);
|
||
if (err)
|
||
{
|
||
log_error ("error spawning '%s': %s\n", pgmname, gpg_strerror (err));
|
||
goto leave;
|
||
}
|
||
close (outbound[0]); outbound[0] = -1;
|
||
close ( inbound[1]); inbound[1] = -1;
|
||
|
||
runner_set_fds (runner, inbound[0], outbound[1]);
|
||
inbound[0] = -1; /* Now owned by RUNNER. */
|
||
outbound[1] = -1; /* Now owned by RUNNER. */
|
||
|
||
runner_set_handler (runner, encfs_handler, encfs_handler_cleanup, parm);
|
||
parm = NULL; /* Now owned by RUNNER. */
|
||
|
||
runner_set_pid (runner, pid);
|
||
pid = (pid_t)(-1); /* The process is now owned by RUNNER. */
|
||
|
||
err = runner_spawn (runner);
|
||
if (err)
|
||
goto leave;
|
||
|
||
*r_id = runner_get_rid (runner);
|
||
log_info ("running '%s' in the background\n", pgmname);
|
||
|
||
leave:
|
||
if (inbound[0] != -1)
|
||
close (inbound[0]);
|
||
if (inbound[1] != -1)
|
||
close (inbound[1]);
|
||
if (outbound[0] != -1)
|
||
close (outbound[0]);
|
||
if (outbound[1] != -1)
|
||
close (outbound[1]);
|
||
if (pid != (pid_t)(-1))
|
||
{
|
||
gnupg_wait_process (pgmname, pid, 1, NULL);
|
||
gnupg_release_process (pid);
|
||
}
|
||
runner_release (runner);
|
||
encfs_handler_cleanup (parm);
|
||
return err;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
/* See be_get_detached_name for a description. Note that the
|
||
dispatcher code makes sure that NULL is stored at R_NAME before
|
||
calling us. */
|
||
gpg_error_t
|
||
be_encfs_get_detached_name (const char *fname, char **r_name, int *r_isdir)
|
||
{
|
||
char *result;
|
||
|
||
if (!fname || !*fname)
|
||
return gpg_error (GPG_ERR_INV_ARG);
|
||
|
||
result = strconcat (fname, ".d", NULL);
|
||
if (!result)
|
||
return gpg_error_from_syserror ();
|
||
*r_name = result;
|
||
*r_isdir = 1;
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Create a new session key and append it as a tuple to the memory
|
||
buffer MB.
|
||
|
||
The EncFS daemon takes a passphrase from stdin and internally
|
||
mangles it by means of some KDF from OpenSSL. We want to store a
|
||
binary key but we need to make sure that certain characters are not
|
||
used because the EncFS utility reads it from stdin and obviously
|
||
acts on some of the characters. This we replace CR (in case of an
|
||
MSDOS version of EncFS), LF (the delimiter used by EncFS) and Nul
|
||
(because it is unlikely to work). We use 32 bytes (256 bit)
|
||
because that is sufficient for the largest cipher (AES-256) and in
|
||
addition gives enough margin for a possible entropy degradation by
|
||
the KDF. */
|
||
gpg_error_t
|
||
be_encfs_create_new_keys (membuf_t *mb)
|
||
{
|
||
char *buffer;
|
||
int i, j;
|
||
|
||
/* Allocate a buffer of 32 bytes plus 8 spare bytes we may need to
|
||
replace the unwanted values. */
|
||
buffer = xtrymalloc_secure (32+8);
|
||
if (!buffer)
|
||
return gpg_error_from_syserror ();
|
||
|
||
/* Randomize the buffer. STRONG random should be enough as it is a
|
||
good compromise between security and performance. The
|
||
anticipated usage of this tool is the quite often creation of new
|
||
containers and thus this should not deplete the system's entropy
|
||
tool too much. */
|
||
gcry_randomize (buffer, 32+8, GCRY_STRONG_RANDOM);
|
||
for (i=j=0; i < 32; i++)
|
||
{
|
||
if (buffer[i] == '\r' || buffer[i] == '\n' || buffer[i] == 0 )
|
||
{
|
||
/* Replace. */
|
||
if (j == 8)
|
||
{
|
||
/* Need to get more random. */
|
||
gcry_randomize (buffer+32, 8, GCRY_STRONG_RANDOM);
|
||
j = 0;
|
||
}
|
||
buffer[i] = buffer[32+j];
|
||
j++;
|
||
}
|
||
}
|
||
|
||
/* Store the key. */
|
||
append_tuple (mb, KEYBLOB_TAG_ENCKEY, buffer, 32);
|
||
|
||
/* Free the temporary buffer. */
|
||
wipememory (buffer, 32+8); /* A failsafe extra wiping. */
|
||
xfree (buffer);
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Create the container described by the filename FNAME and the keyblob
|
||
information in TUPLES. */
|
||
gpg_error_t
|
||
be_encfs_create_container (ctrl_t ctrl, const char *fname, tupledesc_t tuples,
|
||
unsigned int *r_id)
|
||
{
|
||
gpg_error_t err;
|
||
int dummy;
|
||
char *containername = NULL;
|
||
char *mountpoint = NULL;
|
||
|
||
err = be_encfs_get_detached_name (fname, &containername, &dummy);
|
||
if (err)
|
||
goto leave;
|
||
|
||
mountpoint = xtrystrdup ("/tmp/.#g13_XXXXXX");
|
||
if (!mountpoint)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto leave;
|
||
}
|
||
if (!gnupg_mkdtemp (mountpoint))
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
log_error (_("can't create directory '%s': %s\n"),
|
||
"/tmp/.#g13_XXXXXX", gpg_strerror (err));
|
||
goto leave;
|
||
}
|
||
|
||
err = run_encfs_tool (ctrl, ENCFS_CMD_CREATE, containername, mountpoint,
|
||
tuples, r_id);
|
||
|
||
/* In any case remove the temporary mount point. */
|
||
if (rmdir (mountpoint))
|
||
log_error ("error removing temporary mount point '%s': %s\n",
|
||
mountpoint, gpg_strerror (gpg_error_from_syserror ()));
|
||
|
||
|
||
leave:
|
||
xfree (containername);
|
||
xfree (mountpoint);
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Mount the container described by the filename FNAME and the keyblob
|
||
information in TUPLES. On success the runner id is stored at R_ID. */
|
||
gpg_error_t
|
||
be_encfs_mount_container (ctrl_t ctrl,
|
||
const char *fname, const char *mountpoint,
|
||
tupledesc_t tuples, unsigned int *r_id)
|
||
{
|
||
gpg_error_t err;
|
||
int dummy;
|
||
char *containername = NULL;
|
||
|
||
if (!mountpoint)
|
||
{
|
||
log_error ("the encfs backend requires an explicit mountpoint\n");
|
||
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
|
||
goto leave;
|
||
}
|
||
|
||
err = be_encfs_get_detached_name (fname, &containername, &dummy);
|
||
if (err)
|
||
goto leave;
|
||
|
||
err = run_encfs_tool (ctrl, ENCFS_CMD_MOUNT, containername, mountpoint,
|
||
tuples, r_id);
|
||
|
||
leave:
|
||
xfree (containername);
|
||
return err;
|
||
}
|