/*
 *  Extract component parts of OLE2 files (e.g. MS Office Documents)
 *
 *  Copyright (C) 2004-2007 trog@uncon.org
 *
 *  This code is based on the OpenOffice and libgsf sources.
 *                  
 *  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., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */

#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#ifdef	HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>
#include <stdlib.h>
#include "clamav.h"

#if HAVE_MMAP
#if HAVE_SYS_MMAN_H
#include <sys/mman.h>
#else /* HAVE_SYS_MMAN_H */
#undef HAVE_MMAP
#endif
#endif

#include "cltypes.h"
#include "others.h"
#include "ole2_extract.h"

#include "mbox.h"
#include "blob.h" /* sanitiseName() */

#define ole2_endian_convert_16(v) le16_to_host(v)
#define ole2_endian_convert_32(v) le32_to_host(v)

#ifndef HAVE_ATTRIB_PACKED
#define __attribute__(x)
#endif

#ifdef HAVE_PRAGMA_PACK
#pragma pack(1)
#endif

#ifdef HAVE_PRAGMA_PACK_HPPA
#pragma pack 1
#endif

#ifndef	O_BINARY
#define	O_BINARY	0
#endif

typedef struct ole2_header_tag
{
	unsigned char magic[8];		/* should be: 0xd0cf11e0a1b11ae1 */
	unsigned char clsid[16];
	uint16_t minor_version __attribute__ ((packed));
	uint16_t dll_version __attribute__ ((packed));
	int16_t byte_order __attribute__ ((packed));			/* -2=intel */

	uint16_t log2_big_block_size __attribute__ ((packed));		/* usually 9 (2^9 = 512) */
	uint32_t log2_small_block_size __attribute__ ((packed));	/* usually 6 (2^6 = 128) */

	int32_t reserved[2] __attribute__ ((packed));
	int32_t bat_count __attribute__ ((packed));
	int32_t prop_start __attribute__ ((packed));

	uint32_t signature __attribute__ ((packed));
	uint32_t sbat_cutoff __attribute__ ((packed));			/* cutoff for files held in small blocks (4096) */

	int32_t sbat_start __attribute__ ((packed));
	int32_t sbat_block_count __attribute__ ((packed));
	int32_t xbat_start __attribute__ ((packed));
	int32_t xbat_count __attribute__ ((packed));
	int32_t bat_array[109] __attribute__ ((packed));

	/* not part of the ole2 header, but stuff we need in order to decode */
	/* must take account of the size of variables below here when
	   reading the header */
	int32_t sbat_root_start __attribute__ ((packed));
	unsigned char *m_area;
	off_t m_length;
	bitset_t *bitset;
	uint32_t max_block_no;
} ole2_header_t;

typedef struct property_tag
{
	char name[64];		/* in unicode */
	uint16_t name_size __attribute__ ((packed));
	unsigned char type;		/* 1=dir 2=file 5=root */
	unsigned char color;		/* black or red */
	uint32_t prev __attribute__ ((packed));
	uint32_t next __attribute__ ((packed));
	uint32_t child __attribute__ ((packed));

	unsigned char clsid[16];
	uint32_t user_flags __attribute__ ((packed));

	uint32_t create_lowdate __attribute__ ((packed));
	uint32_t create_highdate __attribute__ ((packed));
	uint32_t mod_lowdate __attribute__ ((packed));
	uint32_t mod_highdate __attribute__ ((packed));
	uint32_t start_block __attribute__ ((packed));
	uint32_t size __attribute__ ((packed));
	unsigned char reserved[4];
} property_t;

#ifdef HAVE_PRAGMA_PACK
#pragma pack()
#endif

#ifdef HAVE_PRAGMA_PACK_HPPA
#pragma pack
#endif

static unsigned char magic_id[] = { 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1};

