/*
 *  Extract component parts of ARJ archives.
 *
 *  Copyright (C) 2007-2008 Sourcefire, Inc.
 *
 *  Authors: Trog
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef	HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <clamav.h>
#include <ctype.h>

#include "clamav.h"
#include "cltypes.h"
#include "others.h"
#include "unarj.h"

#define FIRST_HDR_SIZE		30
#define COMMENT_MAX		2048
#define FNAME_MAX		512
#define HEADERSIZE_MAX		(FIRST_HDR_SIZE + 10 + FNAME_MAX + COMMENT_MAX)
#define MAXDICBIT		16
#define DDICSIZ			26624
#define THRESHOLD		3
#ifndef UCHAR_MAX
#define UCHAR_MAX		(255)
#endif
#ifndef CHAR_BIT
#define CHAR_BIT		(8)
#endif
#define MAXMATCH		256
#ifndef FALSE
#define FALSE	(0)
#define TRUE	(1)
#endif

#define CODE_BIT	16
#define NT		(CODE_BIT + 3)
#define PBIT		5
#define TBIT		5
#define NC		(UCHAR_MAX + MAXMATCH + 2 - THRESHOLD)
#define NP		(MAXDICBIT + 1)
#define CBIT		9
#define CTABLESIZE	4096
#define PTABLESIZE	256
#define STRTP		9
#define STOPP		13

#define STRTL		0
#define STOPL		7

#if NT > NP
#define NPT NT
#else
#define NPT NP
#endif

#ifndef MIN
#define MIN(a,b) ((a < b) ? a : b)
#endif

#define GARBLE_FLAG     0x01

#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 arj_main_hdr_tag {
	uint8_t first_hdr_size;		/* must be 30 bytes */
	uint8_t version;
	uint8_t min_version;
	uint8_t host_os;
	uint8_t flags;
	uint8_t security_version;
	uint8_t file_type;
	uint8_t pad;
	uint32_t time_created __attribute__ ((packed));
	uint32_t time_modified __attribute__ ((packed));
	uint32_t archive_size __attribute__ ((packed));
	uint32_t sec_env_file_position __attribute__ ((packed));
	uint16_t entryname_pos __attribute__ ((packed));
	uint16_t sec_trail_size __attribute__ ((packed));
	uint16_t host_data __attribute__ ((packed));
} arj_main_hdr_t;

typedef struct arj_file_hdr_tag {
	uint8_t first_hdr_size;		/* must be 30 bytes */
	uint8_t version;
	uint8_t min_version;
	uint8_t host_os;
	uint8_t flags;
	uint8_t method;
	uint8_t file_type;
	uint8_t password_mod;
	uint32_t time_modified __attribute__ ((packed));
	uint32_t comp_size __attribute__ ((packed));
	uint32_t orig_size __attribute__ ((packed));
	uint32_t orig_crc __attribute__ ((packed));
	uint16_t entryname_pos __attribute__ ((packed));
	uint16_t file_mode __attribute__ ((packed));
	uint16_t host_data __attribute__ ((packed));
} arj_file_hdr_t;

#ifdef HAVE_PRAGMA_PACK
#pragma pack()
#endif

#ifdef HAVE_PRAGMA_PACK_HPPA
#pragma pack
#endif

typedef struct arj_decode_tag {
	unsigned char *text;
	int fd;
	uint16_t blocksize;
	uint16_t bit_buf;
	int bit_count;
	uint32_t comp_size;
	int16_t getlen, getbuf;
	uint16_t left[2 * NC - 1];
	uint16_t right[2 * NC - 1];
	unsigned char c_len[NC];
	uint16_t c_table[CTABLESIZE];
	unsigned char pt_len[NPT];
	unsigned char sub_bit_buf;
	uint16_t pt_table[PTABLESIZE];
	int status;
} arj_decode_t;

