libclamav/cab.c
24fd05e1
 /*
9518ee06
  *  Copyright (C) 2007-2008 Sourcefire, Inc.
  *
2023340a
  *  Authors: Tomasz Kojm
24fd05e1
  *
  *  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 <ctype.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
 #include <fcntl.h>
 
 #include "cltypes.h"
 #include "others.h"
 #include "mspack.h"
 #include "cab.h"
 
 #define EC32(x) le32_to_host(x) /* Convert little endian to host */
 #define EC16(x) le16_to_host(x)
 
 #ifndef O_BINARY
 #define O_BINARY        0
 #endif
 
 /* hard limits */
86e209d6
 #define CAB_FOLDER_LIMIT    5000
24fd05e1
 #define CAB_FILE_LIMIT	    5000
 
 /* Cabinet format data structures */
 
 struct cab_hdr {
     uint32_t	signature;	/* file signature */
     uint32_t	res1;		/* reserved */
     uint32_t	cbCabinet;	/* size of cabinet file */
     uint32_t	res2;		/* reserved */
     uint32_t	coffFiles;	/* offset of the first file entry */
     uint32_t	res3;		/* reserved */
     uint8_t	versionMinor;   /* file format version, minor */
     uint8_t	versionMajor;	/* file format version, major */
     uint16_t	cFolders;	/* number of folder entries */
     uint16_t	cFiles;		/* number of file entries */
     uint16_t	flags;		/* option flags */
     uint16_t	setID;		/* multiple cabs related */
     uint16_t	iCabinet;	/* multiple cabs related */
 };
 
 struct cab_hdr_opt {
     uint16_t	cbCFHeader;	/* size of reserved header area */
     uint8_t	cbCFFolder;	/* size of reserved folder area */
     uint8_t	cbCFData;	/* size of reserved block area */
 };
 
 struct cab_folder_hdr
 {
     uint32_t	coffCabStart;	/* offset of the first data block */
     uint16_t	cCFData;	/* number of data blocks */
     uint16_t	typeCompress;	/* compression type */
 };
 
 struct cab_file_hdr
 {
     uint32_t	cbFile;		    /* uncompressed size */
     uint32_t	uoffFolderStart;    /* uncompressed offset of file in folder */
     uint16_t	iFolder;	    /* folder index */
     uint16_t	date;		    /* date stamp */
     uint16_t	time;		    /* time stamp */
     uint16_t	attribs;	    /* attribute flags */
 };
 
 struct cab_block_hdr
 {
     uint32_t	csum;	    /* data block checksum */
     uint16_t	cbData;	    /* number of compressed bytes */
     uint16_t	cbUncomp;   /* number of uncompressed bytes */
 };
 
fc83da82
 static char *cab_readstr(int fd, int *ret)
24fd05e1
 {
 	int i, bread, found = 0;
 	char buff[256], *str;
 	off_t pos;
 
 
     if((pos = lseek(fd, 0, SEEK_CUR)) == -1) {
871177cd
 	*ret = CL_ESEEK;
24fd05e1
 	return NULL;
     }
 
     bread = read(fd, buff, sizeof(buff));
     for(i = 0; i < bread; i++) {
 	if(!buff[i]) {
 	    found = 1;
 	    break;
 	}
     }
 
     if(!found) {
 	*ret = CL_EFORMAT;
 	return NULL;
     }
 
     if(lseek(fd, (off_t) (pos + i + 1), SEEK_SET) == -1) {
0ba3503b
 	*ret = CL_EFORMAT; /* most likely a corrupted file */
24fd05e1
 	return NULL;
     }
 
     if(!(str = cli_strdup(buff))) {
 	*ret = CL_EMEM;
 	return NULL;
     }
 
     *ret = CL_SUCCESS;
     return str;
 }
 
72ce4b70
 static int cab_chkname(char *name, int san)
