/*
 * CAB file support library
 *
 * Copyright (C) 2021-2023 Michał Trojnara <Michal.Trojnara@stunnel.org>
 * Author: Małgorzata Olszówka <Malgorzata.Olszowka@stunnel.org>
 *
 * Reference specifications:
 * https://www.file-recovery.com/cab-signature-format.htm
 * https://learn.microsoft.com/en-us/previous-versions/ms974336(v=msdn.10)
 */

#include "osslsigncode.h"
#include "helpers.h"

/*
 * FLAG_PREV_CABINET is set if the cabinet file is not the first in a set
 * of cabinet files. When this bit is set, the szCabinetPrev and szDiskPrev
 * fields are present in this CFHEADER.
 */
#define FLAG_PREV_CABINET 0x0001
/*
 * FLAG_NEXT_CABINET is set if the cabinet file is not the last in a set of
 * cabinet files. When this bit is set, the szCabinetNext and szDiskNext
* fields are present in this CFHEADER.
*/
#define FLAG_NEXT_CABINET 0x0002
/*
 * FLAG_RESERVE_PRESENT is set if the cabinet file contains any reserved
 * fields. When this bit is set, the cbCFHeader, cbCFFolder, and cbCFData
 * fields are present in this CFHEADER.
 */
#define FLAG_RESERVE_PRESENT 0x0004


struct cab_ctx_st {
    uint32_t header_size;
    uint32_t sigpos;
    uint32_t siglen;
    uint32_t fileend;
    uint16_t flags;
};

/* FILE_FORMAT method prototypes */
static FILE_FORMAT_CTX *cab_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *outdata);
static ASN1_OBJECT *cab_obsolete_link_get(u_char **p, int *plen, FILE_FORMAT_CTX *ctx);
static PKCS7 *cab_pkcs7_contents_get(FILE_FORMAT_CTX *ctx, BIO *hash, const EVP_MD *md);
static int cab_hash_length_get(FILE_FORMAT_CTX *ctx);
static u_char *cab_digest_calc(FILE_FORMAT_CTX *ctx, const EVP_MD *md);
static int cab_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7);
static PKCS7 *cab_pkcs7_extract(FILE_FORMAT_CTX *ctx);
static PKCS7 *cab_pkcs7_extract_to_nest(FILE_FORMAT_CTX *ctx);
static int cab_remove_pkcs7(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata);
static int cab_process_data(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata);
static PKCS7 *cab_pkcs7_signature_new(FILE_FORMAT_CTX *ctx, BIO *hash);
static int cab_append_pkcs7(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7);
static void cab_update_data_size(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7);
static void cab_bio_free(BIO *hash, BIO *outdata);
static void cab_ctx_cleanup(FILE_FORMAT_CTX *ctx);
static int cab_is_detaching_supported(void);

FILE_FORMAT file_format_cab = {
    .ctx_new = cab_ctx_new,
    .data_blob_get = cab_obsolete_link_get,
    .pkcs7_contents_get = cab_pkcs7_contents_get,
    .hash_length_get = cab_hash_length_get,
    .digest_calc = cab_digest_calc,
    .verify_digests = cab_verify_digests,
    .pkcs7_extract = cab_pkcs7_extract,
    .pkcs7_extract_to_nest = cab_pkcs7_extract_to_nest,
    .remove_pkcs7 = cab_remove_pkcs7,
    .process_data = cab_process_data,
    .pkcs7_signature_new = cab_pkcs7_signature_new,
    .append_pkcs7 = cab_append_pkcs7,
    .update_data_size = cab_update_data_size,
    .bio_free = cab_bio_free,
    .ctx_cleanup = cab_ctx_cleanup,
    .is_detaching_supported = cab_is_detaching_supported
};

/* Prototypes */
static CAB_CTX *cab_ctx_get(char *indata, uint32_t filesize);
static int cab_add_jp_attribute(PKCS7 *p7, int jp);
static size_t cab_write_optional_names(BIO *outdata, char *indata, size_t len, uint16_t flags);
static int cab_modify_header(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata);
static int cab_add_header(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata);
static int cab_check_file(FILE_FORMAT_CTX *ctx);

/*
 * FILE_FORMAT method definitions
 */

/*
 * Allocate and return a CAB file format context.
 * [in, out] options: structure holds the input data
 * [out] hash: message digest BIO
 * [in] outdata: outdata file BIO
 * [returns] pointer to CAB file format context
 */