static char *get_property_name(char *name, int size)
{
	int i, j;
	char *newname;

	if (*name == 0 || size <= 0 || size > 64) {
		return NULL;
	}

	newname = (char *) cli_malloc(size*7);
	if (!newname) {
		return NULL;
	}
	j=0;
	/* size-2 to ignore trailing NULL */
	for (i=0 ; i < size-2; i+=2) {
		if((!(name[i]&0x80)) && isprint(name[i])) {
			newname[j++] = name[i];
		} else {
			if (name[i] < 10 && name[i] >= 0) {
				newname[j++] = '_';
				newname[j++] = name[i] + '0';
			}
			else {
				const uint16_t x = (((uint16_t)name[i]) << 8) | name[i+1];
				newname[j++] = '_';
				newname[j++] = 'a'+((x&0xF));
				newname[j++] = 'a'+((x>>4)&0xF);
				newname[j++] = 'a'+((x>>8)&0xF);
				newname[j++] = 'a'+((x>>16)&0xF);
				newname[j++] = 'a'+((x>>24)&0xF);
			}
			newname[j++] = '_';
		}
	}
	newname[j] = '\0';
	if (strlen(newname) == 0) {
		free(newname);
		return NULL;
	}
	return newname;
}

static void print_property_name(char *pname, int size)
{
        char *name;
                                                                                                                                              
        name = get_property_name(pname, size);
        if (!name) {
                return;
        }
        cli_dbgmsg("%34s ", name);
        free(name);
        return;
}

static void print_ole2_property(property_t *property)
{
	if (property->name_size > 64) {
                cli_dbgmsg("[err name len: %d]\n", property->name_size);
                return;
        }
	print_property_name(property->name, property->name_size);
	switch (property->type) {
	case 2:
		cli_dbgmsg(" [file] ");
		break;
	case 1:
		cli_dbgmsg(" [dir ] ");
		break;
	case 5:
		cli_dbgmsg(" [root] ");
		break;
	default:
		cli_dbgmsg(" [%d]", property->type);
	}
	switch (property->color) {
	case 0:
		cli_dbgmsg(" r ");
		break;
	case 1:
		cli_dbgmsg(" b ");
		break;
	default:
		cli_dbgmsg(" u ");
	}
	cli_dbgmsg(" 0x%.8x 0x%.8x\n", property->size, property->user_flags);
}

static void print_ole2_header(ole2_header_t *hdr)
{
	int i;
	
	if (!hdr) {
		return;
	}
	
	cli_dbgmsg("\nMagic:\t\t\t0x");
	for (i=0 ; i<8; i++) {
		cli_dbgmsg("%x", hdr->magic[i]);
	}
	cli_dbgmsg("\n");

	cli_dbgmsg("CLSID:\t\t\t{");
	for (i=0 ; i<16; i++) {
		cli_dbgmsg("%x ", hdr->clsid[i]);
	}
	cli_dbgmsg("}\n");

	cli_dbgmsg("Minor version:\t\t0x%x\n", hdr->minor_version);
	cli_dbgmsg("DLL version:\t\t0x%x\n", hdr->dll_version);
	cli_dbgmsg("Byte Order:\t\t%d\n", hdr->byte_order);
	cli_dbgmsg("Big Block Size:\t\t%i\n", hdr->log2_big_block_size);
	cli_dbgmsg("Small Block Size:\t%i\n", hdr->log2_small_block_size);
	cli_dbgmsg("BAT count:\t\t%d\n", hdr->bat_count);
	cli_dbgmsg("Prop start:\t\t%d\n", hdr->prop_start);
	cli_dbgmsg("SBAT cutoff:\t\t%d\n", hdr->sbat_cutoff);
	cli_dbgmsg("SBat start:\t\t%d\n", hdr->sbat_start);
	cli_dbgmsg("SBat block count:\t%d\n", hdr->sbat_block_count);
	cli_dbgmsg("XBat start:\t\t%d\n", hdr->xbat_start);
	cli_dbgmsg("XBat block count:\t%d\n\n", hdr->xbat_count);
	return;
}

static int ole2_read_block(int fd, ole2_header_t *hdr, void *buff, int32_t blockno)
{
	off_t offset, offend;

	if (blockno < 0) {
		return FALSE;
	}
	
	/* other methods: (blockno+1) * 512 or (blockno * block_size) + 512; */
	offset = (blockno << hdr->log2_big_block_size) + 512;	/* 512 is header size */
	
	if (hdr->m_area == NULL) {
		if (lseek(fd, offset, SEEK_SET) != offset) {
			return FALSE;
		}
		if (cli_readn(fd, buff, (1 << hdr->log2_big_block_size)) != (1 << hdr->log2_big_block_size)) {
			return FALSE;
		}
	} else {
		offend = offset + (1 << hdr->log2_big_block_size);
		if ((offend <= 0) || (offend > hdr->m_length)) {
			return FALSE;
		}
		memcpy(buff, hdr->m_area+offset, (1 << hdr->log2_big_block_size));
	}
	return TRUE;
}

