libclamav/untar.c
6d91b5f0
 /*
  *  Copyright (C) 2004 Nigel Horne <njh@bandsman.co.uk>
  *
  *  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
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  *
f92f5b94
  * Much of this code is based on minitar.c which is in the public domain.
a0dfa4f3
  * Author: Charles G. Waldman (cgw@pgt.com),  Aug 4 1998
f49103d6
  * There are many tar files that this code cannot decode.
a0dfa4f3
  *
6d91b5f0
  * Change History:
  * $Log: untar.c,v $
255e314c
  * Revision 1.25  2005/03/22 21:26:27  kojm
  * add support for old fashioned tar archives
  *
59403b26
  * Revision 1.24  2005/03/20 18:34:18  nigelhorne
  * Minor tidy
  *
65ee94da
  * Revision 1.23  2005/03/20 09:09:25  nigelhorne
  * Consolidate NAME_MAX
  *
f4ff13a5
  * Revision 1.22  2005/03/10 08:52:10  nigelhorne
  * Tidy
  *
ad6b0de4
  * Revision 1.21  2005/02/16 22:19:21  nigelhorne
  * Check file close
  *
e1dce709
  * Revision 1.20  2005/02/13 22:25:41  kojm
  * do not try to continue if there's no space on device
  *
275dc42a
  * Revision 1.19  2004/12/16 15:34:57  nigelhorne
  * Tidy
  *
ab537286
  * Revision 1.18  2004/11/20 13:15:46  nigelhorne
  * Better handling of false file type identification
  *
0f750119
  * Revision 1.17  2004/10/27 06:36:38  nigelhorne
  * Handle type '1' files
  *
89cf36c6
  * Revision 1.16  2004/10/20 12:21:11  nigelhorne
  * Print warning message about LongLink
  *
77880aed
  * Revision 1.15  2004/10/16 16:08:46  nigelhorne
  * Handle empty files in the middle of archives
  *
f49103d6
  * Revision 1.14  2004/10/13 10:18:54  nigelhorne
  * Added a few extra file types
  *
0698fd8b
  * Revision 1.13  2004/10/04 13:46:50  nigelhorne
  * Handle GNU tar files
  *
7e91785c
  * Revision 1.12  2004/10/04 10:53:15  nigelhorne
  * Handle tar files less than 512 bytes
  *
5eeffbb9
  * Revision 1.11  2004/10/01 13:50:47  nigelhorne
  * Minor code tidy
  *
16b89181
  * Revision 1.10  2004/09/20 13:37:44  kojm
  * 0.80rc
  *
7d80d0c1
  * Revision 1.9  2004/09/14 10:29:31  nigelhorne
  * Fix compilation error on AIX and OSF
  *
ea54f8ca
  * Revision 1.8  2004/09/12 23:43:45  kojm
  * return with CL_EFORMAT instead of CL_EDSIG
  *
b96838ed
  * Revision 1.7  2004/09/12 19:51:59  nigelhorne
  * Now builds with --enable-debug
  *
03eaed11
  * Revision 1.6  2004/09/08 16:02:34  nigelhorne
  * fclose on error
  *
9f9ac4ec
  * Revision 1.5  2004/09/06 14:16:48  nigelhorne
  * Added CYGWIN support
  *
4ff385dd
  * Revision 1.4  2004/09/06 08:45:44  nigelhorne
  * Code Tidy
  *
f92f5b94
  * Revision 1.3  2004/09/06 08:34:47  nigelhorne
  * Randomise extracted file names from tar file
  *
a0dfa4f3
  * Revision 1.2  2004/09/05 18:58:21  nigelhorne
  * Extract files completed
  *
6d91b5f0
  * Revision 1.1  2004/09/05 15:28:10  nigelhorne
  * First draft
  *
  */
255e314c
 static	char	const	rcsid[] = "$Id: untar.c,v 1.25 2005/03/22 21:26:27 kojm Exp $";
a0dfa4f3
 
 #include <stdio.h>
 #include <errno.h>
 #include <string.h>
 #include <unistd.h>
 #include <sys/stat.h>
f92f5b94
 #include <fcntl.h>
5eeffbb9
 #include <sys/param.h>	/* for NAME_MAX */
a0dfa4f3
 
 #include "clamav.h"
 #include "others.h"
f92f5b94
 #include "untar.h"
b96838ed
 #include "mbox.h"
f92f5b94
 #include "blob.h"
a0dfa4f3
 
 #define BLOCKSIZE 512
 
7d80d0c1
 #ifndef	O_BINARY
 #define	O_BINARY	0
 #endif
 
f92f5b94
 static int
 octal(const char *str)
a0dfa4f3
 {
 	int ret = -1;
 
f4ff13a5
 	(void)sscanf(str, "%o", (unsigned int *)&ret);
a0dfa4f3
 	return ret;
 }
 
 int
255e314c
 cli_untar(const char *dir, int desc, unsigned int posix)
