diff options
Diffstat (limited to '')
-rw-r--r-- | g13/g13-syshelp.c | 740 |
1 files changed, 740 insertions, 0 deletions
diff --git a/g13/g13-syshelp.c b/g13/g13-syshelp.c new file mode 100644 index 0000000..65d5c25 --- /dev/null +++ b/g13/g13-syshelp.c @@ -0,0 +1,740 @@ +/* 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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#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> + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "g13-syshelp.h" + +#include <gcrypt.h> +#include <assuan.h> + +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/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 9: p = "GPL-3.0-or-later"; break; + case 11: p = "@G13@-syshelp (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; 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 = gnupg_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; + char *last_configname = NULL; + const char *configname = NULL; + int debug_argparser = 0; + int no_more_options = 0; + 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", GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + + /* 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)); + + /* 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 (gnupg_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oDebug: + case oDebugAll: + debug_argparser++; + break; + } + } + /* Reset the flags. */ + pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + + /* 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, NULL); + + /* 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. */ + + /* The configuraton directories for use by gpgrt_argparser. */ + gnupg_set_confdir (GNUPG_CONFDIR_SYS, gnupg_sysconfdir ()); + gnupg_set_confdir (GNUPG_CONFDIR_USER, gnupg_homedir ()); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags |= (ARGPARSE_FLAG_RESET + | ARGPARSE_FLAG_KEEP + | ARGPARSE_FLAG_SYS + | ARGPARSE_FLAG_USER); + + while (!no_more_options + && gnupg_argparser (&pargs, opts, G13_NAME"-syshelp" EXTSEP_S "conf")) + { + switch (pargs.r_opt) + { + case ARGPARSE_CONFFILE: + { + if (debug_argparser) + log_info (_("reading options from '%s'\n"), + pargs.r_type? pargs.r.ret_str: "[cmdline]"); + if (pargs.r_type) + { + xfree (last_configname); + last_configname = xstrdup (pargs.r.ret_str); + configname = last_configname; + } + else + configname = NULL; + } + break; + + 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: gnupg_set_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 = configname? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR; + break; + } + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (!last_configname) + opt.config_filename = make_filename (gnupg_homedir (), + G13_NAME"-syshelp" EXTSEP_S "conf", + NULL); + else + { + opt.config_filename = last_configname; + last_configname = 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, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); + } + + 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 (gnupg_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); + + /* Check that the user name does not contain a directory + separator. */ + if (strchr (ctrl.client.uname, '/')) + { + log_info ("WARNING: Invalid user name passed\n"); + ctrl.fail_all_cmds = 1; + } + } +#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; + } +} + + +void +g13_syshelp_i_know_what_i_am_doing (void) +{ + const char * const yesfile = "Yes-g13-I-know-what-I-am-doing"; + char *fname; + + fname = make_filename (gnupg_sysconfdir (), yesfile, NULL); + if (gnupg_access (fname, F_OK)) + { + log_info ("*******************************************************\n"); + log_info ("* The G13 support for DM-Crypt is new and not matured.\n"); + log_info ("* Bugs or improper use may delete all your disks!\n"); + log_info ("* To confirm that you are ware of this risk, create\n"); + log_info ("* the file '%s'.\n", fname); + log_info ("*******************************************************\n"); + exit (1); + } + xfree (fname); +} + + +/* 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; +} |