michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include "bzlib.h" michael@0: #include "archivereader.h" michael@0: #include "errors.h" michael@0: #ifdef XP_WIN michael@0: #include "nsAlgorithm.h" // Needed by nsVersionComparator.cpp michael@0: #include "updatehelper.h" michael@0: #endif michael@0: michael@0: #define UPDATER_NO_STRING_GLUE_STL michael@0: #include "nsVersionComparator.cpp" michael@0: #undef UPDATER_NO_STRING_GLUE_STL michael@0: michael@0: #if defined(XP_UNIX) michael@0: # include michael@0: #elif defined(XP_WIN) michael@0: # include michael@0: #endif michael@0: michael@0: static int inbuf_size = 262144; michael@0: static int outbuf_size = 262144; michael@0: static char *inbuf = nullptr; michael@0: static char *outbuf = nullptr; michael@0: michael@0: #ifdef XP_WIN michael@0: #include "resource.h" michael@0: michael@0: /** michael@0: * Obtains the data of the specified resource name and type. michael@0: * michael@0: * @param name The name ID of the resource michael@0: * @param type The type ID of the resource michael@0: * @param data Out parameter which sets the pointer to a buffer containing michael@0: * the needed data. michael@0: * @param size Out parameter which sets the size of the returned data buffer michael@0: * @return TRUE on success michael@0: */ michael@0: BOOL michael@0: LoadFileInResource(int name, int type, const uint8_t *&data, uint32_t& size) michael@0: { michael@0: HMODULE handle = GetModuleHandle(nullptr); michael@0: if (!handle) { michael@0: return FALSE; michael@0: } michael@0: michael@0: HRSRC resourceInfoBlockHandle = FindResource(handle, michael@0: MAKEINTRESOURCE(name), michael@0: MAKEINTRESOURCE(type)); michael@0: if (!resourceInfoBlockHandle) { michael@0: FreeLibrary(handle); michael@0: return FALSE; michael@0: } michael@0: michael@0: HGLOBAL resourceHandle = LoadResource(handle, resourceInfoBlockHandle); michael@0: if (!resourceHandle) { michael@0: FreeLibrary(handle); michael@0: return FALSE; michael@0: } michael@0: michael@0: size = SizeofResource(handle, resourceInfoBlockHandle); michael@0: data = static_cast(::LockResource(resourceHandle)); michael@0: FreeLibrary(handle); michael@0: return TRUE; michael@0: } michael@0: michael@0: /** michael@0: * Performs a verification on the opened MAR file with the passed in michael@0: * certificate name ID and type ID. michael@0: * michael@0: * @param archive The MAR file to verify the signature on michael@0: * @param name The name ID of the resource michael@0: * @param type THe type ID of the resource michael@0: * @return OK on success, CERT_LOAD_ERROR or CERT_VERIFY_ERROR on failure. michael@0: */ michael@0: int michael@0: VerifyLoadedCert(MarFile *archive, int name, int type) michael@0: { michael@0: uint32_t size = 0; michael@0: const uint8_t *data = nullptr; michael@0: if (!LoadFileInResource(name, type, data, size) || !data || !size) { michael@0: return CERT_LOAD_ERROR; michael@0: } michael@0: michael@0: if (mar_verify_signaturesW(archive, &data, &size, 1)) { michael@0: return CERT_VERIFY_ERROR; michael@0: } michael@0: michael@0: return OK; michael@0: } michael@0: #endif michael@0: michael@0: michael@0: /** michael@0: * Performs a verification on the opened MAR file. Both the primary and backup michael@0: * keys stored are stored in the current process and at least the primary key michael@0: * will be tried. Success will be returned as long as one of the two michael@0: * signatures verify. michael@0: * michael@0: * @return OK on success michael@0: */ michael@0: int michael@0: ArchiveReader::VerifySignature() michael@0: { michael@0: if (!mArchive) { michael@0: return ARCHIVE_NOT_OPEN; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: // If the fallback key exists we're running an XPCShell test and we should michael@0: // use the XPCShell specific cert for the signed MAR. michael@0: int rv; michael@0: if (DoesFallbackKeyExist()) { michael@0: rv = VerifyLoadedCert(mArchive, IDR_XPCSHELL_CERT, TYPE_CERT); michael@0: } else { michael@0: rv = VerifyLoadedCert(mArchive, IDR_PRIMARY_CERT, TYPE_CERT); michael@0: if (rv != OK) { michael@0: rv = VerifyLoadedCert(mArchive, IDR_BACKUP_CERT, TYPE_CERT); michael@0: } michael@0: } michael@0: return rv; michael@0: #else michael@0: return OK; michael@0: #endif michael@0: } michael@0: michael@0: /** michael@0: * Verifies that the MAR file matches the current product, channel, and version michael@0: * michael@0: * @param MARChannelID The MAR channel name to use, only updates from MARs michael@0: * with a matching MAR channel name will succeed. michael@0: * If an empty string is passed, no check will be done michael@0: * for the channel name in the product information block. michael@0: * If a comma separated list of values is passed then michael@0: * one value must match. michael@0: * @param appVersion The application version to use, only MARs with an michael@0: * application version >= to appVersion will be applied. michael@0: * @return OK on success michael@0: * COULD_NOT_READ_PRODUCT_INFO_BLOCK if the product info block michael@0: * could not be read. michael@0: * MARCHANNEL_MISMATCH_ERROR if update-settings.ini's MAR michael@0: * channel ID doesn't match the MAR michael@0: * file's MAR channel ID. michael@0: * VERSION_DOWNGRADE_ERROR if the application version for michael@0: * this updater is newer than the michael@0: * one in the MAR. michael@0: */ michael@0: int michael@0: ArchiveReader::VerifyProductInformation(const char *MARChannelID, michael@0: const char *appVersion) michael@0: { michael@0: if (!mArchive) { michael@0: return ARCHIVE_NOT_OPEN; michael@0: } michael@0: michael@0: ProductInformationBlock productInfoBlock; michael@0: int rv = mar_read_product_info_block(mArchive, michael@0: &productInfoBlock); michael@0: if (rv != OK) { michael@0: return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR; michael@0: } michael@0: michael@0: // Only check the MAR channel name if specified, it should be passed in from michael@0: // the update-settings.ini file. michael@0: if (MARChannelID && strlen(MARChannelID)) { michael@0: // Check for at least one match in the comma separated list of values. michael@0: const char *delimiter = " ,\t"; michael@0: // Make a copy of the string in case a read only memory buffer michael@0: // was specified. strtok modifies the input buffer. michael@0: char channelCopy[512] = { 0 }; michael@0: strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1); michael@0: char *channel = strtok(channelCopy, delimiter); michael@0: rv = MAR_CHANNEL_MISMATCH_ERROR; michael@0: while(channel) { michael@0: if (!strcmp(channel, productInfoBlock.MARChannelID)) { michael@0: rv = OK; michael@0: break; michael@0: } michael@0: channel = strtok(nullptr, delimiter); michael@0: } michael@0: } michael@0: michael@0: if (rv == OK) { michael@0: /* Compare both versions to ensure we don't have a downgrade michael@0: -1 if appVersion is older than productInfoBlock.productVersion michael@0: 1 if appVersion is newer than productInfoBlock.productVersion michael@0: 0 if appVersion is the same as productInfoBlock.productVersion michael@0: This even works with strings like: michael@0: - 12.0a1 being older than 12.0a2 michael@0: - 12.0a2 being older than 12.0b1 michael@0: - 12.0a1 being older than 12.0 michael@0: - 12.0 being older than 12.1a1 */ michael@0: int versionCompareResult = michael@0: mozilla::CompareVersions(appVersion, productInfoBlock.productVersion); michael@0: if (1 == versionCompareResult) { michael@0: rv = VERSION_DOWNGRADE_ERROR; michael@0: } michael@0: } michael@0: michael@0: free((void *)productInfoBlock.MARChannelID); michael@0: free((void *)productInfoBlock.productVersion); michael@0: return rv; michael@0: } michael@0: michael@0: int michael@0: ArchiveReader::Open(const NS_tchar *path) michael@0: { michael@0: if (mArchive) michael@0: Close(); michael@0: michael@0: if (!inbuf) { michael@0: inbuf = (char *)malloc(inbuf_size); michael@0: if (!inbuf) { michael@0: // Try again with a smaller buffer. michael@0: inbuf_size = 1024; michael@0: inbuf = (char *)malloc(inbuf_size); michael@0: if (!inbuf) michael@0: return ARCHIVE_READER_MEM_ERROR; michael@0: } michael@0: } michael@0: michael@0: if (!outbuf) { michael@0: outbuf = (char *)malloc(outbuf_size); michael@0: if (!outbuf) { michael@0: // Try again with a smaller buffer. michael@0: outbuf_size = 1024; michael@0: outbuf = (char *)malloc(outbuf_size); michael@0: if (!outbuf) michael@0: return ARCHIVE_READER_MEM_ERROR; michael@0: } michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: mArchive = mar_wopen(path); michael@0: #else michael@0: mArchive = mar_open(path); michael@0: #endif michael@0: if (!mArchive) michael@0: return READ_ERROR; michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: void michael@0: ArchiveReader::Close() michael@0: { michael@0: if (mArchive) { michael@0: mar_close(mArchive); michael@0: mArchive = nullptr; michael@0: } michael@0: michael@0: if (inbuf) { michael@0: free(inbuf); michael@0: inbuf = nullptr; michael@0: } michael@0: michael@0: if (outbuf) { michael@0: free(outbuf); michael@0: outbuf = nullptr; michael@0: } michael@0: } michael@0: michael@0: int michael@0: ArchiveReader::ExtractFile(const char *name, const NS_tchar *dest) michael@0: { michael@0: const MarItem *item = mar_find_item(mArchive, name); michael@0: if (!item) michael@0: return READ_ERROR; michael@0: michael@0: #ifdef XP_WIN michael@0: FILE* fp = _wfopen(dest, L"wb+"); michael@0: #else michael@0: int fd = creat(dest, item->flags); michael@0: if (fd == -1) michael@0: return WRITE_ERROR; michael@0: michael@0: FILE *fp = fdopen(fd, "wb"); michael@0: #endif michael@0: if (!fp) michael@0: return WRITE_ERROR; michael@0: michael@0: int rv = ExtractItemToStream(item, fp); michael@0: michael@0: fclose(fp); michael@0: return rv; michael@0: } michael@0: michael@0: int michael@0: ArchiveReader::ExtractFileToStream(const char *name, FILE *fp) michael@0: { michael@0: const MarItem *item = mar_find_item(mArchive, name); michael@0: if (!item) michael@0: return READ_ERROR; michael@0: michael@0: return ExtractItemToStream(item, fp); michael@0: } michael@0: michael@0: int michael@0: ArchiveReader::ExtractItemToStream(const MarItem *item, FILE *fp) michael@0: { michael@0: /* decompress the data chunk by chunk */ michael@0: michael@0: bz_stream strm; michael@0: int offset, inlen, outlen, ret = OK; michael@0: michael@0: memset(&strm, 0, sizeof(strm)); michael@0: if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK) michael@0: return UNEXPECTED_BZIP_ERROR; michael@0: michael@0: offset = 0; michael@0: for (;;) { michael@0: if (!item->length) { michael@0: ret = UNEXPECTED_MAR_ERROR; michael@0: break; michael@0: } michael@0: michael@0: if (offset < (int) item->length && strm.avail_in == 0) { michael@0: inlen = mar_read(mArchive, item, offset, inbuf, inbuf_size); michael@0: if (inlen <= 0) michael@0: return READ_ERROR; michael@0: offset += inlen; michael@0: strm.next_in = inbuf; michael@0: strm.avail_in = inlen; michael@0: } michael@0: michael@0: strm.next_out = outbuf; michael@0: strm.avail_out = outbuf_size; michael@0: michael@0: ret = BZ2_bzDecompress(&strm); michael@0: if (ret != BZ_OK && ret != BZ_STREAM_END) { michael@0: ret = UNEXPECTED_BZIP_ERROR; michael@0: break; michael@0: } michael@0: michael@0: outlen = outbuf_size - strm.avail_out; michael@0: if (outlen) { michael@0: if (fwrite(outbuf, outlen, 1, fp) != 1) { michael@0: ret = WRITE_ERROR; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (ret == BZ_STREAM_END) { michael@0: ret = OK; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: BZ2_bzDecompressEnd(&strm); michael@0: return ret; michael@0: }