summaryrefslogtreecommitdiff
path: root/tools/gpgtar-create.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gpgtar-create.c')
-rw-r--r--tools/gpgtar-create.c390
1 files changed, 306 insertions, 84 deletions
diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c
index f1b0485..e642da0 100644
--- a/tools/gpgtar-create.c
+++ b/tools/gpgtar-create.c
@@ -1,4 +1,6 @@
/* gpgtar-create.c - Create a TAR archive
+ * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH
+ * Copyright (C) 2010, 2012, 2013 Werner Koch
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
@@ -15,6 +17,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
@@ -25,20 +28,21 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
+#include <unistd.h>
#ifdef HAVE_W32_SYSTEM
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else /*!HAVE_W32_SYSTEM*/
-# include <unistd.h>
# include <pwd.h>
# include <grp.h>
#endif /*!HAVE_W32_SYSTEM*/
-#include <assert.h>
#include "../common/i18n.h"
-#include "../common/exectool.h"
+#include <gpg-error.h>
+#include "../common/exechelp.h"
#include "../common/sysutils.h"
#include "../common/ccparray.h"
+#include "../common/membuf.h"
#include "gpgtar.h"
#ifndef HAVE_LSTAT
@@ -46,6 +50,11 @@
#endif
+/* Count the number of written headers. Extended headers are not
+ * counted. */
+static unsigned long global_header_count;
+
+
/* Object to control the file scanning. */
struct scanctrl_s;
typedef struct scanctrl_s *scanctrl_t;
@@ -105,7 +114,7 @@ fillup_entry_w32 (tar_header_t hdr)
for (p=hdr->name; *p; p++)
if (*p == '/')
*p = '\\';
- wfname = utf8_to_wchar (hdr->name);
+ wfname = gpgrt_fname_to_wchar (hdr->name);
for (p=hdr->name; *p; p++)
if (*p == '\\')
*p = '/';
@@ -285,8 +294,10 @@ add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl)
xfree (hdr);
else
{
+ /* FIXME: We don't have the extended info yet available so we
+ * can't print them. */
if (opt.verbose)
- gpgtar_print_header (hdr, log_get_stream ());
+ gpgtar_print_header (hdr, NULL, log_get_stream ());
*scanctrl->flist_tail = hdr;
scanctrl->flist_tail = &hdr->next;
}
@@ -334,7 +345,7 @@ scan_directory (const char *dname, scanctrl_t scanctrl)
for (p=fname; *p; p++)
if (*p == '/')
*p = '\\';
- wfname = utf8_to_wchar (fname);
+ wfname = gpgrt_fname_to_wchar (fname);
xfree (fname);
if (!wfname)
{
@@ -437,7 +448,7 @@ scan_recursive (const char *dname, scanctrl_t scanctrl)
}
scanctrl->nestlevel++;
- assert (scanctrl->flist_tail);
+ log_assert (scanctrl->flist_tail);
start_tail = scanctrl->flist_tail;
scan_directory (dname, scanctrl);
stop_tail = scanctrl->flist_tail;
@@ -488,7 +499,7 @@ store_xoctal (char *buffer, size_t length, unsigned long long value)
size_t n;
unsigned long long v;
- assert (length > 1);
+ log_assert (length > 1);
v = value;
n = length;
@@ -593,16 +604,75 @@ store_gname (char *buffer, size_t length, unsigned long gid)
}
+static void
+compute_checksum (void *record)
+{
+ struct ustar_raw_header *raw = record;
+ unsigned long chksum = 0;
+ unsigned char *p;
+ size_t n;
+
+ memset (raw->checksum, ' ', sizeof raw->checksum);
+ p = record;
+ for (n=0; n < RECORDSIZE; n++)
+ chksum += *p++;
+ store_xoctal (raw->checksum, sizeof raw->checksum - 1, chksum);
+ raw->checksum[7] = ' ';
+}
+
+
+
+/* Read a symlink without truncating it. Caller must release the
+ * returned buffer. Returns NULL on error. */
+#ifndef HAVE_W32_SYSTEM
+static char *
+myreadlink (const char *name)
+{
+ char *buffer;
+ size_t size;
+ int nread;
+
+ for (size = 1024; size <= 65536; size *= 2)
+ {
+ buffer = xtrymalloc (size);
+ if (!buffer)
+ return NULL;
+
+ nread = readlink (name, buffer, size - 1);
+ if (nread < 0)
+ {
+ xfree (buffer);
+ return NULL;
+ }
+ if (nread < size - 1)
+ {
+ buffer[nread] = 0;
+ return buffer; /* Got it. */
+ }
+
+ xfree (buffer);
+ }
+ gpg_err_set_errno (ERANGE);
+ return NULL;
+}
+#endif /*Unix*/
+
+
+
+/* Build a header. If the filename or the link name ist too long
+ * allocate an exthdr and use a replacement file name in RECORD.
+ * Caller should always release R_EXTHDR; this function initializes it
+ * to point to NULL. */
static gpg_error_t
-build_header (void *record, tar_header_t hdr)
+build_header (void *record, tar_header_t hdr, strlist_t *r_exthdr)
{
gpg_error_t err;
struct ustar_raw_header *raw = record;
size_t namelen, n;
- unsigned long chksum;
- unsigned char *p;
+ strlist_t sl;
memset (record, 0, RECORDSIZE);
+ *r_exthdr = NULL;
/* Store name and prefix. */
namelen = strlen (hdr->name);
@@ -623,10 +693,23 @@ build_header (void *record, tar_header_t hdr)
}
else
{
- err = gpg_error (GPG_ERR_TOO_LARGE);
- log_error ("error storing file '%s': %s\n",
- hdr->name, gpg_strerror (err));
- return err;
+ /* Too long - prepare extended header. */
+ sl = add_to_strlist_try (r_exthdr, hdr->name);
+ if (!sl)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error storing file '%s': %s\n",
+ hdr->name, gpg_strerror (err));
+ return err;
+ }
+ sl->flags = 1; /* Mark as path */
+ /* The name we use is not POSIX compliant but because we
+ * expect that (for security issues) a tarball will anyway
+ * be extracted to a unique new directory, a simple counter
+ * will do. To ease testing we also put in the PID. The
+ * count is bumped after the header has been written. */
+ snprintf (raw->name, sizeof raw->name-1, "_@paxheader.%u.%lu",
+ (unsigned int)getpid(), global_header_count + 1);
}
}
@@ -659,6 +742,7 @@ build_header (void *record, tar_header_t hdr)
if (hdr->typeflag == TF_SYMLINK)
{
int nread;
+ char *p;
nread = readlink (hdr->name, raw->linkname, sizeof raw->linkname -1);
if (nread < 0)
@@ -669,22 +753,133 @@ build_header (void *record, tar_header_t hdr)
return err;
}
raw->linkname[nread] = 0;
+ if (nread == sizeof raw->linkname -1)
+ {
+ /* Truncated - read again and store as extended header. */
+ p = myreadlink (hdr->name);
+ if (!p)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading symlink '%s': %s\n",
+ hdr->name, gpg_strerror (err));
+ return err;
+ }
+
+ sl = add_to_strlist_try (r_exthdr, p);
+ xfree (p);
+ if (!sl)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error storing syslink '%s': %s\n",
+ hdr->name, gpg_strerror (err));
+ return err;
+ }
+ sl->flags = 2; /* Mark as linkpath */
+ }
}
-#endif /*HAVE_W32_SYSTEM*/
+#endif /*!HAVE_W32_SYSTEM*/
- /* Compute the checksum. */
- memset (raw->checksum, ' ', sizeof raw->checksum);
- chksum = 0;
- p = record;
- for (n=0; n < RECORDSIZE; n++)
- chksum += *p++;
- store_xoctal (raw->checksum, sizeof raw->checksum - 1, chksum);
- raw->checksum[7] = ' ';
+ compute_checksum (record);
return 0;
}
+/* Add an extended header record (NAME,VALUE) to the buffer MB. */
+static void
+add_extended_header_record (membuf_t *mb, const char *name, const char *value)
+{
+ size_t n, n0, n1;
+ char numbuf[35];
+ size_t valuelen;
+
+ /* To avoid looping in most cases, we guess the initial value. */
+ valuelen = strlen (value);
+ n1 = valuelen > 95? 3 : 2;
+ do
+ {
+ n0 = n1;
+ /* (3 for the space before name, the '=', and the LF.) */
+ n = n0 + strlen (name) + valuelen + 3;
+ snprintf (numbuf, sizeof numbuf, "%zu", n);
+ n1 = strlen (numbuf);
+ }
+ while (n0 != n1);
+ put_membuf_str (mb, numbuf);
+ put_membuf (mb, " ", 1);
+ put_membuf_str (mb, name);
+ put_membuf (mb, "=", 1);
+ put_membuf (mb, value, valuelen);
+ put_membuf (mb, "\n", 1);
+}
+
+
+
+/* Write the extended header specified by EXTHDR to STREAM. */
+static gpg_error_t
+write_extended_header (estream_t stream, const void *record, strlist_t exthdr)
+{
+ gpg_error_t err = 0;
+ struct ustar_raw_header raw;
+ strlist_t sl;
+ membuf_t mb;
+ char *buffer, *p;
+ size_t buflen;
+
+ init_membuf (&mb, 2*RECORDSIZE);
+
+ for (sl=exthdr; sl; sl = sl->next)
+ {
+ if (sl->flags == 1)
+ add_extended_header_record (&mb, "path", sl->d);
+ else if (sl->flags == 2)
+ add_extended_header_record (&mb, "linkpath", sl->d);
+ }
+
+ buffer = get_membuf (&mb, &buflen);
+ if (!buffer)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error building extended header: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ /* We copy the header from the standard header record, so that an
+ * extracted extended header (using a non-pax aware software) is
+ * written with the same properties as the original file. The real
+ * entry will overwrite it anyway. Of course we adjust the size and
+ * the type. */
+ memcpy (&raw, record, RECORDSIZE);
+ store_xoctal (raw.size, sizeof raw.size, buflen);
+ raw.typeflag[0] = 'x'; /* Mark as extended header. */
+ compute_checksum (&raw);
+
+ err = write_record (stream, &raw);
+ if (err)
+ goto leave;
+
+ for (p = buffer; buflen >= RECORDSIZE; p += RECORDSIZE, buflen -= RECORDSIZE)
+ {
+ err = write_record (stream, p);
+ if (err)
+ goto leave;
+ }
+ if (buflen)
+ {
+ /* Reuse RAW for builidng the last record. */
+ memcpy (&raw, p, buflen);
+ memset ((char*)&raw+buflen, 0, RECORDSIZE - buflen);
+ err = write_record (stream, &raw);
+ if (err)
+ goto leave;
+ }
+
+ leave:
+ xfree (buffer);
+ return err;
+}
+
+
static gpg_error_t
write_file (estream_t stream, tar_header_t hdr)
{
@@ -692,9 +887,10 @@ write_file (estream_t stream, tar_header_t hdr)
char record[RECORDSIZE];
estream_t infp;
size_t nread, nbytes;
+ strlist_t exthdr = NULL;
int any;
- err = build_header (record, hdr);
+ err = build_header (record, hdr, &exthdr);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
@@ -707,7 +903,7 @@ write_file (estream_t stream, tar_header_t hdr)
if (hdr->typeflag == TF_REGULAR)
{
- infp = es_fopen (hdr->name, "rb");
+ infp = es_fopen (hdr->name, "rb,sysopen");
if (!infp)
{
err = gpg_error_from_syserror ();
@@ -719,9 +915,12 @@ write_file (estream_t stream, tar_header_t hdr)
else
infp = NULL;
+ if (exthdr && (err = write_extended_header (stream, record, exthdr)))
+ goto leave;
err = write_record (stream, record);
if (err)
goto leave;
+ global_header_count++;
if (hdr->typeflag == TF_REGULAR)
{
@@ -741,6 +940,8 @@ write_file (estream_t stream, tar_header_t hdr)
any? " (file shrunk?)":"");
goto leave;
}
+ else if (nbytes < RECORDSIZE)
+ memset (record + nbytes, 0, RECORDSIZE - nbytes);
any = 1;
err = write_record (stream, record);
if (err)
@@ -757,6 +958,7 @@ write_file (estream_t stream, tar_header_t hdr)
else if ((err = es_fclose (infp)))
log_error ("error closing file '%s': %s\n", hdr->name, gpg_strerror (err));
+ free_strlist (exthdr);
return err;
}
@@ -791,8 +993,8 @@ gpgtar_create (char **inpattern, const char *files_from, int null_names,
tar_header_t hdr, *start_tail;
estream_t files_from_stream = NULL;
estream_t outstream = NULL;
- estream_t cipher_stream = NULL;
int eof_seen = 0;
+ pid_t pid = (pid_t)(-1);
memset (scanctrl, 0, sizeof *scanctrl);
scanctrl->flist_tail = &scanctrl->flist;
@@ -945,64 +1147,37 @@ gpgtar_create (char **inpattern, const char *files_from, int null_names,
if (files_from_stream && files_from_stream != es_stdin)
es_fclose (files_from_stream);
- if (opt.outfile)
- {
- if (!strcmp (opt.outfile, "-"))
- outstream = es_stdout;
- else
- outstream = es_fopen (opt.outfile, "wb");
- if (!outstream)
- {
- err = gpg_error_from_syserror ();
- goto leave;
- }
- }
- else
- {
- outstream = es_stdout;
- }
-
- if (outstream == es_stdout)
- es_set_binary (es_stdout);
-
- if (encrypt || sign)
- {
- cipher_stream = outstream;
- outstream = es_fopenmem (0, "rwb");
- if (! outstream)
- {
- err = gpg_error_from_syserror ();
- goto leave;
- }
- }
-
- for (hdr = scanctrl->flist; hdr; hdr = hdr->next)
- {
- err = write_file (outstream, hdr);
- if (err)
- goto leave;
- }
- err = write_eof_mark (outstream);
- if (err)
- goto leave;
-
if (encrypt || sign)
{
strlist_t arg;
ccparray_t ccp;
const char **argv;
- err = es_fseek (outstream, 0, SEEK_SET);
- if (err)
- goto leave;
-
/* '--encrypt' may be combined with '--symmetric', but 'encrypt'
- is set either way. Clear it if no recipients are specified.
- XXX: Fix command handling. */
+ * is set either way. Clear it if no recipients are specified.
+ */
if (opt.symmetric && opt.recipients == NULL)
encrypt = 0;
ccparray_init (&ccp, 0);
+ if (opt.batch)
+ ccparray_put (&ccp, "--batch");
+ if (opt.answer_yes)
+ ccparray_put (&ccp, "--yes");
+ if (opt.answer_no)
+ ccparray_put (&ccp, "--no");
+ if (opt.require_compliance)
+ ccparray_put (&ccp, "--require-compliance");
+ if (opt.status_fd != -1)
+ {
+ static char tmpbuf[40];
+
+ snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%d", opt.status_fd);
+ ccparray_put (&ccp, tmpbuf);
+ }
+
+ ccparray_put (&ccp, "--output");
+ ccparray_put (&ccp, opt.outfile? opt.outfile : "-");
if (encrypt)
ccparray_put (&ccp, "--encrypt");
if (sign)
@@ -1030,27 +1205,76 @@ gpgtar_create (char **inpattern, const char *files_from, int null_names,
goto leave;
}
- err = gnupg_exec_tool_stream (opt.gpg_program, argv,
- outstream, NULL, cipher_stream, NULL, NULL);
+ err = gnupg_spawn_process (opt.gpg_program, argv, NULL, NULL,
+ (GNUPG_SPAWN_KEEP_STDOUT
+ | GNUPG_SPAWN_KEEP_STDERR),
+ &outstream, NULL, NULL, &pid);
xfree (argv);
if (err)
goto leave;
+ es_set_binary (outstream);
+ }
+ else if (opt.outfile) /* No crypto */
+ {
+ if (!strcmp (opt.outfile, "-"))
+ outstream = es_stdout;
+ else
+ outstream = es_fopen (opt.outfile, "wb,sysopen");
+ if (!outstream)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ if (outstream == es_stdout)
+ es_set_binary (es_stdout);
+
+ }
+ else /* Also no crypto. */
+ {
+ outstream = es_stdout;
+ es_set_binary (outstream);
+ }
+
+
+ for (hdr = scanctrl->flist; hdr; hdr = hdr->next)
+ {
+ err = write_file (outstream, hdr);
+ if (err)
+ goto leave;
+ }
+ err = write_eof_mark (outstream);
+ if (err)
+ goto leave;
+
+
+ if (pid != (pid_t)(-1))
+ {
+ int exitcode;
+
+ err = es_fclose (outstream);
+ outstream = NULL;
+ if (err)
+ log_error ("error closing pipe: %s\n", gpg_strerror (err));
+ else
+ {
+ err = gnupg_wait_process (opt.gpg_program, pid, 1, &exitcode);
+ if (err)
+ log_error ("running %s failed (exitcode=%d): %s",
+ opt.gpg_program, exitcode, gpg_strerror (err));
+ gnupg_release_process (pid);
+ pid = (pid_t)(-1);
+ }
}
leave:
if (!err)
{
gpg_error_t first_err;
- if (outstream != es_stdout)
+ if (outstream != es_stdout || pid != (pid_t)(-1))
first_err = es_fclose (outstream);
else
first_err = es_fflush (outstream);
outstream = NULL;
- if (cipher_stream != es_stdout)
- err = es_fclose (cipher_stream);
- else
- err = es_fflush (cipher_stream);
- cipher_stream = NULL;
if (! err)
err = first_err;
}
@@ -1060,8 +1284,6 @@ gpgtar_create (char **inpattern, const char *files_from, int null_names,
opt.outfile ? opt.outfile : "-", gpg_strerror (err));
if (outstream && outstream != es_stdout)
es_fclose (outstream);
- if (cipher_stream && cipher_stream != es_stdout)
- es_fclose (cipher_stream);
if (opt.outfile)
gnupg_remove (opt.outfile);
}