static FILE_FORMAT_CTX *cab_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *outdata)
{
    FILE_FORMAT_CTX *ctx;
    CAB_CTX *cab_ctx;
    uint32_t filesize;

    filesize = get_file_size(options->infile);
    if (filesize == 0)
        return NULL; /* FAILED */

    options->indata = map_file(options->infile, filesize);
    if (!options->indata) {
        return NULL; /* FAILED */
    }
    if (memcmp(options->indata, "MSCF", 4)) {
        unmap_file(options->indata, filesize);
        return NULL; /* FAILED */
    }
    cab_ctx = cab_ctx_get(options->indata, filesize);
    if (!cab_ctx) {
        unmap_file(options->indata, filesize);
        return NULL; /* FAILED */
    }
    ctx = OPENSSL_malloc(sizeof(FILE_FORMAT_CTX));
    ctx->format = &file_format_cab;
    ctx->options = options;
    ctx->cab_ctx = cab_ctx;

    /* Push hash on outdata, if hash is NULL the function does nothing */
    BIO_push(hash, outdata);

    if (options->pagehash == 1)
        printf("Warning: -ph option is only valid for PE files\n");
    if (options->add_msi_dse == 1)
        printf("Warning: -add-msi-dse option is only valid for MSI files\n");
    return ctx;
}

/*
 * Allocate and return SpcLink object.
 * [out] p: SpcLink data
 * [out] plen: SpcLink data length
 * [in] ctx: structure holds input and output data (unused)
 * [returns] pointer to ASN1_OBJECT structure corresponding to SPC_CAB_DATA_OBJID
 */
static ASN1_OBJECT *cab_obsolete_link_get(u_char **p, int *plen, FILE_FORMAT_CTX *ctx)
{
    ASN1_OBJECT *dtype;
    SpcLink *link = spc_link_obsolete_get();

    /* squash the unused parameter warning */
    (void)ctx;

    *plen = i2d_SpcLink(link, NULL);
    *p = OPENSSL_malloc((size_t)*plen);
    i2d_SpcLink(link, p);
    *p -= *plen;
    dtype = OBJ_txt2obj(SPC_CAB_DATA_OBJID, 1);
    SpcLink_free(link);
    return dtype; /* OK */
}

/*
 * Allocate and return a data content to be signed.
 * [in] ctx: structure holds input and output data
 * [in] hash: message digest BIO
 * [in] md: message digest algorithm
 * [returns] data content
 */
static PKCS7 *cab_pkcs7_contents_get(FILE_FORMAT_CTX *ctx, BIO *hash, const EVP_MD *md)
{
    ASN1_OCTET_STRING *content;

    /* squash the unused parameter warning, use initialized message digest BIO */
    (void)md;

    /* Strip current signature and modify header */
    if (ctx->cab_ctx->header_size == 20) {
        if (!cab_modify_header(ctx, hash, NULL))
            return NULL; /* FAILED */
    } else {
        if (!cab_add_header(ctx, hash, NULL))
            return NULL; /* FAILED */
    }
    content = spc_indirect_data_content_get(hash, ctx);
    return pkcs7_set_content(content);
}

/*
 * [in] ctx: structure holds input and output data
 * [returns] the size of the message digest when passed an EVP_MD structure (the size of the hash)
 */
static int cab_hash_length_get(FILE_FORMAT_CTX *ctx)
{
    return EVP_MD_size(ctx->options->md);
}

/*
 * Compute a message digest value of the signed or unsigned CAB file.
 * [in] ctx: structure holds input and output data
 * [in] md: message digest algorithm
 * [returns] pointer to calculated message digest
 */