static int32_t ole2_get_next_bat_block(int fd, ole2_header_t *hdr, int32_t current_block)
{
	int32_t bat_array_index;
	uint32_t bat[128];

	if (current_block < 0) {
		return -1;
	}
	
	bat_array_index = current_block / 128;
	if (bat_array_index > hdr->bat_count) {
		cli_dbgmsg("bat_array index error\n");
		return -10;
	}
	if (!ole2_read_block(fd, hdr, &bat,
			ole2_endian_convert_32(hdr->bat_array[bat_array_index]))) {
		return -1;
	}
	return ole2_endian_convert_32(bat[current_block-(bat_array_index * 128)]);
}

static int32_t ole2_get_next_xbat_block(int fd, ole2_header_t *hdr, int32_t current_block)
{
	int32_t xbat_index, xbat_block_index, bat_index, bat_blockno;
	uint32_t xbat[128], bat[128];

	if (current_block < 0) {
		return -1;
	}
	
	xbat_index = current_block / 128;

	/* NB:	The last entry in each XBAT points to the next XBAT block.
		This reduces the number of entries in each block by 1.
	*/
	xbat_block_index = (xbat_index - 109) / 127;
	bat_blockno = (xbat_index - 109) % 127;

	bat_index = current_block % 128;

	if (!ole2_read_block(fd, hdr, &xbat, hdr->xbat_start)) {
		return -1;
	}

	/* Follow the chain of XBAT blocks */
	while (xbat_block_index > 0) {
		if (!ole2_read_block(fd, hdr, &xbat,
				ole2_endian_convert_32(xbat[127]))) {
			return -1;
		}
		xbat_block_index--;
	}

	if (!ole2_read_block(fd, hdr, &bat, ole2_endian_convert_32(xbat[bat_blockno]))) {
		return -1;
	}

	return ole2_endian_convert_32(bat[bat_index]);
}

static int32_t ole2_get_next_block_number(int fd, ole2_header_t *hdr, int32_t current_block)
{
	if (current_block < 0) {
		return -1;
	}

	if ((current_block / 128) > 108) {
		return ole2_get_next_xbat_block(fd, hdr, current_block);
	} else {
		return ole2_get_next_bat_block(fd, hdr, current_block);
	}
}

static int32_t ole2_get_next_sbat_block(int fd, ole2_header_t *hdr, int32_t current_block)
{
	int32_t iter, current_bat_block;
	uint32_t sbat[128];

	if (current_block < 0) {
		return -1;
	}
	
	current_bat_block = hdr->sbat_start;
	iter = current_block / 128;
	while (iter > 0) {
		current_bat_block = ole2_get_next_block_number(fd, hdr, current_bat_block);
		iter--;
	}
	if (!ole2_read_block(fd, hdr, &sbat, current_bat_block)) {
		return -1;
	}
	return ole2_endian_convert_32(sbat[current_block % 128]);
}

/* Retrieve the block containing the data for the given sbat index */
static int32_t ole2_get_sbat_data_block(int fd, ole2_header_t *hdr, void *buff, int32_t sbat_index)
{
	int32_t block_count, current_block;

	if (sbat_index < 0) {
		return FALSE;
	}
	
	if (hdr->sbat_root_start < 0) {
		cli_errmsg("No root start block\n");
		return FALSE;
	}

	block_count = sbat_index / 8;			/* 8 small blocks per big block */
	current_block = hdr->sbat_root_start;
	while (block_count > 0) {
		current_block = ole2_get_next_block_number(fd, hdr, current_block);
		block_count--;
	}
	/* current_block now contains the block number of the sbat array
	   containing the entry for the required small block */

	return(ole2_read_block(fd, hdr, buff, current_block));
}

/* Read the property tree.
   It is read as just an array rather than a tree */