static int fill_buf(arj_decode_t *decode_data, int n)
{
	decode_data->bit_buf = (decode_data->bit_buf << n) & 0xFFFF;
	while (n > decode_data->bit_count) {
		decode_data->bit_buf |= decode_data->sub_bit_buf << (n -= decode_data->bit_count);
		if (decode_data->comp_size != 0) {
			decode_data->comp_size--;
			if (cli_readn(decode_data->fd, &decode_data->sub_bit_buf, 1) != 1) {
				/* the file is most likely corrupted, so
				 * we return CL_EFORMAT instead of CL_EREAD
				 */
				decode_data->status = CL_EFORMAT;
				return CL_EFORMAT;
			}
		} else {
			decode_data->sub_bit_buf = 0;
		}
		decode_data->bit_count = CHAR_BIT;
	}
	decode_data->bit_buf |= decode_data->sub_bit_buf >> (decode_data->bit_count -= n);
	return CL_SUCCESS;
}

static int init_getbits(arj_decode_t *decode_data)
{
	decode_data->bit_buf = 0;
	decode_data->sub_bit_buf = 0;
	decode_data->bit_count = 0;
	return fill_buf(decode_data, 2 * CHAR_BIT);
}

static unsigned short arj_getbits(arj_decode_t *decode_data, int n)
{
	unsigned short x;
	
	x = decode_data->bit_buf >> (2 * CHAR_BIT - n);
	fill_buf(decode_data, n);
	return x;
}

static int decode_start(arj_decode_t *decode_data)
{
	decode_data->blocksize = 0;
	return init_getbits(decode_data);
}

static int write_text(int ofd, unsigned char *data, int length)
{
	int count;
	
	count = cli_writen(ofd, data, length);
	if (count != length) {
		return CL_EWRITE;
	} else {
		return CL_SUCCESS;
	}
}

static int make_table(arj_decode_t *decode_data, int nchar, unsigned char *bitlen, int tablebits,
			unsigned short *table, int tablesize)
{
	unsigned short count[17], weight[17], start[18], *p;
	unsigned int i, k, len, ch, jutbits, avail, nextcode, mask;
	
	for (i = 1; i <=16; i++) {
		count[i] = 0;
	}
	for (i = 0; (int)i < nchar; i++) {
		if (bitlen[i] >= 17) {
			cli_dbgmsg("UNARJ: bounds exceeded\n");
			decode_data->status = CL_EUNPACK;
			return CL_EUNPACK;
		}
		count[bitlen[i]]++;
	}
	
	start[1] = 0;
	for (i = 1; i <= 16; i++) {
		start[i+1] = start[i] + (count[i] << (16 - i));
	}
	if (start[17] != (unsigned short) (1 << 16)) {
		decode_data->status = CL_EUNPACK;
		return CL_EUNPACK;
	}
	
	jutbits = 16 - tablebits;
	if (tablebits >= 17) {
		cli_dbgmsg("UNARJ: bounds exceeded\n");
		decode_data->status = CL_EUNPACK;
		return CL_EUNPACK;
	}
	for (i = 1; (int)i <= tablebits; i++) {
		start[i] >>= jutbits;
		weight[i] = 1 << (tablebits - i);
	}
	while (i <= 16) {
		weight[i] = 1 << (16 - i);
		i++;
	}
	
	i = start[tablebits + 1] >> jutbits;
	if (i != (unsigned short) (1 << 16)) {
		k = 1 << tablebits;
		while (i != k) {
			if (i >= tablesize) {
				cli_dbgmsg("UNARJ: bounds exceeded\n");
				decode_data->status = CL_EUNPACK;
				return CL_EUNPACK;
			}
			table[i++] = 0;
		}
	}
	
	avail = nchar;
	mask = 1 << (15 - tablebits);
	for (ch = 0; (int)ch < nchar; ch++) {
		if ((len = bitlen[ch]) == 0) {
			continue;
		}
		if (len >= 17) {
			cli_dbgmsg("UNARJ: bounds exceeded\n");
			decode_data->status = CL_EUNPACK;
			return CL_EUNPACK;
		}
		k = start[len];
		nextcode = k + weight[len];
		if ((int)len <= tablebits) {
			if (nextcode > (unsigned int) tablesize) {
				decode_data->status = CL_EUNPACK;
				return CL_EUNPACK;
			}
			for (i = start[len]; i < nextcode; i++) {
				table[i] = ch;
			}
		} else {
			p = &table[k >> jutbits];
			i = len - tablebits;
			while (i != 0) {
				if (*p == 0) {
					if (avail >= (2 * NC - 1)) {
						cli_dbgmsg("UNARJ: bounds exceeded\n");
						decode_data->status = CL_EUNPACK;
						return CL_EUNPACK;
					}
					decode_data->right[avail] = decode_data->left[avail] = 0;
					*p = avail++;
				}
				if (*p >= (2 * NC - 1)) {
					cli_dbgmsg("UNARJ: bounds exceeded\n");
					decode_data->status = CL_EUNPACK;
					return CL_EUNPACK;
				}
				if (k & mask) {
					p = &decode_data->right[*p];
				} else {
					p = &decode_data->left[*p];
				}
				k <<= 1;
				i--;
			}
			*p = ch;
		}
		start[len] = nextcode;
	}
	return CL_SUCCESS;
}

