diff options
Diffstat (limited to 'tools/gpgtar-create.c')
-rw-r--r-- | tools/gpgtar-create.c | 390 |
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); } |