24fd05e1
 {
 	size_t i, len = strlen(name);
 
 
     for(i = 0; i < len; i++) {
72ce4b70
 	if(!san && (strchr("%/*?|\\\"+=<>;:\t ", name[i]) || !isascii(name[i]))) {
24fd05e1
 	    cli_dbgmsg("cab_chkname: File name contains disallowed characters\n");
 	    return 1;
72ce4b70
 	} else if(san && !isalnum(name[i])) {
 	    name[i] = '*';
24fd05e1
 	}
     }
 
     return 0;
 }
 
 void cab_free(struct cab_archive *cab)
 {
 	struct cab_folder *folder;
 	struct cab_file *file;
 
 
9518ee06
     if(cab->state) {
4ad62d4e
 	if(cab->state->stream) {
 	    switch(cab->state->cmethod & 0x000f) {
 		case 0x0001:
 		    mszip_free(cab->state->stream);
 		    break;
 		case 0x0002:
 		    qtm_free(cab->state->stream);
 		    break;
 		case 0x0003:
 		    lzx_free(cab->state->stream);
 	    }
9518ee06
 	}
 	free(cab->state);
     }
 
24fd05e1
     while(cab->folders) {
 	folder = cab->folders;
 	cab->folders = cab->folders->next;
 	free(folder);
     }
 
     while(cab->files) {
 	file = cab->files;
 	cab->files = cab->files->next;
 	free(file->name);
 	free(file);
     }
 }
 
 int cab_open(int fd, off_t offset, struct cab_archive *cab)
 {
72ce4b70
 	unsigned int i, folders = 0;
24fd05e1
 	struct cab_file *file, *lfile = NULL;
 	struct cab_folder *folder, *lfolder = NULL;
 	struct cab_hdr hdr;
 	struct cab_hdr_opt hdr_opt;
 	struct cab_folder_hdr folder_hdr;
 	struct cab_file_hdr file_hdr;
 	struct stat sb;
 	uint16_t fidx;
 	char *pt;
 	int ret;
 	off_t resfold = 0, rsize;
 
 
     if(lseek(fd, offset, SEEK_SET) == -1) {
 	cli_errmsg("cab_open: Can't lseek to %u (offset)\n", (unsigned int) offset);
871177cd
 	return CL_ESEEK;
24fd05e1
     }
 
     if(cli_readn(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
 	cli_dbgmsg("cab_open: Can't read cabinet header\n");
0ba3503b
 	return CL_EFORMAT; /* most likely a corrupted file */
24fd05e1
     }
 
     if(EC32(hdr.signature) != 0x4643534d) {
 	cli_dbgmsg("cab_open: Incorrect CAB signature\n");
 	return CL_EFORMAT;
     } else {
 	cli_dbgmsg("CAB: -------------- Cabinet file ----------------\n");
     }
 
     if(fstat(fd, &sb) == -1) {
 	cli_errmsg("cab_open: Can't fstat descriptor %d\n", fd);
871177cd
 	return CL_ESTAT;
24fd05e1
     }
     rsize = sb.st_size;
 
     memset(cab, 0, sizeof(struct cab_archive));
 
     cab->length = EC32(hdr.cbCabinet);
     cli_dbgmsg("CAB: Cabinet length: %u\n", cab->length);
f0f7f92f
     if((off_t) cab->length > rsize) {
72ce4b70
 	cli_dbgmsg("CAB: Truncating file size from %lu to %lu\n", (unsigned long int) cab->length, (unsigned long int) rsize);
f0f7f92f
 	cab->length = (uint32_t) rsize;
     }
24fd05e1
 
     cab->nfolders = EC16(hdr.cFolders);
     if(!cab->nfolders) {
 	cli_dbgmsg("cab_open: No folders in cabinet (fake cab?)\n");
 	return CL_EFORMAT;
     } else {
 	cli_dbgmsg("CAB: Folders: %u\n", cab->nfolders);
 	if(cab->nfolders > CAB_FOLDER_LIMIT) {
 	    cab->nfolders = CAB_FOLDER_LIMIT;
 	    cli_dbgmsg("CAB: *** Number of folders limited to %u ***\n", cab->nfolders);
 	}
     }
 
     cab->nfiles = EC16(hdr.cFiles);
     if(!cab->nfiles) {
 	cli_dbgmsg("cab_open: No files in cabinet (fake cab?)\n");
 	return CL_EFORMAT;
     } else {
 	cli_dbgmsg("CAB: Files: %u\n", cab->nfiles);
 	if(cab->nfiles > CAB_FILE_LIMIT) {
 	    cab->nfiles = CAB_FILE_LIMIT;
 	    cli_dbgmsg("CAB: *** Number of files limited to %u ***\n", cab->nfiles);
 	}
     }
 
     cli_dbgmsg("CAB: File format version: %u.%u\n", hdr.versionMajor, hdr.versionMinor);
 
     cab->flags = EC16(hdr.flags);
     if(cab->flags & 0x0004) {
 	if(cli_readn(fd, &hdr_opt, sizeof(hdr_opt)) != sizeof(hdr_opt)) {
 	    cli_dbgmsg("cab_open: Can't read file header (fake cab?)\n");
0ba3503b
 	    return CL_EFORMAT; /* most likely a corrupted file */
24fd05e1
 	}
 
 	cab->reshdr = EC16(hdr_opt.cbCFHeader);
 	resfold = hdr_opt.cbCFFolder;
 	cab->resdata = hdr_opt.cbCFData;
 
 	if(cab->reshdr) {
 	    if(lseek(fd, cab->reshdr, SEEK_CUR) == -1) {
 		cli_dbgmsg("cab_open: Can't lseek to %u (fake cab?)\n", cab->reshdr);
0ba3503b
 		return CL_EFORMAT; /* most likely a corrupted file */
24fd05e1
 	    }
 	}
     }
 
     if(cab->flags & 0x0001) { /* preceeding cabinet */
 	/* name */
 	pt = cab_readstr(fd, &ret);
 	if(ret)
 	    return ret;
72ce4b70
 	if(cab_chkname(pt, 0))
 	    cli_dbgmsg("CAB: Invalid name of preceeding cabinet\n");
24fd05e1
 	else
 	    cli_dbgmsg("CAB: Preceeding cabinet name: %s\n", pt);
 	free(pt);
 	/* info */
 	pt = cab_readstr(fd, &ret);
 	if(ret)
 	    return ret;
72ce4b70
 	if(cab_chkname(pt, 0))
 	    cli_dbgmsg("CAB: Invalid info for preceeding cabinet\n");
24fd05e1
 	else
 	    cli_dbgmsg("CAB: Preceeding cabinet info: %s\n", pt);
 	free(pt);
     }
 
     if(cab->flags & 0x0002) { /* next cabinet */
 	/* name */
 	pt = cab_readstr(fd, &ret);
 	if(ret)
 	    return ret;
72ce4b70
 	if(cab_chkname(pt, 0))
 	    cli_dbgmsg("CAB: Invalid name of next cabinet\n");
24fd05e1
 	else
 	    cli_dbgmsg("CAB: Next cabinet name: %s\n", pt);
 	free(pt);
 	/* info */
 	pt = cab_readstr(fd, &ret);
 	if(ret)
 	    return ret;
72ce4b70
 	if(cab_chkname(pt, 0))
 	    cli_dbgmsg("CAB: Invalid info for next cabinet\n");
24fd05e1
 	else
 	    cli_dbgmsg("CAB: Next cabinet info: %s\n", pt);
 	free(pt);
     }
 
     /* folders */
     for(i = 0; i < cab->nfolders; i++) {
 	if(cli_readn(fd, &folder_hdr, sizeof(folder_hdr)) != sizeof(folder_hdr)) {
72ce4b70
 	    cli_dbgmsg("cab_open: Can't read header for folder %u\n", i);
 	    break;
24fd05e1
 	}
 
 	if(resfold) {
 	    if(lseek(fd, resfold, SEEK_CUR) == -1) {
72ce4b70
 		cli_dbgmsg("cab_open: Can't lseek to %u (resfold)\n", (unsigned int) resfold);
 		break;
24fd05e1
 	    }
 	}
 
72ce4b70
 	if(EC32(folder_hdr.coffCabStart) + offset > rsize) {
 	    cli_dbgmsg("CAB: Folder out of file\n");
 	    continue;
 	}
 
 	if((EC16(folder_hdr.typeCompress) & 0x000f) > 3) {
 	    cli_dbgmsg("CAB: Unknown compression method\n");
 	    continue;
 	}
 
24fd05e1
 	folder = (struct cab_folder *) cli_calloc(1, sizeof(struct cab_folder));
 	if(!folder) {
 	    cli_errmsg("cab_open: Can't allocate memory for folder\n");
 	    cab_free(cab);
 	    return CL_EMEM;
 	}
 
 	folder->cab = (struct cab_archive *) cab;
 	folder->offset = (off_t) EC32(folder_hdr.coffCabStart) + offset;
 	folder->nblocks = EC16(folder_hdr.cCFData);
 	folder->cmethod = EC16(folder_hdr.typeCompress);
 
 	cli_dbgmsg("CAB: Folder record %u\n", i);
 	cli_dbgmsg("CAB: Folder offset: %u\n", (unsigned int) folder->offset);
 	cli_dbgmsg("CAB: Folder compression method: %d\n", folder->cmethod);
 
 	if(!lfolder)
 	    cab->folders = folder;
 	else
 	    lfolder->next = folder;
 
 	lfolder = folder;
72ce4b70
 	folders++;
24fd05e1
     }
72ce4b70
     cli_dbgmsg("CAB: Recorded folders: %u\n", folders);
24fd05e1
 
     /* files */
72ce4b70
     if(cab->nfolders != folders && lseek(fd, EC16(hdr.coffFiles), SEEK_SET) == -1) {
 	cli_dbgmsg("cab_open: Can't lseek to hdr.coffFiles\n");
 	cab_free(cab);
 	return CL_EFORMAT;
     }
24fd05e1
     for(i = 0; i < cab->nfiles; i++) {
 	if(cli_readn(fd, &file_hdr, sizeof(file_hdr)) != sizeof(file_hdr)) {
72ce4b70
 	    cli_dbgmsg("cab_open: Can't read file %u header\n", i);
 	    break;
24fd05e1
 	}
 
 	file = (struct cab_file *) cli_calloc(1, sizeof(struct cab_file));
 	if(!file) {
 	    cli_errmsg("cab_open: Can't allocate memory for file\n");
 	    cab_free(cab);
 	    return CL_EMEM;
 	}
 
 	file->cab = cab;
 	file->fd = fd;
 	file->offset = EC32(file_hdr.uoffFolderStart);
72ce4b70
 	file->length = EC32(file_hdr.cbFile);
24fd05e1
 	file->attribs = EC32(file_hdr.attribs);
 	fidx = EC32(file_hdr.iFolder);
72ce4b70
 	file->error = CL_SUCCESS;
24fd05e1
 
 	file->name = cab_readstr(fd, &ret);
 	if(ret) {
 	    free(file);
72ce4b70
 	    continue;
24fd05e1
 	}
72ce4b70
 	cab_chkname(file->name, 1);
24fd05e1
 
 	cli_dbgmsg("CAB: File record %u\n", i);
 	cli_dbgmsg("CAB: File name: %s\n", file->name);
794f3f23
 	cli_dbgmsg("CAB: File offset: %u\n", (unsigned int) file->offset);
24fd05e1
 	cli_dbgmsg("CAB: File folder index: %u\n", fidx);
 	cli_dbgmsg("CAB: File attribs: 0x%x\n", file->attribs);
 	if(file->attribs & 0x01)
 	    cli_dbgmsg("CAB:   * file is read-only\n");
 	if(file->attribs & 0x02)
 	    cli_dbgmsg("CAB:   * file is hidden\n");
 	if(file->attribs & 0x04)
 	    cli_dbgmsg("CAB:   * file is a system file\n");
 	if(file->attribs & 0x20)
 	    cli_dbgmsg("CAB:   * file modified since last backup\n");
 	if(file->attribs & 0x40)
 	    cli_dbgmsg("CAB:   * file to be run after extraction\n");
 	if(file->attribs & 0x80)
 	    cli_dbgmsg("CAB:   * file name contains UTF\n");
 
 	/* folder index */
 	if(fidx < 0xfffd) {
 	    if(fidx > cab->nfolders) {
72ce4b70
 		cli_dbgmsg("cab_open: File %s is not associated with any folder\n", file->name);
24fd05e1
 		free(file->name);
 		free(file);
 		continue;
 	    }
 
 	    file->folder = cab->folders;
 	    while(file->folder && fidx--)
 		file->folder = file->folder->next;
 
 	    if(!file->folder) {
72ce4b70
 		cli_dbgmsg("cab_open: Folder not found for file %s\n", file->name);
24fd05e1
 		free(file->name);
 		free(file);
72ce4b70
 		continue;
24fd05e1
 	    }
 
 	} else {
 	    cli_dbgmsg("CAB: File is split *skipping*\n");
 	    free(file->name);
 	    free(file);
 	    continue;
 	}
 
 	if(!lfile)
 	    cab->files = file;
 	else
 	    lfile->next = file;
 
 	lfile = file;
 
     }
 
     return CL_SUCCESS;
 }
 
 static int cab_read_block(int fd, struct cab_state *state, uint16_t resdata)
 {
 	struct cab_block_hdr block_hdr;
 
 
     if(cli_readn(fd, &block_hdr, sizeof(block_hdr)) != sizeof(block_hdr)) {
2b304af9
 	cli_dbgmsg("cab_read_block: Can't read block header\n");
0ba3503b
 	return CL_EFORMAT; /* most likely a corrupted file */
24fd05e1
     }
 
     if(resdata && lseek(fd, (off_t) resdata, SEEK_CUR) == -1) {
 	cli_dbgmsg("cab_read_block: lseek failed\n");
0ba3503b
 	return CL_EFORMAT; /* most likely a corrupted file */
24fd05e1
     }
 
     state->blklen = EC16(block_hdr.cbData);
     state->outlen = EC16(block_hdr.cbUncomp);
 
     if(cli_readn(fd, state->block, state->blklen) != state->blklen) {
 	cli_dbgmsg("cab_read_block: Can't read block data\n");
0ba3503b
 	return CL_EFORMAT; /* most likely a corrupted file */
24fd05e1
     }
 
     state->pt = state->end = state->block;
     state->end += state->blklen;
 
     return CL_SUCCESS;
 }
 
 static int cab_read(struct cab_file *file, unsigned char *buffer, int bytes)
 {
 	uint16_t todo, left;
 
 
72ce4b70
     if((file->cab->state->blknum > file->folder->nblocks) && !file->lread) {
 	file->error = CL_BREAK;
 	return -1;
     }
 
24fd05e1
     todo = bytes;
     while(todo > 0) {
9518ee06
 	left = file->cab->state->end - file->cab->state->pt;
24fd05e1
 
 	if(left) {
 	    if(left > todo)
 		left = todo;
 
9518ee06
 	    memcpy(buffer, file->cab->state->pt, left);
 	    file->cab->state->pt += left;
24fd05e1
 	    buffer += left;
 	    todo -= left;
 
 	} else {
72ce4b70
 	    if(file->cab->state->blknum++ >= file->folder->nblocks)
24fd05e1
 		break;
 
9518ee06
 	    file->error = cab_read_block(file->fd, file->cab->state, file->cab->resdata);
24fd05e1
 	    if(file->error)
 		return -1;
 
 	    if((file->folder->cmethod & 0x000f) == 0x0002) /* Quantum hack */
9518ee06
 		*file->cab->state->end++ = 0xff;
24fd05e1
 
9518ee06
 	    if(file->cab->state->blknum >= file->folder->nblocks) {
24fd05e1
 		if((file->folder->cmethod & 0x000f) == 0x0003) { /* LZX hack */
6cb9a52c
 		    lzx_set_output_length(file->cab->state->stream, (off_t) ((file->cab->state->blknum - 1) * 32768 + file->cab->state->outlen));
24fd05e1
 		}
 	    } else {
6cb9a52c
 		if(file->cab->state->outlen != 32768) {
24fd05e1
 		    cli_dbgmsg("cab_read: WARNING: partial data block\n");
 		}
 	    }
 	}
     }
 
72ce4b70
     return file->lread = bytes - todo;
24fd05e1
 }
 
79af2a64
 static int cab_unstore(struct cab_file *file, int bytes)
5a5e6312
 {
50084a59
 	int todo, bread;
5a5e6312
 	unsigned char buff[4096];
 
 
7e727361
     if(bytes < 0) {
d6f52e09
 	cli_dbgmsg("cab_unstore: bytes < 0\n");
7e727361
 	return CL_EFORMAT;
     }
 
8df99a92
     todo = MIN((unsigned int) bytes, file->max_size);
7e727361
 
5a5e6312
     while(1) {
 
50084a59
 	if((unsigned int) todo <= sizeof(buff))
 	    bread = todo;
 	else
 	    bread = sizeof(buff);
5a5e6312
 
50084a59
 	if((bread = cab_read(file, buff, bread)) == -1) {
 	    cli_dbgmsg("cab_unstore: cab_read failed for descriptor %d\n", file->fd);
 	    return file->error;
 	} else if(cli_writen(file->ofd, buff, bread) != bread) {
 	    cli_warnmsg("cab_unstore: Can't write %d bytes to descriptor %d\n", bread, file->ofd);
871177cd
 	    return CL_EWRITE;
5a5e6312
 	}
50084a59
 
 	todo -= bread;
 
 	if(!bread || todo <= 0)
 	    break;
5a5e6312
     }
 
     return CL_SUCCESS;
 }
 
9518ee06
 #define CAB_CHGFOLDER							\
     if(!file->cab->actfol || (file->folder != file->cab->actfol)) {	\
 	if(file->cab->state) {						\
4ad62d4e
 	    if(file->cab->state->stream) {				\
 		switch(file->cab->state->cmethod & 0x000f) {		\
 		    case 0x0001:					\
 			mszip_free(file->cab->state->stream);		\
 			break;						\
 		    case 0x0002:					\
 			qtm_free(file->cab->state->stream);		\
 			break;						\
 		    case 0x0003:					\
 			lzx_free(file->cab->state->stream);		\
 		}							\
9518ee06
 	    }								\
 	    free(file->cab->state);					\
4ad62d4e
 	    file->cab->state = NULL;					\
9518ee06
 	}								\
 	if(lseek(file->fd, file->folder->offset, SEEK_SET) == -1) {	\
 	    cli_dbgmsg("cab_extract: Can't lseek to %u\n", (unsigned int) file->folder->offset);							\
24555841
 	    close(file->ofd);						\
9518ee06
 	    return CL_EFORMAT; /* truncated file? */			\
 	}								\
 	file->cab->state = (struct cab_state *) cli_calloc(1, sizeof(struct cab_state));								\
 	if(!file->cab->state) {						\
 	    cli_errmsg("cab_extract: Can't allocate memory for internal state\n");									\
24555841
 	    close(file->ofd);						\
9518ee06
 	    return CL_EMEM;						\
 	}								\
 	file->cab->state->cmethod = file->folder->cmethod;		\
 	switch(file->folder->cmethod & 0x000f) {			\
 	    case 0x0001:						\
 		file->cab->state->stream = (struct mszip_stream *) mszip_init(file->fd, file->ofd, 4096, 1, file, &cab_read);				\
 		break;							\
 	    case 0x0002:						\
 		file->cab->state->stream = (struct qtm_stream *) qtm_init(file->fd, file->ofd, (int) (file->folder->cmethod >> 8) & 0x1f, 4096, file, &cab_read);									\
 		break;							\
 	    case 0x0003:						\
 		file->cab->state->stream = (struct lzx_stream *) lzx_init(file->fd, file->ofd, (int) (file->folder->cmethod >> 8) & 0x1f, 0, 4096, 0, file, &cab_read);									\
 	}								\
79af2a64
 	if((file->folder->cmethod & 0x000f) && !file->cab->state->stream) { \
9518ee06
 	    close(file->ofd);						\
871177cd
 	    return CL_EUNPACK;						\
9518ee06
 	}								\
4ad62d4e
 	file->cab->actfol = file->folder;				\
24555841
     } else {								\
     	if(file->cab->state && file->cab->state->stream) {		\
 	    switch(file->cab->state->cmethod & 0x000f) {		\
 		case 0x0001:						\
 		    ((struct mszip_stream *) file->cab->state->stream)->ofd = file->ofd;									\
 		    break;						\
 		case 0x0002:						\
 		    ((struct qtm_stream *) file->cab->state->stream)->ofd = file->ofd;									\
 		    break;						\
 		case 0x0003:						\
 		    ((struct lzx_stream *) file->cab->state->stream)->ofd = file->ofd;									\
 		    break;						\
 	    }								\
 	}								\
9518ee06
     }
 
 
5a5e6312
 int cab_extract(struct cab_file *file, const char *name)
24fd05e1
 {
5a5e6312
 	int ret;
24fd05e1
 
 
     if(!file || !name) {
 	cli_errmsg("cab_extract: !file || !name\n");
 	return CL_ENULLARG;
     }
 
9518ee06
     if(!file->folder) {
24fd05e1
 	cli_errmsg("cab_extract: file->folder == NULL\n");
 	return CL_ENULLARG;
     }
 
5a5e6312
     file->ofd = open(name, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, S_IRWXU);
     if(file->ofd == -1) {
24fd05e1
 	cli_errmsg("cab_extract: Can't open file %s in write mode\n", name);
871177cd
 	return CL_ECREAT;
24fd05e1
     }
 
     switch(file->folder->cmethod & 0x000f) {
5a5e6312
 	case 0x0000: /* STORE */
79af2a64
 	    cli_dbgmsg("CAB: Compression method: STORED\n");
 	    CAB_CHGFOLDER;
f0f7f92f
 	    if(file->length > file->cab->length) {
 		cli_dbgmsg("cab_extract: Stored file larger than archive itself, trimming down\n");
 		file->length = file->cab->length;
 	    }
79af2a64
 	    ret = cab_unstore(file, file->length);
5a5e6312
 	    break;
 
24fd05e1
 	case 0x0001: /* MSZIP */
 	    cli_dbgmsg("CAB: Compression method: MSZIP\n");
9518ee06
 	    CAB_CHGFOLDER;
 	    ret = mszip_decompress(file->cab->state->stream, file->length);
24fd05e1
 	    break;
 
 	case 0x0002: /* QUANTUM */
 	    cli_dbgmsg("CAB: Compression method: QUANTUM\n");
9518ee06
 	    CAB_CHGFOLDER;
 	    ret = qtm_decompress(file->cab->state->stream, file->length);
24fd05e1
 	    break;
 
 	case 0x0003: /* LZX */
 	    cli_dbgmsg("CAB: Compression method: LZX\n");
9518ee06
 	    CAB_CHGFOLDER;
 	    ret = lzx_decompress(file->cab->state->stream, file->length);
24fd05e1
 	    break;
 
 	default:
72ce4b70
 	    cli_dbgmsg("CAB: Not supported compression method: 0x%x\n", file->folder->cmethod & 0x000f);
24fd05e1
 	    ret = CL_EFORMAT;
     }
 
5a5e6312
     close(file->ofd);
24fd05e1
 
72ce4b70
     if(ret == CL_BREAK)
 	ret = CL_SUCCESS;
 
24fd05e1
     return ret;
 }