diff options
author | Werner Koch <wk@gnupg.org> | 2015-10-21 08:38:10 +0200 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2016-02-13 17:06:38 +0100 |
commit | 81494fd30d3815502247a721f50d9eadf86a73fa (patch) | |
tree | 5bb17e6db5ffcbbfd0d28dc3379038683b78468f /g13 | |
parent | tests: Remove some harmless warnings in regression tests. (diff) | |
download | gnupg2-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.am | 17 | ||||
-rw-r--r-- | g13/backend.c | 35 | ||||
-rw-r--r-- | g13/backend.h | 3 | ||||
-rw-r--r-- | g13/call-syshelp.c | 124 | ||||
-rw-r--r-- | g13/call-syshelp.h | 26 | ||||
-rw-r--r-- | g13/g13-syshelp.c | 720 | ||||
-rw-r--r-- | g13/g13-syshelp.h | 93 | ||||
-rw-r--r-- | g13/g13.c | 29 | ||||
-rw-r--r-- | g13/g13.h | 7 | ||||
-rw-r--r-- | g13/keyblob.h | 29 | ||||
-rw-r--r-- | g13/sh-blockdev.c | 151 | ||||
-rw-r--r-- | g13/sh-cmd.c | 555 | ||||
-rw-r--r-- | g13/sh-dmcrypt.c | 406 | ||||
-rw-r--r-- | g13/sh-exectool.c | 303 |
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*/ @@ -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. */ @@ -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; +} |