libclamav/chmunpack.c
a5373b64
 /*
  *  Extract component parts of MS CHM files
  *
2023340a
  *  Copyright (C) 2007-2008 Sourcefire, Inc.
  *
  *  Authors: Trog
a5373b64
  *
  *  This program is free software; you can redistribute it and/or modify
2023340a
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
a5373b64
  *
  *  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.
a5373b64
  */
  
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
b58fdfc2
 #ifdef	HAVE_UNISTD_H
a5373b64
 #include <unistd.h>
b58fdfc2
 #endif
a5373b64
 #include <string.h>
 
ef38c83d
 #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA)
a5373b64
 #if HAVE_MMAP
 #if HAVE_SYS_MMAN_H
 #include <sys/mman.h>
 #else /* HAVE_SYS_MMAN_H */
 #undef HAVE_MMAP
 #endif /* HAVE_SYS_MMAN_H */
 #endif /* HAVE_MMAP */
 #else/* PACKED */
 #undef HAVE_MMAP
 #endif
 
 #include "others.h"
24fd05e1
 #include "mspack.h"
e7fe9178
 #include "cltypes.h"
fc83da82
 #include "chmunpack.h"
8df99a92
 #include "cab.h"
a5373b64
 
 #ifndef HAVE_ATTRIB_PACKED
 #define __attribute__(x)
 #endif
 
 #ifdef HAVE_PRAGMA_PACK
 #pragma pack(1)
 #endif
 
ef38c83d
 #ifdef HAVE_PRAGMA_PACK_HPPA
 #pragma pack 1
 #endif
 
b58fdfc2
 #ifndef	O_BINARY
 #define	O_BINARY	0
 #endif
 
a5373b64
 #define CHM_CHUNK_HDR_LEN (0x14)
 
 #define CHM_CONTROL_LEN (0x18)
 typedef struct lzx_control_tag {
 	uint32_t length __attribute__ ((packed));
871067d9
 	unsigned char signature[4];
a5373b64
 	uint32_t version __attribute__ ((packed));
 	uint32_t reset_interval __attribute__ ((packed));
 	uint32_t window_size __attribute__ ((packed));
 	uint32_t cache_size __attribute__ ((packed));
 } lzx_control_t;
 
 /* Don't need to include rt_offset in the strucuture len*/
 #define CHM_RESET_TABLE_LEN (0x24)
 typedef struct lzx_reset_table_tag {
 	uint32_t num_entries __attribute__ ((packed));
 	uint32_t entry_size __attribute__ ((packed));
 	uint32_t table_offset __attribute__ ((packed));
 	uint64_t uncom_len __attribute__ ((packed));
 	uint64_t com_len __attribute__ ((packed));
 	uint64_t frame_len __attribute__ ((packed));
 	off_t rt_offset __attribute__ ((packed));
 } lzx_reset_table_t;
 
 typedef struct lzx_content_tag {
 	uint64_t offset;
 	uint64_t length;
 } lzx_content_t;
 
 #ifdef HAVE_PRAGMA_PACK
 #pragma pack()
 #endif
 
ef38c83d
 #ifdef HAVE_PRAGMA_PACK_HPPA
 #pragma pack
 #endif
 
871067d9
 #define CHM_SYS_CONTROL_NAME "::DataSpace/Storage/MSCompressed/ControlData"
 #define CHM_SYS_CONTENT_NAME "::DataSpace/Storage/MSCompressed/Content"
 #define CHM_SYS_RESETTABLE_NAME "::DataSpace/Storage/MSCompressed/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable"
 
 #define CHM_SYS_CONTROL_LEN 44
 #define CHM_SYS_CONTENT_LEN 40
 #define CHM_SYS_RESETTABLE_LEN 105
 
75282b5c
 #define chm_endian_convert_16(x) le16_to_host(x) 
 #define chm_endian_convert_32(x) le32_to_host(x) 
 #define chm_endian_convert_64(x) le64_to_host(x)
a5373b64
 
 /* Read in a block of data from either the mmap area or the given fd */
871067d9
 static int chm_read_data(int fd, char *dest, off_t offset, off_t len,
 			char *m_area, off_t m_length)
a5373b64
 {
022027af
 	if ((offset < 0) || (len < 0) || ((offset+len) < 0)) {
 		return FALSE;
 	}
a5373b64
 	if (m_area != NULL) {
 		if ((offset+len) > m_length) {
 			return FALSE;
 		}
 		memcpy(dest, m_area+offset, len);
 	} else {
 		if (lseek(fd, offset, SEEK_SET) != offset) {
 			return FALSE;
 		}
 		if (cli_readn(fd, dest, len) != len) {
 			return FALSE;
 		}
 	}
 	return TRUE;
 }
 
fc83da82
 static uint64_t chm_copy_file_data(int ifd, int ofd, uint64_t len)