static int read_pt_len(arj_decode_t *decode_data, int nn, int nbit, int i_special)
{
	int i, n;
	short c;
	unsigned short mask;
	
	n = arj_getbits(decode_data, nbit);
	if (n == 0) {
		if (nn > NPT) {
			cli_dbgmsg("UNARJ: bounds exceeded\n");
			decode_data->status = CL_EUNPACK;
			return CL_EUNPACK;
		}
		c = arj_getbits(decode_data, nbit);
		for (i = 0; i < nn; i++) {
			decode_data->pt_len[i] = 0;
		}
		for (i = 0; i < 256; i++) {
			decode_data->pt_table[i] = c;
		}
	} else {
		i = 0;
		while ((i < n) && (i < NPT)) {
			c = decode_data->bit_buf >> 13;
			if (c == 7) {
				mask = 1 << 12;
				while (mask & decode_data->bit_buf) {
					mask >>= 1;
					c++;
				}
			}
			fill_buf(decode_data, (c < 7) ? 3 : (int)(c - 3));
			if (decode_data->status != CL_SUCCESS) {
				return decode_data->status;
			}
			decode_data->pt_len[i++] = (unsigned char) c;
			if (i == i_special) {
				c = arj_getbits(decode_data, 2);
				if (decode_data->status != CL_SUCCESS) {
					return decode_data->status;
				}
				while ((--c >= 0) && (i < NPT)) {
					decode_data->pt_len[i++] = 0;
				}
			}
		}
		while ((i < nn) && (i < NPT)) {
			decode_data->pt_len[i++] = 0;
		}
		if (make_table(decode_data, nn, decode_data->pt_len, 8, decode_data->pt_table, PTABLESIZE) != CL_SUCCESS) {
			return CL_EUNPACK;
		}
	}
	return CL_SUCCESS;
}

static int read_c_len(arj_decode_t *decode_data)
{
	short i, c, n;
	unsigned short mask;
	
	n = arj_getbits(decode_data, CBIT);
	if (decode_data->status != CL_SUCCESS) {
		return decode_data->status;
	}
	if (n == 0) {
		c = arj_getbits(decode_data, CBIT);
		if (decode_data->status != CL_SUCCESS) {
			return decode_data->status;
		}
		for (i = 0; i < NC; i++) {
			decode_data->c_len[i] = 0;
		}
		for (i = 0; i < CTABLESIZE; i++) {
			decode_data->c_table[i] = c;
		}
	} else {
		i = 0;
		while (i < n) {
			c = decode_data->pt_table[decode_data->bit_buf >> 8];
			if (c >= NT) {
				mask = 1 << 7;
				do {
					if (c >= (2 * NC - 1)) {
						cli_warnmsg("ERROR: bounds exceeded\n");
						decode_data->status = CL_EFORMAT;
						return CL_EFORMAT;
					}
					if (decode_data->bit_buf & mask) {
						c = decode_data->right[c];
					} else {
						c = decode_data->left[c];
					}
					mask >>= 1;
				} while (c >= NT);
			}
			if (c >= 19) {
				cli_dbgmsg("UNARJ: bounds exceeded\n");
				decode_data->status = CL_EUNPACK;
				return CL_EUNPACK;
			}
			fill_buf(decode_data, (int)(decode_data->pt_len[c]));
			if (decode_data->status != CL_SUCCESS) {
				return decode_data->status;
			}	
			if (c <= 2) {
				if (c == 0) {
					c = 1;
				} else if (c == 1) {
					c = arj_getbits(decode_data, 4) + 3;
				} else {
					c = arj_getbits(decode_data, CBIT) + 20;
				}
				if (decode_data->status != CL_SUCCESS) {
					return decode_data->status;
				}		
				while (--c >= 0) {
					if (i >= NC) {
						cli_warnmsg("ERROR: bounds exceeded\n");
						decode_data->status = CL_EFORMAT;
						return CL_EFORMAT;
					}
					decode_data->c_len[i++] = 0;
				}
			} else {
				if (i >= NC) {
					cli_warnmsg("ERROR: bounds exceeded\n");
					decode_data->status = CL_EFORMAT;
					return CL_EFORMAT;
				}
				decode_data->c_len[i++] = (unsigned char) (c - 2);
			}
		}
		while (i < NC) {
			decode_data->c_len[i++] = 0;
		}
		if (make_table(decode_data, NC, decode_data->c_len, 12, decode_data->c_table, CTABLESIZE) != CL_SUCCESS) {
			return CL_EUNPACK;
		}
	}
	return CL_SUCCESS;
}