static u_char *cab_digest_calc(FILE_FORMAT_CTX *ctx, const EVP_MD *md)
{
    uint32_t idx, fileend, coffFiles;
    u_char *mdbuf = NULL;
    BIO *bhash = BIO_new(BIO_f_md());

#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
    if (!BIO_set_md(bhash, md)) {
        fprintf(stderr, "Unable to set the message digest of BIO\n");
        BIO_free_all(bhash);
        return 0;  /* FAILED */
    }
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
    BIO_push(bhash, BIO_new(BIO_s_null()));

    /* u1 signature[4] 4643534D MSCF: 0-3 */
    BIO_write(bhash, ctx->options->indata, 4);
    /* u4 reserved1 00000000: 4-7 skipped */
    if (ctx->cab_ctx->sigpos) {
        uint16_t nfolders, flags;

        /*
         * u4 cbCabinet - size of this cabinet file in bytes: 8-11
         * u4 reserved2 00000000: 12-15
         */
        BIO_write(bhash, ctx->options->indata + 8, 8);
         /* u4 coffFiles - offset of the first CFFILE entry: 16-19 */
        coffFiles = GET_UINT32_LE(ctx->options->indata + 16);
        BIO_write(bhash, ctx->options->indata + 16, 4);
        /*
         * u4 reserved3 00000000: 20-23
         * u1 versionMinor 03: 24
         * u1 versionMajor 01: 25
         */
        BIO_write(bhash, ctx->options->indata + 20, 6);
        /* u2 cFolders - number of CFFOLDER entries in this cabinet: 26-27 */
        nfolders = GET_UINT16_LE(ctx->options->indata + 26);
        BIO_write(bhash, ctx->options->indata + 26, 2);
        /* u2 cFiles - number of CFFILE entries in this cabinet: 28-29 */
        BIO_write(bhash, ctx->options->indata + 28, 2);
        /* u2 flags: 30-31 */
        flags = GET_UINT16_LE(ctx->options->indata + 30);
        BIO_write(bhash, ctx->options->indata + 30, 2);
        /* u2 setID must be the same for all cabinets in a set: 32-33 */
        BIO_write(bhash, ctx->options->indata + 32, 2);
        /*
        * u2 iCabinet - number of this cabinet file in a set: 34-35 skipped
        * u2 cbCFHeader: 36-37 skipped
        * u1 cbCFFolder: 38 skipped
        * u1 cbCFData: 39 skipped
        * u22 abReserve: 40-55 skipped
        * - Additional data offset: 44-47 skipped
        * - Additional data size: 48-51 skipped
        */
        /* u22 abReserve: 56-59 */
        BIO_write(bhash, ctx->options->indata + 56, 4);
        idx = 60;
        fileend = ctx->cab_ctx->sigpos;
        /* TODO */
        if (flags & FLAG_PREV_CABINET) {
            uint8_t byte;
            /* szCabinetPrev */
            do {
                byte = GET_UINT8_LE(ctx->options->indata + idx);
                BIO_write(bhash, ctx->options->indata + idx, 1);
                idx++;
            } while (byte && idx < fileend);
            /* szDiskPrev */
            do {
                byte = GET_UINT8_LE(ctx->options->indata + idx);
                BIO_write(bhash, ctx->options->indata + idx, 1);
                idx++;
            } while (byte && idx < fileend);
        }
        if (flags & FLAG_NEXT_CABINET) {
            uint8_t byte;
            /* szCabinetNext */
            do {
                byte = GET_UINT8_LE(ctx->options->indata + idx);
                BIO_write(bhash, ctx->options->indata + idx, 1);
                idx++;
            } while (byte && idx < fileend);
            /* szDiskNext */
            do {
                byte = GET_UINT8_LE(ctx->options->indata + idx);
                BIO_write(bhash, ctx->options->indata + idx, 1);
                idx++;
            } while (byte && idx < fileend);
        }
        /*
         * (u8 * cFolders) CFFOLDER - structure contains information about
         * one of the folders or partial folders stored in this cabinet file
         */
        while (nfolders && idx < fileend) {
            BIO_write(bhash, ctx->options->indata + idx, 8);
            idx += 8;
            nfolders--;
        }
        if (idx != coffFiles) {
            fprintf(stderr, "Corrupt coffFiles value: 0x%08X\n", coffFiles);
            BIO_free_all(bhash);
            return 0;  /* FAILED */
        }
    } else {
        /* read what's left of the unsigned CAB file */
        idx = 8;
        fileend = ctx->cab_ctx->fileend;
    }
    /* (variable) ab - the compressed data bytes */
    if (!bio_hash_data(bhash, ctx->options->indata, idx, fileend)) {
        fprintf(stderr, "Unable to calculate digest\n");
        BIO_free_all(bhash);
        return 0;  /* FAILED */
    }
    mdbuf = OPENSSL_malloc((size_t)EVP_MD_size(md));
    BIO_gets(bhash, (char*)mdbuf, EVP_MD_size(md));
    BIO_free_all(bhash);
    return mdbuf; /* OK */
}

/*
 * Calculate message digest and compare to value retrieved from PKCS#7 signedData.
 * [in] ctx: structure holds input and output data
 * [in] p7: PKCS#7 signature
 * [returns] 0 on error or 1 on success
 */
static int cab_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7)
{
    int mdtype = -1;
    const EVP_MD *md;
    u_char mdbuf[EVP_MAX_MD_SIZE];
    u_char *cmdbuf;

    if (is_content_type(p7, SPC_INDIRECT_DATA_OBJID)) {
        ASN1_STRING *content_val = p7->d.sign->contents->d.other->value.sequence;
        const u_char *p = content_val->data;
        SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, content_val->length);
        if (idc) {
            if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) {
                fprintf(stderr, "Failed to extract message digest from signature\n\n");
                SpcIndirectDataContent_free(idc);
                return 0; /* FAILED */
            }
            SpcIndirectDataContent_free(idc);
        }
    }
    if (mdtype == -1) {
        fprintf(stderr, "Failed to extract current message digest\n\n");
        return 0; /* FAILED */
    }
    md = EVP_get_digestbynid(mdtype);
    cmdbuf = cab_digest_calc(ctx, md);
    if (!cmdbuf) {
        fprintf(stderr, "Failed to calculate message digest\n\n");
        return 0; /* FAILED */
    }
    if (!compare_digests(mdbuf, cmdbuf, mdtype)) {
        fprintf(stderr, "Signature verification: failed\n\n");
        OPENSSL_free(cmdbuf);
        return 0; /* FAILED */
    }
    OPENSSL_free(cmdbuf);
    return 1; /* OK */
}