c84fb7d5
 {
 	unsigned char data[8192];
 	uint64_t count, rem;
 	unsigned int todo;
 	
 	rem = len;
 
 	while (rem > 0) {
 		todo = MIN(8192, rem);
 		count = cli_readn(ifd, data, todo);
 		if (count != todo) {
 			return len-rem;
 		}
9d3c38ba
 		if (cli_writen(ofd, data, count) != (int64_t)count) {
c84fb7d5
 			return len-rem-count;
 		}
 		rem -= count;
 	}
 	return len;
 }
 
b865e44a
 static void itsf_print_header(chm_itsf_header_t *itsf_hdr)
a5373b64
 {
 	if (!itsf_hdr) {
 		return;
 	}
 	
 	cli_dbgmsg("---- ITSF ----\n");
 	cli_dbgmsg("Signature:\t%c%c%c%c\n", itsf_hdr->signature[0],
 		itsf_hdr->signature[1],itsf_hdr->signature[2],itsf_hdr->signature[3]);
 	cli_dbgmsg("Version:\t%d\n", itsf_hdr->version);
871067d9
 	cli_dbgmsg("Header len:\t%d\n", itsf_hdr->header_len);
a5373b64
 	cli_dbgmsg("Lang ID:\t%d\n", itsf_hdr->lang_id);
96435bdc
 	cli_dbgmsg("Sec0 offset:\t%lu\n", (unsigned long int) itsf_hdr->sec0_offset);
 	cli_dbgmsg("Sec0 len:\t%lu\n", (unsigned long int) itsf_hdr->sec0_len);
 	cli_dbgmsg("Dir offset:\t%lu\n", (unsigned long int) itsf_hdr->dir_offset);
 	cli_dbgmsg("Dir len:\t%lu\n", (unsigned long int) itsf_hdr->dir_len);
a5373b64
 	if (itsf_hdr->version > 2) {
96435bdc
 		cli_dbgmsg("Data offset:\t%lu\n\n", (unsigned long int) itsf_hdr->data_offset);
a5373b64
 	}
 }
 
b865e44a
 static int itsf_read_header(int fd, chm_itsf_header_t *itsf_hdr, char *m_area, off_t m_length)
a5373b64
 {
ef38c83d
 #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA)
871067d9
 	if (!chm_read_data(fd, (char *) itsf_hdr, 0, CHM_ITSF_MIN_LEN,
a5373b64
 				m_area,	m_length)) {
 		return FALSE;
 	}
 #else
 	if (cli_readn(fd, &itsf_hdr->signature, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsf_hdr->version, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsf_hdr->header_len, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsf_hdr->unknown, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsf_hdr->last_modified, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsf_hdr->lang_id, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsf_hdr->dir_clsid, 16) != 16) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsf_hdr->stream_clsid, 16) != 16) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsf_hdr->sec0_offset, 8) != 8) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsf_hdr->sec0_len, 8) != 8) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsf_hdr->dir_offset, 8) != 8) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsf_hdr->dir_len, 8) != 8) {
 		return FALSE;
 	}
 	if (itsf_hdr->version > 2) {
 		if (cli_readn(fd, &itsf_hdr->data_offset, 8) != 8) {
 			return FALSE;
 		}
 	}
 #endif
 	if (memcmp(itsf_hdr->signature, "ITSF", 4) != 0) {
 		cli_dbgmsg("ITSF signature mismatch\n");
 		return FALSE;
 	}
 	itsf_hdr->version = chm_endian_convert_32(itsf_hdr->version);
 	itsf_hdr->header_len = chm_endian_convert_32(itsf_hdr->header_len);
 	itsf_hdr->last_modified = chm_endian_convert_32(itsf_hdr->last_modified);
 	itsf_hdr->lang_id = chm_endian_convert_32(itsf_hdr->lang_id);
 	itsf_hdr->sec0_offset = chm_endian_convert_64(itsf_hdr->sec0_offset);
 	itsf_hdr->sec0_len = chm_endian_convert_64(itsf_hdr->sec0_len);
 	itsf_hdr->dir_offset = chm_endian_convert_64(itsf_hdr->dir_offset);
 	itsf_hdr->dir_len = chm_endian_convert_64(itsf_hdr->dir_len);
 	if (itsf_hdr->version > 2) {
 		itsf_hdr->data_offset = chm_endian_convert_64(itsf_hdr->data_offset);
 	}
 	return TRUE;
 }
 
b865e44a
 static void itsp_print_header(chm_itsp_header_t *itsp_hdr)
a5373b64
 {
 	if (!itsp_hdr) {
 		return;
 	}
 	
 	cli_dbgmsg("---- ITSP ----\n");
 	cli_dbgmsg("Signature:\t%c%c%c%c\n", itsp_hdr->signature[0],
 		itsp_hdr->signature[1],itsp_hdr->signature[2],itsp_hdr->signature[3]);
 	cli_dbgmsg("Version:\t%d\n", itsp_hdr->version);
871067d9
 	cli_dbgmsg("Block len:\t%u\n", itsp_hdr->block_len);
a5373b64
 	cli_dbgmsg("Block idx int:\t%d\n", itsp_hdr->blockidx_intvl);
 	cli_dbgmsg("Index depth:\t%d\n", itsp_hdr->index_depth);
 	cli_dbgmsg("Index root:\t%d\n", itsp_hdr->index_root);
 	cli_dbgmsg("Index head:\t%u\n", itsp_hdr->index_head);
 	cli_dbgmsg("Index tail:\t%u\n", itsp_hdr->index_tail);
 	cli_dbgmsg("Num Blocks:\t%u\n", itsp_hdr->num_blocks);
871067d9
 	cli_dbgmsg("Lang ID:\t%u\n\n", itsp_hdr->lang_id);
a5373b64
 }
 
