g13: First chunk of code to support dm-crypt.
authorWerner Koch <wk@gnupg.org>
Wed, 21 Oct 2015 06:38:10 +0000 (08:38 +0200)
committerWerner Koch <wk@gnupg.org>
Sat, 13 Feb 2016 16:06:38 +0000 (17:06 +0100)
* 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>
14 files changed:
g13/Makefile.am
g13/backend.c
g13/backend.h
g13/call-syshelp.c [new file with mode: 0644]
g13/call-syshelp.h [new file with mode: 0644]
g13/g13-syshelp.c [new file with mode: 0644]
g13/g13-syshelp.h [new file with mode: 0644]
g13/g13.c
g13/g13.h
g13/keyblob.h
g13/sh-blockdev.c [new file with mode: 0644]
g13/sh-cmd.c [new file with mode: 0644]
g13/sh-dmcrypt.c [new file with mode: 0644]
g13/sh-exectool.c [new file with mode: 0644]

index e17d099..a3cd133 100644 (file)
@@ -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)
index 7b08cd5..52e1e0a 100644 (file)
@@ -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);
     }
index 20d2966..e570fc5 100644 (file)
@@ -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 (file)
index 0000000..2086dd1
--- /dev/null
@@ -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 (file)
index 0000000..c78d53b
--- /dev/null
@@ -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 (file)
index 0000000..c09a5e9
--- /dev/null
@@ -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);
+
+
+\f
+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 (file)
index 0000000..2054882
--- /dev/null
@@ -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*/
index 7a8d775..d9d4098 100644 (file)
--- 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.  */
index 303c84b..f6f88b9 100644 (file)
--- 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*/
index 5c3e74e..47310e1 100644 (file)
@@ -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 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.
    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
    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 (file)
index 0000000..2d431ea
--- /dev/null
@@ -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 (file)
index 0000000..4ef37c1
--- /dev/null
@@ -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;
+};
+
+
+
+\f
+/* Local prototypes.  */
+
+
+
+\f
+/*
+   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;
+}
+
+
+
+\f
+/* 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 (file)
index 0000000..49950fd
--- /dev/null
@@ -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 (file)
index 0000000..ab18095
--- /dev/null
@@ -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;
+}