/*
 * Extract existing signature in DER format.
 * [in] ctx: structure holds input and output data
 * pointer to PKCS#7 structure
 */
static PKCS7 *cab_pkcs7_extract(FILE_FORMAT_CTX *ctx)
{
    const u_char *blob;

    if (!cab_check_file(ctx)) {
        return NULL; /* FAILED */
    }
    blob = (u_char *)ctx->options->indata + ctx->cab_ctx->sigpos;
    return d2i_PKCS7(NULL, &blob, ctx->cab_ctx->siglen);
}

/*
 * Extract existing signature in DER format.
 * [in] ctx: structure holds input and output data
 * pointer to PKCS#7 structure
 */
static PKCS7 *cab_pkcs7_extract_to_nest(FILE_FORMAT_CTX *ctx)
{
    return cab_pkcs7_extract(ctx);
}

/*
 * Remove existing signature.
 * [in, out] ctx: structure holds input and output data
 * [out] hash: message digest BIO (unused)
 * [out] outdata: outdata file BIO
 * [returns] 1 on error or 0 on success
 */
static int cab_remove_pkcs7(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata)
{
    size_t idx, written, len;
    uint32_t tmp;
    uint16_t nfolders, flags;
    char *buf;

    /* squash the unused parameter warning */
    (void)hash;

    if (!cab_check_file(ctx)) {
        return 1; /* FAILED, no signature */
    }
    buf = OPENSSL_malloc(SIZE_64K);
    /*
     * u1 signature[4] 4643534D MSCF: 0-3
     * u4 reserved1 00000000: 4-7
     */
    BIO_write(outdata, ctx->options->indata, 8);
    /* u4 cbCabinet - size of this cabinet file in bytes: 8-11 */
    tmp = GET_UINT32_LE(ctx->options->indata + 8) - 24;
    PUT_UINT32_LE(tmp, buf);
    BIO_write(outdata, buf, 4);
    /* u4 reserved2 00000000: 12-15 */
    BIO_write(outdata, ctx->options->indata + 12, 4);
    /* u4 coffFiles - offset of the first CFFILE entry: 16-19 */
    tmp = GET_UINT32_LE(ctx->options->indata + 16) - 24;
    PUT_UINT32_LE(tmp, buf);
    BIO_write(outdata, buf, 4);
    /*
     * u4 reserved3 00000000: 20-23
     * u1 versionMinor 03: 24
     * u1 versionMajor 01: 25
     * u2 cFolders - number of CFFOLDER entries in this cabinet: 26-27
     * u2 cFiles - number of CFFILE entries in this cabinet: 28-29
     */
    BIO_write(outdata, ctx->options->indata + 20, 10);
    /* u2 flags: 30-31 */
    flags = GET_UINT16_LE(ctx->options->indata + 30);
    /* coverity[result_independent_of_operands] only least significant byte is affected */
    PUT_UINT16_LE(flags & (FLAG_PREV_CABINET | FLAG_NEXT_CABINET), buf);
    BIO_write(outdata, buf, 2);
    /*
     * u2 setID must be the same for all cabinets in a set: 32-33
     * u2 iCabinet - number of this cabinet file in a set: 34-35
     */
    BIO_write(outdata, ctx->options->indata + 32, 4);
    idx = cab_write_optional_names(outdata, ctx->options->indata, 60, flags);
    if (idx >= ctx->cab_ctx->fileend) {
        fprintf(stderr, "Corrupt CAB file - too short\n");
        OPENSSL_free(buf);
        return 0; /* FAILED */
    }
    /*
     * (u8 * cFolders) CFFOLDER - structure contains information about
     * one of the folders or partial folders stored in this cabinet file
     */
    nfolders = GET_UINT16_LE(ctx->options->indata + 26);
    if (nfolders * 8 >= ctx->cab_ctx->fileend - idx) {
        fprintf(stderr, "Corrupt cFolders value: 0x%08X\n", nfolders);
        OPENSSL_free(buf);
        return 0; /* FAILED */
    }
    while (nfolders) {
        tmp = GET_UINT32_LE(ctx->options->indata + idx);
        tmp -= 24;
        PUT_UINT32_LE(tmp, buf);
        BIO_write(outdata, buf, 4);
        BIO_write(outdata, ctx->options->indata + idx + 4, 4);
        idx += 8;
        nfolders--;
    }
    OPENSSL_free(buf);
    /* Write what's left - the compressed data bytes */
    len = ctx->cab_ctx->fileend - ctx->cab_ctx->siglen - idx;
    while (len > 0) {
        if (!BIO_write_ex(outdata, ctx->options->indata + idx, len, &written))
            return 1; /* FAILED */
        len -= written;
        idx += written;
    }
    return 0; /* OK */
}

