Пример #1
0
/* Header Record : fix endianness for useful fields */
static void headerrecord_to_host(hfsHeaderRecord *hdr)
{
    hdr->treeDepth = be16_to_host(hdr->treeDepth);
    hdr->rootNode = be32_to_host(hdr->rootNode);
    hdr->leafRecords = be32_to_host(hdr->leafRecords);
    hdr->firstLeafNode = be32_to_host(hdr->firstLeafNode);
    hdr->lastLeafNode = be32_to_host(hdr->lastLeafNode);
    hdr->nodeSize = be16_to_host(hdr->nodeSize);
    hdr->maxKeyLength = be16_to_host(hdr->maxKeyLength);
    hdr->totalNodes = be32_to_host(hdr->totalNodes);
    hdr->freeNodes = be32_to_host(hdr->freeNodes);
    hdr->attributes = be32_to_host(hdr->attributes); /* not too useful */
}
Пример #2
0
static uint16_t
vba_endian_convert_16(uint16_t value, int big_endian)
{
	if (big_endian)
		return (uint16_t)be16_to_host(value);
	else
		return le16_to_host(value);
}
Пример #3
0
void mbr_convert_to_host(struct mbr_boot_record *record)
{
    struct mbr_partition_entry *entry;
    unsigned i;
 
    for (i = 0; i < MBR_MAX_PARTITION_ENTRIES; ++i) {
        entry = &record->entries[i];
 
        entry->firstLBA = le32_to_host(entry->firstLBA);
        entry->numLBA = le32_to_host(entry->numLBA);
    }
    record->signature = be16_to_host(record->signature);
}
Пример #4
0
/* Node Descriptor : fix endianness for useful fields */
static void nodedescriptor_to_host(hfsNodeDescriptor *node)
{
    node->fLink = be32_to_host(node->fLink);
    node->bLink = be32_to_host(node->bLink);
    node->numRecords = be16_to_host(node->numRecords);
}
Пример #5
0
/* Given the catalog and other details, scan all the volume contents */
static int hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *catHeader,
    hfsHeaderRecord *extHeader, const char *dirname)
{
    int ret = CL_CLEAN;
    unsigned int has_alerts = 0;
    uint32_t thisNode, nodeLimit, nodesScanned = 0;
    uint16_t nodeSize, recordNum, topOfOffsets;
    uint16_t distance, recordStart, nextDist, nextStart;
    uint8_t *nodeBuf = NULL;
    hfsPlusForkData *catFork;

    catFork = &(volHeader->catalogFile);
    nodeLimit = MIN(catHeader->totalNodes, HFSPLUS_NODE_LIMIT);
    thisNode = catHeader->firstLeafNode;
    nodeSize = catHeader->nodeSize;

    /* Need to buffer current node, map will keep moving */
    nodeBuf = cli_malloc(nodeSize);
    if (!nodeBuf) {
        cli_dbgmsg("hfsplus_walk_catalog: failed to acquire node buffer, "
            "size " STDu32 "\n", nodeSize);
        return CL_EMEM;
    }

    /* Walk catalog leaf nodes, and scan contents of each */
    /* Because we want to scan them all, the index nodes add no value */
    while (ret == CL_CLEAN) {
        hfsNodeDescriptor nodeDesc;

        if (thisNode == 0) {
            cli_dbgmsg("hfsplus_walk_catalog: reached end of leaf nodes.\n");
            break;
        }
        if (nodesScanned++ > nodeLimit) {
            cli_dbgmsg("hfsplus_walk_catalog: node scan limit reached.\n");
            break;
        }

        /* fetch node into buffer */
        ret = hfsplus_fetch_node(ctx, volHeader, catHeader, extHeader, thisNode, nodeBuf);
        if (ret != CL_CLEAN) {
            cli_dbgmsg("hfsplus_walk_catalog: node fetch failed.\n");
            break;
        }
        memcpy(&nodeDesc, nodeBuf, 14);

        /* convert and validate node */
        nodedescriptor_to_host(&nodeDesc);
        nodedescriptor_print("leaf node", &nodeDesc);
        if ((nodeDesc.kind != HFS_NODEKIND_LEAF) || (nodeDesc.height != 1)) {
            cli_dbgmsg("hfsplus_walk_catalog: invalid leaf node!\n");
            ret = CL_EFORMAT;
            break;
        }
        if ((nodeSize / 4) < nodeDesc.numRecords) {
            cli_dbgmsg("hfsplus_walk_catalog: too many leaf records for one node!\n");
            ret = CL_EFORMAT;
            break;
        }

        /* Walk this node's records and scan */
        distance = nodeSize;
        recordStart = 14; /* 1st record can be after end of node descriptor */
        /* offsets take 1 u16 per at the end of the node, along with an empty space offset */
        topOfOffsets = nodeSize - (nodeDesc.numRecords * 2) - 2;
        for (recordNum = 0; recordNum < nodeDesc.numRecords; recordNum++) {
            uint16_t keylen;
            int16_t rectype;
            hfsPlusCatalogFile fileRec;

            /* Locate next record */
            nextDist = nodeSize - (recordNum * 2) - 2;
            nextStart = nodeBuf[nextDist] * 0x100 + nodeBuf[nextDist+1];
            /* Check record location */
            if ((nextStart > topOfOffsets-1) || (nextStart < recordStart)) {
                cli_dbgmsg("hfsplus_walk_catalog: bad record location %x for %u!\n", nextStart, recordNum);
                ret = CL_EFORMAT;
                break;
            }
            distance = nextDist;
            recordStart = nextStart;
            /* Get record key length */
            keylen = nodeBuf[recordStart] * 0x100 + nodeBuf[recordStart+1];
            keylen += keylen % 2; /* pad 1 byte if required to make 2-byte align */
            /* Validate keylen */
            if (recordStart + keylen + 4 >= topOfOffsets) {
                cli_dbgmsg("hfsplus_walk_catalog: key too long for location %x for %u!\n",
                    nextStart, recordNum);
                ret = CL_EFORMAT;
                break;
            }
            /* Copy type (after key, which is after keylength field) */
            memcpy(&rectype, &(nodeBuf[recordStart+keylen+2]), 2);
            rectype = be16_to_host(rectype);
            cli_dbgmsg("hfsplus_walk_catalog: record %u nextStart %x keylen %u type %d\n",
                recordNum, nextStart, keylen, rectype);
            /* Non-file records are not needed */
            if (rectype != HFSPLUS_RECTYPE_FILE) {
                continue;
            }
            /* Check file record location */
            if (recordStart+keylen+2+sizeof(hfsPlusCatalogFile) >= topOfOffsets) {
                cli_dbgmsg("hfsplus_walk_catalog: not enough bytes for file record!\n");
                ret = CL_EFORMAT;
                break;
            }
            memcpy(&fileRec, &(nodeBuf[recordStart+keylen+2]), sizeof(hfsPlusCatalogFile));

            /* Only scan files */
            fileRec.permissions.fileMode = be16_to_host(fileRec.permissions.fileMode);
            if ((fileRec.permissions.fileMode & HFS_MODE_TYPEMASK) == HFS_MODE_FILE) {
                /* Convert forks and scan */
                forkdata_to_host(&(fileRec.dataFork));
                forkdata_print("data fork:", &(fileRec.dataFork));
                if (fileRec.dataFork.logicalSize) {
                    ret = hfsplus_scanfile(ctx, volHeader, extHeader, &(fileRec.dataFork), dirname);
                }
                /* Check return code */
                if (ret == CL_VIRUS) {
                    has_alerts = 1;
                    if (SCAN_ALLMATCHES) {
                        /* Continue scanning in SCAN_ALLMATCHES mode */
                        cli_dbgmsg("hfsplus_walk_catalog: data fork alert, continuing");
                        ret = CL_CLEAN;
                    }
                }
                if (ret != CL_CLEAN) {
                    cli_dbgmsg("hfsplus_walk_catalog: data fork retcode %d\n", ret);
                    break;
                }
                /* Scan resource fork */
                forkdata_to_host(&(fileRec.resourceFork));
                forkdata_print("resource fork:", &(fileRec.resourceFork));
                if (fileRec.resourceFork.logicalSize) {
                    ret = hfsplus_scanfile(ctx, volHeader, extHeader, &(fileRec.resourceFork), dirname);
                }
                /* Check return code */
                if (ret == CL_VIRUS) {
                    has_alerts = 1;
                    if (SCAN_ALLMATCHES) {
                        /* Continue scanning in SCAN_ALLMATCHES mode */
                        cli_dbgmsg("hfsplus_walk_catalog: resource fork alert, continuing");
                        ret = CL_CLEAN;
                    }
                }
                if (ret != CL_CLEAN) {
                    cli_dbgmsg("hfsplus_walk_catalog: resource fork retcode %d", ret);
                    break;
                }
            }
            else {
                cli_dbgmsg("hfsplus_walk_catalog: record mode %o is not File\n", fileRec.permissions.fileMode);
            }
        }
        /* if return code, exit loop, message already logged */
        if (ret != CL_CLEAN) {
            break;
        }

        /* After that, proceed to next node */
        if (thisNode == nodeDesc.fLink) {
            /* Future heuristic */
            cli_warnmsg("hfsplus_walk_catalog: simple cycle detected!\n");
            ret = CL_EFORMAT;
            break;
        }
        else {
            thisNode = nodeDesc.fLink;
        }
    }

    free(nodeBuf);
    if (has_alerts) {
        ret = CL_VIRUS;
    }
    return ret;
}
Пример #6
0
/* Read and convert the HFS+ volume header */
static int hfsplus_volumeheader(cli_ctx *ctx, hfsPlusVolumeHeader **header)
{
    hfsPlusVolumeHeader *volHeader;
    const uint8_t *mPtr;

    if (!header) {
       return CL_ENULLARG;
    }

    /* Start with volume header, 512 bytes at offset 1024 */
    if ((*ctx->fmap)->len < 1536) {
        cli_dbgmsg("cli_scanhfsplus: too short for HFS+\n");
        return CL_EFORMAT;
    }
    mPtr = fmap_need_off_once(*ctx->fmap, 1024, 512);
    if (!mPtr) {
       cli_errmsg("cli_scanhfsplus: cannot read header from map\n");
       return CL_EMAP;
    }

    volHeader = cli_malloc(sizeof(hfsPlusVolumeHeader));
    if (!volHeader) {
       cli_errmsg("cli_scanhfsplus: header malloc failed\n");
       return CL_EMEM;
    }
    *header = volHeader;
    memcpy(volHeader, mPtr, 512);

    volHeader->signature = be16_to_host(volHeader->signature);
    volHeader->version = be16_to_host(volHeader->version);
    if ((volHeader->signature == 0x482B) && (volHeader->version == 4)) {
        cli_dbgmsg("cli_scanhfsplus: HFS+ signature matched\n");
    }
    else if ((volHeader->signature == 0x4858) && (volHeader->version == 5)) {
        cli_dbgmsg("cli_scanhfsplus: HFSX v5 signature matched\n");
    }
    else {
        cli_dbgmsg("cli_scanhfsplus: no matching signature\n");
        return CL_EFORMAT;
    }
    /* skip fields that will definitely be ignored */
    volHeader->attributes = be32_to_host(volHeader->attributes);
    volHeader->fileCount = be32_to_host(volHeader->fileCount);
    volHeader->folderCount = be32_to_host(volHeader->folderCount);
    volHeader->blockSize = be32_to_host(volHeader->blockSize);
    volHeader->totalBlocks = be32_to_host(volHeader->totalBlocks);

    cli_dbgmsg("HFS+ Header:\n");
    cli_dbgmsg("Signature: %x\n", volHeader->signature);
    cli_dbgmsg("Attributes: %x\n", volHeader->attributes);
    cli_dbgmsg("File Count: " STDu32 "\n", volHeader->fileCount);
    cli_dbgmsg("Folder Count: " STDu32 "\n", volHeader->folderCount);
    cli_dbgmsg("Block Size: " STDu32 "\n", volHeader->blockSize);
    cli_dbgmsg("Total Blocks: " STDu32 "\n", volHeader->totalBlocks);

    /* Block Size must be power of 2 between 512 and 1 MB */
    if ((volHeader->blockSize < 512) || (volHeader->blockSize > (1 << 20))) {
        cli_dbgmsg("cli_scanhfsplus: Invalid blocksize\n");
        return CL_EFORMAT;
    }
    if (volHeader->blockSize & (volHeader->blockSize - 1)) {
        cli_dbgmsg("cli_scanhfsplus: Invalid blocksize\n");
        return CL_EFORMAT;
    }

    forkdata_to_host(&(volHeader->allocationFile));
    forkdata_to_host(&(volHeader->extentsFile));
    forkdata_to_host(&(volHeader->catalogFile));
    forkdata_to_host(&(volHeader->attributesFile));
    forkdata_to_host(&(volHeader->startupFile));

    if (cli_debug_flag) {
        forkdata_print("allocationFile", &(volHeader->allocationFile));
        forkdata_print("extentsFile", &(volHeader->extentsFile));
        forkdata_print("catalogFile", &(volHeader->catalogFile));
        forkdata_print("attributesFile", &(volHeader->attributesFile));
        forkdata_print("startupFile", &(volHeader->startupFile));
    }

    return CL_CLEAN;
}
Пример #7
0
int cli_scanapm(cli_ctx *ctx)
{
    struct apm_driver_desc_map ddm;
    struct apm_partition_info aptable, apentry;
    int ret = CL_CLEAN, detection = CL_CLEAN, old_school = 0;
    size_t sectorsize, maplen, partsize, sectorcheck;
    off_t pos = 0, partoff = 0;
    unsigned i;
    uint32_t max_prtns = 0;

    if (!ctx || !ctx->fmap) {
        cli_errmsg("cli_scanapm: Invalid context\n");
        return CL_ENULLARG;
    }

    /* read driver description map at sector 0  */
    if (fmap_readn(*ctx->fmap, &ddm, pos, sizeof(ddm)) != sizeof(ddm)) {
        cli_dbgmsg("cli_scanapm: Invalid Apple driver description map\n");
        return CL_EFORMAT;
    }

    /* convert driver description map big-endian to host */
    ddm.signature = be16_to_host(ddm.signature);
    ddm.blockSize = be16_to_host(ddm.blockSize);
    ddm.blockCount = be32_to_host(ddm.blockCount);

    /* check DDM signature */
    if (ddm.signature != DDM_SIGNATURE) {
        cli_dbgmsg("cli_scanapm: Apple driver description map signature mismatch\n");
        return CL_EFORMAT;
    }

    /* sector size is determined by the ddm */
    sectorsize = ddm.blockSize;

    /* size of total file must be described by the ddm */
    maplen = (*ctx->fmap)->real_len;
    if ((ddm.blockSize * ddm.blockCount) != maplen) {
        cli_dbgmsg("cli_scanapm: File described %u size does not match %lu actual size\n",
                   (ddm.blockSize * ddm.blockCount), (unsigned long)maplen);
        return CL_EFORMAT;
    }

    /* check for old-school partition map */
    if (sectorsize == 2048) {
        if (fmap_readn(*ctx->fmap, &aptable, APM_FALLBACK_SECTOR_SIZE, sizeof(aptable)) != sizeof(aptable)) {
            cli_dbgmsg("cli_scanapm: Invalid Apple partition entry\n");
            return CL_EFORMAT;
        }

        aptable.signature = be16_to_host(aptable.signature);
        if (aptable.signature == APM_SIGNATURE) {
            sectorsize = APM_FALLBACK_SECTOR_SIZE;
            old_school = 1;
        }
    }

    /* read partition table at sector 1 (or after the ddm if old-school) */
    pos = APM_PTABLE_BLOCK * sectorsize;

    if (fmap_readn(*ctx->fmap, &aptable, pos, sizeof(aptable)) != sizeof(aptable)) {
        cli_dbgmsg("cli_scanapm: Invalid Apple partition table\n");
        return CL_EFORMAT;
    }

    /* convert partition table big endian to host */
    aptable.signature = be16_to_host(aptable.signature);
    aptable.numPartitions = be32_to_host(aptable.numPartitions);
    aptable.pBlockStart = be32_to_host(aptable.pBlockStart);
    aptable.pBlockCount = be32_to_host(aptable.pBlockCount);

    /* check the partition entry signature */
    if (aptable.signature != APM_SIGNATURE) {
        cli_dbgmsg("cli_scanapm: Apple partition table signature mismatch\n");
        return CL_EFORMAT;
    }

    /* check if partition table partition */
    if (strncmp((char*)aptable.type, "Apple_Partition_Map", 32) &&
            strncmp((char*)aptable.type, "Apple_partition_map", 32) &&
            strncmp((char*)aptable.type, "Apple_patition_map", 32)) {
        cli_dbgmsg("cli_scanapm: Initial Apple Partition Map partition is not detected\n");
        return CL_EFORMAT;
    }

    /* check that the partition table fits in the space specified - HEURISTICS */
    if ((ctx->options & CL_SCAN_PARTITION_INTXN) && (ctx->dconf->other & OTHER_CONF_PRTNINTXN)) {
        ret = apm_prtn_intxn(ctx, &aptable, sectorsize, old_school);
        if (ret != CL_CLEAN) {
            if ((ctx->options & CL_SCAN_ALLMATCHES) && (ret == CL_VIRUS))
                detection = CL_VIRUS;
            else
                return ret;
        }
    }

    /* print debugging info on partition tables */
    cli_dbgmsg("APM Partition Table:\n");
    cli_dbgmsg("Name: %s\n", (char*)aptable.name);
    cli_dbgmsg("Type: %s\n", (char*)aptable.type);
    cli_dbgmsg("Signature: %x\n", aptable.signature);
    cli_dbgmsg("Partition Count: %u\n", aptable.numPartitions);
    cli_dbgmsg("Blocks: [%u, +%u), ([%lu, +%lu))\n",
               aptable.pBlockStart, aptable.pBlockCount,
               (unsigned long)(aptable.pBlockStart * sectorsize),
               (unsigned long)(aptable.pBlockCount * sectorsize));

    /* check engine maxpartitions limit */
    if (aptable.numPartitions < ctx->engine->maxpartitions) {
        max_prtns = aptable.numPartitions;
    }
    else {
        max_prtns = ctx->engine->maxpartitions;
    }

    /* partition table is a partition [at index 1], so skip it */
    for (i = 2; i <= max_prtns; ++i) {
        /* read partition table entry */
        pos = i * sectorsize;
        if (fmap_readn(*ctx->fmap, &apentry, pos, sizeof(apentry)) != sizeof(apentry)) {
            cli_dbgmsg("cli_scanapm: Invalid Apple partition entry\n");
            return CL_EFORMAT;
        }

        /* convert partition entry big endian to host */
        apentry.signature = be16_to_host(apentry.signature);
        apentry.reserved = be16_to_host(apentry.reserved);
        apentry.numPartitions = be32_to_host(apentry.numPartitions);
        apentry.pBlockStart = be32_to_host(apentry.pBlockStart);
        apentry.pBlockCount = be32_to_host(apentry.pBlockCount);

        /* check the partition entry signature */
        if (aptable.signature != APM_SIGNATURE) {
            cli_dbgmsg("cli_scanapm: Apple partition entry signature mismatch\n");
            return CL_EFORMAT;
        }

        /* check if a out-of-order partition map */
        if (!strncmp((char*)apentry.type, "Apple_Partition_Map", 32) ||
                !strncmp((char*)apentry.type, "Apple_partition_map", 32) ||
                !strncmp((char*)apentry.type, "Apple_patition_map", 32)) {

            cli_dbgmsg("cli_scanapm: Out of order Apple Partition Map partition\n");
            continue;
        }

        partoff = apentry.pBlockStart * sectorsize;
        partsize = apentry.pBlockCount * sectorsize;
        /* re-calculate if old_school and aligned [512 * 4 => 2048] */
        if (old_school && ((i % 4) == 0)) {
            if (!strncmp((char*)apentry.type, "Apple_Driver",       32) ||
                    !strncmp((char*)apentry.type, "Apple_Driver43",     32) ||
                    !strncmp((char*)apentry.type, "Apple_Driver43_CD",  32) ||
                    !strncmp((char*)apentry.type, "Apple_Driver_ATA",   32) ||
                    !strncmp((char*)apentry.type, "Apple_Driver_ATAPI", 32) ||
                    !strncmp((char*)apentry.type, "Apple_Patches",      32)) {

                partsize = apentry.pBlockCount * 2048;;
            }
        }

        /* check if invalid partition */
        if ((partoff == 0) || (partoff+partsize > maplen)) {
            cli_dbgmsg("cli_scanapm: Detected invalid Apple partition entry\n");
            continue;
        }

        /* print debugging info on partition */
        cli_dbgmsg("APM Partition Entry %u:\n", i);
        cli_dbgmsg("Name: %s\n", (char*)apentry.name);
        cli_dbgmsg("Type: %s\n", (char*)apentry.type);
        cli_dbgmsg("Signature: %x\n", apentry.signature);
        cli_dbgmsg("Partition Count: %u\n", apentry.numPartitions);
        cli_dbgmsg("Blocks: [%u, +%u), ([%lu, +%lu))\n",
                   apentry.pBlockStart, apentry.pBlockCount, (long unsigned)partoff, (long unsigned)partsize);

        /* send the partition to cli_map_scan */
        ret = cli_map_scan(*ctx->fmap, partoff, partsize, ctx, CL_TYPE_PART_ANY);
        if (ret != CL_CLEAN) {
            if ((ctx->options & CL_SCAN_ALLMATCHES) && (ret == CL_VIRUS))
                detection = CL_VIRUS;
            else
                return ret;
        }
    }

    if (i >= ctx->engine->maxpartitions) {
        cli_dbgmsg("cli_scanapm: max partitions reached\n");
    }

    return detection;
}
Пример #8
0
int cli_scanxar(cli_ctx *ctx)
{
    int rc = CL_SUCCESS;
    unsigned int cksum_fails = 0;
    unsigned int extract_errors = 0;
#if HAVE_LIBXML2
    int fd = -1;
    struct xar_header hdr;
    fmap_t *map = *ctx->fmap;
    long length, offset, size, at;
    int encoding;
    z_stream strm;
    char *toc, *tmpname;
    xmlTextReaderPtr reader = NULL;
    int a_hash, e_hash;
    unsigned char *a_cksum = NULL, *e_cksum = NULL;

    memset(&strm, 0x00, sizeof(z_stream));

    /* retrieve xar header */
    if (fmap_readn(*ctx->fmap, &hdr, 0, sizeof(hdr)) != sizeof(hdr)) {
        cli_dbgmsg("cli_scanxar: Invalid header, too short.\n");
        return CL_EFORMAT;
    }
    hdr.magic = be32_to_host(hdr.magic);

    if (hdr.magic == XAR_HEADER_MAGIC) {
        cli_dbgmsg("cli_scanxar: Matched magic\n");
    }
    else {
        cli_dbgmsg("cli_scanxar: Invalid magic\n");
        return CL_EFORMAT;
    }
    hdr.size = be16_to_host(hdr.size);
    hdr.version = be16_to_host(hdr.version);
    hdr.toc_length_compressed = be64_to_host(hdr.toc_length_compressed);
    hdr.toc_length_decompressed = be64_to_host(hdr.toc_length_decompressed);
    hdr.chksum_alg = be32_to_host(hdr.chksum_alg);

    /* cli_dbgmsg("hdr.magic %x\n", hdr.magic); */
    /* cli_dbgmsg("hdr.size %i\n", hdr.size); */
    /* cli_dbgmsg("hdr.version %i\n", hdr.version); */
    /* cli_dbgmsg("hdr.toc_length_compressed %lu\n", hdr.toc_length_compressed); */
    /* cli_dbgmsg("hdr.toc_length_decompressed %lu\n", hdr.toc_length_decompressed); */
    /* cli_dbgmsg("hdr.chksum_alg %i\n", hdr.chksum_alg); */

    /* Uncompress TOC */
    strm.next_in = (unsigned char *)fmap_need_off_once(*ctx->fmap, hdr.size, hdr.toc_length_compressed);
    if (strm.next_in == NULL) {
        cli_dbgmsg("cli_scanxar: fmap_need_off_once fails on TOC.\n");
        return CL_EREAD;
    }
    strm.avail_in = hdr.toc_length_compressed;
    toc = cli_malloc(hdr.toc_length_decompressed+1);
    if (toc == NULL) {
        cli_dbgmsg("cli_scanxar: cli_malloc fails on TOC decompress buffer.\n");
        return CL_EMEM;
    }
    toc[hdr.toc_length_decompressed] = '\0';
    strm.avail_out = hdr.toc_length_decompressed;
    strm.next_out = (unsigned char *)toc;
    rc = inflateInit(&strm);
    if (rc != Z_OK) {
        cli_dbgmsg("cli_scanxar:inflateInit error %i \n", rc);
        rc = CL_EFORMAT;
        goto exit_toc;
    }
    rc = inflate(&strm, Z_SYNC_FLUSH);
    if (rc != Z_OK && rc != Z_STREAM_END) {
        cli_dbgmsg("cli_scanxar:inflate error %i \n", rc);
        rc = CL_EFORMAT;
        goto exit_toc;
    }
    rc = inflateEnd(&strm);
    if (rc != Z_OK) {
        cli_dbgmsg("cli_scanxar:inflateEnd error %i \n", rc);
        rc = CL_EFORMAT;
        goto exit_toc;
    }

    /* cli_dbgmsg("cli_scanxar: TOC xml:\n%s\n", toc); */
    /* printf("cli_scanxar: TOC xml:\n%s\n", toc); */
    /* cli_dbgmsg("cli_scanxar: TOC end:\n"); */
    /* printf("cli_scanxar: TOC end:\n"); */

    /* scan the xml */
    cli_dbgmsg("cli_scanxar: scanning xar TOC xml in memory.\n");
    rc = cli_mem_scandesc(toc, hdr.toc_length_decompressed, ctx);
    if (rc != CL_SUCCESS) {
        if (rc != CL_VIRUS || !SCAN_ALL)
            goto exit_toc;
    }

    /* make a file to leave if --leave-temps in effect */
    if(ctx->engine->keeptmp) {
        if ((rc = cli_gentempfd(ctx->engine->tmpdir, &tmpname, &fd)) != CL_SUCCESS) {
            cli_dbgmsg("cli_scanxar: Can't create temporary file for TOC.\n");
            goto exit_toc;
        }
        if (cli_writen(fd, toc, hdr.toc_length_decompressed) < 0) {
            cli_dbgmsg("cli_scanxar: cli_writen error writing TOC.\n");
            rc = CL_EWRITE;
            xar_cleanup_temp_file(ctx, fd, tmpname);
            goto exit_toc;
        }
        rc = xar_cleanup_temp_file(ctx, fd, tmpname);
        if (rc != CL_SUCCESS)
            goto exit_toc;
    }

    reader = xmlReaderForMemory(toc, hdr.toc_length_decompressed, "noname.xml", NULL, 0);
    if (reader == NULL) {
        cli_dbgmsg("cli_scanxar: xmlReaderForMemory error for TOC\n");
        goto exit_toc;
    }

    rc = xar_scan_subdocuments(reader, ctx);
    if (rc != CL_SUCCESS) {
        cli_dbgmsg("xar_scan_subdocuments returns %i.\n", rc);
        goto exit_reader;
    }

    /* Walk the TOC XML and extract files */
    fd = -1;
    tmpname = NULL;
    while (CL_SUCCESS == (rc = xar_get_toc_data_values(reader, &length, &offset, &size, &encoding,
                               &a_cksum, &a_hash, &e_cksum, &e_hash))) {
        int do_extract_cksum = 1;
        unsigned char * blockp;
        void *a_sc, *e_sc;
        void *a_mc, *e_mc;
        void *a_hash_ctx, *e_hash_ctx;
        char result[SHA1_HASH_SIZE];
        char * expected;

        /* clean up temp file from previous loop iteration */
        if (fd > -1 && tmpname) {
            rc = xar_cleanup_temp_file(ctx, fd, tmpname);
            if (rc != CL_SUCCESS)
                goto exit_reader;
        }

        at = offset + hdr.toc_length_compressed + hdr.size;

        if ((rc = cli_gentempfd(ctx->engine->tmpdir, &tmpname, &fd)) != CL_SUCCESS) {
            cli_dbgmsg("cli_scanxar: Can't generate temporary file.\n");
            goto exit_reader;
        }

        cli_dbgmsg("cli_scanxar: decompress into temp file:\n%s, size %li,\n"
                   "from xar heap offset %li length %li\n",
                   tmpname, size, offset, length);


        a_hash_ctx = xar_hash_init(a_hash, &a_sc, &a_mc);
        e_hash_ctx = xar_hash_init(e_hash, &e_sc, &e_mc);

        switch (encoding) {
        case CL_TYPE_GZ:
            /* inflate gzip directly because file segments do not contain magic */
            memset(&strm, 0, sizeof(strm));
            if ((rc = inflateInit(&strm)) != Z_OK) {
                cli_dbgmsg("cli_scanxar: InflateInit failed: %d\n", rc);
                rc = CL_EFORMAT;
                extract_errors++;
                break;
            }

            while ((size_t)at < map->len && (unsigned long)at < offset+hdr.toc_length_compressed+hdr.size+length) {
                unsigned long avail_in;
                void * next_in;
                unsigned int bytes = MIN(map->len - at, map->pgsz);
                bytes = MIN(length, bytes);
                if(!(strm.next_in = next_in = (void*)fmap_need_off_once(map, at, bytes))) {
                    cli_dbgmsg("cli_scanxar: Can't read %u bytes @ %lu.\n", bytes, (long unsigned)at);
                    inflateEnd(&strm);
                    rc = CL_EREAD;
                    goto exit_tmpfile;
                }
                at += bytes;
                strm.avail_in = avail_in = bytes;
                do {
                    int inf, outsize = 0;
                    unsigned char buff[FILEBUFF];
                    strm.avail_out = sizeof(buff);
                    strm.next_out = buff;
                    inf = inflate(&strm, Z_SYNC_FLUSH);
                    if (inf != Z_OK && inf != Z_STREAM_END && inf != Z_BUF_ERROR) {
                        cli_dbgmsg("cli_scanxar: inflate error %i %s.\n", inf, strm.msg?strm.msg:"");
                        rc = CL_EFORMAT;
                        extract_errors++;
                        break;
                    }

                    bytes = sizeof(buff) - strm.avail_out;

                    xar_hash_update(e_hash_ctx, buff, bytes, e_hash);

                    if (cli_writen(fd, buff, bytes) < 0) {
                        cli_dbgmsg("cli_scanxar: cli_writen error file %s.\n", tmpname);
                        inflateEnd(&strm);
                        rc = CL_EWRITE;
                        goto exit_tmpfile;
                    }
                    outsize += sizeof(buff) - strm.avail_out;
                    if (cli_checklimits("cli_scanxar", ctx, outsize, 0, 0) != CL_CLEAN) {
                        break;
                    }
                    if (inf == Z_STREAM_END) {
                        break;
                    }
                } while (strm.avail_out == 0);

                if (rc != CL_SUCCESS)
                    break;

                avail_in -= strm.avail_in;
                xar_hash_update(a_hash_ctx, next_in, avail_in, a_hash);
            }

            inflateEnd(&strm);
            break;
        case CL_TYPE_7Z:
#define CLI_LZMA_OBUF_SIZE 1024*1024
#define CLI_LZMA_HDR_SIZE LZMA_PROPS_SIZE+8
#define CLI_LZMA_IBUF_SIZE CLI_LZMA_OBUF_SIZE>>2 /* estimated compression ratio 25% */
        {
            struct CLI_LZMA lz;
            unsigned long in_remaining = length;
            unsigned long out_size = 0;
            unsigned char * buff = __lzma_wrap_alloc(NULL, CLI_LZMA_OBUF_SIZE);
            int lret;

            memset(&lz, 0, sizeof(lz));
            if (buff == NULL) {
                cli_dbgmsg("cli_scanxar: memory request for lzma decompression buffer fails.\n");
                rc = CL_EMEM;
                goto exit_tmpfile;

            }

            blockp = (void*)fmap_need_off_once(map, at, CLI_LZMA_HDR_SIZE);
            if (blockp == NULL) {
                char errbuff[128];
                cli_strerror(errno, errbuff, sizeof(errbuff));
                cli_dbgmsg("cli_scanxar: Can't read %li bytes @ %li, errno:%s.\n",
                           length, at, errbuff);
                rc = CL_EREAD;
                __lzma_wrap_free(NULL, buff);
                goto exit_tmpfile;
            }

            lz.next_in = blockp;
            lz.avail_in = CLI_LZMA_HDR_SIZE;

            xar_hash_update(a_hash_ctx, blockp, CLI_LZMA_HDR_SIZE, a_hash);

            lret = cli_LzmaInit(&lz, 0);
            if (lret != LZMA_RESULT_OK) {
                cli_dbgmsg("cli_scanxar: cli_LzmaInit() fails: %i.\n", lret);
                rc = CL_EFORMAT;
                __lzma_wrap_free(NULL, buff);
                extract_errors++;
                break;
            }

            at += CLI_LZMA_HDR_SIZE;
            in_remaining -= CLI_LZMA_HDR_SIZE;
            while ((size_t)at < map->len && (unsigned long)at < offset+hdr.toc_length_compressed+hdr.size+length) {
                SizeT avail_in;
                SizeT avail_out;
                void * next_in;
                unsigned long in_consumed;

                lz.next_out = buff;
                lz.avail_out = CLI_LZMA_OBUF_SIZE;
                lz.avail_in = avail_in = MIN(CLI_LZMA_IBUF_SIZE, in_remaining);
                lz.next_in = next_in = (void*)fmap_need_off_once(map, at, lz.avail_in);
                if (lz.next_in == NULL) {
                    char errbuff[128];
                    cli_strerror(errno, errbuff, sizeof(errbuff));
                    cli_dbgmsg("cli_scanxar: Can't read %li bytes @ %li, errno: %s.\n",
                               length, at, errbuff);
                    rc = CL_EREAD;
                    __lzma_wrap_free(NULL, buff);
                    cli_LzmaShutdown(&lz);
                    goto exit_tmpfile;
                }

                lret = cli_LzmaDecode(&lz);
                if (lret != LZMA_RESULT_OK && lret != LZMA_STREAM_END) {
                    cli_dbgmsg("cli_scanxar: cli_LzmaDecode() fails: %i.\n", lret);
                    rc = CL_EFORMAT;
                    extract_errors++;
                    break;
                }

                in_consumed = avail_in - lz.avail_in;
                in_remaining -= in_consumed;
                at += in_consumed;
                avail_out = CLI_LZMA_OBUF_SIZE - lz.avail_out;

                if (avail_out == 0)
                    cli_dbgmsg("cli_scanxar: cli_LzmaDecode() produces no output for "
                               "avail_in %lu, avail_out %lu.\n", avail_in, avail_out);

                xar_hash_update(a_hash_ctx, next_in, in_consumed, a_hash);
                xar_hash_update(e_hash_ctx, buff, avail_out, e_hash);

                /* Write a decompressed block. */
                /* cli_dbgmsg("Writing %li bytes to LZMA decompress temp file, " */
                /*            "consumed %li of %li available compressed bytes.\n", */
                /*            avail_out, in_consumed, avail_in); */

                if (cli_writen(fd, buff, avail_out) < 0) {
                    cli_dbgmsg("cli_scanxar: cli_writen error writing lzma temp file for %li bytes.\n",
                               avail_out);
                    __lzma_wrap_free(NULL, buff);
                    cli_LzmaShutdown(&lz);
                    rc = CL_EWRITE;
                    goto exit_tmpfile;
                }

                /* Check file size limitation. */
                out_size += avail_out;
                if (cli_checklimits("cli_scanxar", ctx, out_size, 0, 0) != CL_CLEAN) {
                    break;
                }

                if (lret == LZMA_STREAM_END)
                    break;
            }

            cli_LzmaShutdown(&lz);
            __lzma_wrap_free(NULL, buff);
        }
        break;
        case CL_TYPE_ANY:
        default:
        case CL_TYPE_BZ:
        case CL_TYPE_XZ:
            /* for uncompressed, bzip2, xz, and unknown, just pull the file, cli_magic_scandesc does the rest */
            do_extract_cksum = 0;
            {
                unsigned long write_len;

                if (ctx->engine->maxfilesize)
                    write_len = MIN((size_t)(ctx->engine->maxfilesize), (size_t)length);
                else
                    write_len = length;

                if (!(blockp = (void*)fmap_need_off_once(map, at, length))) {
                    char errbuff[128];
                    cli_strerror(errno, errbuff, sizeof(errbuff));
                    cli_dbgmsg("cli_scanxar: Can't read %li bytes @ %li, errno:%s.\n",
                               length, at, errbuff);
                    rc = CL_EREAD;
                    goto exit_tmpfile;
                }

                xar_hash_update(a_hash_ctx, blockp, length, a_hash);

                if (cli_writen(fd, blockp, write_len) < 0) {
                    cli_dbgmsg("cli_scanxar: cli_writen error %li bytes @ %li.\n", length, at);
                    rc = CL_EWRITE;
                    goto exit_tmpfile;
                }
                /*break;*/
            }
        }

        if (rc == CL_SUCCESS) {
            xar_hash_final(a_hash_ctx, result, a_hash);
            if (a_cksum != NULL) {
                expected = cli_hex2str((char *)a_cksum);
                if (xar_hash_check(a_hash, result, expected) != 0) {
                    cli_dbgmsg("cli_scanxar: archived-checksum missing or mismatch.\n");
                    cksum_fails++;
                } else {
                    cli_dbgmsg("cli_scanxar: archived-checksum matched.\n");
                }
                free(expected);
            }
            if (e_cksum != NULL) {
                if (do_extract_cksum) {
                    xar_hash_final(e_hash_ctx, result, e_hash);
                    expected = cli_hex2str((char *)e_cksum);
                    if (xar_hash_check(e_hash, result, expected) != 0) {
                        cli_dbgmsg("cli_scanxar: extracted-checksum missing or mismatch.\n");
                        cksum_fails++;
                    } else {
                        cli_dbgmsg("cli_scanxar: extracted-checksum matched.\n");
                    }
                    free(expected);
                }
            }

            rc = cli_magic_scandesc(fd, ctx);
            if (rc != CL_SUCCESS) {
                if (rc == CL_VIRUS) {
                    cli_dbgmsg("cli_scanxar: Infected with %s\n", cli_get_last_virus(ctx));
                    if (!SCAN_ALL)
                        goto exit_tmpfile;
                } else if (rc != CL_BREAK) {
                    cli_dbgmsg("cli_scanxar: cli_magic_scandesc error %i\n", rc);
                    goto exit_tmpfile;
                }
            }
        }

        if (a_cksum != NULL) {
            xmlFree(a_cksum);
            a_cksum = NULL;
        }
        if (e_cksum != NULL) {
            xmlFree(e_cksum);
            e_cksum = NULL;
        }
    }

exit_tmpfile:
    xar_cleanup_temp_file(ctx, fd, tmpname);

exit_reader:
    if (a_cksum != NULL)
        xmlFree(a_cksum);
    if (e_cksum != NULL)
        xmlFree(e_cksum);
    xmlTextReaderClose(reader);
    xmlFreeTextReader(reader);

exit_toc:
    free(toc);
    if (rc == CL_BREAK)
        rc = CL_SUCCESS;
#else
    cli_dbgmsg("cli_scanxar: can't scan xar files, need libxml2.\n");
#endif
    if (cksum_fails + extract_errors != 0) {
        cli_warnmsg("cli_scanxar: %u checksum errors and %u extraction errors, use --debug for more info.\n",
                    cksum_fails, extract_errors);
    }

    return rc;
}