Fail if archive comes from a terminal.

Based on patch from Pavel Raiskup <praiskup@redhat.com>.

* gnulib.modules: Add new modules.
* src/buffer.c (_open_archive): Refuse to read archive from a tty.
* tests/Makefile.am (TESTSUITE_AT): Add iotty.at
(check_PROGRAMS): New program ttyemu
* tests/testsuite.at: Include iotty.at
* tests/iotty.at: New file.
* tests/ttyemu.c: New file.
This commit is contained in:
Sergey Poznyakoff
2014-03-20 16:28:25 +02:00
parent da546e69af
commit b0902369e7
6 changed files with 525 additions and 2 deletions

View File

@@ -49,6 +49,7 @@ getpagesize
gettext
gettime
gitlog-to-changelog
grantpt
hash
human
inttostr
@@ -64,6 +65,8 @@ modechange
obstack
openat
parse-datetime
posix_openpt
ptsname
priv-set
progname
quote
@@ -92,6 +95,7 @@ timespec
unlinkat
unlinkdir
unlocked-io
unlockpt
utimensat
version-etc-fsf
xalloc

View File

@@ -689,6 +689,16 @@ _open_archive (enum access_mode wanted_access)
/* When updating the archive, we start with reading. */
access_mode = wanted_access == ACCESS_UPDATE ? ACCESS_READ : wanted_access;
/* Refuse to read archive from a tty.
Do not fail if the tar's output goes directly to tty because such
behavior would go against GNU Coding Standards:
http://lists.gnu.org/archive/html/bug-tar/2014-03/msg00042.html */
if (strcmp (archive_name_array[0], "-") == 0
&& wanted_access == ACCESS_READ && isatty (STDIN_FILENO))
FATAL_ERROR ((0, 0,
_("Refusing to read archive contents from terminal "
"(missing -f option?)")));
read_full_records = read_full_records_option;
records_read = 0;
@@ -731,7 +741,6 @@ _open_archive (enum access_mode wanted_access)
enum compress_type type;
archive = STDIN_FILENO;
type = check_compressed_archive (&shortfile);
if (type != ct_tar && type != ct_none)
FATAL_ERROR ((0, 0,

View File

@@ -116,6 +116,7 @@ TESTSUITE_AT = \
incr09.at\
indexfile.at\
ignfail.at\
iotty.at\
label01.at\
label02.at\
label03.at\
@@ -261,10 +262,12 @@ installcheck-local:
## genfile ##
## ------------ ##
check_PROGRAMS = genfile
check_PROGRAMS = genfile ttyemu
genfile_SOURCES = genfile.c argcv.c argcv.h
ttyemu_SOURCES = ttyemu.c
localedir = $(datadir)/locale
AM_CPPFLAGS = \
-I$(top_srcdir)/gnu\

47
tests/iotty.at Normal file
View File

@@ -0,0 +1,47 @@
# Process this file with autom4te to create testsuite. -*- Autotest -*-
# Test suite for GNU tar.
# Copyright 2014 Free Software Foundation, Inc.
# This file is part of GNU tar.
# GNU tar 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.
# GNU tar 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 <http://www.gnu.org/licenses/>.
# Description: Tar should refuse to read archive from the terminal.
# Reported by: Pavel Raiskup
# References: <5285498.uPPgZ77uHP@nb.usersys.redhat.com>,
# http://lists.gnu.org/archive/html/bug-tar/2014-03/msg00033.html
AT_SETUP([terminal input])
AT_KEYWORDS([options iotty])
AT_TAR_CHECK([
TAPE=-
export TAPE
ttyemu -t5 -i/dev/null tar -x
echo $?
ttyemu -t5 -i/dev/null tar -xz
echo $?
],
[0],
[tar: Refusing to read archive contents from terminal (missing -f option?)
tar: Error is not recoverable: exiting now
2
tar: Refusing to read archive contents from terminal (missing -f option?)
tar: Error is not recoverable: exiting now
2
],
[],[],[],[posix, gnu, oldgnu])
AT_CLEANUP

View File

@@ -213,6 +213,7 @@ m4_include([gzip.at])
m4_include([recurse.at])
m4_include([recurs02.at])
m4_include([shortrec.at])
m4_include([iotty.at])
AT_BANNER([The --same-order option])
m4_include([same-order01.at])

459
tests/ttyemu.c Normal file
View File

@@ -0,0 +1,459 @@
/* Run program with its first three file descriptors attached to a tty.
Copyright 2014 Free Software Foundation, Inc.
This file is part of GNU tar.
GNU tar 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.
GNU tar 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 <http://www.gnu.org/licenses/>.
*/
#define _XOPEN_SOURCE 600
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <termios.h>
#include <sys/ioctl.h>
#ifndef TCSASOFT
# define TCSASOFT 0
#endif
#define C_EOT 4
#define EX_OK 0
#define EX_USAGE 125
#define EX_ERR 126
#define EX_EXEC 127
#define BUF_SIZE 1024
#if 0
# define DEBUG(c) fprintf (stderr, "%s\n", c)
#else
# define DEBUG(c)
#endif
struct buffer
{
char buf[BUF_SIZE];
int avail;
int written;
int cr;
time_t ts;
};
#define shut(fildes) \
do \
{ \
DEBUG (("closing " #fildes)); \
close(fildes); \
fildes = -1; \
} \
while(0)
#define bufinit(buffer,all) \
do \
{ \
(buffer).avail = (buffer).written = 0; \
(buffer).ts = time (NULL); \
if (all) \
(buffer).cr = 0; \
} \
while(0)
#define bufisempty(buffer) ((buffer).avail == (buffer).written)
#define bufavail(buffer) (BUF_SIZE - (buffer).avail)
#define bufread(buffer,fildes,tty) \
do \
{ \
int r = read (fildes, (buffer).buf + (buffer).avail, \
BUF_SIZE - (buffer).avail); \
(buffer).ts = time (NULL); \
if (r < 0) \
{ \
if (errno == EINTR) \
continue; \
if (tty && errno == EIO) \
shut (fildes); \
else \
{ \
fprintf (stderr, "%s:%d: reading from %s: %s", \
__FILE__,__LINE__,#fildes, strerror (errno)); \
exit (EX_ERR); \
} \
} \
else if (r == 0) \
shut (fildes); \
else \
(buffer).avail += r; \
} \
while(0)
#define bufwrite(buffer,fildes) \
do \
{ \
int r = write (fildes, (buffer).buf + (buffer).written, \
(buffer).avail - (buffer).written); \
(buffer).ts = time (NULL); \
if (r < 0) \
{ \
if (errno == EINTR) \
continue; \
if (stop) \
shut (fildes); \
else \
{ \
perror ("writing"); \
exit (EX_ERR); \
} \
} \
else if (r == 0) \
/*shut (fildes)*/; \
else \
(buffer).written += r; \
} \
while(0)
void
tr (struct buffer *bp)
{
int i, j;
for (i = j = bp->written; i < bp->avail;)
{
if (bp->buf[i] == '\r')
{
bp->cr = 1;
i++;
}
else
{
if (bp->cr)
{
bp->cr = 0;
if (bp->buf[i] != '\n')
bp->buf[j++] = '\r';
}
bp->buf[j++] = bp->buf[i++];
}
}
bp->avail = j;
}
int stop;
int status;
void
sigchld (int sig)
{
DEBUG (("child exited"));
wait (&status);
stop = 1;
}
void
noecho (int fd)
{
struct termios to;
if (tcgetattr (fd, &to))
{
perror ("tcgetattr");
exit (EX_ERR);
}
to.c_lflag |= ICANON;
to.c_lflag &= ~(ECHO | ISIG);
to.c_cc[VEOF] = C_EOT;
if (tcsetattr (fd, TCSAFLUSH | TCSASOFT, &to))
{
perror ("tcgetattr");
exit (EX_ERR);
}
}
char *usage_text[] = {
"usage: ttyemu [-ah] [-i INFILE] [-o OUTFILE] [-t TIMEOUT] PROGRAM [ARGS...]",
"ttyemu runs PROGRAM with its first three file descriptors connected to a"
" terminal",
"",
"Options are:",
"",
" -a append output to OUTFILE, instead of overwriting it",
" -i INFILE read input from INFILE",
" -o OUTFILE write output to OUTFILE",
" -t TIMEOUT set I/O timeout",
" -h print this help summary",
"",
"Report bugs and suggestions to <bug-tar@gnu.org>.",
NULL
};
static void
usage (void)
{
int i;
for (i = 0; usage_text[i]; i++)
{
fputs (usage_text[i], stderr);
fputc ('\n', stderr);
}
}
int
main (int argc, char **argv)
{
int i;
int master, slave;
pid_t pid;
fd_set rdset, wrset;
struct buffer ibuf, obuf;
int in = 0, out = 1;
char *infile = NULL, *outfile = NULL;
int outflags = O_TRUNC;
int maxfd;
int eot = C_EOT;
int timeout = 0;
while ((i = getopt (argc, argv, "ai:o:t:h")) != EOF)
{
switch (i)
{
case 'a':
outflags &= ~O_TRUNC;
break;
case 'i':
infile = optarg;
break;
case 'o':
outfile = optarg;
break;
case 't':
timeout = atoi (optarg);
break;
case 'h':
usage ();
return EX_OK;
default:
return EX_USAGE;
}
}
argc -= optind;
argv += optind;
if (argc == 0)
{
usage ();
return EX_USAGE;
}
if (infile)
{
in = open (infile, O_RDONLY);
if (in == -1)
{
perror (infile);
return EX_ERR;
}
}
if (outfile)
{
out = open (outfile, O_RDWR|O_CREAT|outflags, 0666);
if (out == -1)
{
perror (outfile);
return EX_ERR;
}
}
master = posix_openpt (O_RDWR);
if (master == -1)
{
perror ("posix_openpty");
return EX_ERR;
}
if (grantpt (master))
{
perror ("grantpt");
return EX_ERR;
}
if (unlockpt (master))
{
perror ("unlockpt");
return EX_ERR;
}
signal (SIGCHLD, sigchld);
pid = fork ();
if (pid == -1)
{
perror ("fork");
return EX_ERR;
}
if (pid == 0)
{
slave = open (ptsname (master), O_RDWR);
if (slave < 0)
{
perror ("open");
return EX_ERR;
}
noecho (slave);
for (i = 0; i < 3; i++)
{
if (slave != i)
{
close (i);
if (dup (slave) != i)
{
perror ("dup");
_exit (EX_EXEC);
}
}
}
for (i = sysconf (_SC_OPEN_MAX) - 1; i > 2; --i)
close (i);
setsid ();
ioctl (0, TIOCSCTTY, 1);
execvp (argv[0], argv);
perror (argv[0]);
_exit (EX_EXEC);
}
sleep (1);
bufinit (ibuf, 1);
bufinit (obuf, 1);
while (1)
{
FD_ZERO (&rdset);
FD_ZERO (&wrset);
maxfd = 0;
if (in != -1)
{
FD_SET (in, &rdset);
if (in > maxfd)
maxfd = in;
}
if (master != -1)
{
FD_SET (master, &rdset);
if (!stop)
FD_SET (master, &wrset);
if (master > maxfd)
maxfd = master;
}
if (maxfd == 0)
{
if (stop)
break;
pause ();
continue;
}
if (select (maxfd + 1, &rdset, &wrset, NULL, NULL) < 0)
{
if (errno == EINTR)
continue;
perror ("select");
return EX_ERR;
}
if (timeout)
{
time_t now = time (NULL);
if (now - ibuf.ts > timeout || now - obuf.ts > timeout)
{
fprintf (stderr, "ttyemu: I/O timeout\n");
return EX_ERR;
}
}
if (in >= 0)
{
if (bufavail (ibuf) && FD_ISSET (in, &rdset))
bufread (ibuf, in, 0);
}
else if (master == -1)
break;
if (master >= 0 && FD_ISSET (master, &wrset))
{
if (!bufisempty (ibuf))
bufwrite (ibuf, master);
else if (in == -1 && eot)
{
DEBUG (("sent EOT"));
if (write (master, &eot, 1) <= 0)
{
perror ("write");
return EX_ERR;
}
eot = 0;
}
}
if (master >= 0 && bufavail (obuf) && FD_ISSET (master, &rdset))
bufread (obuf, master, 1);
if (bufisempty (obuf))
bufinit (obuf, 0);
else
{
tr (&obuf);
bufwrite (obuf, out);
}
if (bufisempty (ibuf))
bufinit (ibuf, 0);
}
if (WIFEXITED (status))
return WEXITSTATUS (status);
if (WIFSIGNALED (status))
fprintf (stderr, "ttyemu: child process %s failed on signal %d\n",
argv[0], WTERMSIG (status));
else
fprintf (stderr, "ttyemu: child process %s failed\n", argv[0]);
return EX_EXEC;
}