/*
 * Modify specific type data and calculate a hash (message digest) of data.
 * [in, out] ctx: structure holds input and output data
 * [out] hash: message digest BIO
 * [out] outdata: outdata file BIO
 * [returns] 1 on error or 0 on success
 */
static int cab_process_data(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata)
{
    /* Strip current signature and modify header */
    if (ctx->cab_ctx->header_size == 20) {
        if (!cab_modify_header(ctx, hash, outdata))
            return 0; /* FAILED */
    } else {
        if (!cab_add_header(ctx, hash, outdata))
            return 0; /* FAILED */
    }
    return 1; /* OK */
}

/*
 * Create a new PKCS#7 signature.
 * [in, out] ctx: structure holds input and output data
 * [out] hash: message digest BIO
 * [returns] pointer to PKCS#7 structure
 */
static PKCS7 *cab_pkcs7_signature_new(FILE_FORMAT_CTX *ctx, BIO *hash)
{
    ASN1_OCTET_STRING *content;
    PKCS7 *p7 = pkcs7_create(ctx);

    if (!p7) {
        fprintf(stderr, "Creating a new signature failed\n");
        return NULL; /* FAILED */
    }
    if (ctx->options->jp >= 0 && !cab_add_jp_attribute(p7, ctx->options->jp)) {
        fprintf(stderr, "Adding jp attribute failed\n");
        PKCS7_free(p7);
        return NULL; /* FAILED */
    }
    if (!add_indirect_data_object(p7)) {
        fprintf(stderr, "Adding SPC_INDIRECT_DATA_OBJID failed\n");
        PKCS7_free(p7);
        return NULL; /* FAILED */
    }
    content = spc_indirect_data_content_get(hash, ctx);
    if (!content) {
        fprintf(stderr, "Failed to get spcIndirectDataContent\n");
        return NULL; /* FAILED */
    }
    if (!sign_spc_indirect_data_content(p7, content)) {
        fprintf(stderr, "Failed to set signed content\n");
        PKCS7_free(p7);
        ASN1_OCTET_STRING_free(content);
        return NULL; /* FAILED */
    }
    ASN1_OCTET_STRING_free(content);
    return p7;
}

/*
 * Append signature to the outfile.
 * [in, out] ctx: structure holds input and output data (unused)
 * [out] outdata: outdata file BIO
 * [in] p7: PKCS#7 signature
 * [returns] 1 on error or 0 on success
 */
static int cab_append_pkcs7(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7)
{
    u_char *p = NULL;
    int len;       /* signature length */
    int padlen;    /* signature padding length */

    /* squash the unused parameter warning */
    (void)ctx;

    if (((len = i2d_PKCS7(p7, NULL)) <= 0)
        || (p = OPENSSL_malloc((size_t)len)) == NULL) {
        fprintf(stderr, "i2d_PKCS memory allocation failed: %d\n", len);
        return 1; /* FAILED */
    }
    i2d_PKCS7(p7, &p);
    p -= len;
    padlen = len % 8 ? 8 - len % 8 : 0;
    BIO_write(outdata, p, len);
    /* pad (with 0's) asn1 blob to 8 byte boundary */
    if (padlen > 0) {
        memset(p, 0, (size_t)padlen);
        BIO_write(outdata, p, padlen);
    }
    OPENSSL_free(p);
    return 0; /* OK */
}

/*
 * Update additional data size.
 * Additional data size is located at offset 0x30 (from file beginning)
 * and consist of 4 bytes (little-endian order).
 * [in, out] ctx: structure holds input and output data
 * [out] outdata: outdata file BIO
 * [in] p7: PKCS#7 signature
 * [returns] none
 */
static void cab_update_data_size(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7)
{
    int len, padlen;
    u_char buf[] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    };

    /* squash the unused parameter warning */
    (void)ctx;

    if (!p7) {
        /* CMD_REMOVE
         * additional header does not exist so additional data size is unused */
        return;
    }
    (void)BIO_seek(outdata, 0x30);
    len = i2d_PKCS7(p7, NULL);
    padlen = len % 8 ? 8 - len % 8 : 0;
    PUT_UINT32_LE(len + padlen, buf);
    BIO_write(outdata, buf, 4);
}

/*
 * Free up an entire message digest BIO chain.
 * [out] hash: message digest BIO
 * [out] outdata: outdata file BIO (unused)
 * [returns] none
 */
static void cab_bio_free(BIO *hash, BIO *outdata)
{
    /* squash the unused parameter warning */
    (void)outdata;
    BIO_free_all(hash);
}

/*
 * Deallocate a FILE_FORMAT_CTX structure and CAB format specific structure,
 * unmap indata file.
 * [in, out] ctx: structure holds input and output data
 * [out] hash: message digest BIO
 * [in] outdata: outdata file BIO
 * [returns] none
 */
static void cab_ctx_cleanup(FILE_FORMAT_CTX *ctx)
{
    unmap_file(ctx->options->indata, ctx->cab_ctx->fileend);
    OPENSSL_free(ctx->cab_ctx);
    OPENSSL_free(ctx);
}