/*
static void ole2_read_property_tree(int fd, ole2_header_t *hdr, const char *dir,
				int (*handler)(int fd, ole2_header_t *hdr, property_t *prop, const char *dir))
{
	property_t prop_block[4];
	int32_t index, current_block, count=0;
	
	current_block = hdr->prop_start;

	while(current_block >= 0) {
		if (!ole2_read_block(fd, hdr, prop_block,
					current_block)) {
			return;
		}
		for (index=0 ; index < 4 ; index++) {
			if (prop_block[index].type > 0) {
				prop_block[index].name_size = ole2_endian_convert_16(prop_block[index].name_size);
				prop_block[index].prev = ole2_endian_convert_32(prop_block[index].prev);
				prop_block[index].next = ole2_endian_convert_32(prop_block[index].next);
				prop_block[index].child = ole2_endian_convert_32(prop_block[index].child);
				prop_block[index].user_flags = ole2_endian_convert_32(prop_block[index].user_flags);
				prop_block[index].create_lowdate = ole2_endian_convert_32(prop_block[index].create_lowdate);
				prop_block[index].create_highdate = ole2_endian_convert_32(prop_block[index].create_highdate);
				prop_block[index].mod_lowdate = ole2_endian_convert_32(prop_block[index].mod_lowdate);
				prop_block[index].mod_highdate = ole2_endian_convert_32(prop_block[index].mod_highdate);
				prop_block[index].start_block = ole2_endian_convert_32(prop_block[index].start_block);
				prop_block[index].size = ole2_endian_convert_32(prop_block[index].size);
				if (prop_block[index].type > 5) {
					cli_dbgmsg("ERROR: invalid property type: %d\n", prop_block[index].type);
					return;
				}
				if (prop_block[index].type == 5) {
					hdr->sbat_root_start = prop_block[index].start_block;
				}
				print_ole2_property(&prop_block[index]);
				if (!handler(fd, hdr, &prop_block[index], dir)) {
					cli_dbgmsg("ERROR: handler failed\n");
					return;
				}
			}
		}
		current_block = ole2_get_next_block_number(fd, hdr, current_block);
		if (++count > 100000) {
			cli_dbgmsg("ERROR: loop detected\n");
			return;
		}
	}
	return;
}
*/