b865e44a
 static int itsp_read_header(int fd, chm_itsp_header_t *itsp_hdr, off_t offset,
871067d9
 				char *m_area, off_t m_length)
a5373b64
 {
ef38c83d
 #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA)
871067d9
 	if (!chm_read_data(fd, (char *) itsp_hdr, offset, CHM_ITSP_LEN,
a5373b64
 				m_area,	m_length)) {
 		return FALSE;
 	}
 #else
 	if (lseek(fd, offset, SEEK_SET) != offset) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->signature, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->version, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->header_len, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->unknown1, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->block_len, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->blockidx_intvl, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->index_depth, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->index_root, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->index_head, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->index_tail, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->unknown2, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->num_blocks, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->lang_id, 4) != 4) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->system_clsid, 16) != 16) {
 		return FALSE;
 	}
 	if (cli_readn(fd, &itsp_hdr->unknown4, 16) != 16) {
 		return FALSE;
 	}
 #endif
 	if (memcmp(itsp_hdr->signature, "ITSP", 4) != 0) {
 		cli_dbgmsg("ITSP signature mismatch\n");
 		return FALSE;
 	}
 	
 	itsp_hdr->version = chm_endian_convert_32(itsp_hdr->version);
 	itsp_hdr->header_len = chm_endian_convert_32(itsp_hdr->header_len);
 	itsp_hdr->block_len = chm_endian_convert_32(itsp_hdr->block_len);
 	itsp_hdr->blockidx_intvl = chm_endian_convert_32(itsp_hdr->blockidx_intvl);
 	itsp_hdr->index_depth = chm_endian_convert_32(itsp_hdr->index_depth);
 	itsp_hdr->index_root = chm_endian_convert_32(itsp_hdr->index_root);
 	itsp_hdr->index_head = chm_endian_convert_32(itsp_hdr->index_head);
 	itsp_hdr->index_tail = chm_endian_convert_32(itsp_hdr->index_tail);
 	itsp_hdr->num_blocks = chm_endian_convert_32(itsp_hdr->num_blocks);
 	itsp_hdr->lang_id = chm_endian_convert_32(itsp_hdr->lang_id);
 	
 	if ((itsp_hdr->version != 1) || (itsp_hdr->header_len != CHM_ITSP_LEN)) {
 		cli_dbgmsg("ITSP header mismatch\n");
 		return FALSE;
 	}
 	return TRUE;
 }
 
871067d9
 static uint64_t read_enc_int(char **start, char *end)
a5373b64
 {
 	uint64_t retval=0;
871067d9
 	char *current;
a5373b64
 	
 	current = *start;
 	
 	if (current > end) {
9d3c38ba
 		return 0;
a5373b64
 	}
 	
 	do {
 		if (current > end) {
9d3c38ba
 			return 0;
a5373b64
 		}
 		retval = (retval << 7) | (*current & 0x7f);
 	} while (*current++ & 0x80);
 	
 	*start = current;
 	return retval;
 }
 
b865e44a
 /* Read control entries */
 static int read_control_entries(chm_metadata_t *metadata)
a5373b64
 {
b865e44a
 	char *name;
 	uint64_t name_len, section, offset, length;
a5373b64
 
b865e44a
 	while (metadata->chunk_entries--) {
 		if (metadata->chunk_current > metadata->chunk_end) {
a5373b64
 			cli_dbgmsg("read chunk entries failed\n");
 			return FALSE;
 		}
 
b865e44a
 		name_len = read_enc_int(&metadata->chunk_current, metadata->chunk_end);
 		if (((metadata->chunk_current + name_len) > metadata->chunk_end) || ((metadata->chunk_current + name_len) < metadata->chunk_data)) {
52b5464e
 			cli_dbgmsg("Bad CHM name_len detected\n");
 			return FALSE;
 		}
b865e44a
 		name = metadata->chunk_current;
 		metadata->chunk_current += name_len;
 		section = read_enc_int(&metadata->chunk_current, metadata->chunk_end);
 		offset = read_enc_int(&metadata->chunk_current, metadata->chunk_end);
 		length = read_enc_int(&metadata->chunk_current, metadata->chunk_end);
 		
 		/* CHM_SYS_CONTENT_LEN is the shortest name we are searching for */
 		if ((name_len >= CHM_SYS_CONTENT_LEN) && (name[0] == ':') &&
 				(name[1] == ':')) {
 			if ((name_len == CHM_SYS_CONTROL_LEN) && (strcmp(name, CHM_SYS_CONTROL_NAME) == 0)) {
 				cli_dbgmsg("found CHM_SYS_CONTROL_NAME\n");
 				metadata->sys_control.offset = offset;
 				metadata->sys_control.length = length;
 			} else if ((name_len == CHM_SYS_CONTENT_LEN) && (strcmp(name, CHM_SYS_CONTENT_NAME) == 0)) {
 				cli_dbgmsg("found CHM_SYS_CONTENT_NAME\n");
 				metadata->sys_content.offset = offset;
 				metadata->sys_content.length = length;
 			} else if ((name_len == CHM_SYS_RESETTABLE_LEN) && (strcmp(name, CHM_SYS_RESETTABLE_NAME) == 0)) {
 				cli_dbgmsg("found CHM_SYS_RESETTABLE_NAME\n");
 				metadata->sys_reset.offset = offset;
 				metadata->sys_reset.length = length;
93e155f6
 			}
871067d9
 		}
a5373b64
 	}
c84fb7d5
 	return TRUE;
a5373b64
 }
 
