libclamav/untar.c
a8b7c1dd
 /*
45f8ad14
  *  Copyright (C) 2000-2007 Nigel Horne <njh@bandsman.co.uk>
a8b7c1dd
  *
  *  This program 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 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program 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, write to the Free Software
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
a8b7c1dd
  *
bb3fdd1b
  * Much of this code is based on minitar.c which is in the public domain.
95fb46e5
  * Author: Charles G. Waldman (cgw@pgt.com),  Aug 4 1998
63943caf
  * There are many tar files that this code cannot decode.
a8b7c1dd
  */
95e11e5a
 static	char	const	rcsid[] = "$Id: untar.c,v 1.35 2007/02/12 20:46:09 njh Exp $";
dea34e7d
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
95fb46e5
 
 #include <stdio.h>
 #include <errno.h>
 #include <string.h>
45b28aba
 #ifdef	HAVE_UNISTD_H
95fb46e5
 #include <unistd.h>
45b28aba
 #endif
95fb46e5
 #include <sys/stat.h>
bb3fdd1b
 #include <fcntl.h>
45b28aba
 #ifdef	HAVE_SYS_PARAM_H
7c5a7a47
 #include <sys/param.h>	/* for NAME_MAX */
45b28aba
 #endif
95fb46e5
 
 #include "clamav.h"
 #include "others.h"
bb3fdd1b
 #include "untar.h"
11b50569
 #include "mbox.h"
 #include "blob.h"
95fb46e5
 
 #define BLOCKSIZE 512
 
834f22d7
 #ifndef	O_BINARY
 #define	O_BINARY	0
 #endif
 
bb3fdd1b
 static int
 octal(const char *str)
95fb46e5
 {
ed734e16
 	int ret;
95fb46e5
 
ed734e16
 	if(sscanf(str, "%o", (unsigned int *)&ret) != 1)
 		return -1;
95fb46e5
 	return ret;
 }
 
 int
9140eb10
 cli_untar(const char *dir, int desc, unsigned int posix, const struct cl_limits *limits)
