Imported Upstream version 2.4.3
[platform/upstream/gpg2.git] / tools / gpgtar-list.c
1 /* gpgtar-list.c - List a TAR archive
2  * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH
3  * Copyright (C) 2010, 2012, 2013 Werner Koch
4  * Copyright (C) 2010 Free Software Foundation, Inc.
5  *
6  * This file is part of GnuPG.
7  *
8  * GnuPG is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * GnuPG is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, see <https://www.gnu.org/licenses/>.
20  * SPDX-License-Identifier: GPL-3.0-or-later
21  */
22
23 #include <config.h>
24 #include <errno.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "../common/i18n.h"
30 #include <gpg-error.h>
31 #include "gpgtar.h"
32 #include "../common/exechelp.h"
33 #include "../common/sysutils.h"
34 #include "../common/ccparray.h"
35
36
37 \f
38 static unsigned long long
39 parse_xoctal (const void *data, size_t length, const char *filename)
40 {
41   const unsigned char *p = data;
42   unsigned long long value;
43
44   if (!length)
45     value = 0;
46   else if ( (*p & 0x80))
47     {
48       /* Binary format.  */
49       value = (*p++ & 0x7f);
50       while (--length)
51         {
52           value <<= 8;
53           value |= *p++;
54         }
55     }
56   else
57     {
58       /* Octal format  */
59       value = 0;
60       /* Skip leading spaces and zeroes.  */
61       for (; length && (*p == ' ' || *p == '0'); length--, p++)
62         ;
63       for (; length && *p; length--, p++)
64         {
65           if (*p >= '0' && *p <= '7')
66             {
67               value <<= 3;
68               value += (*p - '0');
69             }
70           else
71             {
72               log_error ("%s: invalid octal number encountered - assuming 0\n",
73                          filename);
74               value = 0;
75               break;
76             }
77         }
78     }
79   return value;
80 }
81
82
83 static tar_header_t
84 parse_header (const void *record, const char *filename, tarinfo_t info)
85 {
86   const struct ustar_raw_header *raw = record;
87   size_t n, namelen, prefixlen;
88   tar_header_t header;
89   int use_prefix;
90   int anyerror = 0;
91
92   info->headerblock = info->nblocks - 1;
93
94   use_prefix = (!memcmp (raw->magic, "ustar", 5)
95                 && (raw->magic[5] == ' ' || !raw->magic[5]));
96
97
98   for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++)
99     ;
100   if (namelen == sizeof raw->name)
101     {
102       log_info ("%s: warning: name not terminated by a nul\n", filename);
103       anyerror = 1;
104     }
105   for (n=namelen+1; n < sizeof raw->name; n++)
106     if (raw->name[n])
107       {
108         log_info ("%s: warning: garbage after name\n", filename);
109         anyerror = 1;
110         break;
111       }
112
113   if (use_prefix && raw->prefix[0])
114     {
115       for (prefixlen=0; (prefixlen < sizeof raw->prefix
116                          && raw->prefix[prefixlen]); prefixlen++)
117         ;
118       if (prefixlen == sizeof raw->prefix)
119         log_info ("%s: warning: prefix not terminated by a nul (block %llu)\n",
120                   filename, info->headerblock);
121       for (n=prefixlen+1; n < sizeof raw->prefix; n++)
122         if (raw->prefix[n])
123           {
124             log_info ("%s: warning: garbage after prefix\n", filename);
125             anyerror = 1;
126             break;
127           }
128     }
129   else
130     prefixlen = 0;
131
132   header = xtrycalloc (1, sizeof *header + prefixlen + 1 + namelen);
133   if (!header)
134     {
135       log_error ("%s: error allocating header: %s\n",
136                  filename, gpg_strerror (gpg_error_from_syserror ()));
137       return NULL;
138     }
139   if (prefixlen)
140     {
141       n = prefixlen;
142       memcpy (header->name, raw->prefix, n);
143       if (raw->prefix[n-1] != '/')
144         header->name[n++] = '/';
145     }
146   else
147     n = 0;
148   memcpy (header->name+n, raw->name, namelen);
149   header->name[n+namelen] = 0;
150
151   header->mode  = parse_xoctal (raw->mode, sizeof raw->mode, filename);
152   header->uid   = parse_xoctal (raw->uid, sizeof raw->uid, filename);
153   header->gid   = parse_xoctal (raw->gid, sizeof raw->gid, filename);
154   header->size  = parse_xoctal (raw->size, sizeof raw->size, filename);
155   header->mtime = parse_xoctal (raw->mtime, sizeof raw->mtime, filename);
156   /* checksum = */
157   switch (raw->typeflag[0])
158     {
159     case '0': header->typeflag = TF_REGULAR; break;
160     case '1': header->typeflag = TF_HARDLINK; break;
161     case '2': header->typeflag = TF_SYMLINK; break;
162     case '3': header->typeflag = TF_CHARDEV; break;
163     case '4': header->typeflag = TF_BLOCKDEV; break;
164     case '5': header->typeflag = TF_DIRECTORY; break;
165     case '6': header->typeflag = TF_FIFO; break;
166     case '7': header->typeflag = TF_RESERVED; break;
167     case 'g': header->typeflag = TF_GEXTHDR; break;
168     case 'x': header->typeflag = TF_EXTHDR; break;
169     default:  header->typeflag = TF_UNKNOWN; break;
170     }
171
172   /* Compute the number of data records following this header.  */
173   if (header->typeflag == TF_REGULAR
174       || header->typeflag == TF_EXTHDR
175       || header->typeflag == TF_UNKNOWN)
176     header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE;
177   else
178     header->nrecords = 0;
179
180   if (anyerror)
181     {
182       log_info ("%s: header block %llu is corrupt"
183                 " (size=%llu type=%d nrec=%llu)\n",
184                 filename, info->headerblock,
185                 header->size, header->typeflag, header->nrecords);
186       /* log_printhex (record, RECORDSIZE, " "); */
187     }
188
189   return header;
190 }
191
192 /* Parse the extended header.  This funcion may modify BUFFER.  */
193 static gpg_error_t
194 parse_extended_header (const char *fname,
195                        char *buffer, size_t buflen, strlist_t *r_exthdr)
196 {
197   unsigned int reclen;
198   unsigned char *p, *record;
199   strlist_t sl;
200
201   while (buflen)
202     {
203       record = buffer; /* Remember begin of record.  */
204       reclen = 0;
205       for (p = buffer; buflen && digitp (p); buflen--, p++)
206         {
207           reclen *= 10;
208           reclen += (*p - '0');
209         }
210       if (!buflen || *p != ' ')
211         {
212           log_error ("%s: malformed record length in extended header\n", fname);
213           return gpg_error (GPG_ERR_INV_RECORD);
214         }
215       p++;  /* Skip space.  */
216       buflen--;
217       if (buflen + (p-record) < reclen)
218         {
219           log_error ("%s: extended header record larger"
220                      " than total extended header data\n", fname);
221           return gpg_error (GPG_ERR_INV_RECORD);
222         }
223       if (reclen < (p-record)+2 || record[reclen-1] != '\n')
224         {
225           log_error ("%s: malformed extended header record\n", fname);
226           return gpg_error (GPG_ERR_INV_RECORD);
227         }
228       record[reclen-1] = 0; /* For convenience change LF to a Nul. */
229       reclen -= (p-record);
230       /* P points to the begin of the keyword and RECLEN is the
231        * remaining length of the record excluding the LF.  */
232       if (memchr (p, 0, reclen-1)
233           && (!strncmp (p, "path=", 5) || !strncmp (p, "linkpath=", 9)))
234         {
235           log_error ("%s: extended header record has an embedded nul"
236                      " - ignoring\n", fname);
237         }
238       else if (!strncmp (p, "path=", 5))
239         {
240           sl = add_to_strlist_try (r_exthdr, p+5);
241           if (!sl)
242             return gpg_error_from_syserror ();
243           sl->flags = 1;  /* Mark as path */
244         }
245       else if (!strncmp (p, "linkpath=", 9))
246         {
247           sl = add_to_strlist_try (r_exthdr, p+9);
248           if (!sl)
249             return gpg_error_from_syserror ();
250           sl->flags = 2;  /* Mark as linkpath */
251         }
252
253       buffer = p + reclen;
254       buflen -= reclen;
255     }
256
257   return 0;
258 }
259
260 \f
261 /* Read the next block, assuming it is a tar header.  Returns a header
262  * object on success in R_HEADER, or an error.  If the stream is
263  * consumed (i.e. end-of-archive), R_HEADER is set to NULL.  In case
264  * of an error an error message is printed.  If the header is an
265  * extended header, a string list is allocated and stored at
266  * R_EXTHEADER; the caller should provide a pointer to NULL.  Such an
267  * extended header is fully processed here and the returned R_HEADER
268  * has then the next regular header.  */
269 static gpg_error_t
270 read_header (estream_t stream, tarinfo_t info,
271              tar_header_t *r_header, strlist_t *r_extheader)
272 {
273   gpg_error_t err;
274   char record[RECORDSIZE];
275   int i;
276   tar_header_t hdr;
277   char *buffer;
278   size_t buflen, nrec;
279
280   err = read_record (stream, record);
281   if (err)
282     return err;
283   info->nblocks++;
284
285   for (i=0; i < RECORDSIZE && !record[i]; i++)
286     ;
287   if (i == RECORDSIZE)
288     {
289       /* All zero header - check whether it is the first part of an
290          end of archive mark.  */
291       err = read_record (stream, record);
292       if (err)
293         return err;
294       info->nblocks++;
295
296       for (i=0; i < RECORDSIZE && !record[i]; i++)
297         ;
298       if (i != RECORDSIZE)
299         log_info ("%s: warning: skipping empty header\n",
300                   es_fname_get (stream));
301       else
302         {
303           /* End of archive - FIXME: we might want to check for garbage.  */
304           *r_header = NULL;
305           return 0;
306         }
307     }
308
309   *r_header = parse_header (record, es_fname_get (stream), info);
310   if (!*r_header)
311     return gpg_error_from_syserror ();
312   hdr = *r_header;
313
314   if (hdr->typeflag != TF_EXTHDR || !r_extheader)
315     return 0;
316
317   /* Read the extended header.  */
318   if (!hdr->nrecords)
319     {
320       /* More than 64k for an extedned header is surely too large.  */
321       log_info ("%s: warning: empty extended header\n",
322                  es_fname_get (stream));
323       return 0;
324     }
325   if (hdr->nrecords > 65536 / RECORDSIZE)
326     {
327       /* More than 64k for an extedned header is surely too large.  */
328       log_error ("%s: extended header too large - skipping\n",
329                  es_fname_get (stream));
330       return 0;
331     }
332
333   buffer = xtrymalloc (hdr->nrecords * RECORDSIZE);
334   if (!buffer)
335     {
336       err = gpg_error_from_syserror ();
337       log_error ("%s: error allocating space for extended header: %s\n",
338                  es_fname_get (stream), gpg_strerror (err));
339       return err;
340     }
341   buflen = 0;
342
343   for (nrec=0; nrec < hdr->nrecords;)
344     {
345       err = read_record (stream, buffer + buflen);
346       if (err)
347         {
348           xfree (buffer);
349           return err;
350         }
351       info->nblocks++;
352       nrec++;
353       if (nrec < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE)))
354         buflen += RECORDSIZE;
355       else
356         buflen += (hdr->size % RECORDSIZE);
357     }
358
359   err = parse_extended_header (es_fname_get (stream),
360                                buffer, buflen, r_extheader);
361   if (err)
362     {
363       free_strlist (*r_extheader);
364       *r_extheader = NULL;
365     }
366
367   xfree (buffer);
368   /* Now tha the extedned header has been read, we read the next
369    * header without allowing an extended header.  */
370   return read_header (stream, info, r_header, NULL);
371 }
372
373
374 /* Skip the data records according to HEADER.  Prints an error message
375    on error and return -1. */
376 static int
377 skip_data (estream_t stream, tarinfo_t info, tar_header_t header)
378 {
379   char record[RECORDSIZE];
380   unsigned long long n;
381
382   for (n=0; n < header->nrecords; n++)
383     {
384       if (read_record (stream, record))
385         return -1;
386       info->nblocks++;
387     }
388
389   return 0;
390 }
391
392
393 \f
394 static void
395 print_header (tar_header_t header, strlist_t extheader, estream_t out)
396 {
397   unsigned long mask;
398   char modestr[10+1];
399   int i;
400   strlist_t sl;
401   const char *name, *linkname;
402
403   *modestr = '?';
404   switch (header->typeflag)
405     {
406     case TF_REGULAR:  *modestr = '-'; break;
407     case TF_HARDLINK: *modestr = 'h'; break;
408     case TF_SYMLINK:  *modestr = 'l'; break;
409     case TF_CHARDEV:  *modestr = 'c'; break;
410     case TF_BLOCKDEV: *modestr = 'b'; break;
411     case TF_DIRECTORY:*modestr = 'd'; break;
412     case TF_FIFO:     *modestr = 'f'; break;
413     case TF_RESERVED: *modestr = '='; break;
414     case TF_EXTHDR:   break;
415     case TF_GEXTHDR:  break;
416     case TF_UNKNOWN:  break;
417     case TF_NOTSUP:   break;
418     }
419   for (mask = 0400, i = 0; i < 9; i++, mask >>= 1)
420     modestr[1+i] = (header->mode & mask)? "rwxrwxrwx"[i]:'-';
421   if ((header->typeflag & 04000))
422     modestr[3] = modestr[3] == 'x'? 's':'S';
423   if ((header->typeflag & 02000))
424     modestr[6] = modestr[6] == 'x'? 's':'S';
425   if ((header->typeflag & 01000))
426     modestr[9] = modestr[9] == 'x'? 't':'T';
427   modestr[10] = 0;
428
429   /* FIXME: We do not parse the linkname unless its part of an
430    * extended header.  */
431   name = header->name;
432   linkname = header->typeflag == TF_SYMLINK? "?" : NULL;
433
434   for (sl = extheader; sl; sl = sl->next)
435     {
436       if (sl->flags == 1)
437         name = sl->d;
438       else if (sl->flags == 2)
439         linkname = sl->d;
440     }
441
442   es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s%s%s\n",
443               modestr, header->nlink, header->uid, header->gid, header->size,
444               isotimestamp (header->mtime),
445               name,
446               linkname? " -> " : "",
447               linkname? linkname : "");
448 }
449
450
451 \f
452 /* List the tarball FILENAME or, if FILENAME is NULL, the tarball read
453    from stdin.  */
454 gpg_error_t
455 gpgtar_list (const char *filename, int decrypt)
456 {
457   gpg_error_t err;
458   estream_t stream = NULL;
459   tar_header_t header = NULL;
460   strlist_t extheader = NULL;
461   struct tarinfo_s tarinfo_buffer;
462   tarinfo_t tarinfo = &tarinfo_buffer;
463   pid_t pid = (pid_t)(-1);
464
465   memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer);
466
467   if (decrypt)
468     {
469       strlist_t arg;
470       ccparray_t ccp;
471       int except[2] = { -1, -1 };
472       const char **argv;
473
474       ccparray_init (&ccp, 0);
475       if (opt.batch)
476         ccparray_put (&ccp, "--batch");
477       if (opt.require_compliance)
478         ccparray_put (&ccp, "--require-compliance");
479       if (opt.status_fd != -1)
480         {
481           static char tmpbuf[40];
482
483           snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%d", opt.status_fd);
484           ccparray_put (&ccp, tmpbuf);
485           except[0] = opt.status_fd;
486         }
487       ccparray_put (&ccp, "--output");
488       ccparray_put (&ccp, "-");
489       ccparray_put (&ccp, "--decrypt");
490       for (arg = opt.gpg_arguments; arg; arg = arg->next)
491         ccparray_put (&ccp, arg->d);
492       if (filename)
493         {
494           ccparray_put (&ccp, "--");
495           ccparray_put (&ccp, filename);
496         }
497
498       ccparray_put (&ccp, NULL);
499       argv = ccparray_get (&ccp, NULL);
500       if (!argv)
501         {
502           err = gpg_error_from_syserror ();
503           goto leave;
504         }
505
506       err = gnupg_spawn_process (opt.gpg_program, argv,
507                                  except[0] == -1? NULL : except,
508                                  ((filename? 0 : GNUPG_SPAWN_KEEP_STDIN)
509                                   | GNUPG_SPAWN_KEEP_STDERR),
510                                  NULL, &stream, NULL, &pid);
511       xfree (argv);
512       if (err)
513         goto leave;
514       es_set_binary (stream);
515     }
516   else if (filename)  /* No decryption requested.  */
517     {
518       if (!strcmp (filename, "-"))
519         stream = es_stdin;
520       else
521         stream = es_fopen (filename, "rb,sysopen");
522       if (!stream)
523         {
524           err = gpg_error_from_syserror ();
525           log_error ("error opening '%s': %s\n", filename, gpg_strerror (err));
526           goto leave;
527         }
528       if (stream == es_stdin)
529         es_set_binary (es_stdin);
530     }
531   else
532     {
533       stream = es_stdin;
534       es_set_binary (es_stdin);
535     }
536
537   for (;;)
538     {
539       err = read_header (stream, tarinfo, &header, &extheader);
540       if (err || header == NULL)
541         goto leave;
542
543       print_header (header, extheader, es_stdout);
544
545       if (skip_data (stream, tarinfo, header))
546         goto leave;
547       free_strlist (extheader);
548       extheader = NULL;
549       xfree (header);
550       header = NULL;
551     }
552
553   if (pid != (pid_t)(-1))
554     {
555       int exitcode;
556
557       err = es_fclose (stream);
558       stream = NULL;
559       if (err)
560         log_error ("error closing pipe: %s\n", gpg_strerror (err));
561       else
562         {
563           err = gnupg_wait_process (opt.gpg_program, pid, 1, &exitcode);
564           if (err)
565             log_error ("running %s failed (exitcode=%d): %s",
566                        opt.gpg_program, exitcode, gpg_strerror (err));
567           gnupg_release_process (pid);
568           pid = (pid_t)(-1);
569         }
570     }
571
572  leave:
573   free_strlist (extheader);
574   xfree (header);
575   if (stream != es_stdin)
576     es_fclose (stream);
577   return err;
578 }
579
580
581 gpg_error_t
582 gpgtar_read_header (estream_t stream, tarinfo_t info,
583                     tar_header_t *r_header, strlist_t *r_extheader)
584 {
585   return read_header (stream, info, r_header, r_extheader);
586 }
587
588 void
589 gpgtar_print_header (tar_header_t header, strlist_t extheader, estream_t out)
590 {
591   if (header && out)
592     print_header (header, extheader, out);
593 }