b865e44a
 static int prepare_file(int fd, chm_metadata_t *metadata)
a5373b64
 {
b865e44a
 	uint64_t name_len, section;
a5373b64
 
c71cae8c
 	while (metadata->chunk_entries != 0) {
b865e44a
 		if (metadata->chunk_current >= metadata->chunk_end) {
 			return CL_EFORMAT;
 		}
 	
 		name_len = read_enc_int(&metadata->chunk_current, metadata->chunk_end);
 		if (((metadata->chunk_current + name_len) >= metadata->chunk_end) ||
 				((metadata->chunk_current + name_len) < metadata->chunk_data)) {
 			cli_dbgmsg("Bad CHM name_len detected\n");
 			return CL_EFORMAT;
 		}
 		metadata->chunk_current += name_len;
 		section = read_enc_int(&metadata->chunk_current, metadata->chunk_end);
 		metadata->file_offset = read_enc_int(&metadata->chunk_current, metadata->chunk_end);
c71cae8c
 		metadata->file_length = read_enc_int(&metadata->chunk_current, metadata->chunk_end);
 		metadata->chunk_entries--;
b865e44a
 		if (section == 1) {
 			return CL_SUCCESS;
 		}
a5373b64
 	}
b865e44a
 	
 	return CL_BREAK;
a5373b64
 }
 
b865e44a
 static int read_chunk(chm_metadata_t *metadata, int fd)
a5373b64
 {
b865e44a
 	cli_dbgmsg("in read_chunk\n");
 
 	if (metadata->itsp_hdr.block_len < 8 || metadata->itsp_hdr.block_len > 33554432) {
9c403bce
 		return CL_EFORMAT;
a5373b64
 	}
b865e44a
 
 	if (metadata->m_area != NULL) {
 		if (metadata->chunk_offset > metadata->m_length) {
 			return CL_EFORMAT;
a5373b64
 		}
b865e44a
 		if ((metadata->chunk_offset + metadata->itsp_hdr.block_len) > metadata->m_length) {
 			return CL_EFORMAT;
a5373b64
 		}
b865e44a
 		metadata->chunk_data = metadata->m_area + metadata->chunk_offset;
 
 	} else {
 		if (!metadata->chunk_data) {
 			metadata->chunk_data = (char *) cli_malloc(metadata->itsp_hdr.block_len);
 			if (!metadata->chunk_data) {
 				return CL_EMEM;
 			}
a5373b64
 		}
b865e44a
         	if (lseek(fd, metadata->chunk_offset, SEEK_SET) != metadata->chunk_offset) {
                 	goto abort;
         	}
96435bdc
         	if ((uint32_t) cli_readn(fd, metadata->chunk_data, metadata->itsp_hdr.block_len) != metadata->itsp_hdr.block_len) {
b865e44a
                		goto abort;
         	}
 	}
 	metadata->chunk_current = metadata->chunk_data + CHM_CHUNK_HDR_LEN;
 	metadata->chunk_end = metadata->chunk_data + metadata->itsp_hdr.block_len;
 
 	if (memcmp(metadata->chunk_data, "PMGL", 4) == 0) {
 		metadata->chunk_entries = (uint16_t)((((uint8_t const *)(metadata->chunk_data))[metadata->itsp_hdr.block_len-2] << 0)
 					| (((uint8_t const *)(metadata->chunk_data))[metadata->itsp_hdr.block_len-1] << 8));
 	} else if (memcmp(metadata->chunk_data, "PMGI", 4) != 0) {
128c7bed
 		if (!metadata->m_area && metadata->chunk_data) {
b865e44a
 			free(metadata->chunk_data);
a5373b64
 		}
b865e44a
 		return CL_BREAK;
a5373b64
 	}
72d885e8
 
b865e44a
 	return CL_SUCCESS;
a5373b64
 abort:
128c7bed
 	if (!metadata->m_area && metadata->chunk_data) {
b865e44a
 		free(metadata->chunk_data);
 		metadata->chunk_data = NULL;
 	}
 	return CL_EFORMAT;
a5373b64
 }
 
 static void print_sys_control(lzx_control_t *lzx_control)
 {
 	if (!lzx_control) {
 		return;
 	}
 
 	cli_dbgmsg("---- Control ----\n");	
871067d9
 	cli_dbgmsg("Length:\t\t%u\n", lzx_control->length);
a5373b64
 	cli_dbgmsg("Signature:\t%c%c%c%c\n", lzx_control->signature[0],
 		lzx_control->signature[1],lzx_control->signature[2],lzx_control->signature[3]);
 	cli_dbgmsg("Version:\t%d\n", lzx_control->version);
 	cli_dbgmsg("Reset Interval:\t%d\n", lzx_control->reset_interval);
 	cli_dbgmsg("Window Size:\t%d\n", lzx_control->window_size);
 	cli_dbgmsg("Cache Size:\t%d\n\n", lzx_control->cache_size);
 }
 