static int cab_is_detaching_supported(void)
{
    return 1; /* OK */
}

/*
 * CAB helper functions
 */

/*
 * Verify mapped CAB file and create CAB format specific structure.
 * [in] indata: mapped CAB file
 * [in] filesize: size of CAB file
 * [returns] pointer to CAB format specific structure
 */
static CAB_CTX *cab_ctx_get(char *indata, uint32_t filesize)
{
    CAB_CTX *cab_ctx;
    uint32_t reserved, header_size = 0, sigpos = 0, siglen = 0;
    uint16_t flags;

    if (filesize < 44) {
        fprintf(stderr, "CAB file is too short\n");
        return NULL; /* FAILED */
    }
    reserved = GET_UINT32_LE(indata + 4);
    if (reserved) {
        fprintf(stderr, "Reserved1: 0x%08X\n", reserved);
        return NULL; /* FAILED */
    }
    /* flags specify bit-mapped values that indicate the presence of optional data */
    flags = GET_UINT16_LE(indata + 30);
    if (flags & FLAG_PREV_CABINET) {
        /* FLAG_NEXT_CABINET works */
        fprintf(stderr, "Multivolume cabinet file is unsupported: flags 0x%04X\n", flags);
        return NULL; /* FAILED */
    }
    if (flags & FLAG_RESERVE_PRESENT) {
        /*
        * Additional headers is located at offset 36 (cbCFHeader, cbCFFolder, cbCFData);
        * size of header (4 bytes, little-endian order) must be 20 (checkpoint).
        */
        header_size = GET_UINT32_LE(indata + 36);
        if (header_size != 20) {
            fprintf(stderr, "Additional header size: 0x%08X\n", header_size);
            return NULL; /* FAILED */
        }
        reserved = GET_UINT32_LE(indata + 40);
        if (reserved != 0x00100000) {
            fprintf(stderr, "abReserved: 0x%08X\n", reserved);
            return NULL; /* FAILED */
        }
        /*
        * File size is defined at offset 8, however if additional header exists, this size is not valid.
        * sigpos - additional data offset is located at offset 44 (from file beginning)
        * and consist of 4 bytes (little-endian order)
        * siglen - additional data size is located at offset 48 (from file beginning)
        * and consist of 4 bytes (little-endian order)
        * If there are additional headers, size of the CAB archive file is calculated
        * as additional data offset plus additional data size.
        */
        sigpos = GET_UINT32_LE(indata + 44);
        siglen = GET_UINT32_LE(indata + 48);
        if ((sigpos < filesize && sigpos + siglen != filesize) || (sigpos >= filesize)) {
            fprintf(stderr, "Additional data offset:\t%u bytes\nAdditional data size:\t%u bytes\n",
                sigpos, siglen);
            fprintf(stderr, "File size:\t\t%u bytes\n", filesize);
            return NULL; /* FAILED */
        }
        if ((sigpos > 0 && siglen == 0) || (sigpos == 0 && siglen > 0)) {
            fprintf(stderr, "Corrupt signature\n");
            return NULL; /* FAILED */
        }
    }
    cab_ctx = OPENSSL_zalloc(sizeof(CAB_CTX));
    cab_ctx->header_size = header_size;
    cab_ctx->sigpos = sigpos;
    cab_ctx->siglen = siglen;
    cab_ctx->fileend = filesize;
    cab_ctx->flags = flags;
    return cab_ctx; /* OK */
}

/*
 * Add level of permissions in Microsoft Internet Explorer 4.x for CAB files,
 * only low level is supported.
 * [in, out] p7: PKCS#7 signature
 * [in] jp: low (0) level
 * [returns] 0 on error or 1 on success
 */
static int cab_add_jp_attribute(PKCS7 *p7, int jp)
{
    STACK_OF(PKCS7_SIGNER_INFO) *signer_info;
    PKCS7_SIGNER_INFO *si;
    ASN1_STRING *astr;
    const u_char *attrs = NULL;
    const u_char java_attrs_low[] = {
        0x30, 0x06, 0x03, 0x02, 0x00, 0x01, 0x30, 0x00
    };

    signer_info = PKCS7_get_signer_info(p7);
    if (!signer_info)
        return 0; /* FAILED */
    si = sk_PKCS7_SIGNER_INFO_value(signer_info, 0);
    if (!si)
        return 0; /* FAILED */
    switch (jp) {
        case 0:
            attrs = java_attrs_low;
            break;
        case 1:
            /* XXX */
        case 2:
            /* XXX */
        default:
            break;
        }
    if (attrs) {
        astr = ASN1_STRING_new();
        ASN1_STRING_set(astr, attrs, sizeof java_attrs_low);
        return PKCS7_add_signed_attribute(si, OBJ_txt2nid(MS_JAVA_SOMETHING),
                V_ASN1_SEQUENCE, astr);
    }
    return 1; /* OK */
}