a0dfa4f3
 {
 	int size = 0;
 	int in_block = 0;
f92f5b94
 	char fullname[NAME_MAX + 1];
7e91785c
 	FILE *outfile = NULL;
a0dfa4f3
 
 	cli_dbgmsg("In untar(%s, %d)\n", dir ? dir : "", desc);
 
 	for(;;) {
 		char block[BLOCKSIZE];
f4ff13a5
 		const int nread = cli_readn(desc, block, (unsigned int)sizeof(block));
a0dfa4f3
 
 		if(!in_block && nread == 0)
 			break;
 
7e91785c
 		if(nread < 0) {
03eaed11
 			if(outfile)
 				fclose(outfile);
7e91785c
 			cli_errmsg("cli_untar: block read error\n");
a0dfa4f3
 			return CL_EIO;
 		}
 
 		if(!in_block) {
 			char type;
4ff385dd
 			const char *suffix;
f92f5b94
 			size_t suffixLen = 0;
 			int fd, directory;
 			char magic[7], name[101], osize[13];
 
 			if(outfile) {
 				if(fclose(outfile)) {
 					cli_errmsg("cli_untar: cannot close file %s\n",
 					    fullname);
 					return CL_EIO;
 				}
 				outfile = (FILE*)0;
 			}
a0dfa4f3
 
5eeffbb9
 			if(block[0] == '\0')	/* We're done */
a0dfa4f3
 				break;
 
b96838ed
 			/* Notice assumption that BLOCKSIZE > 262 */
255e314c
 			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;
 				}
a0dfa4f3
 			}
 
 			type = block[156];
 
f49103d6
 			/*
 			 * Extra types from djgardner@users.sourceforge.net
 			 */
a0dfa4f3
 			switch(type) {
f49103d6
 				case '0':	/* plain file */
 				case '\0':	/* plain file */
 				case '7':	/* contiguous file */
a0dfa4f3
 					directory = 0;
 					break;
0f750119
 				case '1':	/* Link to already archived file */
f49103d6
 				case '5':	/* directory */
 				case '2':	/* sym link */
 				case '3':	/* char device */
 				case '4':	/* block device */
 				case '6':	/* fifo special */
a0dfa4f3
 					directory = 1;
 					break;
89cf36c6
 				case 'L':	/* GNU extension - ././@LongLink */
 					cli_errmsg("cli_untar: only standard TAR files are currently supported\n", type);
 					return CL_EFORMAT;
a0dfa4f3
 				default:
ab537286
 					/*cli_errmsg("cli_untar: unknown type flag %c\n", type);
 					return CL_EFORMAT;*/
 					/*
 					 * It isn't really a tar file
 					 */
 					cli_dbgmsg("cli_untar: unknown type flag %c\n", type);
 					/*
 					 * We don't know that it's clean at all,
 					 * it would be better to have a
 					 * CL_CONTINUE return value since it
 					 * may be a different format
 					 */
 					return CL_CLEAN;
a0dfa4f3
 			}
 
f49103d6
 			if(directory) {
 				in_block = 0;
a0dfa4f3
 				continue;
f49103d6
 			}
a0dfa4f3
 
f92f5b94
 			strncpy(name, block, 100);
 			name[100] = '\0';
a0dfa4f3
 
f92f5b94
 			/*
 			 * see also fileblobSetFilename()
 			 * TODO: check if the suffix needs to be put back
 			 */
 			sanitiseName(name);
 			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;
a0dfa4f3
 				}
 			}
f92f5b94
 			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));
 				cli_dbgmsg("%lu %d %d\n", suffixLen, sizeof(fullname), strlen(fullname));
 				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",
 				    fullname);
 				close(fd);
 				return CL_ETMPFILE;
 			}
 
 			strncpy(osize, block+124, 12);
 			osize[12] = '\0';
 			size = octal(osize);
03eaed11
 			if(size < 0) {
f92f5b94
 				cli_errmsg("Invalid size in tar header\n");
 				fclose(outfile);
ea54f8ca
 				return CL_EFORMAT;
f92f5b94
 			}
77880aed
 			cli_dbgmsg("cli_untar: size = %d\n", size);
a0dfa4f3
 		} else { /* write or continue writing file contents */
 			const int nbytes = size>512? 512:size;
59403b26
 			const int nwritten = fwrite(block, 1, (size_t)nbytes, outfile);
a0dfa4f3
 
 			if(nwritten != nbytes) {
e1dce709
 				cli_errmsg("cli_untar: only wrote %d bytes to file %s (out of disk space?)\n",
a0dfa4f3
 					nwritten, fullname);
e1dce709
 				fclose(outfile);
 				return CL_EIO;
a0dfa4f3
 			}
 			size -= nbytes;
 		}
77880aed
 		if (size == 0)
 			in_block = 0;
a0dfa4f3
 	}
f92f5b94
 	if(outfile)
ad6b0de4
 		return fclose(outfile);
 
ea54f8ca
 	return 0;
a0dfa4f3
 }