b865e44a
 static int read_sys_control(int fd, chm_metadata_t *metadata, lzx_control_t *lzx_control)
a5373b64
 {
 	off_t offset;
 	
b865e44a
 	if (metadata->sys_control.length != 28) {
 		return FALSE;
a5373b64
 	}
b865e44a
 	offset = metadata->itsf_hdr.data_offset + metadata->sys_control.offset;
3c21733d
 	if (offset < 0) {
b865e44a
 		return FALSE;
a5373b64
 	}
 
ef38c83d
 #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA)
871067d9
 	if (!chm_read_data(fd, (char *) lzx_control, offset, CHM_CONTROL_LEN,
b865e44a
 				metadata->m_area, metadata->m_length)) {
 		return FALSE;
a5373b64
 	}
 #else
 	if (lseek(fd, offset, SEEK_SET) != offset) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_control->length, 4) != 4) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_control->signature, 4) != 4) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_control->version, 4) != 4) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_control->reset_interval, 4) != 4) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_control->window_size, 4) != 4) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_control->cache_size, 4) != 4) {
b865e44a
 		return FALSE;
a5373b64
 	}
 #endif
 	lzx_control->length = chm_endian_convert_32(lzx_control->length);
 	lzx_control->version = chm_endian_convert_32(lzx_control->version);
 	lzx_control->reset_interval = chm_endian_convert_32(lzx_control->reset_interval);
 	lzx_control->window_size = chm_endian_convert_32(lzx_control->window_size);
 	lzx_control->cache_size = chm_endian_convert_32(lzx_control->cache_size);
 	
871067d9
 	if (strncmp((const char *) "LZXC", (const char *) lzx_control->signature, 4) != 0) {
1405207a
 		cli_dbgmsg("bad sys_control signature\n");
b865e44a
 		return FALSE;
a5373b64
 	}
 	switch(lzx_control->version) {
 		case 1:
 			break;
 		case 2:
 			lzx_control->reset_interval *= LZX_FRAME_SIZE;
 			lzx_control->window_size *= LZX_FRAME_SIZE;
 			break;
 		default:
 			cli_dbgmsg("Unknown sys_control version:%d\n", lzx_control->version);
b865e44a
 			return FALSE;
a5373b64
 	}
 			
 	print_sys_control(lzx_control);
b865e44a
 	return TRUE;
a5373b64
 }
 
 static void print_sys_content(lzx_content_t *lzx_content)
 {
 	if (!lzx_content) {
 		return;
 	}
 	
 	cli_dbgmsg("---- Content ----\n");
96435bdc
 	cli_dbgmsg("Offset:\t%lu\n", (unsigned long int) lzx_content->offset);
 	cli_dbgmsg("Length:\t%lu\n\n", (unsigned long int) lzx_content->length);
a5373b64
 }
 
b865e44a
 static int read_sys_content(int fd, chm_metadata_t *metadata, lzx_content_t *lzx_content)
a5373b64
 {
b865e44a
 	lzx_content->offset = metadata->itsf_hdr.data_offset + metadata->sys_content.offset;
 	lzx_content->length = metadata->sys_content.length;
a5373b64
 	
 	print_sys_content(lzx_content);
b865e44a
 	return TRUE;
a5373b64
 }
 
 static void print_sys_reset_table(lzx_reset_table_t *lzx_reset_table)
 {
 	if (!lzx_reset_table) {
 		return;
 	}
 	
 	cli_dbgmsg("---- Reset Table ----\n");
871067d9
 	cli_dbgmsg("Num Entries:\t%u\n", lzx_reset_table->num_entries);
 	cli_dbgmsg("Entry Size:\t%u\n", lzx_reset_table->entry_size);
 	cli_dbgmsg("Table Offset:\t%u\n", lzx_reset_table->table_offset);
96435bdc
 	cli_dbgmsg("Uncom Len:\t%lu\n", (unsigned long int) lzx_reset_table->uncom_len);
 	cli_dbgmsg("Com Len:\t%lu\n", (unsigned long int) lzx_reset_table->com_len);
 	cli_dbgmsg("Frame Len:\t%lu\n\n", (unsigned long int) lzx_reset_table->frame_len);
a5373b64
 }
 