/*
 * Write name of previous and next cabinet file.
 * Multivolume cabinet file is unsupported TODO.
 * [out] outdata: outdata file BIO
 * [in] indata: mapped CAB file
 * [in] len: offset
 * [in] flags: FLAG_PREV_CABINET, FLAG_NEXT_CABINET
 * [returns] offset
 */
static size_t cab_write_optional_names(BIO *outdata, char *indata, size_t i, uint16_t flags)
{
    if (flags & FLAG_PREV_CABINET) {
        /* szCabinetPrev */
        while (GET_UINT8_LE(indata + i)) {
            BIO_write(outdata, indata + i, 1);
            i++;
        }
        BIO_write(outdata, indata + i, 1);
        i++;
        /* szDiskPrev */
        while (GET_UINT8_LE(indata + i)) {
            BIO_write(outdata, indata + i, 1);
            i++;
        }
        BIO_write(outdata, indata + i, 1);
        i++;
    }
    if (flags & FLAG_NEXT_CABINET) {
        /* szCabinetNext */
        while (GET_UINT8_LE(indata + i)) {
            BIO_write(outdata, indata + i, 1);
            i++;
        }
        BIO_write(outdata, indata + i, 1);
        i++;
        /* szDiskNext */
        while (GET_UINT8_LE(indata + i)) {
            BIO_write(outdata, indata + i, 1);
            i++;
        }
        BIO_write(outdata, indata + i, 1);
        i++;
    }
    return i;
}

/*
 * Modify CAB header.
 * [in, out] ctx: structure holds input and output data
 * [out] hash: message digest BIO
 * [out] outdata: outdata file BIO
 * [returns] 0 on error or 1 on success
 */
static int cab_modify_header(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata)
{
    size_t idx, written, len;
    uint16_t nfolders, flags;
    u_char buf[] = {0x00, 0x00};

    /* u1 signature[4] 4643534D MSCF: 0-3 */
    BIO_write(hash, ctx->options->indata, 4);
    /* u4 reserved1 00000000: 4-7 */
    BIO_write(outdata, ctx->options->indata + 4, 4);
    /*
     * u4 cbCabinet - size of this cabinet file in bytes: 8-11
     * u4 reserved2 00000000: 12-15
     * u4 coffFiles - offset of the first CFFILE entry: 16-19
     * u4 reserved3 00000000: 20-23
     * u1 versionMinor 03: 24
     * u1 versionMajor 01: 25
     * u2 cFolders - number of CFFOLDER entries in this cabinet: 26-27
     * u2 cFiles - number of CFFILE entries in this cabinet: 28-29
     */
    BIO_write(hash, ctx->options->indata + 8, 22);
    /* u2 flags: 30-31 */
    flags = GET_UINT16_LE(ctx->options->indata + 30);
    PUT_UINT16_LE(flags, buf);
    BIO_write(hash, buf, 2);
    /* u2 setID must be the same for all cabinets in a set: 32-33 */
    BIO_write(hash, ctx->options->indata + 32, 2);
    /*
     * u2 iCabinet - number of this cabinet file in a set: 34-35
     * u2 cbCFHeader: 36-37
     * u1 cbCFFolder: 38
     * u1 cbCFData: 39
     * u16 abReserve: 40-55
     * - Additional data offset: 44-47
     * - Additional data size: 48-51
     */
    BIO_write(outdata, ctx->options->indata + 34, 22);
    /* u4 abReserve: 56-59 */
    BIO_write(hash, ctx->options->indata + 56, 4);

    idx = cab_write_optional_names(outdata, ctx->options->indata, 60, flags);
    if (idx >= ctx->cab_ctx->fileend) {
        fprintf(stderr, "Corrupt CAB file - too short\n");
        return 0; /* FAILED */
    }
    /*
     * (u8 * cFolders) CFFOLDER - structure contains information about
     * one of the folders or partial folders stored in this cabinet file
     */
    nfolders = GET_UINT16_LE(ctx->options->indata + 26);
    if (nfolders * 8 >= ctx->cab_ctx->fileend - idx) {
        fprintf(stderr, "Corrupt cFolders value: 0x%08X\n", nfolders);
        return 0; /* FAILED */
    }
    while (nfolders) {
        BIO_write(hash, ctx->options->indata + idx, 8);
        idx += 8;
        nfolders--;
    }
    /* Write what's left - the compressed data bytes */
    len = ctx->cab_ctx->sigpos - idx;
    while (len > 0) {
        if (!BIO_write_ex(hash, ctx->options->indata + idx, len, &written))
            return 0; /* FAILED */
        len -= written;
        idx += written;
    }
    return 1; /* OK */
}

/*
 * Add signed CAB header.
 * [in, out] ctx: structure holds input and output data
 * [out] hash: message digest BIO
 * [out] outdata: outdata file BIO
 * [returns] 0 on error or 1 on success
 */
