tools/gpgtar: Add '--dry-run'.
[gnupg.git] / tools / gpgtar.c
1 /* gpgtar.c - A simple TAR implementation mainly useful for Windows.
2  * Copyright (C) 2010 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /* GnuPG comes with a shell script gpg-zip which creates archive files
21    in the same format as PGP Zip, which is actually a USTAR format.
22    That is fine and works nicely on all Unices but for Windows we
23    don't have a compatible shell and the supply of tar programs is
24    limited.  Given that we need just a few tar option and it is an
25    open question how many Unix concepts are to be mapped to Windows,
26    we might as well write our own little tar customized for use with
27    gpg.  So here we go.  */
28
29 #include <config.h>
30 #include <assuan.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <npth.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <assert.h>
38
39 #include "util.h"
40 #include "i18n.h"
41 #include "sysutils.h"
42 #include "../common/asshelp.h"
43 #include "../common/openpgpdefs.h"
44 #include "../common/init.h"
45 #include "../common/strlist.h"
46
47 #include "gpgtar.h"
48
49
50 /* Constants to identify the commands and options. */
51 enum cmd_and_opt_values
52   {
53     aNull = 0,
54     aEncrypt    = 'e',
55     aDecrypt    = 'd',
56     aSign       = 's',
57     aList       = 't',
58
59     oSymmetric  = 'c',
60     oRecipient  = 'r',
61     oUser       = 'u',
62     oOutput     = 'o',
63     oDirectory  = 'C',
64     oQuiet      = 'q',
65     oVerbose    = 'v',
66     oFilesFrom  = 'T',
67     oNoVerbose  = 500,
68
69     aSignEncrypt,
70     oGpgProgram,
71     oSkipCrypto,
72     oOpenPGP,
73     oCMS,
74     oSetFilename,
75     oNull,
76
77     /* Compatibility with gpg-zip.  */
78     oGpgArgs,
79     oTarArgs,
80
81     /* Debugging.  */
82     oDryRun,
83   };
84
85
86 /* The list of commands and options. */
87 static ARGPARSE_OPTS opts[] = {
88   ARGPARSE_group (300, N_("@Commands:\n ")),
89
90   ARGPARSE_c (aEncrypt,   "encrypt", N_("create an archive")),
91   ARGPARSE_c (aDecrypt,   "decrypt", N_("extract an archive")),
92   ARGPARSE_c (aSign,      "sign",    N_("create a signed archive")),
93   ARGPARSE_c (aList,      "list-archive", N_("list an archive")),
94
95   ARGPARSE_group (301, N_("@\nOptions:\n ")),
96
97   ARGPARSE_s_n (oSymmetric, "symmetric", N_("use symmetric encryption")),
98   ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
99   ARGPARSE_s_s (oUser, "local-user",
100                 N_("|USER-ID|use USER-ID to sign or decrypt")),
101   ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
102   ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
103   ARGPARSE_s_n (oQuiet, "quiet",  N_("be somewhat more quiet")),
104   ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
105   ARGPARSE_s_n (oSkipCrypto, "skip-crypto", N_("skip the crypto processing")),
106   ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
107   ARGPARSE_s_s (oSetFilename, "set-filename", "@"),
108   ARGPARSE_s_n (oOpenPGP, "openpgp", "@"),
109   ARGPARSE_s_n (oCMS, "cms", "@"),
110
111   ARGPARSE_group (302, N_("@\nTar options:\n ")),
112
113   ARGPARSE_s_s (oDirectory, "directory",
114                 N_("|DIRECTORY|extract files into DIRECTORY")),
115   ARGPARSE_s_s (oFilesFrom, "files-from",
116                 N_("|FILE|get names to create from FILE")),
117   ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")),
118
119   ARGPARSE_s_s (oGpgArgs, "gpg-args", "@"),
120   ARGPARSE_s_s (oTarArgs, "tar-args", "@"),
121
122   ARGPARSE_end ()
123 };
124
125
126 /* The list of commands and options for tar that we understand. */
127 static ARGPARSE_OPTS tar_opts[] = {
128   ARGPARSE_s_s (oDirectory, "directory",
129                 N_("|DIRECTORY|extract files into DIRECTORY")),
130   ARGPARSE_s_s (oFilesFrom, "files-from",
131                 N_("|FILE|get names to create from FILE")),
132   ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")),
133
134   ARGPARSE_end ()
135 };
136
137
138 \f
139 /* Print usage information and and provide strings for help. */
140 static const char *
141 my_strusage( int level )
142 {
143   const char *p;
144
145   switch (level)
146     {
147     case 11: p = "@GPGTAR@ (@GNUPG@)";
148       break;
149     case 13: p = VERSION; break;
150     case 17: p = PRINTABLE_OS_NAME; break;
151     case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
152
153     case 1:
154     case 40:
155       p = _("Usage: gpgtar [options] [files] [directories] (-h for help)");
156       break;
157     case 41:
158       p = _("Syntax: gpgtar [options] [files] [directories]\n"
159             "Encrypt or sign files into an archive\n");
160       break;
161
162     default: p = NULL; break;
163     }
164   return p;
165 }
166
167
168 static void
169 set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
170 {
171   enum cmd_and_opt_values cmd = *ret_cmd;
172
173   if (!cmd || cmd == new_cmd)
174     cmd = new_cmd;
175   else if (cmd == aSign && new_cmd == aEncrypt)
176     cmd = aSignEncrypt;
177   else if (cmd == aEncrypt && new_cmd == aSign)
178     cmd = aSignEncrypt;
179   else
180     {
181       log_error (_("conflicting commands\n"));
182       exit (2);
183     }
184
185   *ret_cmd = cmd;
186 }
187 \f
188 /* Shell-like argument splitting.
189
190    For compatibility with gpg-zip we accept arguments for GnuPG and
191    tar given as a string argument to '--gpg-args' and '--tar-args'.
192    gpg-zip was implemented as a Bourne Shell script, and therefore, we
193    need to split the string the same way the shell would.  */
194 static int
195 shell_parse_stringlist (const char *str, strlist_t *r_list)
196 {
197   strlist_t list = NULL;
198   const char *s = str;
199   char quoted = 0;
200   char arg[1024];
201   char *p = arg;
202 #define addchar(c) \
203   do { if (p - arg + 2 < sizeof arg) *p++ = (c); else return 1; } while (0)
204 #define addargument()                           \
205   do {                                          \
206     if (p > arg)                                \
207       {                                         \
208         *p = 0;                                 \
209         append_to_strlist (&list, arg);         \
210         p = arg;                                \
211       }                                         \
212   } while (0)
213
214 #define unquoted        0
215 #define singlequote     '\''
216 #define doublequote     '"'
217
218   for (; *s; s++)
219     {
220       switch (quoted)
221         {
222         case unquoted:
223           if (isspace (*s))
224             addargument ();
225           else if (*s == singlequote || *s == doublequote)
226             quoted = *s;
227           else
228             addchar (*s);
229           break;
230
231         case singlequote:
232           if (*s == singlequote)
233             quoted = unquoted;
234           else
235             addchar (*s);
236           break;
237
238         case doublequote:
239           assert (s > str || !"cannot be quoted at first char");
240           if (*s == doublequote && *(s - 1) != '\\')
241             quoted = unquoted;
242           else
243             addchar (*s);
244           break;
245
246         default:
247           assert (! "reached");
248         }
249     }
250
251   /* Append the last argument.  */
252   addargument ();
253
254 #undef doublequote
255 #undef singlequote
256 #undef unquoted
257 #undef addargument
258 #undef addchar
259   *r_list = list;
260   return 0;
261 }
262
263
264 /* Like shell_parse_stringlist, but returns an argv vector
265    instead of a strlist.  */
266 static int
267 shell_parse_argv (const char *s, int *r_argc, char ***r_argv)
268 {
269   int i;
270   strlist_t list;
271
272   if (shell_parse_stringlist (s, &list))
273     return 1;
274
275   *r_argc = strlist_length (list);
276   *r_argv = xtrycalloc (*r_argc, sizeof **r_argv);
277   if (*r_argv == NULL)
278     return 1;
279
280   for (i = 0; list; i++)
281     (*r_argv)[i] = list->d, list = list->next;
282   return 0;
283 }
284 \f
285 /* Define Assuan hooks for NPTH.  */
286
287 ASSUAN_SYSTEM_NPTH_IMPL;
288
289 \f
290 /* Global flags.  */
291 enum cmd_and_opt_values cmd = 0;
292 int skip_crypto = 0;
293 const char *files_from = NULL;
294 int null_names = 0;
295
296
297 /* Command line parsing.  */
298 static void
299 parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
300 {
301   int no_more_options = 0;
302
303   while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
304     {
305       switch (pargs->r_opt)
306         {
307         case oOutput:    opt.outfile = pargs->r.ret_str; break;
308         case oDirectory: opt.directory = pargs->r.ret_str; break;
309         case oSetFilename: opt.filename = pargs->r.ret_str; break;
310         case oQuiet:     opt.quiet = 1; break;
311         case oVerbose:   opt.verbose++; break;
312         case oNoVerbose: opt.verbose = 0; break;
313         case oFilesFrom: files_from = pargs->r.ret_str; break;
314         case oNull: null_names = 1; break;
315
316         case aList:
317         case aDecrypt:
318         case aEncrypt:
319         case aSign:
320           set_cmd (&cmd, pargs->r_opt);
321           break;
322
323         case oRecipient:
324           add_to_strlist (&opt.recipients, pargs->r.ret_str);
325           break;
326
327         case oUser:
328           log_info ("note: ignoring option --user\n");
329           opt.user = pargs->r.ret_str;
330           break;
331
332         case oSymmetric:
333           log_info ("note: ignoring option --symmetric\n");
334           set_cmd (&cmd, aEncrypt);
335           opt.symmetric = 1;
336           break;
337
338         case oGpgProgram:
339           opt.gpg_program = pargs->r.ret_str;
340           break;
341
342         case oSkipCrypto:
343           skip_crypto = 1;
344           break;
345
346         case oOpenPGP: /* Dummy option for now.  */ break;
347         case oCMS:     /* Dummy option for now.  */ break;
348
349         case oGpgArgs:;
350           strlist_t list;
351           if (shell_parse_stringlist (pargs->r.ret_str, &list))
352             log_error ("failed to parse gpg arguments '%s'\n",
353                        pargs->r.ret_str);
354           else
355             {
356               if (opt.gpg_arguments)
357                 strlist_last (opt.gpg_arguments)->next = list;
358               else
359                 opt.gpg_arguments = list;
360             }
361           break;
362
363         case oTarArgs:;
364           int tar_argc;
365           char **tar_argv;
366
367           if (shell_parse_argv (pargs->r.ret_str, &tar_argc, &tar_argv))
368             log_error ("failed to parse tar arguments '%s'\n",
369                        pargs->r.ret_str);
370           else
371             {
372               ARGPARSE_ARGS tar_args;
373               tar_args.argc = &tar_argc;
374               tar_args.argv = &tar_argv;
375               tar_args.flags = ARGPARSE_FLAG_ARG0;
376               parse_arguments (&tar_args, tar_opts);
377               if (tar_args.err)
378                 log_error ("unsupported tar arguments '%s'\n",
379                            pargs->r.ret_str);
380               pargs->err = tar_args.err;
381             }
382           break;
383
384         case oDryRun:
385           opt.dry_run = 1;
386           break;
387
388         default: pargs->err = 2; break;
389         }
390     }
391 }
392
393 \f
394 /* gpgtar main. */
395 int
396 main (int argc, char **argv)
397 {
398   gpg_error_t err;
399   const char *fname;
400   ARGPARSE_ARGS pargs;
401
402   assert (sizeof (struct ustar_raw_header) == 512);
403
404   gnupg_reopen_std (GPGTAR_NAME);
405   set_strusage (my_strusage);
406   log_set_prefix (GPGTAR_NAME, 1);
407
408   /* Make sure that our subsystems are ready.  */
409   i18n_init();
410   init_common_subsystems (&argc, &argv);
411   npth_init ();
412   assuan_set_assuan_log_prefix (log_get_prefix (NULL));
413   assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
414   assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
415   assuan_sock_init ();
416
417   /* Parse the command line. */
418   pargs.argc  = &argc;
419   pargs.argv  = &argv;
420   pargs.flags = ARGPARSE_FLAG_KEEP;
421   parse_arguments (&pargs, opts);
422
423   if ((files_from && !null_names) || (!files_from && null_names))
424     log_error ("--files-from and --null may only be used in conjunction\n");
425   if (files_from && strcmp (files_from, "-"))
426     log_error ("--files-from only supports argument \"-\"\n");
427
428   if (log_get_errorcount (0))
429     exit (2);
430
431   /* Print a warning if an argument looks like an option.  */
432   if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
433     {
434       int i;
435
436       for (i=0; i < argc; i++)
437         if (argv[i][0] == '-' && argv[i][1] == '-')
438           log_info (_("NOTE: '%s' is not considered an option\n"), argv[i]);
439     }
440
441   if (opt.verbose > 1)
442     opt.debug_level = 1024;
443   setup_libassuan_logging (&opt.debug_level);
444
445   switch (cmd)
446     {
447     case aList:
448       if (argc > 1)
449         usage (1);
450       fname = argc ? *argv : NULL;
451       if (opt.filename)
452         log_info ("note: ignoring option --set-filename\n");
453       if (files_from)
454         log_info ("note: ignoring option --files-from\n");
455       err = gpgtar_list (fname, !skip_crypto);
456       if (err && log_get_errorcount (0) == 0)
457         log_error ("listing archive failed: %s\n", gpg_strerror (err));
458       break;
459
460     case aEncrypt:
461       if ((!argc && !null_names)
462           || (argc && null_names))
463         usage (1);
464       if (opt.filename)
465         log_info ("note: ignoring option --set-filename\n");
466       err = gpgtar_create (null_names? NULL :argv, !skip_crypto);
467       if (err && log_get_errorcount (0) == 0)
468         log_error ("creating archive failed: %s\n", gpg_strerror (err));
469       break;
470
471     case aDecrypt:
472       if (argc != 1)
473         usage (1);
474       if (opt.outfile)
475         log_info ("note: ignoring option --output\n");
476       if (files_from)
477         log_info ("note: ignoring option --files-from\n");
478       fname = argc ? *argv : NULL;
479       err = gpgtar_extract (fname, !skip_crypto);
480       if (err && log_get_errorcount (0) == 0)
481         log_error ("extracting archive failed: %s\n", gpg_strerror (err));
482       break;
483
484     default:
485       log_error (_("invalid command (there is no implicit command)\n"));
486       break;
487     }
488
489   return log_get_errorcount (0)? 1:0;
490 }
491
492
493 /* Read the next record from STREAM.  RECORD is a buffer provided by
494    the caller and must be at leadt of size RECORDSIZE.  The function
495    return 0 on success and and error code on failure; a diagnostic
496    printed as well.  Note that there is no need for an EOF indicator
497    because a tarball has an explicit EOF record. */
498 gpg_error_t
499 read_record (estream_t stream, void *record)
500 {
501   gpg_error_t err;
502   size_t nread;
503
504   nread = es_fread (record, 1, RECORDSIZE, stream);
505   if (nread != RECORDSIZE)
506     {
507       err = gpg_error_from_syserror ();
508       if (es_ferror (stream))
509         log_error ("error reading '%s': %s\n",
510                    es_fname_get (stream), gpg_strerror (err));
511       else
512         log_error ("error reading '%s': premature EOF "
513                    "(size of last record: %zu)\n",
514                    es_fname_get (stream), nread);
515     }
516   else
517     err = 0;
518
519   return err;
520 }
521
522
523 /* Write the RECORD of size RECORDSIZE to STREAM.  FILENAME is the
524    name of the file used for diagnostics.  */
525 gpg_error_t
526 write_record (estream_t stream, const void *record)
527 {
528   gpg_error_t err;
529   size_t nwritten;
530
531   nwritten = es_fwrite (record, 1, RECORDSIZE, stream);
532   if (nwritten != RECORDSIZE)
533     {
534       err = gpg_error_from_syserror ();
535       log_error ("error writing '%s': %s\n",
536                  es_fname_get (stream), gpg_strerror (err));
537     }
538   else
539     err = 0;
540
541   return err;
542 }
543
544
545 /* Return true if FP is an unarmored OpenPGP message.  Note that this
546    function reads a few bytes from FP but pushes them back.  */
547 #if 0
548 static int
549 openpgp_message_p (estream_t fp)
550 {
551   int ctb;
552
553   ctb = es_getc (fp);
554   if (ctb != EOF)
555     {
556       if (es_ungetc (ctb, fp))
557         log_fatal ("error ungetting first byte: %s\n",
558                    gpg_strerror (gpg_error_from_syserror ()));
559
560       if ((ctb & 0x80))
561         {
562           switch ((ctb & 0x40) ? (ctb & 0x3f) : ((ctb>>2)&0xf))
563             {
564             case PKT_MARKER:
565             case PKT_SYMKEY_ENC:
566             case PKT_ONEPASS_SIG:
567             case PKT_PUBKEY_ENC:
568             case PKT_SIGNATURE:
569             case PKT_COMMENT:
570             case PKT_OLD_COMMENT:
571             case PKT_PLAINTEXT:
572             case PKT_COMPRESSED:
573             case PKT_ENCRYPTED:
574               return 1; /* Yes, this seems to be an OpenPGP message.  */
575             default:
576               break;
577             }
578         }
579     }
580   return 0;
581 }
582 #endif