* 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/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* GnuPG comes with a shell script gpg-zip which creates archive files
gpg. So here we go. */
#include <config.h>
+#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
-#ifdef HAVE_STAT
-# include <sys/stat.h>
-#endif
#include "util.h"
#include "i18n.h"
#include "sysutils.h"
#include "../common/openpgpdefs.h"
+#include "../common/init.h"
+#include "../common/strlist.h"
#include "gpgtar.h"
enum cmd_and_opt_values
{
aNull = 0,
+ aCreate = 600,
+ aExtract,
aEncrypt = 'e',
aDecrypt = 'd',
aSign = 's',
oRecipient = 'r',
oUser = 'u',
oOutput = 'o',
+ oDirectory = 'C',
oQuiet = 'q',
oVerbose = 'v',
oFilesFrom = 'T',
oNoVerbose = 500,
aSignEncrypt,
+ oGpgProgram,
oSkipCrypto,
oOpenPGP,
oCMS,
oSetFilename,
- oNull
+ oNull,
+
+ /* Compatibility with gpg-zip. */
+ oGpgArgs,
+ oTarArgs,
+
+ /* Debugging. */
+ oDryRun,
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
-
- ARGPARSE_c (aEncrypt, "encrypt", N_("create an archive")),
- ARGPARSE_c (aDecrypt, "decrypt", N_("extract an archive")),
+
+ ARGPARSE_c (aCreate, "create", N_("create an archive")),
+ ARGPARSE_c (aExtract, "extract", N_("extract an archive")),
+ ARGPARSE_c (aEncrypt, "encrypt", N_("create an encrypted archive")),
+ ARGPARSE_c (aDecrypt, "decrypt", N_("extract an encrypted archive")),
ARGPARSE_c (aSign, "sign", N_("create a signed archive")),
ARGPARSE_c (aList, "list-archive", N_("list an archive")),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
+ ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
ARGPARSE_s_n (oSkipCrypto, "skip-crypto", N_("skip the crypto processing")),
+ ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_s (oSetFilename, "set-filename", "@"),
+ ARGPARSE_s_n (oOpenPGP, "openpgp", "@"),
+ ARGPARSE_s_n (oCMS, "cms", "@"),
+
+ ARGPARSE_group (302, N_("@\nTar options:\n ")),
+
+ ARGPARSE_s_s (oDirectory, "directory",
+ N_("|DIRECTORY|extract files into DIRECTORY")),
ARGPARSE_s_s (oFilesFrom, "files-from",
N_("|FILE|get names to create from FILE")),
ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")),
- ARGPARSE_s_n (oOpenPGP, "openpgp", "@"),
- ARGPARSE_s_n (oCMS, "cms", "@"),
+
+ ARGPARSE_s_s (oGpgArgs, "gpg-args", "@"),
+ ARGPARSE_s_s (oTarArgs, "tar-args", "@"),
ARGPARSE_end ()
};
-\f
-static void tar_and_encrypt (char **inpattern);
-static void decrypt_and_untar (const char *fname);
-static void decrypt_and_list (const char *fname);
+/* The list of commands and options for tar that we understand. */
+static ARGPARSE_OPTS tar_opts[] = {
+ ARGPARSE_s_s (oDirectory, "directory",
+ N_("|DIRECTORY|extract files into DIRECTORY")),
+ ARGPARSE_s_s (oFilesFrom, "files-from",
+ N_("|FILE|get names to create from FILE")),
+ ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")),
+ ARGPARSE_end ()
+};
\f
-/* Print usage information and and provide strings for help. */
+/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
switch (level)
{
- case 11: p = "gpgtar (GnuPG)";
+ case 11: p = "@GPGTAR@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
cmd = aSignEncrypt;
else if (cmd == aEncrypt && new_cmd == aSign)
cmd = aSignEncrypt;
- else
+ else
{
log_error (_("conflicting commands\n"));
exit (2);
*ret_cmd = cmd;
}
+\f
+/* Shell-like argument splitting.
+ For compatibility with gpg-zip we accept arguments for GnuPG and
+ tar given as a string argument to '--gpg-args' and '--tar-args'.
+ gpg-zip was implemented as a Bourne Shell script, and therefore, we
+ need to split the string the same way the shell would. */
+static int
+shell_parse_stringlist (const char *str, strlist_t *r_list)
+{
+ strlist_t list = NULL;
+ const char *s = str;
+ char quoted = 0;
+ char arg[1024];
+ char *p = arg;
+#define addchar(c) \
+ do { if (p - arg + 2 < sizeof arg) *p++ = (c); else return 1; } while (0)
+#define addargument() \
+ do { \
+ if (p > arg) \
+ { \
+ *p = 0; \
+ append_to_strlist (&list, arg); \
+ p = arg; \
+ } \
+ } while (0)
+
+#define unquoted 0
+#define singlequote '\''
+#define doublequote '"'
+
+ for (; *s; s++)
+ {
+ switch (quoted)
+ {
+ case unquoted:
+ if (isspace (*s))
+ addargument ();
+ else if (*s == singlequote || *s == doublequote)
+ quoted = *s;
+ else
+ addchar (*s);
+ break;
-\f
-/* gpgtar main. */
-int
-main (int argc, char **argv)
+ case singlequote:
+ if (*s == singlequote)
+ quoted = unquoted;
+ else
+ addchar (*s);
+ break;
+
+ case doublequote:
+ assert (s > str || !"cannot be quoted at first char");
+ if (*s == doublequote && *(s - 1) != '\\')
+ quoted = unquoted;
+ else
+ addchar (*s);
+ break;
+
+ default:
+ assert (! "reached");
+ }
+ }
+
+ /* Append the last argument. */
+ addargument ();
+
+#undef doublequote
+#undef singlequote
+#undef unquoted
+#undef addargument
+#undef addchar
+ *r_list = list;
+ return 0;
+}
+
+
+/* Like shell_parse_stringlist, but returns an argv vector
+ instead of a strlist. */
+static int
+shell_parse_argv (const char *s, int *r_argc, char ***r_argv)
{
- ARGPARSE_ARGS pargs;
- const char *fname;
- int no_more_options = 0;
- enum cmd_and_opt_values cmd = 0;
- int skip_crypto = 0;
- const char *files_from = NULL;
- int null_names = 0;
+ int i;
+ strlist_t list;
- assert (sizeof (struct ustar_raw_header) == 512);
+ if (shell_parse_stringlist (s, &list))
+ return 1;
- gnupg_reopen_std ("gpgtar");
- set_strusage (my_strusage);
- log_set_prefix ("gpgtar", 1);
+ *r_argc = strlist_length (list);
+ *r_argv = xtrycalloc (*r_argc, sizeof **r_argv);
+ if (*r_argv == NULL)
+ return 1;
- /* Make sure that our subsystems are ready. */
- i18n_init();
- init_common_subsystems ();
+ for (i = 0; list; i++)
+ {
+ gpgrt_annotate_leaked_object (list);
+ (*r_argv)[i] = list->d;
+ list = list->next;
+ }
+ gpgrt_annotate_leaked_object (*r_argv);
+ return 0;
+}
+\f
+/* Global flags. */
+enum cmd_and_opt_values cmd = 0;
+int skip_crypto = 0;
+const char *files_from = NULL;
+int null_names = 0;
- /* Parse the command line. */
- pargs.argc = &argc;
- pargs.argv = &argv;
- pargs.flags = ARGPARSE_FLAG_KEEP;
- while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts))
+
+/* Command line parsing. */
+static void
+parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
+{
+ int no_more_options = 0;
+
+ while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
{
- switch (pargs.r_opt)
+ switch (pargs->r_opt)
{
- case oOutput: opt.outfile = pargs.r.ret_str; break;
- case oSetFilename: opt.filename = pargs.r.ret_str; break;
+ case oOutput: opt.outfile = pargs->r.ret_str; break;
+ case oDirectory: opt.directory = pargs->r.ret_str; break;
+ case oSetFilename: opt.filename = pargs->r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oNoVerbose: opt.verbose = 0; break;
- case oFilesFrom: files_from = pargs.r.ret_str; break;
+ case oFilesFrom: files_from = pargs->r.ret_str; break;
case oNull: null_names = 1; break;
-
+
case aList:
case aDecrypt:
case aEncrypt:
case aSign:
- set_cmd (&cmd, pargs.r_opt);
+ set_cmd (&cmd, pargs->r_opt);
break;
+ case aCreate:
+ set_cmd (&cmd, aEncrypt);
+ skip_crypto = 1;
+ break;
+
+ case aExtract:
+ set_cmd (&cmd, aDecrypt);
+ skip_crypto = 1;
+ break;
+
+ case oRecipient:
+ add_to_strlist (&opt.recipients, pargs->r.ret_str);
+ break;
+
+ case oUser:
+ opt.user = pargs->r.ret_str;
+ break;
+
case oSymmetric:
set_cmd (&cmd, aEncrypt);
opt.symmetric = 1;
break;
+ case oGpgProgram:
+ opt.gpg_program = pargs->r.ret_str;
+ break;
+
case oSkipCrypto:
skip_crypto = 1;
break;
case oOpenPGP: /* Dummy option for now. */ break;
case oCMS: /* Dummy option for now. */ break;
- default: pargs.err = 2; break;
+ case oGpgArgs:;
+ {
+ strlist_t list;
+ if (shell_parse_stringlist (pargs->r.ret_str, &list))
+ log_error ("failed to parse gpg arguments '%s'\n",
+ pargs->r.ret_str);
+ else
+ {
+ if (opt.gpg_arguments)
+ strlist_last (opt.gpg_arguments)->next = list;
+ else
+ opt.gpg_arguments = list;
+ }
+ }
+ break;
+
+ case oTarArgs:;
+ {
+ int tar_argc;
+ char **tar_argv;
+
+ if (shell_parse_argv (pargs->r.ret_str, &tar_argc, &tar_argv))
+ log_error ("failed to parse tar arguments '%s'\n",
+ pargs->r.ret_str);
+ else
+ {
+ ARGPARSE_ARGS tar_args;
+ tar_args.argc = &tar_argc;
+ tar_args.argv = &tar_argv;
+ tar_args.flags = ARGPARSE_FLAG_ARG0;
+ parse_arguments (&tar_args, tar_opts);
+ if (tar_args.err)
+ log_error ("unsupported tar arguments '%s'\n",
+ pargs->r.ret_str);
+ pargs->err = tar_args.err;
+ }
+ }
+ break;
+
+ case oDryRun:
+ opt.dry_run = 1;
+ break;
+
+ default: pargs->err = 2; break;
}
}
-
+}
+
+\f
+/* gpgtar main. */
+int
+main (int argc, char **argv)
+{
+ gpg_error_t err;
+ const char *fname;
+ ARGPARSE_ARGS pargs;
+
+ assert (sizeof (struct ustar_raw_header) == 512);
+
+ gnupg_reopen_std (GPGTAR_NAME);
+ set_strusage (my_strusage);
+ log_set_prefix (GPGTAR_NAME, GPGRT_LOG_WITH_PREFIX);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init();
+ init_common_subsystems (&argc, &argv);
+
+ /* Parse the command line. */
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags = ARGPARSE_FLAG_KEEP;
+ parse_arguments (&pargs, opts);
+
if ((files_from && !null_names) || (!files_from && null_names))
log_error ("--files-from and --null may only be used in conjunction\n");
if (files_from && strcmp (files_from, "-"))
if (log_get_errorcount (0))
exit (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]);
+ }
+
+ if (! opt.gpg_program)
+ opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
+
+ if (opt.verbose > 1)
+ opt.debug_level = 1024;
+
switch (cmd)
{
case aList:
log_info ("note: ignoring option --set-filename\n");
if (files_from)
log_info ("note: ignoring option --files-from\n");
- if (skip_crypto)
- gpgtar_list (fname);
- else
- decrypt_and_list (fname);
+ err = gpgtar_list (fname, !skip_crypto);
+ if (err && log_get_errorcount (0) == 0)
+ log_error ("listing archive failed: %s\n", gpg_strerror (err));
break;
case aEncrypt:
+ case aSign:
+ case aSignEncrypt:
if ((!argc && !null_names)
|| (argc && null_names))
usage (1);
if (opt.filename)
log_info ("note: ignoring option --set-filename\n");
- if (skip_crypto)
- gpgtar_create (null_names? NULL :argv);
- else
- tar_and_encrypt (null_names? NULL : argv);
+ err = gpgtar_create (null_names? NULL :argv,
+ !skip_crypto
+ && (cmd == aEncrypt || cmd == aSignEncrypt),
+ cmd == aSign || cmd == aSignEncrypt);
+ if (err && log_get_errorcount (0) == 0)
+ log_error ("creating archive failed: %s\n", gpg_strerror (err));
break;
case aDecrypt:
if (files_from)
log_info ("note: ignoring option --files-from\n");
fname = argc ? *argv : NULL;
- if (skip_crypto)
- gpgtar_extract (fname);
- else
- decrypt_and_untar (fname);
+ err = gpgtar_extract (fname, !skip_crypto);
+ if (err && log_get_errorcount (0) == 0)
+ log_error ("extracting archive failed: %s\n", gpg_strerror (err));
break;
default:
/* Read the next record from STREAM. RECORD is a buffer provided by
the caller and must be at leadt of size RECORDSIZE. The function
- return 0 on success and and error code on failure; a diagnostic
+ return 0 on success and error code on failure; a diagnostic
printed as well. Note that there is no need for an EOF indicator
because a tarball has an explicit EOF record. */
gpg_error_t
{
err = gpg_error_from_syserror ();
if (es_ferror (stream))
- log_error ("error reading `%s': %s\n",
+ log_error ("error reading '%s': %s\n",
es_fname_get (stream), gpg_strerror (err));
else
- log_error ("error reading `%s': premature EOF "
+ log_error ("error reading '%s': premature EOF "
"(size of last record: %zu)\n",
es_fname_get (stream), nread);
}
if (nwritten != RECORDSIZE)
{
err = gpg_error_from_syserror ();
- log_error ("error writing `%s': %s\n",
+ log_error ("error writing '%s': %s\n",
es_fname_get (stream), gpg_strerror (err));
}
else
err = 0;
-
+
return err;
}
/* Return true if FP is an unarmored OpenPGP message. Note that this
- fucntion reads a few bytes from FP but pushes them back. */
+ function reads a few bytes from FP but pushes them back. */
#if 0
static int
openpgp_message_p (estream_t fp)
if (ctb != EOF)
{
if (es_ungetc (ctb, fp))
- log_fatal ("error ungetting first byte: %s\n",
+ log_fatal ("error ungetting first byte: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
-
+
if ((ctb & 0x80))
{
switch ((ctb & 0x40) ? (ctb & 0x3f) : ((ctb>>2)&0xf))
return 0;
}
#endif
-
-
-
-\f
-static void
-tar_and_encrypt (char **inpattern)
-{
- (void)inpattern;
- log_error ("tar_and_encrypt has not yet been implemented\n");
-}
-
-
-\f
-static void
-decrypt_and_untar (const char *fname)
-{
- (void)fname;
- log_error ("decrypt_and_untar has not yet been implemented\n");
-}
-
-
-\f
-static void
-decrypt_and_list (const char *fname)
-{
- (void)fname;
- log_error ("decrypt_and_list has not yet been implemented\n");
-}
-
-
-
-
-/* A wrapper around mkdir which takes a string for the mode argument.
- This makes it easier to handle the mode argument which is not
- defined on all systems. The format of the modestring is
-
- "-rwxrwxrwx"
-
- '-' is a don't care or not set. 'r', 'w', 'x' are read allowed,
- write allowed, execution allowed with the first group for the user,
- the second for the group and the third for all others. If the
- string is shorter than above the missing mode characters are meant
- to be not set. */
-int
-gnupg_mkdir (const char *name, const char *modestr)
-{
-#ifdef HAVE_W32CE_SYSTEM
- wchar_t *wname;
- (void)modestr;
-
- wname = utf8_to_wchar (name);
- if (!wname)
- return -1;
- if (!CreateDirectoryW (wname, NULL))
- {
- xfree (wname);
- return -1; /* ERRNO is automagically provided by gpg-error.h. */
- }
- xfree (wname);
- return 0;
-#elif MKDIR_TAKES_ONE_ARG
- (void)modestr;
- /* Note: In the case of W32 we better use CreateDirectory and try to
- set appropriate permissions. However using mkdir is easier
- because this sets ERRNO. */
- return mkdir (name);
-#else
- mode_t mode = 0;
-
- if (modestr && *modestr)
- {
- modestr++;
- if (*modestr && *modestr++ == 'r')
- mode |= S_IRUSR;
- if (*modestr && *modestr++ == 'w')
- mode |= S_IWUSR;
- if (*modestr && *modestr++ == 'x')
- mode |= S_IXUSR;
- if (*modestr && *modestr++ == 'r')
- mode |= S_IRGRP;
- if (*modestr && *modestr++ == 'w')
- mode |= S_IWGRP;
- if (*modestr && *modestr++ == 'x')
- mode |= S_IXGRP;
- if (*modestr && *modestr++ == 'r')
- mode |= S_IROTH;
- if (*modestr && *modestr++ == 'w')
- mode |= S_IWOTH;
- if (*modestr && *modestr++ == 'x')
- mode |= S_IXOTH;
- }
- return mkdir (name, mode);
-#endif
-}
-
-#ifdef HAVE_W32_SYSTEM
-/* Return a malloced string encoded in UTF-8 from the wide char input
- string STRING. Caller must free this value. Returns NULL and sets
- ERRNO on failure. Calling this function with STRING set to NULL is
- not defined. */
-char *
-wchar_to_utf8 (const wchar_t *string)
-{
- int n;
- char *result;
-
- n = WideCharToMultiByte (CP_UTF8, 0, string, -1, NULL, 0, NULL, NULL);
- if (n < 0)
- {
- errno = EINVAL;
- return NULL;
- }
-
- result = xtrymalloc (n+1);
- if (!result)
- return NULL;
-
- n = WideCharToMultiByte (CP_UTF8, 0, string, -1, result, n, NULL, NULL);
- if (n < 0)
- {
- xfree (result);
- errno = EINVAL;
- result = NULL;
- }
- return result;
-}
-
-
-/* Return a malloced wide char string from an UTF-8 encoded input
- string STRING. Caller must free this value. Returns NULL and sets
- ERRNO on failure. Calling this function with STRING set to NULL is
- not defined. */
-wchar_t *
-utf8_to_wchar (const char *string)
-{
- int n;
- size_t nbytes;
- wchar_t *result;
-
- n = MultiByteToWideChar (CP_UTF8, 0, string, -1, NULL, 0);
- if (n < 0)
- {
- errno = EINVAL;
- return NULL;
- }
-
- nbytes = (size_t)(n+1) * sizeof(*result);
- if (nbytes / sizeof(*result) != (n+1))
- {
- errno = ENOMEM;
- return NULL;
- }
- result = xtrymalloc (nbytes);
- if (!result)
- return NULL;
-
- n = MultiByteToWideChar (CP_UTF8, 0, string, -1, result, n);
- if (n < 0)
- {
- free (result);
- errno = EINVAL;
- result = NULL;
- }
- return result;
-}
-#endif /*HAVE_W32_SYSTEM*/