summaryrefslogtreecommitdiffstats
path: root/g13
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2015-10-21 08:38:10 +0200
committerWerner Koch <wk@gnupg.org>2016-02-13 17:06:38 +0100
commit81494fd30d3815502247a721f50d9eadf86a73fa (patch)
tree5bb17e6db5ffcbbfd0d28dc3379038683b78468f /g13
parenttests: Remove some harmless warnings in regression tests. (diff)
downloadgnupg2-81494fd30d3815502247a721f50d9eadf86a73fa.tar.xz
gnupg2-81494fd30d3815502247a721f50d9eadf86a73fa.zip
g13: First chunk of code to support dm-crypt.
* g13/call-syshelp.c, g13/call-syshelp.h: New. * g13/g13-syshelp.c, g13/g13-syshelp.h: New. * g13/sh-cmd.c: New. * g13/sh-blockdev.c: New. * g13/sh-exectool.c: New. * g13/sh-dmcrypt.c: New. * g13/Makefile.am (sbin_PROGRAMS): Add g13-syshelp.c (g13_syshelp_SOURCES): New. (g13_syshelp_LDADD): New. * g13/g13.c (opts): Add option --type. (g13_deinit_default_ctrl): New. (main): Implement that option. Call g13_deinit_default_ctrl. * g13/g13.h (struct call_syshelp_s): New declaration. (server_control_s): Add field syshelp_local. * g13/keyblob.h (KEYBLOB_TAG_CREATED): New. (KEYBLOB_TAG_ALGOSTR): New. (KEYBLOB_TAG_HDRCOPY): New. * g13/backend.c (be_parse_conttype_name): New. (be_get_detached_name): Add CONTTYPE_DM_CRYPT. Signed-off-by: Werner Koch <wk@gnupg.org>
Diffstat (limited to 'g13')
-rw-r--r--g13/Makefile.am17
-rw-r--r--g13/backend.c35
-rw-r--r--g13/backend.h3
-rw-r--r--g13/call-syshelp.c124
-rw-r--r--g13/call-syshelp.h26
-rw-r--r--g13/g13-syshelp.c720
-rw-r--r--g13/g13-syshelp.h93
-rw-r--r--g13/g13.c29
-rw-r--r--g13/g13.h7
-rw-r--r--g13/keyblob.h29
-rw-r--r--g13/sh-blockdev.c151
-rw-r--r--g13/sh-cmd.c555
-rw-r--r--g13/sh-dmcrypt.c406
-rw-r--r--g13/sh-exectool.c303
14 files changed, 2489 insertions, 9 deletions
diff --git a/g13/Makefile.am b/g13/Makefile.am
index e17d099c3..a3cd1333d 100644
--- a/g13/Makefile.am
+++ b/g13/Makefile.am
@@ -21,6 +21,7 @@
EXTRA_DIST = ChangeLog-2011
bin_PROGRAMS = g13
+sbin_PROGRAMS = g13-syshelp
AM_CPPFLAGS = -I$(top_srcdir)/common
@@ -37,6 +38,7 @@ g13_SOURCES = \
create.c create.h \
mount.c mount.h \
mountinfo.c mountinfo.h \
+ call-syshelp.c call-syshelp.h \
runner.c runner.h \
backend.c backend.h \
be-encfs.c be-encfs.h \
@@ -45,3 +47,18 @@ g13_SOURCES = \
g13_LDADD = $(libcommonpth) \
$(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
$(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV)
+
+
+g13_syshelp_SOURCES = \
+ g13-syshelp.c g13-syshelp.h \
+ g13-common.c g13-common.h \
+ keyblob.h \
+ utils.c utils.h \
+ sh-cmd.c \
+ sh-exectool.c \
+ sh-blockdev.c \
+ sh-dmcrypt.c
+
+g13_syshelp_LDADD = $(libcommon) \
+ $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) \
+ $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV)
diff --git a/g13/backend.c b/g13/backend.c
index 7b08cd52a..52e1e0a34 100644
--- a/g13/backend.c
+++ b/g13/backend.c
@@ -41,6 +41,38 @@ no_such_backend (int conttype)
}
+/* Parse NAME and return the corresponding content type. If the name
+ is not known, a error message is printed and zero returned. If
+ NAME is NULL the supported backend types are listed and 0 is
+ returned. */
+int
+be_parse_conttype_name (const char *name)
+{
+ static struct { const char *name; int conttype; } names[] = {
+ { "encfs", CONTTYPE_ENCFS },
+ { "dm-crypt", CONTTYPE_DM_CRYPT }
+ };
+ int i;
+
+ if (!name)
+ {
+ log_info ("Known backend types:\n");
+ for (i=0; i < DIM (names); i++)
+ log_info (" %s\n", names[i].name);
+ return 0;
+ }
+
+ for (i=0; i < DIM (names); i++)
+ {
+ if (!strcmp (names[i].name, name))
+ return names[i].conttype;
+ }
+
+ log_error ("invalid backend type '%s' given\n", name);
+ return 0;
+}
+
+
/* Return true if CONTTYPE is supported by us. */
int
be_is_supported_conttype (int conttype)
@@ -75,6 +107,9 @@ be_get_detached_name (int conttype, const char *fname,
case CONTTYPE_ENCFS:
return be_encfs_get_detached_name (fname, r_name, r_isdir);
+ case CONTTYPE_DM_CRYPT:
+ return 0;
+
default:
return no_such_backend (conttype);
}
diff --git a/g13/backend.h b/g13/backend.h
index 20d296606..e570fc5a1 100644
--- a/g13/backend.h
+++ b/g13/backend.h
@@ -23,7 +23,8 @@
#include "../common/membuf.h"
#include "utils.h" /* For tupledesc_t */
-int be_is_supported_conttype (int conttype);
+int be_parse_conttype_name (const char *name);
+int be_is_supported_conttype (int conttype);
gpg_error_t be_get_detached_name (int conttype, const char *fname,
char **r_name, int *r_isdir);
gpg_error_t be_create_new_keys (int conttype, membuf_t *mb);
diff --git a/g13/call-syshelp.c b/g13/call-syshelp.c
new file mode 100644
index 000000000..2086dd1db
--- /dev/null
+++ b/g13/call-syshelp.c
@@ -0,0 +1,124 @@
+/* call-syshelp.c - Communication with g13-syshelp
+ * Copyright (C) 2015 Werner Koch
+ *
+ * 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 <time.h>
+#include <assert.h>
+#include <npth.h>
+
+#include "g13.h"
+#include <assuan.h>
+#include "i18n.h"
+#include "utils.h"
+
+/* Local data for this module. A pointer to this is stored in the
+ CTRL object of each connection. */
+struct call_syshelp_s
+{
+ assuan_context_t assctx; /* The Assuan context for the current
+ g13-syshep connection. */
+};
+
+
+/* Fork off the syshelp tool if this has not already been done. */
+static gpg_error_t
+start_syshelp (ctrl_t ctrl)
+{
+ gpg_error_t err;
+ assuan_context_t ctx;
+ assuan_fd_t no_close_list[3];
+ int i;
+
+ if (ctrl->syshelp_local->assctx)
+ return 0; /* Already set. */
+
+ if (opt.verbose)
+ log_info ("starting a new syshelp\n");
+
+ if (es_fflush (NULL))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error flushing pending output: %s\n", gpg_strerror (err));
+ return err;
+ }
+
+ i = 0;
+ if (log_get_fd () != -1)
+ no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ());
+ no_close_list[i++] = assuan_fd_from_posix_fd (es_fileno (es_stderr));
+ no_close_list[i] = ASSUAN_INVALID_FD;
+
+ err = assuan_new (&ctx);
+ if (err)
+ {
+ log_error ("can't allocate assuan context: %s\n", gpg_strerror (err));
+ return err;
+ }
+
+ /* Call userv to start g13-syshelp. This userv script needs tpo be
+ installed under the name "gnupg-g13-syshelp":
+
+ if ( glob service-user root
+ )
+ reset
+ suppress-args
+ execute /home/wk/b/gnupg/g13/g13-syshelp -v
+ else
+ error Nothing to do for this service-user
+ fi
+ quit
+ */
+ {
+ const char *argv[3];
+
+ argv[0] = "userv";
+ argv[1] = "gnupg-g13-syshelp";
+ argv[2] = NULL;
+
+ err = assuan_pipe_connect (ctx, "/usr/bin/userv", argv,
+ no_close_list, NULL, NULL, 0);
+ }
+ if (err)
+ {
+ log_error ("can't connect to '%s' - : %s\n",
+ "g13-syshelp", gpg_strerror (err));
+ log_info ("(is userv and its gnupg-g13-syshelp script installed?)\n");
+ assuan_release (ctx);
+ return err;
+ }
+ ctrl->syshelp_local->assctx = ctx;
+
+ if (DBG_IPC)
+ log_debug ("connection to g13-syshelp established\n");
+
+ return 0;
+}
+
+
+/* Release local resources associated with CTRL. */
+void
+call_syshelp_release (ctrl_t ctrl)
+{
+ assuan_release (ctrl->syshelp_local->assctx);
+ ctrl->syshelp_local->assctx = NULL;
+}
diff --git a/g13/call-syshelp.h b/g13/call-syshelp.h
new file mode 100644
index 000000000..c78d53b5c
--- /dev/null
+++ b/g13/call-syshelp.h
@@ -0,0 +1,26 @@
+/* call-syshelp.h - Communication with g13-syshelp
+ * Copyright (C) 2015 Werner Koch
+ *
+ * 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/>.
+ */
+
+#ifndef GNUPG_G13_CALL_SYSHELP_H
+#define GNUPG_G13_CALL_SYSHELP_H
+
+void call_syshelp_release (ctrl_t ctrl);
+
+
+#endif /*GNUPG_G13_CALL_SYSHELP_H*/
diff --git a/g13/g13-syshelp.c b/g13/g13-syshelp.c
new file mode 100644
index 000000000..c09a5e917
--- /dev/null
+++ b/g13/g13-syshelp.c
@@ -0,0 +1,720 @@
+/* g13-syshelp.c - Helper for disk key management with GnuPG
+ * Copyright (C) 2015 Werner Koch
+ *
+ * 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 <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#ifdef HAVE_PWD_H
+# include <pwd.h>
+#endif
+#include <unistd.h>
+
+#include "g13-syshelp.h"
+
+#include <gcrypt.h>
+#include <assuan.h>
+
+#include "i18n.h"
+#include "sysutils.h"
+#include "asshelp.h"
+#include "../common/init.h"
+#include "keyblob.h"
+
+
+enum cmd_and_opt_values {
+ aNull = 0,
+ oQuiet = 'q',
+ oVerbose = 'v',
+ oRecipient = 'r',
+
+ aGPGConfList = 500,
+
+ oDebug,
+ oDebugLevel,
+ oDebugAll,
+ oDebugNone,
+ oDebugWait,
+ oDebugAllowCoreDump,
+ oLogFile,
+ oNoLogFile,
+ oAuditLog,
+
+ oOutput,
+
+ oAgentProgram,
+ oGpgProgram,
+ oType,
+
+ oDisplay,
+ oTTYname,
+ oTTYtype,
+ oLCctype,
+ oLCmessages,
+ oXauthority,
+
+ oStatusFD,
+ oLoggerFD,
+
+ oNoVerbose,
+ oNoSecmemWarn,
+ oHomedir,
+ oDryRun,
+ oNoDetach,
+
+ oNoRandomSeedFile,
+ oFakedSystemTime
+ };
+
+
+static ARGPARSE_OPTS opts[] = {
+
+ ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
+
+ ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
+ ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
+
+ ARGPARSE_s_s (oDebug, "debug", "@"),
+ ARGPARSE_s_s (oDebugLevel, "debug-level",
+ N_("|LEVEL|set the debugging level to LEVEL")),
+ ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
+ ARGPARSE_s_n (oDebugNone, "debug-none", "@"),
+ ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
+ ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
+
+ ARGPARSE_end ()
+};
+
+
+/* The list of supported debug flags. */
+static struct debug_flags_s debug_flags [] =
+ {
+ { DBG_MOUNT_VALUE , "mount" },
+ { DBG_CRYPTO_VALUE , "crypto" },
+ { DBG_MEMORY_VALUE , "memory" },
+ { DBG_MEMSTAT_VALUE, "memstat" },
+ { DBG_IPC_VALUE , "ipc" },
+ { 0, NULL }
+ };
+
+
+/* The timer tick interval used by the idle task. */
+#define TIMERTICK_INTERVAL_SEC (1)
+
+/* It is possible that we are currently running under setuid permissions. */
+static int maybe_setuid = 1;
+
+/* Helper to implement --debug-level and --debug. */
+static const char *debug_level;
+static unsigned int debug_value;
+
+
+/* Local prototypes. */
+static void g13_syshelp_deinit_default_ctrl (ctrl_t ctrl);
+static void release_tab_items (tab_item_t tab);
+static tab_item_t parse_g13tab (const char *username);
+
+
+
+static const char *
+my_strusage( int level )
+{
+ const char *p;
+
+ switch (level)
+ {
+ case 11: p = "@G13@-syshelp (@GNUPG@)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
+ break;
+ case 1:
+ case 40: p = _("Usage: @G13@-syshelp [options] [files] (-h for help)");
+ break;
+ case 41:
+ p = _("Syntax: @G13@-syshelp [options] [files]\n"
+ "Helper to perform root-only tasks for g13\n");
+ break;
+
+ case 31: p = "\nHome: "; break;
+ case 32: p = opt.homedir; break;
+
+ default: p = NULL; break;
+ }
+ return p;
+}
+
+
+/* Setup the debugging. With a DEBUG_LEVEL of NULL only the active
+ debug flags are propagated to the subsystems. With DEBUG_LEVEL
+ set, a specific set of debug flags is set; and individual debugging
+ flags will be added on top. */
+static void
+set_debug (void)
+{
+ int numok = (debug_level && digitp (debug_level));
+ int numlvl = numok? atoi (debug_level) : 0;
+
+ if (!debug_level)
+ ;
+ else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
+ opt.debug = 0;
+ else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
+ opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE;
+ else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
+ opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE;
+ else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
+ opt.debug = (DBG_IPC_VALUE|DBG_MOUNT_VALUE|DBG_CRYPTO_VALUE);
+ else if (!strcmp (debug_level, "guru") || numok)
+ {
+ opt.debug = ~0;
+ /* if (numok) */
+ /* opt.debug &= ~(DBG_HASHING_VALUE); */
+ }
+ else
+ {
+ log_error (_("invalid debug-level '%s' given\n"), debug_level);
+ g13_exit(2);
+ }
+
+ opt.debug |= debug_value;
+
+ if (opt.debug && !opt.verbose)
+ opt.verbose = 1;
+ if (opt.debug)
+ opt.quiet = 0;
+
+ if (opt.debug & DBG_CRYPTO_VALUE )
+ gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
+ gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
+
+ if (opt.debug)
+ parse_debug_flag (NULL, &opt.debug, debug_flags);
+}
+
+
+int
+main ( int argc, char **argv)
+{
+ ARGPARSE_ARGS pargs;
+ int orig_argc;
+ char **orig_argv;
+ gpg_error_t err = 0;
+ /* const char *fname; */
+ int may_coredump;
+ FILE *configfp = NULL;
+ char *configname = NULL;
+ unsigned configlineno;
+ int parse_debug = 0;
+ int no_more_options = 0;
+ int default_config =1;
+ char *logfile = NULL;
+ /* int debug_wait = 0; */
+ int use_random_seed = 1;
+ /* int nodetach = 0; */
+ /* int nokeysetup = 0; */
+ struct server_control_s ctrl;
+
+ /*mtrace();*/
+
+ early_system_init ();
+ gnupg_reopen_std (G13_NAME "-syshelp");
+ set_strusage (my_strusage);
+ gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+
+ log_set_prefix (G13_NAME "-syshelp", 1);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init ();
+ init_common_subsystems (&argc, &argv);
+
+ /* Check that the Libgcrypt is suitable. */
+ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+ log_fatal (_("%s is too old (need %s, have %s)\n"), "libgcrypt",
+ NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+
+ /* Take extra care of the random pool. */
+ gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
+
+ may_coredump = disable_core_dumps ();
+
+ g13_init_signals ();
+
+ dotlock_create (NULL, 0); /* Register locking cleanup. */
+
+ opt.session_env = session_env_new ();
+ if (!opt.session_env)
+ log_fatal ("error allocating session environment block: %s\n",
+ strerror (errno));
+
+ opt.homedir = default_homedir ();
+
+ /* First check whether we have a debug option on the commandline. */
+ orig_argc = argc;
+ orig_argv = argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
+ while (arg_parse( &pargs, opts))
+ {
+ if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
+ parse_debug++;
+ }
+
+ /* Initialize the secure memory. */
+ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+ maybe_setuid = 0;
+
+ /*
+ Now we are now working under our real uid
+ */
+
+ /* Setup malloc hooks. */
+ {
+ struct assuan_malloc_hooks malloc_hooks;
+
+ malloc_hooks.malloc = gcry_malloc;
+ malloc_hooks.realloc = gcry_realloc;
+ malloc_hooks.free = gcry_free;
+ assuan_set_malloc_hooks (&malloc_hooks);
+ }
+
+ /* Prepare libassuan. */
+ assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
+ /*assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);*/
+ setup_libassuan_logging (&opt.debug);
+
+ /* Setup a default control structure for command line mode. */
+ memset (&ctrl, 0, sizeof ctrl);
+ g13_syshelp_init_default_ctrl (&ctrl);
+ ctrl.no_server = 1;
+ ctrl.status_fd = -1; /* No status output. */
+
+ if (default_config )
+ configname = make_filename (gnupg_sysconfdir (),
+ G13_NAME"-syshelp.conf", NULL);
+
+ argc = orig_argc;
+ argv = orig_argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags = 1; /* Do not remove the args. */
+
+ next_pass:
+ if (configname)
+ {
+ configlineno = 0;
+ configfp = fopen (configname, "r");
+ if (!configfp)
+ {
+ if (default_config)
+ {
+ if (parse_debug)
+ log_info (_("NOTE: no default option file '%s'\n"), configname);
+ }
+ else
+ {
+ log_error (_("option file '%s': %s\n"),
+ configname, strerror(errno));
+ g13_exit(2);
+ }
+ xfree (configname);
+ configname = NULL;
+ }
+ if (parse_debug && configname)
+ log_info (_("reading options from '%s'\n"), configname);
+ default_config = 0;
+ }
+
+ while (!no_more_options
+ && optfile_parse (configfp, configname, &configlineno, &pargs, opts))
+ {
+ switch (pargs.r_opt)
+ {
+ case oQuiet: opt.quiet = 1; break;
+
+ case oDryRun: opt.dry_run = 1; break;
+
+ case oVerbose:
+ opt.verbose++;
+ gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
+ break;
+ case oNoVerbose:
+ opt.verbose = 0;
+ gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
+ break;
+
+ case oLogFile: logfile = pargs.r.ret_str; break;
+ case oNoLogFile: logfile = NULL; break;
+
+ case oNoDetach: /*nodetach = 1; */break;
+
+ case oDebug:
+ if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
+ {
+ pargs.r_opt = ARGPARSE_INVALID_ARG;
+ pargs.err = ARGPARSE_PRINT_ERROR;
+ }
+ break;
+ case oDebugAll: debug_value = ~0; break;
+ case oDebugNone: debug_value = 0; break;
+ case oDebugLevel: debug_level = pargs.r.ret_str; break;
+ case oDebugWait: /*debug_wait = pargs.r.ret_int; */break;
+ case oDebugAllowCoreDump:
+ may_coredump = enable_core_dumps ();
+ break;
+
+ case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break;
+ case oLoggerFD: log_set_fd (pargs.r.ret_int ); break;
+
+ case oHomedir: opt.homedir = pargs.r.ret_str; break;
+
+ case oFakedSystemTime:
+ {
+ time_t faked_time = isotime2epoch (pargs.r.ret_str);
+ if (faked_time == (time_t)(-1))
+ faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
+ gnupg_set_time (faked_time, 0);
+ }
+ break;
+
+ case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break;
+
+ case oNoRandomSeedFile: use_random_seed = 0; break;
+
+ default:
+ pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR;
+ break;
+ }
+ }
+
+ if (configfp)
+ {
+ fclose (configfp);
+ configfp = NULL;
+ /* Keep a copy of the config filename. */
+ opt.config_filename = configname;
+ configname = NULL;
+ goto next_pass;
+ }
+ xfree (configname);
+ configname = NULL;
+
+ if (!opt.config_filename)
+ opt.config_filename = make_filename (opt.homedir, G13_NAME".conf", NULL);
+
+ if (log_get_errorcount(0))
+ g13_exit(2);
+
+ /* Now that we have the options parsed we need to update the default
+ control structure. */
+ g13_syshelp_init_default_ctrl (&ctrl);
+
+ if (may_coredump && !opt.quiet)
+ log_info (_("WARNING: program may create a core file!\n"));
+
+ if (logfile)
+ {
+ log_set_file (logfile);
+ log_set_prefix (NULL, 1|2|4);
+ }
+
+ if (gnupg_faked_time_p ())
+ {
+ gnupg_isotime_t tbuf;
+
+ log_info (_("WARNING: running with faked system time: "));
+ gnupg_get_isotime (tbuf);
+ dump_isotime (tbuf);
+ log_printf ("\n");
+ }
+
+ /* Print any pending secure memory warnings. */
+ gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
+
+ /* Setup the debug flags for all subsystems. */
+ set_debug ();
+
+ /* Install a regular exit handler to make real sure that the secure
+ memory gets wiped out. */
+ g13_install_emergency_cleanup ();
+
+ /* Terminate if we found any error until now. */
+ if (log_get_errorcount(0))
+ g13_exit (2);
+
+ /* Set the standard GnuPG random seed file. */
+ if (use_random_seed)
+ {
+ char *p = make_filename (opt.homedir, "random_seed", NULL);
+ gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
+ xfree(p);
+ }
+
+ /* Get the UID of the caller. */
+#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID)
+ {
+ const char *uidstr;
+ struct passwd *pwd = NULL;
+
+ uidstr = getenv ("USERV_UID");
+
+ /* Print a quick note if we are not started via userv. */
+ if (!uidstr)
+ {
+ if (getuid ())
+ {
+ log_info ("WARNING: Not started via userv\n");
+ ctrl.fail_all_cmds = 1;
+ }
+ ctrl.client.uid = getuid ();
+ }
+ else
+ {
+ unsigned long myuid;
+
+ errno = 0;
+ myuid = strtoul (uidstr, NULL, 10);
+ if (myuid == ULONG_MAX && errno)
+ {
+ log_info ("WARNING: Started via broken userv: %s\n",
+ strerror (errno));
+ ctrl.fail_all_cmds = 1;
+ ctrl.client.uid = getuid ();
+ }
+ else
+ ctrl.client.uid = (uid_t)myuid;
+ }
+
+ pwd = getpwuid (ctrl.client.uid);
+ if (!pwd || !*pwd->pw_name)
+ {
+ log_info ("WARNING: Name for UID not found: %s\n", strerror (errno));
+ ctrl.fail_all_cmds = 1;
+ ctrl.client.uname = xstrdup ("?");
+ }
+ else
+ ctrl.client.uname = xstrdup (pwd->pw_name);
+ }
+#else /*!HAVE_PWD_H || !HAVE_GETPWUID*/
+ log_info ("WARNING: System does not support required syscalls\n");
+ ctrl.fail_all_cmds = 1;
+ ctrl.client.uid = getuid ();
+ ctrl.client.uname = xstrdup ("?");
+#endif /*!HAVE_PWD_H || !HAVE_GETPWUID*/
+
+ /* Read the table entries for this user. */
+ if (!ctrl.fail_all_cmds
+ && !(ctrl.client.tab = parse_g13tab (ctrl.client.uname)))
+ ctrl.fail_all_cmds = 1;
+
+ /* Start the server. */
+ err = syshelp_server (&ctrl);
+ if (err)
+ log_error ("server exited with error: %s <%s>\n",
+ gpg_strerror (err), gpg_strsource (err));
+
+ /* Cleanup. */
+ g13_syshelp_deinit_default_ctrl (&ctrl);
+ g13_exit (0);
+ return 8; /*NOTREACHED*/
+}
+
+
+/* Store defaults into the per-connection CTRL object. */
+void
+g13_syshelp_init_default_ctrl (ctrl_t ctrl)
+{
+ ctrl->conttype = CONTTYPE_DM_CRYPT;
+}
+
+/* Release all resources allocated by default in the CTRl object. */
+static void
+g13_syshelp_deinit_default_ctrl (ctrl_t ctrl)
+{
+ xfree (ctrl->client.uname);
+ release_tab_items (ctrl->client.tab);
+}
+
+
+/* Release the list of g13tab itejms at TAB. */
+static void
+release_tab_items (tab_item_t tab)
+{
+ while (tab)
+ {
+ tab_item_t next = tab->next;
+ xfree (tab->mountpoint);
+ xfree (tab);
+ tab = next;
+ }
+}
+
+
+/* Parse the /etc/gnupg/g13tab for user USERNAME. Return a table for
+ the user on success. Return NULL on error and print
+ diagnostics. */
+static tab_item_t
+parse_g13tab (const char *username)
+{
+ gpg_error_t err;
+ int c, n;
+ char line[512];
+ char *p;
+ char *fname;
+ estream_t fp;
+ int lnr;
+ char **words = NULL;
+ tab_item_t table = NULL;
+ tab_item_t *tabletail, ti;
+
+ fname = make_filename (gnupg_sysconfdir (), G13_NAME"tab", NULL);
+ fp = es_fopen (fname, "r");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
+ goto leave;
+ }
+
+ tabletail = &table;
+ err = 0;
+ lnr = 0;
+ while (es_fgets (line, DIM(line)-1, fp))
+ {
+ lnr++;
+ n = strlen (line);
+ if (!n || line[n-1] != '\n')
+ {
+ /* Eat until end of line. */
+ while ((c=es_getc (fp)) != EOF && c != '\n')
+ ;
+ err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
+ : GPG_ERR_INCOMPLETE_LINE);
+ log_error (_("file '%s', line %d: %s\n"),
+ fname, lnr, gpg_strerror (err));
+ continue;
+ }
+ line[--n] = 0; /* Chop the LF. */
+ if (n && line[n-1] == '\r')
+ line[--n] = 0; /* Chop an optional CR. */
+
+ /* Allow for empty lines and spaces */
+ for (p=line; spacep (p); p++)
+ ;
+ if (!*p || *p == '#')
+ continue;
+
+ /* Parse the line. The format is
+ * <username> <blockdev> [<label>|"-" [<mountpoint>]]
+ */
+ xfree (words);
+ words = strtokenize (p, " \t");
+ if (!words)
+ {
+ err = gpg_error_from_syserror ();
+ break;
+ }
+ if (!words[0] || !words[1])
+ {
+ log_error (_("file '%s', line %d: %s\n"),
+ fname, lnr, gpg_strerror (GPG_ERR_SYNTAX));
+ continue;
+ }
+ if (!(*words[1] == '/'
+ || !strncmp (words[1], "PARTUUID=", 9)
+ || !strncmp (words[1], "partuuid=", 9)))
+ {
+ log_error (_("file '%s', line %d: %s\n"),
+ fname, lnr, "Invalid block device syntax");
+ continue;
+ }
+ if (words[2])
+ {
+ if (strlen (words[2]) > 16 || strchr (words[2], '/'))
+ {
+ log_error (_("file '%s', line %d: %s\n"),
+ fname, lnr, "Label too long or invalid syntax");
+ continue;
+ }
+
+ if (words[3] && *words[3] != '/')
+ {
+ log_error (_("file '%s', line %d: %s\n"),
+ fname, lnr, "Invalid mountpoint syntax");
+ continue;
+ }
+ }
+ if (strcmp (words[0], username))
+ continue; /* Skip entries for other usernames! */
+
+ ti = xtrymalloc (sizeof *ti + strlen (words[1]));
+ if (!ti)
+ {
+ err = gpg_error_from_syserror ();
+ break;
+ }
+ ti->next = NULL;
+ ti->label = NULL;
+ ti->mountpoint = NULL;
+ strcpy (ti->blockdev, *words[1]=='/'? words[1] : words[1]+9);
+ if (words[2])
+ {
+ if (strcmp (words[2], "-")
+ && !(ti->label = xtrystrdup (words[2])))
+ {
+ err = gpg_error_from_syserror ();
+ xfree (ti);
+ break;
+ }
+ if (words[3] && !(ti->mountpoint = xtrystrdup (words[3])))
+ {
+ err = gpg_error_from_syserror ();
+ xfree (ti->label);
+ xfree (ti);
+ break;
+ }
+ }
+ *tabletail = ti;
+ tabletail = &ti->next;
+ }
+
+ if (!err && !es_feof (fp))
+ err = gpg_error_from_syserror ();
+ if (err)
+ log_error (_("error reading '%s', line %d: %s\n"),
+ fname, lnr, gpg_strerror (err));
+
+ leave:
+ xfree (words);
+ es_fclose (fp);
+ xfree (fname);
+ if (err)
+ {
+ release_tab_items (table);
+ return NULL;
+ }
+ return table;
+}
diff --git a/g13/g13-syshelp.h b/g13/g13-syshelp.h
new file mode 100644
index 000000000..205488257
--- /dev/null
+++ b/g13/g13-syshelp.h
@@ -0,0 +1,93 @@
+/* g130syshelp.h - Global definitions for G13-SYSHELP.
+ * Copyright (C) 2015 Werner Koch
+ *
+ * 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/>.
+ */
+
+#ifndef G13_SYSHELP_H
+#define G13_SYSHELP_H
+
+#include "g13-common.h"
+
+
+struct tab_item_s;
+typedef struct tab_item_s *tab_item_t;
+
+struct tab_item_s
+{
+ tab_item_t next;
+ char *label; /* Optional malloced label for that entry. */
+ char *mountpoint; /* NULL or a malloced mountpoint. */
+ char blockdev[1]; /* String with the name of the block device. If
+ it starts with a slash is is a regular device
+ name, otherwise it is a PARTUUID. */
+};
+
+
+
+/* Forward declaration for an object defined in g13-sh-cmd.c. */
+struct server_local_s;
+
+/* Session control object. This object is passed down to most
+ functions. The default values for it are set by
+ g13_syshelp_init_default_ctrl(). */
+struct server_control_s
+{
+ int no_server; /* We are not running under server control */
+ int status_fd; /* Only for non-server mode */
+ struct server_local_s *server_local;
+
+ struct {
+ uid_t uid; /* UID of the client calling use. */
+ char *uname;
+ tab_item_t tab;/* Linked list with the g13tab items for this user. */
+ } client;
+
+ /* Flag indicating that we should fail all commands. */
+ int fail_all_cmds;
+
+ /* Type of the current container. See the CONTTYPE_ constants. */
+ int conttype;
+
+ /* A pointer into client.tab with the selected tab line or NULL. */
+ tab_item_t devti;
+};
+
+
+/*-- g13-syshelp.c --*/
+void g13_syshelp_init_default_ctrl (struct server_control_s *ctrl);
+
+/*-- sh-cmd.c --*/
+gpg_error_t syshelp_server (ctrl_t ctrl);
+gpg_error_t sh_encrypt_keyblob (ctrl_t ctrl,
+ const void *keyblob, size_t keybloblen,
+ char **r_enckeyblob, size_t *r_enckeybloblen);
+
+/*-- sh-exectool.c --*/
+gpg_error_t sh_exec_tool (const char *pgmname, const char *argv[],
+ const char *input_string,
+ char **result, size_t *resultlen);
+
+/*-- sh-blockdev.c --*/
+gpg_error_t sh_blockdev_getsz (const char *name, unsigned long long *r_nblocks);
+gpg_error_t sh_is_empty_partition (const char *name);
+
+/*-- sh-dmcrypt.c --*/
+gpg_error_t sh_dmcrypt_create_container (ctrl_t ctrl, const char *devname,
+ estream_t devfp);
+
+
+#endif /*G13_SYSHELP_H*/
diff --git a/g13/g13.c b/g13/g13.c
index 7a8d775ae..d9d40982a 100644
--- a/g13/g13.c
+++ b/g13/g13.c
@@ -43,6 +43,8 @@
#include "create.h"
#include "mount.h"
#include "mountinfo.h"
+#include "backend.h"
+#include "call-syshelp.h"
enum cmd_and_opt_values {
@@ -73,6 +75,7 @@ enum cmd_and_opt_values {
oAgentProgram,
oGpgProgram,
+ oType,
oDisplay,
oTTYname,
@@ -114,6 +117,7 @@ static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
+ ARGPARSE_s_s (oType, "type", N_("|NAME|use container format NAME")),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
@@ -570,6 +574,19 @@ main ( int argc, char **argv)
add_to_strlist (&recipients, pargs.r.ret_str);
break;
+ case oType:
+ if (!strcmp (pargs.r.ret_str, "help"))
+ {
+ be_parse_conttype_name (NULL);
+ g13_exit (0);
+ }
+ cmdline_conttype = be_parse_conttype_name (pargs.r.ret_str);
+ if (!cmdline_conttype)
+ {
+ pargs.r_opt = ARGPARSE_INVALID_ARG;
+ pargs.err = ARGPARSE_PRINT_ERROR;
+ }
+ break;
default:
pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR;
@@ -756,6 +773,8 @@ main ( int argc, char **argv)
break;
}
+ g13_deinit_default_ctrl (&ctrl);
+
if (!err)
join_idle_task ();
@@ -767,12 +786,20 @@ main ( int argc, char **argv)
/* Store defaults into the per-connection CTRL object. */
void
-g13_init_default_ctrl (struct server_control_s *ctrl)
+g13_init_default_ctrl (ctrl_t ctrl)
{
ctrl->conttype = cmdline_conttype? cmdline_conttype : CONTTYPE_ENCFS;
}
+/* Release remaining resources allocated in the CTRL object. */
+void
+g13_deinit_default_ctrl (ctrl_t ctrl)
+{
+ call_syshelp_release (ctrl);
+}
+
+
/* This function is called for each signal we catch. It is run in the
main context or the one of a NPth thread and thus it is not
restricted in what it may do. */
diff --git a/g13/g13.h b/g13/g13.h
index 303c84bd0..f6f88b96b 100644
--- a/g13/g13.h
+++ b/g13/g13.h
@@ -25,6 +25,9 @@
/* Forward declaration for an object defined in server.c. */
struct server_local_s;
+/* Forward declaration for an object defined in call-syshelp.c. */
+struct call_syshelp_s;
+
/* Session control object. This object is passed down to most
functions. The default values for it are set by
@@ -34,6 +37,7 @@ struct server_control_s
int no_server; /* We are not running under server control */
int status_fd; /* Only for non-server mode */
struct server_local_s *server_local;
+ struct call_syshelp_s *syshelp_local;
int agent_seen; /* Flag indicating that the gpg-agent has been
accessed. */
@@ -47,6 +51,7 @@ struct server_control_s
/*-- g13.c --*/
-void g13_init_default_ctrl (struct server_control_s *ctrl);
+void g13_init_default_ctrl (ctrl_t ctrl);
+void g13_deinit_default_ctrl (ctrl_t ctrl);
#endif /*G13_H*/
diff --git a/g13/keyblob.h b/g13/keyblob.h
index 5c3e74e12..47310e185 100644
--- a/g13/keyblob.h
+++ b/g13/keyblob.h
@@ -20,7 +20,8 @@
#ifndef G13_KEYBLOB_H
#define G13_KEYBLOB_H
-/* The header block is the actual core of G13. Here is the format:
+/* The setup area (header block) is the actual core of G13. Here is
+ the format:
u8 Packet type. Value is 61 (0x3d).
u8 Constant value 255 (0xff).
@@ -29,7 +30,7 @@
u8 Version. Value is 1.
u8 reserved
u8 reserved
- u8 OS Flag: reserved, should be 0.
+ u8 OS Flag: 0 = unspecified, 1 = Linux
u32 Length of the entire header. This includes all bytes
starting at the packet type and ending with the last
padding byte of the header.
@@ -37,9 +38,9 @@
u8 Number of copies of this header at the end of the
container (usually 0).
b6 reserved
- n bytes: OpenPGP encrypted and optionally signed message.
- n bytes: CMS encrypted and optionally signed packet. Such a CMS
- packet will be enclosed in a a private flagged OpenPGP
+ n bytes: OpenPGP encrypted and optionally signed keyblob.
+ n bytes: CMS encrypted and optionally signed keyblob. Such a CMS
+ packet will be enclosed in a private flagged OpenPGP
packet. Either the OpenPGP encrypted packet as described
above, the CMS encrypted or both packets must exist. The
encapsulation packet has this structure:
@@ -54,6 +55,8 @@
u32 Length of the following structure
b10 Value: "GnuPG/PAD\x00".
b(n) Padding stuff.
+ (repeat the above value
+ or if the remaining N < 10, all 0x00).
Given this structure the minimum padding is 16 bytes.
n bytes: File system container.
@@ -77,6 +80,14 @@
keyblob. If a value is given it is expected to be the GUID of the
partition. */
+#define KEYBLOB_TAG_CREATED 3
+/* This is an ISO 8601 time string with the date the container was
+ created. */
+
+#define KEYBLOB_TAG_ALGOSTR 10
+/* For a dm-crypt container this is the used algorithm string. For
+ example: "aes-cbc-essiv:sha256". */
+
#define KEYBLOB_TAG_KEYNO 16
/* This tag indicates a new key. The value is a 4 byte big endian
integer giving the key number. If the container type does only
@@ -105,8 +116,14 @@
The value is the key used for MACing. */
+#define KEYBLOB_TAG_HDRCOPY 21
+/* The value of this tag is a copy of the setup area prefix header
+ block (packet 61 with marker "GnuPG/G13\x00". We use it to allow
+ signing of that cleartext data. */
+
+
#define KEYBLOB_TAG_FILLER 0xffff
-/* This tag may be used for alignment and padding porposes. The value
+/* This tag may be used for alignment and padding purposes. The value
has no meaning. */
diff --git a/g13/sh-blockdev.c b/g13/sh-blockdev.c
new file mode 100644
index 000000000..2d431ea24
--- /dev/null
+++ b/g13/sh-blockdev.c
@@ -0,0 +1,151 @@
+/* sh-blockdev.c - Block device functions for g13-syshelp
+ * Copyright (C) 2015 Werner Koch
+ *
+ * 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 <stdarg.h>
+#include <errno.h>
+#include <assert.h>
+#include <limits.h>
+
+#include "g13-syshelp.h"
+#include <assuan.h>
+#include "i18n.h"
+#include "keyblob.h"
+
+#ifndef HAVE_STRTOULL
+# error building this tool requires strtoull(3)
+#endif
+#ifndef ULLONG_MAX
+# error ULLONG_MAX missing
+#endif
+
+
+/* Return the size measured in the number of 512 byte sectors for the
+ block device NAME. */
+gpg_error_t
+sh_blockdev_getsz (const char *name, unsigned long long *r_nblocks)
+{
+ gpg_error_t err;
+ const char *argv[3];
+ char *result;
+
+ *r_nblocks = 0;
+ argv[0] = "--getsz";
+ argv[1] = name;
+ argv[2] = NULL;
+ err = sh_exec_tool ("/sbin/blockdev", argv, NULL, &result, NULL);
+ if (!err)
+ {
+ gpg_err_set_errno (0);
+ *r_nblocks = strtoull (result, NULL, 10);
+ if (*r_nblocks == ULLONG_MAX && errno)
+ {
+ err = gpg_error_from_syserror ();
+ *r_nblocks = 0;
+ }
+ xfree (result);
+ }
+ return err;
+}
+
+
+/* Return 0 if the device NAME looks like an empty partition. */
+gpg_error_t
+sh_is_empty_partition (const char *name)
+{
+ gpg_error_t err;
+ const char *argv[6];
+ char *buffer;
+ estream_t fp;
+ char *p;
+ size_t nread;
+
+ argv[0] = "-o";
+ argv[1] = "value";
+ argv[2] = "-s";
+ argv[3] = "UUID";
+ argv[4] = name;
+ argv[5] = NULL;
+ err = sh_exec_tool ("/sbin/blkid", argv, NULL, &buffer, NULL);
+ if (err)
+ return gpg_error (GPG_ERR_FALSE);
+ if (*buffer)
+ {
+ /* There seems to be an UUID - thus we have a file system. */
+ xfree (buffer);
+ return gpg_error (GPG_ERR_FALSE);
+ }
+ xfree (buffer);
+
+ argv[0] = "-o";
+ argv[1] = "value";
+ argv[2] = "-s";
+ argv[3] = "PARTUUID";
+ argv[4] = name;
+ argv[5] = NULL;
+ err = sh_exec_tool ("/sbin/blkid", argv, NULL, &buffer, NULL);
+ if (err)
+ return gpg_error (GPG_ERR_FALSE);
+ if (!*buffer)
+ {
+ /* If there is no PARTUUID we assume that name has already a
+ mapped partition. */
+ xfree (buffer);
+ return gpg_error (GPG_ERR_FALSE);
+ }
+ xfree (buffer);
+
+ /* As a safeguard we require that the first 32k of a partition are
+ all zero before we assume the partition is empty. */
+ buffer = xtrymalloc (32 * 1024);
+ if (!buffer)
+ return gpg_error_from_syserror ();
+ fp = es_fopen (name, "rb,samethread");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error opening '%s': %s\n", name, gpg_strerror (err));
+ xfree (buffer);
+ return gpg_error (GPG_ERR_FALSE);
+ }
+ if (es_read (fp, buffer, 32 * 1024, &nread))
+ err = gpg_error_from_syserror ();
+ else if (nread != 32 *1024)
+ err = gpg_error (GPG_ERR_TOO_SHORT);
+ else
+ err = 0;
+ es_fclose (fp);
+ if (err)
+ {
+ log_error ("error reading the first 32 KiB from '%s': %s\n",
+ name, gpg_strerror (err));
+ xfree (buffer);
+ return err;
+ }
+ for (p=buffer; nread && !*p; nread--, p++)
+ ;
+ xfree (buffer);
+ if (nread)
+ return gpg_error (GPG_ERR_FALSE); /* No all zeroes. */
+
+ return 0;
+}
diff --git a/g13/sh-cmd.c b/g13/sh-cmd.c
new file mode 100644
index 000000000..4ef37c105
--- /dev/null
+++ b/g13/sh-cmd.c
@@ -0,0 +1,555 @@
+/* sh-cmd.c - The Assuan server for g13-syshelp
+ * Copyright (C) 2015 Werner Koch
+ *
+ * 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 <stdarg.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "g13-syshelp.h"
+#include <assuan.h>
+#include "i18n.h"
+#include "keyblob.h"
+
+
+/* Local data for this server module. A pointer to this is stored in
+ the CTRL object of each connection. */
+struct server_local_s
+{
+ /* The Assuan contect we are working on. */
+ assuan_context_t assuan_ctx;
+
+ /* The malloced name of the device. */
+ char *devicename;
+
+ /* A stream open for read of the device set by the DEVICE command or
+ NULL if no DEVICE command has been used. */
+ estream_t devicefp;
+};
+
+
+
+
+/* Local prototypes. */
+
+
+
+
+/*
+ Helper functions.
+ */
+
+/* Set an error and a description. */
+#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
+#define set_error_fail_cmd() set_error (GPG_ERR_NOT_INITIALIZED, \
+ "not called via userv or unknown user")
+
+
+/* Skip over options. Blanks after the options are also removed. */
+static char *
+skip_options (const char *line)
+{
+ while (spacep (line))
+ line++;
+ while ( *line == '-' && line[1] == '-' )
+ {
+ while (*line && !spacep (line))
+ line++;
+ while (spacep (line))
+ line++;
+ }
+ return (char*)line;
+}
+
+
+/* Check whether the option NAME appears in LINE. */
+/* static int */
+/* has_option (const char *line, const char *name) */
+/* { */
+/* const char *s; */
+/* int n = strlen (name); */
+
+/* s = strstr (line, name); */
+/* if (s && s >= skip_options (line)) */
+/* return 0; */
+/* return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); */
+/* } */
+
+
+/* Helper to print a message while leaving a command. */
+static gpg_error_t
+leave_cmd (assuan_context_t ctx, gpg_error_t err)
+{
+ if (err)
+ {
+ const char *name = assuan_get_command_name (ctx);
+ if (!name)
+ name = "?";
+ if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
+ log_error ("command '%s' failed: %s\n", name,
+ gpg_strerror (err));
+ else
+ log_error ("command '%s' failed: %s <%s>\n", name,
+ gpg_strerror (err), gpg_strsource (err));
+ }
+ return err;
+}
+
+
+
+
+/* The handler for Assuan OPTION commands. */
+static gpg_error_t
+option_handler (assuan_context_t ctx, const char *key, const char *value)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+
+ (void)ctrl;
+ (void)key;
+ (void)value;
+
+ if (ctrl->fail_all_cmds)
+ err = set_error_fail_cmd ();
+ else
+ err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
+
+ return err;
+}
+
+
+/* The handler for an Assuan RESET command. */
+static gpg_error_t
+reset_notify (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void)line;
+
+ xfree (ctrl->server_local->devicename);
+ ctrl->server_local->devicename = NULL;
+ es_fclose (ctrl->server_local->devicefp);
+ ctrl->server_local->devicefp = NULL;
+ ctrl->devti = NULL;
+
+ assuan_close_input_fd (ctx);
+ assuan_close_output_fd (ctx);
+ return 0;
+}
+
+
+static const char hlp_device[] =
+ "DEVICE <name>\n"
+ "\n"
+ "Set the device used by further commands.\n"
+ "A device name or a PARTUUID string may be used.";
+static gpg_error_t
+cmd_device (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+ tab_item_t ti;
+ estream_t fp = NULL;
+
+ /* strcpy (line, "/dev/sdb1"); /\* FIXME *\/ */
+ line = skip_options (line);
+
+ /* Always close an open device stream of this session. */
+ xfree (ctrl->server_local->devicename);
+ ctrl->server_local->devicename = NULL;
+ es_fclose (ctrl->server_local->devicefp);
+ ctrl->server_local->devicefp = NULL;
+
+ /* Are we allowed to use the given device? */
+ for (ti=ctrl->client.tab; ti; ti = ti->next)
+ if (!strcmp (line, ti->blockdev))
+ break;
+ if (!ti)
+ {
+ set_error (GPG_ERR_EACCES, "device not configured for user");
+ goto leave;
+ }
+
+ ctrl->server_local->devicename = xtrystrdup (line);
+ if (!ctrl->server_local->devicename)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+
+ /* Check whether we have permissions to open the device and keep an
+ FD open. */
+ fp = es_fopen (ctrl->server_local->devicename, "rb");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error opening '%s': %s\n",
+ ctrl->server_local->devicename, gpg_strerror (err));
+ goto leave;
+ }
+
+ es_fclose (ctrl->server_local->devicefp);
+ ctrl->server_local->devicefp = fp;
+ fp = NULL;
+ ctrl->devti = ti;
+
+ leave:
+ es_fclose (fp);
+ if (err)
+ {
+ xfree (ctrl->server_local->devicename);
+ ctrl->server_local->devicename = NULL;
+ ctrl->devti = NULL;
+ }
+ return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_create[] =
+ "CREATE <type>\n"
+ "\n"
+ "Create a new encrypted partition on the current device.\n"
+ "<type> must be \"dm-crypt\" for now.";
+static gpg_error_t
+cmd_create (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+
+ line = skip_options (line);
+
+ if (strcmp (line, "dm-crypt"))
+ {
+ err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
+ goto leave;
+ }
+
+ if (!ctrl->server_local->devicename
+ || !ctrl->server_local->devicefp
+ || !ctrl->devti)
+ {
+ err = set_error (GPG_ERR_ENOENT, "No device has been set");
+ goto leave;
+ }
+
+ err = sh_is_empty_partition (ctrl->server_local->devicename);
+ if (err)
+ {
+ assuan_set_error (ctx, err, "Partition is not empty");
+ goto leave;
+ }
+
+ err = sh_dmcrypt_create_container (ctrl,
+ ctrl->server_local->devicename,
+ ctrl->server_local->devicefp);
+
+
+
+
+ leave:
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_getinfo[] =
+ "GETINFO <what>\n"
+ "\n"
+ "Multipurpose function to return a variety of information.\n"
+ "Supported values for WHAT are:\n"
+ "\n"
+ " version - Return the version of the program.\n"
+ " pid - Return the process id of the server.\n"
+ " showtab - Show the table for the user.";
+static gpg_error_t
+cmd_getinfo (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+ char *buf;
+
+ if (!strcmp (line, "version"))
+ {
+ const char *s = PACKAGE_VERSION;
+ err = assuan_send_data (ctx, s, strlen (s));
+ }
+ else if (!strcmp (line, "pid"))
+ {
+ char numbuf[50];
+
+ snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
+ err = assuan_send_data (ctx, numbuf, strlen (numbuf));
+ }
+ else if (!strncmp (line, "getsz", 5))
+ {
+ unsigned long long nblocks;
+ err = sh_blockdev_getsz (line+6, &nblocks);
+ if (!err)
+ log_debug ("getsz=%llu\n", nblocks);
+ }
+ else if (!strcmp (line, "showtab"))
+ {
+ tab_item_t ti;
+
+ for (ti=ctrl->client.tab; !err && ti; ti = ti->next)
+ {
+ buf = es_bsprintf ("%s %s%s %s %s%s\n",
+ ctrl->client.uname,
+ *ti->blockdev=='/'? "":"partuuid=",
+ ti->blockdev,
+ ti->label? ti->label : "-",
+ ti->mountpoint? " ":"",
+ ti->mountpoint? ti->mountpoint:"");
+ if (!buf)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ err = assuan_send_data (ctx, buf, strlen (buf));
+ if (!err)
+ err = assuan_send_data (ctx, NULL, 0); /* Flush */
+ }
+ xfree (buf);
+ }
+ }
+ else
+ err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
+
+ return leave_cmd (ctx, err);
+}
+
+
+/* This command handler is used for all commands if this process has
+ not been started as expected. */
+static gpg_error_t
+fail_command (assuan_context_t ctx, char *line)
+{
+ gpg_error_t err;
+ const char *name = assuan_get_command_name (ctx);
+
+ (void)line;
+
+ if (!name)
+ name = "?";
+
+ err = set_error_fail_cmd ();
+ log_error ("command '%s' failed: %s\n", name, gpg_strerror (err));
+ return err;
+}
+
+
+/* Tell the Assuan library about our commands. */
+static int
+register_commands (assuan_context_t ctx, int fail_all)
+{
+ static struct {
+ const char *name;
+ assuan_handler_t handler;
+ const char * const help;
+ } table[] = {
+ { "DEVICE", cmd_device, hlp_device },
+ { "CREATE", cmd_create, hlp_create },
+ { "INPUT", NULL },
+ { "OUTPUT", NULL },
+ { "GETINFO", cmd_getinfo, hlp_getinfo },
+ { NULL }
+ };
+ gpg_error_t err;
+ int i;
+
+ for (i=0; table[i].name; i++)
+ {
+ err = assuan_register_command (ctx, table[i].name,
+ fail_all ? fail_command : table[i].handler,
+ table[i].help);
+ if (err)
+ return err;
+ }
+ return 0;
+}
+
+
+/* Startup the server. */
+gpg_error_t
+syshelp_server (ctrl_t ctrl)
+{
+ gpg_error_t err;
+ assuan_fd_t filedes[2];
+ assuan_context_t ctx = NULL;
+
+ /* We use a pipe based server so that we can work from scripts.
+ assuan_init_pipe_server will automagically detect when we are
+ called with a socketpair and ignore FILEDES in this case. */
+ filedes[0] = assuan_fdopen (0);
+ filedes[1] = assuan_fdopen (1);
+ err = assuan_new (&ctx);
+ if (err)
+ {
+ log_error ("failed to allocate an Assuan context: %s\n",
+ gpg_strerror (err));
+ goto leave;
+ }
+
+ err = assuan_init_pipe_server (ctx, filedes);
+ if (err)
+ {
+ log_error ("failed to initialize the server: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ err = register_commands (ctx, 0 /*FIXME:ctrl->fail_all_cmds*/);
+ if (err)
+ {
+ log_error ("failed to the register commands with Assuan: %s\n",
+ gpg_strerror (err));
+ goto leave;
+ }
+
+ assuan_set_pointer (ctx, ctrl);
+
+ {
+ char *tmp = xtryasprintf ("G13-syshelp %s ready to serve requests "
+ "from %lu(%s)",
+ PACKAGE_VERSION,
+ (unsigned long)ctrl->client.uid,
+ ctrl->client.uname);
+ if (tmp)
+ {
+ assuan_set_hello_line (ctx, tmp);
+ xfree (tmp);
+ }
+ }
+
+ assuan_register_reset_notify (ctx, reset_notify);
+ assuan_register_option_handler (ctx, option_handler);
+
+ ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
+ if (!ctrl->server_local)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ ctrl->server_local->assuan_ctx = ctx;
+
+ while ( !(err = assuan_accept (ctx)) )
+ {
+ err = assuan_process (ctx);
+ if (err)
+ log_info ("Assuan processing failed: %s\n", gpg_strerror (err));
+ }
+ if (err == -1)
+ err = 0;
+ else
+ log_info ("Assuan accept problem: %s\n", gpg_strerror (err));
+
+ leave:
+ reset_notify (ctx, NULL); /* Release all items hold by SERVER_LOCAL. */
+ if (ctrl->server_local)
+ {
+ xfree (ctrl->server_local);
+ ctrl->server_local = NULL;
+ }
+
+ assuan_release (ctx);
+ return err;
+}
+
+
+gpg_error_t
+sh_encrypt_keyblob (ctrl_t ctrl, const void *keyblob, size_t keybloblen,
+ char **r_enckeyblob, size_t *r_enckeybloblen)
+{
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+ gpg_error_t err;
+ unsigned char *enckeyblob;
+ size_t enckeybloblen;
+
+ *r_enckeyblob = NULL;
+
+ /* Send the plaintext. */
+ err = g13_status (ctrl, STATUS_PLAINTEXT_FOLLOWS, NULL);
+ if (err)
+ return err;
+ assuan_begin_confidential (ctx);
+ err = assuan_send_data (ctx, keyblob, keybloblen);
+ if (!err)
+ err = assuan_send_data (ctx, NULL, 0);
+ assuan_end_confidential (ctx);
+ if (!err)
+ err = assuan_write_line (ctx, "END");
+ if (err)
+ {
+ log_error (_("error sending data: %s\n"), gpg_strerror (err));
+ return err;
+ }
+
+ /* Inquire the ciphertext. */
+ err = assuan_inquire (ctx, "ENCKEYBLOB",
+ &enckeyblob, &enckeybloblen, 16 * 1024);
+ if (err)
+ {
+ log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
+ return err;
+ }
+
+ *r_enckeyblob = enckeyblob;
+ *r_enckeybloblen = enckeybloblen;
+ return 0;
+}
+
+
+/* Send a status line with status ID NO. The arguments are a list of
+ strings terminated by a NULL argument. */
+gpg_error_t
+g13_status (ctrl_t ctrl, int no, ...)
+{
+ gpg_error_t err = 0;
+ va_list arg_ptr;
+ const char *text;
+
+ va_start (arg_ptr, no);
+
+ if (1)
+ {
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+ char buf[950], *p;
+ size_t n;
+
+ p = buf;
+ n = 0;
+ while ( (text = va_arg (arg_ptr, const char *)) )
+ {
+ if (n)
+ {
+ *p++ = ' ';
+ n++;
+ }
+ for ( ; *text && n < DIM (buf)-2; n++)
+ *p++ = *text++;
+ }
+ *p = 0;
+ err = assuan_write_status (ctx, get_status_string (no), buf);
+ }
+
+ va_end (arg_ptr);
+ return err;
+}
diff --git a/g13/sh-dmcrypt.c b/g13/sh-dmcrypt.c
new file mode 100644
index 000000000..49950fd2b
--- /dev/null
+++ b/g13/sh-dmcrypt.c
@@ -0,0 +1,406 @@
+/* sh-dmcrypt.c - The DM-Crypt part for g13-syshelp
+ * Copyright (C) 2015 Werner Koch
+ *
+ * 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 <stdarg.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#ifdef HAVE_STAT
+# include <sys/stat.h>
+#endif
+#include <unistd.h>
+
+#include "g13-syshelp.h"
+#include <assuan.h>
+#include "i18n.h"
+#include "utils.h"
+#include "keyblob.h"
+
+/* The standard disk block size (logical). */
+#define SECTOR_SIZE 512
+
+/* The physical block size used by modern devices. */
+#define PHY_SECTOR_SIZE (SECTOR_SIZE*8) /* 4 KiB */
+
+/* The length of the crypto setup area in sectors. 16 KiB is a nice
+ multiple of a modern block size and should be sufficient for all
+ kind of extra public key encryption packet. */
+#define SETUP_AREA_SECTORS 32 /* 16 KiB */
+
+/* The number of header block copies stored at the begin and end of
+ the device. */
+#define HEADER_SETUP_AREA_COPIES 2
+#define FOOTER_SETUP_AREA_COPIES 2
+
+/* The length in blocks of the space we put at the start and at the
+ end of the device. This space is used to store N copies of the
+ setup area for the actual encrypted container inbetween. */
+#define HEADER_SECTORS (SETUP_AREA_SECTORS * HEADER_SETUP_AREA_COPIES)
+#define FOOTER_SECTORS (SETUP_AREA_SECTORS * FOOTER_SETUP_AREA_COPIES)
+
+/* Minimim size of the encrypted space in blocks. This is more or
+ less an arbitrary value. */
+#define MIN_ENCRYPTED_SPACE 32
+
+/* Some consistency checks for the above constants. */
+#if (PHY_SECTOR_SIZE % SECTOR_SIZE)
+# error the physical secotor size should be a multiple of 512
+#endif
+#if ((SETUP_AREA_SECTORS*SECTOR_SIZE) % PHY_SECTOR_SIZE)
+# error The setup area size should be a multiple of the phy. sector size.
+#endif
+
+
+/* Check whether the block device DEVNAME is used by device mapper.
+ Returns: 0 if the device is good and not yet used by DM. */
+static gpg_error_t
+check_blockdev (const char *devname)
+{
+ gpg_error_t err;
+ struct stat sb;
+ unsigned int devmajor, devminor;
+ char *result = NULL;
+ char **lines = NULL;
+ char **fields = NULL;
+ int lno, count;
+
+ if (stat (devname, &sb))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error stating '%s': %s\n", devname, gpg_strerror (err));
+ return err;
+ }
+ if (!S_ISBLK (sb.st_mode))
+ {
+ err = gpg_error (GPG_ERR_ENOTBLK);
+ log_error ("can't use '%s': %s\n", devname, gpg_strerror (err));
+ return err;
+ }
+ devmajor = major (sb.st_rdev);
+ devminor = minor (sb.st_rdev);
+
+ {
+ const char *argv[2];
+
+ argv[0] = "deps";
+ argv[1] = NULL;
+ err = sh_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL);
+ }
+ if (err)
+ {
+ log_error ("error running '%s' to search for '%s': %s\n",
+ "dmsetup deps", devname, gpg_strerror (err));
+ goto leave;
+ }
+ lines = strsplit (result, '\n', 0, NULL);
+ if (!lines)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ if (lines[0] && !strcmp (lines[0], "No devices found"))
+ ;
+ else
+ {
+ for (lno=0; lines[lno]; lno++)
+ {
+ unsigned int xmajor, xminor;
+
+ if (!*lines[lno])
+ continue;
+ xfree (fields);
+ fields = strsplit (lines[lno], ':', 0, &count);
+ if (!fields)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ if (count < 3
+ || sscanf (fields[2], " (%u,%u)", &xmajor, &xminor) != 2)
+ {
+ log_error ("error running '%s' to search for '%s': %s\n",
+ "dmsetup deps", devname, "unexpected output");
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ goto leave;
+ }
+
+ if (xmajor == devmajor && xminor == devminor)
+ {
+ log_error ("device '%s' (%u:%u) already used by device mapper\n",
+ devname, devmajor, devminor);
+ err = gpg_error (GPG_ERR_EBUSY);
+ goto leave;
+ }
+ }
+ }
+
+
+ leave:
+ xfree (fields);
+ xfree (lines);
+ xfree (result);
+ return err;
+}
+
+
+/* Return a malloced buffer with the prefix of the setup area. This
+ is the data written right before the encrypted keyblob. Return NULL
+ on error and sets ERRNO. */
+static void *
+mk_setup_area_prefix (size_t *r_length)
+{
+ unsigned char *packet;
+ size_t setuparealen;
+
+ packet = xtrymalloc (32);
+ if (!packet)
+ return NULL;
+ *r_length = 32;
+
+ setuparealen = SETUP_AREA_SECTORS * SECTOR_SIZE;
+
+ packet[0] = (0xc0|61); /* CTB for the private packet type 0x61. */
+ packet[1] = 0xff; /* 5 byte length packet, value 20. */
+ packet[2] = 0;
+ packet[3] = 0;
+ packet[4] = 0;
+ packet[5] = 26;
+ memcpy (packet+6, "GnuPG/G13", 10); /* Packet subtype. */
+ packet[16] = 1; /* G13 packet format version. */
+ packet[17] = 0; /* Reserved. */
+ packet[18] = 0; /* Reserved. */
+ packet[19] = 1; /* OS Flag = Linux */
+ packet[20] = (setuparealen >> 24); /* Total length of header. */
+ packet[21] = (setuparealen >> 16);
+ packet[22] = (setuparealen >> 8);
+ packet[23] = (setuparealen);
+ packet[24] = HEADER_SETUP_AREA_COPIES;
+ packet[25] = FOOTER_SETUP_AREA_COPIES;
+ packet[26] = 0; /* Reserved. */
+ packet[27] = 0; /* Reserved. */
+ packet[28] = 0; /* Reserved. */
+ packet[29] = 0; /* Reserved. */
+ packet[30] = 0; /* Reserved. */
+ packet[31] = 0; /* Reserved. */
+
+ return packet;
+}
+
+
+gpg_error_t
+sh_dmcrypt_create_container (ctrl_t ctrl, const char *devname, estream_t devfp)
+{
+ gpg_error_t err;
+ char *header_space;
+ char *targetname = NULL;
+ size_t nread;
+ char *p;
+ char hexkey[16*2+1];
+ char *table = NULL;
+ unsigned long long nblocks;
+ char *result = NULL;
+ unsigned char twobyte[2];
+ membuf_t keyblob;
+ void *keyblob_buf = NULL;
+ size_t keyblob_len;
+ size_t n;
+ const char *s;
+
+ if (!ctrl->devti)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ header_space = xtrymalloc (HEADER_SECTORS * SECTOR_SIZE);
+ if (!header_space)
+ return gpg_error_from_syserror ();
+
+ /* Start building the keyblob. */
+ init_membuf (&keyblob, 512);
+ append_tuple (&keyblob, KEYBLOB_TAG_BLOBVERSION, "\x01", 1);
+ n = CONTTYPE_DM_CRYPT;
+ twobyte[0] = (n >> 8);
+ twobyte[1] = n;
+ append_tuple (&keyblob, KEYBLOB_TAG_CONTTYPE, twobyte, 2);
+ {
+ gnupg_isotime_t tbuf;
+
+ gnupg_get_isotime (tbuf);
+ append_tuple (&keyblob, KEYBLOB_TAG_CREATED, tbuf, strlen (tbuf));
+ }
+
+ /* Rewind out stream. */
+ if (es_fseeko (devfp, 0, SEEK_SET))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error seeking to begin of '%s': %s\n",
+ devname, gpg_strerror (err));
+ goto leave;
+ }
+ es_clearerr (devfp);
+
+ /* Extra check that the device is empty. */
+ if (es_read (devfp, header_space, HEADER_SECTORS * SECTOR_SIZE, &nread))
+ err = gpg_error_from_syserror ();
+ else if (nread != HEADER_SECTORS * SECTOR_SIZE)
+ err = gpg_error (GPG_ERR_TOO_SHORT);
+ else
+ err = 0;
+ if (err)
+ {
+ log_error ("error reading header space of '%s': %s\n",
+ devname, gpg_strerror (err));
+ goto leave;
+ }
+ for (p=header_space; nread && !*p; nread--, p++)
+ ;
+ if (nread)
+ {
+ log_error ("header space of '%s' already used - use %s to override\n",
+ devname, "--force");
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ goto leave;
+ }
+
+ /* Check that the device is not used by device mapper. */
+ err = check_blockdev (devname);
+ if (err)
+ goto leave;
+
+ /* Compute the number of blocks. */
+ err = sh_blockdev_getsz (devname, &nblocks);
+ if (err)
+ {
+ log_error ("error getting size of '%s': %s\n",
+ devname, gpg_strerror (err));
+ goto leave;
+ }
+ if (nblocks <= HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS)
+ {
+ log_error ("device '%s' is too small (min=%d blocks)\n",
+ devname,
+ HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS);
+ err = gpg_error (GPG_ERR_TOO_SHORT);
+ goto leave;
+ }
+ nblocks -= HEADER_SECTORS + FOOTER_SECTORS;
+
+ /* Device mapper needs a name for the device: Take it from the label
+ or use "0". */
+ targetname = strconcat ("g13-", ctrl->client.uname, "-",
+ ctrl->devti->label? ctrl->devti->label : "0",
+ NULL);
+ if (!targetname)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ /* Create the key. */
+ {
+ char key[16];
+ gcry_randomize (key, sizeof key, GCRY_STRONG_RANDOM);
+ append_tuple (&keyblob, KEYBLOB_TAG_ENCKEY, key, sizeof key);
+ bin2hex (key, 16, hexkey);
+ wipememory (key, 16);
+ /* Add a 2*(4+16) byte filler to conceal the fact that we use
+ AES-128. If we ever want to switch to 256 bit we can resize
+ that filler to keep the keyblob at the same size. */
+ append_tuple (&keyblob, KEYBLOB_TAG_FILLER, key, sizeof key);
+ append_tuple (&keyblob, KEYBLOB_TAG_FILLER, key, sizeof key);
+ }
+
+ /* Build dmcrypt table. */
+ s = "aes-cbc-essiv:sha256";
+ append_tuple (&keyblob, KEYBLOB_TAG_ALGOSTR, s, strlen (s));
+ table = es_bsprintf ("0 %llu crypt %s %s 0 %s %d",
+ nblocks, s, hexkey, devname, HEADER_SECTORS);
+ if (!table)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ wipememory (hexkey, sizeof hexkey);
+
+ /* Add a copy of the setup area prefix to the keyblob. */
+ p = mk_setup_area_prefix (&n);
+ if (!p)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ append_tuple (&keyblob, KEYBLOB_TAG_HDRCOPY, p, n);
+
+ /* Turn the keyblob into a buffer and callback to encrypt it. */
+ keyblob_buf = get_membuf (&keyblob, &keyblob_len);
+ if (!keyblob_buf)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ err = sh_encrypt_keyblob (ctrl, keyblob_buf, keyblob_len, &p, &n);
+ if (err)
+ {
+ log_error ("encrypting the keyblob failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+ wipememory (keyblob_buf, keyblob_len);
+ xfree (keyblob_buf);
+ keyblob_buf = NULL;
+
+ /* Create the container. */
+ /* { */
+ /* const char *argv[3]; */
+
+ /* argv[0] = "create"; */
+ /* argv[1] = targetname; */
+ /* argv[2] = NULL; */
+ /* err = sh_exec_tool ("/sbin/dmsetup", argv, table, &result, NULL); */
+ /* } */
+ /* if (err) */
+ /* { */
+ /* log_error ("error running dmsetup for '%s': %s\n", */
+ /* devname, gpg_strerror (err)); */
+ /* goto leave; */
+ /* } */
+ /* log_debug ("dmsetup result: %s\n", result); */
+
+ /* Write the setup area. */
+
+
+ leave:
+ wipememory (hexkey, sizeof hexkey);
+ if (table)
+ {
+ wipememory (table, strlen (table));
+ xfree (table);
+ }
+ if (keyblob_buf)
+ {
+ wipememory (keyblob_buf, keyblob_len);
+ xfree (keyblob_buf);
+ }
+ xfree (get_membuf (&keyblob, NULL));
+ xfree (targetname);
+ xfree (result);
+ xfree (header_space);
+ return err;
+}
diff --git a/g13/sh-exectool.c b/g13/sh-exectool.c
new file mode 100644
index 000000000..ab1809552
--- /dev/null
+++ b/g13/sh-exectool.c
@@ -0,0 +1,303 @@
+/* sh-exectool.c - Utility functions to execute a helper tool
+ * Copyright (C) 2015 Werner Koch
+ *
+ * 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 <stdarg.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "g13-syshelp.h"
+#include <assuan.h>
+#include "i18n.h"
+#include "membuf.h"
+#include "exechelp.h"
+#include "sysutils.h"
+
+typedef struct
+{
+ const char *pgmname;
+ int cont;
+ int used;
+ char buffer[256];
+} read_and_log_buffer_t;
+
+
+static void
+read_and_log_stderr (read_and_log_buffer_t *state, es_poll_t *fderr)
+{
+ gpg_error_t err;
+ int c;
+
+ if (!fderr)
+ {
+ /* Flush internal buffer. */
+ if (state->used)
+ {
+ const char *pname;
+ int len;
+
+ state->buffer[state->used] = 0;
+ state->used = 0;
+
+ pname = strrchr (state->pgmname, '/');
+ if (pname && pname != state->pgmname && pname[1])
+ pname++;
+ else
+ pname = state->pgmname;
+ /* If our pgmname plus colon is identical to the start of
+ the output, print only the output. */
+ len = strlen (pname);
+ if (!state->cont
+ && !strncmp (state->buffer, pname, len)
+ && strlen (state->buffer) > strlen (pname)
+ && state->buffer[len] == ':' )
+ log_info ("%s\n", state->buffer);
+ else
+ log_info ("%s%c %s\n",
+ pname, state->cont? '+':':', state->buffer);
+ }
+ state->cont = 0;
+ return;
+ }
+ for (;;)
+ {
+ c = es_fgetc (fderr->stream);
+ if (c == EOF)
+ {
+ if (es_feof (fderr->stream))
+ {
+ fderr->ignore = 1; /* Not anymore needed. */
+ }
+ else if (es_ferror (fderr->stream))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading stderr of '%s': %s\n",
+ state->pgmname, gpg_strerror (err));
+ fderr->ignore = 1; /* Disable. */
+ }
+
+ break;
+ }
+ else if (c == '\n')
+ {
+ read_and_log_stderr (state, NULL);
+ }
+ else
+ {
+ if (state->used >= sizeof state->buffer - 1)
+ {
+ read_and_log_stderr (state, NULL);
+ state->cont = 1;
+ }
+ state->buffer[state->used++] = c;
+ }
+ }
+}
+
+
+static gpg_error_t
+read_stdout (membuf_t *mb, es_poll_t *fdout, const char *pgmname)
+{
+ gpg_error_t err = 0;
+ int c;
+
+ for (;;)
+ {
+ c = es_fgetc (fdout->stream);
+ if (c == EOF)
+ {
+ if (es_feof (fdout->stream))
+ {
+ fdout->ignore = 1; /* Ready. */
+ }
+ else if (es_ferror (fdout->stream))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading stdout of '%s': %s\n",
+ pgmname, gpg_strerror (err));
+ fdout->ignore = 1; /* Disable. */
+ }
+
+ break;
+ }
+ else
+ {
+ char buf[1];
+ *buf = c;
+ put_membuf (mb, buf, 1);
+ }
+ }
+
+ return err;
+}
+
+
+/* Run the program PGMNAME with the command line arguments given in
+ the NULL terminates array ARGV. If INPUT_STRING is not NULL it
+ will be fed to stdin of the process. stderr is logged using
+ log_info and the process' stdout is returned in a newly malloced
+ buffer RESULT with the length stored at RESULTLEN if not given as
+ NULL. A hidden Nul is appended to the output. On error NULL is
+ stored at RESULT, a diagnostic is printed, and an error code
+ returned. */
+gpg_error_t
+sh_exec_tool (const char *pgmname, const char *argv[],
+ const char *input_string,
+ char **result, size_t *resultlen)
+{
+ gpg_error_t err;
+ pid_t pid;
+ estream_t infp = NULL;
+ estream_t outfp, errfp;
+ es_poll_t fds[3];
+ int count;
+ read_and_log_buffer_t fderrstate;
+ membuf_t fdout_mb;
+ size_t len, nwritten;
+
+ *result = NULL;
+ if (resultlen)
+ *resultlen = 0;
+ memset (fds, 0, sizeof fds);
+ memset (&fderrstate, 0, sizeof fderrstate);
+ init_membuf (&fdout_mb, 4096);
+
+ err = gnupg_spawn_process (pgmname, argv, GPG_ERR_SOURCE_DEFAULT,
+ NULL, GNUPG_SPAWN_NONBLOCK,
+ input_string? &infp : NULL,
+ &outfp, &errfp, &pid);
+ if (err)
+ {
+ log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err));
+ return err;
+ }
+
+ fderrstate.pgmname = pgmname;
+
+ fds[0].stream = infp;
+ fds[0].want_write = 1;
+ if (!input_string)
+ fds[0].ignore = 1;
+ fds[1].stream = outfp;
+ fds[1].want_read = 1;
+ fds[2].stream = errfp;
+ fds[2].want_read = 1;
+ /* Now read as long as we have something to poll. We continue
+ reading even after EOF or error on stdout so that we get the
+ other error messages or remaining outout. */
+ while (!fds[1].ignore && !fds[2].ignore)
+ {
+ count = es_poll (fds, DIM(fds), -1);
+ if (count == -1)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error polling '%s': %s\n", pgmname, gpg_strerror (err));
+ goto leave;
+ }
+ if (!count)
+ {
+ log_debug ("unexpected timeout while polling '%s'\n", pgmname);
+ break;
+ }
+
+ if (fds[0].got_write)
+ {
+ len = strlen (input_string);
+ log_debug ("writing '%s'\n", input_string);
+ if (es_write (fds[0].stream, input_string, len, &nwritten))
+ {
+ if (errno != EAGAIN)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error writing '%s': %s\n",
+ pgmname, gpg_strerror (err));
+ goto leave;
+ }
+ else
+ log_debug (" .. EAGAIN\n");
+ }
+ else
+ {
+ assert (nwritten <= len);
+ input_string += nwritten;
+ }
+
+ if (es_fflush (fds[0].stream) && errno != EAGAIN)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error writing '%s' (flush): %s\n",
+ pgmname, gpg_strerror (err));
+ if (gpg_err_code (err) == GPG_ERR_EPIPE && !*input_string)
+ {
+ /* fixme: How can we tell whether estream has
+ pending bytes after a HUP - which is an
+ error? */
+ }
+ else
+ goto leave;
+ }
+ if (!*input_string)
+ {
+ fds[0].ignore = 1; /* ready. */
+ es_fclose (infp); infp = NULL;
+ }
+ }
+
+ if (fds[1].got_read)
+ read_stdout (&fdout_mb, fds + 1, pgmname); /* FIXME: Add error
+ handling. */
+ if (fds[2].got_read)
+ read_and_log_stderr (&fderrstate, fds + 2);
+
+ }
+
+ read_and_log_stderr (&fderrstate, NULL); /* Flush. */
+ es_fclose (infp); infp = NULL;
+ es_fclose (outfp); outfp = NULL;
+ es_fclose (errfp); errfp = NULL;
+
+ err = gnupg_wait_process (pgmname, pid, 1, NULL);
+ pid = (pid_t)(-1);
+
+ leave:
+ if (err)
+ {
+ gnupg_kill_process (pid);
+ xfree (get_membuf (&fdout_mb, NULL));
+ }
+ else
+ {
+ put_membuf (&fdout_mb, "", 1); /* Make sure it is a string. */
+ *result = get_membuf (&fdout_mb, resultlen);
+ if (!*result)
+ err = gpg_error_from_syserror ();
+ }
+
+ es_fclose (infp);
+ es_fclose (outfp);
+ es_fclose (errfp);
+ if (pid != (pid_t)(-1))
+ gnupg_wait_process (pgmname, pid, 1, NULL);
+ gnupg_release_process (pid);
+
+ return err;
+}