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