static int cab_add_header(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata)
{
    size_t idx, written, len;
    uint32_t tmp;
    uint16_t nfolders, flags;
    u_char cabsigned[] = {
        0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
        0xde, 0xad, 0xbe, 0xef, /* size of cab file */
        0xde, 0xad, 0xbe, 0xef, /* size of asn1 blob */
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    };
    char *buf = OPENSSL_malloc(SIZE_64K);
    memset(buf, 0, SIZE_64K);

    /* u1 signature[4] 4643534D MSCF: 0-3 */
    BIO_write(hash, ctx->options->indata, 4);
    /* u4 reserved1 00000000: 4-7 */
    BIO_write(outdata, ctx->options->indata + 4, 4);
    /* u4 cbCabinet - size of this cabinet file in bytes: 8-11 */
    tmp = GET_UINT32_LE(ctx->options->indata + 8) + 24;
    PUT_UINT32_LE(tmp, buf);
    BIO_write(hash, buf, 4);
    /* u4 reserved2 00000000: 12-15 */
    BIO_write(hash, ctx->options->indata + 12, 4);
    /* u4 coffFiles - offset of the first CFFILE entry: 16-19 */
    tmp = GET_UINT32_LE(ctx->options->indata + 16) + 24;
    PUT_UINT32_LE(tmp, buf + 4);
    BIO_write(hash, buf + 4, 4);
    /*
     * u4 reserved3 00000000: 20-23
     * u1 versionMinor 03: 24
     * u1 versionMajor 01: 25
     * u2 cFolders - number of CFFOLDER entries in this cabinet: 26-27
     * u2 cFiles - number of CFFILE entries in this cabinet: 28-29
     */
    memcpy(buf + 4, ctx->options->indata + 20, 10);
    flags = GET_UINT16_LE(ctx->options->indata + 30);
    buf[4+10] = (char)flags | FLAG_RESERVE_PRESENT;
    /* u2 setID must be the same for all cabinets in a set: 32-33 */
    memcpy(buf + 16, ctx->options->indata + 32, 2);
    BIO_write(hash, buf + 4, 14);
    /* u2 iCabinet - number of this cabinet file in a set: 34-35 */
    BIO_write(outdata, ctx->options->indata + 34, 2);
    memcpy(cabsigned + 8, buf, 4);
    BIO_write(outdata, cabsigned, 20);
    BIO_write(hash, cabsigned+20, 4);

    idx = cab_write_optional_names(outdata, ctx->options->indata, 36, flags);
    if (idx >= ctx->cab_ctx->fileend) {
        fprintf(stderr, "Corrupt CAB file - too short\n");
        OPENSSL_free(buf);
        return 0; /* FAILED */
    }
    /*
     * (u8 * cFolders) CFFOLDER - structure contains information about
     * one of the folders or partial folders stored in this cabinet file
     */
    nfolders = GET_UINT16_LE(ctx->options->indata + 26);
    if (nfolders * 8 >= ctx->cab_ctx->fileend - idx) {
        fprintf(stderr, "Corrupt cFolders value: 0x%08X\n", nfolders);
        OPENSSL_free(buf);
        return 0; /* FAILED */
    }
    while (nfolders) {
        tmp = GET_UINT32_LE(ctx->options->indata + idx);
        tmp += 24;
        PUT_UINT32_LE(tmp, buf);
        BIO_write(hash, buf, 4);
        BIO_write(hash, ctx->options->indata + idx + 4, 4);
        idx += 8;
        nfolders--;
    }
    OPENSSL_free(buf);
    /* Write what's left - the compressed data bytes */
    len = ctx->cab_ctx->fileend - idx;
    while (len > 0) {
        if (!BIO_write_ex(hash, ctx->options->indata + idx, len, &written))
            return 0; /* FAILED */
        len -= written;
        idx += written;
    }
    return 1; /* OK */
}

/*
 * Check if the signature exists.
 * [in, out] ctx: structure holds input and output data
 * [returns] 0 on error or 1 on success
 */
static int cab_check_file(FILE_FORMAT_CTX *ctx)
{
    if (!ctx) {
        fprintf(stderr, "Init error\n");
        return 0; /* FAILED */
    }
    if (ctx->cab_ctx->header_size != 20) {
        fprintf(stderr, "No signature found\n");
        return 0; /* FAILED */
    }
    if (ctx->cab_ctx->sigpos == 0 || ctx->cab_ctx->siglen == 0
        || ctx->cab_ctx->sigpos > ctx->cab_ctx->fileend) {
        fprintf(stderr, "No signature found\n");
        return 0; /* FAILED */
    }
    return 1; /* OK */
}

/*
Local Variables:
   c-basic-offset: 4
   tab-width: 4
   indent-tabs-mode: nil
End:

  vim: set ts=4 expandtab:
*/
