diff --git a/tools/gpgconf.c b/tools/gpgconf.c index e68eecdd7..539461d80 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -1,943 +1,959 @@ /* gpgconf.c - Configuration utility for GnuPG * Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc. * Copyright (C) 2016 g10 Code GmbH. * * 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "gpgconf.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/init.h" #include "../common/status.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oVerbose = 'v', oRuntime = 'r', oComponent = 'c', oNull = '0', oNoVerbose = 500, oHomedir, oBuilddir, oStatusFD, oShowSocket, oChUid, aListComponents, aCheckPrograms, aListOptions, aChangeOptions, aCheckOptions, aApplyDefaults, aListConfig, aCheckConfig, aQuerySWDB, aListDirs, aLaunch, aKill, aCreateSocketDir, aRemoveSocketDir, aApplyProfile, - aReload + aReload, + aShowCodepages }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { { 300, NULL, 0, N_("@Commands:\n ") }, { aListComponents, "list-components", 256, N_("list all components") }, { aCheckPrograms, "check-programs", 256, N_("check all programs") }, { aListOptions, "list-options", 256, N_("|COMPONENT|list options") }, { aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") }, { aCheckOptions, "check-options", 256, N_("|COMPONENT|check options") }, { aApplyDefaults, "apply-defaults", 256, N_("apply global default values") }, { aApplyProfile, "apply-profile", 256, N_("|FILE|update configuration files using FILE") }, { aListDirs, "list-dirs", 256, N_("get the configuration directories for @GPGCONF@") }, { aListConfig, "list-config", 256, N_("list global configuration file") }, { aCheckConfig, "check-config", 256, N_("check global configuration file") }, { aQuerySWDB, "query-swdb", 256, N_("query the software version database") }, { aReload, "reload", 256, N_("reload all or a given component")}, { aLaunch, "launch", 256, N_("launch a given component")}, { aKill, "kill", 256, N_("kill a given component")}, { aCreateSocketDir, "create-socketdir", 256, "@"}, { aRemoveSocketDir, "remove-socketdir", 256, "@"}, + ARGPARSE_c (aShowCodepages, "show-codepages", "@"), { 301, NULL, 0, N_("@\nOptions:\n ") }, { oOutput, "output", 2, N_("use as output file") }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("quiet") }, { oDryRun, "dry-run", 0, N_("do not make any changes") }, { oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") }, - ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_i (oStatusFD, "status-fd", + N_("|FD|write status info to this FD")), /* hidden options */ { oHomedir, "homedir", 2, "@" }, { oBuilddir, "build-prefix", 2, "@" }, { oNull, "null", 0, "@" }, { oNoVerbose, "no-verbose", 0, "@"}, ARGPARSE_s_n (oShowSocket, "show-socket", "@"), ARGPARSE_s_s (oChUid, "chuid", "@"), ARGPARSE_end(), }; /* The stream to output the status information. Status Output is disabled if * this is NULL. */ static estream_t statusfp; /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPGCONF@ (@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 <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: @GPGCONF@ [options] (-h for help)"); break; case 41: p = _("Syntax: @GPGCONF@ [options]\n" "Manage configuration options for tools of the @GNUPG@ system\n"); break; default: p = NULL; break; } return p; } /* Return the fp for the output. This is usually stdout unless --output has been used. In the latter case this function opens that file. */ static estream_t get_outfp (estream_t *fp) { if (!*fp) { if (opt.outfile) { *fp = es_fopen (opt.outfile, "w"); if (!*fp) gc_error (1, errno, "can not open '%s'", opt.outfile); } else *fp = es_stdout; } return *fp; } /* Set the status FD. */ static void set_status_fd (int fd) { static int last_fd = -1; if (fd != -1 && last_fd == fd) return; if (statusfp && statusfp != es_stdout && statusfp != es_stderr) es_fclose (statusfp); statusfp = NULL; if (fd == -1) return; if (fd == 1) statusfp = es_stdout; else if (fd == 2) statusfp = es_stderr; else statusfp = es_fdopen (fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", fd, gpg_strerror (gpg_error_from_syserror ())); } last_fd = fd; } /* Write a status line with code NO followed by the output of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ void gpgconf_write_status (int no, const char *format, ...) { va_list arg_ptr; if (!statusfp) return; /* Not enabled. */ es_fputs ("[GNUPG:] ", statusfp); es_fputs (get_status_string (no), statusfp); if (format) { es_putc (' ', statusfp); va_start (arg_ptr, format); es_vfprintf (statusfp, format, arg_ptr); va_end (arg_ptr); } es_putc ('\n', statusfp); } static void list_dirs (estream_t fp, char **names) { static struct { const char *name; const char *(*fnc)(void); const char *extra; } list[] = { { "sysconfdir", gnupg_sysconfdir, NULL }, { "bindir", gnupg_bindir, NULL }, { "libexecdir", gnupg_libexecdir, NULL }, { "libdir", gnupg_libdir, NULL }, { "datadir", gnupg_datadir, NULL }, { "localedir", gnupg_localedir, NULL }, { "socketdir", gnupg_socketdir, NULL }, { "dirmngr-socket", dirmngr_socket_name, NULL,}, { "agent-ssh-socket", gnupg_socketdir, GPG_AGENT_SSH_SOCK_NAME }, { "agent-extra-socket", gnupg_socketdir, GPG_AGENT_EXTRA_SOCK_NAME }, { "agent-browser-socket",gnupg_socketdir, GPG_AGENT_BROWSER_SOCK_NAME }, { "agent-socket", gnupg_socketdir, GPG_AGENT_SOCK_NAME }, { "homedir", gnupg_homedir, NULL } }; int idx, j; char *tmp; const char *s; for (idx = 0; idx < DIM (list); idx++) { s = list[idx].fnc (); if (list[idx].extra) { tmp = make_filename (s, list[idx].extra, NULL); s = tmp; } else tmp = NULL; if (!names) es_fprintf (fp, "%s:%s\n", list[idx].name, gc_percent_escape (s)); else { for (j=0; names[j]; j++) if (!strcmp (names[j], list[idx].name)) { es_fputs (s, fp); es_putc (opt.null? '\0':'\n', fp); } } xfree (tmp); } #ifdef HAVE_W32_SYSTEM tmp = read_w32_registry_string (NULL, GNUPG_REGISTRY_DIR, "HomeDir"); if (tmp) { es_fflush (fp); log_info ("Warning: homedir taken from registry key (%s %s)\n", GNUPG_REGISTRY_DIR, "HomeDir"); xfree (tmp); } #endif /*HAVE_W32_SYSTEM*/ } /* Check whether NAME is valid argument for query_swdb(). Valid names * start with a letter and contain only alphanumeric characters or an * underscore. */ static int valid_swdb_name_p (const char *name) { if (!name || !*name || !alphap (name)) return 0; for (name++; *name; name++) if (!alnump (name) && *name != '_') return 0; return 1; } /* Query the SWDB file. If necessary and possible this functions asks * the dirmngr to load an updated version of that file. The caller * needs to provide the NAME to query (e.g. "gnupg", "libgcrypt") and * optional the currently installed version in CURRENT_VERSION. The * output written to OUT is a colon delimited line with these fields: * * name :: The name of the package * curvers:: The installed version if given. * status :: This value tells the status of the software package * '-' :: No information available * (error or CURRENT_VERSION not given) * '?' :: Unknown NAME * 'u' :: Update available * 'c' :: The version is Current * 'n' :: The current version is already Newer than the * available one. * urgency :: If the value is greater than zero an urgent update is required. * error :: 0 on success or an gpg_err_code_t * Common codes seen: * GPG_ERR_TOO_OLD :: The SWDB file is to old to be used. * GPG_ERR_ENOENT :: The SWDB file is not available. * GPG_ERR_BAD_SIGNATURE :: Corrupted SWDB file. * filedate:: Date of the swdb file (yyyymmddThhmmss) * verified:: Date we checked the validity of the file (yyyyymmddThhmmss) * version :: The version string from the swdb. * reldate :: Release date of that version (yyyymmddThhmmss) * size :: Size of the package in bytes. * hash :: SHA-2 hash of the package. * */ static void query_swdb (estream_t out, const char *name, const char *current_version) { gpg_error_t err; const char *search_name; char *fname = NULL; estream_t fp = NULL; char *line = NULL; char *self_version = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; char *fields[2]; char *p; gnupg_isotime_t filedate = {0}; gnupg_isotime_t verified = {0}; char *value_ver = NULL; gnupg_isotime_t value_date = {0}; char *value_size = NULL; char *value_sha2 = NULL; unsigned long value_size_ul = 0; int status, i; if (!valid_swdb_name_p (name)) { log_error ("error in package name '%s': %s\n", name, gpg_strerror (GPG_ERR_INV_NAME)); goto leave; } if (!strcmp (name, "gnupg")) search_name = GNUPG_SWDB_TAG; else if (!strcmp (name, "gnupg1")) search_name = "gnupg1"; else search_name = name; if (!current_version && !strcmp (name, "gnupg")) { /* Use our own version but string a possible beta string. */ self_version = xstrdup (PACKAGE_VERSION); p = strchr (self_version, '-'); if (p) *p = 0; current_version = self_version; } if (current_version && (strchr (current_version, ':') || compare_version_strings (current_version, NULL))) { log_error ("error in version string '%s': %s\n", current_version, gpg_strerror (GPG_ERR_INV_ARG)); goto leave; } fname = make_filename (gnupg_homedir (), "swdb.lst", NULL); fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); es_fprintf (out, "%s:%s:-::%u:::::::\n", name, current_version? current_version : "", gpg_err_code (err)); if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } /* Note that the parser uses the first occurrence of a matching * values and ignores possible duplicated values. */ maxlen = 2048; /* Set limit. */ while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; if (split_fields (line, fields, DIM (fields)) < DIM(fields)) continue; /* Skip empty lines and names w/o a value. */ if (*fields[0] == '#') continue; /* Skip comments. */ /* Record the meta data. */ if (!*filedate && !strcmp (fields[0], ".filedate")) { string2isotime (filedate, fields[1]); continue; } if (!*verified && !strcmp (fields[0], ".verified")) { string2isotime (verified, fields[1]); continue; } /* Tokenize the name. */ p = strrchr (fields[0], '_'); if (!p) continue; /* Name w/o an underscore. */ *p++ = 0; /* Wait for the requested name. */ if (!strcmp (fields[0], search_name)) { if (!strcmp (p, "ver") && !value_ver) value_ver = xstrdup (fields[1]); else if (!strcmp (p, "date") && !*value_date) string2isotime (value_date, fields[1]); else if (!strcmp (p, "size") && !value_size) value_size = xstrdup (fields[1]); else if (!strcmp (p, "sha2") && !value_sha2) value_sha2 = xstrdup (fields[1]); } } if (len < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } if (!*filedate || !*verified) { err = gpg_error (GPG_ERR_INV_TIME); es_fprintf (out, "%s:%s:-::%u:::::::\n", name, current_version? current_version : "", gpg_err_code (err)); goto leave; } if (!value_ver) { es_fprintf (out, "%s:%s:?:::::::::\n", name, current_version? current_version : ""); goto leave; } if (value_size) { gpg_err_set_errno (0); value_size_ul = strtoul (value_size, &p, 10); if (errno) value_size_ul = 0; else if (*p == 'k') value_size_ul *= 1024; } err = 0; status = '-'; if (compare_version_strings (value_ver, NULL)) err = gpg_error (GPG_ERR_INV_VALUE); else if (!current_version) ; else if (!(i = compare_version_strings (value_ver, current_version))) status = 'c'; else if (i > 0) status = 'u'; else status = 'n'; es_fprintf (out, "%s:%s:%c::%d:%s:%s:%s:%s:%lu:%s:\n", name, current_version? current_version : "", status, err, filedate, verified, value_ver, value_date, value_size_ul, value_sha2? value_sha2 : ""); leave: xfree (value_ver); xfree (value_size); xfree (value_sha2); xfree (line); es_fclose (fp); xfree (fname); xfree (self_version); } /* gpgconf main. */ int main (int argc, char **argv) { gpg_error_t err; gpgrt_argparse_t pargs; const char *fname; int no_more_options = 0; enum cmd_and_opt_values cmd = 0; estream_t outfp = NULL; int show_socket = 0; const char *changeuser = NULL; early_system_init (); gnupg_reopen_std (GPGCONF_NAME); gpgrt_set_strusage (my_strusage); log_set_prefix (GPGCONF_NAME, GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); gc_components_init (); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; while (!no_more_options && gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oOutput: opt.outfile = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oDryRun: opt.dry_run = 1; break; case oRuntime: opt.runtime = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oBuilddir: gnupg_set_builddir (pargs.r.ret_str); break; case oNull: opt.null = 1; break; case oStatusFD: set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); break; case oShowSocket: show_socket = 1; break; case oChUid: changeuser = pargs.r.ret_str; break; case aListDirs: case aListComponents: case aCheckPrograms: case aListOptions: case aChangeOptions: case aCheckOptions: case aApplyDefaults: case aApplyProfile: case aListConfig: case aCheckConfig: case aQuerySWDB: case aReload: case aLaunch: case aKill: case aCreateSocketDir: case aRemoveSocketDir: + case aShowCodepages: cmd = pargs.r_opt; break; default: pargs.err = 2; break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) gpgconf_failure (GPG_ERR_USER_2); /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (_("Note: '%s' is not considered an option\n"), argv[i]); } fname = argc ? *argv : NULL; /* If requested switch to the requested user or die. */ if (changeuser && (err = gnupg_chuid (changeuser, 0))) gpgconf_failure (err); /* Set the configuraton directories for use by gpgrt_argparser. We * don't have a configuration file for this program but we have code * which reads the component's config files. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); switch (cmd) { case aListComponents: default: /* List all components. */ gc_component_list_components (get_outfp (&outfp)); break; case aCheckPrograms: /* Check all programs. */ gc_check_programs (get_outfp (&outfp)); break; case aListOptions: case aChangeOptions: case aCheckOptions: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } else { int idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } if (cmd == aCheckOptions) gc_component_check_options (idx, get_outfp (&outfp), NULL); else { gc_component_retrieve_options (idx); if (gc_process_gpgconf_conf (NULL, 1, 0, NULL)) gpgconf_failure (0); if (cmd == aListOptions) gc_component_list_options (idx, get_outfp (&outfp)); else if (cmd == aChangeOptions) gc_component_change_options (idx, es_stdin, get_outfp (&outfp), 0); } } break; case aLaunch: case aKill: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } else if (!strcmp (fname, "all")) { if (cmd == aLaunch) { if (gc_component_launch (-1)) gpgconf_failure (0); } else { gc_component_kill (-1); } } else { /* Launch/Kill a given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } else if (cmd == aLaunch) { err = gc_component_launch (idx); if (show_socket) { char *names[2]; if (idx == GC_COMPONENT_GPG_AGENT) names[0] = "agent-socket"; else if (idx == GC_COMPONENT_DIRMNGR) names[0] = "dirmngr-socket"; else names[0] = NULL; names[1] = NULL; get_outfp (&outfp); list_dirs (outfp, names); } if (err) gpgconf_failure (0); } else { /* We don't error out if the kill failed because this command should do nothing if the component is not running. */ gc_component_kill (idx); } } break; case aReload: if (!fname || !strcmp (fname, "all")) { /* Reload all. */ gc_component_reload (-1); } else { /* Reload given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } else { gc_component_reload (idx); } } break; case aListConfig: if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp))) gpgconf_failure (0); break; case aCheckConfig: if (gc_process_gpgconf_conf (fname, 0, 0, NULL)) gpgconf_failure (0); break; case aApplyDefaults: if (fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("No argument allowed"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } gc_component_retrieve_options (-1); if (gc_process_gpgconf_conf (NULL, 1, 1, NULL)) gpgconf_failure (0); break; case aApplyProfile: gc_component_retrieve_options (-1); if (gc_apply_profile (fname)) gpgconf_failure (0); break; case aListDirs: /* Show the system configuration directories for gpgconf. */ get_outfp (&outfp); list_dirs (outfp, argc? argv : NULL); break; case aQuerySWDB: /* Query the software version database. */ if (!fname || argc > 2) { es_fprintf (es_stderr, "usage: %s --query-swdb NAME [VERSION]\n", GPGCONF_NAME); gpgconf_failure (GPG_ERR_USER_2); } get_outfp (&outfp); query_swdb (outfp, fname, argc > 1? argv[1] : NULL); break; case aCreateSocketDir: { char *socketdir; unsigned int flags; /* Make sure that the top /run/user/UID/gnupg dir has been * created. */ gnupg_socketdir (); /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 64) && !opt.dry_run) { /* No sub dir - create it. */ if (gnupg_mkdir (socketdir, "-rwx")) gc_error (1, errno, "error creating '%s'", socketdir); /* Try again. */ xfree (socketdir); socketdir = _gnupg_socketdir_internal (1, &flags); } /* Give some info. */ if ( (flags & ~32) || opt.verbose || opt.dry_run) { log_info ("socketdir is '%s'\n", socketdir); if ((flags & 1)) log_info ("\tgeneral error\n"); if ((flags & 2)) log_info ("\tno /run/user dir\n"); if ((flags & 4)) log_info ("\tbad permissions\n"); if ((flags & 8)) log_info ("\tbad permissions (subdir)\n"); if ((flags & 16)) log_info ("\tmkdir failed\n"); if ((flags & 32)) log_info ("\tnon-default homedir\n"); if ((flags & 64)) log_info ("\tno such subdir\n"); if ((flags & 128)) log_info ("\tusing homedir as fallback\n"); } if ((flags & ~32) && !opt.dry_run) gc_error (1, 0, "error creating socket directory"); xfree (socketdir); } break; case aRemoveSocketDir: { char *socketdir; unsigned int flags; /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 128)) log_info ("ignoring request to remove non /run/user socket dir\n"); else if (opt.dry_run) ; else if (rmdir (socketdir)) { /* If the director is not empty we first try to delete * socket files. */ err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY || gpg_err_code (err) == GPG_ERR_EEXIST) { static const char * const names[] = { GPG_AGENT_SOCK_NAME, GPG_AGENT_EXTRA_SOCK_NAME, GPG_AGENT_BROWSER_SOCK_NAME, GPG_AGENT_SSH_SOCK_NAME, SCDAEMON_SOCK_NAME, DIRMNGR_SOCK_NAME }; int i; char *p; for (i=0; i < DIM(names); i++) { p = strconcat (socketdir , "/", names[i], NULL); if (p) gnupg_remove (p); xfree (p); } if (rmdir (socketdir)) gc_error (1, 0, "error removing '%s': %s", socketdir, gpg_strerror (err)); } else if (gpg_err_code (err) == GPG_ERR_ENOENT) gc_error (0, 0, "warning: removing '%s' failed: %s", socketdir, gpg_strerror (err)); else gc_error (1, 0, "error removing '%s': %s", socketdir, gpg_strerror (err)); } xfree (socketdir); } break; + case aShowCodepages: +#ifdef HAVE_W32_SYSTEM + { + get_outfp (&outfp); + es_fprintf (outfp, "Console: CP%u\n", GetConsoleOutputCP ()); + es_fprintf (outfp, "ANSI: CP%u\n", GetACP ()); + es_fprintf (outfp, "OEM: CP%u\n", GetOEMCP ()); + } +#endif + break; + + } if (outfp != es_stdout) if (es_fclose (outfp)) gc_error (1, errno, "error closing '%s'", opt.outfile); if (log_get_errorcount (0)) gpgconf_failure (0); else gpgconf_write_status (STATUS_SUCCESS, NULL); return 0; } void gpgconf_failure (gpg_error_t err) { log_flush (); if (!err) err = gpg_error (GPG_ERR_GENERAL); gpgconf_write_status (STATUS_FAILURE, "- %u", gpg_err_code (err) == GPG_ERR_USER_2? GPG_ERR_EINVAL : err); exit (gpg_err_code (err) == GPG_ERR_USER_2? 2 : 1); } diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c index 8a54c70ee..0311a465b 100644 --- a/tools/gpgtar-create.c +++ b/tools/gpgtar-create.c @@ -1,1073 +1,1063 @@ /* gpgtar-create.c - Create a TAR archive * Copyright (C) 2010 Free Software Foundation, Inc. * * 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 . */ #include #include #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN # include #else /*!HAVE_W32_SYSTEM*/ # include # include # include #endif /*!HAVE_W32_SYSTEM*/ #include #include "../common/i18n.h" #include "../common/exectool.h" #include "../common/sysutils.h" #include "../common/ccparray.h" #include "gpgtar.h" #ifndef HAVE_LSTAT #define lstat(a,b) stat ((a), (b)) #endif /* Object to control the file scanning. */ struct scanctrl_s; typedef struct scanctrl_s *scanctrl_t; struct scanctrl_s { tar_header_t flist; tar_header_t *flist_tail; int nestlevel; }; /* On Windows convert name to UTF8 and return it; caller must release * the result. On Unix or if ALREADY_UTF8 is set, this function is a * mere xtrystrcopy. On failure NULL is returned and ERRNO set. */ static char * name_to_utf8 (const char *name, int already_utf8) { #ifdef HAVE_W32_SYSTEM wchar_t *wstring; char *result; if (already_utf8) result = xtrystrdup (name); else { wstring = native_to_wchar (name); if (!wstring) return NULL; result = wchar_to_utf8 (wstring); xfree (wstring); } return result; #else /*!HAVE_W32_SYSTEM */ (void)already_utf8; return xtrystrdup (name); #endif /*!HAVE_W32_SYSTEM */ } /* Given a fresh header object HDR with only the name field set, try to gather all available info. This is the W32 version. */ #ifdef HAVE_W32_SYSTEM static gpg_error_t fillup_entry_w32 (tar_header_t hdr) { char *p; wchar_t *wfname; WIN32_FILE_ATTRIBUTE_DATA fad; DWORD attr; for (p=hdr->name; *p; p++) if (*p == '/') *p = '\\'; wfname = utf8_to_wchar (hdr->name); for (p=hdr->name; *p; p++) if (*p == '\\') *p = '/'; if (!wfname) { log_error ("error converting '%s': %s\n", hdr->name, w32_strerror (-1)); return gpg_error_from_syserror (); } if (!GetFileAttributesExW (wfname, GetFileExInfoStandard, &fad)) { log_error ("error stat-ing '%s': %s\n", hdr->name, w32_strerror (-1)); xfree (wfname); return gpg_error_from_syserror (); } xfree (wfname); attr = fad.dwFileAttributes; if ((attr & FILE_ATTRIBUTE_NORMAL)) hdr->typeflag = TF_REGULAR; else if ((attr & FILE_ATTRIBUTE_DIRECTORY)) hdr->typeflag = TF_DIRECTORY; else if ((attr & FILE_ATTRIBUTE_DEVICE)) hdr->typeflag = TF_NOTSUP; else if ((attr & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_TEMPORARY))) hdr->typeflag = TF_NOTSUP; else hdr->typeflag = TF_REGULAR; /* Map some attributes to USTAR defined mode bits. */ hdr->mode = 0640; /* User may read and write, group only read. */ if ((attr & FILE_ATTRIBUTE_DIRECTORY)) hdr->mode |= 0110; /* Dirs are user and group executable. */ if ((attr & FILE_ATTRIBUTE_READONLY)) hdr->mode &= ~0200; /* Clear the user write bit. */ if ((attr & FILE_ATTRIBUTE_HIDDEN)) hdr->mode &= ~0707; /* Clear all user and other bits. */ if ((attr & FILE_ATTRIBUTE_SYSTEM)) hdr->mode |= 0004; /* Make it readable by other. */ /* Only set the size for a regular file. */ if (hdr->typeflag == TF_REGULAR) hdr->size = (fad.nFileSizeHigh * (unsigned long long)(MAXDWORD+1) + fad.nFileSizeLow); hdr->mtime = (((unsigned long long)fad.ftLastWriteTime.dwHighDateTime << 32) | fad.ftLastWriteTime.dwLowDateTime); if (!hdr->mtime) hdr->mtime = (((unsigned long long)fad.ftCreationTime.dwHighDateTime << 32) | fad.ftCreationTime.dwLowDateTime); hdr->mtime -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ hdr->mtime /= 10000000; /* Convert from 0.1us to seconds. */ return 0; } #endif /*HAVE_W32_SYSTEM*/ /* Given a fresh header object HDR with only the name field set, try to gather all available info. This is the POSIX version. */ #ifndef HAVE_W32_SYSTEM static gpg_error_t fillup_entry_posix (tar_header_t hdr) { gpg_error_t err; struct stat sbuf; if (lstat (hdr->name, &sbuf)) { err = gpg_error_from_syserror (); log_error ("error stat-ing '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } if (S_ISREG (sbuf.st_mode)) hdr->typeflag = TF_REGULAR; else if (S_ISDIR (sbuf.st_mode)) hdr->typeflag = TF_DIRECTORY; else if (S_ISCHR (sbuf.st_mode)) hdr->typeflag = TF_CHARDEV; else if (S_ISBLK (sbuf.st_mode)) hdr->typeflag = TF_BLOCKDEV; else if (S_ISFIFO (sbuf.st_mode)) hdr->typeflag = TF_FIFO; else if (S_ISLNK (sbuf.st_mode)) hdr->typeflag = TF_SYMLINK; else hdr->typeflag = TF_NOTSUP; /* FIXME: Save DEV and INO? */ /* Set the USTAR defined mode bits using the system macros. */ if (sbuf.st_mode & S_IRUSR) hdr->mode |= 0400; if (sbuf.st_mode & S_IWUSR) hdr->mode |= 0200; if (sbuf.st_mode & S_IXUSR) hdr->mode |= 0100; if (sbuf.st_mode & S_IRGRP) hdr->mode |= 0040; if (sbuf.st_mode & S_IWGRP) hdr->mode |= 0020; if (sbuf.st_mode & S_IXGRP) hdr->mode |= 0010; if (sbuf.st_mode & S_IROTH) hdr->mode |= 0004; if (sbuf.st_mode & S_IWOTH) hdr->mode |= 0002; if (sbuf.st_mode & S_IXOTH) hdr->mode |= 0001; #ifdef S_IXUID if (sbuf.st_mode & S_IXUID) hdr->mode |= 04000; #endif #ifdef S_IXGID if (sbuf.st_mode & S_IXGID) hdr->mode |= 02000; #endif #ifdef S_ISVTX if (sbuf.st_mode & S_ISVTX) hdr->mode |= 01000; #endif hdr->nlink = sbuf.st_nlink; hdr->uid = sbuf.st_uid; hdr->gid = sbuf.st_gid; /* Only set the size for a regular file. */ if (hdr->typeflag == TF_REGULAR) hdr->size = sbuf.st_size; hdr->mtime = sbuf.st_mtime; return 0; } #endif /*!HAVE_W32_SYSTEM*/ /* Add a new entry. The name of a directory entry is ENTRYNAME; if that is NULL, DNAME is the name of the directory itself. Under Windows ENTRYNAME shall have backslashes replaced by standard slashes. */ static gpg_error_t add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl) { gpg_error_t err; tar_header_t hdr; char *p; size_t dnamelen = strlen (dname); log_assert (dnamelen); hdr = xtrycalloc (1, sizeof *hdr + dnamelen + 1 + (entryname? strlen (entryname) : 0) + 1); if (!hdr) return gpg_error_from_syserror (); p = stpcpy (hdr->name, dname); if (entryname) { if (dname[dnamelen-1] != '/') *p++ = '/'; strcpy (p, entryname); } else { if (hdr->name[dnamelen-1] == '/') hdr->name[dnamelen-1] = 0; } #ifdef HAVE_DOSISH_SYSTEM err = fillup_entry_w32 (hdr); #else err = fillup_entry_posix (hdr); #endif if (err) xfree (hdr); else { if (opt.verbose) gpgtar_print_header (hdr, log_get_stream ()); *scanctrl->flist_tail = hdr; scanctrl->flist_tail = &hdr->next; } return 0; } static gpg_error_t scan_directory (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; #ifdef HAVE_W32_SYSTEM WIN32_FIND_DATAW fi; HANDLE hd = INVALID_HANDLE_VALUE; char *p; if (!*dname) return 0; /* An empty directory name has no entries. */ { char *fname; wchar_t *wfname; fname = xtrymalloc (strlen (dname) + 2 + 2 + 1); if (!fname) { err = gpg_error_from_syserror (); goto leave; } if (!strcmp (dname, "/")) strcpy (fname, "/*"); /* Trailing slash is not allowed. */ else if (!strcmp (dname, ".")) strcpy (fname, "*"); else if (*dname && dname[strlen (dname)-1] == '/') strcpy (stpcpy (fname, dname), "*"); else if (*dname && dname[strlen (dname)-1] != '*') strcpy (stpcpy (fname, dname), "/*"); else strcpy (fname, dname); for (p=fname; *p; p++) if (*p == '/') *p = '\\'; wfname = utf8_to_wchar (fname); xfree (fname); if (!wfname) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, gpg_strerror (err)); goto leave; } hd = FindFirstFileW (wfname, &fi); if (hd == INVALID_HANDLE_VALUE) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, w32_strerror (-1)); xfree (wfname); goto leave; } xfree (wfname); } do { char *fname = wchar_to_utf8 (fi.cFileName); if (!fname) { err = gpg_error_from_syserror (); log_error ("error converting filename: %s\n", w32_strerror (-1)); break; } for (p=fname; *p; p++) if (*p == '\\') *p = '/'; if (!strcmp (fname, "." ) || !strcmp (fname, "..")) err = 0; /* Skip self and parent dir entry. */ else if (!strncmp (dname, "./", 2) && dname[2]) err = add_entry (dname+2, fname, scanctrl); else err = add_entry (dname, fname, scanctrl); xfree (fname); } while (!err && FindNextFileW (hd, &fi)); if (err) ; else if (GetLastError () == ERROR_NO_MORE_FILES) err = 0; else { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, w32_strerror (-1)); } leave: if (hd != INVALID_HANDLE_VALUE) FindClose (hd); #else /*!HAVE_W32_SYSTEM*/ DIR *dir; struct dirent *de; if (!*dname) return 0; /* An empty directory name has no entries. */ dir = opendir (dname); if (!dir) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, gpg_strerror (err)); return err; } while ((de = readdir (dir))) { if (!strcmp (de->d_name, "." ) || !strcmp (de->d_name, "..")) continue; /* Skip self and parent dir entry. */ err = add_entry (dname, de->d_name, scanctrl); if (err) goto leave; } leave: closedir (dir); #endif /*!HAVE_W32_SYSTEM*/ return err; } static gpg_error_t scan_recursive (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; tar_header_t hdr, *start_tail, *stop_tail; if (scanctrl->nestlevel > 200) { log_error ("directories too deeply nested\n"); return gpg_error (GPG_ERR_RESOURCE_LIMIT); } scanctrl->nestlevel++; assert (scanctrl->flist_tail); start_tail = scanctrl->flist_tail; scan_directory (dname, scanctrl); stop_tail = scanctrl->flist_tail; hdr = *start_tail; for (; hdr && hdr != *stop_tail; hdr = hdr->next) if (hdr->typeflag == TF_DIRECTORY) { if (opt.verbose > 1) log_info ("scanning directory '%s'\n", hdr->name); scan_recursive (hdr->name, scanctrl); } scanctrl->nestlevel--; return err; } /* Returns true if PATTERN is acceptable. */ static int pattern_valid_p (const char *pattern) { if (!*pattern) return 0; if (*pattern == '.' && pattern[1] == '.') return 0; if (*pattern == '/' #ifdef HAVE_DOSISH_SYSTEM || *pattern == '\\' #endif ) return 0; /* Absolute filenames are not supported. */ #ifdef HAVE_DRIVE_LETTERS if (((*pattern >= 'a' && *pattern <= 'z') || (*pattern >= 'A' && *pattern <= 'Z')) && pattern[1] == ':') return 0; /* Drive letter are not allowed either. */ #endif /*HAVE_DRIVE_LETTERS*/ return 1; /* Okay. */ } static void store_xoctal (char *buffer, size_t length, unsigned long long value) { char *p, *pend; size_t n; unsigned long long v; assert (length > 1); v = value; n = length; p = pend = buffer + length; *--p = 0; /* Nul byte. */ n--; do { *--p = '0' + (v % 8); v /= 8; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = '0'; } else /* Does not fit into the field. Store as binary number. */ { v = value; n = length; p = pend = buffer + length; do { *--p = v; v /= 256; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = 0; if (*p & 0x80) BUG (); *p |= 0x80; /* Set binary flag. */ } else BUG (); } } static void store_uname (char *buffer, size_t length, unsigned long uid) { static int initialized; static unsigned long lastuid; static char lastuname[32]; if (!initialized || uid != lastuid) { #ifdef HAVE_W32_SYSTEM mem2str (lastuname, uid? "user":"root", sizeof lastuname); #else struct passwd *pw = getpwuid (uid); lastuid = uid; initialized = 1; if (pw) mem2str (lastuname, pw->pw_name, sizeof lastuname); else { log_info ("failed to get name for uid %lu\n", uid); *lastuname = 0; } #endif } mem2str (buffer, lastuname, length); } static void store_gname (char *buffer, size_t length, unsigned long gid) { static int initialized; static unsigned long lastgid; static char lastgname[32]; if (!initialized || gid != lastgid) { #ifdef HAVE_W32_SYSTEM mem2str (lastgname, gid? "users":"root", sizeof lastgname); #else struct group *gr = getgrgid (gid); lastgid = gid; initialized = 1; if (gr) mem2str (lastgname, gr->gr_name, sizeof lastgname); else { log_info ("failed to get name for gid %lu\n", gid); *lastgname = 0; } #endif } mem2str (buffer, lastgname, length); } static gpg_error_t build_header (void *record, tar_header_t hdr) { gpg_error_t err; struct ustar_raw_header *raw = record; size_t namelen, n; unsigned long chksum; unsigned char *p; memset (record, 0, RECORDSIZE); /* Store name and prefix. */ namelen = strlen (hdr->name); if (namelen < sizeof raw->name) memcpy (raw->name, hdr->name, namelen); else { n = (namelen < sizeof raw->prefix)? namelen : sizeof raw->prefix; for (n--; n ; n--) if (hdr->name[n] == '/') break; if (namelen - n < sizeof raw->name) { /* Note that the N is < sizeof prefix and that the delimiting slash is not stored. */ memcpy (raw->prefix, hdr->name, n); memcpy (raw->name, hdr->name+n+1, namelen - n); } else { err = gpg_error (GPG_ERR_TOO_LARGE); log_error ("error storing file '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } } store_xoctal (raw->mode, sizeof raw->mode, hdr->mode); store_xoctal (raw->uid, sizeof raw->uid, hdr->uid); store_xoctal (raw->gid, sizeof raw->gid, hdr->gid); store_xoctal (raw->size, sizeof raw->size, hdr->size); store_xoctal (raw->mtime, sizeof raw->mtime, hdr->mtime); switch (hdr->typeflag) { case TF_REGULAR: raw->typeflag[0] = '0'; break; case TF_HARDLINK: raw->typeflag[0] = '1'; break; case TF_SYMLINK: raw->typeflag[0] = '2'; break; case TF_CHARDEV: raw->typeflag[0] = '3'; break; case TF_BLOCKDEV: raw->typeflag[0] = '4'; break; case TF_DIRECTORY: raw->typeflag[0] = '5'; break; case TF_FIFO: raw->typeflag[0] = '6'; break; default: return gpg_error (GPG_ERR_NOT_SUPPORTED); } memcpy (raw->magic, "ustar", 6); raw->version[0] = '0'; raw->version[1] = '0'; store_uname (raw->uname, sizeof raw->uname, hdr->uid); store_gname (raw->gname, sizeof raw->gname, hdr->gid); #ifndef HAVE_W32_SYSTEM if (hdr->typeflag == TF_SYMLINK) { int nread; nread = readlink (hdr->name, raw->linkname, sizeof raw->linkname -1); if (nread < 0) { err = gpg_error_from_syserror (); log_error ("error reading symlink '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } raw->linkname[nread] = 0; } #endif /*HAVE_W32_SYSTEM*/ /* Compute the checksum. */ memset (raw->checksum, ' ', sizeof raw->checksum); chksum = 0; p = record; for (n=0; n < RECORDSIZE; n++) chksum += *p++; store_xoctal (raw->checksum, sizeof raw->checksum - 1, chksum); raw->checksum[7] = ' '; return 0; } static gpg_error_t write_file (estream_t stream, tar_header_t hdr) { gpg_error_t err; char record[RECORDSIZE]; estream_t infp; size_t nread, nbytes; int any; err = build_header (record, hdr); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) { log_info ("skipping unsupported file '%s'\n", hdr->name); err = 0; } return err; } if (hdr->typeflag == TF_REGULAR) { infp = es_fopen (hdr->name, "rb"); if (!infp) { err = gpg_error_from_syserror (); log_error ("can't open '%s': %s - skipped\n", hdr->name, gpg_strerror (err)); return err; } } else infp = NULL; err = write_record (stream, record); if (err) goto leave; if (hdr->typeflag == TF_REGULAR) { hdr->nrecords = (hdr->size + RECORDSIZE-1)/RECORDSIZE; any = 0; while (hdr->nrecords--) { nbytes = hdr->nrecords? RECORDSIZE : (hdr->size % RECORDSIZE); if (!nbytes) nbytes = RECORDSIZE; nread = es_fread (record, 1, nbytes, infp); if (nread != nbytes) { err = gpg_error_from_syserror (); log_error ("error reading file '%s': %s%s\n", hdr->name, gpg_strerror (err), any? " (file shrunk?)":""); goto leave; } any = 1; err = write_record (stream, record); if (err) goto leave; } nread = es_fread (record, 1, 1, infp); if (nread) log_info ("note: file '%s' has grown\n", hdr->name); } leave: if (err) es_fclose (infp); else if ((err = es_fclose (infp))) log_error ("error closing file '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } static gpg_error_t write_eof_mark (estream_t stream) { gpg_error_t err; char record[RECORDSIZE]; memset (record, 0, sizeof record); err = write_record (stream, record); if (!err) err = write_record (stream, record); return err; } /* Create a new tarball using the names in the array INPATTERN. If INPATTERN is NULL take the pattern as null terminated strings from stdin or from the file specified by FILES_FROM. If NULL_NAMES is set the filenames in such a file are delimited by a binary Nul and not by a LF. */ gpg_error_t gpgtar_create (char **inpattern, const char *files_from, int null_names, int encrypt, int sign) { gpg_error_t err = 0; struct scanctrl_s scanctrl_buffer; scanctrl_t scanctrl = &scanctrl_buffer; tar_header_t hdr, *start_tail; estream_t files_from_stream = NULL; estream_t outstream = NULL; estream_t cipher_stream = NULL; int eof_seen = 0; memset (scanctrl, 0, sizeof *scanctrl); scanctrl->flist_tail = &scanctrl->flist; - /* { unsigned int cpno, cpno2, cpno3; */ - - /* cpno = GetConsoleOutputCP (); */ - /* cpno2 = GetACP (); */ - /* cpno3 = GetOEMCP (); */ - /* log_debug ("Codepages: Console: %u ANSI: %u OEM: %u\n", */ - /* cpno, cpno2, cpno3); */ - /* } */ - - if (!inpattern) { if (!files_from || !strcmp (files_from, "-")) { files_from = "-"; files_from_stream = es_stdin; if (null_names) es_set_binary (es_stdin); } else if (!(files_from_stream=es_fopen (files_from, null_names? "rb":"r"))) { err = gpg_error_from_syserror (); log_error ("error opening '%s': %s\n", files_from, gpg_strerror (err)); return err; } } if (opt.directory && gnupg_chdir (opt.directory)) { err = gpg_error_from_syserror (); log_error ("chdir to '%s' failed: %s\n", opt.directory, gpg_strerror (err)); return err; } while (!eof_seen) { char *pat, *p; int skip_this = 0; if (inpattern) { const char *pattern = *inpattern; if (!pattern) break; /* End of array. */ inpattern++; if (!*pattern) continue; pat = name_to_utf8 (pattern, 0); } else /* Read Nul or LF delimited pattern from files_from_stream. */ { int c; char namebuf[4096]; size_t n = 0; for (;;) { if ((c = es_getc (files_from_stream)) == EOF) { if (es_ferror (files_from_stream)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", files_from, gpg_strerror (err)); goto leave; } c = null_names ? 0 : '\n'; eof_seen = 1; } if (n >= sizeof namebuf - 1) { if (!skip_this) { skip_this = 1; log_error ("error reading '%s': %s\n", files_from, "filename too long"); } } else namebuf[n++] = c; if (null_names) { if (!c) { namebuf[n] = 0; break; } } else /* Shall be LF delimited. */ { if (!c) { if (!skip_this) { skip_this = 1; log_error ("error reading '%s': %s\n", files_from, "filename with embedded Nul"); } } else if ( c == '\n' ) { namebuf[n] = 0; ascii_trim_spaces (namebuf); n = strlen (namebuf); break; } } } if (skip_this || n < 2) continue; pat = name_to_utf8 (namebuf, opt.utf8strings); } if (!pat) { err = gpg_error_from_syserror (); log_error ("memory allocation problem: %s\n", gpg_strerror (err)); goto leave; } for (p=pat; *p; p++) if (*p == '\\') *p = '/'; if (opt.verbose > 1) log_info ("scanning '%s'\n", pat); start_tail = scanctrl->flist_tail; if (skip_this || !pattern_valid_p (pat)) log_error ("skipping invalid name '%s'\n", pat); else if (!add_entry (pat, NULL, scanctrl) && *start_tail && ((*start_tail)->typeflag & TF_DIRECTORY)) scan_recursive (pat, scanctrl); xfree (pat); } if (files_from_stream && files_from_stream != es_stdin) es_fclose (files_from_stream); if (opt.outfile) { if (!strcmp (opt.outfile, "-")) outstream = es_stdout; else outstream = es_fopen (opt.outfile, "wb"); if (!outstream) { err = gpg_error_from_syserror (); goto leave; } } else { outstream = es_stdout; } if (outstream == es_stdout) es_set_binary (es_stdout); if (encrypt || sign) { cipher_stream = outstream; outstream = es_fopenmem (0, "rwb"); if (! outstream) { err = gpg_error_from_syserror (); goto leave; } } for (hdr = scanctrl->flist; hdr; hdr = hdr->next) { err = write_file (outstream, hdr); if (err) goto leave; } err = write_eof_mark (outstream); if (err) goto leave; if (encrypt || sign) { strlist_t arg; ccparray_t ccp; const char **argv; err = es_fseek (outstream, 0, SEEK_SET); if (err) goto leave; /* '--encrypt' may be combined with '--symmetric', but 'encrypt' is set either way. Clear it if no recipients are specified. XXX: Fix command handling. */ if (opt.symmetric && opt.recipients == NULL) encrypt = 0; ccparray_init (&ccp, 0); if (encrypt) ccparray_put (&ccp, "--encrypt"); if (sign) ccparray_put (&ccp, "--sign"); if (opt.user) { ccparray_put (&ccp, "--local-user"); ccparray_put (&ccp, opt.user); } if (opt.symmetric) ccparray_put (&ccp, "--symmetric"); for (arg = opt.recipients; arg; arg = arg->next) { ccparray_put (&ccp, "--recipient"); ccparray_put (&ccp, arg->d); } for (arg = opt.gpg_arguments; arg; arg = arg->next) ccparray_put (&ccp, arg->d); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, outstream, NULL, cipher_stream, NULL, NULL); xfree (argv); if (err) goto leave; } leave: if (!err) { gpg_error_t first_err; if (outstream != es_stdout) first_err = es_fclose (outstream); else first_err = es_fflush (outstream); outstream = NULL; if (cipher_stream != es_stdout) err = es_fclose (cipher_stream); else err = es_fflush (cipher_stream); cipher_stream = NULL; if (! err) err = first_err; } if (err) { log_error ("creating tarball '%s' failed: %s\n", opt.outfile ? opt.outfile : "-", gpg_strerror (err)); if (outstream && outstream != es_stdout) es_fclose (outstream); if (cipher_stream && cipher_stream != es_stdout) es_fclose (cipher_stream); if (opt.outfile) gnupg_remove (opt.outfile); } scanctrl->flist_tail = NULL; while ( (hdr = scanctrl->flist) ) { scanctrl->flist = hdr->next; xfree (hdr); } return err; }