diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | NEWS | 6 | ||||
-rwxr-xr-x | autogen.sh | 1 | ||||
-rw-r--r-- | common/ChangeLog | 11 | ||||
-rw-r--r-- | common/estream.c | 546 | ||||
-rw-r--r-- | common/estream.h | 69 | ||||
-rw-r--r-- | configure.ac | 6 | ||||
-rw-r--r-- | po/.gitattributes | 5 | ||||
-rw-r--r-- | tools/ChangeLog | 8 | ||||
-rw-r--r-- | tools/Makefile.am | 18 | ||||
-rw-r--r-- | tools/gpgtar-create.c | 903 | ||||
-rw-r--r-- | tools/gpgtar-extract.c | 349 | ||||
-rw-r--r-- | tools/gpgtar-list.c | 332 | ||||
-rw-r--r-- | tools/gpgtar.c | 538 | ||||
-rw-r--r-- | tools/gpgtar.h | 132 |
17 files changed, 2815 insertions, 115 deletions
diff --git a/.gitignore b/.gitignore index 04335fa09..63b8c14ef 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,4 @@ tools/make-dns-cert tools/mk-tdata tools/symcryptrun tools/watchgnupg +tools/gpgtar @@ -1,5 +1,8 @@ 2011-01-11 Werner Koch <[email protected]> + * configure.ac: Add option --enable-gpgtar. + (AC_CHECK_FUNCS): Add stat. + * autogen.sh <w32>: Remove superfluous --without-included-gettext. 2011-01-10 Werner Koch <[email protected]> diff --git a/Makefile.am b/Makefile.am index ecc325797..103375a18 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,7 +20,7 @@ ACLOCAL_AMFLAGS = -I m4 -I gl/m4 AUTOMAKE_OPTIONS = dist-bzip2 no-dist-gzip -DISTCHECK_CONFIGURE_FLAGS = --enable-symcryptrun --enable-mailto +DISTCHECK_CONFIGURE_FLAGS = --enable-symcryptrun --enable-mailto --enable-gpgtar EXTRA_DIST = scripts/config.rpath autogen.sh README.SVN DISTCLEANFILES = g10defs.h @@ -1,11 +1,11 @@ Noteworthy changes in version 2.0.17 (unreleased) ------------------------------------------------- - * Fixed output of "gpgconf --check-options". + * Allow more hash algorithms with the OpenPGP v2 card. - * gpg-agent now tests for a newly created gpg-agent.conf after a HUP. + * The gpg-agent now tests for a new gpg-agent.conf on a HUP. - * Allow more hash algorithms with the OpenPGP v2 card. + * Fixed output of "gpgconf --check-options". * Fixed a bug where Scdaemon sends a signal to Gpg-agent running in non-daemon mode. diff --git a/autogen.sh b/autogen.sh index 2203b8252..be284594c 100755 --- a/autogen.sh +++ b/autogen.sh @@ -86,6 +86,7 @@ if test "$1" = "--build-w32"; then ./configure --enable-maintainer-mode --prefix=${w32root} \ --host=${host} --build=${build} \ + --enable-gpgtar \ --with-gpg-error-prefix=${w32root} \ --with-ksba-prefix=${w32root} \ --with-libgcrypt-prefix=${w32root} \ diff --git a/common/ChangeLog b/common/ChangeLog index 84843cd09..eb1f22e2c 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,14 @@ +2011-01-11 Werner Koch <[email protected]> + + Estream changes as used gnupg master from 2010-07-19. + + * estream.c (es_fname_get, es_fname_set): New. + (fname_set_internal): New. + (struct estream_internal): Add fields printable_fname and + printable_fname_inuse. + (_es_get_std_stream): Set stream name. + (es_fopen, es_freopen, es_deinitialize): Set fname. + 2011-01-10 Thomas Mraz <[email protected]> (wk) * pka.c (get_pka_info) [!USE_ADNS]: Turn ANSWER into a union to diff --git a/common/estream.c b/common/estream.c index 401590552..3ab68b5ff 100644 --- a/common/estream.c +++ b/common/estream.c @@ -1,5 +1,5 @@ /* estream.c - Extended Stream I/O Library - * Copyright (C) 2004, 2005, 2006, 2007, 2009 g10 Code GmbH + * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 g10 Code GmbH * * This file is part of Libestream. * @@ -15,6 +15,40 @@ * * You should have received a copy of the GNU General Public License * along with Libestream; if not, see <http://www.gnu.org/licenses/>. + * + * ALTERNATIVELY, Libestream may be distributed under the terms of the + * following license, in which case the provisions of this license are + * required INSTEAD OF the GNU General Public License. If you wish to + * allow use of your version of this file only under the terms of the + * GNU General Public License, and not to allow others to use your + * version of this file under the terms of the following license, + * indicate your decision by deleting this paragraph and the license + * below. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef USE_ESTREAM_SUPPORT_H @@ -27,6 +61,9 @@ #if defined(_WIN32) && !defined(HAVE_W32_SYSTEM) # define HAVE_W32_SYSTEM 1 +# if defined(__MINGW32CE__) && !defined (HAVE_W32CE_SYSTEM) +# define HAVE_W32CE_SYSTEM +# endif #endif #include <sys/types.h> @@ -44,6 +81,9 @@ #ifdef HAVE_W32_SYSTEM # include <windows.h> #endif +#ifdef HAVE_W32CE_SYSTEM +# include <gpg-error.h> /* ERRNO replacement. */ +#endif #ifdef WITHOUT_GNU_PTH /* Give the Makefile a chance to build without Pth. */ # undef HAVE_PTH @@ -76,6 +116,22 @@ void *memrchr (const void *block, int c, size_t size); #define O_BINARY 0 #endif +#ifdef HAVE_W32CE_SYSTEM +# define _set_errno(a) gpg_err_set_errno ((a)) +/* Setmode is missing in cegcc but available since CE 5.0. */ +int _setmode (int handle, int mode); +# define setmode(a,b) _setmode ((a),(b)) +#else +# define _set_errno(a) do { errno = (a); } while (0) +#endif + +#ifdef HAVE_W32_SYSTEM +# define IS_INVALID_FD(a) ((void*)(a) == (void*)(-1)) +#else +# define IS_INVALID_FD(a) ((a) == -1) +#endif + + /* Generally used types. */ typedef void *(*func_realloc_t) (void *mem, size_t size); @@ -134,9 +190,11 @@ dummy_mutex_call_int (estream_mutex_t mutex) #ifdef HAVE_PTH # define ESTREAM_SYS_READ es_pth_read # define ESTREAM_SYS_WRITE es_pth_write +# define ESTREAM_SYS_YIELD() pth_yield (NULL) #else # define ESTREAM_SYS_READ read # define ESTREAM_SYS_WRITE write +# define ESTREAM_SYS_YIELD() do { } while (0) #endif /* Misc definitions. */ @@ -153,6 +211,7 @@ struct estream_internal void *cookie; /* Cookie. */ void *opaque; /* Opaque data. */ unsigned int modeflags; /* Flags for the backend. */ + char *printable_fname; /* Malloced filename for es_fname_get. */ off_t offset; es_cookie_read_function_t func_read; es_cookie_write_function_t func_write; @@ -166,7 +225,10 @@ struct estream_internal unsigned int eof: 1; } indicators; unsigned int deallocate_buffer: 1; + unsigned int is_stdstream:1; /* This is a standard stream. */ + unsigned int stdstream_fd:2; /* 0, 1 or 2 for a standard stream. */ unsigned int print_err: 1; /* Error in print_fun_writer. */ + unsigned int printable_fname_inuse: 1; /* es_fname_get has been used. */ int print_errno; /* Errno from print_fun_writer. */ size_t print_ntotal; /* Bytes written from in print_fun_writer. */ FILE *print_fp; /* Stdio stream used by print_fun_writer. */ @@ -196,13 +258,22 @@ static estream_mutex_t estream_list_lock; #define ESTREAM_LIST_LOCK ESTREAM_MUTEX_LOCK (estream_list_lock) #define ESTREAM_LIST_UNLOCK ESTREAM_MUTEX_UNLOCK (estream_list_lock) +/* File descriptors registered to be used as the standard file handles. */ +static int custom_std_fds[3]; +static unsigned char custom_std_fds_valid[3]; + + #ifndef EOPNOTSUPP # define EOPNOTSUPP ENOSYS #endif - +/* Local prototypes. */ +static void fname_set_internal (estream_t stream, const char *fname, int quote); + + + /* Macros. */ /* Calculate array dimension. */ @@ -255,9 +326,11 @@ mem_free (void *p) * List manipulation. */ -/* Add STREAM to the list of registered stream objects. */ +/* Add STREAM to the list of registered stream objects. If + WITH_LOCKED_LIST is true we assumed that the list of streams is + already locked. */ static int -es_list_add (estream_t stream) +es_list_add (estream_t stream, int with_locked_list) { estream_list_t list_obj; int ret; @@ -267,14 +340,16 @@ es_list_add (estream_t stream) ret = -1; else { - ESTREAM_LIST_LOCK; + if (!with_locked_list) + ESTREAM_LIST_LOCK; list_obj->car = stream; list_obj->cdr = estream_list; list_obj->prev_cdr = &estream_list; if (estream_list) estream_list->prev_cdr = &list_obj->cdr; estream_list = list_obj; - ESTREAM_LIST_UNLOCK; + if (!with_locked_list) + ESTREAM_LIST_UNLOCK; ret = 0; } @@ -283,11 +358,12 @@ es_list_add (estream_t stream) /* Remove STREAM from the list of registered stream objects. */ static void -es_list_remove (estream_t stream) +es_list_remove (estream_t stream, int with_locked_list) { estream_list_t list_obj; - ESTREAM_LIST_LOCK; + if (!with_locked_list) + ESTREAM_LIST_LOCK; for (list_obj = estream_list; list_obj; list_obj = list_obj->cdr) if (list_obj->car == stream) { @@ -297,7 +373,8 @@ es_list_remove (estream_t stream) mem_free (list_obj); break; } - ESTREAM_LIST_UNLOCK; + if (!with_locked_list) + ESTREAM_LIST_UNLOCK; } /* Type of an stream-iterator-function. */ @@ -360,6 +437,14 @@ es_pth_write (int fd, const void *buffer, size_t size) +static void +es_deinit (void) +{ + /* Flush all streams. */ + es_fflush (NULL); +} + + /* * Initialization. */ @@ -367,17 +452,20 @@ es_pth_write (int fd, const void *buffer, size_t size) static int es_init_do (void) { -#ifdef HAVE_PTH static int initialized; if (!initialized) { +#ifdef HAVE_PTH if (!pth_init () && errno != EPERM ) return -1; if (pth_mutex_init (&estream_list_lock)) initialized = 1; - } +#else + initialized = 1; #endif + atexit (es_deinit); + } return 0; } @@ -427,7 +515,7 @@ es_func_mem_create (void *ES__RESTRICT *ES__RESTRICT cookie, if (!data && (data_n || data_len)) { - errno = EINVAL; + _set_errno (EINVAL); return -1; } @@ -511,7 +599,7 @@ es_func_mem_write (void *cookie, const void *buffer, size_t size) newsize = mem_cookie->memory_size + (nleft - size); if (newsize < mem_cookie->offset) { - errno = EINVAL; + _set_errno (EINVAL); return -1; } @@ -522,7 +610,7 @@ es_func_mem_write (void *cookie, const void *buffer, size_t size) newsize += mem_cookie->block_size - 1; if (newsize < mem_cookie->offset) { - errno = EINVAL; + _set_errno (EINVAL); return -1; } newsize /= mem_cookie->block_size; @@ -532,7 +620,7 @@ es_func_mem_write (void *cookie, const void *buffer, size_t size) /* Check for a total limit. */ if (mem_cookie->memory_limit && newsize > mem_cookie->memory_limit) { - errno = ENOSPC; + _set_errno (ENOSPC); return -1; } @@ -581,7 +669,7 @@ es_func_mem_seek (void *cookie, off_t *offset, int whence) break; default: - errno = EINVAL; + _set_errno (EINVAL); return -1; } @@ -592,14 +680,14 @@ es_func_mem_seek (void *cookie, off_t *offset, int whence) if (!mem_cookie->flags.grow) { - errno = ENOSPC; + _set_errno (ENOSPC); return -1; } newsize = pos_new + mem_cookie->block_size - 1; if (newsize < pos_new) { - errno = EINVAL; + _set_errno (EINVAL); return -1; } newsize /= mem_cookie->block_size; @@ -607,7 +695,7 @@ es_func_mem_seek (void *cookie, off_t *offset, int whence) if (mem_cookie->memory_limit && newsize > mem_cookie->memory_limit) { - errno = ENOSPC; + _set_errno (ENOSPC); return -1; } @@ -703,10 +791,18 @@ es_func_fd_read (void *cookie, void *buffer, size_t size) { estream_cookie_fd_t file_cookie = cookie; ssize_t bytes_read; - - do - bytes_read = ESTREAM_SYS_READ (file_cookie->fd, buffer, size); - while (bytes_read == -1 && errno == EINTR); + + if (IS_INVALID_FD (file_cookie->fd)) + { + ESTREAM_SYS_YIELD (); + bytes_read = 0; + } + else + { + do + bytes_read = ESTREAM_SYS_READ (file_cookie->fd, buffer, size); + while (bytes_read == -1 && errno == EINTR); + } return bytes_read; } @@ -714,14 +810,21 @@ es_func_fd_read (void *cookie, void *buffer, size_t size) /* Write function for fd objects. */ static ssize_t es_func_fd_write (void *cookie, const void *buffer, size_t size) - { estream_cookie_fd_t file_cookie = cookie; ssize_t bytes_written; - do - bytes_written = ESTREAM_SYS_WRITE (file_cookie->fd, buffer, size); - while (bytes_written == -1 && errno == EINTR); + if (IS_INVALID_FD (file_cookie->fd)) + { + ESTREAM_SYS_YIELD (); + bytes_written = size; /* Yeah: Success writing to the bit bucket. */ + } + else + { + do + bytes_written = ESTREAM_SYS_WRITE (file_cookie->fd, buffer, size); + while (bytes_written == -1 && errno == EINTR); + } return bytes_written; } @@ -734,13 +837,21 @@ es_func_fd_seek (void *cookie, off_t *offset, int whence) off_t offset_new; int err; - offset_new = lseek (file_cookie->fd, *offset, whence); - if (offset_new == -1) - err = -1; + if (IS_INVALID_FD (file_cookie->fd)) + { + _set_errno (ESPIPE); + err = -1; + } else { - *offset = offset_new; - err = 0; + offset_new = lseek (file_cookie->fd, *offset, whence); + if (offset_new == -1) + err = -1; + else + { + *offset = offset_new; + err = 0; + } } return err; @@ -755,7 +866,10 @@ es_func_fd_destroy (void *cookie) if (fd_cookie) { - err = fd_cookie->no_close? 0 : close (fd_cookie->fd); + if (IS_INVALID_FD (fd_cookie->fd)) + err = 0; + else + err = fd_cookie->no_close? 0 : close (fd_cookie->fd); mem_free (fd_cookie); } else @@ -822,7 +936,10 @@ es_func_fp_read (void *cookie, void *buffer, size_t size) estream_cookie_fp_t file_cookie = cookie; ssize_t bytes_read; - bytes_read = fread (buffer, 1, size, file_cookie->fp); + if (file_cookie->fp) + bytes_read = fread (buffer, 1, size, file_cookie->fp); + else + bytes_read = 0; if (!bytes_read && ferror (file_cookie->fp)) return -1; return bytes_read; @@ -836,7 +953,11 @@ es_func_fp_write (void *cookie, const void *buffer, size_t size) estream_cookie_fp_t file_cookie = cookie; size_t bytes_written; - bytes_written = fwrite (buffer, 1, size, file_cookie->fp); + + if (file_cookie->fp) + bytes_written = fwrite (buffer, 1, size, file_cookie->fp); + else + bytes_written = size; /* Successfully written to the bit bucket. */ if (bytes_written != size) return -1; return bytes_written; @@ -849,23 +970,31 @@ es_func_fp_seek (void *cookie, off_t *offset, int whence) estream_cookie_fp_t file_cookie = cookie; long int offset_new; + if (!file_cookie->fp) + { + _set_errno (ESPIPE); + return -1; + } + if ( fseek (file_cookie->fp, (long int)*offset, whence) ) { - fprintf (stderr, "\nfseek failed: errno=%d (%s)\n", errno,strerror (errno)); - return -1; + /* fprintf (stderr, "\nfseek failed: errno=%d (%s)\n", */ + /* errno,strerror (errno)); */ + return -1; } offset_new = ftell (file_cookie->fp); if (offset_new == -1) { - fprintf (stderr, "\nftell failed: errno=%d (%s)\n", errno,strerror (errno)); - return -1; + /* fprintf (stderr, "\nftell failed: errno=%d (%s)\n", */ + /* errno,strerror (errno)); */ + return -1; } *offset = offset_new; return 0; } -/* Destroy function for fd objects. */ +/* Destroy function for FILE* objects. */ static int es_func_fp_destroy (void *cookie) { @@ -874,8 +1003,13 @@ es_func_fp_destroy (void *cookie) if (fp_cookie) { - fflush (fp_cookie->fp); - err = fp_cookie->no_close? 0 : fclose (fp_cookie->fp); + if (fp_cookie->fp) + { + fflush (fp_cookie->fp); + err = fp_cookie->no_close? 0 : fclose (fp_cookie->fp); + } + else + err = 0; mem_free (fp_cookie); } else @@ -942,14 +1076,6 @@ es_func_file_create (void **cookie, int *filedes, return err; } -static es_cookie_io_functions_t estream_functions_file = - { - es_func_fd_read, - es_func_fd_write, - es_func_fd_seek, - es_func_fd_destroy - }; - static int es_convert_mode (const char *mode, unsigned int *modeflags) @@ -971,7 +1097,7 @@ es_convert_mode (const char *mode, unsigned int *modeflags) oflags = O_APPEND | O_CREAT; break; default: - errno = EINVAL; + _set_errno (EINVAL); return -1; } for (mode++; *mode; mode++) @@ -1010,7 +1136,7 @@ es_fill (estream_t stream) if (!stream->intern->func_read) { - errno = EOPNOTSUPP; + _set_errno (EOPNOTSUPP); err = -1; } else @@ -1144,7 +1270,11 @@ es_initialize (estream_t stream, stream->intern->print_fp = NULL; stream->intern->indicators.err = 0; stream->intern->indicators.eof = 0; + stream->intern->is_stdstream = 0; + stream->intern->stdstream_fd = 0; stream->intern->deallocate_buffer = 0; + stream->intern->printable_fname = NULL; + stream->intern->printable_fname_inuse = 0; stream->data_len = 0; stream->data_offset = 0; @@ -1152,7 +1282,7 @@ es_initialize (estream_t stream, stream->unread_data_len = 0; /* Depending on the modeflags we set whether we start in writing or reading mode. This is required in case we are working on a - wronly stream which is not seeekable (like stdout). Without this + stream which is not seeekable (like stdout). Without this pre-initialization we would do a seek at the first write call and as this will fail no utput will be delivered. */ if ((modeflags & O_WRONLY) || (modeflags & O_RDWR) ) @@ -1173,7 +1303,7 @@ es_deinitialize (estream_t stream) int save_errno = errno; fclose (stream->intern->print_fp); stream->intern->print_fp = NULL; - errno = save_errno; + _set_errno (save_errno); } func_close = stream->intern->func_close; @@ -1184,14 +1314,18 @@ es_deinitialize (estream_t stream) if (func_close) SET_UNLESS_NONZERO (err, tmp_err, (*func_close) (stream->intern->cookie)); - + mem_free (stream->intern->printable_fname); + stream->intern->printable_fname = NULL; + stream->intern->printable_fname_inuse = 0; + return err; } /* Create a new stream object, initialize it. */ static int es_create (estream_t *stream, void *cookie, int fd, - es_cookie_io_functions_t functions, unsigned int modeflags) + es_cookie_io_functions_t functions, unsigned int modeflags, + int with_locked_list) { estream_internal_t stream_internal_new; estream_t stream_new; @@ -1223,7 +1357,7 @@ es_create (estream_t *stream, void *cookie, int fd, ESTREAM_MUTEX_INITIALIZE (stream_new->intern->lock); es_initialize (stream_new, cookie, fd, functions, modeflags); - err = es_list_add (stream_new); + err = es_list_add (stream_new, with_locked_list); if (err) goto out; @@ -1245,13 +1379,13 @@ es_create (estream_t *stream, void *cookie, int fd, /* Deinitialize a stream object and destroy it. */ static int -es_destroy (estream_t stream) +es_destroy (estream_t stream, int with_locked_list) { int err = 0; if (stream) { - es_list_remove (stream); + es_list_remove (stream, with_locked_list); err = es_deinitialize (stream); mem_free (stream->intern); mem_free (stream); @@ -1460,7 +1594,7 @@ es_seek (estream_t ES__RESTRICT stream, off_t offset, int whence, if (! func_seek) { - errno = EOPNOTSUPP; + _set_errno (EOPNOTSUPP); err = -1; goto out; } @@ -1730,7 +1864,7 @@ es_skip (estream_t stream, size_t size) if (stream->data_offset + size > stream->data_len) { - errno = EINVAL; + _set_errno (EINVAL); err = -1; } else @@ -1771,7 +1905,7 @@ doreadline (estream_t ES__RESTRICT stream, size_t max_length, goto out; err = es_create (&line_stream, line_stream_cookie, -1, - estream_functions_mem, O_RDWR); + estream_functions_mem, O_RDWR, 0); if (err) goto out; @@ -1856,7 +1990,7 @@ doreadline (estream_t ES__RESTRICT stream, size_t max_length, out: if (line_stream) - es_destroy (line_stream); + es_destroy (line_stream, 0); else if (line_stream_cookie) es_func_mem_destroy (line_stream_cookie); @@ -1961,6 +2095,8 @@ es_set_buffering (estream_t ES__RESTRICT stream, buffer_new = buffer; else { + if (!size) + size = BUFSIZ; buffer_new = mem_alloc (size); if (! buffer_new) { @@ -2053,14 +2189,17 @@ es_fopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode) goto out; create_called = 1; - err = es_create (&stream, cookie, fd, estream_functions_file, modeflags); + err = es_create (&stream, cookie, fd, estream_functions_fd, modeflags, 0); if (err) goto out; + if (stream && path) + fname_set_internal (stream, path, 1); + out: if (err && create_called) - (*estream_functions_file.func_close) (cookie); + (*estream_functions_fd.func_close) (cookie); return stream; } @@ -2093,7 +2232,7 @@ es_mopen (unsigned char *ES__RESTRICT data, size_t data_n, size_t data_len, goto out; create_called = 1; - err = es_create (&stream, cookie, -1, estream_functions_mem, modeflags); + err = es_create (&stream, cookie, -1, estream_functions_mem, modeflags, 0); out: @@ -2124,7 +2263,7 @@ es_fopenmem (size_t memlimit, const char *ES__RESTRICT mode) memlimit)) return NULL; - if (es_create (&stream, cookie, -1, estream_functions_mem, modeflags)) + if (es_create (&stream, cookie, -1, estream_functions_mem, modeflags, 0)) (*estream_functions_mem.func_close) (cookie); return stream; @@ -2148,7 +2287,7 @@ es_fopencookie (void *ES__RESTRICT cookie, if (err) goto out; - err = es_create (&stream, cookie, -1, functions, modeflags); + err = es_create (&stream, cookie, -1, functions, modeflags, 0); if (err) goto out; @@ -2159,7 +2298,7 @@ es_fopencookie (void *ES__RESTRICT cookie, estream_t -do_fdopen (int filedes, const char *mode, int no_close) +do_fdopen (int filedes, const char *mode, int no_close, int with_locked_list) { unsigned int modeflags; int create_called; @@ -2180,7 +2319,8 @@ do_fdopen (int filedes, const char *mode, int no_close) goto out; create_called = 1; - err = es_create (&stream, cookie, filedes, estream_functions_fd, modeflags); + err = es_create (&stream, cookie, filedes, estream_functions_fd, + modeflags, with_locked_list); out: @@ -2193,19 +2333,19 @@ do_fdopen (int filedes, const char *mode, int no_close) estream_t es_fdopen (int filedes, const char *mode) { - return do_fdopen (filedes, mode, 0); + return do_fdopen (filedes, mode, 0, 0); } /* A variant of es_fdopen which does not close FILEDES at the end. */ estream_t es_fdopen_nc (int filedes, const char *mode) { - return do_fdopen (filedes, mode, 1); + return do_fdopen (filedes, mode, 1, 0); } estream_t -do_fpopen (FILE *fp, const char *mode, int no_close) +do_fpopen (FILE *fp, const char *mode, int no_close, int with_locked_list) { unsigned int modeflags; int create_called; @@ -2221,14 +2361,15 @@ do_fpopen (FILE *fp, const char *mode, int no_close) if (err) goto out; - fflush (fp); + if (fp) + fflush (fp); err = es_func_fp_create (&cookie, fp, modeflags, no_close); if (err) goto out; - + create_called = 1; - err = es_create (&stream, cookie, fileno (fp), estream_functions_fp, - modeflags); + err = es_create (&stream, cookie, fp? fileno (fp):-1, estream_functions_fp, + modeflags, with_locked_list); out: @@ -2250,7 +2391,7 @@ do_fpopen (FILE *fp, const char *mode, int no_close) estream_t es_fpopen (FILE *fp, const char *mode) { - return do_fpopen (fp, mode, 0); + return do_fpopen (fp, mode, 0, 0); } @@ -2258,7 +2399,86 @@ es_fpopen (FILE *fp, const char *mode) estream_t es_fpopen_nc (FILE *fp, const char *mode) { - return do_fpopen (fp, mode, 1); + return do_fpopen (fp, mode, 1, 0); +} + + +/* Set custom standard descriptors to be used for stdin, stdout and + stderr. This function needs to be called before any of the + standard streams are accessed. */ +void +_es_set_std_fd (int no, int fd) +{ + ESTREAM_LIST_LOCK; + if (no >= 0 && no < 3 && !custom_std_fds_valid[no]) + { + custom_std_fds[no] = fd; + custom_std_fds_valid[no] = 1; + } + ESTREAM_LIST_UNLOCK; +} + + +/* Return the stream used for stdin, stdout or stderr. */ +estream_t +_es_get_std_stream (int fd) +{ + estream_list_t list_obj; + estream_t stream = NULL; + + fd %= 3; /* We only allow 0, 1 or 2 but we don't want to return an error. */ + ESTREAM_LIST_LOCK; + for (list_obj = estream_list; list_obj; list_obj = list_obj->cdr) + if (list_obj->car->intern->is_stdstream + && list_obj->car->intern->stdstream_fd == fd) + { + stream = list_obj->car; + break; + } + if (!stream) + { + /* Standard stream not yet created. We first try to create them + from registered file descriptors. */ + if (!fd && custom_std_fds_valid[0]) + stream = do_fdopen (custom_std_fds[0], "r", 1, 1); + else if (fd == 1 && custom_std_fds_valid[1]) + stream = do_fdopen (custom_std_fds[1], "a", 1, 1); + else if (custom_std_fds_valid[2]) + stream = do_fdopen (custom_std_fds[2], "a", 1, 1); + + if (!stream) + { + /* Second try is to use the standard C streams. */ + if (!fd) + stream = do_fpopen (stdin, "r", 1, 1); + else if (fd == 1) + stream = do_fpopen (stdout, "a", 1, 1); + else + stream = do_fpopen (stderr, "a", 1, 1); + } + + if (!stream) + { + /* Last try: Create a bit bucket. */ + stream = do_fpopen (NULL, fd? "a":"r", 0, 1); + if (!stream) + { + fprintf (stderr, "fatal: error creating a dummy estream" + " for %d: %s\n", fd, strerror (errno)); + abort(); + } + } + + stream->intern->is_stdstream = 1; + stream->intern->stdstream_fd = fd; + if (fd == 2) + es_set_buffering (stream, NULL, _IOLBF, 0); + fname_set_internal (stream, + fd == 0? "[stdin]" : + fd == 1? "[stdout]" : "[stderr]", 0); + } + ESTREAM_LIST_UNLOCK; + return stream; } @@ -2291,7 +2511,7 @@ es_freopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode, goto leave; create_called = 1; - es_initialize (stream, cookie, fd, estream_functions_file, modeflags); + es_initialize (stream, cookie, fd, estream_functions_fd, modeflags); leave: @@ -2300,18 +2520,22 @@ es_freopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode, if (create_called) es_func_fd_destroy (cookie); - es_destroy (stream); + es_destroy (stream, 0); stream = NULL; } else - ESTREAM_UNLOCK (stream); + { + if (stream && path) + fname_set_internal (stream, path, 1); + ESTREAM_UNLOCK (stream); + } } else { /* FIXME? We don't support re-opening at the moment. */ - errno = EINVAL; + _set_errno (EINVAL); es_deinitialize (stream); - es_destroy (stream); + es_destroy (stream, 0); stream = NULL; } @@ -2324,7 +2548,7 @@ es_fclose (estream_t stream) { int err; - err = es_destroy (stream); + err = es_destroy (stream, 0); return err; } @@ -2426,6 +2650,23 @@ es_clearerr (estream_t stream) } +static int +do_fflush (estream_t stream) +{ + int err; + + if (stream->flags.writing) + err = es_flush (stream); + else + { + es_empty (stream); + err = 0; + } + + return err; +} + + int es_fflush (estream_t stream) { @@ -2434,17 +2675,11 @@ es_fflush (estream_t stream) if (stream) { ESTREAM_LOCK (stream); - if (stream->flags.writing) - err = es_flush (stream); - else - { - es_empty (stream); - err = 0; - } + err = do_fflush (stream); ESTREAM_UNLOCK (stream); } else - err = es_list_iterate (es_fflush); + err = es_list_iterate (do_fflush); return err ? EOF : 0; } @@ -2691,6 +2926,17 @@ es_fgets (char *ES__RESTRICT buffer, int length, estream_t ES__RESTRICT stream) int +es_fputs_unlocked (const char *ES__RESTRICT s, estream_t ES__RESTRICT stream) +{ + size_t length; + int err; + + length = strlen (s); + err = es_writen (stream, s, length, NULL); + return err ? EOF : 0; +} + +int es_fputs (const char *ES__RESTRICT s, estream_t ES__RESTRICT stream) { size_t length; @@ -2821,7 +3067,7 @@ es_read_line (estream_t stream, { /* This should never happen. If it does, the function has been called with wrong arguments. */ - errno = EINVAL; + _set_errno (EINVAL); return -1; } length -= 3; /* Reserve 3 bytes for CR,LF,EOL. */ @@ -2855,7 +3101,7 @@ es_read_line (estream_t stream, if (max_length) *max_length = 0; ESTREAM_UNLOCK (stream); - errno = save_errno; + _set_errno (save_errno); return -1; } buffer = *addr_of_buffer; @@ -2885,6 +3131,15 @@ es_free (void *a) int +es_vfprintf_unlocked (estream_t ES__RESTRICT stream, + const char *ES__RESTRICT format, + va_list ap) +{ + return es_print (stream, format, ap); +} + + +int es_vfprintf (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, va_list ap) { @@ -2898,9 +3153,9 @@ es_vfprintf (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, } -static int +int es_fprintf_unlocked (estream_t ES__RESTRICT stream, - const char *ES__RESTRICT format, ...) + const char *ES__RESTRICT format, ...) { int ret; @@ -2973,21 +3228,32 @@ tmpfd (void) { #ifdef HAVE_W32_SYSTEM int attempts, n; +#ifdef HAVE_W32CE_SYSTEM + wchar_t buffer[MAX_PATH+9+12+1]; +# define mystrlen(a) wcslen (a) + wchar_t *name, *p; +#else char buffer[MAX_PATH+9+12+1]; +# define mystrlen(a) strlen (a) char *name, *p; +#endif HANDLE file; int pid = GetCurrentProcessId (); unsigned int value; int i; n = GetTempPath (MAX_PATH+1, buffer); - if (!n || n > MAX_PATH || strlen (buffer) > MAX_PATH) + if (!n || n > MAX_PATH || mystrlen (buffer) > MAX_PATH) { - errno = ENOENT; + _set_errno (ENOENT); return -1; } - p = buffer + strlen (buffer); + p = buffer + mystrlen (buffer); +#ifdef HAVE_W32CE_SYSTEM + wcscpy (p, L"_estream"); +#else strcpy (p, "_estream"); +#endif p += 8; /* We try to create the directory but don't care about an error as it may already exist and the CreateFile would throw an error @@ -3004,7 +3270,11 @@ tmpfd (void) *p++ = tohex (((value >> 28) & 0x0f)); value <<= 4; } +#ifdef HAVE_W32CE_SYSTEM + wcscpy (p, L".tmp"); +#else strcpy (p, ".tmp"); +#endif file = CreateFile (buffer, GENERIC_READ | GENERIC_WRITE, 0, @@ -3014,17 +3284,21 @@ tmpfd (void) NULL); if (file != INVALID_HANDLE_VALUE) { +#ifdef HAVE_W32CE_SYSTEM + int fd = (int)file; +#else int fd = _open_osfhandle ((long)file, 0); if (fd == -1) { CloseHandle (file); return -1; } +#endif return fd; } Sleep (1); /* One ms as this is the granularity of GetTickCount. */ } - errno = ENOENT; + _set_errno (ENOENT); return -1; #else /*!HAVE_W32_SYSTEM*/ FILE *fp; @@ -3077,7 +3351,7 @@ es_tmpfile (void) goto out; create_called = 1; - err = es_create (&stream, cookie, fd, estream_functions_fd, modeflags); + err = es_create (&stream, cookie, fd, estream_functions_fd, modeflags, 0); out: @@ -3100,8 +3374,8 @@ es_setvbuf (estream_t ES__RESTRICT stream, { int err; - if (((type == _IOFBF) || (type == _IOLBF) || (type == _IONBF)) - && (! ((! size) && (type != _IONBF)))) + if ((type == _IOFBF || type == _IOLBF || type == _IONBF) + && (!buf || size || type == _IONBF)) { ESTREAM_LOCK (stream); err = es_set_buffering (stream, buf, type, size); @@ -3109,7 +3383,7 @@ es_setvbuf (estream_t ES__RESTRICT stream, } else { - errno = EINVAL; + _set_errno (EINVAL); err = -1; } @@ -3146,6 +3420,68 @@ es_opaque_get (estream_t stream) return opaque; } + +static void +fname_set_internal (estream_t stream, const char *fname, int quote) +{ + if (stream->intern->printable_fname + && !stream->intern->printable_fname_inuse) + { + mem_free (stream->intern->printable_fname); + stream->intern->printable_fname = NULL; + } + if (stream->intern->printable_fname) + return; /* Can't change because it is in use. */ + + if (*fname != '[') + quote = 0; + else + quote = !!quote; + + stream->intern->printable_fname = mem_alloc (strlen (fname) + quote + 1); + if (fname) + { + if (quote) + stream->intern->printable_fname[0] = '\\'; + strcpy (stream->intern->printable_fname+quote, fname); + } +} + + +/* Set the filename attribute of STREAM. There is no error return. + as long as STREAM is valid. This function is called internally by + functions which open a filename. */ +void +es_fname_set (estream_t stream, const char *fname) +{ + if (fname) + { + ESTREAM_LOCK (stream); + fname_set_internal (stream, fname, 1); + ESTREAM_UNLOCK (stream); + } +} + + +/* Return the filename attribute of STREAM. In case no filename has + been set, "[?]" will be returned. The returned file name is valid + as long as STREAM is valid. */ +const char * +es_fname_get (estream_t stream) +{ + const char *fname; + + ESTREAM_LOCK (stream); + fname = stream->intern->printable_fname; + if (fname) + stream->intern->printable_fname_inuse = 1; + ESTREAM_UNLOCK (stream); + if (!fname) + fname = "[?]"; + return fname; +} + + /* Print a BUFFER to STREAM while replacing all control characters and the characters in DELIMITERS by standard C escape sequences. Returns 0 on success or -1 on error. If BYTES_WRITTEN is not NULL diff --git a/common/estream.h b/common/estream.h index d1f94724f..6eb986fd6 100644 --- a/common/estream.h +++ b/common/estream.h @@ -1,5 +1,5 @@ /* estream.h - Extended stream I/O Library - * Copyright (C) 2004, 2005, 2006, 2007 g10 Code GmbH + * Copyright (C) 2004, 2005, 2006, 2007, 2010 g10 Code GmbH * * This file is part of Libestream. * @@ -15,6 +15,40 @@ * * You should have received a copy of the GNU General Public License * along with Libestream; if not, see <http://www.gnu.org/licenses/>. + * + * ALTERNATIVELY, Libestream may be distributed under the terms of the + * following license, in which case the provisions of this license are + * required INSTEAD OF the GNU General Public License. If you wish to + * allow use of your version of this file only under the terms of the + * GNU General Public License, and not to allow others to use your + * version of this file under the terms of the following license, + * indicate your decision by deleting this paragraph and the license + * below. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef ESTREAM_H @@ -46,6 +80,8 @@ #define es_fdopen_nc _ESTREAM_PREFIX(es_fdopen_nc) #define es_fpopen _ESTREAM_PREFIX(es_fpopen) #define es_fpopen_nc _ESTREAM_PREFIX(es_fpopen_nc) +#define _es_set_std_fd _ESTREAM_PREFIX(_es_set_std_fd) +#define _es_get_std_stream _ESTREAM_PREFIX(_es_get_std_stream) #define es_freopen _ESTREAM_PREFIX(es_freopen) #define es_fopencookie _ESTREAM_PREFIX(es_fopencookie) #define es_fclose _ESTREAM_PREFIX(es_fclose) @@ -79,16 +115,21 @@ #define es_fwrite _ESTREAM_PREFIX(es_fwrite) #define es_fgets _ESTREAM_PREFIX(es_fgets) #define es_fputs _ESTREAM_PREFIX(es_fputs) +#define es_fputs_unlocked _ESTREAM_PREFIX(es_fputs_unlocked) #define es_getline _ESTREAM_PREFIX(es_getline) #define es_read_line _ESTREAM_PREFIX(es_read_line) #define es_free _ESTREAM_PREFIX(es_free) -#define es_fprf _ESTREAM_PREFIX(es_fprf) -#define es_vfprf _ESTREAM_PREFIX(es_vfprf) +#define es_fprintf _ESTREAM_PREFIX(es_fprintf) +#define es_fprintf_unlocked _ESTREAM_PREFIX(es_fprintf_unlocked) +#define es_vfprintf _ESTREAM_PREFIX(es_vfprint) +#define es_vfprintf_unlocked _ESTREAM_PREFIX(es_vfprint_unlocked) #define es_setvbuf _ESTREAM_PREFIX(es_setvbuf) #define es_setbuf _ESTREAM_PREFIX(es_setbuf) #define es_tmpfile _ESTREAM_PREFIX(es_tmpfile) #define es_opaque_set _ESTREAM_PREFIX(es_opaque_set) #define es_opaque_get _ESTREAM_PREFIX(es_opaque_get) +#define es_fname_set _ESTREAM_PREFIX(es_fname_set) +#define es_fname_get _ESTREAM_PREFIX(es_fname_get) #define es_write_sanitized_utf8_buffer \ _ESTREAM_PREFIX(es_write_sanitized_utf8_buffer) #endif /*_ESTREAM_EXT_SYM_PREFIX*/ @@ -213,6 +254,14 @@ int es_fclose (estream_t stream); int es_fileno (estream_t stream); int es_fileno_unlocked (estream_t stream); +void _es_set_std_fd (int no, int fd); +estream_t _es_get_std_stream (int fd); + +#define es_stdin _es_get_std_stream (0) +#define es_stdout _es_get_std_stream (1) +#define es_stderr _es_get_std_stream (2) + + void es_flockfile (estream_t stream); int es_ftrylockfile (estream_t stream); void es_funlockfile (estream_t stream); @@ -277,6 +326,8 @@ size_t es_fwrite (const void *ES__RESTRICT ptr, size_t size, size_t memb, char *es_fgets (char *ES__RESTRICT s, int n, estream_t ES__RESTRICT stream); int es_fputs (const char *ES__RESTRICT s, estream_t ES__RESTRICT stream); +int es_fputs_unlocked (const char *ES__RESTRICT s, + estream_t ES__RESTRICT stream); ssize_t es_getline (char *ES__RESTRICT *ES__RESTRICT lineptr, size_t *ES__RESTRICT n, @@ -289,9 +340,17 @@ void es_free (void *a); int es_fprintf (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, ...) _ESTREAM_GCC_A_PRINTF(2,3); +int es_fprintf_unlocked (estream_t ES__RESTRICT stream, + const char *ES__RESTRICT format, ...) + _ESTREAM_GCC_A_PRINTF(2,3); + int es_vfprintf (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, va_list ap) _ESTREAM_GCC_A_PRINTF(2,0); +int es_vfprintf_unlocked (estream_t ES__RESTRICT stream, + const char *ES__RESTRICT format, va_list ap) + _ESTREAM_GCC_A_PRINTF(2,0); + int es_setvbuf (estream_t ES__RESTRICT stream, char *ES__RESTRICT buf, int mode, size_t size); void es_setbuf (estream_t ES__RESTRICT stream, char *ES__RESTRICT buf); @@ -301,6 +360,9 @@ estream_t es_tmpfile (void); void es_opaque_set (estream_t ES__RESTRICT stream, void *ES__RESTRICT opaque); void *es_opaque_get (estream_t stream); +void es_fname_set (estream_t stream, const char *fname); +const char *es_fname_get (estream_t stream); + #ifdef GNUPG_MAJOR_VERSION int es_write_sanitized_utf8_buffer (estream_t stream, @@ -309,7 +371,6 @@ int es_write_sanitized_utf8_buffer (estream_t stream, size_t *bytes_written); #endif /*GNUPG_MAJOR_VERSION*/ - #ifdef __cplusplus } #endif diff --git a/configure.ac b/configure.ac index bf1b39fdc..267c9cb7a 100644 --- a/configure.ac +++ b/configure.ac @@ -87,6 +87,8 @@ GNUPG_BUILD_PROGRAM(scdaemon, yes) GNUPG_BUILD_PROGRAM(tools, yes) GNUPG_BUILD_PROGRAM(doc, yes) GNUPG_BUILD_PROGRAM(symcryptrun, no) +GNUPG_BUILD_PROGRAM(gpgtar, no) + AC_SUBST(PACKAGE) AC_SUBST(PACKAGE_GT) @@ -1108,7 +1110,7 @@ AC_CHECK_FUNCS([unsetenv fcntl ftruncate]) AC_CHECK_FUNCS([gettimeofday getrusage getrlimit setrlimit clock_gettime]) AC_CHECK_FUNCS([atexit raise getpagesize strftime nl_langinfo setlocale]) AC_CHECK_FUNCS([waitpid wait4 sigaction sigprocmask pipe stat getaddrinfo]) -AC_CHECK_FUNCS([ttyname rand ftello fsync]) +AC_CHECK_FUNCS([ttyname rand ftello fsync stat]) AC_CHECK_TYPES([struct sigaction, sigset_t],,,[#include <signal.h>]) @@ -1412,6 +1414,7 @@ AM_CONDITIONAL(BUILD_SCDAEMON, test "$build_scdaemon" = "yes") AM_CONDITIONAL(BUILD_TOOLS, test "$build_tools" = "yes") AM_CONDITIONAL(BUILD_DOC, test "$build_doc" = "yes") AM_CONDITIONAL(BUILD_SYMCRYPTRUN, test "$build_symcryptrun" = "yes") +AM_CONDITIONAL(BUILD_GPGTAR, test "$build_gpgtar" = "yes") AM_CONDITIONAL(RUN_GPG_TESTS, test x$cross_compiling = xno -a "$build_gpg" = yes ) @@ -1521,6 +1524,7 @@ echo " S/MIME: $build_gpgsm Agent: $build_agent $build_agent_threaded Smartcard: $build_scdaemon $build_scdaemon_extra + Gpgtar: $build_gpgtar Protect tool: $show_gnupg_protect_tool_pgm Default agent: $show_gnupg_agent_pgm diff --git a/po/.gitattributes b/po/.gitattributes index 0c8874e0e..17b178c0e 100644 --- a/po/.gitattributes +++ b/po/.gitattributes @@ -1,2 +1,7 @@ +# You should add +#[filter "cleanpo"] +# clean = "awk '/^\"POT-Creation-Date:/&&!s{s=1;next};!/^#: /{print}'" +# to your config file. + /??.po filter=cleanpo /??_??.po filter=cleanpo diff --git a/tools/ChangeLog b/tools/ChangeLog index 039764df4..f7abab373 100644 --- a/tools/ChangeLog +++ b/tools/ChangeLog @@ -1,3 +1,11 @@ +2011-01-11 Werner Koch <[email protected]> + + * gpgtar.c, gpgtar.h, gpgtar-create.c, gpgtar-extract.c + * gpgtar-list.c: New. Take from GnuPG master and add missing + functions. + * Makefile.am (bin_PROGRAMS): Add gpgtar. + (gpgtar_SOURCES, gpgtar_CFLAGS, gpgtar_LDADD): New. + 2010-08-23 Werner Koch <[email protected]> * gpgconf-comp.c (retrieve_options_from_program) diff --git a/tools/Makefile.am b/tools/Makefile.am index 1d04964c0..e07d8e3e0 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -42,7 +42,14 @@ else symcryptrun = endif -bin_PROGRAMS = gpgconf gpg-connect-agent gpgkey2ssh ${symcryptrun} +if BUILD_GPGTAR + gpgtar = gpgtar +else + gpgtar = +endif + + +bin_PROGRAMS = gpgconf gpg-connect-agent gpgkey2ssh ${symcryptrun} ${gpgtar} if !HAVE_W32_SYSTEM bin_PROGRAMS += watchgnupg gpgparsemail endif @@ -99,6 +106,15 @@ gpg_check_pattern_LDADD = $(common_libs) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) endif +gpgtar_SOURCES = \ + gpgtar.c gpgtar.h \ + gpgtar-create.c \ + gpgtar-extract.c \ + gpgtar-list.c \ + no-libgcrypt.c +gpgtar_CFLAGS = $(GPG_ERROR_CFLAGS) $(PTH_CFLAGS) +gpgtar_LDADD = $(common_libs) $(GPG_ERROR_LIBS) $(NETLIBS) $(W32SOCKLIBS) + # Make sure that all libs are build before we use them. This is # important for things like make -j2. $(PROGRAMS): $(common_libs) $(pwquery_libs) ../common/libgpgrl.a diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c new file mode 100644 index 000000000..09587e455 --- /dev/null +++ b/tools/gpgtar-create.c @@ -0,0 +1,903 @@ +/* gpgtar-create.c - Create a TAR archive + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#ifdef HAVE_W32_SYSTEM +# define WIN32_LEAN_AND_MEAN +# include <windows.h> +# include <fcntl.h> /* for setmode() */ +#else /*!HAVE_W32_SYSTEM*/ +# include <unistd.h> +# include <pwd.h> +# include <grp.h> +#endif /*!HAVE_W32_SYSTEM*/ +#include <assert.h> + +#include "i18n.h" +#include "../common/sysutils.h" +#include "gpgtar.h" + +#ifndef HAVE_LSTAT +#define lstat(a,b) stat ((a), (b)) +#endif + + +/* 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; +}; + + + + +/* 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 = utf8_to_wchar (hdr->name); + for (p=hdr->name; *p; p++) + if (*p == '\\') + *p = '/'; + if (!wfname) + { + log_error ("error utf8-ing `%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 obje`<ct 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 director 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); + + 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 + { + if (opt.verbose) + gpgtar_print_header (hdr, es_stderr); + *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 + 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 = utf8_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 utf8-ing 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++; + + 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 == '/' || *pattern == DIRSEP_C) + 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; + + 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 gpg_error_t +build_header (void *record, tar_header_t hdr) +{ + gpg_error_t err; + struct ustar_raw_header *raw = record; + size_t namelen, n; + unsigned long chksum; + unsigned char *p; + + memset (record, 0, RECORDSIZE); + + /* 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 + { + err = gpg_error (GPG_ERR_TOO_LARGE); + log_error ("error storing file `%s': %s\n", + hdr->name, gpg_strerror (err)); + return err; + } + } + + 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; + + 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; + } +#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] = ' '; + + return 0; +} + + +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; + int any; + + err = build_header (record, hdr); + 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"); + 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; + + err = write_record (stream, record); + if (err) + goto leave; + + 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; + } + 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)); + + 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. */ +void +gpgtar_create (char **inpattern) +{ + gpg_error_t err = 0; + struct scanctrl_s scanctrl_buffer; + scanctrl_t scanctrl = &scanctrl_buffer; + tar_header_t hdr, *start_tail; + estream_t outstream = NULL; + int eof_seen = 0; + +#ifdef HAVE_DOSISH_SYSTEM + if (!inpattern) + setmode (es_fileno (es_stdin), O_BINARY); +#endif + + memset (scanctrl, 0, sizeof *scanctrl); + scanctrl->flist_tail = &scanctrl->flist; + + 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 = xtrystrdup (pattern); + } + else /* Read null delimited pattern from stdin. */ + { + int c; + char namebuf[4096]; + size_t n = 0; + + for (;;) + { + if ((c = es_getc (es_stdin)) == EOF) + { + if (es_ferror (es_stdin)) + { + err = gpg_error_from_syserror (); + log_error ("error reading `%s': %s\n", + "[stdin]", strerror (errno)); + goto leave; + } + /* Note: The Nul is a delimiter and not a terminator. */ + c = 0; + eof_seen = 1; + } + if (n >= sizeof namebuf - 1) + { + if (!skip_this) + { + skip_this = 1; + log_error ("error reading `%s': %s\n", + "[stdin]", "filename too long"); + } + } + else + namebuf[n++] = c; + if (!c) + { + namebuf[n] = 0; + break; + } + } + + if (skip_this || n < 2) + continue; + + pat = xtrystrdup (namebuf); + } + + 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 (opt.outfile) + { + if (!strcmp (opt.outfile, "-")) + outstream = es_stdout; + else + outstream = es_fopen (opt.outfile, "wb"); + if (!outstream) + { + err = gpg_error_from_syserror (); + log_error (_("can't create `%s': %s\n"), + opt.outfile, gpg_strerror (err)); + goto leave; + } + } + else + { + outstream = es_stdout; + } + +#ifdef HAVE_DOSISH_SYSTEM + if (outstream == es_stdout) + setmode (es_fileno (es_stdout), O_BINARY); +#endif + + for (hdr = scanctrl->flist; hdr; hdr = hdr->next) + { + err = write_file (outstream, hdr); + if (err) + goto leave; + } + err = write_eof_mark (outstream); + + leave: + if (!err) + { + if (outstream != es_stdout) + err = es_fclose (outstream); + else + err = es_fflush (outstream); + outstream = NULL; + } + if (err) + { + log_error ("creating tarball `%s' failed: %s\n", + es_fname_get (outstream), gpg_strerror (err)); + if (outstream && outstream != es_stdout) + es_fclose (outstream); + if (opt.outfile) + remove (opt.outfile); + } + scanctrl->flist_tail = NULL; + while ( (hdr = scanctrl->flist) ) + { + scanctrl->flist = hdr->next; + xfree (hdr); + } +} diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c new file mode 100644 index 000000000..736c7fc4b --- /dev/null +++ b/tools/gpgtar-extract.c @@ -0,0 +1,349 @@ +/* gpgtar-extract.c - Extract from a TAR archive + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <assert.h> +#ifdef HAVE_W32_SYSTEM +# include <fcntl.h> /* for setmode() */ +#endif /*HAVE_W32_SYSTEM*/ + +#include "i18n.h" +#include "../common/sysutils.h" +#include "gpgtar.h" + +#ifndef GPG_ERR_LIMIT_REACHED +#define GPG_ERR_LIMIT_REACHED 183 +#endif + + +static gpg_error_t +extract_regular (estream_t stream, const char *dirname, + tar_header_t hdr) +{ + gpg_error_t err; + char record[RECORDSIZE]; + size_t n, nbytes, nwritten; + char *fname; + estream_t outfp = NULL; + + fname = strconcat (dirname, "/", hdr->name, NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + log_error ("error creating filename: %s\n", gpg_strerror (err)); + goto leave; + } + else + err = 0; + + outfp = es_fopen (fname, "wb"); + 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; + n++; + nbytes = (n < hdr->nrecords)? RECORDSIZE : (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 (remove (fname)) + log_error ("error removing incomplete file `%s': %s\n", + fname, gpg_strerror (gpg_error_from_syserror ())); + } + xfree (fname); + return err; +} + + +static gpg_error_t +extract_directory (const char *dirname, tar_header_t hdr) +{ + gpg_error_t err; + char *fname; + size_t prefixlen; + + prefixlen = strlen (dirname) + 1; + fname = strconcat (dirname, "/", hdr->name, NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + log_error ("error creating filename: %s\n", gpg_strerror (err)); + goto leave; + } + else + err = 0; + + if (fname[strlen (fname)-1] == '/') + fname[strlen (fname)-1] = 0; + + /* Note that we don't need to care about EEXIST because we always + extract into a new hierarchy. */ + if (gnupg_mkdir (fname, "-rwx------")) + { + err = gpg_error_from_syserror (); + 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. */ + char *p; + int rc = 0; + + 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, tar_header_t hdr) +{ + gpg_error_t err; + size_t n; + + n = strlen (hdr->name); +#ifdef HAVE_DOSISH_SYSTEM + if (strchr (hdr->name, '\\')) + { + log_error ("filename `%s' contains a backslash - " + "can't extract on this system\n", hdr->name); + return gpg_error (GPG_ERR_INV_NAME); + } +#endif /*HAVE_DOSISH_SYSTEM*/ + + if (!n + || strstr (hdr->name, "//") + || strstr (hdr->name, "/../") + || !strncmp (hdr->name, "../", 3) + || (n >= 3 && !strcmp (hdr->name+n-3, "/.." ))) + { + log_error ("filename `%s' as suspicious parts - not extracting\n", + hdr->name); + return gpg_error (GPG_ERR_INV_NAME); + } + + if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN) + err = extract_regular (stream, dirname, hdr); + else if (hdr->typeflag == TF_DIRECTORY) + err = extract_directory (dirname, hdr); + 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); + } + 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 "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; +} + + + +void +gpgtar_extract (const char *filename) +{ + gpg_error_t err; + estream_t stream; + tar_header_t header = NULL; + const char *dirprefix = NULL; + char *dirname = NULL; + + if (filename) + { + if (!strcmp (filename, "-")) + stream = es_stdin; + else + stream = es_fopen (filename, "rb"); + if (!stream) + { + err = gpg_error_from_syserror (); + log_error ("error opening `%s': %s\n", filename, gpg_strerror (err)); + return; + } + } + else + stream = es_stdin; + +#ifdef HAVE_DOSISH_SYSTEM + if (stream == es_stdin) + setmode (es_fileno (es_stdin), O_BINARY); +#endif + + if (filename && stream != es_stdin) + { + dirprefix = strrchr (filename, '/'); + if (dirprefix) + dirprefix++; + else + dirprefix = filename; + } + else if (opt.filename) + { + dirprefix = strrchr (opt.filename, '/'); + if (dirprefix) + dirprefix++; + else + dirprefix = opt.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); + + for (;;) + { + header = gpgtar_read_header (stream); + if (!header) + goto leave; + + if (extract (stream, dirname, header)) + goto leave; + xfree (header); + header = NULL; + } + + + leave: + xfree (header); + xfree (dirname); + if (stream != es_stdin) + es_fclose (stream); + return; +} diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c new file mode 100644 index 000000000..0df7a26dc --- /dev/null +++ b/tools/gpgtar-list.c @@ -0,0 +1,332 @@ +/* gpgtar-list.c - List a TAR archive + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <fcntl.h> + +#include "i18n.h" +#include "gpgtar.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) +{ + const struct ustar_raw_header *raw = record; + size_t n, namelen, prefixlen; + tar_header_t header; + int use_prefix; + + 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 byte\n", filename); + for (n=namelen+1; n < sizeof raw->name; n++) + if (raw->name[n]) + { + log_info ("%s: warning: garbage after name\n", filename); + 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 byte\n", + filename); + for (n=prefixlen+1; n < sizeof raw->prefix; n++) + if (raw->prefix[n]) + { + log_info ("%s: warning: garbage after prefix\n", filename); + 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; + default: header->typeflag = TF_UNKNOWN; break; + } + + + /* Compute the number of data records following this header. */ + if (header->typeflag == TF_REGULAR || header->typeflag == TF_UNKNOWN) + header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE; + else + header->nrecords = 0; + + + return header; +} + + + +/* Read the next block, assming it is a tar header. Returns a header + object on success or NULL one error. In case of an error an error + message has been printed. */ +static tar_header_t +read_header (estream_t stream) +{ + gpg_error_t err; + char record[RECORDSIZE]; + int i; + + err = read_record (stream, record); + if (err) + return NULL; + + 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 NULL; + + 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. */ + return NULL; + } + } + + return parse_header (record, es_fname_get (stream)); +} + + +/* Skip the data records according to HEADER. Prints an error message + on error and return -1. */ +static int +skip_data (estream_t stream, 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; + } + + return 0; +} + + + +static void +print_header (tar_header_t header, estream_t out) +{ + unsigned long mask; + char modestr[10+1]; + int i; + + *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_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; + + es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s\n", + modestr, header->nlink, header->uid, header->gid, header->size, + isotimestamp (header->mtime), header->name); +} + + + +/* List the tarball FILENAME or, if FILENAME is NULL, the tarball read + from stdin. */ +void +gpgtar_list (const char *filename) +{ + gpg_error_t err; + estream_t stream; + tar_header_t header; + + if (filename) + { + if (!strcmp (filename, "-")) + stream = es_stdin; + else + stream = es_fopen (filename, "rb"); + if (!stream) + { + err = gpg_error_from_syserror (); + log_error ("error opening `%s': %s\n", filename, gpg_strerror (err)); + return; + } + } + else + stream = es_stdin; + +#ifdef HAVE_DOSISH_SYSTEM + if (stream == es_stdin) + setmode (es_fileno (es_stdin), O_BINARY); +#endif + + for (;;) + { + header = read_header (stream); + if (!header) + goto leave; + + print_header (header, es_stdout); + + if (skip_data (stream, header)) + goto leave; + xfree (header); + header = NULL; + } + + + leave: + xfree (header); + if (stream != es_stdin) + es_fclose (stream); + return; +} + +tar_header_t +gpgtar_read_header (estream_t stream) +{ + /*FIXME: Change to return an error code. */ + return read_header (stream); +} + +void +gpgtar_print_header (tar_header_t header, estream_t out) +{ + if (header && out) + print_header (header, out); +} diff --git a/tools/gpgtar.c b/tools/gpgtar.c new file mode 100644 index 000000000..f88964fec --- /dev/null +++ b/tools/gpgtar.c @@ -0,0 +1,538 @@ +/* gpgtar.c - A simple TAR implementation mainly useful for Windows. + * 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 <http://www.gnu.org/licenses/>. + */ + +/* GnuPG comes with a shell script gpg-zip which creates archive files + in the same format as PGP Zip, which is actually a USTAR format. + That is fine and works nicely on all Unices but for Windows we + don't have a compatible shell and the supply of tar programs is + limited. Given that we need just a few tar option and it is an + open question how many Unix concepts are to be mapped to Windows, + we might as well write our own little tar customized for use with + gpg. So here we go. */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#ifdef HAVE_STAT +# include <sys/stat.h> +#endif + +#include "util.h" +#include "i18n.h" +#include "sysutils.h" +#include "../common/openpgpdefs.h" + +#include "gpgtar.h" + + +/* Constants to identify the commands and options. */ +enum cmd_and_opt_values + { + aNull = 0, + aEncrypt = 'e', + aDecrypt = 'd', + aSign = 's', + aList = 't', + + oSymmetric = 'c', + oRecipient = 'r', + oUser = 'u', + oOutput = 'o', + oQuiet = 'q', + oVerbose = 'v', + oFilesFrom = 'T', + oNoVerbose = 500, + + aSignEncrypt, + oSkipCrypto, + oOpenPGP, + oCMS, + oSetFilename, + oNull + }; + + +/* The list of commands and options. */ +static ARGPARSE_OPTS opts[] = { + ARGPARSE_group (300, N_("@Commands:\n ")), + + ARGPARSE_c (aEncrypt, "encrypt", N_("create an archive")), + ARGPARSE_c (aDecrypt, "decrypt", N_("extract an archive")), + ARGPARSE_c (aSign, "sign", N_("create a signed archive")), + ARGPARSE_c (aList, "list-archive", N_("list an archive")), + + ARGPARSE_group (301, N_("@\nOptions:\n ")), + + ARGPARSE_s_n (oSymmetric, "symmetric", N_("use symmetric encryption")), + ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), + ARGPARSE_s_s (oUser, "local-user", + N_("|USER-ID|use USER-ID to sign or decrypt")), + ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_n (oSkipCrypto, "skip-crypto", N_("skip the crypto processing")), + ARGPARSE_s_s (oSetFilename, "set-filename", "@"), + ARGPARSE_s_s (oFilesFrom, "files-from", + N_("|FILE|get names to create from FILE")), + ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")), + ARGPARSE_s_n (oOpenPGP, "openpgp", "@"), + ARGPARSE_s_n (oCMS, "cms", "@"), + + ARGPARSE_end () +}; + + + +static void tar_and_encrypt (char **inpattern); +static void decrypt_and_untar (const char *fname); +static void decrypt_and_list (const char *fname); + + + + +/* Print usage information and and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 11: p = "gpgtar (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: + p = _("Usage: gpgtar [options] [files] [directories] (-h for help)"); + break; + case 41: + p = _("Syntax: gpgtar [options] [files] [directories]\n" + "Encrypt or sign files into an archive\n"); + break; + + default: p = NULL; break; + } + return p; +} + + +static void +set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) +{ + enum cmd_and_opt_values cmd = *ret_cmd; + + if (!cmd || cmd == new_cmd) + cmd = new_cmd; + else if (cmd == aSign && new_cmd == aEncrypt) + cmd = aSignEncrypt; + else if (cmd == aEncrypt && new_cmd == aSign) + cmd = aSignEncrypt; + else + { + log_error (_("conflicting commands\n")); + exit (2); + } + + *ret_cmd = cmd; +} + + + +/* gpgtar main. */ +int +main (int argc, char **argv) +{ + ARGPARSE_ARGS pargs; + const char *fname; + int no_more_options = 0; + enum cmd_and_opt_values cmd = 0; + int skip_crypto = 0; + const char *files_from = NULL; + int null_names = 0; + + assert (sizeof (struct ustar_raw_header) == 512); + + gnupg_reopen_std ("gpgtar"); + set_strusage (my_strusage); + log_set_prefix ("gpgtar", 1); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (); + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = ARGPARSE_FLAG_KEEP; + while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oOutput: opt.outfile = pargs.r.ret_str; break; + case oSetFilename: opt.filename = pargs.r.ret_str; break; + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oNoVerbose: opt.verbose = 0; break; + case oFilesFrom: files_from = pargs.r.ret_str; break; + case oNull: null_names = 1; break; + + case aList: + case aDecrypt: + case aEncrypt: + case aSign: + set_cmd (&cmd, pargs.r_opt); + break; + + case oSymmetric: + set_cmd (&cmd, aEncrypt); + opt.symmetric = 1; + break; + + case oSkipCrypto: + skip_crypto = 1; + break; + + case oOpenPGP: /* Dummy option for now. */ break; + case oCMS: /* Dummy option for now. */ break; + + default: pargs.err = 2; break; + } + } + + if ((files_from && !null_names) || (!files_from && null_names)) + log_error ("--files-from and --null may only be used in conjunction\n"); + if (files_from && strcmp (files_from, "-")) + log_error ("--files-from only supports argument \"-\"\n"); + + if (log_get_errorcount (0)) + exit (2); + + switch (cmd) + { + case aList: + if (argc > 1) + usage (1); + fname = argc ? *argv : NULL; + if (opt.filename) + log_info ("note: ignoring option --set-filename\n"); + if (files_from) + log_info ("note: ignoring option --files-from\n"); + if (skip_crypto) + gpgtar_list (fname); + else + decrypt_and_list (fname); + break; + + case aEncrypt: + if ((!argc && !null_names) + || (argc && null_names)) + usage (1); + if (opt.filename) + log_info ("note: ignoring option --set-filename\n"); + if (skip_crypto) + gpgtar_create (null_names? NULL :argv); + else + tar_and_encrypt (null_names? NULL : argv); + break; + + case aDecrypt: + if (argc != 1) + usage (1); + if (opt.outfile) + log_info ("note: ignoring option --output\n"); + if (files_from) + log_info ("note: ignoring option --files-from\n"); + fname = argc ? *argv : NULL; + if (skip_crypto) + gpgtar_extract (fname); + else + decrypt_and_untar (fname); + break; + + default: + log_error (_("invalid command (there is no implicit command)\n")); + break; + } + + return log_get_errorcount (0)? 1:0; +} + + +/* Read the next record from STREAM. RECORD is a buffer provided by + the caller and must be at leadt of size RECORDSIZE. The function + return 0 on success and and error code on failure; a diagnostic + printed as well. Note that there is no need for an EOF indicator + because a tarball has an explicit EOF record. */ +gpg_error_t +read_record (estream_t stream, void *record) +{ + gpg_error_t err; + size_t nread; + + nread = es_fread (record, 1, RECORDSIZE, stream); + if (nread != RECORDSIZE) + { + err = gpg_error_from_syserror (); + if (es_ferror (stream)) + log_error ("error reading `%s': %s\n", + es_fname_get (stream), gpg_strerror (err)); + else + log_error ("error reading `%s': premature EOF " + "(size of last record: %zu)\n", + es_fname_get (stream), nread); + } + else + err = 0; + + return err; +} + + +/* Write the RECORD of size RECORDSIZE to STREAM. FILENAME is the + name of the file used for diagnostics. */ +gpg_error_t +write_record (estream_t stream, const void *record) +{ + gpg_error_t err; + size_t nwritten; + + nwritten = es_fwrite (record, 1, RECORDSIZE, stream); + if (nwritten != RECORDSIZE) + { + err = gpg_error_from_syserror (); + log_error ("error writing `%s': %s\n", + es_fname_get (stream), gpg_strerror (err)); + } + else + err = 0; + + return err; +} + + +/* Return true if FP is an unarmored OpenPGP message. Note that this + fucntion reads a few bytes from FP but pushes them back. */ +#if 0 +static int +openpgp_message_p (estream_t fp) +{ + int ctb; + + ctb = es_getc (fp); + if (ctb != EOF) + { + if (es_ungetc (ctb, fp)) + log_fatal ("error ungetting first byte: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + + if ((ctb & 0x80)) + { + switch ((ctb & 0x40) ? (ctb & 0x3f) : ((ctb>>2)&0xf)) + { + case PKT_MARKER: + case PKT_SYMKEY_ENC: + case PKT_ONEPASS_SIG: + case PKT_PUBKEY_ENC: + case PKT_SIGNATURE: + case PKT_COMMENT: + case PKT_OLD_COMMENT: + case PKT_PLAINTEXT: + case PKT_COMPRESSED: + case PKT_ENCRYPTED: + return 1; /* Yes, this seems to be an OpenPGP message. */ + default: + break; + } + } + } + return 0; +} +#endif + + + + +static void +tar_and_encrypt (char **inpattern) +{ + (void)inpattern; + log_error ("tar_and_encrypt has not yet been implemented\n"); +} + + + +static void +decrypt_and_untar (const char *fname) +{ + (void)fname; + log_error ("decrypt_and_untar has not yet been implemented\n"); +} + + + +static void +decrypt_and_list (const char *fname) +{ + (void)fname; + log_error ("decrypt_and_list has not yet been implemented\n"); +} + + + + +/* A wrapper around mkdir which takes a string for the mode argument. + This makes it easier to handle the mode argument which is not + defined on all systems. The format of the modestring is + + "-rwxrwxrwx" + + '-' is a don't care or not set. 'r', 'w', 'x' are read allowed, + write allowed, execution allowed with the first group for the user, + the second for the group and the third for all others. If the + string is shorter than above the missing mode characters are meant + to be not set. */ +int +gnupg_mkdir (const char *name, const char *modestr) +{ +#ifdef HAVE_W32CE_SYSTEM + wchar_t *wname; + (void)modestr; + + wname = utf8_to_wchar (name); + if (!wname) + return -1; + if (!CreateDirectoryW (wname, NULL)) + { + xfree (wname); + return -1; /* ERRNO is automagically provided by gpg-error.h. */ + } + xfree (wname); + return 0; +#elif MKDIR_TAKES_ONE_ARG + (void)modestr; + /* Note: In the case of W32 we better use CreateDirectory and try to + set appropriate permissions. However using mkdir is easier + because this sets ERRNO. */ + return mkdir (name); +#else + mode_t mode = 0; + + if (modestr && *modestr) + { + modestr++; + if (*modestr && *modestr++ == 'r') + mode |= S_IRUSR; + if (*modestr && *modestr++ == 'w') + mode |= S_IWUSR; + if (*modestr && *modestr++ == 'x') + mode |= S_IXUSR; + if (*modestr && *modestr++ == 'r') + mode |= S_IRGRP; + if (*modestr && *modestr++ == 'w') + mode |= S_IWGRP; + if (*modestr && *modestr++ == 'x') + mode |= S_IXGRP; + if (*modestr && *modestr++ == 'r') + mode |= S_IROTH; + if (*modestr && *modestr++ == 'w') + mode |= S_IWOTH; + if (*modestr && *modestr++ == 'x') + mode |= S_IXOTH; + } + return mkdir (name, mode); +#endif +} + +#ifdef HAVE_W32_SYSTEM +/* Return a malloced string encoded in UTF-8 from the wide char input + string STRING. Caller must free this value. Returns NULL and sets + ERRNO on failure. Calling this function with STRING set to NULL is + not defined. */ +char * +wchar_to_utf8 (const wchar_t *string) +{ + int n; + char *result; + + n = WideCharToMultiByte (CP_UTF8, 0, string, -1, NULL, 0, NULL, NULL); + if (n < 0) + { + errno = EINVAL; + return NULL; + } + + result = xtrymalloc (n+1); + if (!result) + return NULL; + + n = WideCharToMultiByte (CP_UTF8, 0, string, -1, result, n, NULL, NULL); + if (n < 0) + { + xfree (result); + errno = EINVAL; + result = NULL; + } + return result; +} + + +/* Return a malloced wide char string from an UTF-8 encoded input + string STRING. Caller must free this value. Returns NULL and sets + ERRNO on failure. Calling this function with STRING set to NULL is + not defined. */ +wchar_t * +utf8_to_wchar (const char *string) +{ + int n; + size_t nbytes; + wchar_t *result; + + n = MultiByteToWideChar (CP_UTF8, 0, string, -1, NULL, 0); + if (n < 0) + { + errno = EINVAL; + return NULL; + } + + nbytes = (size_t)(n+1) * sizeof(*result); + if (nbytes / sizeof(*result) != (n+1)) + { + errno = ENOMEM; + return NULL; + } + result = xtrymalloc (nbytes); + if (!result) + return NULL; + + n = MultiByteToWideChar (CP_UTF8, 0, string, -1, result, n); + if (n < 0) + { + free (result); + errno = EINVAL; + result = NULL; + } + return result; +} +#endif /*HAVE_W32_SYSTEM*/ diff --git a/tools/gpgtar.h b/tools/gpgtar.h new file mode 100644 index 000000000..579089469 --- /dev/null +++ b/tools/gpgtar.h @@ -0,0 +1,132 @@ +/* gpgtar.h - Global definitions for gpgtar + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef GPGTAR_H +#define GPGTAR_H + +#include "../common/util.h" +#include "../common/estream.h" + +/* We keep all global options in the structure OPT. */ +struct +{ + int verbose; + int quiet; + const char *outfile; + int symmetric; + const char *filename; +} opt; + + +/* The size of a tar record. All IO is done in chunks of this size. + Note that we don't care about blocking because this version of tar + is not expected to be used directly on a tape drive in fact it is + used in a pipeline with GPG and thus any blocking would be + useless. */ +#define RECORDSIZE 512 + + +/* Description of the USTAR header format. */ +struct ustar_raw_header +{ + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; + char pad[12]; +}; + + +/* Filetypes as defined by USTAR. */ +typedef enum + { + TF_REGULAR, + TF_HARDLINK, + TF_SYMLINK, + TF_CHARDEV, + TF_BLOCKDEV, + TF_DIRECTORY, + TF_FIFO, + TF_RESERVED, + TF_UNKNOWN, /* Needs to be treated as regular file. */ + TF_NOTSUP /* Not supported (used with --create). */ + } typeflag_t; + + +/* The internal represenation of a TAR header. */ +struct tar_header_s; +typedef struct tar_header_s *tar_header_t; +struct tar_header_s +{ + tar_header_t next; /* Used to build a linked list iof entries. */ + + unsigned long mode; /* The file mode. */ + unsigned long nlink; /* Number of hard links. */ + unsigned long uid; /* The user id of the file. */ + unsigned long gid; /* The group id of the file. */ + unsigned long long size; /* The size of the file. */ + unsigned long long mtime; /* Modification time since Epoch. Note + that we don't use time_t here but a + type which is more likely to be larger + that 32 bit and thus allows to track + times beyond 2106. */ + typeflag_t typeflag; /* The type of the file. */ + + + unsigned long long nrecords; /* Number of data records. */ + + char name[1]; /* Filename (dynamically extended). */ +}; + + +/*-- gpgtar.c --*/ +gpg_error_t read_record (estream_t stream, void *record); +gpg_error_t write_record (estream_t stream, const void *record); + +int gnupg_mkdir (const char *name, const char *modestr); +#ifdef HAVE_W32_SYSTEM +char *wchar_to_utf8 (const wchar_t *string); +wchar_t *utf8_to_wchar (const char *string); +#endif + +/*-- gpgtar-create.c --*/ +void gpgtar_create (char **inpattern); + +/*-- gpgtar-extract.c --*/ +void gpgtar_extract (const char *filename); + +/*-- gpgtar-list.c --*/ +void gpgtar_list (const char *filename); +tar_header_t gpgtar_read_header (estream_t stream); +void gpgtar_print_header (tar_header_t header, estream_t out); + + +#endif /*GPGTAR_H*/ |