b865e44a
 static int read_sys_reset_table(int fd, chm_metadata_t *metadata, lzx_reset_table_t *lzx_reset_table)
a5373b64
 {
 	off_t offset;
 
b865e44a
 	if (metadata->sys_reset.length < 40) {
 		return FALSE;
a5373b64
 	}
 	/* Skip past unknown entry in offset calc */
b865e44a
 	offset = metadata->itsf_hdr.data_offset + metadata->sys_reset.offset + 4;
a5373b64
 	
3c21733d
 	if (offset < 0) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	
 	/* Save the entry offset for later use */
 	lzx_reset_table->rt_offset = offset-4;
 
ef38c83d
 #if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA)
871067d9
 	if (!chm_read_data(fd, (char *) lzx_reset_table, offset, CHM_RESET_TABLE_LEN,
b865e44a
 				metadata->m_area, metadata->m_length)) {
 		return FALSE;
a5373b64
 	}
 #else	
 	if (lseek(fd, offset, SEEK_SET) != offset) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_reset_table->num_entries, 4) != 4) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_reset_table->entry_size, 4) != 4) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_reset_table->table_offset, 4) != 4) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_reset_table->uncom_len, 8) != 8) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_reset_table->com_len, 8) != 8) {
b865e44a
 		return FALSE;
a5373b64
 	}
 	if (cli_readn(fd, &lzx_reset_table->frame_len, 8) != 8) {
b865e44a
 		return FALSE;
a5373b64
 	}
 #endif
 	lzx_reset_table->num_entries = chm_endian_convert_32(lzx_reset_table->num_entries);
 	lzx_reset_table->entry_size = chm_endian_convert_32(lzx_reset_table->entry_size);
 	lzx_reset_table->table_offset = chm_endian_convert_32(lzx_reset_table->table_offset);
 	lzx_reset_table->uncom_len = chm_endian_convert_64(lzx_reset_table->uncom_len);
 	lzx_reset_table->com_len = chm_endian_convert_64(lzx_reset_table->com_len);
 	lzx_reset_table->frame_len = chm_endian_convert_64(lzx_reset_table->frame_len);
 
 	if (lzx_reset_table->frame_len != LZX_FRAME_SIZE) {
871067d9
 		cli_dbgmsg("bad sys_reset_table frame_len: 0x%lx\n", (long unsigned int) lzx_reset_table->frame_len);
b865e44a
 		return FALSE;
a5373b64
 	}
 	if ((lzx_reset_table->entry_size != 4) && (lzx_reset_table->entry_size != 8)) {
 		cli_dbgmsg("bad sys_reset_table entry_size: 0x%x\n",lzx_reset_table->entry_size);
b865e44a
 		return FALSE;
a5373b64
 	}
 	print_sys_reset_table(lzx_reset_table);
b865e44a
 	return TRUE;
a5373b64
 }
 
c84fb7d5
 /* *****************************************************************/
a5373b64
 /* This section interfaces to the mspack files. As such, this is a */
 /* little bit dirty compared to my usual code */
 
8df99a92
 static int chm_decompress_stream(int fd, chm_metadata_t *metadata, const char *dirname, cli_ctx *ctx)
