w32: Also change the directory on daemon startup.
[gnupg.git] / tools / gpgtar.c
index a86aafe..2757ab0 100644 (file)
@@ -14,7 +14,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * 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
  */
 
 /* GnuPG comes with a shell script gpg-zip which creates archive files
    gpg.  So here we go.  */
 
 #include <config.h>
    gpg.  So here we go.  */
 
 #include <config.h>
-#include <assuan.h>
+#include <ctype.h>
 #include <errno.h>
 #include <errno.h>
-#include <npth.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
 
-#include "util.h"
-#include "i18n.h"
-#include "sysutils.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
 #include "../common/openpgpdefs.h"
 #include "../common/init.h"
 #include "../common/openpgpdefs.h"
 #include "../common/init.h"
+#include "../common/strlist.h"
 
 #include "gpgtar.h"
 
 
 #include "gpgtar.h"
 
@@ -48,6 +48,8 @@
 enum cmd_and_opt_values
   {
     aNull = 0,
 enum cmd_and_opt_values
   {
     aNull = 0,
+    aCreate = 600,
+    aExtract,
     aEncrypt    = 'e',
     aDecrypt    = 'd',
     aSign       = 's',
     aEncrypt    = 'e',
     aDecrypt    = 'd',
     aSign       = 's',
@@ -57,17 +59,26 @@ enum cmd_and_opt_values
     oRecipient = 'r',
     oUser       = 'u',
     oOutput    = 'o',
     oRecipient = 'r',
     oUser       = 'u',
     oOutput    = 'o',
+    oDirectory  = 'C',
     oQuiet      = 'q',
     oVerbose   = 'v',
     oFilesFrom  = 'T',
     oNoVerbose = 500,
 
     aSignEncrypt,
     oQuiet      = 'q',
     oVerbose   = 'v',
     oFilesFrom  = 'T',
     oNoVerbose = 500,
 
     aSignEncrypt,
+    oGpgProgram,
     oSkipCrypto,
     oOpenPGP,
     oCMS,
     oSetFilename,
     oSkipCrypto,
     oOpenPGP,
     oCMS,
     oSetFilename,
-    oNull
+    oNull,
+
+    /* Compatibility with gpg-zip.  */
+    oGpgArgs,
+    oTarArgs,
+
+    /* Debugging.  */
+    oDryRun,
   };
 
 
   };
 
 
@@ -75,8 +86,10 @@ enum cmd_and_opt_values
 static ARGPARSE_OPTS opts[] = {
   ARGPARSE_group (300, N_("@Commands:\n ")),
 
 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_c (aSign,      "sign",    N_("create a signed archive")),
   ARGPARSE_c (aList,      "list-archive", N_("list an archive")),
 
@@ -89,20 +102,42 @@ static ARGPARSE_OPTS opts[] = {
   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 (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 (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_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_s (oGpgArgs, "gpg-args", "@"),
+  ARGPARSE_s_s (oTarArgs, "tar-args", "@"),
+
+  ARGPARSE_end ()
+};
+
+
+/* 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_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_end ()
 };
 
 
 \f
 
   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 )
 {
 static const char *
 my_strusage( int level )
 {
@@ -150,75 +185,169 @@ set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
 
   *ret_cmd = cmd;
 }
 
   *ret_cmd = cmd;
 }
+\f
+/* Shell-like argument splitting.
 
 
-ASSUAN_SYSTEM_NPTH_IMPL;
+   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_NAME);
-  set_strusage (my_strusage);
-  log_set_prefix (GPGTAR_NAME, 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 (&argc, &argv);
-  npth_init ();
-  assuan_set_assuan_log_prefix (log_get_prefix (NULL));
-  assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
-  assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
-  assuan_sock_init ();
+  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 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:
         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;
 
          break;
 
+        case aCreate:
+          set_cmd (&cmd, aEncrypt);
+          skip_crypto = 1;
+          break;
+
+        case aExtract:
+          set_cmd (&cmd, aDecrypt);
+          skip_crypto = 1;
+          break;
+
         case oRecipient:
         case oRecipient:
-          add_to_strlist (&opt.recipients, pargs.r.ret_str);
+          add_to_strlist (&opt.recipients, pargs->r.ret_str);
           break;
 
         case oUser:
           break;
 
         case oUser:
-          log_info ("note: ignoring option --user\n");
-          opt.user = pargs.r.ret_str;
+          opt.user = pargs->r.ret_str;
           break;
 
         case oSymmetric:
           break;
 
         case oSymmetric:
-          log_info ("note: ignoring option --symmetric\n");
           set_cmd (&cmd, aEncrypt);
           opt.symmetric = 1;
           break;
 
           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 oSkipCrypto:
           skip_crypto = 1;
           break;
@@ -226,9 +355,78 @@ main (int argc, char **argv)
         case oOpenPGP: /* Dummy option for now.  */ break;
         case oCMS:     /* Dummy option for now.  */ 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 && !null_names) || (!files_from && null_names))
     log_error ("--files-from and --null may only be used in conjunction\n");
@@ -248,9 +446,11 @@ main (int argc, char **argv)
           log_info (_("NOTE: '%s' is not considered an option\n"), argv[i]);
     }
 
           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;
   if (opt.verbose > 1)
     opt.debug_level = 1024;
-  setup_libassuan_logging (&opt.debug_level);
 
   switch (cmd)
     {
 
   switch (cmd)
     {
@@ -262,16 +462,25 @@ main (int argc, char **argv)
         log_info ("note: ignoring option --set-filename\n");
       if (files_from)
         log_info ("note: ignoring option --files-from\n");
         log_info ("note: ignoring option --set-filename\n");
       if (files_from)
         log_info ("note: ignoring option --files-from\n");
-      gpgtar_list (fname, !skip_crypto);
+      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:
       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 ((!argc && !null_names)
           || (argc && null_names))
         usage (1);
       if (opt.filename)
         log_info ("note: ignoring option --set-filename\n");
-      gpgtar_create (null_names? NULL :argv, !skip_crypto);
+      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:
       break;
 
     case aDecrypt:
@@ -282,7 +491,9 @@ main (int argc, char **argv)
       if (files_from)
         log_info ("note: ignoring option --files-from\n");
       fname = argc ? *argv : NULL;
       if (files_from)
         log_info ("note: ignoring option --files-from\n");
       fname = argc ? *argv : NULL;
-      gpgtar_extract (fname, !skip_crypto);
+      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:
       break;
 
     default:
@@ -296,7 +507,7 @@ main (int argc, char **argv)
 
 /* Read the next record from STREAM.  RECORD is a buffer provided by
    the caller and must be at leadt of size RECORDSIZE.  The function
 
 /* 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
    printed as well.  Note that there is no need for an EOF indicator
    because a tarball has an explicit EOF record. */
 gpg_error_t