static uint16_t decode_c(arj_decode_t *decode_data)
{
	uint16_t j, mask;
	
	if (decode_data->blocksize == 0) {
		decode_data->blocksize = arj_getbits(decode_data, 16);
		read_pt_len(decode_data, NT, TBIT, 3);
		read_c_len(decode_data);
		read_pt_len(decode_data, NT, PBIT, -1);
	}
	decode_data->blocksize--;
	j = decode_data->c_table[decode_data->bit_buf >> 4];
	if (j >= NC) {
		mask = 1 << 3;
		do {
			if (j >= (2 * NC - 1)) {
				cli_warnmsg("ERROR: bounds exceeded\n");
				decode_data->status = CL_EUNPACK;
				return 0;
			}
			if (decode_data->bit_buf & mask) {
				j = decode_data->right[j];
			} else {
				j = decode_data->left[j];
			}
			mask >>= 1;
		} while (j >= NC);
	}
	fill_buf(decode_data, (int)(decode_data->c_len[j]));
	return j;
}

static uint16_t decode_p(arj_decode_t *decode_data)
{
	unsigned short j, mask;
	
	j = decode_data->pt_table[decode_data->bit_buf >> 8];
	if (j >= NP) {
		mask = 1 << 7;
		do {
			if (j >= (2 * NC - 1)) {
				cli_warnmsg("ERROR: bounds exceeded\n");
				decode_data->status = CL_EUNPACK;
				return 0;
			}
			if (decode_data->bit_buf & mask) {
				j = decode_data->right[j];
			} else {
				j = decode_data->left[j];
			}
			mask >>= 1;
		} while (j >= NP);
	}
	fill_buf(decode_data, (int)(decode_data->pt_len[j]));
	if (j != 0) {
		j--;
		j = (1 << j) + arj_getbits(decode_data, (int)j);
	}
	return j;
}

static int decode(int fd, arj_metadata_t *metadata)
{
	int ret;

	arj_decode_t decode_data;
	uint32_t count=0, out_ptr=0;
	int16_t chr, i, j;

	decode_data.text = (unsigned char *) cli_malloc(DDICSIZ);
	if (!decode_data.text) {
		return CL_EMEM;
	}
	decode_data.fd = fd;
	decode_data.comp_size = metadata->comp_size;
	ret = decode_start(&decode_data);
	if (ret != CL_SUCCESS) {
		free(decode_data.text);
		return ret;
	}
	decode_data.status = CL_SUCCESS;

	while (count < metadata->orig_size) {
		if ((chr = decode_c(&decode_data)) <= UCHAR_MAX) {
			decode_data.text[out_ptr] = (unsigned char) chr;
			count++;
			if (++out_ptr >= DDICSIZ) {
				out_ptr = 0;
				if (write_text(metadata->ofd, decode_data.text, DDICSIZ) != CL_SUCCESS) {
					free(decode_data.text);
					return CL_EWRITE;
				}
			}
		} else {
			j = chr - (UCHAR_MAX + 1 - THRESHOLD);
			count += j;
			i = decode_p(&decode_data);
			if ((i = out_ptr - i - 1) < 0) {
				i += DDICSIZ;
			}
			if ((i >= DDICSIZ) || (i < 0)) {
				cli_warnmsg("UNARJ: bounds exceeded - probably a corrupted file.\n");
				break;
			}
			if (out_ptr > i && out_ptr < DDICSIZ - MAXMATCH - 1) {
				while ((--j >= 0) && (i < DDICSIZ) && (out_ptr < DDICSIZ)) {
					decode_data.text[out_ptr++] = decode_data.text[i++];
				}
			} else {
				while (--j >= 0) {				
					decode_data.text[out_ptr] = decode_data.text[i];
					if (++out_ptr >= DDICSIZ) {
						out_ptr = 0;
						if (write_text(metadata->ofd, decode_data.text, DDICSIZ) != CL_SUCCESS) {
							free(decode_data.text);
							return CL_EWRITE;
						}
					}
					if (++i >= DDICSIZ) {
						i = 0;
					}
				}
			}
		}
		if (decode_data.status != CL_SUCCESS) {
			free(decode_data.text);
			return decode_data.status;
		}
	}
	if (out_ptr != 0) {
		write_text(metadata->ofd, decode_data.text, out_ptr);
	}
	
	free(decode_data.text);
	return CL_SUCCESS;
}

