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>
This commit is contained in:
Werner Koch 2015-10-21 08:38:10 +02:00
parent b2b079cb2f
commit 286a89da7c
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
14 changed files with 2489 additions and 9 deletions

View 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)

View 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);
}

View 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);

124
g13/call-syshelp.c Normal file
View File

@ -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;
}

26
g13/call-syshelp.h Normal file
View File

@ -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*/

720
g13/g13-syshelp.c Normal file
View File

@ -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;
}

93
g13/g13-syshelp.h Normal file
View File

@ -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*/

View File

@ -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. */

View File

@ -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*/

View 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 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. */

151
g13/sh-blockdev.c Normal file
View File

@ -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;
}

555
g13/sh-cmd.c Normal file
View File

@ -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;
}

406
g13/sh-dmcrypt.c Normal file
View File

@ -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;
}

303
g13/sh-exectool.c Normal file
View File

@ -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;
}