From b0902369e789c9aadf584799889a34637688d601 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Thu, 20 Mar 2014 16:28:25 +0200 Subject: [PATCH] Fail if archive comes from a terminal. Based on patch from Pavel Raiskup . * 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. --- gnulib.modules | 4 + src/buffer.c | 11 +- tests/Makefile.am | 5 +- tests/iotty.at | 47 +++++ tests/testsuite.at | 1 + tests/ttyemu.c | 459 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 525 insertions(+), 2 deletions(-) create mode 100644 tests/iotty.at create mode 100644 tests/ttyemu.c diff --git a/gnulib.modules b/gnulib.modules index 25e8ab3..3ba06b6 100644 --- a/gnulib.modules +++ b/gnulib.modules @@ -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 diff --git a/src/buffer.c b/src/buffer.c index 95e8a26..248dd88 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -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, diff --git a/tests/Makefile.am b/tests/Makefile.am index d4c9362..3896b72 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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\ diff --git a/tests/iotty.at b/tests/iotty.at new file mode 100644 index 0000000..ea15c84 --- /dev/null +++ b/tests/iotty.at @@ -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 . + +# 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 diff --git a/tests/testsuite.at b/tests/testsuite.at index c52890b..3096e0d 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -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]) diff --git a/tests/ttyemu.c b/tests/ttyemu.c new file mode 100644 index 0000000..9137290 --- /dev/null +++ b/tests/ttyemu.c @@ -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 . +*/ +#define _XOPEN_SOURCE 600 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 .", + 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; +} -- 2.43.0