#define ARJ_BFIL(dd) {dd->getbuf|=dd->bit_buf>>dd->getlen;fill_buf(dd,CODE_BIT-dd->getlen);dd->getlen=CODE_BIT;}
#define ARJ_GETBIT(dd,c) {if(dd->getlen<=0)ARJ_BFIL(dd) c=(dd->getbuf&0x8000)!=0;dd->getbuf<<=1;dd->getlen--;}
#define ARJ_BPUL(dd,l) {dd->getbuf<<=l;dd->getlen-=l;}
#define ARJ_GETBITS(dd,c,l) {if(dd->getlen<l)ARJ_BFIL(dd) c=(uint16_t)dd->getbuf>>(CODE_BIT-l);ARJ_BPUL(dd,l)}

static uint16_t decode_ptr(arj_decode_t *decode_data)
{
	uint16_t c, width, plus, pwr;
	
	plus = 0;
	pwr = 1 << STRTP;
	for (width = STRTP; width < STOPP; width++) {
		ARJ_GETBIT(decode_data, c);
		if (c == 0) {
			break;
		}
		plus += pwr;
		pwr <<= 1;
	}
	if (width != 0) {
		ARJ_GETBITS(decode_data, c, width);
	}
	c += plus;
	return c;
}

static uint16_t decode_len(arj_decode_t *decode_data)
{
	uint16_t c, width, plus, pwr;

	plus = 0;
	pwr = 1 << STRTL;
	for (width = STRTL; width < STOPL; width++) {
		ARJ_GETBIT(decode_data, c);
		if (c == 0) {
			break;
		}
		plus += pwr;
		pwr <<= 1;
	}
	if (width != 0) {
		ARJ_GETBITS(decode_data, c, width);
	}
	c += plus;
	return c;
}