a5373b64
 {
b865e44a
 	lzx_content_t lzx_content;
 	lzx_reset_table_t lzx_reset_table;
 	lzx_control_t lzx_control;
 	int window_bits, length, tmpfd, retval=-1;
24fd05e1
 	struct lzx_stream * stream;
871067d9
 	char filename[1024];
8df99a92
 	struct cab_file file;
a5373b64
 	
 	snprintf(filename, 1024, "%s/clamav-unchm.bin", dirname);
b865e44a
 	tmpfd = open(filename, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, S_IRWXU);
630d154a
 	if (tmpfd<0) {
a8d1578e
 		cli_dbgmsg("open failed for %s\n", filename);
b865e44a
 		return -1;
a5373b64
 	}
24fd05e1
 
b865e44a
 	if (!metadata->sys_control.length || !metadata->sys_content.length ||!metadata->sys_reset.length) {
 		cli_dbgmsg("Control file missing\n");
871067d9
 		goto abort;
a5373b64
 	}
871067d9
 
b865e44a
 	if (!read_sys_control(fd, metadata, &lzx_control)) {
 		goto abort;
 	}
 	if (!read_sys_content(fd, metadata, &lzx_content)) {
 		goto abort;
 	}
 	if (!read_sys_reset_table(fd, metadata, &lzx_reset_table)) {
a5373b64
 		goto abort;
 	}
 	
b865e44a
 	switch (lzx_control.window_size) {
a5373b64
 		case 0x008000:
 			window_bits = 15;
 			break;
 		case 0x010000:
 			window_bits = 16;
 			break;
 		case 0x020000:
 			window_bits = 17;
 			break;
 		case 0x040000:
 			window_bits = 18;
 			break;
 		case 0x080000:
 			window_bits = 19;
 			break;
 		case 0x100000:
 			window_bits = 20;
 			break;
 		case 0x200000:
 			window_bits = 21;
 			break;
 		default:
b865e44a
 			cli_dbgmsg("bad control window size: 0x%x\n", lzx_control.window_size);
a5373b64
 			goto abort;
 	}
 	
b865e44a
 	if (lzx_control.reset_interval % LZX_FRAME_SIZE) {
 		cli_dbgmsg("bad reset_interval: 0x%x\n", lzx_control.window_size);
a5373b64
 		goto abort;
 	}
 	
b865e44a
 	length = lzx_reset_table.uncom_len;
 	length += lzx_control.reset_interval;
 	length &= -lzx_control.reset_interval;
a5373b64
 	
96435bdc
 	cli_dbgmsg("Compressed offset: %lu\n", (unsigned long int) lzx_content.offset);
 	if ((uint64_t) lseek(fd, lzx_content.offset, SEEK_SET) != lzx_content.offset) {
b865e44a
 		goto abort;
 	}
8df99a92
 
 	memset(&file, 0, sizeof(struct cab_file));
 	file.max_size = ctx->engine->maxfilesize;
24fd05e1
 	stream = lzx_init(fd, tmpfd, window_bits,
b865e44a
 			lzx_control.reset_interval / LZX_FRAME_SIZE,
8df99a92
 			4096, length, &file, NULL);
a5373b64
 	if (!stream) {
24fd05e1
 		cli_dbgmsg("lzx_init failed\n");
a5373b64
 		goto abort;
 	}
 	
24fd05e1
 	lzx_decompress(stream, length);
 	lzx_free(stream);
a5373b64
 	
2d5e85fa
 #ifndef C_WINDOWS
ce4fc78d
 	/* Delete the file */
2fcb0080
 	if(cli_unlink(filename))
 		retval = -1;
 	else
2d5e85fa
 #endif
2fcb0080
 		retval = tmpfd;
a5373b64
 	
 abort:
b865e44a
 	if ((retval == -1) && (tmpfd >= 0)) {
630d154a
 		close(tmpfd);
 	}
b865e44a
 	return retval;
 }
 
 /* ************ End dirty section ********************/
 
 static int chm_init_metadata(chm_metadata_t *metadata)
 {
 	if (!metadata) {
 		return CL_ENULLARG;
a5373b64
 	}
b865e44a
 	
 	metadata->sys_control.length = metadata->sys_content.length = metadata->sys_reset.length = 0;
 	metadata->m_area = NULL;
 	metadata->ufd = -1;
 	metadata->num_chunks = metadata->chunk_entries = 0;
 	metadata->chunk_data = NULL;
 	return CL_SUCCESS;
 }
 
 void cli_chm_close(chm_metadata_t *metadata)
 {
 	if (metadata->ufd >= 0) {
 		close(metadata->ufd);
a5373b64
 	}
b865e44a
 	if (!metadata->m_area && metadata->chunk_data) {
 		free(metadata->chunk_data);
a5373b64
 	}
b865e44a
 #ifdef HAVE_MMAP
 	if (metadata->m_area) {
128c7bed
 		munmap(metadata->m_area, metadata->m_length);
b865e44a
 	}
 #endif
a5373b64
 }
 
8df99a92
 int cli_chm_extract_file(int fd, char *dirname, chm_metadata_t *metadata, cli_ctx *ctx)
b865e44a
 {
 	char filename[1024];
8df99a92
 	uint64_t len;
b865e44a
 
 	cli_dbgmsg("in cli_chm_extract_file\n");
 	
 	if (lseek(metadata->ufd, metadata->file_offset, SEEK_SET) != (off_t) metadata->file_offset) {
 		cli_dbgmsg("seek in uncompressed stream failed\n");
 		return CL_EFORMAT;
 	}
96435bdc
 	snprintf(filename, 1024, "%s/%lu.chm", dirname, (unsigned long int) metadata->file_offset);
b865e44a
 	metadata->ofd = open(filename, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, S_IRWXU);
 	if (metadata->ofd < 0) {
871177cd
 		return CL_ECREAT;
b865e44a
 	}
8df99a92
 	len = ctx->engine->maxfilesize ? (MIN(ctx->engine->maxfilesize, metadata->file_length)) : metadata->file_length;
 	if (chm_copy_file_data(metadata->ufd, metadata->ofd, len) != len) {
 		cli_dbgmsg("failed to copy %lu bytes\n", (unsigned long int) len);
b865e44a
 		close(metadata->ofd);
0ba3503b
 		return CL_EFORMAT; /* most likely a corrupted file */
b865e44a
 	}
 		
 	return CL_SUCCESS;
 }	
a5373b64
 