static void ole2_walk_property_tree(int fd, ole2_header_t *hdr, const char *dir, int32_t prop_index,
				int (*handler)(int fd, ole2_header_t *hdr, property_t *prop, const char *dir),
				unsigned int rec_level, unsigned int *file_count, const struct cl_limits *limits)
{
	property_t prop_block[4];
	int32_t index, current_block, i;
	char *dirname;

	current_block = hdr->prop_start;

	if ((prop_index < 0) || (prop_index > hdr->max_block_no) || (rec_level > 100) || (*file_count > 100000)) {
		return;
	}

	if (limits && limits->maxfiles && (*file_count > limits->maxfiles)) {
		cli_dbgmsg("OLE2: File limit reached (max: %d)\n", limits->maxfiles);
		return;
	}
	
	if (limits && limits->maxreclevel && (rec_level > limits->maxreclevel)) {
		cli_dbgmsg("OLE2: Recursion limit reached (max: %d)\n", limits->maxreclevel);
		return;
	}

	index = prop_index / 4;
	for (i=0 ; i < index ; i++) {
		current_block = ole2_get_next_block_number(fd, hdr, current_block);
		if (current_block < 0) {
			return;
		}
	}
	index = prop_index % 4;
	if (!ole2_read_block(fd, hdr, prop_block,
			current_block)) {
		return;
	}	
	if (prop_block[index].type <= 0) {
		return;
	}
	prop_block[index].name_size = ole2_endian_convert_16(prop_block[index].name_size);
	prop_block[index].prev = ole2_endian_convert_32(prop_block[index].prev);
	prop_block[index].next = ole2_endian_convert_32(prop_block[index].next);
	prop_block[index].child = ole2_endian_convert_32(prop_block[index].child);
	prop_block[index].user_flags = ole2_endian_convert_32(prop_block[index].user_flags);
	prop_block[index].create_lowdate = ole2_endian_convert_32(prop_block[index].create_lowdate);
	prop_block[index].create_highdate = ole2_endian_convert_32(prop_block[index].create_highdate);
	prop_block[index].mod_lowdate = ole2_endian_convert_32(prop_block[index].mod_lowdate);
	prop_block[index].mod_highdate = ole2_endian_convert_32(prop_block[index].mod_highdate);
	prop_block[index].start_block = ole2_endian_convert_32(prop_block[index].start_block);
	prop_block[index].size = ole2_endian_convert_32(prop_block[index].size);
	
	print_ole2_property(&prop_block[index]);

	/* Check we aren't in a loop */
	if (cli_bitset_test(hdr->bitset, (unsigned long) prop_index)) {
		/* Loop in property tree detected */
		cli_dbgmsg("OLE2: Property tree loop detected at index %d\n", prop_index);
		return;
	}
	if (!cli_bitset_set(hdr->bitset, (unsigned long) prop_index)) {
		return;
	}

	switch (prop_block[index].type) {
		case 5: /* Root Entry */
			if ((prop_index != 0) || (rec_level !=0) ||
					(*file_count != 0)) {
				/* Can only have RootEntry as the top */
				cli_dbgmsg("ERROR: illegal Root Entry\n");
				return;
			}
			hdr->sbat_root_start = prop_block[index].start_block;
			ole2_walk_property_tree(fd, hdr, dir,
				prop_block[index].prev, handler, rec_level+1, file_count, limits);
			ole2_walk_property_tree(fd, hdr, dir,
				prop_block[index].next, handler, rec_level+1, file_count, limits);
			ole2_walk_property_tree(fd, hdr, dir,
				prop_block[index].child, handler, rec_level+1, file_count, limits);
			break;
		case 2: /* File */
			(*file_count)++;
			if (!handler(fd, hdr, &prop_block[index], dir)) {
				cli_dbgmsg("ERROR: handler failed\n");
				/* If we don't return on this error then
					we can sometimes pull VBA code
					from corrupted files.
				*/
			
			}
			ole2_walk_property_tree(fd, hdr, dir,
				prop_block[index].prev, handler, rec_level, file_count, limits);
			ole2_walk_property_tree(fd, hdr, dir,
				prop_block[index].next, handler, rec_level, file_count, limits);
			ole2_walk_property_tree(fd, hdr, dir,
				prop_block[index].child, handler, rec_level, file_count, limits);
			break;
		case 1: /* Directory */
			dirname = (char *) cli_malloc(strlen(dir)+8);
			if (!dirname)  {
				return;
			}
			snprintf(dirname, strlen(dir)+8, "%s/%.6d", dir, prop_index);
			if (mkdir(dirname, 0700) != 0) {
				free(dirname);
				return;
			}
			cli_dbgmsg("OLE2 dir entry: %s\n",dirname);
			ole2_walk_property_tree(fd, hdr, dir,
				prop_block[index].prev, handler, rec_level+1, file_count, limits);
			ole2_walk_property_tree(fd, hdr, dir,
				prop_block[index].next, handler, rec_level+1, file_count, limits);
			ole2_walk_property_tree(fd, hdr, dirname,
				prop_block[index].child, handler, rec_level+1, file_count, limits);
			free(dirname);
			break;
		default:
			cli_dbgmsg("ERROR: unknown OLE2 entry type: %d\n", prop_block[index].type);
			break;
	}
	return;
}
/* Write file Handler - write the contents of the entry to a file */
static int handler_writefile(int fd, ole2_header_t *hdr, property_t *prop, const char *dir)
{
	unsigned char *buff;
	int32_t current_block, ofd, len, offset;
	char *name, *newname;
	bitset_t *blk_bitset;

	if (prop->type != 2) {
		/* Not a file */
		return TRUE;
	}

	if (prop->name_size > 64) {
		cli_dbgmsg("\nERROR: property name too long: %d\n", prop->name_size);
		return FALSE;
	}

	if (! (name = get_property_name(prop->name, prop->name_size))) {
		/* File without a name - create a name for it */
		off_t i;
                                                                                                                            
		i = lseek(fd, 0, SEEK_CUR);
		name = (char *) cli_malloc(11);
		if (!name) {
			return FALSE;
		}
		snprintf(name, 11, "%.10ld", i + (long int) prop);
	} else {
		/* Sanitize the file name */
		sanitiseName(name);
	}

	newname = (char *) cli_malloc(strlen(name) + strlen(dir) + 2);
	if (!newname) {
		free(name);
		return FALSE;
	}

	sprintf(newname, "%s/%s", dir, name);
	free(name);

	ofd = open(newname, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, S_IRWXU);
	if (ofd < 0) {
		cli_errmsg("ERROR: failed to create file: %s\n", newname);
		free(newname);
		return FALSE;
	}
	free(newname);
	current_block = prop->start_block;
	len = prop->size;

	buff = (unsigned char *) cli_malloc(1 << hdr->log2_big_block_size);
	if (!buff) {
		close(ofd);
		return FALSE;
	}
	
	blk_bitset = cli_bitset_init();
	if (!blk_bitset) {
		cli_errmsg("ERROR [handler_writefile]: init bitset failed\n");
		close(ofd);
		return FALSE;
	}
	while((current_block >= 0) && (len > 0)) {
		if (current_block > hdr->max_block_no) {
                        cli_dbgmsg("OLE2: Max block number for file size exceeded: %d\n", current_block);
                        close(ofd);
                        free(buff);
                        cli_bitset_free(blk_bitset);
                        return FALSE;
                }
		/* Check we aren't in a loop */
		if (cli_bitset_test(blk_bitset, (unsigned long) current_block)) {
			/* Loop in block list */
			cli_dbgmsg("OLE2: Block list loop detected\n");
			close(ofd);
			free(buff);
			cli_bitset_free(blk_bitset);
			return FALSE;
		}
		if (!cli_bitset_set(blk_bitset, (unsigned long) current_block)) {
			close(ofd);
			free(buff);
			cli_bitset_free(blk_bitset);
			return FALSE;
		}			
		if (prop->size < (int64_t)hdr->sbat_cutoff) {
			/* Small block file */
			if (!ole2_get_sbat_data_block(fd, hdr, buff, current_block)) {
				cli_dbgmsg("ole2_get_sbat_data_block failed\n");
				close(ofd);
				free(buff);
				cli_bitset_free(blk_bitset);
				return FALSE;
			}
			/* buff now contains the block with 8 small blocks in it */
			offset = 64 * (current_block % 8);
			if (cli_writen(ofd, &buff[offset], MIN(len,64)) != MIN(len,64)) {
				close(ofd);
				free(buff);
				cli_bitset_free(blk_bitset);
				return FALSE;
			}

			len -= MIN(len,64);
			current_block = ole2_get_next_sbat_block(fd, hdr, current_block);
		} else {
			/* Big block file */
			if (!ole2_read_block(fd, hdr, buff, current_block)) {
				close(ofd);
				free(buff);
				cli_bitset_free(blk_bitset);
				return FALSE;
			}
			if (cli_writen(ofd, buff, MIN(len,(1 << hdr->log2_big_block_size))) !=
							MIN(len,(1 << hdr->log2_big_block_size))) {
				close(ofd);
				free(buff);
				cli_bitset_free(blk_bitset);
				return FALSE;
			}

			current_block = ole2_get_next_block_number(fd, hdr, current_block);
			len -= MIN(len,(1 << hdr->log2_big_block_size));
		}
	}
	close(ofd);
	free(buff);
	cli_bitset_free(blk_bitset);
	return TRUE;
}