static int decode_f(int fd, arj_metadata_t *metadata)
{
	int ret;

	arj_decode_t decode_data, *dd;
	uint32_t count=0, out_ptr=0;
	int16_t chr, i, j, pos;

	dd = &decode_data;
	decode_data.text = (unsigned char *) cli_malloc(DDICSIZ);
	if (!decode_data.text) {
		return CL_EMEM;
	}
	decode_data.fd = fd;
	decode_data.comp_size = metadata->comp_size;
	ret = init_getbits(&decode_data);
	if (ret != CL_SUCCESS) {
		return ret;
	}
    	decode_data.getlen = decode_data.getbuf = 0;
	decode_data.status = CL_SUCCESS;
	
	while (count < metadata->orig_size) {
		chr = decode_len(&decode_data);
		if (decode_data.status != CL_SUCCESS) {
			free(decode_data.text);
			return decode_data.status;
		}		
		if (chr == 0) {
			ARJ_GETBITS(dd, chr, CHAR_BIT);
			if (decode_data.status != CL_SUCCESS) {
				free(decode_data.text);
				return decode_data.status;
			}
			decode_data.text[out_ptr] = (unsigned char) chr;
			count++;
			if (++out_ptr >= DDICSIZ) {
				out_ptr = 0;
				if (write_text(metadata->ofd, decode_data.text, DDICSIZ) != CL_SUCCESS) {
					free(decode_data.text);
					return CL_EWRITE;
				}
			}
		} else {
			j = chr - 1 + THRESHOLD;
			count += j;
			pos = decode_ptr(&decode_data);
			if (decode_data.status != CL_SUCCESS) {
				free(decode_data.text);
				return decode_data.status;
			}
			if ((i = out_ptr - pos - 1) < 0) {
				i += DDICSIZ;
			}
			if ((i >= DDICSIZ) || (i < 0)) {
				cli_warnmsg("UNARJ: bounds exceeded - probably a corrupted file.\n");
				break;
			}
			while (j-- > 0) {
				decode_data.text[out_ptr] = decode_data.text[i];
				if (++out_ptr >= DDICSIZ) {
					out_ptr = 0;
					if (write_text(metadata->ofd, decode_data.text, DDICSIZ) != CL_SUCCESS) {
						free(decode_data.text);
						return CL_EWRITE;
					}
				}
				if (++i >= DDICSIZ) {
					i = 0;
				}
			}
		}
	}
	if (out_ptr != 0) {
		write_text(metadata->ofd, decode_data.text, out_ptr);
	}
	
	free(decode_data.text);
	return CL_SUCCESS;
}	

static uint32_t arj_unstore(int ifd, int ofd, uint32_t len)
{
	unsigned char data[8192];
	uint32_t count, rem;
	unsigned int todo;

	cli_dbgmsg("in arj_unstore\n");
	rem = len;

	while (rem > 0) {
		todo = (unsigned int) MIN(8192, rem);
		count = cli_readn(ifd, data, todo);
		if (count != todo) {
			return len-rem;
		}
		if (cli_writen(ofd, data, count) != count) {
			return len-rem-count;
		}
		rem -= count;
	}
	return len;
}

static int is_arj_archive(int fd)
{
	const char header_id[2] = {0x60, 0xea};
	char mark[2];
	
	if (cli_readn(fd, &mark[0], 2) != 2) {
		return FALSE;
	}
	if (memcmp(&mark[0], &header_id[0], 2) == 0) {
		return TRUE;
	}
	cli_dbgmsg("Not an ARJ archive\n");
	return FALSE;
}

static int arj_read_main_header(int fd)
{
	uint16_t header_size, count;
	uint32_t crc;
	arj_main_hdr_t main_hdr;
	char *filename, *comment;
	off_t header_offset;

	if (cli_readn(fd, &header_size, 2) != 2) {
		return FALSE;
	}
	header_offset = lseek(fd, 0, SEEK_CUR);
	header_size = le16_to_host(header_size);
	cli_dbgmsg("Header Size: %d\n", header_size);
	if (header_size == 0) {
		/* End of archive */
		return FALSE;
	}
	if (header_size > HEADERSIZE_MAX) {
		cli_dbgmsg("arj_read_header: invalid header_size: %u\n ", header_size);
		return FALSE;
	}
	
	if (cli_readn(fd, &main_hdr, 30) != 30) {
		return FALSE;
	}
	
	cli_dbgmsg("ARJ Main File Header\n");
	cli_dbgmsg("First Header Size: %d\n", main_hdr.first_hdr_size);
	cli_dbgmsg("Version: %d\n", main_hdr.version);
	cli_dbgmsg("Min version: %d\n", main_hdr.min_version);
	cli_dbgmsg("Host OS: %d\n", main_hdr.host_os);
	cli_dbgmsg("Flags: 0x%x\n", main_hdr.flags);
	cli_dbgmsg("Security version: %d\n", main_hdr.security_version);
	cli_dbgmsg("File type: %d\n", main_hdr.file_type);

	if (main_hdr.first_hdr_size < 30) {
		cli_dbgmsg("Format error. First Header Size < 30\n");
		return FALSE;
	}
	if (main_hdr.first_hdr_size > 30) {
		if (lseek(fd, main_hdr.first_hdr_size - 30, SEEK_CUR) == -1) {
			return FALSE;
		}
	}

	filename = (char *) cli_malloc(header_size);
	if (!filename) {
		return FALSE;
	}
	for (count=0 ; count < header_size ; count++) {
		if (cli_readn(fd, &filename[count], 1) != 1) {
			free(filename);
			return FALSE;
		}
		if (filename[count] == '\0') {
			break;
		}
	}
	if (count == header_size) {
		free(filename);
		return FALSE;
	}
	comment = (char *) cli_malloc(header_size);
	if (!comment) {
		free(filename);
		return FALSE;
	}
	for (count=0 ; count < header_size ; count++) {
		if (cli_readn(fd, &comment[count], 1) != 1) {
			free(filename);
			free(comment);
			return FALSE;
		}
		if (comment[count] == '\0') {
			break;
		}
	}
	if (count == header_size) {
		free(filename);
		free(comment);
		return FALSE;
	}
	cli_dbgmsg("Filename: %s\n", filename);
	cli_dbgmsg("Comment: %s\n", comment);
	
	free(filename);
	free(comment);
	
	if (cli_readn(fd, &crc, 4) != 4) {
		return FALSE;
	}
	
	/* Skip past any extended header data */
	for (;;) {
		if (cli_readn(fd, &count, 2) != 2) {
			return FALSE;
		}
		count = le16_to_host(count);
		cli_dbgmsg("Extended header size: %d\n", count);
		if (count == 0) {
			break;
		}
		/* Skip extended header + 4byte CRC */
		if (lseek(fd, (off_t) (count + 4), SEEK_CUR) == -1) {
			return FALSE;
		}
	}
	return TRUE;
}

