agent: Avoid appending a '\0' byte to the response of READKEY
[gnupg.git] / tools / gpgtar.c
index 4ea9e73..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
- * 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>
 
-#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/strlist.h"
 
 #include "gpgtar.h"
 
 enum cmd_and_opt_values
   {
     aNull = 0,
+    aCreate = 600,
+    aExtract,
     aEncrypt    = 'e',
     aDecrypt    = 'd',
     aSign       = 's',
+    aList       = 't',
 
     oSymmetric  = 'c',
     oRecipient = 'r',
     oUser       = 'u',
     oOutput    = 'o',
+    oDirectory  = 'C',
     oQuiet      = 'q',
     oVerbose   = 'v',
     oFilesFrom  = 'T',
     oNoVerbose = 500,
 
     aSignEncrypt,
+    oGpgProgram,
     oSkipCrypto,
+    oOpenPGP,
+    oCMS,
     oSetFilename,
-    aList,
-    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")),
 
@@ -84,26 +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 (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_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 )
 {
@@ -111,7 +145,7 @@ 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;
@@ -143,7 +177,7 @@ set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
     cmd = aSignEncrypt;
   else if (cmd == aEncrypt && new_cmd == aSign)
     cmd = aSignEncrypt;
-  else 
+  else
     {
       log_error (_("conflicting commands\n"));
       exit (2);
@@ -151,67 +185,249 @@ set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
 
   *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 (&argc, &argv);
+  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;
 
-        default: pargs.err = 2; break;
+        case oOpenPGP: /* Dummy option for now.  */ break;
+        case oCMS:     /* Dummy option for now.  */ 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, "-"))
@@ -220,6 +436,22 @@ main (int argc, char **argv)
   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:
@@ -230,22 +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");
-      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:
@@ -256,10 +491,9 @@ main (int argc, char **argv)
       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:
@@ -273,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
-   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
@@ -287,10 +521,10 @@ read_record (estream_t stream, void *record)
     {
       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);
     }
@@ -313,18 +547,18 @@ write_record (estream_t stream, const void *record)
   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)
@@ -335,9 +569,9 @@ 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))
@@ -361,31 +595,3 @@ openpgp_message_p (estream_t fp)
   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");
-}