b865e44a
 int cli_chm_prepare_file(int fd, char *dirname, chm_metadata_t *metadata)
 {
 	int retval;
 	
 	cli_dbgmsg("in cli_chm_prepare_file\n");
 
c71cae8c
 	do {
 		if (metadata->chunk_entries == 0) {
 			if (metadata->num_chunks == 0) {
 				return CL_BREAK;
 			}
 			if ((retval = read_chunk(metadata, fd)) != CL_SUCCESS) {
 				return retval;
 			}
 			metadata->num_chunks--;
 			metadata->chunk_offset += metadata->itsp_hdr.block_len;
b865e44a
 		}
c71cae8c
 		retval = prepare_file(fd, metadata);
 	} while (retval == CL_BREAK); /* Ran out of chunk entries before finding a file */
 	return retval;
b865e44a
 }
 
8df99a92
 int cli_chm_open(int fd, const char *dirname, chm_metadata_t *metadata, cli_ctx *ctx)
a5373b64
 {
 	struct stat statbuf;
b865e44a
 	int retval;
a5373b64
 
b865e44a
 	cli_dbgmsg("in cli_chm_open\n");
a5373b64
 	
b865e44a
 	if ((retval = chm_init_metadata(metadata)) != CL_SUCCESS) {
 		return retval;
a5373b64
 	}
b865e44a
 
a5373b64
 #ifdef HAVE_MMAP
 	if (fstat(fd, &statbuf) == 0) {
 		if (statbuf.st_size < CHM_ITSF_MIN_LEN) {
 			goto abort;
 		}
b865e44a
 		metadata->m_length = statbuf.st_size;
 		metadata->m_area = (char *) mmap(NULL, metadata->m_length, PROT_READ, MAP_PRIVATE, fd, 0);
 		if (metadata->m_area == MAP_FAILED) {
 			metadata->m_area = NULL;
a5373b64
 		}
 	}
 #endif
 
b865e44a
 	if (!itsf_read_header(fd, &metadata->itsf_hdr, metadata->m_area, metadata->m_length)) {
a5373b64
 		goto abort;
 	}
b865e44a
 	itsf_print_header(&metadata->itsf_hdr);
a5373b64
 
b865e44a
 	if (!itsp_read_header(fd, &metadata->itsp_hdr, metadata->itsf_hdr.dir_offset, metadata->m_area, metadata->m_length)) {
a5373b64
 		goto abort;
 	}
b865e44a
 	itsp_print_header(&metadata->itsp_hdr);
 
 	metadata->chunk_offset = metadata->itsf_hdr.dir_offset+CHM_ITSP_LEN;
a5373b64
 	
 	/* TODO: need to check this first calculation,
 		currently have no files of this type */
b865e44a
 	if (metadata->itsp_hdr.index_head > 0) {
 		metadata->chunk_offset += metadata->itsp_hdr.index_head * metadata->itsp_hdr.block_len;
a5373b64
 	}
 
b865e44a
 	metadata->num_chunks = metadata->itsp_hdr.index_tail - metadata->itsp_hdr.index_head + 1;
a5373b64
 	
 	/* Versions before 3 didn't have a data_offset */
 	/* TODO: need to check this calculation,
 		 currently have no files of this type */
b865e44a
 	if (metadata->itsf_hdr.version < 3) {
 		metadata->itsf_hdr.data_offset = metadata->itsf_hdr.dir_offset + CHM_ITSP_LEN +
 				(metadata->itsp_hdr.block_len*metadata->itsp_hdr.num_blocks);
a5373b64
 	}
b865e44a
 	
 	while (metadata->num_chunks) {
 		if (read_chunk(metadata, fd) != CL_SUCCESS) {
1405207a
 			cli_dbgmsg("read_chunk failed\n");
a5373b64
 			goto abort;
 		}
9c403bce
 		if (read_control_entries(metadata) == FALSE) {
 			goto abort;
 		}
b865e44a
 		metadata->num_chunks--;
 		metadata->chunk_offset += metadata->itsp_hdr.block_len;
 	}
a5373b64
 
b865e44a
 	if (!metadata->sys_content.length || !metadata->sys_control.length || !metadata->sys_reset.length) {
1405207a
 		cli_dbgmsg("sys file missing\n");
b865e44a
 		goto abort;
a5373b64
 	}
b865e44a
 	
8df99a92
 	metadata->ufd = chm_decompress_stream(fd, metadata, dirname, ctx);
b865e44a
 	if (metadata->ufd == -1) {
 		goto abort;
 	}
 	
 	metadata->chunk_entries = 0;
 	metadata->chunk_data = NULL;
 	metadata->chunk_offset = metadata->itsf_hdr.dir_offset+CHM_ITSP_LEN;
 	metadata->num_chunks = metadata->itsp_hdr.index_tail - metadata->itsp_hdr.index_head + 1;
a5373b64
 
b865e44a
 	return CL_SUCCESS;
a5373b64
 
 abort:
 #ifdef HAVE_MMAP
b865e44a
 	if (metadata->m_area) {
 		munmap(metadata->m_area, metadata->m_length);
a5373b64
 	}
 #endif
b865e44a
 	return CL_EFORMAT;
a5373b64
 }