static int arj_read_file_header(int fd, arj_metadata_t *metadata)
{
	uint16_t header_size, count;
	char *filename, *comment;
	arj_file_hdr_t file_hdr;
	
	if (cli_readn(fd, &header_size, 2) != 2) {
		return CL_EFORMAT;
	}
	header_size = le16_to_host(header_size);
	cli_dbgmsg("Header Size: %d\n", header_size);
	if (header_size == 0) {
		/* End of archive */
		return CL_BREAK;
	}
	if (header_size > HEADERSIZE_MAX) {
		cli_dbgmsg("arj_read_file_header: invalid header_size: %u\n ", header_size);
		return CL_EFORMAT;
	}
	
	if (cli_readn(fd, &file_hdr, 30) != 30) {
		return CL_EFORMAT;
	}
	file_hdr.comp_size = le32_to_host(file_hdr.comp_size);
	file_hdr.orig_size = le32_to_host(file_hdr.orig_size);
	
	cli_dbgmsg("ARJ File Header\n");
	cli_dbgmsg("First Header Size: %d\n", file_hdr.first_hdr_size);
	cli_dbgmsg("Version: %d\n", file_hdr.version);
	cli_dbgmsg("Min version: %d\n", file_hdr.min_version);
	cli_dbgmsg("Host OS: %d\n", file_hdr.host_os);
	cli_dbgmsg("Flags: 0x%x\n", file_hdr.flags);
	cli_dbgmsg("Method: %d\n", file_hdr.method);
	cli_dbgmsg("File type: %d\n", file_hdr.file_type);
	cli_dbgmsg("File type: %d\n", file_hdr.password_mod);
	cli_dbgmsg("Compressed size: %u\n", file_hdr.comp_size);
	cli_dbgmsg("Original size: %u\n", file_hdr.orig_size);	

	if (file_hdr.first_hdr_size < 30) {
		cli_dbgmsg("Format error. First Header Size < 30\n");
		return CL_EFORMAT;
	}

	/* Note: this skips past any extended file start position data (multi-volume) */
	if (file_hdr.first_hdr_size > 30) {
		if (lseek(fd, file_hdr.first_hdr_size - 30, SEEK_CUR) == -1) {
			return CL_EFORMAT;
		}
	}

	filename = (char *) cli_malloc(header_size);
	if (!filename) {
		return CL_EMEM;
	}
	for (count=0 ; count < header_size ; count++) {
		if (cli_readn(fd, &filename[count], 1) != 1) {
			free(filename);
			return CL_EFORMAT;
		}
		if (filename[count] == '\0') {
			break;
		}
	}
	if (count == header_size) {
		free(filename);
		return CL_EFORMAT;
	}

	comment = (char *) cli_malloc(header_size);
	if (!comment) {
		free(filename);
		return CL_EFORMAT;
	}
	for (count=0 ; count < header_size ; count++) {
		if (cli_readn(fd, &comment[count], 1) != 1) {
			free(filename);
			free(comment);
			return CL_EFORMAT;
		}
		if (comment[count] == '\0') {
			break;
		}
	}
	if (count == header_size) {
		free(filename);
		free(comment);
		return CL_EFORMAT;
	}
	cli_dbgmsg("Filename: %s\n", filename);
	cli_dbgmsg("Comment: %s\n", comment);
	metadata->filename = cli_strdup(filename);

	free(filename);
	free(comment);

	/* Skip CRC */
	if (lseek(fd, (off_t) 4, SEEK_CUR) == -1) {
		return CL_EFORMAT;
	}
	
	/* Skip past any extended header data */
	for (;;) {
		if (cli_readn(fd, &count, 2) != 2) {
			return CL_EFORMAT;
		}
		count = le16_to_host(count);
		cli_dbgmsg("Extended header size: %d\n", count);
		if (count == 0) {
			break;
		}
		/* Skip extended header + 4byte CRC */
		if (lseek(fd, (off_t) (count + 4), SEEK_CUR) == -1) {
			return CL_EFORMAT;
		}
	}
	metadata->comp_size = file_hdr.comp_size;
	metadata->orig_size = file_hdr.orig_size;
	metadata->method = file_hdr.method;
	metadata->encrypted = ((file_hdr.flags & GARBLE_FLAG) != 0) ? TRUE : FALSE;
	metadata->ofd = -1;
	if (!metadata->filename) {
		return CL_EMEM;
	}
	
	return CL_SUCCESS;
}

