diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c
index 3a7687d9f..f52295b5c 100644
--- a/tools/gpgtar-create.c
+++ b/tools/gpgtar-create.c
@@ -1,1287 +1,1290 @@
/* 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.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_W32_SYSTEM
# define WIN32_LEAN_AND_MEAN
# include
#else /*!HAVE_W32_SYSTEM*/
# include
# include
#endif /*!HAVE_W32_SYSTEM*/
#include "../common/i18n.h"
#include
#include "../common/exechelp.h"
#include "../common/sysutils.h"
#include "../common/ccparray.h"
#include "../common/membuf.h"
#include "gpgtar.h"
#ifndef HAVE_LSTAT
#define lstat(a,b) gnupg_stat ((a), (b))
#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;
struct scanctrl_s
{
tar_header_t flist;
tar_header_t *flist_tail;
int nestlevel;
};
/* On Windows convert name to UTF8 and return it; caller must release
* the result. On Unix or if ALREADY_UTF8 is set, this function is a
* mere xtrystrcopy. On failure NULL is returned and ERRNO set. */
static char *
name_to_utf8 (const char *name, int already_utf8)
{
#ifdef HAVE_W32_SYSTEM
wchar_t *wstring;
char *result;
if (already_utf8)
result = xtrystrdup (name);
else
{
wstring = native_to_wchar (name);
if (!wstring)
return NULL;
result = wchar_to_utf8 (wstring);
xfree (wstring);
}
return result;
#else /*!HAVE_W32_SYSTEM */
(void)already_utf8;
return xtrystrdup (name);
#endif /*!HAVE_W32_SYSTEM */
}
/* Given a fresh header object HDR with only the name field set, try
to gather all available info. This is the W32 version. */
#ifdef HAVE_W32_SYSTEM
static gpg_error_t
fillup_entry_w32 (tar_header_t hdr)
{
char *p;
wchar_t *wfname;
WIN32_FILE_ATTRIBUTE_DATA fad;
DWORD attr;
for (p=hdr->name; *p; p++)
if (*p == '/')
*p = '\\';
wfname = gpgrt_fname_to_wchar (hdr->name);
for (p=hdr->name; *p; p++)
if (*p == '\\')
*p = '/';
if (!wfname)
{
log_error ("error converting '%s': %s\n", hdr->name, w32_strerror (-1));
return gpg_error_from_syserror ();
}
if (!GetFileAttributesExW (wfname, GetFileExInfoStandard, &fad))
{
log_error ("error stat-ing '%s': %s\n", hdr->name, w32_strerror (-1));
xfree (wfname);
return gpg_error_from_syserror ();
}
xfree (wfname);
attr = fad.dwFileAttributes;
if ((attr & FILE_ATTRIBUTE_NORMAL))
hdr->typeflag = TF_REGULAR;
else if ((attr & FILE_ATTRIBUTE_DIRECTORY))
hdr->typeflag = TF_DIRECTORY;
else if ((attr & FILE_ATTRIBUTE_DEVICE))
hdr->typeflag = TF_NOTSUP;
else if ((attr & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_TEMPORARY)))
hdr->typeflag = TF_NOTSUP;
else
hdr->typeflag = TF_REGULAR;
/* Map some attributes to USTAR defined mode bits. */
hdr->mode = 0640; /* User may read and write, group only read. */
if ((attr & FILE_ATTRIBUTE_DIRECTORY))
hdr->mode |= 0110; /* Dirs are user and group executable. */
if ((attr & FILE_ATTRIBUTE_READONLY))
hdr->mode &= ~0200; /* Clear the user write bit. */
if ((attr & FILE_ATTRIBUTE_HIDDEN))
hdr->mode &= ~0707; /* Clear all user and other bits. */
if ((attr & FILE_ATTRIBUTE_SYSTEM))
hdr->mode |= 0004; /* Make it readable by other. */
/* Only set the size for a regular file. */
if (hdr->typeflag == TF_REGULAR)
hdr->size = (fad.nFileSizeHigh * ((unsigned long long)MAXDWORD+1)
+ fad.nFileSizeLow);
hdr->mtime = (((unsigned long long)fad.ftLastWriteTime.dwHighDateTime << 32)
| fad.ftLastWriteTime.dwLowDateTime);
if (!hdr->mtime)
hdr->mtime = (((unsigned long long)fad.ftCreationTime.dwHighDateTime << 32)
| fad.ftCreationTime.dwLowDateTime);
hdr->mtime -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */
hdr->mtime /= 10000000; /* Convert from 0.1us to seconds. */
return 0;
}
#endif /*HAVE_W32_SYSTEM*/
/* Given a fresh header object HDR with only the name field set, try
to gather all available info. This is the POSIX version. */
#ifndef HAVE_W32_SYSTEM
static gpg_error_t
fillup_entry_posix (tar_header_t hdr)
{
gpg_error_t err;
struct stat sbuf;
if (lstat (hdr->name, &sbuf))
{
err = gpg_error_from_syserror ();
log_error ("error stat-ing '%s': %s\n", hdr->name, gpg_strerror (err));
return err;
}
if (S_ISREG (sbuf.st_mode))
hdr->typeflag = TF_REGULAR;
else if (S_ISDIR (sbuf.st_mode))
hdr->typeflag = TF_DIRECTORY;
else if (S_ISCHR (sbuf.st_mode))
hdr->typeflag = TF_CHARDEV;
else if (S_ISBLK (sbuf.st_mode))
hdr->typeflag = TF_BLOCKDEV;
else if (S_ISFIFO (sbuf.st_mode))
hdr->typeflag = TF_FIFO;
else if (S_ISLNK (sbuf.st_mode))
hdr->typeflag = TF_SYMLINK;
else
hdr->typeflag = TF_NOTSUP;
/* FIXME: Save DEV and INO? */
/* Set the USTAR defined mode bits using the system macros. */
if (sbuf.st_mode & S_IRUSR)
hdr->mode |= 0400;
if (sbuf.st_mode & S_IWUSR)
hdr->mode |= 0200;
if (sbuf.st_mode & S_IXUSR)
hdr->mode |= 0100;
if (sbuf.st_mode & S_IRGRP)
hdr->mode |= 0040;
if (sbuf.st_mode & S_IWGRP)
hdr->mode |= 0020;
if (sbuf.st_mode & S_IXGRP)
hdr->mode |= 0010;
if (sbuf.st_mode & S_IROTH)
hdr->mode |= 0004;
if (sbuf.st_mode & S_IWOTH)
hdr->mode |= 0002;
if (sbuf.st_mode & S_IXOTH)
hdr->mode |= 0001;
#ifdef S_IXUID
if (sbuf.st_mode & S_IXUID)
hdr->mode |= 04000;
#endif
#ifdef S_IXGID
if (sbuf.st_mode & S_IXGID)
hdr->mode |= 02000;
#endif
#ifdef S_ISVTX
if (sbuf.st_mode & S_ISVTX)
hdr->mode |= 01000;
#endif
hdr->nlink = sbuf.st_nlink;
hdr->uid = sbuf.st_uid;
hdr->gid = sbuf.st_gid;
/* Only set the size for a regular file. */
if (hdr->typeflag == TF_REGULAR)
hdr->size = sbuf.st_size;
hdr->mtime = sbuf.st_mtime;
return 0;
}
#endif /*!HAVE_W32_SYSTEM*/
/* Add a new entry. The name of a directory entry is ENTRYNAME; if
that is NULL, DNAME is the name of the directory itself. Under
Windows ENTRYNAME shall have backslashes replaced by standard
slashes. */
static gpg_error_t
add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl)
{
gpg_error_t err;
tar_header_t hdr;
char *p;
size_t dnamelen = strlen (dname);
log_assert (dnamelen);
hdr = xtrycalloc (1, sizeof *hdr + dnamelen + 1
+ (entryname? strlen (entryname) : 0) + 1);
if (!hdr)
return gpg_error_from_syserror ();
p = stpcpy (hdr->name, dname);
if (entryname)
{
if (dname[dnamelen-1] != '/')
*p++ = '/';
strcpy (p, entryname);
}
else
{
if (hdr->name[dnamelen-1] == '/')
hdr->name[dnamelen-1] = 0;
}
#ifdef HAVE_DOSISH_SYSTEM
err = fillup_entry_w32 (hdr);
#else
err = fillup_entry_posix (hdr);
#endif
if (err)
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, NULL, log_get_stream ());
*scanctrl->flist_tail = hdr;
scanctrl->flist_tail = &hdr->next;
}
return 0;
}
static gpg_error_t
scan_directory (const char *dname, scanctrl_t scanctrl)
{
gpg_error_t err = 0;
#ifdef HAVE_W32_SYSTEM
/* Note that we introduced gnupg_opendir only after we had deployed
* this code and thus we don't change it for now. */
WIN32_FIND_DATAW fi;
HANDLE hd = INVALID_HANDLE_VALUE;
char *p;
if (!*dname)
return 0; /* An empty directory name has no entries. */
{
char *fname;
wchar_t *wfname;
fname = xtrymalloc (strlen (dname) + 2 + 2 + 1);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!strcmp (dname, "/"))
strcpy (fname, "/*"); /* Trailing slash is not allowed. */
else if (!strcmp (dname, "."))
strcpy (fname, "*");
else if (*dname && dname[strlen (dname)-1] == '/')
strcpy (stpcpy (fname, dname), "*");
else if (*dname && dname[strlen (dname)-1] != '*')
strcpy (stpcpy (fname, dname), "/*");
else
strcpy (fname, dname);
for (p=fname; *p; p++)
if (*p == '/')
*p = '\\';
wfname = gpgrt_fname_to_wchar (fname);
xfree (fname);
if (!wfname)
{
err = gpg_error_from_syserror ();
log_error (_("error reading directory '%s': %s\n"),
dname, gpg_strerror (err));
goto leave;
}
hd = FindFirstFileW (wfname, &fi);
if (hd == INVALID_HANDLE_VALUE)
{
err = gpg_error_from_syserror ();
log_error (_("error reading directory '%s': %s\n"),
dname, w32_strerror (-1));
xfree (wfname);
goto leave;
}
xfree (wfname);
}
do
{
char *fname = wchar_to_utf8 (fi.cFileName);
if (!fname)
{
err = gpg_error_from_syserror ();
log_error ("error converting filename: %s\n", w32_strerror (-1));
break;
}
for (p=fname; *p; p++)
if (*p == '\\')
*p = '/';
if (!strcmp (fname, "." ) || !strcmp (fname, ".."))
err = 0; /* Skip self and parent dir entry. */
else if (!strncmp (dname, "./", 2) && dname[2])
err = add_entry (dname+2, fname, scanctrl);
else
err = add_entry (dname, fname, scanctrl);
xfree (fname);
}
while (!err && FindNextFileW (hd, &fi));
if (err)
;
else if (GetLastError () == ERROR_NO_MORE_FILES)
err = 0;
else
{
err = gpg_error_from_syserror ();
log_error (_("error reading directory '%s': %s\n"),
dname, w32_strerror (-1));
}
leave:
if (hd != INVALID_HANDLE_VALUE)
FindClose (hd);
#else /*!HAVE_W32_SYSTEM*/
DIR *dir;
struct dirent *de;
if (!*dname)
return 0; /* An empty directory name has no entries. */
dir = opendir (dname);
if (!dir)
{
err = gpg_error_from_syserror ();
log_error (_("error reading directory '%s': %s\n"),
dname, gpg_strerror (err));
return err;
}
while ((de = readdir (dir)))
{
if (!strcmp (de->d_name, "." ) || !strcmp (de->d_name, ".."))
continue; /* Skip self and parent dir entry. */
err = add_entry (dname, de->d_name, scanctrl);
if (err)
goto leave;
}
leave:
closedir (dir);
#endif /*!HAVE_W32_SYSTEM*/
return err;
}
static gpg_error_t
scan_recursive (const char *dname, scanctrl_t scanctrl)
{
gpg_error_t err = 0;
tar_header_t hdr, *start_tail, *stop_tail;
if (scanctrl->nestlevel > 200)
{
log_error ("directories too deeply nested\n");
return gpg_error (GPG_ERR_RESOURCE_LIMIT);
}
scanctrl->nestlevel++;
log_assert (scanctrl->flist_tail);
start_tail = scanctrl->flist_tail;
scan_directory (dname, scanctrl);
stop_tail = scanctrl->flist_tail;
hdr = *start_tail;
for (; hdr && hdr != *stop_tail; hdr = hdr->next)
if (hdr->typeflag == TF_DIRECTORY)
{
if (opt.verbose > 1)
log_info ("scanning directory '%s'\n", hdr->name);
scan_recursive (hdr->name, scanctrl);
}
scanctrl->nestlevel--;
return err;
}
/* Returns true if PATTERN is acceptable. */
static int
pattern_valid_p (const char *pattern)
{
if (!*pattern)
return 0;
if (*pattern == '.' && pattern[1] == '.')
return 0;
if (*pattern == '/'
#ifdef HAVE_DOSISH_SYSTEM
|| *pattern == '\\'
#endif
)
return 0; /* Absolute filenames are not supported. */
#ifdef HAVE_DRIVE_LETTERS
if (((*pattern >= 'a' && *pattern <= 'z')
|| (*pattern >= 'A' && *pattern <= 'Z'))
&& pattern[1] == ':')
return 0; /* Drive letter are not allowed either. */
#endif /*HAVE_DRIVE_LETTERS*/
return 1; /* Okay. */
}
static void
store_xoctal (char *buffer, size_t length, unsigned long long value)
{
char *p, *pend;
size_t n;
unsigned long long v;
log_assert (length > 1);
v = value;
n = length;
p = pend = buffer + length;
*--p = 0; /* Nul byte. */
n--;
do
{
*--p = '0' + (v % 8);
v /= 8;
n--;
}
while (v && n);
if (!v)
{
/* Pad. */
for ( ; n; n--)
*--p = '0';
}
else /* Does not fit into the field. Store as binary number. */
{
v = value;
n = length;
p = pend = buffer + length;
do
{
*--p = v;
v /= 256;
n--;
}
while (v && n);
if (!v)
{
/* Pad. */
for ( ; n; n--)
*--p = 0;
if (*p & 0x80)
BUG ();
*p |= 0x80; /* Set binary flag. */
}
else
BUG ();
}
}
static void
store_uname (char *buffer, size_t length, unsigned long uid)
{
static int initialized;
static unsigned long lastuid;
static char lastuname[32];
if (!initialized || uid != lastuid)
{
#ifdef HAVE_W32_SYSTEM
mem2str (lastuname, uid? "user":"root", sizeof lastuname);
#else
struct passwd *pw = getpwuid (uid);
lastuid = uid;
initialized = 1;
if (pw)
mem2str (lastuname, pw->pw_name, sizeof lastuname);
else
{
log_info ("failed to get name for uid %lu\n", uid);
*lastuname = 0;
}
#endif
}
mem2str (buffer, lastuname, length);
}
static void
store_gname (char *buffer, size_t length, unsigned long gid)
{
static int initialized;
static unsigned long lastgid;
static char lastgname[32];
if (!initialized || gid != lastgid)
{
#ifdef HAVE_W32_SYSTEM
mem2str (lastgname, gid? "users":"root", sizeof lastgname);
#else
struct group *gr = getgrgid (gid);
lastgid = gid;
initialized = 1;
if (gr)
mem2str (lastgname, gr->gr_name, sizeof lastgname);
else
{
log_info ("failed to get name for gid %lu\n", gid);
*lastgname = 0;
}
#endif
}
mem2str (buffer, lastgname, length);
}
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, strlist_t *r_exthdr)
{
gpg_error_t err;
struct ustar_raw_header *raw = record;
size_t namelen, n;
strlist_t sl;
memset (record, 0, RECORDSIZE);
*r_exthdr = NULL;
/* Store name and prefix. */
namelen = strlen (hdr->name);
if (namelen < sizeof raw->name)
memcpy (raw->name, hdr->name, namelen);
else
{
n = (namelen < sizeof raw->prefix)? namelen : sizeof raw->prefix;
for (n--; n ; n--)
if (hdr->name[n] == '/')
break;
if (namelen - n < sizeof raw->name)
{
/* Note that the N is < sizeof prefix and that the
delimiting slash is not stored. */
memcpy (raw->prefix, hdr->name, n);
memcpy (raw->name, hdr->name+n+1, namelen - n);
}
else
{
/* 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);
}
}
store_xoctal (raw->mode, sizeof raw->mode, hdr->mode);
store_xoctal (raw->uid, sizeof raw->uid, hdr->uid);
store_xoctal (raw->gid, sizeof raw->gid, hdr->gid);
store_xoctal (raw->size, sizeof raw->size, hdr->size);
store_xoctal (raw->mtime, sizeof raw->mtime, hdr->mtime);
switch (hdr->typeflag)
{
case TF_REGULAR: raw->typeflag[0] = '0'; break;
case TF_HARDLINK: raw->typeflag[0] = '1'; break;
case TF_SYMLINK: raw->typeflag[0] = '2'; break;
case TF_CHARDEV: raw->typeflag[0] = '3'; break;
case TF_BLOCKDEV: raw->typeflag[0] = '4'; break;
case TF_DIRECTORY: raw->typeflag[0] = '5'; break;
case TF_FIFO: raw->typeflag[0] = '6'; break;
default: return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
memcpy (raw->magic, "ustar", 6);
raw->version[0] = '0';
raw->version[1] = '0';
store_uname (raw->uname, sizeof raw->uname, hdr->uid);
store_gname (raw->gname, sizeof raw->gname, hdr->gid);
#ifndef HAVE_W32_SYSTEM
if (hdr->typeflag == TF_SYMLINK)
{
int nread;
char *p;
nread = readlink (hdr->name, raw->linkname, sizeof raw->linkname -1);
if (nread < 0)
{
err = gpg_error_from_syserror ();
log_error ("error reading symlink '%s': %s\n",
hdr->name, gpg_strerror (err));
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*/
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)
{
gpg_error_t err;
char record[RECORDSIZE];
estream_t infp;
size_t nread, nbytes;
strlist_t exthdr = NULL;
int any;
err = build_header (record, hdr, &exthdr);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
{
log_info ("skipping unsupported file '%s'\n", hdr->name);
err = 0;
}
return err;
}
if (hdr->typeflag == TF_REGULAR)
{
infp = es_fopen (hdr->name, "rb,sysopen");
if (!infp)
{
err = gpg_error_from_syserror ();
log_error ("can't open '%s': %s - skipped\n",
hdr->name, gpg_strerror (err));
return err;
}
}
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)
{
hdr->nrecords = (hdr->size + RECORDSIZE-1)/RECORDSIZE;
any = 0;
while (hdr->nrecords--)
{
nbytes = hdr->nrecords? RECORDSIZE : (hdr->size % RECORDSIZE);
if (!nbytes)
nbytes = RECORDSIZE;
nread = es_fread (record, 1, nbytes, infp);
if (nread != nbytes)
{
err = gpg_error_from_syserror ();
log_error ("error reading file '%s': %s%s\n",
hdr->name, gpg_strerror (err),
any? " (file shrunk?)":"");
goto leave;
}
else if (nbytes < RECORDSIZE)
memset (record + nbytes, 0, RECORDSIZE - nbytes);
any = 1;
err = write_record (stream, record);
if (err)
goto leave;
}
nread = es_fread (record, 1, 1, infp);
if (nread)
log_info ("note: file '%s' has grown\n", hdr->name);
}
leave:
if (err)
es_fclose (infp);
else if ((err = es_fclose (infp)))
log_error ("error closing file '%s': %s\n", hdr->name, gpg_strerror (err));
free_strlist (exthdr);
return err;
}
static gpg_error_t
write_eof_mark (estream_t stream)
{
gpg_error_t err;
char record[RECORDSIZE];
memset (record, 0, sizeof record);
err = write_record (stream, record);
if (!err)
err = write_record (stream, record);
return err;
}
/* Create a new tarball using the names in the array INPATTERN. If
INPATTERN is NULL take the pattern as null terminated strings from
stdin or from the file specified by FILES_FROM. If NULL_NAMES is
set the filenames in such a file are delimited by a binary Nul and
not by a LF. */
gpg_error_t
gpgtar_create (char **inpattern, const char *files_from, int null_names,
int encrypt, int sign)
{
gpg_error_t err = 0;
struct scanctrl_s scanctrl_buffer;
scanctrl_t scanctrl = &scanctrl_buffer;
tar_header_t hdr, *start_tail;
estream_t files_from_stream = NULL;
estream_t outstream = NULL;
int eof_seen = 0;
pid_t pid = (pid_t)(-1);
memset (scanctrl, 0, sizeof *scanctrl);
scanctrl->flist_tail = &scanctrl->flist;
if (!inpattern)
{
if (!files_from || !strcmp (files_from, "-"))
{
files_from = "-";
files_from_stream = es_stdin;
if (null_names)
es_set_binary (es_stdin);
}
else if (!(files_from_stream=es_fopen (files_from, null_names? "rb":"r")))
{
err = gpg_error_from_syserror ();
log_error ("error opening '%s': %s\n",
files_from, gpg_strerror (err));
return err;
}
}
if (opt.directory && gnupg_chdir (opt.directory))
{
err = gpg_error_from_syserror ();
log_error ("chdir to '%s' failed: %s\n",
opt.directory, gpg_strerror (err));
return err;
}
while (!eof_seen)
{
char *pat, *p;
int skip_this = 0;
if (inpattern)
{
const char *pattern = *inpattern;
if (!pattern)
break; /* End of array. */
inpattern++;
if (!*pattern)
continue;
pat = name_to_utf8 (pattern, 0);
}
else /* Read Nul or LF delimited pattern from files_from_stream. */
{
int c;
char namebuf[4096];
size_t n = 0;
for (;;)
{
if ((c = es_getc (files_from_stream)) == EOF)
{
if (es_ferror (files_from_stream))
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n",
files_from, gpg_strerror (err));
goto leave;
}
c = null_names ? 0 : '\n';
eof_seen = 1;
}
if (n >= sizeof namebuf - 1)
{
if (!skip_this)
{
skip_this = 1;
log_error ("error reading '%s': %s\n",
files_from, "filename too long");
}
}
else
namebuf[n++] = c;
if (null_names)
{
if (!c)
{
namebuf[n] = 0;
break;
}
}
else /* Shall be LF delimited. */
{
if (!c)
{
if (!skip_this)
{
skip_this = 1;
log_error ("error reading '%s': %s\n",
files_from, "filename with embedded Nul");
}
}
else if ( c == '\n' )
{
namebuf[n] = 0;
ascii_trim_spaces (namebuf);
n = strlen (namebuf);
break;
}
}
}
if (skip_this || n < 2)
continue;
pat = name_to_utf8 (namebuf, opt.utf8strings);
}
if (!pat)
{
err = gpg_error_from_syserror ();
log_error ("memory allocation problem: %s\n", gpg_strerror (err));
goto leave;
}
for (p=pat; *p; p++)
if (*p == '\\')
*p = '/';
if (opt.verbose > 1)
log_info ("scanning '%s'\n", pat);
start_tail = scanctrl->flist_tail;
if (skip_this || !pattern_valid_p (pat))
log_error ("skipping invalid name '%s'\n", pat);
else if (!add_entry (pat, NULL, scanctrl)
&& *start_tail && ((*start_tail)->typeflag & TF_DIRECTORY))
scan_recursive (pat, scanctrl);
xfree (pat);
}
if (files_from_stream && files_from_stream != es_stdin)
es_fclose (files_from_stream);
if (encrypt || sign)
{
strlist_t arg;
ccparray_t ccp;
+ int except[2] = { -1, -1 };
const char **argv;
/* '--encrypt' may be combined with '--symmetric', but 'encrypt'
* 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);
+ except[0] = opt.status_fd;
}
ccparray_put (&ccp, "--output");
ccparray_put (&ccp, opt.outfile? opt.outfile : "-");
if (encrypt)
ccparray_put (&ccp, "--encrypt");
if (sign)
ccparray_put (&ccp, "--sign");
if (opt.user)
{
ccparray_put (&ccp, "--local-user");
ccparray_put (&ccp, opt.user);
}
if (opt.symmetric)
ccparray_put (&ccp, "--symmetric");
for (arg = opt.recipients; arg; arg = arg->next)
{
ccparray_put (&ccp, "--recipient");
ccparray_put (&ccp, arg->d);
}
for (arg = opt.gpg_arguments; arg; arg = arg->next)
ccparray_put (&ccp, arg->d);
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
- err = gnupg_spawn_process (opt.gpg_program, argv, NULL,
+ err = gnupg_spawn_process (opt.gpg_program, argv,
+ except[0] == -1? NULL : except,
(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 || pid != (pid_t)(-1))
first_err = es_fclose (outstream);
else
first_err = es_fflush (outstream);
outstream = NULL;
if (! err)
err = first_err;
}
if (err)
{
log_error ("creating tarball '%s' failed: %s\n",
opt.outfile ? opt.outfile : "-", gpg_strerror (err));
if (outstream && outstream != es_stdout)
es_fclose (outstream);
if (opt.outfile)
gnupg_remove (opt.outfile);
}
scanctrl->flist_tail = NULL;
while ( (hdr = scanctrl->flist) )
{
scanctrl->flist = hdr->next;
xfree (hdr);
}
return err;
}
diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c
index f96887f40..bc2e672de 100644
--- a/tools/gpgtar-extract.c
+++ b/tools/gpgtar-extract.c
@@ -1,485 +1,488 @@
/* gpgtar-extract.c - Extract from 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.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "../common/i18n.h"
#include
#include "../common/exechelp.h"
#include "../common/sysutils.h"
#include "../common/ccparray.h"
#include "gpgtar.h"
static gpg_error_t
check_suspicious_name (const char *name)
{
size_t n;
n = strlen (name);
#ifdef HAVE_DOSISH_SYSTEM
if (strchr (name, '\\'))
{
log_error ("filename '%s' contains a backslash - "
"can't extract on this system\n", name);
return gpg_error (GPG_ERR_INV_NAME);
}
#endif /*HAVE_DOSISH_SYSTEM*/
if (!n
|| strstr (name, "//")
|| strstr (name, "/../")
|| !strncmp (name, "../", 3)
|| (n >= 3 && !strcmp (name+n-3, "/.." )))
{
log_error ("filename '%s' has suspicious parts - not extracting\n",
name);
return gpg_error (GPG_ERR_INV_NAME);
}
return 0;
}
static gpg_error_t
extract_regular (estream_t stream, const char *dirname,
tarinfo_t info, tar_header_t hdr, strlist_t exthdr)
{
gpg_error_t err;
char record[RECORDSIZE];
size_t n, nbytes, nwritten;
char *fname_buffer = NULL;
const char *fname;
estream_t outfp = NULL;
strlist_t sl;
fname = hdr->name;
for (sl = exthdr; sl; sl = sl->next)
if (sl->flags == 1)
fname = sl->d;
err = check_suspicious_name (fname);
if (err)
goto leave;
fname_buffer = strconcat (dirname, "/", fname, NULL);
if (!fname_buffer)
{
err = gpg_error_from_syserror ();
log_error ("error creating filename: %s\n", gpg_strerror (err));
goto leave;
}
fname = fname_buffer;
if (opt.dry_run)
outfp = es_fopen ("/dev/null", "wb");
else
outfp = es_fopen (fname, "wb,sysopen");
if (!outfp)
{
err = gpg_error_from_syserror ();
log_error ("error creating '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
for (n=0; n < hdr->nrecords;)
{
err = read_record (stream, record);
if (err)
goto leave;
info->nblocks++;
n++;
if (n < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE)))
nbytes = RECORDSIZE;
else
nbytes = (hdr->size % RECORDSIZE);
nwritten = es_fwrite (record, 1, nbytes, outfp);
if (nwritten != nbytes)
{
err = gpg_error_from_syserror ();
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
}
/* Fixme: Set permissions etc. */
leave:
if (!err && opt.verbose)
log_info ("extracted '%s'\n", fname);
es_fclose (outfp);
if (err && fname && outfp)
{
if (gnupg_remove (fname))
log_error ("error removing incomplete file '%s': %s\n",
fname, gpg_strerror (gpg_error_from_syserror ()));
}
xfree (fname_buffer);
return err;
}
static gpg_error_t
extract_directory (const char *dirname, tar_header_t hdr, strlist_t exthdr)
{
gpg_error_t err;
const char *name;
char *fname = NULL;
strlist_t sl;
name = hdr->name;
for (sl = exthdr; sl; sl = sl->next)
if (sl->flags == 1)
name = sl->d;
err = check_suspicious_name (name);
if (err)
goto leave;
fname = strconcat (dirname, "/", name, NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
log_error ("error creating filename: %s\n", gpg_strerror (err));
goto leave;
}
/* Remove a possible trailing slash. */
if (fname[strlen (fname)-1] == '/')
fname[strlen (fname)-1] = 0;
if (! opt.dry_run && gnupg_mkdir (fname, "-rwx------"))
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_EEXIST)
{
/* Ignore existing directories while extracting. */
err = 0;
}
if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
/* Try to create the directory with parents but keep the
original error code in case of a failure. */
int rc = 0;
char *p;
size_t prefixlen;
/* (PREFIXLEN is the length of the new directory we use to
* extract the tarball.) */
prefixlen = strlen (dirname) + 1;
for (p = fname+prefixlen; (p = strchr (p, '/')); p++)
{
*p = 0;
rc = gnupg_mkdir (fname, "-rwx------");
*p = '/';
if (rc)
break;
}
if (!rc && !gnupg_mkdir (fname, "-rwx------"))
err = 0;
}
if (err)
log_error ("error creating directory '%s': %s\n",
fname, gpg_strerror (err));
}
leave:
if (!err && opt.verbose)
log_info ("created '%s/'\n", fname);
xfree (fname);
return err;
}
static gpg_error_t
extract (estream_t stream, const char *dirname, tarinfo_t info,
tar_header_t hdr, strlist_t exthdr)
{
gpg_error_t err;
size_t n;
if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN)
err = extract_regular (stream, dirname, info, hdr, exthdr);
else if (hdr->typeflag == TF_DIRECTORY)
err = extract_directory (dirname, hdr, exthdr);
else
{
char record[RECORDSIZE];
log_info ("unsupported file type %d for '%s' - skipped\n",
(int)hdr->typeflag, hdr->name);
for (err = 0, n=0; !err && n < hdr->nrecords; n++)
{
err = read_record (stream, record);
if (!err)
info->nblocks++;
}
}
return err;
}
/* Create a new directory to be used for extracting the tarball.
Returns the name of the directory which must be freed by the
caller. In case of an error a diagnostic is printed and NULL
returned. */
static char *
create_directory (const char *dirprefix)
{
gpg_error_t err = 0;
char *prefix_buffer = NULL;
char *dirname = NULL;
size_t n;
int idx;
/* Remove common suffixes. */
n = strlen (dirprefix);
if (n > 4 && (!compare_filenames (dirprefix + n - 4, EXTSEP_S GPGEXT_GPG)
|| !compare_filenames (dirprefix + n - 4, EXTSEP_S "pgp")
|| !compare_filenames (dirprefix + n - 4, EXTSEP_S "asc")
|| !compare_filenames (dirprefix + n - 4, EXTSEP_S "pem")
|| !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7m")
|| !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7e")))
{
prefix_buffer = xtrystrdup (dirprefix);
if (!prefix_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
prefix_buffer[n-4] = 0;
dirprefix = prefix_buffer;
}
for (idx=1; idx < 5000; idx++)
{
xfree (dirname);
dirname = xtryasprintf ("%s_%d_", dirprefix, idx);
if (!dirname)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!gnupg_mkdir (dirname, "-rwx------"))
goto leave; /* Ready. */
if (errno != EEXIST && errno != ENOTDIR)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
err = gpg_error (GPG_ERR_LIMIT_REACHED);
leave:
if (err)
{
log_error ("error creating an extract directory: %s\n",
gpg_strerror (err));
xfree (dirname);
dirname = NULL;
}
xfree (prefix_buffer);
return dirname;
}
gpg_error_t
gpgtar_extract (const char *filename, int decrypt)
{
gpg_error_t err;
estream_t stream = NULL;
tar_header_t header = NULL;
strlist_t extheader = NULL;
const char *dirprefix = NULL;
char *dirname = NULL;
struct tarinfo_s tarinfo_buffer;
tarinfo_t tarinfo = &tarinfo_buffer;
pid_t pid = (pid_t)(-1);
char *logfilename = NULL;
memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer);
if (opt.directory)
dirname = xtrystrdup (opt.directory);
else
{
if (opt.filename)
{
dirprefix = strrchr (opt.filename, '/');
if (dirprefix)
dirprefix++;
else
dirprefix = opt.filename;
}
else if (filename)
{
dirprefix = strrchr (filename, '/');
if (dirprefix)
dirprefix++;
else
dirprefix = filename;
}
if (!dirprefix || !*dirprefix)
dirprefix = "GPGARCH";
dirname = create_directory (dirprefix);
if (!dirname)
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
if (opt.verbose)
log_info ("extracting to '%s/'\n", dirname);
if (decrypt)
{
strlist_t arg;
ccparray_t ccp;
+ int except[2] = { -1, -1 };
const char **argv;
ccparray_init (&ccp, 0);
if (opt.batch)
ccparray_put (&ccp, "--batch");
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);
+ except[0] = opt.status_fd;
}
if (opt.with_log)
{
ccparray_put (&ccp, "--log-file");
logfilename = xstrconcat (dirname, ".log", NULL);
ccparray_put (&ccp, logfilename);
}
ccparray_put (&ccp, "--output");
ccparray_put (&ccp, "-");
ccparray_put (&ccp, "--decrypt");
for (arg = opt.gpg_arguments; arg; arg = arg->next)
ccparray_put (&ccp, arg->d);
if (filename)
{
ccparray_put (&ccp, "--");
ccparray_put (&ccp, filename);
}
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
- err = gnupg_spawn_process (opt.gpg_program, argv, NULL,
+ err = gnupg_spawn_process (opt.gpg_program, argv,
+ except[0] == -1? NULL : except,
((filename? 0 : GNUPG_SPAWN_KEEP_STDIN)
| GNUPG_SPAWN_KEEP_STDERR),
NULL, &stream, NULL, &pid);
xfree (argv);
if (err)
goto leave;
es_set_binary (stream);
}
else if (filename)
{
if (!strcmp (filename, "-"))
stream = es_stdin;
else
stream = es_fopen (filename, "rb,sysopen");
if (!stream)
{
err = gpg_error_from_syserror ();
log_error ("error opening '%s': %s\n", filename, gpg_strerror (err));
return err;
}
if (stream == es_stdin)
es_set_binary (es_stdin);
}
else
{
stream = es_stdin;
es_set_binary (es_stdin);
}
for (;;)
{
err = gpgtar_read_header (stream, tarinfo, &header, &extheader);
if (err || header == NULL)
goto leave;
err = extract (stream, dirname, tarinfo, header, extheader);
if (err)
goto leave;
free_strlist (extheader);
extheader = NULL;
xfree (header);
header = NULL;
}
if (pid != (pid_t)(-1))
{
int exitcode;
err = es_fclose (stream);
stream = 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:
free_strlist (extheader);
xfree (header);
xfree (dirname);
xfree (logfilename);
if (stream != es_stdin)
es_fclose (stream);
return err;
}
diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c
index a536b05f2..c5bf25825 100644
--- a/tools/gpgtar-list.c
+++ b/tools/gpgtar-list.c
@@ -1,590 +1,593 @@
/* gpgtar-list.c - List 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.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
#include
#include
#include
#include
#include "../common/i18n.h"
#include
#include "gpgtar.h"
#include "../common/exechelp.h"
#include "../common/sysutils.h"
#include "../common/ccparray.h"
static unsigned long long
parse_xoctal (const void *data, size_t length, const char *filename)
{
const unsigned char *p = data;
unsigned long long value;
if (!length)
value = 0;
else if ( (*p & 0x80))
{
/* Binary format. */
value = (*p++ & 0x7f);
while (--length)
{
value <<= 8;
value |= *p++;
}
}
else
{
/* Octal format */
value = 0;
/* Skip leading spaces and zeroes. */
for (; length && (*p == ' ' || *p == '0'); length--, p++)
;
for (; length && *p; length--, p++)
{
if (*p >= '0' && *p <= '7')
{
value <<= 3;
value += (*p - '0');
}
else
{
log_error ("%s: invalid octal number encountered - assuming 0\n",
filename);
value = 0;
break;
}
}
}
return value;
}
static tar_header_t
parse_header (const void *record, const char *filename, tarinfo_t info)
{
const struct ustar_raw_header *raw = record;
size_t n, namelen, prefixlen;
tar_header_t header;
int use_prefix;
int anyerror = 0;
info->headerblock = info->nblocks - 1;
use_prefix = (!memcmp (raw->magic, "ustar", 5)
&& (raw->magic[5] == ' ' || !raw->magic[5]));
for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++)
;
if (namelen == sizeof raw->name)
{
log_info ("%s: warning: name not terminated by a nul\n", filename);
anyerror = 1;
}
for (n=namelen+1; n < sizeof raw->name; n++)
if (raw->name[n])
{
log_info ("%s: warning: garbage after name\n", filename);
anyerror = 1;
break;
}
if (use_prefix && raw->prefix[0])
{
for (prefixlen=0; (prefixlen < sizeof raw->prefix
&& raw->prefix[prefixlen]); prefixlen++)
;
if (prefixlen == sizeof raw->prefix)
log_info ("%s: warning: prefix not terminated by a nul (block %llu)\n",
filename, info->headerblock);
for (n=prefixlen+1; n < sizeof raw->prefix; n++)
if (raw->prefix[n])
{
log_info ("%s: warning: garbage after prefix\n", filename);
anyerror = 1;
break;
}
}
else
prefixlen = 0;
header = xtrycalloc (1, sizeof *header + prefixlen + 1 + namelen);
if (!header)
{
log_error ("%s: error allocating header: %s\n",
filename, gpg_strerror (gpg_error_from_syserror ()));
return NULL;
}
if (prefixlen)
{
n = prefixlen;
memcpy (header->name, raw->prefix, n);
if (raw->prefix[n-1] != '/')
header->name[n++] = '/';
}
else
n = 0;
memcpy (header->name+n, raw->name, namelen);
header->name[n+namelen] = 0;
header->mode = parse_xoctal (raw->mode, sizeof raw->mode, filename);
header->uid = parse_xoctal (raw->uid, sizeof raw->uid, filename);
header->gid = parse_xoctal (raw->gid, sizeof raw->gid, filename);
header->size = parse_xoctal (raw->size, sizeof raw->size, filename);
header->mtime = parse_xoctal (raw->mtime, sizeof raw->mtime, filename);
/* checksum = */
switch (raw->typeflag[0])
{
case '0': header->typeflag = TF_REGULAR; break;
case '1': header->typeflag = TF_HARDLINK; break;
case '2': header->typeflag = TF_SYMLINK; break;
case '3': header->typeflag = TF_CHARDEV; break;
case '4': header->typeflag = TF_BLOCKDEV; break;
case '5': header->typeflag = TF_DIRECTORY; break;
case '6': header->typeflag = TF_FIFO; break;
case '7': header->typeflag = TF_RESERVED; break;
case 'g': header->typeflag = TF_GEXTHDR; break;
case 'x': header->typeflag = TF_EXTHDR; break;
default: header->typeflag = TF_UNKNOWN; break;
}
/* Compute the number of data records following this header. */
if (header->typeflag == TF_REGULAR
|| header->typeflag == TF_EXTHDR
|| header->typeflag == TF_UNKNOWN)
header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE;
else
header->nrecords = 0;
if (anyerror)
{
log_info ("%s: header block %llu is corrupt"
" (size=%llu type=%d nrec=%llu)\n",
filename, info->headerblock,
header->size, header->typeflag, header->nrecords);
/* log_printhex (record, RECORDSIZE, " "); */
}
return header;
}
/* Parse the extended header. This funcion may modify BUFFER. */
static gpg_error_t
parse_extended_header (const char *fname,
char *buffer, size_t buflen, strlist_t *r_exthdr)
{
unsigned int reclen;
unsigned char *p, *record;
strlist_t sl;
while (buflen)
{
record = buffer; /* Remember begin of record. */
reclen = 0;
for (p = buffer; buflen && digitp (p); buflen--, p++)
{
reclen *= 10;
reclen += (*p - '0');
}
if (!buflen || *p != ' ')
{
log_error ("%s: malformed record length in extended header\n", fname);
return gpg_error (GPG_ERR_INV_RECORD);
}
p++; /* Skip space. */
buflen--;
if (buflen + (p-record) < reclen)
{
log_error ("%s: extended header record larger"
" than total extended header data\n", fname);
return gpg_error (GPG_ERR_INV_RECORD);
}
if (reclen < (p-record)+2 || record[reclen-1] != '\n')
{
log_error ("%s: malformed extended header record\n", fname);
return gpg_error (GPG_ERR_INV_RECORD);
}
record[reclen-1] = 0; /* For convenience change LF to a Nul. */
reclen -= (p-record);
/* P points to the begin of the keyword and RECLEN is the
* remaining length of the record excluding the LF. */
if (memchr (p, 0, reclen-1)
&& (!strncmp (p, "path=", 5) || !strncmp (p, "linkpath=", 9)))
{
log_error ("%s: extended header record has an embedded nul"
" - ignoring\n", fname);
}
else if (!strncmp (p, "path=", 5))
{
sl = add_to_strlist_try (r_exthdr, p+5);
if (!sl)
return gpg_error_from_syserror ();
sl->flags = 1; /* Mark as path */
}
else if (!strncmp (p, "linkpath=", 9))
{
sl = add_to_strlist_try (r_exthdr, p+9);
if (!sl)
return gpg_error_from_syserror ();
sl->flags = 2; /* Mark as linkpath */
}
buffer = p + reclen;
buflen -= reclen;
}
return 0;
}
/* Read the next block, assuming it is a tar header. Returns a header
* object on success in R_HEADER, or an error. If the stream is
* consumed (i.e. end-of-archive), R_HEADER is set to NULL. In case
* of an error an error message is printed. If the header is an
* extended header, a string list is allocated and stored at
* R_EXTHEADER; the caller should provide a pointer to NULL. Such an
* extended header is fully processed here and the returned R_HEADER
* has then the next regular header. */
static gpg_error_t
read_header (estream_t stream, tarinfo_t info,
tar_header_t *r_header, strlist_t *r_extheader)
{
gpg_error_t err;
char record[RECORDSIZE];
int i;
tar_header_t hdr;
char *buffer;
size_t buflen, nrec;
err = read_record (stream, record);
if (err)
return err;
info->nblocks++;
for (i=0; i < RECORDSIZE && !record[i]; i++)
;
if (i == RECORDSIZE)
{
/* All zero header - check whether it is the first part of an
end of archive mark. */
err = read_record (stream, record);
if (err)
return err;
info->nblocks++;
for (i=0; i < RECORDSIZE && !record[i]; i++)
;
if (i != RECORDSIZE)
log_info ("%s: warning: skipping empty header\n",
es_fname_get (stream));
else
{
/* End of archive - FIXME: we might want to check for garbage. */
*r_header = NULL;
return 0;
}
}
*r_header = parse_header (record, es_fname_get (stream), info);
if (!*r_header)
return gpg_error_from_syserror ();
hdr = *r_header;
if (hdr->typeflag != TF_EXTHDR || !r_extheader)
return 0;
/* Read the extended header. */
if (!hdr->nrecords)
{
/* More than 64k for an extedned header is surely too large. */
log_info ("%s: warning: empty extended header\n",
es_fname_get (stream));
return 0;
}
if (hdr->nrecords > 65536 / RECORDSIZE)
{
/* More than 64k for an extedned header is surely too large. */
log_error ("%s: extended header too large - skipping\n",
es_fname_get (stream));
return 0;
}
buffer = xtrymalloc (hdr->nrecords * RECORDSIZE);
if (!buffer)
{
err = gpg_error_from_syserror ();
log_error ("%s: error allocating space for extended header: %s\n",
es_fname_get (stream), gpg_strerror (err));
return err;
}
buflen = 0;
for (nrec=0; nrec < hdr->nrecords;)
{
err = read_record (stream, buffer + buflen);
if (err)
{
xfree (buffer);
return err;
}
info->nblocks++;
nrec++;
if (nrec < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE)))
buflen += RECORDSIZE;
else
buflen += (hdr->size % RECORDSIZE);
}
err = parse_extended_header (es_fname_get (stream),
buffer, buflen, r_extheader);
if (err)
{
free_strlist (*r_extheader);
*r_extheader = NULL;
}
xfree (buffer);
/* Now tha the extedned header has been read, we read the next
* header without allowing an extended header. */
return read_header (stream, info, r_header, NULL);
}
/* Skip the data records according to HEADER. Prints an error message
on error and return -1. */
static int
skip_data (estream_t stream, tarinfo_t info, tar_header_t header)
{
char record[RECORDSIZE];
unsigned long long n;
for (n=0; n < header->nrecords; n++)
{
if (read_record (stream, record))
return -1;
info->nblocks++;
}
return 0;
}
static void
print_header (tar_header_t header, strlist_t extheader, estream_t out)
{
unsigned long mask;
char modestr[10+1];
int i;
strlist_t sl;
const char *name, *linkname;
*modestr = '?';
switch (header->typeflag)
{
case TF_REGULAR: *modestr = '-'; break;
case TF_HARDLINK: *modestr = 'h'; break;
case TF_SYMLINK: *modestr = 'l'; break;
case TF_CHARDEV: *modestr = 'c'; break;
case TF_BLOCKDEV: *modestr = 'b'; break;
case TF_DIRECTORY:*modestr = 'd'; break;
case TF_FIFO: *modestr = 'f'; break;
case TF_RESERVED: *modestr = '='; break;
case TF_EXTHDR: break;
case TF_GEXTHDR: break;
case TF_UNKNOWN: break;
case TF_NOTSUP: break;
}
for (mask = 0400, i = 0; i < 9; i++, mask >>= 1)
modestr[1+i] = (header->mode & mask)? "rwxrwxrwx"[i]:'-';
if ((header->typeflag & 04000))
modestr[3] = modestr[3] == 'x'? 's':'S';
if ((header->typeflag & 02000))
modestr[6] = modestr[6] == 'x'? 's':'S';
if ((header->typeflag & 01000))
modestr[9] = modestr[9] == 'x'? 't':'T';
modestr[10] = 0;
/* FIXME: We do not parse the linkname unless its part of an
* extended header. */
name = header->name;
linkname = header->typeflag == TF_SYMLINK? "?" : NULL;
for (sl = extheader; sl; sl = sl->next)
{
if (sl->flags == 1)
name = sl->d;
else if (sl->flags == 2)
linkname = sl->d;
}
es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s%s%s\n",
modestr, header->nlink, header->uid, header->gid, header->size,
isotimestamp (header->mtime),
name,
linkname? " -> " : "",
linkname? linkname : "");
}
/* List the tarball FILENAME or, if FILENAME is NULL, the tarball read
from stdin. */
gpg_error_t
gpgtar_list (const char *filename, int decrypt)
{
gpg_error_t err;
estream_t stream = NULL;
tar_header_t header = NULL;
strlist_t extheader = NULL;
struct tarinfo_s tarinfo_buffer;
tarinfo_t tarinfo = &tarinfo_buffer;
pid_t pid = (pid_t)(-1);
memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer);
if (decrypt)
{
strlist_t arg;
ccparray_t ccp;
+ int except[2] = { -1, -1 };
const char **argv;
ccparray_init (&ccp, 0);
if (opt.batch)
ccparray_put (&ccp, "--batch");
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);
+ except[0] = opt.status_fd;
}
ccparray_put (&ccp, "--output");
ccparray_put (&ccp, "-");
ccparray_put (&ccp, "--decrypt");
for (arg = opt.gpg_arguments; arg; arg = arg->next)
ccparray_put (&ccp, arg->d);
if (filename)
{
ccparray_put (&ccp, "--");
ccparray_put (&ccp, filename);
}
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
- err = gnupg_spawn_process (opt.gpg_program, argv, NULL,
+ err = gnupg_spawn_process (opt.gpg_program, argv,
+ except[0] == -1? NULL : except,
((filename? 0 : GNUPG_SPAWN_KEEP_STDIN)
| GNUPG_SPAWN_KEEP_STDERR),
NULL, &stream, NULL, &pid);
xfree (argv);
if (err)
goto leave;
es_set_binary (stream);
}
else if (filename) /* No decryption requested. */
{
if (!strcmp (filename, "-"))
stream = es_stdin;
else
stream = es_fopen (filename, "rb,sysopen");
if (!stream)
{
err = gpg_error_from_syserror ();
log_error ("error opening '%s': %s\n", filename, gpg_strerror (err));
goto leave;
}
if (stream == es_stdin)
es_set_binary (es_stdin);
}
else
{
stream = es_stdin;
es_set_binary (es_stdin);
}
for (;;)
{
err = read_header (stream, tarinfo, &header, &extheader);
if (err || header == NULL)
goto leave;
print_header (header, extheader, es_stdout);
if (skip_data (stream, tarinfo, header))
goto leave;
free_strlist (extheader);
extheader = NULL;
xfree (header);
header = NULL;
}
if (pid != (pid_t)(-1))
{
int exitcode;
err = es_fclose (stream);
stream = 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:
free_strlist (extheader);
xfree (header);
if (stream != es_stdin)
es_fclose (stream);
return err;
}
gpg_error_t
gpgtar_read_header (estream_t stream, tarinfo_t info,
tar_header_t *r_header, strlist_t *r_extheader)
{
return read_header (stream, info, r_header, r_extheader);
}
void
gpgtar_print_header (tar_header_t header, strlist_t extheader, estream_t out)
{
if (header && out)
print_header (header, extheader, out);
}