95fb46e5
 {
 	int size = 0;
 	int in_block = 0;
9140eb10
 	unsigned int files = 0;
bb3fdd1b
 	char fullname[NAME_MAX + 1];
f0931086
 	FILE *outfile = NULL;
95fb46e5
 
 	cli_dbgmsg("In untar(%s, %d)\n", dir ? dir : "", desc);
 
 	for(;;) {
 		char block[BLOCKSIZE];
9c107190
 		const int nread = cli_readn(desc, block, (unsigned int)sizeof(block));
95fb46e5
 
 		if(!in_block && nread == 0)
 			break;
 
f0931086
 		if(nread < 0) {
a7f44dcb
 			if(outfile)
 				fclose(outfile);
f0931086
 			cli_errmsg("cli_untar: block read error\n");
95fb46e5
 			return CL_EIO;
 		}
 
 		if(!in_block) {
 			char type;
6f303447
 			const char *suffix;
bb3fdd1b
 			size_t suffixLen = 0;
d2888b89
 			int fd, directory, skipEntry = 0;
bb3fdd1b
 			char magic[7], name[101], osize[13];
 
 			if(outfile) {
 				if(fclose(outfile)) {
 					cli_errmsg("cli_untar: cannot close file %s\n",
45b28aba
 						fullname);
bb3fdd1b
 					return CL_EIO;
 				}
 				outfile = (FILE*)0;
 			}
95fb46e5
 
7c5a7a47
 			if(block[0] == '\0')	/* We're done */
95fb46e5
 				break;
 
9140eb10
 			if(limits && limits->maxfiles && (files >= limits->maxfiles)) {
 				cli_dbgmsg("cli_untar: number of files exceeded %u\n", limits->maxfiles);
 				return CL_CLEAN;
 			}
 
90e80a54
 			/* Notice assumption that BLOCKSIZE > 262 */
a7f5fd00
 			if(posix) {
 				strncpy(magic, block+257, 5);
 				magic[5] = '\0';
 				if(strcmp(magic, "ustar") != 0) {
 					cli_dbgmsg("Incorrect magic string '%s' in tar header\n", magic);
 					return CL_EFORMAT;
 				}
95fb46e5
 			}
 
 			type = block[156];
 
63943caf
 			/*
 			 * Extra types from djgardner@users.sourceforge.net
 			 */
95fb46e5
 			switch(type) {
45f8ad14
 				default:
 					cli_warnmsg("cli_untar: unknown type flag %c\n", type);
63943caf
 				case '0':	/* plain file */
 				case '\0':	/* plain file */
 				case '7':	/* contiguous file */
45f8ad14
 				case 'M':	/* continuation of a file from another volume; might as well scan it. */
9140eb10
 					files++;
95fb46e5
 					directory = 0;
 					break;
ab592ce9
 				case '1':	/* Link to already archived file */
63943caf
 				case '5':	/* directory */
 				case '2':	/* sym link */
 				case '3':	/* char device */
 				case '4':	/* block device */
 				case '6':	/* fifo special */
d2888b89
 				case 'V':	/* Volume header */
95fb46e5
 					directory = 1;
 					break;
d2888b89
 				case 'K':
 				case 'L':
 					/* GNU extension - ././@LongLink
 					 * Discard the blocks with the extended filename,
 					 * the last header will contain parts of it anyway
 					 */
45f8ad14
 				case 'N': 	/* Old GNU format way of storing long filenames. */
 				case 'A':	/* Solaris ACL */
 				case 'E':	/* Solaris Extended attribute s*/
 				case 'I':	/* Inode only */
 				case 'g':	/* Global extended header */
 				case 'x': 	/* Extended attributes */
 				case 'X':	/* Extended attributes (POSIX) */
d2888b89
 					directory = 0;
 					skipEntry = 1;
 					break;
95fb46e5
 			}
 
63943caf
 			if(directory) {
 				in_block = 0;
95fb46e5
 				continue;
63943caf
 			}
95fb46e5
 
d2888b89
 			strncpy(osize, block+124, 12);
 			osize[12] = '\0';
 			size = octal(osize);
 			if(size < 0) {
 				cli_errmsg("Invalid size in tar header\n");
9c9ca17e
 				if(outfile)
 					fclose(outfile);
d2888b89
 				return CL_EFORMAT;
 			}
 			cli_dbgmsg("cli_untar: size = %d\n", size);
9140eb10
 			if(limits && limits->maxfilesize && ((unsigned int)size > limits->maxfilesize)) {
 				cli_dbgmsg("cli_untar: size exceeded %d bytes\n", size);
 				skipEntry++;
 			}
d2888b89
 
 			if(skipEntry) {
 				const int nskip = (size % BLOCKSIZE || !size) ? size + BLOCKSIZE - (size % BLOCKSIZE) : size;
9140eb10
 
 				cli_dbgmsg("cli_untar: skipping entry\n");
d2888b89
 				lseek(desc, nskip, SEEK_CUR);
 				continue;
 			}
 
bb3fdd1b
 			strncpy(name, block, 100);
 			name[100] = '\0';
95fb46e5
 
bb3fdd1b
 			/*
 			 * see also fileblobSetFilename()
 			 * TODO: check if the suffix needs to be put back
 			 */
11b50569
 			sanitiseName(name);
bb3fdd1b
 			suffix = strrchr(name, '.');
 			if(suffix == NULL)
 				suffix = "";
 			else {
 				suffixLen = strlen(suffix);
 				if(suffixLen > 4) {
 					/* Found a full stop which isn't a suffix */
 					suffix = "";
 					suffixLen = 0;
95fb46e5
 				}
 			}
bb3fdd1b
 			snprintf(fullname, sizeof(fullname) - 1 - suffixLen, "%s/%.*sXXXXXX", dir,
 				(int)(sizeof(fullname) - 9 - suffixLen - strlen(dir)), name);
 #if	defined(C_LINUX) || defined(C_BSD) || defined(HAVE_MKSTEMP) || defined(C_SOLARIS) || defined(C_CYGWIN)
 			fd = mkstemp(fullname);
 #else
 			(void)mktemp(fullname);
 			fd = open(fullname, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_BINARY, 0600);
 #endif
 
 			if(fd < 0) {
 				cli_errmsg("Can't create temporary file %s: %s\n", fullname, strerror(errno));
95e11e5a
 				cli_dbgmsg("%lu %lu %lu\n",
 					(unsigned long)suffixLen,
 					(unsigned long)sizeof(fullname),
 					(unsigned long)strlen(fullname));
bb3fdd1b
 				return CL_ETMPFILE;
 			}
 
 			cli_dbgmsg("cli_untar: extracting %s\n", fullname);
 
 			in_block = 1;
 			if((outfile = fdopen(fd, "wb")) == NULL) {
 				cli_errmsg("cli_untar: cannot create file %s\n",
45b28aba
 					fullname);
bb3fdd1b
 				close(fd);
 				return CL_ETMPFILE;
 			}
95fb46e5
 		} else { /* write or continue writing file contents */
 			const int nbytes = size>512? 512:size;
45b28aba
 			const int nwritten = (int)fwrite(block, 1, (size_t)nbytes, outfile);
95fb46e5
 
 			if(nwritten != nbytes) {
585756f1
 				cli_errmsg("cli_untar: only wrote %d bytes to file %s (out of disc space?)\n",
95fb46e5
 					nwritten, fullname);
9c9ca17e
 				if(outfile)
 					fclose(outfile);
c95e404a
 				return CL_EIO;
95fb46e5
 			}
 			size -= nbytes;
 		}
d4112005
 		if (size == 0)
 			in_block = 0;
95fb46e5
 	}
bb3fdd1b
 	if(outfile)
1318d845
 		return fclose(outfile);
 
bbd221d0
 	return 0;
95fb46e5
 }