int cli_unarj_open(int fd, const char *dirname)
{

	cli_dbgmsg("in cli_unarj_open\n");

	if (!is_arj_archive(fd)) {
		cli_dbgmsg("Not in ARJ format\n");
		return CL_EFORMAT;
	}
	if (!arj_read_main_header(fd)) {
		cli_dbgmsg("Failed to read main header\n");
		return CL_EFORMAT;
	}
	return CL_SUCCESS;
}

int cli_unarj_prepare_file(int fd, const char *dirname, arj_metadata_t *metadata)
{
	cli_dbgmsg("in cli_unarj_prepare_file\n");
	if (!metadata || !dirname || (fd < 0)) {
		return CL_ENULLARG;
	}
	/* Each file is preceeded by the ARJ file marker */
	if (!is_arj_archive(fd)) {
		cli_dbgmsg("Not in ARJ format\n");
		return CL_EFORMAT;
	}
	return arj_read_file_header(fd, metadata);
}

int cli_unarj_extract_file(int fd, const char *dirname, arj_metadata_t *metadata)
{
	off_t offset;
	int ret = CL_SUCCESS;
	char filename[1024];
	
	cli_dbgmsg("in cli_unarj_extract_file\n");
	if (!metadata || !dirname || (fd < 0)) {
		return CL_ENULLARG;
	}

	if (metadata->encrypted) {
		cli_dbgmsg("PASSWORDed file (skipping)\n");
		offset = lseek(fd, 0, SEEK_CUR) + metadata->comp_size;
		cli_dbgmsg("Target offset: %ld\n", offset);
		if (lseek(fd, offset, SEEK_SET) != offset) {
			return CL_ESEEK;
		}
		return CL_SUCCESS;
	}
	
	snprintf(filename, 1024, "%s/file.uar", dirname);
	cli_dbgmsg("Filename: %s\n", filename);
	metadata->ofd = open(filename, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, 0600);
	if (metadata->ofd < 0) {
		return CL_EOPEN;
	}
	switch (metadata->method) {
		case 0:
			ret = arj_unstore(fd, metadata->ofd, metadata->comp_size);
			if (ret != metadata->comp_size) {
				ret = CL_EWRITE;
			} else {
				ret = CL_SUCCESS;
			}
			break;
		case 1:
		case 2:
		case 3:
			ret = decode(fd, metadata);
			break;
		case 4:
			ret = decode_f(fd, metadata);
			break;
		default:
			ret = CL_EFORMAT;
			break;
	}
	return ret;
}