#if !defined(HAVE_ATTRIB_PACKED) && !defined(HAVE_PRAGMA_PACK) && !defined(HAVE_PRAGMA_PACK_HPPA)
static int ole2_read_header(int fd, ole2_header_t *hdr)
{
	int i;
	
	if (cli_readn(fd, &hdr->magic, 8) != 8) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->clsid, 16) != 16) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->minor_version, 2) != 2) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->dll_version, 2) != 2) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->byte_order, 2) != 2) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->log2_big_block_size, 2) != 2) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->log2_small_block_size, 4) != 4) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->reserved, 8) != 8) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->bat_count, 4) != 4) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->prop_start, 4) != 4) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->signature, 4) != 4) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->sbat_cutoff, 4) != 4) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->sbat_start, 4) != 4) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->sbat_block_count, 4) != 4) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->xbat_start, 4) != 4) {
		return FALSE;
	}
	if (cli_readn(fd, &hdr->xbat_count, 4) != 4) {
		return FALSE;
	}
	for (i=0 ; i < 109 ; i++) {
		if (cli_readn(fd, &hdr->bat_array[i], 4) != 4) {
			return FALSE;
		}
	}
	return TRUE;
}
#endif

int cli_ole2_extract(int fd, const char *dirname, const struct cl_limits *limits)
{
	ole2_header_t hdr;
	int hdr_size;
	struct stat statbuf;
	unsigned int file_count=0;
	
	cli_dbgmsg("in cli_ole2_extract()\n");
	
	/* size of header - size of other values in struct */
	hdr_size = sizeof(struct ole2_header_tag) - sizeof(int32_t) -
			sizeof(unsigned char *) - sizeof(off_t) - sizeof(bitset_t *) -
			sizeof(uint32_t);

	hdr.m_area = NULL;

	if (fstat(fd, &statbuf) == 0) {
		if (statbuf.st_size < hdr_size) {
			return 0;
		}
#ifdef HAVE_MMAP
		hdr.m_length = statbuf.st_size;
		hdr.m_area = (unsigned char *) mmap(NULL, hdr.m_length, PROT_READ, MAP_PRIVATE, fd, 0);
		if (hdr.m_area == MAP_FAILED) {
			hdr.m_area = NULL;
		} else {
			cli_dbgmsg("mmap'ed file\n");
			memcpy(&hdr, hdr.m_area, hdr_size);
		}
#endif
	}

	if (hdr.m_area == NULL) {
#if defined(HAVE_ATTRIB_PACKED) || defined(HAVE_PRAGMA_PACK) || defined(HAVE_PRAGMA_PACK_HPPA)
		if (cli_readn(fd, &hdr, hdr_size) != hdr_size) {
			return 0;
		}
#else
		if (!ole2_read_header(fd, &hdr)) {
			return 0;
		}
#endif
	}
	
	hdr.minor_version = ole2_endian_convert_16(hdr.minor_version);
	hdr.dll_version = ole2_endian_convert_16(hdr.dll_version);
	hdr.byte_order = ole2_endian_convert_16(hdr.byte_order);
	hdr.log2_big_block_size = ole2_endian_convert_16(hdr.log2_big_block_size);
	hdr.log2_small_block_size = ole2_endian_convert_32(hdr.log2_small_block_size);
	hdr.bat_count = ole2_endian_convert_32(hdr.bat_count);
	hdr.prop_start = ole2_endian_convert_32(hdr.prop_start);
	hdr.sbat_cutoff = ole2_endian_convert_32(hdr.sbat_cutoff);
	hdr.sbat_start = ole2_endian_convert_32(hdr.sbat_start);
	hdr.sbat_block_count = ole2_endian_convert_32(hdr.sbat_block_count);
	hdr.xbat_start = ole2_endian_convert_32(hdr.xbat_start);
	hdr.xbat_count = ole2_endian_convert_32(hdr.xbat_count);

	hdr.sbat_root_start = -1;

	hdr.bitset = cli_bitset_init();
	if (!hdr.bitset) {
		return CL_EOLE2;
	}

	if (memcmp(hdr.magic, magic_id, 8) != 0) {
		cli_dbgmsg("OLE2 magic failed!\n");
#ifdef HAVE_MMAP
		if (hdr.m_area != NULL) {
			munmap(hdr.m_area, hdr.m_length);
		}
#endif
		cli_bitset_free(hdr.bitset);
		return CL_EOLE2;
	}

	if (hdr.log2_big_block_size != 9) {
		cli_errmsg("WARNING: not scanned; untested big block size - please report\n");
		goto abort;
	}
	if (hdr.log2_small_block_size != 6) {
		cli_errmsg("WARNING: not scanned; untested small block size - please report\n");
		goto abort;
	}
	if (hdr.sbat_cutoff != 4096) {
		cli_errmsg("WARNING: not scanned; untested sbat cutoff - please report\n");
		goto abort;
	}

	/* 8 SBAT blocks per file block */
	hdr.max_block_no = ((statbuf.st_size / hdr.log2_big_block_size) + 1) * 8;
	
	print_ole2_header(&hdr);
	cli_dbgmsg("Max block number: %lu\n", hdr.max_block_no);

	/* NOTE: Select only ONE of the following two methods */
	
	/* ole2_read_property_tree(fd, &hdr, dirname, handler_writefile); */
	
	/* OR */
	
	ole2_walk_property_tree(fd, &hdr, dirname, 0, handler_writefile, 0, &file_count, limits);

abort:
#ifdef HAVE_MMAP
	if (hdr.m_area != NULL) {
		munmap(hdr.m_area, hdr.m_length);
	}
#endif
	cli_bitset_free(hdr.bitset);
	return 0;
}
