diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /onlineupdate/source | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'onlineupdate/source')
79 files changed, 17160 insertions, 0 deletions
diff --git a/onlineupdate/source/libmar/README b/onlineupdate/source/libmar/README new file mode 100644 index 000000000..422a28959 --- /dev/null +++ b/onlineupdate/source/libmar/README @@ -0,0 +1,6 @@ +This directory contains code for a simple archive file format, which +is documented at http://wiki.mozilla.org/Software_Update:MAR + +The src directory builds a small static library used to create, read, and +extract an archive file. The tool directory builds a command line utility +around the library. diff --git a/onlineupdate/source/libmar/sign/Makefile.in b/onlineupdate/source/libmar/sign/Makefile.in new file mode 100644 index 000000000..c5eaeb444 --- /dev/null +++ b/onlineupdate/source/libmar/sign/Makefile.in @@ -0,0 +1,10 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This makefile just builds support for reading archives. +include $(topsrcdir)/config/rules.mk + +# The intermediate (.ii/.s) files for host and target can have the same name... +# disable parallel builds +.NOTPARALLEL: diff --git a/onlineupdate/source/libmar/sign/mar_sign.c b/onlineupdate/source/libmar/sign/mar_sign.c new file mode 100644 index 000000000..161cadc0d --- /dev/null +++ b/onlineupdate/source/libmar/sign/mar_sign.c @@ -0,0 +1,1160 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <onlineupdate/mar_private.h> +#include <onlineupdate/mar_cmdline.h> +#include <onlineupdate/mar.h> +#include "cryptox.h" +#ifndef _WIN32 +#include <unistd.h> +#endif + +#include "nss_secutil.h" +#include "base64.h" + +/** + * Initializes the NSS context. + * + * @param NSSConfigDir The config dir containing the private key to use + * @return 0 on success + * -1 on error +*/ +int +NSSInitCryptoContext(const char *NSSConfigDir) +{ + SECStatus status = NSS_Initialize(NSSConfigDir, + "", "", SECMOD_DB, NSS_INIT_READONLY); + if (SECSuccess != status) { + fprintf(stderr, "ERROR: Could not initialize NSS\n"); + return -1; + } + + return 0; +} + +/** + * Obtains a signing context. + * + * @param ctx A pointer to the signing context to fill + * @return 0 on success + * -1 on error +*/ +int +NSSSignBegin(const char *certName, + SGNContext **ctx, + SECKEYPrivateKey **privKey, + CERTCertificate **cert, + uint32_t *signatureLength) +{ + secuPWData pwdata = { PW_NONE, 0 }; + if (!certName || !ctx || !privKey || !cert || !signatureLength) { + fprintf(stderr, "ERROR: Invalid parameter passed to NSSSignBegin\n"); + return -1; + } + + /* Get the cert and embedded public key out of the database */ + *cert = PK11_FindCertFromNickname(certName, &pwdata); + if (!*cert) { + fprintf(stderr, "ERROR: Could not find cert from nickname\n"); + return -1; + } + + /* Get the private key out of the database */ + *privKey = PK11_FindKeyByAnyCert(*cert, &pwdata); + if (!*privKey) { + fprintf(stderr, "ERROR: Could not find private key\n"); + return -1; + } + + *signatureLength = PK11_SignatureLen(*privKey); + + if (*signatureLength > BLOCKSIZE) { + fprintf(stderr, + "ERROR: Program must be compiled with a larger block size" + " to support signing with signatures this large: %u.\n", + *signatureLength); + return -1; + } + + /* Check that the key length is large enough for our requirements */ + if (*signatureLength < XP_MIN_SIGNATURE_LEN_IN_BYTES) { + fprintf(stderr, "ERROR: Key length must be >= %d bytes\n", + XP_MIN_SIGNATURE_LEN_IN_BYTES); + return -1; + } + + *ctx = SGN_NewContext (SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, *privKey); + if (!*ctx) { + fprintf(stderr, "ERROR: Could not create signature context\n"); + return -1; + } + + if (SGN_Begin(*ctx) != SECSuccess) { + fprintf(stderr, "ERROR: Could not begin signature\n"); + return -1; + } + + return 0; +} + +/** + * Writes the passed buffer to the file fp and updates the signature contexts. + * + * @param fpDest The file pointer to write to. + * @param buffer The buffer to write. + * @param size The size of the buffer to write. + * @param ctxs Pointer to the first element in an array of signature + * contexts to update. + * @param ctxCount The number of signature contexts pointed to by ctxs + * @param err The name of what is being written to in case of error. + * @return 0 on success + * -2 on write error + * -3 on signature update error +*/ +int +WriteAndUpdateSignatures(FILE *fpDest, void *buffer, + uint32_t size, SGNContext **ctxs, + uint32_t ctxCount, + const char *err) +{ + uint32_t k; + if (!size) { + return 0; + } + + if (fwrite(buffer, size, 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write %s\n", err); + return -2; + } + + for (k = 0; k < ctxCount; ++k) { + if (SGN_Update(ctxs[k], buffer, size) != SECSuccess) { + fprintf(stderr, "ERROR: Could not update signature context for %s\n", err); + return -3; + } + } + return 0; +} + +/** + * Adjusts each entry's content offset in the passed in index by the + * specified amount. + * + * @param indexBuf A buffer containing the MAR index + * @param indexLength The length of the MAR index + * @param offsetAmount The amount to adjust each index entry by +*/ +void +AdjustIndexContentOffsets(char *indexBuf, uint32_t indexLength, uint32_t offsetAmount) +{ + char *indexBufLoc = indexBuf; + + /* Consume the index and adjust each index by the specified amount */ + while (indexBufLoc != (indexBuf + indexLength)) { + /* Adjust the offset */ + uint32_t* offsetToContent = (uint32_t *)indexBufLoc; + *offsetToContent = ntohl(*offsetToContent); + *offsetToContent += offsetAmount; + *offsetToContent = htonl(*offsetToContent); + /* Skip past the offset, length, and flags */ + indexBufLoc += 3 * sizeof(uint32_t); + indexBufLoc += strlen(indexBufLoc) + 1; + } +} + +/** + * Reads from fpSrc, writes it to fpDest, and updates the signature contexts. + * + * @param fpSrc The file pointer to read from. + * @param fpDest The file pointer to write to. + * @param buffer The buffer to write. + * @param size The size of the buffer to write. + * @param ctxs Pointer to the first element in an array of signature + * contexts to update. + * @param ctxCount The number of signature contexts pointed to by ctxs + * @param err The name of what is being written to in case of error. + * @return 0 on success + * -1 on read error + * -2 on write error + * -3 on signature update error +*/ +int +ReadWriteAndUpdateSignatures(FILE *fpSrc, FILE *fpDest, void *buffer, + uint32_t size, SGNContext **ctxs, + uint32_t ctxCount, + const char *err) +{ + if (!size) { + return 0; + } + + if (fread(buffer, size, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read %s\n", err); + return -1; + } + + return WriteAndUpdateSignatures(fpDest, buffer, size, ctxs, ctxCount, err); +} + + +/** + * Reads from fpSrc, writes it to fpDest. + * + * @param fpSrc The file pointer to read from. + * @param fpDest The file pointer to write to. + * @param buffer The buffer to write. + * @param size The size of the buffer to write. + * @param err The name of what is being written to in case of error. + * @return 0 on success + * -1 on read error + * -2 on write error +*/ +int +ReadAndWrite(FILE *fpSrc, FILE *fpDest, void *buffer, + uint32_t size, const char *err) +{ + if (!size) { + return 0; + } + + if (fread(buffer, size, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read %s\n", err); + return -1; + } + + if (fwrite(buffer, size, 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write %s\n", err); + return -2; + } + + return 0; +} + +/** + * Writes out a copy of the MAR at src but with the signature block stripped. + * + * @param src The path of the source MAR file + * @param dest The path of the MAR file to write out that + has no signature block + * @return 0 on success + * -1 on error +*/ +int +strip_signature_block(const char *src, const char * dest) +{ + uint32_t offsetToIndex, dstOffsetToIndex, indexLength, + numSignatures = 0, leftOver; + int32_t stripAmount = 0; + int64_t oldPos, sizeOfEntireMAR = 0, realSizeOfSrcMAR, numBytesToCopy, + numChunks, i; + FILE *fpSrc = NULL, *fpDest = NULL; + int rv = -1, hasSignatureBlock; + char buf[BLOCKSIZE]; + char *indexBuf = NULL; + + if (!src || !dest) { + fprintf(stderr, "ERROR: Invalid parameter passed in.\n"); + return -1; + } + + fpSrc = fopen(src, "rb"); + if (!fpSrc) { + fprintf(stderr, "ERROR: could not open source file: %s\n", src); + goto failure; + } + + fpDest = fopen(dest, "wb"); + if (!fpDest) { + fprintf(stderr, "ERROR: could not create target file: %s\n", dest); + goto failure; + } + + /* Determine if the source MAR file has the new fields for signing or not */ + if (get_mar_file_info(src, &hasSignatureBlock, NULL, NULL, NULL, NULL)) { + fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n"); + goto failure; + } + + /* MAR ID */ + if (ReadAndWrite(fpSrc, fpDest, buf, MAR_ID_SIZE, "MAR ID")) { + goto failure; + } + + /* Offset to index */ + if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read offset\n"); + goto failure; + } + offsetToIndex = ntohl(offsetToIndex); + + /* Get the real size of the MAR */ + oldPos = ftello(fpSrc); + if (fseeko(fpSrc, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to end of file.\n"); + goto failure; + } + realSizeOfSrcMAR = ftello(fpSrc); + if (fseeko(fpSrc, oldPos, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek back to current location.\n"); + goto failure; + } + + if (hasSignatureBlock) { + /* Get the MAR length and adjust its size */ + if (fread(&sizeOfEntireMAR, + sizeof(sizeOfEntireMAR), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could read mar size\n"); + goto failure; + } + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + if (sizeOfEntireMAR != realSizeOfSrcMAR) { + fprintf(stderr, "ERROR: Source MAR is not of the right size\n"); + goto failure; + } + + /* Get the num signatures in the source file so we know what to strip */ + if (fread(&numSignatures, sizeof(numSignatures), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could read num signatures\n"); + goto failure; + } + numSignatures = ntohl(numSignatures); + + for (i = 0; i < numSignatures; i++) { + uint32_t signatureLen; + + /* Skip past the signature algorithm ID */ + if (fseeko(fpSrc, sizeof(uint32_t), SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not skip past signature algorithm ID\n"); + } + + /* Read in the length of the signature so we know how far to skip */ + if (fread(&signatureLen, sizeof(uint32_t), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read signatures length.\n"); + return CryptoX_Error; + } + signatureLen = ntohl(signatureLen); + + /* Skip past the signature */ + if (fseeko(fpSrc, signatureLen, SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not skip past signature algorithm ID\n"); + } + + stripAmount += sizeof(uint32_t) + sizeof(uint32_t) + signatureLen; + } + + } else { + sizeOfEntireMAR = realSizeOfSrcMAR; + numSignatures = 0; + } + + if (((int64_t)offsetToIndex) > sizeOfEntireMAR) { + fprintf(stderr, "ERROR: Offset to index is larger than the file size.\n"); + goto failure; + } + + dstOffsetToIndex = offsetToIndex; + if (!hasSignatureBlock) { + dstOffsetToIndex += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); + } + dstOffsetToIndex -= stripAmount; + + /* Write out the index offset */ + dstOffsetToIndex = htonl(dstOffsetToIndex); + if (fwrite(&dstOffsetToIndex, sizeof(dstOffsetToIndex), 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write offset to index\n"); + goto failure; + } + dstOffsetToIndex = ntohl(dstOffsetToIndex); + + /* Write out the new MAR file size */ + if (!hasSignatureBlock) { + sizeOfEntireMAR += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); + } + sizeOfEntireMAR -= stripAmount; + + /* Write out the MAR size */ + sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR); + if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write size of MAR\n"); + goto failure; + } + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + + /* Write out the number of signatures, which is 0 */ + numSignatures = 0; + if (fwrite(&numSignatures, sizeof(numSignatures), 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write out num signatures\n"); + goto failure; + } + + /* Write out the rest of the MAR excluding the index header and index + offsetToIndex unfortunately has to remain 32-bit because for backwards + compatibility with the old MAR file format. */ + if (ftello(fpSrc) > ((int64_t)offsetToIndex)) { + fprintf(stderr, "ERROR: Index offset is too small.\n"); + goto failure; + } + numBytesToCopy = ((int64_t)offsetToIndex) - ftello(fpSrc); + numChunks = numBytesToCopy / BLOCKSIZE; + leftOver = numBytesToCopy % BLOCKSIZE; + + /* Read each file and write it to the MAR file */ + for (i = 0; i < numChunks; ++i) { + if (ReadAndWrite(fpSrc, fpDest, buf, BLOCKSIZE, "content block")) { + goto failure; + } + } + + /* Write out the left over */ + if (ReadAndWrite(fpSrc, fpDest, buf, + leftOver, "left over content block")) { + goto failure; + } + + /* Length of the index */ + if (ReadAndWrite(fpSrc, fpDest, &indexLength, + sizeof(indexLength), "index length")) { + goto failure; + } + indexLength = ntohl(indexLength); + + /* Consume the index and adjust each index by the difference */ + indexBuf = malloc(indexLength); + if (fread(indexBuf, indexLength, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read index\n"); + goto failure; + } + + /* Adjust each entry in the index */ + if (hasSignatureBlock) { + AdjustIndexContentOffsets(indexBuf, indexLength, -stripAmount); + } else { + AdjustIndexContentOffsets(indexBuf, indexLength, + sizeof(sizeOfEntireMAR) + + sizeof(numSignatures) - + stripAmount); + } + + if (fwrite(indexBuf, indexLength, 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write index\n"); + goto failure; + } + + rv = 0; +failure: + if (fpSrc) { + fclose(fpSrc); + } + + if (fpDest) { + fclose(fpDest); + } + + if (rv) { + remove(dest); + } + + if (indexBuf) { + free(indexBuf); + } + + if (rv) { + remove(dest); + } + return rv; +} + +/** + * Extracts a signature from a MAR file, base64 encodes it, and writes it out + * + * @param src The path of the source MAR file + * @param sigIndex The index of the signature to extract + * @param dest The path of file to write the signature to + * @return 0 on success + * -1 on error +*/ +int +extract_signature(const char *src, uint32_t sigIndex, const char * dest) +{ + FILE *fpSrc = NULL, *fpDest = NULL; + uint32_t i; + uint32_t signatureCount; + uint32_t signatureLen = 0; + uint8_t *extractedSignature = NULL; + char *base64Encoded = NULL; + int rv = -1; + if (!src || !dest) { + fprintf(stderr, "ERROR: Invalid parameter passed in.\n"); + goto failure; + } + + fpSrc = fopen(src, "rb"); + if (!fpSrc) { + fprintf(stderr, "ERROR: could not open source file: %s\n", src); + goto failure; + } + + fpDest = fopen(dest, "wb"); + if (!fpDest) { + fprintf(stderr, "ERROR: could not create target file: %s\n", dest); + goto failure; + } + + /* Skip to the start of the signature block */ + if (fseeko(fpSrc, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) { + fprintf(stderr, "ERROR: could not seek to signature block\n"); + goto failure; + } + + /* Get the number of signatures */ + if (fread(&signatureCount, sizeof(signatureCount), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: could not read signature count\n"); + goto failure; + } + signatureCount = ntohl(signatureCount); + if (sigIndex >= signatureCount) { + fprintf(stderr, "ERROR: Signature index was out of range\n"); + goto failure; + } + + /* Skip to the correct signature */ + for (i = 0; i <= sigIndex; i++) { + /* Avoid leaking while skipping signatures */ + free(extractedSignature); + + /* skip past the signature algorithm ID */ + if (fseeko(fpSrc, sizeof(uint32_t), SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not seek past sig algorithm ID.\n"); + goto failure; + } + + /* Get the signature length */ + if (fread(&signatureLen, sizeof(signatureLen), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: could not read signature length\n"); + goto failure; + } + signatureLen = ntohl(signatureLen); + + /* Get the signature */ + extractedSignature = malloc(signatureLen); + if (fread(extractedSignature, signatureLen, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: could not read signature\n"); + goto failure; + } + } + + base64Encoded = BTOA_DataToAscii(extractedSignature, signatureLen); + if (!base64Encoded) { + fprintf(stderr, "ERROR: could not obtain base64 encoded data\n"); + goto failure; + } + + if (fwrite(base64Encoded, strlen(base64Encoded), 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write base64 encoded string\n"); + goto failure; + } + + rv = 0; +failure: + if (base64Encoded) { + PORT_Free(base64Encoded); + } + + if (extractedSignature) { + free(extractedSignature); + } + + if (fpSrc) { + fclose(fpSrc); + } + + if (fpDest) { + fclose(fpDest); + } + + if (rv) { + remove(dest); + } + + return rv; +} + +/** + * Imports a base64 encoded signature into a MAR file + * + * @param src The path of the source MAR file + * @param sigIndex The index of the signature to import + * @param base64SigFile A file which contains the signature to import + * @param dest The path of the destination MAR file with replaced signature + * @return 0 on success + * -1 on error +*/ +int +import_signature(const char *src, uint32_t sigIndex, + const char *base64SigFile, const char *dest) +{ + int rv = -1; + FILE *fpSrc = NULL; + FILE *fpDest = NULL; + FILE *fpSigFile = NULL; + uint32_t i; + uint32_t signatureCount, signatureLen, signatureAlgorithmID, + numChunks, leftOver; + char buf[BLOCKSIZE]; + uint64_t sizeOfSrcMAR, sizeOfBase64EncodedFile; + char *passedInSignatureB64 = NULL; + uint8_t *passedInSignatureRaw = NULL; + uint8_t *extractedMARSignature = NULL; + unsigned int passedInSignatureLenRaw; + + if (!src || !dest) { + fprintf(stderr, "ERROR: Invalid parameter passed in.\n"); + goto failure; + } + + fpSrc = fopen(src, "rb"); + if (!fpSrc) { + fprintf(stderr, "ERROR: could not open source file: %s\n", src); + goto failure; + } + + fpDest = fopen(dest, "wb"); + if (!fpDest) { + fprintf(stderr, "ERROR: could not open dest file: %s\n", dest); + goto failure; + } + + fpSigFile = fopen(base64SigFile , "rb"); + if (!fpSigFile) { + fprintf(stderr, "ERROR: could not open sig file: %s\n", base64SigFile); + goto failure; + } + + /* Get the src file size */ + if (fseeko(fpSrc, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to end of src file.\n"); + goto failure; + } + sizeOfSrcMAR = ftello(fpSrc); + if (fseeko(fpSrc, 0, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to start of src file.\n"); + goto failure; + } + + /* Get the sig file size */ + if (fseeko(fpSigFile, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to end of sig file.\n"); + goto failure; + } + sizeOfBase64EncodedFile= ftello(fpSigFile); + if (fseeko(fpSigFile, 0, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to start of sig file.\n"); + goto failure; + } + + /* Read in the base64 encoded signature to import */ + passedInSignatureB64 = malloc(sizeOfBase64EncodedFile + 1); + passedInSignatureB64[sizeOfBase64EncodedFile] = '\0'; + if (fread(passedInSignatureB64, sizeOfBase64EncodedFile, 1, fpSigFile) != 1) { + fprintf(stderr, "ERROR: Could read b64 sig file.\n"); + goto failure; + } + + /* Decode the base64 encoded data */ + passedInSignatureRaw = ATOB_AsciiToData(passedInSignatureB64, &passedInSignatureLenRaw); + if (!passedInSignatureRaw) { + fprintf(stderr, "ERROR: could not obtain base64 decoded data\n"); + goto failure; + } + + /* Read everything up until the signature block offset and write it out */ + if (ReadAndWrite(fpSrc, fpDest, buf, + SIGNATURE_BLOCK_OFFSET, "signature block offset")) { + goto failure; + } + + /* Get the number of signatures */ + if (ReadAndWrite(fpSrc, fpDest, &signatureCount, + sizeof(signatureCount), "signature count")) { + goto failure; + } + signatureCount = ntohl(signatureCount); + if (signatureCount > MAX_SIGNATURES) { + fprintf(stderr, "ERROR: Signature count was out of range\n"); + goto failure; + } + + if (sigIndex >= signatureCount) { + fprintf(stderr, "ERROR: Signature index was out of range\n"); + goto failure; + } + + /* Read and write the whole signature block, but if we reach the + signature offset, then we should replace it with the specified + base64 decoded signature */ + for (i = 0; i < signatureCount; i++) { + /* Read/Write the signature algorithm ID */ + if (ReadAndWrite(fpSrc, fpDest, + &signatureAlgorithmID, + sizeof(signatureAlgorithmID), "sig algorithm ID")) { + goto failure; + } + + /* Read/Write the signature length */ + if (ReadAndWrite(fpSrc, fpDest, + &signatureLen, sizeof(signatureLen), "sig length")) { + goto failure; + } + signatureLen = ntohl(signatureLen); + + /* Get the signature */ + if (extractedMARSignature) { + free(extractedMARSignature); + } + extractedMARSignature = malloc(signatureLen); + + if (sigIndex == i) { + if (passedInSignatureLenRaw != signatureLen) { + fprintf(stderr, "ERROR: Signature length must be the same\n"); + goto failure; + } + + if (fread(extractedMARSignature, signatureLen, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read signature\n"); + goto failure; + } + + if (fwrite(passedInSignatureRaw, passedInSignatureLenRaw, + 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write signature\n"); + goto failure; + } + } else { + if (ReadAndWrite(fpSrc, fpDest, + extractedMARSignature, signatureLen, "signature")) { + goto failure; + } + } + } + + /* We replaced the signature so let's just skip past the rest o the + file. */ + numChunks = (sizeOfSrcMAR - ftello(fpSrc)) / BLOCKSIZE; + leftOver = (sizeOfSrcMAR - ftello(fpSrc)) % BLOCKSIZE; + + /* Read each file and write it to the MAR file */ + for (i = 0; i < numChunks; ++i) { + if (ReadAndWrite(fpSrc, fpDest, buf, BLOCKSIZE, "content block")) { + goto failure; + } + } + + if (ReadAndWrite(fpSrc, fpDest, buf, leftOver, "left over content block")) { + goto failure; + } + + rv = 0; + +failure: + + if (fpSrc) { + fclose(fpSrc); + } + + if (fpDest) { + fclose(fpDest); + } + + if (fpSigFile) { + fclose(fpSigFile); + } + + if (rv) { + remove(dest); + } + + if (extractedMARSignature) { + free(extractedMARSignature); + } + + if (passedInSignatureB64) { + free(passedInSignatureB64); + } + + if (passedInSignatureRaw) { + PORT_Free(passedInSignatureRaw); + } + + return rv; +} + +/** + * Writes out a copy of the MAR at src but with embedded signatures. + * The passed in MAR file must not already be signed or an error will + * be returned. + * + * @param NSSConfigDir The NSS directory containing the private key for signing + * @param certNames The nicknames of the certificate to use for signing + * @param certCount The number of certificate names contained in certNames. + * One signature will be produced for each certificate. + * @param src The path of the source MAR file to sign + * @param dest The path of the MAR file to write out that is signed + * @return 0 on success + * -1 on error +*/ +int +mar_repackage_and_sign(const char *NSSConfigDir, + const char * const *certNames, + uint32_t certCount, + const char *src, + const char *dest) +{ + uint32_t offsetToIndex, dstOffsetToIndex, indexLength, + numSignatures = 0, leftOver, + signatureAlgorithmID, signatureSectionLength = 0; + uint32_t signatureLengths[MAX_SIGNATURES]; + int64_t oldPos, sizeOfEntireMAR = 0, realSizeOfSrcMAR, + signaturePlaceholderOffset, numBytesToCopy, + numChunks, i; + FILE *fpSrc = NULL, *fpDest = NULL; + int rv = -1, hasSignatureBlock; + SGNContext *ctxs[MAX_SIGNATURES]; + SECItem secItems[MAX_SIGNATURES]; + char buf[BLOCKSIZE]; + SECKEYPrivateKey *privKeys[MAX_SIGNATURES]; + CERTCertificate *certs[MAX_SIGNATURES]; + char *indexBuf = NULL; + uint32_t k; + + memset(signatureLengths, 0, sizeof(signatureLengths)); + memset(ctxs, 0, sizeof(ctxs)); + memset(secItems, 0, sizeof(secItems)); + memset(privKeys, 0, sizeof(privKeys)); + memset(certs, 0, sizeof(certs)); + + if (!NSSConfigDir || !certNames || certCount == 0 || !src || !dest) { + fprintf(stderr, "ERROR: Invalid parameter passed in.\n"); + return -1; + } + + if (NSSInitCryptoContext(NSSConfigDir)) { + fprintf(stderr, "ERROR: Could not init config dir: %s\n", NSSConfigDir); + goto failure; + } + + PK11_SetPasswordFunc(SECU_GetModulePassword); + + fpSrc = fopen(src, "rb"); + if (!fpSrc) { + fprintf(stderr, "ERROR: could not open source file: %s\n", src); + goto failure; + } + + fpDest = fopen(dest, "wb"); + if (!fpDest) { + fprintf(stderr, "ERROR: could not create target file: %s\n", dest); + goto failure; + } + + /* Determine if the source MAR file has the new fields for signing or not */ + if (get_mar_file_info(src, &hasSignatureBlock, NULL, NULL, NULL, NULL)) { + fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n"); + goto failure; + } + + for (k = 0; k < certCount; k++) { + if (NSSSignBegin(certNames[k], &ctxs[k], &privKeys[k], + &certs[k], &signatureLengths[k])) { + fprintf(stderr, "ERROR: NSSSignBegin failed\n"); + goto failure; + } + } + + /* MAR ID */ + if (ReadWriteAndUpdateSignatures(fpSrc, fpDest, + buf, MAR_ID_SIZE, + ctxs, certCount, "MAR ID")) { + goto failure; + } + + /* Offset to index */ + if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read offset\n"); + goto failure; + } + offsetToIndex = ntohl(offsetToIndex); + + /* Get the real size of the MAR */ + oldPos = ftello(fpSrc); + if (fseeko(fpSrc, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to end of file.\n"); + goto failure; + } + realSizeOfSrcMAR = ftello(fpSrc); + if (fseeko(fpSrc, oldPos, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek back to current location.\n"); + goto failure; + } + + if (hasSignatureBlock) { + /* Get the MAR length and adjust its size */ + if (fread(&sizeOfEntireMAR, + sizeof(sizeOfEntireMAR), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could read mar size\n"); + goto failure; + } + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + if (sizeOfEntireMAR != realSizeOfSrcMAR) { + fprintf(stderr, "ERROR: Source MAR is not of the right size\n"); + goto failure; + } + + /* Get the num signatures in the source file */ + if (fread(&numSignatures, sizeof(numSignatures), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could read num signatures\n"); + goto failure; + } + numSignatures = ntohl(numSignatures); + + /* We do not support resigning, if you have multiple signatures, + you must add them all at the same time. */ + if (numSignatures) { + fprintf(stderr, "ERROR: MAR is already signed\n"); + goto failure; + } + } else { + sizeOfEntireMAR = realSizeOfSrcMAR; + } + + if (((int64_t)offsetToIndex) > sizeOfEntireMAR) { + fprintf(stderr, "ERROR: Offset to index is larger than the file size.\n"); + goto failure; + } + + /* Calculate the total signature block length */ + for (k = 0; k < certCount; k++) { + signatureSectionLength += sizeof(signatureAlgorithmID) + + sizeof(signatureLengths[k]) + + signatureLengths[k]; + } + dstOffsetToIndex = offsetToIndex; + if (!hasSignatureBlock) { + dstOffsetToIndex += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); + } + dstOffsetToIndex += signatureSectionLength; + + /* Write out the index offset */ + dstOffsetToIndex = htonl(dstOffsetToIndex); + if (WriteAndUpdateSignatures(fpDest, &dstOffsetToIndex, + sizeof(dstOffsetToIndex), ctxs, certCount, + "index offset")) { + goto failure; + } + dstOffsetToIndex = ntohl(dstOffsetToIndex); + + /* Write out the new MAR file size */ + sizeOfEntireMAR += signatureSectionLength; + if (!hasSignatureBlock) { + sizeOfEntireMAR += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); + } + + /* Write out the MAR size */ + sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR); + if (WriteAndUpdateSignatures(fpDest, &sizeOfEntireMAR, + sizeof(sizeOfEntireMAR), ctxs, certCount, + "size of MAR")) { + goto failure; + } + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + + /* Write out the number of signatures */ + numSignatures = certCount; + numSignatures = htonl(numSignatures); + if (WriteAndUpdateSignatures(fpDest, &numSignatures, + sizeof(numSignatures), ctxs, certCount, + "num signatures")) { + goto failure; + } + numSignatures = ntohl(numSignatures); + + signaturePlaceholderOffset = ftello(fpDest); + + for (k = 0; k < certCount; k++) { + /* Write out the signature algorithm ID, Only an ID of 1 is supported */ + signatureAlgorithmID = htonl(1); + if (WriteAndUpdateSignatures(fpDest, &signatureAlgorithmID, + sizeof(signatureAlgorithmID), + ctxs, certCount, "num signatures")) { + goto failure; + } + signatureAlgorithmID = ntohl(signatureAlgorithmID); + + /* Write out the signature length */ + signatureLengths[k] = htonl(signatureLengths[k]); + if (WriteAndUpdateSignatures(fpDest, &signatureLengths[k], + sizeof(signatureLengths[k]), + ctxs, certCount, "signature length")) { + goto failure; + } + signatureLengths[k] = ntohl(signatureLengths[k]); + + /* Write out a placeholder for the signature, we'll come back to this later + *** THIS IS NOT SIGNED because it is a placeholder that will be replaced + below, plus it is going to be the signature itself. *** */ + memset(buf, 0, sizeof(buf)); + if (fwrite(buf, signatureLengths[k], 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write signature length\n"); + goto failure; + } + } + + /* Write out the rest of the MAR excluding the index header and index + offsetToIndex unfortunately has to remain 32-bit because for backwards + compatibility with the old MAR file format. */ + if (ftello(fpSrc) > ((int64_t)offsetToIndex)) { + fprintf(stderr, "ERROR: Index offset is too small.\n"); + goto failure; + } + numBytesToCopy = ((int64_t)offsetToIndex) - ftello(fpSrc); + numChunks = numBytesToCopy / BLOCKSIZE; + leftOver = numBytesToCopy % BLOCKSIZE; + + /* Read each file and write it to the MAR file */ + for (i = 0; i < numChunks; ++i) { + if (ReadWriteAndUpdateSignatures(fpSrc, fpDest, buf, + BLOCKSIZE, ctxs, certCount, + "content block")) { + goto failure; + } + } + + /* Write out the left over */ + if (ReadWriteAndUpdateSignatures(fpSrc, fpDest, buf, + leftOver, ctxs, certCount, + "left over content block")) { + goto failure; + } + + /* Length of the index */ + if (ReadWriteAndUpdateSignatures(fpSrc, fpDest, &indexLength, + sizeof(indexLength), ctxs, certCount, + "index length")) { + goto failure; + } + indexLength = ntohl(indexLength); + + /* Consume the index and adjust each index by signatureSectionLength */ + indexBuf = malloc(indexLength); + if (fread(indexBuf, indexLength, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read index\n"); + goto failure; + } + + /* Adjust each entry in the index */ + if (hasSignatureBlock) { + AdjustIndexContentOffsets(indexBuf, indexLength, signatureSectionLength); + } else { + AdjustIndexContentOffsets(indexBuf, indexLength, + sizeof(sizeOfEntireMAR) + + sizeof(numSignatures) + + signatureSectionLength); + } + + if (WriteAndUpdateSignatures(fpDest, indexBuf, + indexLength, ctxs, certCount, "index")) { + goto failure; + } + + /* Ensure that we don't sign a file that is too large to be accepted by + the verification function. */ + if (ftello(fpDest) > MAX_SIZE_OF_MAR_FILE) { + goto failure; + } + + for (k = 0; k < certCount; k++) { + /* Get the signature */ + if (SGN_End(ctxs[k], &secItems[k]) != SECSuccess) { + fprintf(stderr, "ERROR: Could not end signature context\n"); + goto failure; + } + if (signatureLengths[k] != secItems[k].len) { + fprintf(stderr, "ERROR: Signature is not the expected length\n"); + goto failure; + } + } + + /* Get back to the location of the signature placeholder */ + if (fseeko(fpDest, signaturePlaceholderOffset, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to signature offset\n"); + goto failure; + } + + for (k = 0; k < certCount; k++) { + /* Skip to the position of the next signature */ + if (fseeko(fpDest, sizeof(signatureAlgorithmID) + + sizeof(signatureLengths[k]), SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not seek to signature offset\n"); + goto failure; + } + + /* Write out the calculated signature. + *** THIS IS NOT SIGNED because it is the signature itself. *** */ + if (fwrite(secItems[k].data, secItems[k].len, 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write signature\n"); + goto failure; + } + } + + rv = 0; +failure: + if (fpSrc) { + fclose(fpSrc); + } + + if (fpDest) { + fclose(fpDest); + } + + if (rv) { + remove(dest); + } + + if (indexBuf) { + free(indexBuf); + } + + /* Cleanup */ + for (k = 0; k < certCount; k++) { + if (ctxs[k]) { + SGN_DestroyContext(ctxs[k], PR_TRUE); + } + + if (certs[k]) { + CERT_DestroyCertificate(certs[k]); + } + + if (privKeys[k]) { + SECKEY_DestroyPrivateKey(privKeys[k]); + } + + SECITEM_FreeItem(&secItems[k], PR_FALSE); + } + + if (rv) { + remove(dest); + } + + return rv; +} diff --git a/onlineupdate/source/libmar/sign/nss_secutil.c b/onlineupdate/source/libmar/sign/nss_secutil.c new file mode 100644 index 000000000..875c14309 --- /dev/null +++ b/onlineupdate/source/libmar/sign/nss_secutil.c @@ -0,0 +1,238 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* With the exception of GetPasswordString, this file was + copied from NSS's cmd/lib/secutil.c hg revision 8f011395145e */ + +#include "nss_secutil.h" + +#include "prprf.h" +#ifdef _WIN32 +#include <io.h> +#else +#include <unistd.h> +#endif + +static char consoleName[] = { +#ifdef UNIX + "/dev/tty" +#else + "CON:" +#endif +}; + +#if defined(_WINDOWS) +static char * quiet_fgets (char *buf, int length, FILE *input) +{ + char *end = buf; + + /* fflush (input); */ + memset (buf, 0, length); + + if (!isatty(fileno(input))) { + return fgets(buf,length,input); + } + + while (1) + { + int c; +#if defined (_WIN32_WCE) + c = getchar(); /* gets a character from stdin */ +#else + c = getch(); /* getch gets a character from the console */ +#endif + if (c == '\b') + { + if (end > buf) + end--; + } + + else if (--length > 0) + *end++ = c; + + if (!c || c == '\n' || c == '\r') + break; + } + + return buf; +} +#endif + +char * +GetPasswordString(void *arg, char *prompt) +{ + FILE *input = stdin; + char phrase[200] = {'\0'}; + int isInputTerminal = isatty(fileno(stdin)); + + (void) arg; (void) prompt; // avoid warnings + +#ifndef _WINDOWS + if (isInputTerminal) { + input = fopen(consoleName, "r"); + if (input == NULL) { + fprintf(stderr, "Error opening input terminal for read\n"); + return NULL; + } + } +#endif + + if (isInputTerminal) { + fprintf(stdout, "Please enter your password:\n"); + fflush(stdout); + } + + QUIET_FGETS (phrase, sizeof(phrase), input); + + if (isInputTerminal) { + fprintf(stdout, "\n"); + } + +#ifndef _WINDOWS + if (isInputTerminal) { + fclose(input); + } +#endif + + /* Strip off the newlines if present */ + if (phrase[PORT_Strlen(phrase)-1] == '\n' || + phrase[PORT_Strlen(phrase)-1] == '\r') { + phrase[PORT_Strlen(phrase)-1] = 0; + } + return (char*) PORT_Strdup(phrase); +} + +char * +SECU_FilePasswd(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + char* phrases, *phrase; + PRFileDesc *fd; + int32_t nb; + char *pwFile = arg; + int i; + const long maxPwdFileSize = 4096; + char* tokenName = NULL; + int tokenLen = 0; + + if (!pwFile) + return 0; + + if (retry) { + return 0; /* no good retrying - the files contents will be the same */ + } + + phrases = PORT_ZAlloc(maxPwdFileSize + 1); + + if (!phrases) { + return 0; /* out of memory */ + } + + fd = PR_Open(pwFile, PR_RDONLY, 0); + if (!fd) { + fprintf(stderr, "No password file \"%s\" exists.\n", pwFile); + PORT_Free(phrases); + return NULL; + } + + nb = PR_Read(fd, phrases, maxPwdFileSize); + + PR_Close(fd); + + if (nb == 0) { + fprintf(stderr,"password file contains no data\n"); + PORT_Free(phrases); + return NULL; + } + + if (slot) { + tokenName = PK11_GetTokenName(slot); + if (tokenName) { + tokenLen = PORT_Strlen(tokenName); + } + } + i = 0; + do + { + int startphrase = i; + int phraseLen; + + /* handle the Windows EOL case */ + while (i < nb && phrases[i] != '\r' && phrases[i] != '\n') i++; + /* terminate passphrase */ + phrases[i++] = '\0'; + /* clean up any EOL before the start of the next passphrase */ + while ( (i<nb) && (phrases[i] == '\r' || phrases[i] == '\n')) { + phrases[i++] = '\0'; + } + /* now analyze the current passphrase */ + phrase = &phrases[startphrase]; + if (!tokenName) + break; + if (PORT_Strncmp(phrase, tokenName, tokenLen)) continue; + phraseLen = PORT_Strlen(phrase); + if (phraseLen < (tokenLen+1)) continue; + if (phrase[tokenLen] != ':') continue; + phrase = &phrase[tokenLen+1]; + break; + + } while (i<nb); + + phrase = PORT_Strdup((char*)phrase); + PORT_Free(phrases); + return phrase; +} + +char * +SECU_GetModulePassword(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + char prompt[255]; + secuPWData *pwdata = (secuPWData *)arg; + secuPWData pwnull = { PW_NONE, 0 }; + secuPWData pwxtrn = { PW_EXTERNAL, "external" }; + char *pw; + + if (pwdata == NULL) + pwdata = &pwnull; + + if (PK11_ProtectedAuthenticationPath(slot)) { + pwdata = &pwxtrn; + } + if (retry && pwdata->source != PW_NONE) { + PR_fprintf(PR_STDERR, "Incorrect password/PIN entered.\n"); + return NULL; + } + + switch (pwdata->source) { + case PW_NONE: + sprintf(prompt, "Enter Password or Pin for \"%s\":", + PK11_GetTokenName(slot)); + return GetPasswordString(NULL, prompt); + case PW_FROMFILE: + /* Instead of opening and closing the file every time, get the pw + * once, then keep it in memory (duh). + */ + pw = SECU_FilePasswd(slot, retry, pwdata->data); + pwdata->source = PW_PLAINTEXT; + pwdata->data = strdup(pw); + /* it's already been dup'ed */ + return pw; + case PW_EXTERNAL: + sprintf(prompt, + "Press Enter, then enter PIN for \"%s\" on external device.\n", + PK11_GetTokenName(slot)); + pw = GetPasswordString(NULL, prompt); + if (pw) { + memset(pw, 0, PORT_Strlen(pw)); + PORT_Free(pw); + } + /* Fall Through */ + case PW_PLAINTEXT: + return strdup(pwdata->data); + default: + break; + } + + PR_fprintf(PR_STDERR, "Password check failed: No password found.\n"); + return NULL; +} diff --git a/onlineupdate/source/libmar/sign/nss_secutil.h b/onlineupdate/source/libmar/sign/nss_secutil.h new file mode 100644 index 000000000..f599fcce5 --- /dev/null +++ b/onlineupdate/source/libmar/sign/nss_secutil.h @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* With the exception of GetPasswordString, this file was + copied from NSS's cmd/lib/secutil.h hg revision 8f011395145e */ + +#ifndef NSS_SECUTIL_H_ +#define NSS_SECUTIL_H_ + +#include "nss.h" +#include "pk11pub.h" +#include "cryptohi.h" +#include "hasht.h" +#include "cert.h" +#include "key.h" +#include <stdint.h> + +typedef struct +{ + enum + { + PW_NONE = 0, + PW_FROMFILE = 1, + PW_PLAINTEXT = 2, + PW_EXTERNAL = 3 + } source; + char *data; +} secuPWData; + +#if( defined(_WINDOWS) && !defined(_WIN32_WCE)) +#include <conio.h> +#include <io.h> +#define QUIET_FGETS quiet_fgets +static char * quiet_fgets (char *buf, int length, FILE *input); +#else +#define QUIET_FGETS fgets +#endif + +char * +SECU_GetModulePassword(PK11SlotInfo *slot, PRBool retry, void *arg); + +#endif diff --git a/onlineupdate/source/libmar/src/Makefile.in b/onlineupdate/source/libmar/src/Makefile.in new file mode 100644 index 000000000..1da582e5b --- /dev/null +++ b/onlineupdate/source/libmar/src/Makefile.in @@ -0,0 +1,13 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This makefile just builds support for reading archives. + +include $(topsrcdir)/config/rules.mk + +# The intermediate (.ii/.s) files for host and target can have the same name... +# disable parallel builds +.NOTPARALLEL: diff --git a/onlineupdate/source/libmar/src/mar_create.c b/onlineupdate/source/libmar/src/mar_create.c new file mode 100644 index 000000000..599e0a2a7 --- /dev/null +++ b/onlineupdate/source/libmar/src/mar_create.c @@ -0,0 +1,398 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <onlineupdate/mar_private.h> +#include <onlineupdate/mar_cmdline.h> +#include <onlineupdate/mar.h> + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <netinet/in.h> +#include <unistd.h> +#endif + +struct MarItemStack { + void *head; + uint32_t size_used; + uint32_t size_allocated; + uint32_t last_offset; +}; + +/** + * Push a new item onto the stack of items. The stack is a single block + * of memory. + */ +static int mar_push(struct MarItemStack *stack, uint32_t length, uint32_t flags, + const char *name) { + int namelen; + uint32_t n_offset, n_length, n_flags; + uint32_t size; + char *data; + + namelen = strlen(name); + size = MAR_ITEM_SIZE(namelen); + + if (stack->size_allocated - stack->size_used < size) { + /* increase size of stack */ + size_t size_needed = ROUND_UP(stack->size_used + size, BLOCKSIZE); + stack->head = realloc(stack->head, size_needed); + if (!stack->head) + return -1; + stack->size_allocated = size_needed; + } + + data = (((char *) stack->head) + stack->size_used); + + n_offset = htonl(stack->last_offset); + n_length = htonl(length); + n_flags = htonl(flags); + + memcpy(data, &n_offset, sizeof(n_offset)); + data += sizeof(n_offset); + + memcpy(data, &n_length, sizeof(n_length)); + data += sizeof(n_length); + + memcpy(data, &n_flags, sizeof(n_flags)); + data += sizeof(n_flags); + + memcpy(data, name, namelen + 1); + + stack->size_used += size; + stack->last_offset += length; + return 0; +} + +static int mar_concat_file(FILE *fp, const char *path) { + FILE *in; + char buf[BLOCKSIZE]; + size_t len; + int rv = 0; + + in = fopen(path, "rb"); + if (!in) { + fprintf(stderr, "ERROR: could not open file in mar_concat_file()\n"); + perror(path); + return -1; + } + + while ((len = fread(buf, 1, BLOCKSIZE, in)) > 0) { + if (fwrite(buf, len, 1, fp) != 1) { + rv = -1; + break; + } + } + + fclose(in); + return rv; +} + +/** + * Writes out the product information block to the specified file. + * + * @param fp The opened MAR file being created. + * @param stack A pointer to the MAR item stack being used to create + * the MAR + * @param infoBlock The product info block to store in the file. + * @return 0 on success. +*/ +static int +mar_concat_product_info_block(FILE *fp, + struct MarItemStack *stack, + struct ProductInformationBlock *infoBlock) +{ + char buf[PIB_MAX_MAR_CHANNEL_ID_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE]; + uint32_t additionalBlockID = 1, infoBlockSize, unused; + if (!fp || !infoBlock || + !infoBlock->MARChannelID || + !infoBlock->productVersion) { + return -1; + } + + /* The MAR channel name must be < 64 bytes per the spec */ + if (strlen(infoBlock->MARChannelID) > PIB_MAX_MAR_CHANNEL_ID_SIZE) { + return -1; + } + + /* The product version must be < 32 bytes per the spec */ + if (strlen(infoBlock->productVersion) > PIB_MAX_PRODUCT_VERSION_SIZE) { + return -1; + } + + /* Although we don't need the product information block size to include the + maximum MAR channel name and product version, we allocate the maximum + amount to make it easier to modify the MAR file for repurposing MAR files + to different MAR channels. + 2 is for the NULL terminators. */ + infoBlockSize = sizeof(infoBlockSize) + + sizeof(additionalBlockID) + + PIB_MAX_MAR_CHANNEL_ID_SIZE + + PIB_MAX_PRODUCT_VERSION_SIZE + 2; + if (stack) { + stack->last_offset += infoBlockSize; + } + + /* Write out the product info block size */ + infoBlockSize = htonl(infoBlockSize); + if (fwrite(&infoBlockSize, + sizeof(infoBlockSize), 1, fp) != 1) { + return -1; + } + infoBlockSize = ntohl(infoBlockSize); + + /* Write out the product info block ID */ + additionalBlockID = htonl(additionalBlockID); + if (fwrite(&additionalBlockID, + sizeof(additionalBlockID), 1, fp) != 1) { + return -1; + } + additionalBlockID = ntohl(additionalBlockID); + + /* Write out the channel name and NULL terminator */ + if (fwrite(infoBlock->MARChannelID, + strlen(infoBlock->MARChannelID) + 1, 1, fp) != 1) { + return -1; + } + + /* Write out the product version string and NULL terminator */ + if (fwrite(infoBlock->productVersion, + strlen(infoBlock->productVersion) + 1, 1, fp) != 1) { + return -1; + } + + /* Write out the rest of the block that is unused */ + unused = infoBlockSize - (sizeof(infoBlockSize) + + sizeof(additionalBlockID) + + strlen(infoBlock->MARChannelID) + + strlen(infoBlock->productVersion) + 2); + memset(buf, 0, sizeof(buf)); + if (fwrite(buf, unused, 1, fp) != 1) { + return -1; + } + return 0; +} + +/** + * Refreshes the product information block with the new information. + * The input MAR must not be signed or the function call will fail. + * + * @param path The path to the MAR file whose product info block + * should be refreshed. + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure +*/ +int +refresh_product_info_block(const char *path, + struct ProductInformationBlock *infoBlock) +{ + FILE *fp ; + int rv; + uint32_t numSignatures, additionalBlockSize, additionalBlockID, + offsetAdditionalBlocks, numAdditionalBlocks, i; + int additionalBlocks, hasSignatureBlock; + + rv = get_mar_file_info(path, + &hasSignatureBlock, + &numSignatures, + &additionalBlocks, + &offsetAdditionalBlocks, + &numAdditionalBlocks); + if (rv) { + fprintf(stderr, "ERROR: Could not obtain MAR information.\n"); + return -1; + } + + if (hasSignatureBlock && numSignatures) { + fprintf(stderr, "ERROR: Cannot refresh a signed MAR\n"); + return -1; + } + + fp = fopen(path, "r+b"); + if (!fp) { + fprintf(stderr, "ERROR: could not open target file: %s\n", path); + return -1; + } + + if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET)) { + fprintf(stderr, "ERROR: could not seek to additional blocks\n"); + fclose(fp); + return -1; + } + + for (i = 0; i < numAdditionalBlocks; ++i) { + /* Get the position of the start of this block */ + int64_t oldPos = ftello(fp); + + /* Read the additional block size */ + if (fread(&additionalBlockSize, + sizeof(additionalBlockSize), + 1, fp) != 1) { + fclose(fp); + return -1; + } + additionalBlockSize = ntohl(additionalBlockSize); + + /* Read the additional block ID */ + if (fread(&additionalBlockID, + sizeof(additionalBlockID), + 1, fp) != 1) { + fclose(fp); + return -1; + } + additionalBlockID = ntohl(additionalBlockID); + + if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) { + if (fseeko(fp, oldPos, SEEK_SET)) { + fprintf(stderr, "Could not seek back to Product Information Block\n"); + fclose(fp); + return -1; + } + + if (mar_concat_product_info_block(fp, NULL, infoBlock)) { + fprintf(stderr, "Could not concat Product Information Block\n"); + fclose(fp); + return -1; + } + + fclose(fp); + return 0; + } else { + /* This is not the additional block you're looking for. Move along. */ + if (fseek(fp, additionalBlockSize, SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not seek past current block.\n"); + fclose(fp); + return -1; + } + } + } + + /* If we had a product info block we would have already returned */ + fclose(fp); + fprintf(stderr, "ERROR: Could not refresh because block does not exist\n"); + return -1; +} + +/** + * Create a MAR file from a set of files. + * @param dest The path to the file to create. This path must be + * compatible with fopen. + * @param numfiles The number of files to store in the archive. + * @param files The list of null-terminated file paths. Each file + * path must be compatible with fopen. + * @param infoBlock The information to store in the product information block. + * @return A non-zero value if an error occurs. + */ +int mar_create(const char *dest, int + num_files, char **files, + struct ProductInformationBlock *infoBlock) { + struct MarItemStack stack; + uint32_t offset_to_index = 0, size_of_index, + numSignatures, numAdditionalSections; + uint64_t sizeOfEntireMAR = 0; + struct stat st; + FILE *fp; + int i, rv = -1; + + memset(&stack, 0, sizeof(stack)); + + fp = fopen(dest, "wb"); + if (!fp) { + fprintf(stderr, "ERROR: could not create target file: %s\n", dest); + return -1; + } + + if (fwrite(MAR_ID, MAR_ID_SIZE, 1, fp) != 1) + goto failure; + if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1) + goto failure; + + stack.last_offset = MAR_ID_SIZE + + sizeof(offset_to_index) + + sizeof(numSignatures) + + sizeof(numAdditionalSections) + + sizeof(sizeOfEntireMAR); + + /* We will circle back on this at the end of the MAR creation to fill it */ + if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) { + goto failure; + } + + /* Write out the number of signatures, for now only at most 1 is supported */ + numSignatures = 0; + if (fwrite(&numSignatures, sizeof(numSignatures), 1, fp) != 1) { + goto failure; + } + + /* Write out the number of additional sections, for now just 1 + for the product info block */ + numAdditionalSections = htonl(1); + if (fwrite(&numAdditionalSections, + sizeof(numAdditionalSections), 1, fp) != 1) { + goto failure; + } + numAdditionalSections = ntohl(numAdditionalSections); + + if (mar_concat_product_info_block(fp, &stack, infoBlock)) { + goto failure; + } + + for (i = 0; i < num_files; ++i) { + if (stat(files[i], &st)) { + fprintf(stderr, "ERROR: file not found: %s\n", files[i]); + goto failure; + } + + if (mar_push(&stack, st.st_size, st.st_mode & 0777, files[i])) + goto failure; + + /* concatenate input file to archive */ + if (mar_concat_file(fp, files[i])) + goto failure; + } + + /* write out the index (prefixed with length of index) */ + size_of_index = htonl(stack.size_used); + if (fwrite(&size_of_index, sizeof(size_of_index), 1, fp) != 1) + goto failure; + if (fwrite(stack.head, stack.size_used, 1, fp) != 1) + goto failure; + + /* To protect against invalid MAR files, we assume that the MAR file + size is less than or equal to MAX_SIZE_OF_MAR_FILE. */ + if (ftell(fp) > MAX_SIZE_OF_MAR_FILE) { + goto failure; + } + + /* write out offset to index file in network byte order */ + offset_to_index = htonl(stack.last_offset); + if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) + goto failure; + if (fwrite(&offset_to_index, sizeof(offset_to_index), 1, fp) != 1) + goto failure; + offset_to_index = ntohl(stack.last_offset); + + sizeOfEntireMAR = ((uint64_t)stack.last_offset) + + stack.size_used + + sizeof(size_of_index); + sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR); + if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) + goto failure; + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + + rv = 0; +failure: + if (stack.head) + free(stack.head); + fclose(fp); + if (rv) + remove(dest); + return rv; +} diff --git a/onlineupdate/source/libmar/src/mar_extract.c b/onlineupdate/source/libmar/src/mar_extract.c new file mode 100644 index 000000000..11e570242 --- /dev/null +++ b/onlineupdate/source/libmar/src/mar_extract.c @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <onlineupdate/mar_private.h> +#include <onlineupdate/mar.h> + +#ifdef _WIN32 +#include <io.h> +#include <direct.h> +#endif + +/* Ensure that the directory containing this file exists */ +static int mar_ensure_parent_dir(const char *path) +{ + char *slash = strrchr(path, '/'); + if (slash) + { + *slash = '\0'; + mar_ensure_parent_dir(path); +#ifdef _WIN32 + _mkdir(path); +#else + mkdir(path, 0755); +#endif + *slash = '/'; + } + return 0; +} + +static int mar_test_callback(MarFile *mar, const MarItem *item, void *unused) { + FILE *fp; + char buf[BLOCKSIZE]; + int fd, len, offset = 0; + + (void) unused; // avoid warnings + + if (mar_ensure_parent_dir(item->name)) + return -1; + +#ifdef _WIN32 + fd = _open(item->name, _O_BINARY|_O_CREAT|_O_TRUNC|_O_WRONLY, item->flags); +#else + fd = creat(item->name, item->flags); +#endif + if (fd == -1) { + fprintf(stderr, "ERROR: could not create file in mar_test_callback()\n"); + perror(item->name); + return -1; + } + + fp = fdopen(fd, "wb"); + if (!fp) + return -1; + + while ((len = mar_read(mar, item, offset, buf, sizeof(buf))) > 0) { + if (fwrite(buf, len, 1, fp) != 1) + break; + offset += len; + } + + fclose(fp); + return len == 0 ? 0 : -1; +} + +int mar_extract(const char *path) { + MarFile *mar; + int rv; + + mar = mar_open(path); + if (!mar) + return -1; + + rv = mar_enum_items(mar, mar_test_callback, NULL); + + mar_close(mar); + return rv; +} diff --git a/onlineupdate/source/libmar/src/mar_read.c b/onlineupdate/source/libmar/src/mar_read.c new file mode 100644 index 000000000..2a6238ca2 --- /dev/null +++ b/onlineupdate/source/libmar/src/mar_read.c @@ -0,0 +1,572 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <onlineupdate/mar_private.h> +#include <onlineupdate/mar.h> + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <netinet/in.h> +#endif + + +/* this is the same hash algorithm used by nsZipArchive.cpp */ +static uint32_t mar_hash_name(const char *name) { + uint32_t val = 0; + unsigned char* c; + + for (c = (unsigned char *) name; *c; ++c) + val = val*37 + *c; + + return val % TABLESIZE; +} + +static int mar_insert_item(MarFile *mar, const char *name, int namelen, + uint32_t offset, uint32_t length, uint32_t flags) { + MarItem *item, *root; + uint32_t hash; + + item = (MarItem *) malloc(sizeof(MarItem) + namelen); + if (!item) + return -1; + item->next = NULL; + item->offset = offset; + item->length = length; + item->flags = flags; + memcpy(item->name, name, namelen + 1); + + hash = mar_hash_name(name); + + root = mar->item_table[hash]; + if (!root) { + mar->item_table[hash] = item; + } else { + /* append item */ + while (root->next) + root = root->next; + root->next = item; + } + return 0; +} + +static int mar_consume_index(MarFile *mar, char **buf, const char *buf_end) { + /* + * Each item has the following structure: + * uint32_t offset (network byte order) + * uint32_t length (network byte order) + * uint32_t flags (network byte order) + * char name[N] (where N >= 1) + * char null_byte; + */ + uint32_t offset; + uint32_t length; + uint32_t flags; + const char *name; + int namelen; + + if ((buf_end - *buf) < (int)(3*sizeof(uint32_t) + 2)) + return -1; + + memcpy(&offset, *buf, sizeof(offset)); + *buf += sizeof(offset); + + memcpy(&length, *buf, sizeof(length)); + *buf += sizeof(length); + + memcpy(&flags, *buf, sizeof(flags)); + *buf += sizeof(flags); + + offset = ntohl(offset); + length = ntohl(length); + flags = ntohl(flags); + + name = *buf; + /* find namelen; must take care not to read beyond buf_end */ + while (**buf) { + if (*buf == buf_end) + return -1; + ++(*buf); + } + namelen = (*buf - name); + /* consume null byte */ + if (*buf == buf_end) + return -1; + ++(*buf); + + return mar_insert_item(mar, name, namelen, offset, length, flags); +} + +static int mar_read_index(MarFile *mar) { + char id[MAR_ID_SIZE], *buf, *bufptr, *bufend; + uint32_t offset_to_index, size_of_index; + + /* verify MAR ID */ + if (fread(id, MAR_ID_SIZE, 1, mar->fp) != 1) + return -1; + if (memcmp(id, MAR_ID, MAR_ID_SIZE) != 0) + return -1; + + if (fread(&offset_to_index, sizeof(uint32_t), 1, mar->fp) != 1) + return -1; + offset_to_index = ntohl(offset_to_index); + + if (fseek(mar->fp, offset_to_index, SEEK_SET)) + return -1; + if (fread(&size_of_index, sizeof(uint32_t), 1, mar->fp) != 1) + return -1; + size_of_index = ntohl(size_of_index); + + buf = (char *) malloc(size_of_index); + if (!buf) + return -1; + if (fread(buf, size_of_index, 1, mar->fp) != 1) { + free(buf); + return -1; + } + + bufptr = buf; + bufend = buf + size_of_index; + while (bufptr < bufend && mar_consume_index(mar, &bufptr, bufend) == 0); + + free(buf); + return (bufptr == bufend) ? 0 : -1; +} + +/** + * Internal shared code for mar_open and mar_wopen. + * On failure, will fclose(fp). + */ +static MarFile *mar_fpopen(FILE *fp) +{ + MarFile *mar; + + mar = (MarFile *) malloc(sizeof(*mar)); + if (!mar) { + fclose(fp); + return NULL; + } + + mar->fp = fp; + memset(mar->item_table, 0, sizeof(mar->item_table)); + if (mar_read_index(mar)) { + mar_close(mar); + return NULL; + } + + return mar; +} + +MarFile *mar_open(const char *path) { + FILE *fp; + + fp = fopen(path, "rb"); + if (!fp) { + fprintf(stderr, "ERROR: could not open file in mar_open()\n"); + perror(path); + return NULL; + } + + return mar_fpopen(fp); +} + +#ifdef _WIN32 +MarFile *mar_wopen(const wchar_t *path) { + FILE *fp; + + _wfopen_s(&fp, path, L"rb"); + if (!fp) { + fprintf(stderr, "ERROR: could not open file in mar_wopen()\n"); + _wperror(path); + return NULL; + } + + return mar_fpopen(fp); +} +#endif + +void mar_close(MarFile *mar) { + MarItem *item; + int i; + + fclose(mar->fp); + + for (i = 0; i < TABLESIZE; ++i) { + item = mar->item_table[i]; + while (item) { + MarItem *temp = item; + item = item->next; + free(temp); + } + } + + free(mar); +} + +/** + * Determines the MAR file information. + * + * @param fp An opened MAR file in read mode. + * @param hasSignatureBlock Optional out parameter specifying if the MAR + * file has a signature block or not. + * @param numSignatures Optional out parameter for storing the number + * of signatures in the MAR file. + * @param hasAdditionalBlocks Optional out parameter specifying if the MAR + * file has additional blocks or not. + * @param offsetAdditionalBlocks Optional out parameter for the offset to the + * first additional block. Value is only valid if + * hasAdditionalBlocks is not equal to 0. + * @param numAdditionalBlocks Optional out parameter for the number of + * additional blocks. Value is only valid if + * hasAdditionalBlocks is not equal to 0. + * @return 0 on success and non-zero on failure. + */ +int get_mar_file_info_fp(FILE *fp, + int *hasSignatureBlock, + uint32_t *numSignatures, + int *hasAdditionalBlocks, + uint32_t *offsetAdditionalBlocks, + uint32_t *numAdditionalBlocks) +{ + uint32_t offsetToIndex, offsetToContent, signatureCount, signatureLen, i; + + /* One of hasSignatureBlock or hasAdditionalBlocks must be non NULL */ + if (!hasSignatureBlock && !hasAdditionalBlocks) { + return -1; + } + + + /* Skip to the start of the offset index */ + if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) { + return -1; + } + + /* Read the offset to the index. */ + if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fp) != 1) { + return -1; + } + offsetToIndex = ntohl(offsetToIndex); + + if (numSignatures) { + /* Skip past the MAR file size field */ + if (fseek(fp, sizeof(uint64_t), SEEK_CUR)) { + return -1; + } + + /* Read the number of signatures field */ + if (fread(numSignatures, sizeof(*numSignatures), 1, fp) != 1) { + return -1; + } + *numSignatures = ntohl(*numSignatures); + } + + /* Skip to the first index entry past the index size field + We do it in 2 calls because offsetToIndex + sizeof(uint32_t) + could overflow in theory. */ + if (fseek(fp, offsetToIndex, SEEK_SET)) { + return -1; + } + + if (fseek(fp, sizeof(uint32_t), SEEK_CUR)) { + return -1; + } + + /* Read the first offset to content field. */ + if (fread(&offsetToContent, sizeof(offsetToContent), 1, fp) != 1) { + return -1; + } + offsetToContent = ntohl(offsetToContent); + + /* Check if we have a new or old MAR file */ + if (hasSignatureBlock) { + if (offsetToContent == MAR_ID_SIZE + sizeof(uint32_t)) { + *hasSignatureBlock = 0; + } else { + *hasSignatureBlock = 1; + } + } + + /* If the caller doesn't care about the product info block + value, then just return */ + if (!hasAdditionalBlocks) { + return 0; + } + + /* Skip to the start of the signature block */ + if (fseeko(fp, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) { + return -1; + } + + /* Get the number of signatures */ + if (fread(&signatureCount, sizeof(signatureCount), 1, fp) != 1) { + return -1; + } + signatureCount = ntohl(signatureCount); + + /* Check that we have less than the max amount of signatures so we don't + waste too much of either updater's or signmar's time. */ + if (signatureCount > MAX_SIGNATURES) { + return -1; + } + + /* Skip past the whole signature block */ + for (i = 0; i < signatureCount; i++) { + /* Skip past the signature algorithm ID */ + if (fseek(fp, sizeof(uint32_t), SEEK_CUR)) { + return -1; + } + + /* Read the signature length and skip past the signature */ + if (fread(&signatureLen, sizeof(uint32_t), 1, fp) != 1) { + return -1; + } + signatureLen = ntohl(signatureLen); + if (fseek(fp, signatureLen, SEEK_CUR)) { + return -1; + } + } + + if (ftell(fp) == (long)offsetToContent) { + *hasAdditionalBlocks = 0; + } else { + if (numAdditionalBlocks) { + /* We have an additional block, so read in the number of additional blocks + and set the offset. */ + *hasAdditionalBlocks = 1; + if (fread(numAdditionalBlocks, sizeof(uint32_t), 1, fp) != 1) { + return -1; + } + *numAdditionalBlocks = ntohl(*numAdditionalBlocks); + if (offsetAdditionalBlocks) { + *offsetAdditionalBlocks = ftell(fp); + } + } else if (offsetAdditionalBlocks) { + /* numAdditionalBlocks is not specified but offsetAdditionalBlocks + is, so fill it! */ + *offsetAdditionalBlocks = ftell(fp) + sizeof(uint32_t); + } + } + + return 0; +} + +/** + * Reads the product info block from the MAR file's additional block section. + * The caller is responsible for freeing the fields in infoBlock + * if the return is successful. + * + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure +*/ +int +read_product_info_block(char *path, + struct ProductInformationBlock *infoBlock) +{ + int rv; + MarFile mar; + mar.fp = fopen(path, "rb"); + if (!mar.fp) { + fprintf(stderr, "ERROR: could not open file in read_product_info_block()\n"); + perror(path); + return -1; + } + rv = mar_read_product_info_block(&mar, infoBlock); + fclose(mar.fp); + return rv; +} + +/** + * Reads the product info block from the MAR file's additional block section. + * The caller is responsible for freeing the fields in infoBlock + * if the return is successful. + * + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure +*/ +int +mar_read_product_info_block(MarFile *mar, + struct ProductInformationBlock *infoBlock) +{ + uint32_t i, offsetAdditionalBlocks, numAdditionalBlocks, + additionalBlockSize, additionalBlockID; + int hasAdditionalBlocks; + + /* The buffer size is 97 bytes because the MAR channel name < 64 bytes, and + product version < 32 bytes + 3 NULL terminator bytes. */ + char buf[97] = { '\0' }; + int ret = get_mar_file_info_fp(mar->fp, NULL, NULL, + &hasAdditionalBlocks, + &offsetAdditionalBlocks, + &numAdditionalBlocks); + if (ret) + return ret; + for (i = 0; i < numAdditionalBlocks; ++i) { + /* Read the additional block size */ + if (fread(&additionalBlockSize, + sizeof(additionalBlockSize), + 1, mar->fp) != 1) { + return -1; + } + additionalBlockSize = ntohl(additionalBlockSize) - + sizeof(additionalBlockSize) - + sizeof(additionalBlockID); + + /* Read the additional block ID */ + if (fread(&additionalBlockID, + sizeof(additionalBlockID), + 1, mar->fp) != 1) { + return -1; + } + additionalBlockID = ntohl(additionalBlockID); + + if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) { + const char *location; + int len; + + /* This block must be at most 104 bytes. + MAR channel name < 64 bytes, and product version < 32 bytes + 3 NULL + terminator bytes. We only check for 96 though because we remove 8 + bytes above from the additionalBlockSize: We subtract + sizeof(additionalBlockSize) and sizeof(additionalBlockID) */ + if (additionalBlockSize > 96) { + return -1; + } + + if (fread(buf, additionalBlockSize, 1, mar->fp) != 1) { + return -1; + } + + /* Extract the MAR channel name from the buffer. For now we + point to the stack allocated buffer but we strdup this + if we are within bounds of each field's max length. */ + location = buf; + len = strlen(location); + infoBlock->MARChannelID = location; + location += len + 1; + if (len >= 64) { + infoBlock->MARChannelID = NULL; + return -1; + } + + /* Extract the version from the buffer */ + len = strlen(location); + infoBlock->productVersion = location; + location += len + 1; + if (len >= 32) { + infoBlock->MARChannelID = NULL; + infoBlock->productVersion = NULL; + return -1; + } + infoBlock->MARChannelID = + strdup(infoBlock->MARChannelID); + infoBlock->productVersion = + strdup(infoBlock->productVersion); + return 0; + } else { + /* This is not the additional block you're looking for. Move along. */ + if (fseek(mar->fp, additionalBlockSize, SEEK_CUR)) { + return -1; + } + } + } + + /* If we had a product info block we would have already returned */ + return -1; +} + +const MarItem *mar_find_item(MarFile *mar, const char *name) { + uint32_t hash; + const MarItem *item; + + hash = mar_hash_name(name); + + item = mar->item_table[hash]; + while (item && strcmp(item->name, name) != 0) + item = item->next; + + return item; +} + +int mar_enum_items(MarFile *mar, MarItemCallback callback, void *closure) { + MarItem *item; + int i; + + for (i = 0; i < TABLESIZE; ++i) { + item = mar->item_table[i]; + while (item) { + int rv = callback(mar, item, closure); + if (rv) + return rv; + item = item->next; + } + } + + return 0; +} + +int mar_read(MarFile *mar, const MarItem *item, int offset, char *buf, + int bufsize) { + int nr; + + if (offset == (int) item->length) + return 0; + if (offset > (int) item->length) + return -1; + + nr = item->length - offset; + if (nr > bufsize) + nr = bufsize; + + if (fseek(mar->fp, item->offset + offset, SEEK_SET)) + return -1; + + return fread(buf, 1, nr, mar->fp); +} + +/** + * Determines the MAR file information. + * + * @param path The path of the MAR file to check. + * @param hasSignatureBlock Optional out parameter specifying if the MAR + * file has a signature block or not. + * @param numSignatures Optional out parameter for storing the number + * of signatures in the MAR file. + * @param hasAdditionalBlocks Optional out parameter specifying if the MAR + * file has additional blocks or not. + * @param offsetAdditionalBlocks Optional out parameter for the offset to the + * first additional block. Value is only valid if + * hasAdditionalBlocks is not equal to 0. + * @param numAdditionalBlocks Optional out parameter for the number of + * additional blocks. Value is only valid if + * has_additional_blocks is not equal to 0. + * @return 0 on success and non-zero on failure. + */ +int get_mar_file_info(const char *path, + int *hasSignatureBlock, + uint32_t *numSignatures, + int *hasAdditionalBlocks, + uint32_t *offsetAdditionalBlocks, + uint32_t *numAdditionalBlocks) +{ + int rv; + FILE *fp = fopen(path, "rb"); + if (!fp) { + fprintf(stderr, "ERROR: could not open file in get_mar_file_info()\n"); + perror(path); + return -1; + } + + rv = get_mar_file_info_fp(fp, hasSignatureBlock, + numSignatures, hasAdditionalBlocks, + offsetAdditionalBlocks, numAdditionalBlocks); + + fclose(fp); + return rv; +} diff --git a/onlineupdate/source/libmar/tool/Makefile.in b/onlineupdate/source/libmar/tool/Makefile.in new file mode 100644 index 000000000..20a7c475a --- /dev/null +++ b/onlineupdate/source/libmar/tool/Makefile.in @@ -0,0 +1,23 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# The mar executable is output into dist/host/bin since it is something that +# would only be used by our build system and should not itself be included in a +# Mozilla distribution. + +HOST_CFLAGS += \ + -DNO_SIGN_VERIFY \ + $(DEFINES) \ + $(NULL) + +include $(topsrcdir)/config/rules.mk + +ifdef CROSS_COMPILE +ifdef HOST_NSPR_MDCPUCFG +HOST_CFLAGS += -DMDCPUCFG=$(HOST_NSPR_MDCPUCFG) +CFLAGS += -DMDCPUCFG=$(HOST_NSPR_MDCPUCFG) +endif +endif diff --git a/onlineupdate/source/libmar/tool/mar.c b/onlineupdate/source/libmar/tool/mar.c new file mode 100644 index 000000000..3db3bb86e --- /dev/null +++ b/onlineupdate/source/libmar/tool/mar.c @@ -0,0 +1,463 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <onlineupdate/mar.h> +#include <onlineupdate/mar_cmdline.h> + +#ifdef _WIN32 +#include <windows.h> +#include <direct.h> +#define chdir _chdir +#else +#include <unistd.h> +#include <errno.h> +#endif + +#ifndef APP_VERSION +#error "Missing APP_VERSION" +#endif + +#define MAR_CHANNEL_ID "LOOnlineUpdater" /* Dummy value; replace or + remove in the future */ + +#if !defined(NO_SIGN_VERIFY) && (!defined(_WIN32) || defined(MAR_NSS)) +#include "cert.h" +#include "pk11pub.h" +int NSSInitCryptoContext(const char *NSSConfigDir); +#endif + +int mar_repackage_and_sign(const char *NSSConfigDir, + const char * const *certNames, + uint32_t certCount, + const char *src, + const char * dest); + +static void print_version(void) { + printf("Version: %s\n", APP_VERSION); + printf("Default Channel ID: %s\n", MAR_CHANNEL_ID); +} + +static void print_usage(void) { + printf("usage:\n"); + printf("Create a MAR file:\n"); + printf(" mar [-H MARChannelID] [-V ProductVersion] [-C workingDir] " + "-c archive.mar [files...]\n"); + printf(" mar [-H MARChannelID] [-V ProductVersion] [-C workingDir] " + "-c archive.mar -f input_file.txt\n"); + + printf("Extract a MAR file:\n"); + printf(" mar [-C workingDir] -x archive.mar\n"); +#ifndef NO_SIGN_VERIFY + printf("Sign a MAR file:\n"); + printf(" mar [-C workingDir] -d NSSConfigDir -n certname -s " + "archive.mar out_signed_archive.mar\n"); + + printf("Strip a MAR signature:\n"); + printf(" mar [-C workingDir] -r " + "signed_input_archive.mar output_archive.mar\n"); + + printf("Extract a MAR signature:\n"); + printf(" mar [-C workingDir] -n(i) -X " + "signed_input_archive.mar base_64_encoded_signature_file\n"); + + printf("Import a MAR signature:\n"); + printf(" mar [-C workingDir] -n(i) -I " + "signed_input_archive.mar base_64_encoded_signature_file " + "changed_signed_output.mar\n"); + printf("(i) is the index of the certificate to extract\n"); +#if defined(MACOSX) || (defined(_WIN32) && !defined(MAR_NSS)) + printf("Verify a MAR file:\n"); + printf(" mar [-C workingDir] -D DERFilePath -v signed_archive.mar\n"); + printf("At most %d signature certificate DER files are specified by " + "-D0 DERFilePath1 -D1 DERFilePath2, ...\n", MAX_SIGNATURES); +#else + printf("Verify a MAR file:\n"); + printf(" mar [-C workingDir] -d NSSConfigDir -n certname " + "-v signed_archive.mar\n"); + printf("At most %d signature certificate names are specified by " + "-n0 certName -n1 certName2, ...\n", MAX_SIGNATURES); +#endif + printf("At most %d verification certificate names are specified by " + "-n0 certName -n1 certName2, ...\n", MAX_SIGNATURES); +#endif + printf("Print information on a MAR file:\n"); + printf(" mar -t archive.mar\n"); + + printf("Print detailed information on a MAR file including signatures:\n"); + printf(" mar -T archive.mar\n"); + + printf("Refresh the product information block of a MAR file:\n"); + printf(" mar [-H MARChannelID] [-V ProductVersion] [-C workingDir] " + "-i unsigned_archive_to_refresh.mar\n"); + + printf("Print executable version:\n"); + printf(" mar --version\n"); + printf("This program does not handle unicode file paths properly\n"); +} + +static int mar_test_callback(MarFile *mar, + const MarItem *item, + void *unused) { + (void) mar; (void) unused; // avoid warnings + + printf("%u\t0%o\t%s\n", item->length, item->flags, item->name); + return 0; +} + +static int mar_test(const char *path) { + MarFile *mar; + + mar = mar_open(path); + if (!mar) + return -1; + + printf("SIZE\tMODE\tNAME\n"); + mar_enum_items(mar, mar_test_callback, NULL); + + mar_close(mar); + return 0; +} + +int main(int argc, char **argv) { + char *NSSConfigDir = NULL; + const char *certNames[MAX_SIGNATURES]; + char *MARChannelID = MAR_CHANNEL_ID; + char *productVersion = APP_VERSION; +#ifndef NO_SIGN_VERIFY + uint32_t k; +#endif + int rv = -1; + uint32_t certCount = 0; + int32_t sigIndex = -1; + +#if !defined(NO_SIGN_VERIFY) + uint32_t fileSizes[MAX_SIGNATURES]; + const uint8_t* certBuffers[MAX_SIGNATURES]; + char* DERFilePaths[MAX_SIGNATURES]; +#if (!defined(_WIN32) && !defined(MACOSX)) || defined(MAR_NSS) + CERTCertificate* certs[MAX_SIGNATURES]; +#endif +#endif + + memset((void*)certNames, 0, sizeof(certNames)); +#if defined(_WIN32) && !defined(MAR_NSS) && !defined(NO_SIGN_VERIFY) + memset((void*)certBuffers, 0, sizeof(certBuffers)); +#endif +#if !defined(NO_SIGN_VERIFY) && ((!defined(MAR_NSS) && defined(_WIN32)) || \ + defined(MACOSX)) + memset(DERFilePaths, 0, sizeof(DERFilePaths)); + memset(fileSizes, 0, sizeof(fileSizes)); +#endif + + if (argc > 1 && 0 == strcmp(argv[1], "--version")) { + print_version(); + return 0; + } + + if (argc < 3) { + print_usage(); + return -1; + } + + while (argc > 0) { + if (argv[1][0] == '-' && (argv[1][1] == 'c' || + argv[1][1] == 't' || argv[1][1] == 'x' || + argv[1][1] == 'v' || argv[1][1] == 's' || + argv[1][1] == 'i' || argv[1][1] == 'T' || + argv[1][1] == 'r' || argv[1][1] == 'X' || + argv[1][1] == 'I')) { + break; + /* -C workingdirectory */ + } else if (argv[1][0] == '-' && argv[1][1] == 'C') { + chdir(argv[2]); + argv += 2; + argc -= 2; + } +#if !defined(NO_SIGN_VERIFY) && ((!defined(MAR_NSS) && defined(_WIN32)) || \ + defined(MACOSX)) + /* -D DERFilePath, also matches -D[index] DERFilePath + We allow an index for verifying to be symmetric + with the import and export command line arguments. */ + else if (argv[1][0] == '-' && + argv[1][1] == 'D' && + (argv[1][2] == (char)('0' + certCount) || argv[1][2] == '\0')) { + if (certCount >= MAX_SIGNATURES) { + print_usage(); + return -1; + } + DERFilePaths[certCount++] = argv[2]; + argv += 2; + argc -= 2; + } +#endif + /* -d NSSConfigdir */ + else if (argv[1][0] == '-' && argv[1][1] == 'd') { + NSSConfigDir = argv[2]; + argv += 2; + argc -= 2; + /* -n certName, also matches -n[index] certName + We allow an index for verifying to be symmetric + with the import and export command line arguments. */ + } else if (argv[1][0] == '-' && + argv[1][1] == 'n' && + (argv[1][2] == (char)('0' + certCount) || + argv[1][2] == '\0' || + !strcmp(argv[2], "-X") || + !strcmp(argv[2], "-I"))) { + if (certCount >= MAX_SIGNATURES) { + print_usage(); + return -1; + } + certNames[certCount++] = argv[2]; + if (strlen(argv[1]) > 2 && + (!strcmp(argv[2], "-X") || !strcmp(argv[2], "-I")) && + argv[1][2] >= '0' && argv[1][2] <= '9') { + sigIndex = argv[1][2] - '0'; + argv++; + argc--; + } else { + argv += 2; + argc -= 2; + } + /* MAR channel ID */ + } else if (argv[1][0] == '-' && argv[1][1] == 'H') { + MARChannelID = argv[2]; + argv += 2; + argc -= 2; + /* Product Version */ + } else if (argv[1][0] == '-' && argv[1][1] == 'V') { + productVersion = argv[2]; + argv += 2; + argc -= 2; + } + else { + print_usage(); + return -1; + } + } + + if (argv[1][0] != '-') { + print_usage(); + return -1; + } + + switch (argv[1][1]) { + case 'c': { + struct ProductInformationBlock infoBlock; + infoBlock.MARChannelID = MARChannelID; + infoBlock.productVersion = productVersion; + if (argv[argc - 2][0] == '-' && argv[argc - 2][1] == 'f') + { + char buf[1000]; + FILE* file; + char** files; + int num_files = 0; + + files = (char **)malloc(sizeof(char*)*10000); + errno = 0; + file = fopen(argv[argc - 1], "r"); + if (!file) + { + printf("%d %s", errno, strerror(errno)); + printf("Could not open file: %s", argv[argc - 1]); + exit(1); + } + + while(fgets(buf, 1000, file) != NULL) + { + int j; + size_t str_len; + for (j=strlen(buf)-1;j>=0 && (buf[j]=='\n' || buf[j]=='\r');j--) + ; + buf[j+1]='\0'; + str_len = strlen(buf) + 1; + files[num_files] = (char*)malloc(sizeof(char)*str_len); + strcpy(files[num_files], buf); + ++num_files; + } + fclose(file); + return mar_create(argv[2], num_files, files, &infoBlock); + } + else + return mar_create(argv[2], argc - 3, argv + 3, &infoBlock); + } + case 'i': { + struct ProductInformationBlock infoBlock; + infoBlock.MARChannelID = MARChannelID; + infoBlock.productVersion = productVersion; + return refresh_product_info_block(argv[2], &infoBlock); + } + case 'T': { + struct ProductInformationBlock infoBlock; + uint32_t numSignatures, numAdditionalBlocks; + int hasSignatureBlock, hasAdditionalBlock; + if (!get_mar_file_info(argv[2], + &hasSignatureBlock, + &numSignatures, + &hasAdditionalBlock, + NULL, &numAdditionalBlocks)) { + if (hasSignatureBlock) { + printf("Signature block found with %d signature%s\n", + numSignatures, + numSignatures != 1 ? "s" : ""); + } + if (hasAdditionalBlock) { + printf("%d additional block%s found:\n", + numAdditionalBlocks, + numAdditionalBlocks != 1 ? "s" : ""); + } + + rv = read_product_info_block(argv[2], &infoBlock); + if (!rv) { + printf(" - Product Information Block:\n"); + printf(" - MAR channel name: %s\n" + " - Product version: %s\n", + infoBlock.MARChannelID, + infoBlock.productVersion); + free((void *)infoBlock.MARChannelID); + free((void *)infoBlock.productVersion); + } + } + printf("\n"); + /* The fall through from 'T' to 't' is intentional */ + } + /* Fall through */ + case 't': + return mar_test(argv[2]); + + /* Extract a MAR file */ + case 'x': + return mar_extract(argv[2]); + +#ifndef NO_SIGN_VERIFY + /* Extract a MAR signature */ + case 'X': + if (sigIndex == -1) { + fprintf(stderr, "ERROR: Signature index was not passed.\n"); + return -1; + } + if (sigIndex >= MAX_SIGNATURES || sigIndex < -1) { + fprintf(stderr, "ERROR: Signature index is out of range: %d.\n", + sigIndex); + return -1; + } + return extract_signature(argv[2], sigIndex, argv[3]); + + /* Import a MAR signature */ + case 'I': + if (sigIndex == -1) { + fprintf(stderr, "ERROR: signature index was not passed.\n"); + return -1; + } + if (sigIndex >= MAX_SIGNATURES || sigIndex < -1) { + fprintf(stderr, "ERROR: Signature index is out of range: %d.\n", + sigIndex); + return -1; + } + if (argc < 5) { + print_usage(); + return -1; + } + return import_signature(argv[2], sigIndex, argv[3], argv[4]); + + case 'v': + if (certCount == 0) { + print_usage(); + return -1; + } + +#if (!defined(_WIN32) && !defined(MACOSX)) || defined(MAR_NSS) + if (!NSSConfigDir || certCount == 0) { + print_usage(); + return -1; + } + + if (NSSInitCryptoContext(NSSConfigDir)) { + fprintf(stderr, "ERROR: Could not initialize crypto library.\n"); + return -1; + } +#endif + + rv = 0; + for (k = 0; k < certCount; ++k) { +#if (defined(_WIN32) || defined(MACOSX)) && !defined(MAR_NSS) + rv = mar_read_entire_file(DERFilePaths[k], MAR_MAX_CERT_SIZE, + &certBuffers[k], &fileSizes[k]); +#else + /* It is somewhat circuitous to look up a CERTCertificate and then pass + * in its DER encoding just so we can later re-create that + * CERTCertificate to extract the public key out of it. However, by doing + * things this way, we maximize the reuse of the mar_verify_signatures + * function and also we keep the control flow as similar as possible + * between programs and operating systems, at least for the functions + * that are critically important to security. + */ + certs[k] = PK11_FindCertFromNickname(certNames[k], NULL); + if (certs[k]) { + certBuffers[k] = certs[k]->derCert.data; + fileSizes[k] = certs[k]->derCert.len; + } else { + rv = -1; + } +#endif + if (rv) { + fprintf(stderr, "ERROR: could not read file %s", DERFilePaths[k]); + break; + } + } + + if (!rv) { + MarFile *mar = mar_open(argv[2]); + if (mar) { + rv = mar_verify_signatures(mar, certBuffers, fileSizes, certCount); + mar_close(mar); + } else { + fprintf(stderr, "ERROR: Could not open MAR file.\n"); + rv = -1; + } + } + for (k = 0; k < certCount; ++k) { +#if (defined(_WIN32) || defined(MACOSX)) && !defined(MAR_NSS) + free((void*)certBuffers[k]); +#else + /* certBuffers[k] is owned by certs[k] so don't free it */ + CERT_DestroyCertificate(certs[k]); +#endif + } + if (rv) { + /* Determine if the source MAR file has the new fields for signing */ + int hasSignatureBlock; + if (get_mar_file_info(argv[2], &hasSignatureBlock, + NULL, NULL, NULL, NULL)) { + fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n"); + } else if (!hasSignatureBlock) { + fprintf(stderr, "ERROR: The MAR file is in the old format so has" + " no signature to verify.\n"); + } + return -1; + } + return 0; + + case 's': + if (!NSSConfigDir || certCount == 0 || argc < 4) { + print_usage(); + return -1; + } + return mar_repackage_and_sign(NSSConfigDir, certNames, certCount, + argv[2], argv[3]); + + case 'r': + return strip_signature_block(argv[2], argv[3]); +#endif /* endif NO_SIGN_VERIFY disabled */ + + default: + print_usage(); + return -1; + } +} diff --git a/onlineupdate/source/libmar/verify/MacVerifyCrypto.cpp b/onlineupdate/source/libmar/verify/MacVerifyCrypto.cpp new file mode 100644 index 000000000..e86fac3c5 --- /dev/null +++ b/onlineupdate/source/libmar/verify/MacVerifyCrypto.cpp @@ -0,0 +1,414 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <CoreFoundation/CoreFoundation.h> +#include <Security/Security.h> +#include <dlfcn.h> + +#include "cryptox.h" + +// We declare the necessary parts of the Security Transforms API here since +// we're building with the 10.6 SDK, which doesn't know about Security +// Transforms. +extern "C" { + const CFStringRef kSecTransformInputAttributeName = CFSTR("INPUT"); + typedef CFTypeRef SecTransformRef; + typedef struct OpaqueSecKeyRef* SecKeyRef; + + typedef SecTransformRef (*SecTransformCreateReadTransformWithReadStreamFunc) + (CFReadStreamRef inputStream); + SecTransformCreateReadTransformWithReadStreamFunc + SecTransformCreateReadTransformWithReadStreamPtr = NULL; + typedef CFTypeRef (*SecTransformExecuteFunc)(SecTransformRef transform, + CFErrorRef* error); + SecTransformExecuteFunc SecTransformExecutePtr = NULL; + typedef SecTransformRef (*SecVerifyTransformCreateFunc)(SecKeyRef key, + CFDataRef signature, + CFErrorRef* error); + SecVerifyTransformCreateFunc SecVerifyTransformCreatePtr = NULL; + typedef Boolean (*SecTransformSetAttributeFunc)(SecTransformRef transform, + CFStringRef key, + CFTypeRef value, + CFErrorRef* error); + SecTransformSetAttributeFunc SecTransformSetAttributePtr = NULL; +} + +#define MAC_OS_X_VERSION_10_7_HEX 0x00001070 + +static int sOnLionOrLater = -1; + +static bool OnLionOrLater() +{ + if (sOnLionOrLater < 0) { + SInt32 major = 0, minor = 0; + + CFURLRef url = + CFURLCreateWithString(kCFAllocatorDefault, + CFSTR("file:///System/Library/CoreServices/SystemVersion.plist"), + NULL); + CFReadStreamRef stream = + CFReadStreamCreateWithFile(kCFAllocatorDefault, url); + CFReadStreamOpen(stream); + CFDictionaryRef sysVersionPlist = (CFDictionaryRef) + CFPropertyListCreateWithStream(kCFAllocatorDefault, + stream, 0, kCFPropertyListImmutable, + NULL, NULL); + CFReadStreamClose(stream); + CFRelease(stream); + CFRelease(url); + + CFStringRef versionString = (CFStringRef) + CFDictionaryGetValue(sysVersionPlist, CFSTR("ProductVersion")); + CFArrayRef versions = + CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, + versionString, CFSTR(".")); + CFIndex count = CFArrayGetCount(versions); + if (count > 0) { + CFStringRef component = (CFStringRef) CFArrayGetValueAtIndex(versions, 0); + major = CFStringGetIntValue(component); + if (count > 1) { + component = (CFStringRef) CFArrayGetValueAtIndex(versions, 1); + minor = CFStringGetIntValue(component); + } + } + CFRelease(sysVersionPlist); + CFRelease(versions); + + if (major < 10) { + sOnLionOrLater = 0; + } else { + int version = 0x1000 + (minor << 4); + sOnLionOrLater = version >= MAC_OS_X_VERSION_10_7_HEX ? 1 : 0; + } + } + + return sOnLionOrLater > 0 ? true : false; +} + +static bool sCssmInitialized = false; +static CSSM_VERSION sCssmVersion = {2, 0}; +static const CSSM_GUID sMozCssmGuid = + { 0x9243121f, 0x5820, 0x4b41, + { 0xa6, 0x52, 0xba, 0xb6, 0x3f, 0x9d, 0x3d, 0x7f }}; +static CSSM_CSP_HANDLE sCspHandle = CSSM_INVALID_HANDLE; + +void* cssmMalloc (CSSM_SIZE aSize, void* aAllocRef) { + (void)aAllocRef; + return malloc(aSize); +} + +void cssmFree (void* aPtr, void* aAllocRef) { + (void)aAllocRef; + free(aPtr); + return; +} + +void* cssmRealloc (void* aPtr, CSSM_SIZE aSize, void* aAllocRef) { + (void)aAllocRef; + return realloc(aPtr, aSize); +} + +void* cssmCalloc (uint32 aNum, CSSM_SIZE aSize, void* aAllocRef) { + (void)aAllocRef; + return calloc(aNum, aSize); +} + +static CSSM_API_MEMORY_FUNCS cssmMemFuncs = { + &cssmMalloc, + &cssmFree, + &cssmRealloc, + &cssmCalloc, + NULL + }; + +CryptoX_Result +CryptoMac_InitCryptoProvider() +{ + if (!OnLionOrLater()) { + return CryptoX_Success; + } + + if (!SecTransformCreateReadTransformWithReadStreamPtr) { + SecTransformCreateReadTransformWithReadStreamPtr = + (SecTransformCreateReadTransformWithReadStreamFunc) + dlsym(RTLD_DEFAULT, "SecTransformCreateReadTransformWithReadStream"); + } + if (!SecTransformExecutePtr) { + SecTransformExecutePtr = (SecTransformExecuteFunc) + dlsym(RTLD_DEFAULT, "SecTransformExecute"); + } + if (!SecVerifyTransformCreatePtr) { + SecVerifyTransformCreatePtr = (SecVerifyTransformCreateFunc) + dlsym(RTLD_DEFAULT, "SecVerifyTransformCreate"); + } + if (!SecTransformSetAttributePtr) { + SecTransformSetAttributePtr = (SecTransformSetAttributeFunc) + dlsym(RTLD_DEFAULT, "SecTransformSetAttribute"); + } + if (!SecTransformCreateReadTransformWithReadStreamPtr || + !SecTransformExecutePtr || + !SecVerifyTransformCreatePtr || + !SecTransformSetAttributePtr) { + return CryptoX_Error; + } + return CryptoX_Success; +} + +CryptoX_Result +CryptoMac_VerifyBegin(CryptoX_SignatureHandle* aInputData) +{ + if (!aInputData) { + return CryptoX_Error; + } + + void* inputData = CFDataCreateMutable(kCFAllocatorDefault, 0); + if (!inputData) { + return CryptoX_Error; + } + + if (!OnLionOrLater()) { + CSSM_DATA_PTR cssmData = (CSSM_DATA_PTR)malloc(sizeof(CSSM_DATA)); + if (!cssmData) { + CFRelease(inputData); + return CryptoX_Error; + } + cssmData->Data = (uint8*)inputData; + cssmData->Length = 0; + *aInputData = cssmData; + return CryptoX_Success; + } + + *aInputData = inputData; + return CryptoX_Success; +} + +CryptoX_Result +CryptoMac_VerifyUpdate(CryptoX_SignatureHandle* aInputData, void* aBuf, + unsigned int aLen) +{ + if (aLen == 0) { + return CryptoX_Success; + } + if (!aInputData || !*aInputData) { + return CryptoX_Error; + } + + CFMutableDataRef inputData; + if (!OnLionOrLater()) { + inputData = (CFMutableDataRef)((CSSM_DATA_PTR)*aInputData)->Data; + ((CSSM_DATA_PTR)*aInputData)->Length += aLen; + } else { + inputData = (CFMutableDataRef)*aInputData; + } + + CFDataAppendBytes(inputData, (const uint8*)aBuf, aLen); + return CryptoX_Success; +} + +CryptoX_Result +CryptoMac_LoadPublicKey(const unsigned char* aCertData, + unsigned int aDataSize, + CryptoX_PublicKey* aPublicKey) +{ + if (!aCertData || aDataSize == 0 || !aPublicKey) { + return CryptoX_Error; + } + *aPublicKey = NULL; + + if (!OnLionOrLater()) { + if (!sCspHandle) { + CSSM_RETURN rv; + if (!sCssmInitialized) { + CSSM_PVC_MODE pvcPolicy = CSSM_PVC_NONE; + rv = CSSM_Init(&sCssmVersion, + CSSM_PRIVILEGE_SCOPE_PROCESS, + &sMozCssmGuid, + CSSM_KEY_HIERARCHY_NONE, + &pvcPolicy, + NULL); + if (rv != CSSM_OK) { + return CryptoX_Error; + } + sCssmInitialized = true; + } + + rv = CSSM_ModuleLoad(&gGuidAppleCSP, + CSSM_KEY_HIERARCHY_NONE, + NULL, + NULL); + if (rv != CSSM_OK) { + return CryptoX_Error; + } + + CSSM_CSP_HANDLE cspHandle; + rv = CSSM_ModuleAttach(&gGuidAppleCSP, + &sCssmVersion, + &cssmMemFuncs, + 0, + CSSM_SERVICE_CSP, + 0, + CSSM_KEY_HIERARCHY_NONE, + NULL, + 0, + NULL, + &cspHandle); + if (rv != CSSM_OK) { + return CryptoX_Error; + } + sCspHandle = cspHandle; + } + } + + CFDataRef certData = CFDataCreate(kCFAllocatorDefault, + aCertData, + aDataSize); + if (!certData) { + return CryptoX_Error; + } + + SecCertificateRef cert = SecCertificateCreateWithData(kCFAllocatorDefault, + certData); + CFRelease(certData); + if (!cert) { + return CryptoX_Error; + } + + OSStatus status = SecCertificateCopyPublicKey(cert, + (SecKeyRef*)aPublicKey); + CFRelease(cert); + if (status != 0) { + return CryptoX_Error; + } + + return CryptoX_Success; +} + +CryptoX_Result +CryptoMac_VerifySignature(CryptoX_SignatureHandle* aInputData, + CryptoX_PublicKey* aPublicKey, + const unsigned char* aSignature, + unsigned int aSignatureLen) +{ + if (!aInputData || !*aInputData || !aPublicKey || !*aPublicKey || + !aSignature || aSignatureLen == 0) { + return CryptoX_Error; + } + + if (!OnLionOrLater()) { + if (!sCspHandle) { + return CryptoX_Error; + } + + CSSM_KEY* publicKey; + OSStatus status = SecKeyGetCSSMKey((SecKeyRef)*aPublicKey, + (const CSSM_KEY**)&publicKey); + if (status) { + return CryptoX_Error; + } + + CSSM_CC_HANDLE ccHandle; + if (CSSM_CSP_CreateSignatureContext(sCspHandle, + CSSM_ALGID_SHA1WithRSA, + NULL, + publicKey, + &ccHandle) != CSSM_OK) { + return CryptoX_Error; + } + + CryptoX_Result result = CryptoX_Error; + CSSM_DATA signatureData; + signatureData.Data = (uint8*)aSignature; + signatureData.Length = aSignatureLen; + CSSM_DATA inputData; + inputData.Data = + CFDataGetMutableBytePtr((CFMutableDataRef) + (((CSSM_DATA_PTR)*aInputData)->Data)); + inputData.Length = ((CSSM_DATA_PTR)*aInputData)->Length; + if (CSSM_VerifyData(ccHandle, + &inputData, + 1, + CSSM_ALGID_NONE, + &signatureData) == CSSM_OK) { + result = CryptoX_Success; + } + return result; + } + + CFDataRef signatureData = CFDataCreate(kCFAllocatorDefault, + aSignature, aSignatureLen); + if (!signatureData) { + return CryptoX_Error; + } + + CFErrorRef error; + SecTransformRef verifier = + SecVerifyTransformCreatePtr((SecKeyRef)*aPublicKey, + signatureData, + &error); + if (!verifier || error) { + CFRelease(signatureData); + return CryptoX_Error; + } + + SecTransformSetAttributePtr(verifier, + kSecTransformInputAttributeName, + (CFDataRef)*aInputData, + &error); + if (error) { + CFRelease(signatureData); + CFRelease(verifier); + return CryptoX_Error; + } + + CryptoX_Result result = CryptoX_Error; + CFTypeRef rv = SecTransformExecutePtr(verifier, &error); + if (error) { + CFRelease(signatureData); + CFRelease(verifier); + return CryptoX_Error; + } + + if (CFGetTypeID(rv) == CFBooleanGetTypeID() && + CFBooleanGetValue((CFBooleanRef)rv) == true) { + result = CryptoX_Success; + } + + CFRelease(signatureData); + CFRelease(verifier); + + return result; +} + +void +CryptoMac_FreeSignatureHandle(CryptoX_SignatureHandle* aInputData) +{ + if (!aInputData || !*aInputData) { + return; + } + + CFMutableDataRef inputData = NULL; + if (OnLionOrLater()) { + inputData = (CFMutableDataRef)*aInputData; + } else { + inputData = (CFMutableDataRef)((CSSM_DATA_PTR)*aInputData)->Data; + } + + CFRelease(inputData); + if (!OnLionOrLater()) { + free((CSSM_DATA_PTR)*aInputData); + } +} + +void +CryptoMac_FreePublicKey(CryptoX_PublicKey* aPublicKey) +{ + if (!aPublicKey || !*aPublicKey) { + return; + } + if (!OnLionOrLater() && sCspHandle != CSSM_INVALID_HANDLE) { + CSSM_ModuleDetach(sCspHandle); + sCspHandle = CSSM_INVALID_HANDLE; + } + CFRelease((SecKeyRef)*aPublicKey); +} diff --git a/onlineupdate/source/libmar/verify/cryptox.c b/onlineupdate/source/libmar/verify/cryptox.c new file mode 100644 index 000000000..7cce8bf03 --- /dev/null +++ b/onlineupdate/source/libmar/verify/cryptox.c @@ -0,0 +1,282 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#endif + +#include <stdlib.h> +#include "cryptox.h" + +#ifdef _WIN32 +#pragma warning(push) +#pragma warning(disable: 4204) +#endif + +#if defined(MAR_NSS) + +/** + * Loads the public key for the specified cert name from the NSS store. + * + * @param certData The DER-encoded X509 certificate to extract the key from. + * @param certDataSize The size of certData. + * @param publicKey Out parameter for the public key to use. + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +NSS_LoadPublicKey(const unsigned char *certData, unsigned int certDataSize, + SECKEYPublicKey **publicKey) +{ + CERTCertificate * cert; + SECItem certDataItem = { siBuffer, (unsigned char*) certData, certDataSize }; + + if (!certData || !publicKey) { + return CryptoX_Error; + } + + cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &certDataItem, NULL, + PR_FALSE, PR_TRUE); + /* Get the cert and embedded public key out of the database */ + if (!cert) { + return CryptoX_Error; + } + *publicKey = CERT_ExtractPublicKey(cert); + CERT_DestroyCertificate(cert); + + if (!*publicKey) { + return CryptoX_Error; + } + return CryptoX_Success; +} + +CryptoX_Result +NSS_VerifyBegin(VFYContext **ctx, + SECKEYPublicKey * const *publicKey) +{ + SECStatus status; + if (!ctx || !publicKey || !*publicKey) { + return CryptoX_Error; + } + + /* Check that the key length is large enough for our requirements */ + if ((SECKEY_PublicKeyStrength(*publicKey) * 8) < + XP_MIN_SIGNATURE_LEN_IN_BYTES) { + fprintf(stderr, "ERROR: Key length must be >= %d bytes\n", + XP_MIN_SIGNATURE_LEN_IN_BYTES); + return CryptoX_Error; + } + + *ctx = VFY_CreateContext(*publicKey, NULL, + SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, NULL); + if (*ctx == NULL) { + return CryptoX_Error; + } + + status = VFY_Begin(*ctx); + return SECSuccess == status ? CryptoX_Success : CryptoX_Error; +} + +/** + * Verifies if a verify context matches the passed in signature. + * + * @param ctx The verify context that the signature should match. + * @param signature The signature to match. + * @param signatureLen The length of the signature. + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +NSS_VerifySignature(VFYContext * const *ctx, + const unsigned char *signature, + unsigned int signatureLen) +{ + SECItem signedItem; + SECStatus status; + if (!ctx || !signature || !*ctx) { + return CryptoX_Error; + } + + signedItem.len = signatureLen; + signedItem.data = (unsigned char*)signature; + status = VFY_EndWithSignature(*ctx, &signedItem); + return SECSuccess == status ? CryptoX_Success : CryptoX_Error; +} + +#elif defined(WNT) +/** + * Verifies if a signature + public key matches a hash context. + * + * @param hash The hash context that the signature should match. + * @param pubKey The public key to use on the signature. + * @param signature The signature to check. + * @param signatureLen The length of the signature. + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +CryptoAPI_VerifySignature(HCRYPTHASH *hash, + HCRYPTKEY *pubKey, + const BYTE *signature, + DWORD signatureLen) +{ + DWORD i; + BOOL result; +/* Windows APIs expect the bytes in the signature to be in little-endian + * order, but we write the signature in big-endian order. Other APIs like + * NSS and OpenSSL expect big-endian order. + */ + BYTE *signatureReversed; + if (!hash || !pubKey || !signature || signatureLen < 1) { + return CryptoX_Error; + } + + signatureReversed = malloc(signatureLen); + if (!signatureReversed) { + return CryptoX_Error; + } + + for (i = 0; i < signatureLen; i++) { + signatureReversed[i] = signature[signatureLen - 1 - i]; + } + result = CryptVerifySignature(*hash, signatureReversed, + signatureLen, *pubKey, NULL, 0); + free(signatureReversed); + return result ? CryptoX_Success : CryptoX_Error; +} + +/** + * Obtains the public key for the passed in cert data + * + * @param provider The crypto provider + * @param certData Data of the certificate to extract the public key from + * @param sizeOfCertData The size of the certData buffer + * @param certStore Pointer to the handle of the certificate store to use + * @param CryptoX_Success on success +*/ +CryptoX_Result +CryptoAPI_LoadPublicKey(HCRYPTPROV provider, + BYTE *certData, + DWORD sizeOfCertData, + HCRYPTKEY *publicKey) +{ + CRYPT_DATA_BLOB blob; + CERT_CONTEXT *context; + if (!provider || !certData || !publicKey) { + return CryptoX_Error; + } + + blob.cbData = sizeOfCertData; + blob.pbData = certData; + if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &blob, + CERT_QUERY_CONTENT_FLAG_CERT, + CERT_QUERY_FORMAT_FLAG_BINARY, + 0, NULL, NULL, NULL, + NULL, NULL, (const void **)&context)) { + return CryptoX_Error; + } + + if (!CryptImportPublicKeyInfo(provider, + PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + &context->pCertInfo->SubjectPublicKeyInfo, + publicKey)) { + CertFreeCertificateContext(context); + return CryptoX_Error; + } + + CertFreeCertificateContext(context); + return CryptoX_Success; +} + +/* Try to acquire context in this way: + * 1. Enhanced provider without creating a new key set + * 2. Enhanced provider with creating a new key set + * 3. Default provider without creating a new key set + * 4. Default provider without creating a new key set + * #2 and #4 should not be needed because of the CRYPT_VERIFYCONTEXT, + * but we add it just in case. + * + * @param provider Out parameter containing the provider handle. + * @return CryptoX_Success on success, CryptoX_Error on error. + */ +CryptoX_Result +CryptoAPI_InitCryptoContext(HCRYPTPROV *provider) +{ + if (!CryptAcquireContext(provider, + NULL, + MS_ENHANCED_PROV, + PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + if (!CryptAcquireContext(provider, + NULL, + MS_ENHANCED_PROV, + PROV_RSA_FULL, + CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT)) { + if (!CryptAcquireContext(provider, + NULL, + NULL, + PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + if (!CryptAcquireContext(provider, + NULL, + NULL, + PROV_RSA_FULL, + CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT)) { + *provider = CryptoX_InvalidHandleValue; + return CryptoX_Error; + } + } + } + } + return CryptoX_Success; +} + +/** + * Begins a signature verification hash context + * + * @param provider The crypt provider to use + * @param hash Out parameter for a handle to the hash context + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +CryptoAPI_VerifyBegin(HCRYPTPROV provider, HCRYPTHASH* hash) +{ + BOOL result; + if (!provider || !hash) { + return CryptoX_Error; + } + + *hash = (HCRYPTHASH)NULL; + result = CryptCreateHash(provider, CALG_SHA1, + 0, 0, hash); + return result ? CryptoX_Success : CryptoX_Error; +} + +/** + * Updates a signature verification hash context + * + * @param hash The hash context to update + * @param buf The buffer to update the hash context with + * @param len The size of the passed in buffer + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +CryptoAPI_VerifyUpdate(HCRYPTHASH* hash, BYTE *buf, DWORD len) +{ + BOOL result; + if (!hash || !buf) { + return CryptoX_Error; + } + + result = CryptHashData(*hash, buf, len, 0); + return result ? CryptoX_Success : CryptoX_Error; +} + +#ifdef _WIN32 +#pragma warning(pop) +#endif + +#endif + + + diff --git a/onlineupdate/source/libmar/verify/cryptox.h b/onlineupdate/source/libmar/verify/cryptox.h new file mode 100644 index 000000000..b0f00725f --- /dev/null +++ b/onlineupdate/source/libmar/verify/cryptox.h @@ -0,0 +1,172 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CRYPTOX_H +#define CRYPTOX_H + +#define XP_MIN_SIGNATURE_LEN_IN_BYTES 256 + +#define CryptoX_Result int +#define CryptoX_Success 0 +#define CryptoX_Error (-1) +#define CryptoX_Succeeded(X) ((X) == CryptoX_Success) +#define CryptoX_Failed(X) ((X) != CryptoX_Success) + +#if defined(MAR_NSS) + +#include "cert.h" +#include "keyhi.h" +#include "cryptohi.h" + +#define CryptoX_InvalidHandleValue NULL +#define CryptoX_ProviderHandle void* +#define CryptoX_SignatureHandle VFYContext * +#define CryptoX_PublicKey SECKEYPublicKey * +#define CryptoX_Certificate CERTCertificate * + +#ifdef __cplusplus +extern "C" { +#endif +CryptoX_Result NSS_LoadPublicKey(const unsigned char* certData, + unsigned int certDataSize, + SECKEYPublicKey** publicKey); +CryptoX_Result NSS_VerifyBegin(VFYContext **ctx, + SECKEYPublicKey * const *publicKey); +CryptoX_Result NSS_VerifySignature(VFYContext * const *ctx , + const unsigned char *signature, + unsigned int signatureLen); +#ifdef __cplusplus +} // extern "C" +#endif + +#define CryptoX_InitCryptoProvider(CryptoHandle) \ + CryptoX_Success +#define CryptoX_VerifyBegin(CryptoHandle, SignatureHandle, PublicKey) \ + NSS_VerifyBegin(SignatureHandle, PublicKey) +#define CryptoX_FreeSignatureHandle(SignatureHandle) \ + VFY_DestroyContext(*SignatureHandle, PR_TRUE) +#define CryptoX_VerifyUpdate(SignatureHandle, buf, len) \ + VFY_Update(*SignatureHandle, (const unsigned char*)(buf), len) +#define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, publicKey) \ + NSS_LoadPublicKey(certData, dataSize, publicKey) +#define CryptoX_VerifySignature(hash, publicKey, signedData, len) \ + NSS_VerifySignature(hash, (const unsigned char *)(signedData), len) +#define CryptoX_FreePublicKey(key) \ + SECKEY_DestroyPublicKey(*key) +#define CryptoX_FreeCertificate(cert) \ + CERT_DestroyCertificate(*cert) + +#elif defined(MACOSX) + +#define CryptoX_InvalidHandleValue NULL +#define CryptoX_ProviderHandle void* +#define CryptoX_SignatureHandle void* +#define CryptoX_PublicKey void* +#define CryptoX_Certificate void* + +// Forward-declare Objective-C functions implemented in MacVerifyCrypto.mm. +#ifdef __cplusplus +extern "C" { +#endif +CryptoX_Result CryptoMac_InitCryptoProvider(); +CryptoX_Result CryptoMac_VerifyBegin(CryptoX_SignatureHandle* aInputData); +CryptoX_Result CryptoMac_VerifyUpdate(CryptoX_SignatureHandle* aInputData, + void* aBuf, unsigned int aLen); +CryptoX_Result CryptoMac_LoadPublicKey(const unsigned char* aCertData, + unsigned int aDataSize, + CryptoX_PublicKey* aPublicKey); +CryptoX_Result CryptoMac_VerifySignature(CryptoX_SignatureHandle* aInputData, + CryptoX_PublicKey* aPublicKey, + const unsigned char* aSignature, + unsigned int aSignatureLen); +void CryptoMac_FreeSignatureHandle(CryptoX_SignatureHandle* aInputData); +void CryptoMac_FreePublicKey(CryptoX_PublicKey* aPublicKey); +#ifdef __cplusplus +} // extern "C" +#endif + +#define CryptoX_InitCryptoProvider(aProviderHandle) \ + CryptoMac_InitCryptoProvider() +#define CryptoX_VerifyBegin(aCryptoHandle, aInputData, aPublicKey) \ + CryptoMac_VerifyBegin(aInputData) +#define CryptoX_VerifyUpdate(aInputData, aBuf, aLen) \ + CryptoMac_VerifyUpdate(aInputData, aBuf, aLen) +#define CryptoX_LoadPublicKey(aProviderHandle, aCertData, aDataSize, \ + aPublicKey) \ + CryptoMac_LoadPublicKey(aCertData, aDataSize, aPublicKey) +#define CryptoX_VerifySignature(aInputData, aPublicKey, aSignature, \ + aSignatureLen) \ + CryptoMac_VerifySignature(aInputData, aPublicKey, aSignature, aSignatureLen) +#define CryptoX_FreeSignatureHandle(aInputData) \ + CryptoMac_FreeSignatureHandle(aInputData) +#define CryptoX_FreePublicKey(aPublicKey) \ + CryptoMac_FreePublicKey(aPublicKey) +#define CryptoX_FreeCertificate(aCertificate) + +#elif defined(WNT) + +#include <windows.h> +#include <wincrypt.h> + +CryptoX_Result CryptoAPI_InitCryptoContext(HCRYPTPROV *provider); +CryptoX_Result CryptoAPI_LoadPublicKey(HCRYPTPROV hProv, + BYTE *certData, + DWORD sizeOfCertData, + HCRYPTKEY *publicKey); +CryptoX_Result CryptoAPI_VerifyBegin(HCRYPTPROV provider, HCRYPTHASH* hash); +CryptoX_Result CryptoAPI_VerifyUpdate(HCRYPTHASH* hash, + BYTE *buf, DWORD len); +CryptoX_Result CryptoAPI_VerifySignature(HCRYPTHASH *hash, + HCRYPTKEY *pubKey, + const BYTE *signature, + DWORD signatureLen); + +#define CryptoX_InvalidHandleValue ((ULONG_PTR)NULL) +#define CryptoX_ProviderHandle HCRYPTPROV +#define CryptoX_SignatureHandle HCRYPTHASH +#define CryptoX_PublicKey HCRYPTKEY +#define CryptoX_Certificate HCERTSTORE +#define CryptoX_InitCryptoProvider(CryptoHandle) \ + CryptoAPI_InitCryptoContext(CryptoHandle) +#define CryptoX_VerifyBegin(CryptoHandle, SignatureHandle, PublicKey) \ + CryptoAPI_VerifyBegin(CryptoHandle, SignatureHandle) +#define CryptoX_FreeSignatureHandle(SignatureHandle) +#define CryptoX_VerifyUpdate(SignatureHandle, buf, len) \ + CryptoAPI_VerifyUpdate(SignatureHandle, (BYTE *)(buf), len) +#define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, publicKey) \ + CryptoAPI_LoadPublicKey(CryptoHandle, (BYTE*)(certData), dataSize, publicKey) +#define CryptoX_VerifySignature(hash, publicKey, signedData, len) \ + CryptoAPI_VerifySignature(hash, publicKey, signedData, len) +#define CryptoX_FreePublicKey(key) \ + CryptDestroyKey(*(key)) +#define CryptoX_FreeCertificate(cert) \ + CertCloseStore(*(cert), CERT_CLOSE_STORE_FORCE_FLAG); + +#else + +/* This default implementation is necessary because we don't want to + * link to NSS from updater code on non Windows platforms. On Windows + * we use CryptoAPI instead of NSS. We don't call any function as they + * would just fail, but this simplifies linking. + */ + +#define CryptoX_InvalidHandleValue NULL +#define CryptoX_ProviderHandle void* +#define CryptoX_SignatureHandle void* +#define CryptoX_PublicKey void* +#define CryptoX_Certificate void* +#define CryptoX_InitCryptoProvider(CryptoHandle) \ + CryptoX_Error +#define CryptoX_VerifyBegin(CryptoHandle, SignatureHandle, PublicKey) \ + CryptoX_Error +#define CryptoX_FreeSignatureHandle(SignatureHandle) +#define CryptoX_VerifyUpdate(SignatureHandle, buf, len) CryptoX_Error +#define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, publicKey) \ + CryptoX_Error +#define CryptoX_VerifySignature(hash, publicKey, signedData, len) CryptoX_Error +#define CryptoX_FreePublicKey(key) CryptoX_Error + +#endif + +#endif diff --git a/onlineupdate/source/libmar/verify/mar_verify.c b/onlineupdate/source/libmar/verify/mar_verify.c new file mode 100644 index 000000000..9f33f8bad --- /dev/null +++ b/onlineupdate/source/libmar/verify/mar_verify.c @@ -0,0 +1,466 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <onlineupdate/mar_private.h> +#include <onlineupdate/mar.h> +#include "cryptox.h" + +int +mar_read_entire_file(const char * filePath, uint32_t maxSize, + /*out*/ const uint8_t * *data, + /*out*/ uint32_t *size) +{ + int result; + FILE * f; + + if (!filePath || !data || !size) { + return -1; + } + + f = fopen(filePath, "rb"); + if (!f) { + return -1; + } + + result = -1; + if (!fseeko(f, 0, SEEK_END)) { + int64_t fileSize = ftello(f); + if (fileSize > 0 && fileSize <= maxSize && !fseeko(f, 0, SEEK_SET)) { + unsigned char * fileData; + + *size = (unsigned int) fileSize; + fileData = malloc(*size); + if (fileData) { + if (fread(fileData, *size, 1, f) == 1) { + *data = fileData; + result = 0; + } else { + free(fileData); + } + } + } + } + + fclose(f); + + return result; +} + +int mar_extract_and_verify_signatures_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey *keys, + uint32_t keyCount); +int mar_verify_signatures_for_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey *keys, + const uint8_t * const *extractedSignatures, + uint32_t keyCount, + uint32_t *numVerified); + +/** + * Reads the specified number of bytes from the file pointer and + * stores them in the passed buffer. + * + * @param fp The file pointer to read from. + * @param buffer The buffer to store the read results. + * @param size The number of bytes to read, buffer must be + * at least of this size. + * @param ctxs Pointer to the first element in an array of verify context. + * @param count The number of elements in ctxs + * @param err The name of what is being written to in case of error. + * @return 0 on success + * -1 on read error + * -2 on verify update error +*/ +int +ReadAndUpdateVerifyContext(FILE *fp, + void *buffer, + uint32_t size, + CryptoX_SignatureHandle *ctxs, + uint32_t count, + const char *err) +{ + uint32_t k; + if (!fp || !buffer || !ctxs || count == 0 || !err) { + fprintf(stderr, "ERROR: Invalid parameter specified.\n"); + return CryptoX_Error; + } + + if (!size) { + return CryptoX_Success; + } + + if (fread(buffer, size, 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read %s\n", err); + return CryptoX_Error; + } + + for (k = 0; k < count; k++) { + if (CryptoX_Failed(CryptoX_VerifyUpdate(&ctxs[k], buffer, size))) { + fprintf(stderr, "ERROR: Could not update verify context for %s\n", err); + return -2; + } + } + return CryptoX_Success; +} + +/** + * Verifies a MAR file by verifying each signature with the corresponding + * certificate. That is, the first signature will be verified using the first + * certificate given, the second signature will be verified using the second + * certificate given, etc. The signature count must exactly match the number of + * certificates given, and all signature verifications must succeed. + * + * @param mar The file who's signature should be calculated + * @param certData Pointer to the first element in an array of + * certificate data + * @param certDataSizes Pointer to the first element in an array for size of + * the data stored + * @param certCount The number of elements in certData and certDataSizes + * @return 0 on success +*/ +int +mar_verify_signatures(MarFile *mar, + const uint8_t * const *certData, + const uint32_t *certDataSizes, + uint32_t certCount) { + int rv = -1; + CryptoX_ProviderHandle provider = CryptoX_InvalidHandleValue; + CryptoX_PublicKey keys[MAX_SIGNATURES]; + uint32_t k; + + memset(keys, 0, sizeof(keys)); + + if (!mar || !certData || !certDataSizes || certCount == 0) { + fprintf(stderr, "ERROR: Invalid parameter specified.\n"); + goto failure; + } + + if (!mar->fp) { + fprintf(stderr, "ERROR: MAR file is not open.\n"); + goto failure; + } + + if (CryptoX_Failed(CryptoX_InitCryptoProvider(&provider))) { + fprintf(stderr, "ERROR: Could not init crypto library.\n"); + goto failure; + } + + for (k = 0; k < certCount; ++k) { + if (CryptoX_Failed(CryptoX_LoadPublicKey(provider, certData[k], certDataSizes[k], + &keys[k]))) { + fprintf(stderr, "ERROR: Could not load public key.\n"); + goto failure; + } + } + + rv = mar_extract_and_verify_signatures_fp(mar->fp, provider, keys, certCount); + +failure: + + for (k = 0; k < certCount; ++k) { + if (keys[k]) { + CryptoX_FreePublicKey(&keys[k]); + } + } + + return rv; +} + +/** + * Extracts each signature from the specified MAR file, + * then calls mar_verify_signatures_for_fp to verify each signature. + * + * @param fp An opened MAR file handle + * @param provider A library provider + * @param keys The public keys to use to verify the MAR + * @param keyCount The number of keys pointed to by keys + * @return 0 on success +*/ +int +mar_extract_and_verify_signatures_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey *keys, + uint32_t keyCount) { + uint32_t signatureCount, signatureLen, numVerified = 0; + uint32_t signatureAlgorithmIDs[MAX_SIGNATURES]; + int rv = -1; + uint8_t *extractedSignatures[MAX_SIGNATURES]; + uint32_t i; + + memset(signatureAlgorithmIDs, 0, sizeof(signatureAlgorithmIDs)); + memset(extractedSignatures, 0, sizeof(extractedSignatures)); + + if (!fp) { + fprintf(stderr, "ERROR: Invalid file pointer passed.\n"); + return CryptoX_Error; + } + + /* To protect against invalid MAR files, we assume that the MAR file + size is less than or equal to MAX_SIZE_OF_MAR_FILE. */ + if (fseeko(fp, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to the end of the MAR file.\n"); + return CryptoX_Error; + } + if (ftello(fp) > MAX_SIZE_OF_MAR_FILE) { + fprintf(stderr, "ERROR: MAR file is too large to be verified.\n"); + return CryptoX_Error; + } + + /* Skip to the start of the signature block */ + if (fseeko(fp, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to the signature block.\n"); + return CryptoX_Error; + } + + /* Get the number of signatures */ + if (fread(&signatureCount, sizeof(signatureCount), 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read number of signatures.\n"); + return CryptoX_Error; + } + signatureCount = ntohl(signatureCount); + + /* Check that we have less than the max amount of signatures so we don't + waste too much of either updater's or signmar's time. */ + if (signatureCount > MAX_SIGNATURES) { + fprintf(stderr, "ERROR: At most %d signatures can be specified.\n", + MAX_SIGNATURES); + return CryptoX_Error; + } + + for (i = 0; i < signatureCount; i++) { + /* Get the signature algorithm ID */ + if (fread(&signatureAlgorithmIDs[i], sizeof(uint32_t), 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read signatures algorithm ID.\n"); + return CryptoX_Error; + } + signatureAlgorithmIDs[i] = ntohl(signatureAlgorithmIDs[i]); + + if (fread(&signatureLen, sizeof(uint32_t), 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read signatures length.\n"); + return CryptoX_Error; + } + signatureLen = ntohl(signatureLen); + + /* To protected against invalid input make sure the signature length + isn't too big. */ + if (signatureLen > MAX_SIGNATURE_LENGTH) { + fprintf(stderr, "ERROR: Signature length is too large to verify.\n"); + return CryptoX_Error; + } + + extractedSignatures[i] = malloc(signatureLen); + if (!extractedSignatures[i]) { + fprintf(stderr, "ERROR: Could allocate buffer for signature.\n"); + return CryptoX_Error; + } + if (fread(extractedSignatures[i], signatureLen, 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read extracted signature.\n"); + for (i = 0; i < signatureCount; ++i) { + free(extractedSignatures[i]); + } + return CryptoX_Error; + } + + /* We don't try to verify signatures we don't know about */ + if (signatureAlgorithmIDs[i] != 1) { + fprintf(stderr, "ERROR: Unknown signature algorithm ID.\n"); + for (i = 0; i < signatureCount; ++i) { + free(extractedSignatures[i]); + } + return CryptoX_Error; + } + } + + rv = mar_verify_signatures_for_fp(fp, + provider, + keys, + (const uint8_t * const *)extractedSignatures, + signatureCount, + &numVerified); + for (i = 0; i < signatureCount; ++i) { + free(extractedSignatures[i]); + } + + /* If we reached here and we verified every + signature, return success. */ + if (numVerified == signatureCount && keyCount == numVerified) { + assert(rv == 0); (void) rv; + return CryptoX_Success; + } + + if (numVerified == 0) { + fprintf(stderr, "ERROR: Not all signatures were verified.\n"); + } else { + fprintf(stderr, "ERROR: Only %d of %d signatures were verified.\n", + numVerified, signatureCount); + } + return CryptoX_Error; +} + +/** + * Verifies a MAR file by verifying each signature with the corresponding + * certificate. That is, the first signature will be verified using the first + * certificate given, the second signature will be verified using the second + * certificate given, etc. The signature count must exactly match the number of + * certificates given, and all signature verifications must succeed. + * + * @param fp An opened MAR file handle + * @param provider A library provider + * @param keys A pointer to the first element in an + * array of keys. + * @param extractedSignatures Pointer to the first element in an array + * of extracted signatures. + * @param signatureCount The number of signatures in the MAR file + * @param numVerified Out parameter which will be filled with + * the number of verified signatures. + * This information can be useful for printing + * error messages. + * @return 0 on success, *numVerified == signatureCount. +*/ +int +mar_verify_signatures_for_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey *keys, + const uint8_t * const *extractedSignatures, + uint32_t signatureCount, + uint32_t *numVerified) +{ + CryptoX_SignatureHandle signatureHandles[MAX_SIGNATURES]; + char buf[BLOCKSIZE]; + uint32_t signatureLengths[MAX_SIGNATURES]; + uint32_t i; + int rv = CryptoX_Error; + + (void) provider; (void) keys; // avoid warnings + + memset(signatureHandles, 0, sizeof(signatureHandles)); + memset(signatureLengths, 0, sizeof(signatureLengths)); + + if (!extractedSignatures || !numVerified) { + fprintf(stderr, "ERROR: Invalid parameter specified.\n"); + goto failure; + } + + *numVerified = 0; + + /* This function is only called when we have at least one signature, + but to protected against future people who call this function we + make sure a non zero value is passed in. + */ + if (!signatureCount) { + fprintf(stderr, "ERROR: There must be at least one signature.\n"); + goto failure; + } + + for (i = 0; i < signatureCount; i++) { + if (CryptoX_Failed(CryptoX_VerifyBegin(provider, + &signatureHandles[i], &keys[i]))) { + fprintf(stderr, "ERROR: Could not initialize signature handle.\n"); + goto failure; + } + } + + /* Skip to the start of the file */ + if (fseeko(fp, 0, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to start of the file\n"); + goto failure; + } + + /* Bytes 0-3: MAR1 + Bytes 4-7: index offset + Bytes 8-15: size of entire MAR + */ + if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp, buf, + SIGNATURE_BLOCK_OFFSET + + sizeof(uint32_t), + signatureHandles, + signatureCount, + "signature block"))) { + goto failure; + } + + /* Read the signature block */ + for (i = 0; i < signatureCount; i++) { + /* Get the signature algorithm ID */ + if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp, + &buf, + sizeof(uint32_t), + signatureHandles, + signatureCount, + "signature algorithm ID"))) { + goto failure; + } + + if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp, + &signatureLengths[i], + sizeof(uint32_t), + signatureHandles, + signatureCount, + "signature length"))) { + goto failure; + } + signatureLengths[i] = ntohl(signatureLengths[i]); + if (signatureLengths[i] > MAX_SIGNATURE_LENGTH) { + fprintf(stderr, "ERROR: Embedded signature length is too large.\n"); + goto failure; + } + + /* Skip past the signature itself as those are not included */ + if (fseeko(fp, signatureLengths[i], SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not seek past signature.\n"); + goto failure; + } + } + + /* Read the rest of the file after the signature block */ + while (!feof(fp)) { + int numRead = fread(buf, 1, BLOCKSIZE , fp); + if (ferror(fp)) { + fprintf(stderr, "ERROR: Error reading data block.\n"); + goto failure; + } + + for (i = 0; i < signatureCount; i++) { + if (CryptoX_Failed(CryptoX_VerifyUpdate(&signatureHandles[i], + buf, numRead))) { + fprintf(stderr, "ERROR: Error updating verify context with" + " data block.\n"); + goto failure; + } + } + } + + /* Verify the signatures */ + for (i = 0; i < signatureCount; i++) { + if (CryptoX_Failed(CryptoX_VerifySignature(&signatureHandles[i], + &keys[i], + extractedSignatures[i], + signatureLengths[i]))) { + fprintf(stderr, "ERROR: Error verifying signature.\n"); + goto failure; + } + ++*numVerified; + } + + rv = CryptoX_Success; +failure: + for (i = 0; i < signatureCount; i++) { + CryptoX_FreeSignatureHandle(&signatureHandles[i]); + } + + return rv; +} diff --git a/onlineupdate/source/mbsdiff/bsdiff.cxx b/onlineupdate/source/mbsdiff/bsdiff.cxx new file mode 100644 index 000000000..72bbcde18 --- /dev/null +++ b/onlineupdate/source/mbsdiff/bsdiff.cxx @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + bsdiff.c -- Binary patch generator. + + Copyright 2003 Colin Percival + + For the terms under which this work may be distributed, please see + the adjoining file "LICENSE". + + ChangeLog: + 2005-05-05 - Use the modified header struct from bspatch.h; use 32-bit + values throughout. + --Benjamin Smedberg <benjamin@smedbergs.us> + 2005-05-18 - Use the same CRC algorithm as bzip2, and leverage the CRC table + provided by libbz2. + --Darin Fisher <darin@meer.net> +*/ + +#include "bspatch.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdarg.h> +#ifdef _WIN32 +#include <io.h> +#include <winsock2.h> +#else +#include <unistd.h> +#include <arpa/inet.h> +#define _O_BINARY 0 +#endif + +#undef MIN +#define MIN(x,y) (((x)<(y)) ? (x) : (y)) + +/*---------------------------------------------------------------------------*/ + +/* This variable lives in libbz2. It's declared in bzlib_private.h, so we just + * declare it here to avoid including that entire header file. + */ +extern "C" unsigned int BZ2_crc32Table[256]; + +static unsigned int +crc32(const unsigned char *buf, unsigned int len) +{ + unsigned int crc = 0xffffffffL; + + const unsigned char *end = buf + len; + for (; buf != end; ++buf) + crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf]; + + crc = ~crc; + return crc; +} + +/*---------------------------------------------------------------------------*/ + +static void +reporterr(int e, const char *fmt, ...) +{ + if (fmt) { + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + } + + exit(e); +} + +static void +split(int32_t *I,int32_t *V,int32_t start,int32_t len,int32_t h) +{ + int32_t i,j,k,x,tmp,jj,kk; + + if(len<16) { + for(k=start;k<start+len;k+=j) { + j=1;x=V[I[k]+h]; + for(i=1;k+i<start+len;i++) { + if(V[I[k+i]+h]<x) { + x=V[I[k+i]+h]; + j=0; + }; + if(V[I[k+i]+h]==x) { + tmp=I[k+j];I[k+j]=I[k+i];I[k+i]=tmp; + j++; + }; + }; + for(i=0;i<j;i++) V[I[k+i]]=k+j-1; + if(j==1) I[k]=-1; + }; + return; + }; + + x=V[I[start+len/2]+h]; + jj=0;kk=0; + for(i=start;i<start+len;i++) { + if(V[I[i]+h]<x) jj++; + if(V[I[i]+h]==x) kk++; + }; + jj+=start;kk+=jj; + + i=start;j=0;k=0; + while(i<jj) { + if(V[I[i]+h]<x) { + i++; + } else if(V[I[i]+h]==x) { + tmp=I[i];I[i]=I[jj+j];I[jj+j]=tmp; + j++; + } else { + tmp=I[i];I[i]=I[kk+k];I[kk+k]=tmp; + k++; + }; + }; + + while(jj+j<kk) { + if(V[I[jj+j]+h]==x) { + j++; + } else { + tmp=I[jj+j];I[jj+j]=I[kk+k];I[kk+k]=tmp; + k++; + }; + }; + + if(jj>start) split(I,V,start,jj-start,h); + + for(i=0;i<kk-jj;i++) V[I[jj+i]]=kk-1; + if(jj==kk-1) I[jj]=-1; + + if(start+len>kk) split(I,V,kk,start+len-kk,h); +} + +static void +qsufsort(int32_t *I,int32_t *V,unsigned char *old,int32_t oldsize) +{ + int32_t buckets[256]; + int32_t i,h,len; + + for(i=0;i<256;i++) buckets[i]=0; + for(i=0;i<oldsize;i++) buckets[old[i]]++; + for(i=1;i<256;i++) buckets[i]+=buckets[i-1]; + for(i=255;i>0;i--) buckets[i]=buckets[i-1]; + buckets[0]=0; + + for(i=0;i<oldsize;i++) I[++buckets[old[i]]]=i; + I[0]=oldsize; + for(i=0;i<oldsize;i++) V[i]=buckets[old[i]]; + V[oldsize]=0; + for(i=1;i<256;i++) if(buckets[i]==buckets[i-1]+1) I[buckets[i]]=-1; + I[0]=-1; + + for(h=1;I[0]!=-(oldsize+1);h+=h) { + len=0; + for(i=0;i<oldsize+1;) { + if(I[i]<0) { + len-=I[i]; + i-=I[i]; + } else { + if(len) I[i-len]=-len; + len=V[I[i]]+1-i; + split(I,V,i,len,h); + i+=len; + len=0; + }; + }; + if(len) I[i-len]=-len; + }; + + for(i=0;i<oldsize+1;i++) I[V[i]]=i; +} + +static int32_t +matchlen(unsigned char *old,int32_t oldsize,unsigned char *newbuf,int32_t newsize) +{ + int32_t i; + + for(i=0;(i<oldsize)&&(i<newsize);i++) + if(old[i]!=newbuf[i]) break; + + return i; +} + +static int32_t +search(int32_t *I,unsigned char *old,int32_t oldsize, + unsigned char *newbuf,int32_t newsize,int32_t st,int32_t en,int32_t *pos) +{ + int32_t x,y; + + if(en-st<2) { + x=matchlen(old+I[st],oldsize-I[st],newbuf,newsize); + y=matchlen(old+I[en],oldsize-I[en],newbuf,newsize); + + if(x>y) { + *pos=I[st]; + return x; + } else { + *pos=I[en]; + return y; + } + }; + + x=st+(en-st)/2; + if(memcmp(old+I[x],newbuf,MIN(oldsize-I[x],newsize))<0) { + return search(I,old,oldsize,newbuf,newsize,x,en,pos); + } else { + return search(I,old,oldsize,newbuf,newsize,st,x,pos); + }; +} + +int main(int argc,char *argv[]) +{ + int fd; + unsigned char *old = nullptr,*newbuf = nullptr; + int32_t oldsize = 0, newsize = 0; + int32_t *I = nullptr,*V = nullptr; + + int32_t scan,pos = 0,len; + int32_t lastscan,lastpos,lastoffset; + int32_t oldscore,scsc; + + int32_t s,Sf,lenf,Sb,lenb; + int32_t overlap,Ss,lens; + int32_t i; + + int32_t dblen,eblen; + unsigned char *db,*eb = nullptr; + + unsigned int scrc; + + MBSPatchHeader header = { + {'M','B','D','I','F','F','1','0'}, + 0, 0, 0, 0, 0, 0 + }; + + uint32_t numtriples; + + if(argc!=4) + reporterr(1,"usage: %s <oldfile> <newfile> <patchfile>\n",argv[0]); + + /* Allocate oldsize+1 bytes instead of oldsize bytes to ensure + that we never try to malloc(0) and get a NULL pointer */ + if(((fd=open(argv[1],O_RDONLY|_O_BINARY,0))<0) || + ((oldsize=lseek(fd,0,SEEK_END))==-1) || + ((old=(unsigned char*) malloc(oldsize+1))==NULL) || + (lseek(fd,0,SEEK_SET)!=0) || + (read(fd,old,oldsize)!=oldsize) || + (close(fd)==-1)) + reporterr(1,"%s\n",argv[1]); + + scrc = crc32(old, oldsize); + + if(((I=(int32_t*) malloc((oldsize+1)*sizeof(int32_t)))==NULL) || + ((V=(int32_t*) malloc((oldsize+1)*sizeof(int32_t)))==NULL)) + reporterr(1,NULL); + + qsufsort(I,V,old,oldsize); + + free(V); + + /* Allocate newsize+1 bytes instead of newsize bytes to ensure + that we never try to malloc(0) and get a NULL pointer */ + if(((fd=open(argv[2],O_RDONLY|_O_BINARY,0))<0) || + ((newsize=lseek(fd,0,SEEK_END))==-1) || + ((newbuf=(unsigned char*) malloc(newsize+1))==NULL) || + (lseek(fd,0,SEEK_SET)!=0) || + (read(fd,newbuf,newsize)!=newsize) || + (close(fd)==-1)) reporterr(1,"%s\n",argv[2]); + + if(((db=(unsigned char*) malloc(newsize+1))==NULL) || + ((eb=(unsigned char*) malloc(newsize+1))==NULL)) + reporterr(1,NULL); + + dblen=0; + eblen=0; + + if((fd=open(argv[3],O_CREAT|O_TRUNC|O_WRONLY|_O_BINARY,0666))<0) + reporterr(1,"%s\n",argv[3]); + + /* start writing here */ + + /* We don't know the lengths yet, so we will write the header again + at the end */ + + if(write(fd,&header,sizeof(MBSPatchHeader))!=sizeof(MBSPatchHeader)) + reporterr(1,"%s\n",argv[3]); + + scan=0;len=0; + lastscan=0;lastpos=0;lastoffset=0; + numtriples = 0; + while(scan<newsize) { + oldscore=0; + + for(scsc=scan+=len;scan<newsize;scan++) { + len=search(I,old,oldsize,newbuf+scan,newsize-scan, + 0,oldsize,&pos); + + for(;scsc<scan+len;scsc++) + if((scsc+lastoffset<oldsize) && + (old[scsc+lastoffset] == newbuf[scsc])) + oldscore++; + + if(((len==oldscore) && (len!=0)) || + (len>oldscore+8)) break; + + if((scan+lastoffset<oldsize) && + (old[scan+lastoffset] == newbuf[scan])) + oldscore--; + }; + + if((len!=oldscore) || (scan==newsize)) { + MBSPatchTriple triple; + + s=0;Sf=0;lenf=0; + for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) { + if(old[lastpos+i]==newbuf[lastscan+i]) s++; + i++; + if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; }; + }; + + lenb=0; + if(scan<newsize) { + s=0;Sb=0; + for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) { + if(old[pos-i]==newbuf[scan-i]) s++; + if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; }; + }; + }; + + if(lastscan+lenf>scan-lenb) { + overlap=(lastscan+lenf)-(scan-lenb); + s=0;Ss=0;lens=0; + for(i=0;i<overlap;i++) { + if(newbuf[lastscan+lenf-overlap+i]== + old[lastpos+lenf-overlap+i]) s++; + if(newbuf[scan-lenb+i]== + old[pos-lenb+i]) s--; + if(s>Ss) { Ss=s; lens=i+1; }; + }; + + lenf+=lens-overlap; + lenb-=lens; + }; + + for(i=0;i<lenf;i++) + db[dblen+i]=newbuf[lastscan+i]-old[lastpos+i]; + for(i=0;i<(scan-lenb)-(lastscan+lenf);i++) + eb[eblen+i]=newbuf[lastscan+lenf+i]; + + dblen+=lenf; + eblen+=(scan-lenb)-(lastscan+lenf); + + triple.x = htonl(lenf); + triple.y = htonl((scan-lenb)-(lastscan+lenf)); + triple.z = htonl((pos-lenb)-(lastpos+lenf)); + if (write(fd,&triple,sizeof(triple)) != sizeof(triple)) + reporterr(1,NULL); + +#ifdef DEBUG_bsmedberg + printf("Writing a block:\n" + " X: %u\n" + " Y: %u\n" + " Z: %i\n", + (uint32_t) lenf, + (uint32_t) ((scan-lenb)-(lastscan+lenf)), + (uint32_t) ((pos-lenb)-(lastpos+lenf))); +#endif + + ++numtriples; + + lastscan=scan-lenb; + lastpos=pos-lenb; + lastoffset=pos-scan; + }; + }; + + if(write(fd,db,dblen)!=dblen) + reporterr(1,NULL); + + if(write(fd,eb,eblen)!=eblen) + reporterr(1,NULL); + + header.slen = htonl(oldsize); + header.scrc32 = htonl(scrc); + header.dlen = htonl(newsize); + header.cblen = htonl(numtriples * sizeof(MBSPatchTriple)); + header.difflen = htonl(dblen); + header.extralen = htonl(eblen); + + if (lseek(fd,0,SEEK_SET) == -1 || + write(fd,&header,sizeof(header)) != sizeof(header) || + close(fd) == -1) + reporterr(1,NULL); + + free(db); + free(eb); + free(I); + free(old); + free(newbuf); + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/onlineupdate/source/service/certificatecheck.cxx b/onlineupdate/source/service/certificatecheck.cxx new file mode 100644 index 000000000..58d70162f --- /dev/null +++ b/onlineupdate/source/service/certificatecheck.cxx @@ -0,0 +1,292 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> +#include <softpub.h> +#include <wintrust.h> + +#include "certificatecheck.hxx" +#include "servicebase.hxx" + +#pragma comment(lib, "wintrust.lib") +#pragma comment(lib, "crypt32.lib") + +static const int ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; + +/** + * Checks to see if a file stored at filePath matches the specified info. + * + * @param filePath The PE file path to check + * @param infoToMatch The acceptable information to match + * @return ERROR_SUCCESS if successful, ERROR_NOT_FOUND if the info + * does not match, or the last error otherwise. + */ +DWORD +CheckCertificateForPEFile(LPCWSTR filePath, + CertificateCheckInfo &infoToMatch) +{ + HCERTSTORE certStore = nullptr; + HCRYPTMSG cryptMsg = nullptr; + PCCERT_CONTEXT certContext = nullptr; + PCMSG_SIGNER_INFO signerInfo = nullptr; + DWORD lastError = ERROR_SUCCESS; + + // Get the HCERTSTORE and HCRYPTMSG from the signed file. + DWORD encoding, contentType, formatType; + BOOL result = CryptQueryObject(CERT_QUERY_OBJECT_FILE, + filePath, + CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, + CERT_QUERY_CONTENT_FLAG_ALL, + 0, &encoding, &contentType, + &formatType, &certStore, &cryptMsg, nullptr); + if (!result) + { + lastError = GetLastError(); + LOG_WARN(("CryptQueryObject failed. (%d)", lastError)); + goto cleanup; + } + + // Pass in nullptr to get the needed signer information size. + DWORD signerInfoSize; + result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, + nullptr, &signerInfoSize); + if (!result) + { + lastError = GetLastError(); + LOG_WARN(("CryptMsgGetParam failed. (%d)", lastError)); + goto cleanup; + } + + // Allocate the needed size for the signer information. + signerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, signerInfoSize); + if (!signerInfo) + { + lastError = GetLastError(); + LOG_WARN(("Unable to allocate memory for Signer Info. (%d)", lastError)); + goto cleanup; + } + + // Get the signer information (PCMSG_SIGNER_INFO). + // In particular we want the issuer and serial number. + result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, + (PVOID)signerInfo, &signerInfoSize); + if (!result) + { + lastError = GetLastError(); + LOG_WARN(("CryptMsgGetParam failed. (%d)", lastError)); + goto cleanup; + } + + // Search for the signer certificate in the certificate store. + CERT_INFO certInfo; + certInfo.Issuer = signerInfo->Issuer; + certInfo.SerialNumber = signerInfo->SerialNumber; + certContext = CertFindCertificateInStore(certStore, ENCODING, 0, + CERT_FIND_SUBJECT_CERT, + (PVOID)&certInfo, nullptr); + if (!certContext) + { + lastError = GetLastError(); + LOG_WARN(("CertFindCertificateInStore failed. (%d)", lastError)); + goto cleanup; + } + + if (!DoCertificateAttributesMatch(certContext, infoToMatch)) + { + lastError = ERROR_NOT_FOUND; + LOG_WARN(("Certificate did not match issuer or name. (%d)", lastError)); + goto cleanup; + } + +cleanup: + if (signerInfo) + { + LocalFree(signerInfo); + } + if (certContext) + { + CertFreeCertificateContext(certContext); + } + if (certStore) + { + CertCloseStore(certStore, 0); + } + if (cryptMsg) + { + CryptMsgClose(cryptMsg); + } + return lastError; +} + +/** + * Checks to see if a file stored at filePath matches the specified info. + * + * @param certContext The certificate context of the file + * @param infoToMatch The acceptable information to match + * @return FALSE if the info does not match or if any error occurs in the check + */ +BOOL +DoCertificateAttributesMatch(PCCERT_CONTEXT certContext, + CertificateCheckInfo &infoToMatch) +{ + DWORD dwData; + LPTSTR szName = nullptr; + + if (infoToMatch.issuer) + { + // Pass in nullptr to get the needed size of the issuer buffer. + dwData = CertGetNameString(certContext, + CERT_NAME_SIMPLE_DISPLAY_TYPE, + CERT_NAME_ISSUER_FLAG, nullptr, + nullptr, 0); + + if (!dwData) + { + LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); + return FALSE; + } + + // Allocate memory for Issuer name buffer. + LPTSTR szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); + if (!szName) + { + LOG_WARN(("Unable to allocate memory for issuer name. (%d)", + GetLastError())); + return FALSE; + } + + // Get Issuer name. + if (!CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, + CERT_NAME_ISSUER_FLAG, nullptr, szName, dwData)) + { + LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); + LocalFree(szName); + return FALSE; + } + + // If the issuer does not match, return a failure. + if (!infoToMatch.issuer || + wcscmp(szName, infoToMatch.issuer)) + { + LocalFree(szName); + return FALSE; + } + + LocalFree(szName); + szName = nullptr; + } + + if (infoToMatch.name) + { + // Pass in nullptr to get the needed size of the name buffer. + dwData = CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, + 0, nullptr, nullptr, 0); + if (!dwData) + { + LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); + return FALSE; + } + + // Allocate memory for the name buffer. + szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); + if (!szName) + { + LOG_WARN(("Unable to allocate memory for subject name. (%d)", + GetLastError())); + return FALSE; + } + + // Obtain the name. + if (!(CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, + nullptr, szName, dwData))) + { + LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); + LocalFree(szName); + return FALSE; + } + + // If the issuer does not match, return a failure. + if (!infoToMatch.name || + wcscmp(szName, infoToMatch.name)) + { + LocalFree(szName); + return FALSE; + } + + // We have a match! + LocalFree(szName); + } + + // If there were any errors we would have aborted by now. + return TRUE; +} + +/** + * Duplicates the specified string + * + * @param inputString The string to duplicate + * @return The duplicated string which should be freed by the caller. + */ +LPWSTR +AllocateAndCopyWideString(LPCWSTR inputString) +{ + LPWSTR outputString = + (LPWSTR)LocalAlloc(LPTR, (wcslen(inputString) + 1) * sizeof(WCHAR)); + if (outputString) + { + lstrcpyW(outputString, inputString); + } + return outputString; +} + +/** + * Verifies the trust of the specified file path. + * + * @param filePath The file path to check. + * @return ERROR_SUCCESS if successful, or the last error code otherwise. + */ +DWORD +VerifyCertificateTrustForFile(LPCWSTR filePath) +{ + // Setup the file to check. + WINTRUST_FILE_INFO fileToCheck; + ZeroMemory(&fileToCheck, sizeof(fileToCheck)); + fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO); + fileToCheck.pcwszFilePath = filePath; + + // Setup what to check, we want to check it is signed and trusted. + WINTRUST_DATA trustData; + ZeroMemory(&trustData, sizeof(trustData)); + trustData.cbStruct = sizeof(trustData); + trustData.pPolicyCallbackData = nullptr; + trustData.pSIPClientData = nullptr; + trustData.dwUIChoice = WTD_UI_NONE; + trustData.fdwRevocationChecks = WTD_REVOKE_NONE; + trustData.dwUnionChoice = WTD_CHOICE_FILE; + trustData.dwStateAction = 0; + trustData.hWVTStateData = nullptr; + trustData.pwszURLReference = nullptr; + // no UI + trustData.dwUIContext = 0; + trustData.pFile = &fileToCheck; + + GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; + // Check if the file is signed by something that is trusted. + LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData); + if (ERROR_SUCCESS == ret) + { + // The hash that represents the subject is trusted and there were no + // verification errors. No publisher nor time stamp chain errors. + LOG(("The file \"%ls\" is signed and the signature was verified.", + filePath)); + return ERROR_SUCCESS; + } + + DWORD lastError = GetLastError(); + LOG_WARN(("There was an error validating trust of the certificate for file" + " \"%ls\". Returned: %d. (%d)", filePath, ret, lastError)); + return ret; +} diff --git a/onlineupdate/source/service/certificatecheck.hxx b/onlineupdate/source/service/certificatecheck.hxx new file mode 100644 index 000000000..1efbcb153 --- /dev/null +++ b/onlineupdate/source/service/certificatecheck.hxx @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _CERTIFICATECHECK_H_ +#define _CERTIFICATECHECK_H_ + +#include <wincrypt.h> + +struct CertificateCheckInfo +{ + LPCWSTR name; + LPCWSTR issuer; +}; + +BOOL DoCertificateAttributesMatch(PCCERT_CONTEXT pCertContext, + CertificateCheckInfo &infoToMatch); +DWORD VerifyCertificateTrustForFile(LPCWSTR filePath); +DWORD CheckCertificateForPEFile(LPCWSTR filePath, + CertificateCheckInfo &infoToMatch); + +#endif diff --git a/onlineupdate/source/service/maintenanceservice.cxx b/onlineupdate/source/service/maintenanceservice.cxx new file mode 100644 index 000000000..da324de54 --- /dev/null +++ b/onlineupdate/source/service/maintenanceservice.cxx @@ -0,0 +1,438 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include <shlwapi.h> +#include <stdio.h> +#include <wchar.h> +#include <shlobj.h> + +#include "serviceinstall.hxx" +#include "maintenanceservice.hxx" +#include "servicebase.hxx" +#include "workmonitor.hxx" +#include "uachelper.h" +#include "updatehelper.h" + +// Link w/ subsystem window so we don't get a console when executing +// this binary through the installer. +#pragma comment(linker, "/SUBSYSTEM:windows") + +SERVICE_STATUS gSvcStatus = { 0 }; +SERVICE_STATUS_HANDLE gSvcStatusHandle = nullptr; +HANDLE gWorkDoneEvent = nullptr; +HANDLE gThread = nullptr; +bool gServiceControlStopping = false; + +// logs are pretty small, about 20 lines, so 10 seems reasonable. +#define LOGS_TO_KEEP 10 + +BOOL GetLogDirectoryPath(WCHAR *path); + +int +wmain(int argc, WCHAR **argv) +{ + if (argc < 2) + { + LOG_WARN(("missing mandatory command line argument")); + return 1; + } + // If command-line parameter is "install", install the service + // or upgrade if already installed + // If command line parameter is "forceinstall", install the service + // even if it is older than what is already installed. + // If command-line parameter is "upgrade", upgrade the service + // but do not install it if it is not already installed. + // If command line parameter is "uninstall", uninstall the service. + // Otherwise, the service is probably being started by the SCM. + bool forceInstall = !lstrcmpi(argv[1], L"forceinstall"); + if (!lstrcmpi(argv[1], L"install") || forceInstall) + { + WCHAR updatePath[MAX_PATH + 1]; + if (GetLogDirectoryPath(updatePath)) + { + LogInit(updatePath, L"maintenanceservice-install.log"); + } + + SvcInstallAction action = InstallSvc; + if (forceInstall) + { + action = ForceInstallSvc; + LOG(("Installing service with force specified...")); + } + else + { + LOG(("Installing service...")); + } + + bool ret = SvcInstall(action); + if (!ret) + { + LOG_WARN(("Could not install service. (%d)", GetLastError())); + LogFinish(); + return 1; + } + + LOG(("The service was installed successfully")); + LogFinish(); + return 0; + } + + if (!lstrcmpi(argv[1], L"upgrade")) + { + WCHAR updatePath[MAX_PATH + 1]; + if (GetLogDirectoryPath(updatePath)) + { + LogInit(updatePath, L"maintenanceservice-install.log"); + } + + LOG(("Upgrading service if installed...")); + if (!SvcInstall(UpgradeSvc)) + { + LOG_WARN(("Could not upgrade service. (%d)", GetLastError())); + LogFinish(); + return 1; + } + + LOG(("The service was upgraded successfully")); + LogFinish(); + return 0; + } + + if (!lstrcmpi(argv[1], L"uninstall")) + { + WCHAR updatePath[MAX_PATH + 1]; + if (GetLogDirectoryPath(updatePath)) + { + LogInit(updatePath, L"maintenanceservice-uninstall.log"); + } + LOG(("Uninstalling service...")); + if (!SvcUninstall()) + { + LOG_WARN(("Could not uninstall service. (%d)", GetLastError())); + LogFinish(); + return 1; + } + LOG(("The service was uninstalled successfully")); + LogFinish(); + return 0; + } + + SERVICE_TABLE_ENTRYW DispatchTable[] = + { + { SVC_NAME, (LPSERVICE_MAIN_FUNCTIONW) SvcMain }, + { nullptr, nullptr } + }; + + // This call returns when the service has stopped. + // The process should simply terminate when the call returns. + if (!StartServiceCtrlDispatcherW(DispatchTable)) + { + LOG_WARN(("StartServiceCtrlDispatcher failed. (%d)", GetLastError())); + } + + return 0; +} + +/** + * Obtains the base path where logs should be stored + * + * @param path The out buffer for the backup log path of size MAX_PATH + 1 + * @return TRUE if successful. + */ +BOOL +GetLogDirectoryPath(WCHAR *path) +{ + HRESULT hr = SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA, nullptr, + SHGFP_TYPE_CURRENT, path); + if (FAILED(hr)) + { + return FALSE; + } + + if (!PathAppendSafe(path, L"Mozilla")) + { + return FALSE; + } + // The directory should already be created from the installer, but + // just to be safe in case someone deletes. + CreateDirectoryW(path, nullptr); + + if (!PathAppendSafe(path, L"logs")) + { + return FALSE; + } + CreateDirectoryW(path, nullptr); + return TRUE; +} + +/** + * Calculated a backup path based on the log number. + * + * @param path The out buffer to store the log path of size MAX_PATH + 1 + * @param basePath The base directory where the calculated path should go + * @param logNumber The log number, 0 == updater.log + * @return TRUE if successful. + */ +BOOL +GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber) +{ + WCHAR logName[64] = { L'\0' }; + wcsncpy(path, basePath, sizeof(logName) / sizeof(logName[0]) - 1); + if (logNumber <= 0) + { + swprintf(logName, sizeof(logName) / sizeof(logName[0]), + L"maintenanceservice.log"); + } + else + { + swprintf(logName, sizeof(logName) / sizeof(logName[0]), + L"maintenanceservice-%d.log", logNumber); + } + return PathAppendSafe(path, logName); +} + +/** + * Moves the old log files out of the way before a new one is written. + * If you for example keep 3 logs, then this function will do: + * updater2.log -> updater3.log + * updater1.log -> updater2.log + * updater.log -> updater1.log + * Which clears room for a new updater.log in the basePath directory + * + * @param basePath The base directory path where log files are stored + * @param numLogsToKeep The number of logs to keep + */ +void +BackupOldLogs(LPCWSTR basePath, int numLogsToKeep) +{ + WCHAR oldPath[MAX_PATH + 1]; + WCHAR newPath[MAX_PATH + 1]; + for (int i = numLogsToKeep; i >= 1; i--) + { + if (!GetBackupLogPath(oldPath, basePath, i -1)) + { + continue; + } + + if (!GetBackupLogPath(newPath, basePath, i)) + { + continue; + } + + if (!MoveFileExW(oldPath, newPath, MOVEFILE_REPLACE_EXISTING)) + { + continue; + } + } +} + +/** + * Ensures the service is shutdown once all work is complete. + * There is an issue on XP SP2 and below where the service can hang + * in a stop pending state even though the SCM is notified of a stopped + * state. Control *should* be returned to StartServiceCtrlDispatcher from the + * call to SetServiceStatus on a stopped state in the wmain thread. + * Sometimes this is not the case though. This thread will terminate the process + * if it has been 5 seconds after all work is done and the process is still not + * terminated. This thread is only started once a stopped state was sent to the + * SCM. The stop pending hang can be reproduced intermittently even if you set + * a stopped state directly and never set a stop pending state. It is safe to + * forcefully terminate the process ourselves since all work is done once we + * start this thread. +*/ +DWORD WINAPI +EnsureProcessTerminatedThread(LPVOID) +{ + Sleep(5000); + exit(0); +} + +void +StartTerminationThread() +{ + // If the process does not self terminate like it should, this thread + // will terminate the process after 5 seconds. + HANDLE thread = CreateThread(nullptr, 0, EnsureProcessTerminatedThread, + nullptr, 0, nullptr); + if (thread) + { + CloseHandle(thread); + } +} + +/** + * Main entry point when running as a service. + */ +void WINAPI +SvcMain(DWORD argc, LPWSTR *argv) +{ + // Setup logging, and backup the old logs + WCHAR updatePath[MAX_PATH + 1]; + if (GetLogDirectoryPath(updatePath)) + { + BackupOldLogs(updatePath, LOGS_TO_KEEP); + LogInit(updatePath, L"maintenanceservice.log"); + } + + // Disable every privilege we don't need. Processes started using + // CreateProcess will use the same token as this process. + UACHelper::DisablePrivileges(nullptr); + + // Register the handler function for the service + gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler); + if (!gSvcStatusHandle) + { + LOG_WARN(("RegisterServiceCtrlHandler failed. (%d)", GetLastError())); + ExecuteServiceCommand(argc, argv); + LogFinish(); + exit(1); + } + + // These values will be re-used later in calls involving gSvcStatus + gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + gSvcStatus.dwServiceSpecificExitCode = 0; + + // Report initial status to the SCM + ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000); + + // This event will be used to tell the SvcCtrlHandler when the work is + // done for when a stop command is manually issued. + gWorkDoneEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!gWorkDoneEvent) + { + ReportSvcStatus(SERVICE_STOPPED, 1, 0); + StartTerminationThread(); + return; + } + + // Initialization complete and we're about to start working on + // the actual command. Report the service state as running to the SCM. + ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); + + // The service command was executed, stop logging and set an event + // to indicate the work is done in case someone is waiting on a + // service stop operation. + ExecuteServiceCommand(argc, argv); + LogFinish(); + + SetEvent(gWorkDoneEvent); + + // If we aren't already in a stopping state then tell the SCM we're stopped + // now. If we are already in a stopping state then the SERVICE_STOPPED state + // will be set by the SvcCtrlHandler. + if (!gServiceControlStopping) + { + ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); + StartTerminationThread(); + } +} + +/** + * Sets the current service status and reports it to the SCM. + * + * @param currentState The current state (see SERVICE_STATUS) + * @param exitCode The system error code + * @param waitHint Estimated time for pending operation in milliseconds + */ +void +ReportSvcStatus(DWORD currentState, + DWORD exitCode, + DWORD waitHint) +{ + static DWORD dwCheckPoint = 1; + + gSvcStatus.dwCurrentState = currentState; + gSvcStatus.dwWin32ExitCode = exitCode; + gSvcStatus.dwWaitHint = waitHint; + + if (SERVICE_START_PENDING == currentState || + SERVICE_STOP_PENDING == currentState) + { + gSvcStatus.dwControlsAccepted = 0; + } + else + { + gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_SHUTDOWN; + } + + if ((SERVICE_RUNNING == currentState) || + (SERVICE_STOPPED == currentState)) + { + gSvcStatus.dwCheckPoint = 0; + } + else + { + gSvcStatus.dwCheckPoint = dwCheckPoint++; + } + + // Report the status of the service to the SCM. + SetServiceStatus(gSvcStatusHandle, &gSvcStatus); +} + +/** + * Since the SvcCtrlHandler should only spend at most 30 seconds before + * returning, this function does the service stop work for the SvcCtrlHandler. +*/ +DWORD WINAPI +StopServiceAndWaitForCommandThread(LPVOID) +{ + do + { + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000); + } + while (WaitForSingleObject(gWorkDoneEvent, 100) == WAIT_TIMEOUT); + CloseHandle(gWorkDoneEvent); + gWorkDoneEvent = nullptr; + ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); + StartTerminationThread(); + return 0; +} + +/** + * Called by SCM whenever a control code is sent to the service + * using the ControlService function. + */ +void WINAPI +SvcCtrlHandler(DWORD dwCtrl) +{ + // After a SERVICE_CONTROL_STOP there should be no more commands sent to + // the SvcCtrlHandler. + if (gServiceControlStopping) + { + return; + } + + // Handle the requested control code. + switch (dwCtrl) + { + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + { + gServiceControlStopping = true; + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000); + + // The SvcCtrlHandler thread should not spend more than 30 seconds in + // shutdown so we spawn a new thread for stopping the service + HANDLE thread = CreateThread(nullptr, 0, + StopServiceAndWaitForCommandThread, + nullptr, 0, nullptr); + if (thread) + { + CloseHandle(thread); + } + else + { + // Couldn't start the thread so just call the stop ourselves. + // If it happens to take longer than 30 seconds the caller will + // get an error. + StopServiceAndWaitForCommandThread(nullptr); + } + } + break; + default: + break; + } +} diff --git a/onlineupdate/source/service/maintenanceservice.hxx b/onlineupdate/source/service/maintenanceservice.hxx new file mode 100644 index 000000000..af5db9e29 --- /dev/null +++ b/onlineupdate/source/service/maintenanceservice.hxx @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +void WINAPI SvcMain(DWORD dwArgc, LPWSTR *lpszArgv); +void SvcInit(DWORD dwArgc, LPWSTR *lpszArgv); +void WINAPI SvcCtrlHandler(DWORD dwCtrl); +void ReportSvcStatus(DWORD dwCurrentState, + DWORD dwWin32ExitCode, + DWORD dwWaitHint); diff --git a/onlineupdate/source/service/registrycertificates.cxx b/onlineupdate/source/service/registrycertificates.cxx new file mode 100644 index 000000000..ea0905cea --- /dev/null +++ b/onlineupdate/source/service/registrycertificates.cxx @@ -0,0 +1,181 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> + +#include <memory> + +#include "registrycertificates.hxx" +#include "pathhash.h" +#include "servicebase.hxx" +#include "updatehelper.h" +#define MAX_KEY_LENGTH 255 + +namespace { + +struct AutoRegKey +{ + AutoRegKey(HKEY key): + mKey(key) + { + } + + ~AutoRegKey() + { + releaseKey(mKey); + } + + void releaseKey(HKEY key) + { + if (key != nullptr) + { + RegCloseKey(key); + } + } + + HKEY mKey; + + HKEY get() + { + return mKey; + } +}; + +} + +/** + * Verifies if the file path matches any certificate stored in the registry. + * + * @param filePath The file path of the application to check if allowed. + * @return TRUE if the binary matches any of the allowed certificates. + */ +BOOL +DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate, LPCWSTR filePath) +{ + WCHAR maintenanceServiceKey[MAX_PATH + 1]; + if (!CalculateRegistryPathFromFilePath(basePathForUpdate, + maintenanceServiceKey)) + { + return FALSE; + } + + // We use KEY_WOW64_64KEY to always force 64-bit view. + // The user may have both x86 and x64 applications installed + // which each register information. We need a consistent place + // to put those certificate attributes in and hence why we always + // force the non redirected registry under Wow6432Node. + // This flag is ignored on 32bit systems. + HKEY baseKeyRaw; + LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + maintenanceServiceKey, 0, + KEY_READ | KEY_WOW64_64KEY, &baseKeyRaw); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not open key. (%d)", retCode)); + // Our tests run with a different apply directory for each test. + // We use this registry key on our test slaves to store the + // allowed name/issuers. + retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + TEST_ONLY_FALLBACK_KEY_PATH, 0, + KEY_READ | KEY_WOW64_64KEY, &baseKeyRaw); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not open fallback key. (%d)", retCode)); + return FALSE; + } + } + AutoRegKey baseKey(baseKeyRaw); + + // Get the number of subkeys. + DWORD subkeyCount = 0; + retCode = RegQueryInfoKeyW(baseKey.get(), nullptr, nullptr, nullptr, &subkeyCount, + nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not query info key. (%d)", retCode)); + return FALSE; + } + + // Enumerate the subkeys, each subkey represents an allowed certificate. + for (DWORD i = 0; i < subkeyCount; i++) + { + WCHAR subkeyBuffer[MAX_KEY_LENGTH]; + DWORD subkeyBufferCount = MAX_KEY_LENGTH; + retCode = RegEnumKeyExW(baseKey.get(), i, subkeyBuffer, + &subkeyBufferCount, nullptr, + nullptr, nullptr, nullptr); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not enum certs. (%d)", retCode)); + return FALSE; + } + + // Open the subkey for the current certificate + HKEY subKeyRaw; + retCode = RegOpenKeyExW(baseKey.get(), + subkeyBuffer, + 0, + KEY_READ | KEY_WOW64_64KEY, + &subKeyRaw); + AutoRegKey subKey(subKeyRaw); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not open subkey. (%d)", retCode)); + continue; // Try the next subkey + } + + const int MAX_CHAR_COUNT = 256; + DWORD valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR); + WCHAR name[MAX_CHAR_COUNT] = { L'\0' }; + WCHAR issuer[MAX_CHAR_COUNT] = { L'\0' }; + + // Get the name from the registry + retCode = RegQueryValueExW(subKey.get(), L"name", 0, nullptr, + (LPBYTE)name, &valueBufSize); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not obtain name from registry. (%d)", retCode)); + continue; // Try the next subkey + } + + // Get the issuer from the registry + valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR); + retCode = RegQueryValueExW(subKey.get(), L"issuer", 0, nullptr, + (LPBYTE)issuer, &valueBufSize); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not obtain issuer from registry. (%d)", retCode)); + continue; // Try the next subkey + } + + CertificateCheckInfo allowedCertificate = + { + name, + issuer, + }; + + retCode = CheckCertificateForPEFile(filePath, allowedCertificate); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Error on certificate check. (%d)", retCode)); + continue; // Try the next subkey + } + + retCode = VerifyCertificateTrustForFile(filePath); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Error on certificate trust check. (%d)", retCode)); + continue; // Try the next subkey + } + + // Raise the roof, we found a match! + return TRUE; + } + + // No certificates match, :'( + return FALSE; +} diff --git a/onlineupdate/source/service/registrycertificates.hxx b/onlineupdate/source/service/registrycertificates.hxx new file mode 100644 index 000000000..858b05d7c --- /dev/null +++ b/onlineupdate/source/service/registrycertificates.hxx @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _REGISTRYCERTIFICATES_H_ +#define _REGISTRYCERTIFICATES_H_ + +#include "certificatecheck.hxx" + +BOOL DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate, + LPCWSTR filePath); + +#endif diff --git a/onlineupdate/source/service/resource.hxx b/onlineupdate/source/service/resource.hxx new file mode 100644 index 000000000..f1a1c3836 --- /dev/null +++ b/onlineupdate/source/service/resource.hxx @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by updater.rc +// +#define IDI_DIALOG 1003 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1003 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/onlineupdate/source/service/servicebase.cxx b/onlineupdate/source/service/servicebase.cxx new file mode 100644 index 000000000..4fb632878 --- /dev/null +++ b/onlineupdate/source/service/servicebase.cxx @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "servicebase.hxx" +#include "windowsHelper.hxx" + +// Shared code between applications and updater.exe + +/** + * Verifies if 2 files are byte for byte equivalent. + * + * @param file1Path The first file to verify. + * @param file2Path The second file to verify. + * @param sameContent Out parameter, TRUE if the files are equal + * @return TRUE If there was no error checking the files. + */ +BOOL +VerifySameFiles(LPCWSTR file1Path, LPCWSTR file2Path, BOOL &sameContent) +{ + sameContent = FALSE; + AutoHandle file1(CreateFileW(file1Path, GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (file1 == INVALID_HANDLE_VALUE) + { + return FALSE; + } + AutoHandle file2(CreateFileW(file2Path, GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (file2 == INVALID_HANDLE_VALUE) + { + return FALSE; + } + + DWORD fileSize1 = GetFileSize(file1.get(), nullptr); + DWORD fileSize2 = GetFileSize(file2.get(), nullptr); + if (INVALID_FILE_SIZE == fileSize1 || INVALID_FILE_SIZE == fileSize2) + { + return FALSE; + } + + if (fileSize1 != fileSize2) + { + // sameContent is already set to FALSE + return TRUE; + } + + char buf1[COMPARE_BLOCKSIZE]; + char buf2[COMPARE_BLOCKSIZE]; + DWORD numBlocks = fileSize1 / COMPARE_BLOCKSIZE; + DWORD leftOver = fileSize1 % COMPARE_BLOCKSIZE; + DWORD readAmount; + for (DWORD i = 0; i < numBlocks; i++) + { + if (!ReadFile(file1.get(), buf1, COMPARE_BLOCKSIZE, &readAmount, nullptr) || + readAmount != COMPARE_BLOCKSIZE) + { + return FALSE; + } + + if (!ReadFile(file2.get(), buf2, COMPARE_BLOCKSIZE, &readAmount, nullptr) || + readAmount != COMPARE_BLOCKSIZE) + { + return FALSE; + } + + if (memcmp(buf1, buf2, COMPARE_BLOCKSIZE)) + { + // sameContent is already set to FALSE + return TRUE; + } + } + + if (leftOver) + { + if (!ReadFile(file1.get(), buf1, leftOver, &readAmount, nullptr) || + readAmount != leftOver) + { + return FALSE; + } + + if (!ReadFile(file2.get(), buf2, leftOver, &readAmount, nullptr) || + readAmount != leftOver) + { + return FALSE; + } + + if (memcmp(buf1, buf2, leftOver)) + { + // sameContent is already set to FALSE + return TRUE; + } + } + + sameContent = TRUE; + return TRUE; +} diff --git a/onlineupdate/source/service/servicebase.hxx b/onlineupdate/source/service/servicebase.hxx new file mode 100644 index 000000000..45375fbe5 --- /dev/null +++ b/onlineupdate/source/service/servicebase.hxx @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include "updatelogging.h" + +BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra); +BOOL VerifySameFiles(LPCWSTR file1Path, LPCWSTR file2Path, BOOL& sameContent); + +// 32KiB for comparing files at a time seems reasonable. +// The bigger the better for speed, but this will be used +// on the stack so I don't want it to be too big. +#define COMPARE_BLOCKSIZE 32768 + +// The following string resource value is used to uniquely identify the signed +// Mozilla application as an updater. Before the maintenance service will +// execute the updater it must have this updater identity string in its string +// table. No other signed Mozilla product will have this string table value. +#define UPDATER_IDENTITY_STRING "libreoffice-updater.exe-7bab28a0-0599-4f37-9efe-f7f8b71f05e3" +#define IDS_UPDATER_IDENTITY 1006 diff --git a/onlineupdate/source/service/serviceinstall.cxx b/onlineupdate/source/service/serviceinstall.cxx new file mode 100644 index 000000000..7d53d25cb --- /dev/null +++ b/onlineupdate/source/service/serviceinstall.cxx @@ -0,0 +1,850 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include <aclapi.h> +#include <stdlib.h> +#include <shlwapi.h> + +// Used for DNLEN and UNLEN +#include <lm.h> + +#include "serviceinstall.hxx" +#include "servicebase.hxx" +#include "updatehelper.h" +#include "shellapi.h" +#include "readstrings.h" +#include "errors.h" + +#include <memory> + +#pragma comment(lib, "version.lib") + +namespace { + +struct AutoServiceHandle +{ + AutoServiceHandle(SC_HANDLE handle): + mHandle(handle) + { + } + + ~AutoServiceHandle() + { + releaseHandle(mHandle); + } + + void releaseHandle(SC_HANDLE handle) + { + if (handle != nullptr) + CloseServiceHandle(handle); + } + + SC_HANDLE get() const + { + return mHandle; + } + + operator bool() const + { + return mHandle != nullptr; + } + + void reset(SC_HANDLE handle = nullptr) + { + releaseHandle(mHandle); + mHandle = handle; + } + + SC_HANDLE mHandle; +}; + +} + +/** + * A wrapper function to read strings for the maintenance service. + * + * @param path The path of the ini file to read from + * @param results The maintenance service strings that were read + * @return OK on success +*/ +static int +ReadMaintenanceServiceStrings(LPCWSTR path, + MaintenanceServiceStringTable *results) +{ + // Read in the maintenance service description string if specified. + const unsigned int kNumStrings = 1; + // TODO: moggi: needs adaptation for LibreOffice + const char *kServiceKeys = "MozillaMaintenanceDescription\0"; + char serviceStrings[kNumStrings][MAX_TEXT_LEN]; + int result = ReadStrings(path, kServiceKeys, + kNumStrings, serviceStrings); + if (result != OK) + { + serviceStrings[0][0] = '\0'; + } + strncpy(results->serviceDescription, + serviceStrings[0], MAX_TEXT_LEN - 1); + results->serviceDescription[MAX_TEXT_LEN - 1] = '\0'; + return result; +} + +/** + * Obtains the version number from the specified PE file's version information + * Version Format: A.B.C.D (Example 10.0.0.300) + * + * @param path The path of the file to check the version on + * @param A The first part of the version number + * @param B The second part of the version number + * @param C The third part of the version number + * @param D The fourth part of the version number + * @return TRUE if successful + */ +static BOOL +GetVersionNumberFromPath(LPWSTR path, DWORD &A, DWORD &B, + DWORD &C, DWORD &D) +{ + DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0); + std::unique_ptr<char[]> fileVersionInfo(new char[fileVersionInfoSize]); + if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize, + fileVersionInfo.get())) + { + LOG_WARN(("Could not obtain file info of old service. (%d)", + GetLastError())); + return FALSE; + } + + VS_FIXEDFILEINFO *fixedFileInfo = + reinterpret_cast<VS_FIXEDFILEINFO *>(fileVersionInfo.get()); + UINT size; + if (!VerQueryValueW(fileVersionInfo.get(), L"\\", + reinterpret_cast<LPVOID*>(&fixedFileInfo), &size)) + { + LOG_WARN(("Could not query file version info of old service. (%d)", + GetLastError())); + return FALSE; + } + + A = HIWORD(fixedFileInfo->dwFileVersionMS); + B = LOWORD(fixedFileInfo->dwFileVersionMS); + C = HIWORD(fixedFileInfo->dwFileVersionLS); + D = LOWORD(fixedFileInfo->dwFileVersionLS); + return TRUE; +} + +/** + * Updates the service description with what is stored in updater.ini + * at the same path as the currently executing module binary. + * + * @param serviceHandle A handle to an opened service with + * SERVICE_CHANGE_CONFIG access right + * @param TRUE on success. +*/ +BOOL +UpdateServiceDescription(SC_HANDLE serviceHandle) +{ + WCHAR updaterINIPath[MAX_PATH + 1]; + if (!GetModuleFileNameW(nullptr, updaterINIPath, + sizeof(updaterINIPath) / + sizeof(updaterINIPath[0]))) + { + LOG_WARN(("Could not obtain module filename when attempting to " + "modify service description. (%d)", GetLastError())); + return FALSE; + } + + if (!PathRemoveFileSpecW(updaterINIPath)) + { + LOG_WARN(("Could not remove file spec when attempting to " + "modify service description. (%d)", GetLastError())); + return FALSE; + } + + if (!PathAppendSafe(updaterINIPath, L"updater.ini")) + { + LOG_WARN(("Could not append updater.ini filename when attempting to " + "modify service description. (%d)", GetLastError())); + return FALSE; + } + + if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES) + { + LOG_WARN(("updater.ini file does not exist, will not modify " + "service description. (%d)", GetLastError())); + return FALSE; + } + + MaintenanceServiceStringTable serviceStrings; + int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings); + if (rv != OK || !strlen(serviceStrings.serviceDescription)) + { + LOG_WARN(("updater.ini file does not contain a maintenance " + "service description.")); + return FALSE; + } + + WCHAR serviceDescription[MAX_TEXT_LEN]; + if (!MultiByteToWideChar(CP_UTF8, 0, + serviceStrings.serviceDescription, -1, + serviceDescription, + sizeof(serviceDescription) / + sizeof(serviceDescription[0]))) + { + LOG_WARN(("Could not convert description to wide string format. (%d)", + GetLastError())); + return FALSE; + } + + SERVICE_DESCRIPTIONW descriptionConfig; + descriptionConfig.lpDescription = serviceDescription; + if (!ChangeServiceConfig2W(serviceHandle, + SERVICE_CONFIG_DESCRIPTION, + &descriptionConfig)) + { + LOG_WARN(("Could not change service config. (%d)", GetLastError())); + return FALSE; + } + + LOG(("The service description was updated successfully.")); + return TRUE; +} + +/** + * Determines if the MozillaMaintenance service path needs to be updated + * and fixes it if it is wrong. + * + * @param service A handle to the service to fix. + * @param currentServicePath The current (possibly wrong) path that is used. + * @param servicePathWasWrong Out parameter set to TRUE if a fix was needed. + * @return TRUE if the service path is now correct. +*/ +BOOL +FixServicePath(SC_HANDLE service, + LPCWSTR currentServicePath, + BOOL &servicePathWasWrong) +{ + // When we originally upgraded the MozillaMaintenance service we + // would uninstall the service on each upgrade. This had an + // intermittent error which could cause the service to use the file + // maintenanceservice_tmp.exe as the install path. Only a small number + // of Nightly users would be affected by this, but we check for this + // state here and fix the user if they are affected. + // + // We also fix the path in the case of the path not being quoted. + size_t currentServicePathLen = wcslen(currentServicePath); + bool doesServiceHaveCorrectPath = + currentServicePathLen > 2 && + !wcsstr(currentServicePath, L"maintenanceservice_tmp.exe") && + currentServicePath[0] == L'\"' && + currentServicePath[currentServicePathLen - 1] == L'\"'; + + if (doesServiceHaveCorrectPath) + { + LOG(("The MozillaMaintenance service path is correct.")); + servicePathWasWrong = FALSE; + return TRUE; + } + // This is a recoverable situation so not logging as a warning + LOG(("The MozillaMaintenance path is NOT correct. It was: %ls", + currentServicePath)); + + servicePathWasWrong = TRUE; + WCHAR fixedPath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(fixedPath, currentServicePath, MAX_PATH); + PathUnquoteSpacesW(fixedPath); + if (!PathRemoveFileSpecW(fixedPath)) + { + LOG_WARN(("Couldn't remove file spec. (%d)", GetLastError())); + return FALSE; + } + if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe")) + { + LOG_WARN(("Couldn't append file spec. (%d)", GetLastError())); + return FALSE; + } + PathQuoteSpacesW(fixedPath); + + + if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, + SERVICE_NO_CHANGE, fixedPath, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr)) + { + LOG_WARN(("Could not fix service path. (%d)", GetLastError())); + return FALSE; + } + + LOG(("Fixed service path to: %ls.", fixedPath)); + return TRUE; +} + +/** + * Installs or upgrades the SVC_NAME service. + * If an existing service is already installed, we replace it with the + * currently running process. + * + * @param action The action to perform. + * @return TRUE if the service was installed/upgraded + */ +BOOL +SvcInstall(SvcInstallAction action) +{ + // Get a handle to the local computer SCM database with full access rights. + AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, + SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) + { + LOG_WARN(("Could not open service manager. (%d)", GetLastError())); + return FALSE; + } + + WCHAR newServiceBinaryPath[MAX_PATH + 1]; + if (!GetModuleFileNameW(nullptr, newServiceBinaryPath, + sizeof(newServiceBinaryPath) / + sizeof(newServiceBinaryPath[0]))) + { + LOG_WARN(("Could not obtain module filename when attempting to " + "install service. (%d)", + GetLastError())); + return FALSE; + } + + // Check if we already have the service installed. + AutoServiceHandle schService(OpenServiceW(schSCManager.get(), + SVC_NAME, + SERVICE_ALL_ACCESS)); + DWORD lastError = GetLastError(); + if (!schService && ERROR_SERVICE_DOES_NOT_EXIST != lastError) + { + // The service exists but we couldn't open it + LOG_WARN(("Could not open service. (%d)", GetLastError())); + return FALSE; + } + + if (schService) + { + // The service exists but it may not have the correct permissions. + // This could happen if the permissions were not set correctly originally + // or have been changed after the installation. This will reset the + // permissions back to allow limited user accounts. + if (!SetUserAccessServiceDACL(schService.get())) + { + LOG_WARN(("Could not reset security ACE on service handle. It might not be " + "possible to start the service. This error should never " + "happen. (%d)", GetLastError())); + } + + // The service exists and we opened it + DWORD bytesNeeded; + if (!QueryServiceConfigW(schService.get(), nullptr, 0, &bytesNeeded) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + LOG_WARN(("Could not determine buffer size for query service config. (%d)", + GetLastError())); + return FALSE; + } + + // Get the service config information, in particular we want the binary + // path of the service. + std::unique_ptr<char[]> serviceConfigBuffer(new char[bytesNeeded]); + if (!QueryServiceConfigW(schService.get(), + reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), + bytesNeeded, &bytesNeeded)) + { + LOG_WARN(("Could open service but could not query service config. (%d)", + GetLastError())); + return FALSE; + } + QUERY_SERVICE_CONFIGW &serviceConfig = + *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()); + + // Check if we need to fix the service path + BOOL servicePathWasWrong; + static BOOL alreadyCheckedFixServicePath = FALSE; + if (!alreadyCheckedFixServicePath) + { + if (!FixServicePath(schService.get(), serviceConfig.lpBinaryPathName, + servicePathWasWrong)) + { + LOG_WARN(("Could not fix service path. This should never happen. (%d)", + GetLastError())); + // True is returned because the service is pointing to + // maintenanceservice_tmp.exe so it actually was upgraded to the + // newest installed service. + return TRUE; + } + else if (servicePathWasWrong) + { + // Now that the path is fixed we should re-attempt the install. + // This current process' image path is maintenanceservice_tmp.exe. + // The service used to point to maintenanceservice_tmp.exe. + // The service was just fixed to point to maintenanceservice.exe. + // Re-attempting an install from scratch will work as normal. + alreadyCheckedFixServicePath = TRUE; + LOG(("Restarting install action: %d", action)); + return SvcInstall(action); + } + } + + // Ensure the service path is not quoted. We own this memory and know it to + // be large enough for the quoted path, so it is large enough for the + // unquoted path. This function cannot fail. + PathUnquoteSpacesW(serviceConfig.lpBinaryPathName); + + // Obtain the existing maintenanceservice file's version number and + // the new file's version number. Versions are in the format of + // A.B.C.D. + DWORD existingA, existingB, existingC, existingD; + DWORD newA, newB, newC, newD; + BOOL obtainedExistingVersionInfo = + GetVersionNumberFromPath(serviceConfig.lpBinaryPathName, + existingA, existingB, + existingC, existingD); + if (!GetVersionNumberFromPath(newServiceBinaryPath, newA, + newB, newC, newD)) + { + LOG_WARN(("Could not obtain version number from new path")); + return FALSE; + } + + // Check if we need to replace the old binary with the new one + // If we couldn't get the old version info then we assume we should + // replace it. + if (ForceInstallSvc == action || + !obtainedExistingVersionInfo || + (existingA < newA) || + (existingA == newA && existingB < newB) || + (existingA == newA && existingB == newB && + existingC < newC) || + (existingA == newA && existingB == newB && + existingC == newC && existingD < newD)) + { + + // We have a newer updater, so update the description from the INI file. + UpdateServiceDescription(schService.get()); + + schService.reset(); + if (!StopService()) + { + return FALSE; + } + + if (!wcscmp(newServiceBinaryPath, serviceConfig.lpBinaryPathName)) + { + LOG(("File is already in the correct location, no action needed for " + "upgrade. The path is: \"%ls\"", newServiceBinaryPath)); + return TRUE; + } + + BOOL result = TRUE; + + // Attempt to copy the new binary over top the existing binary. + // If there is an error we try to move it out of the way and then + // copy it in. First try the safest / easiest way to overwrite the file. + if (!CopyFileW(newServiceBinaryPath, + serviceConfig.lpBinaryPathName, FALSE)) + { + LOG_WARN(("Could not overwrite old service binary file. " + "This should never happen, but if it does the next " + "upgrade will fix it, the service is not a critical " + "component that needs to be installed for upgrades " + "to work. (%d)", GetLastError())); + + // We rename the last 3 filename chars in an unsafe way. Manually + // verify there are more than 3 chars for safe failure in MoveFileExW. + const size_t len = wcslen(serviceConfig.lpBinaryPathName); + if (len > 3) + { + // Calculate the temp file path that we're moving the file to. This + // is the same as the proper service path but with a .old extension. + LPWSTR oldServiceBinaryTempPath = + new WCHAR[len + 1]; + memset(oldServiceBinaryTempPath, 0, (len + 1) * sizeof (WCHAR)); + wcsncpy(oldServiceBinaryTempPath, serviceConfig.lpBinaryPathName, len); + // Rename the last 3 chars to 'old' + wcsncpy(oldServiceBinaryTempPath + len - 3, L"old", 3); + + // Move the current (old) service file to the temp path. + if (MoveFileExW(serviceConfig.lpBinaryPathName, + oldServiceBinaryTempPath, + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) + { + // The old binary is moved out of the way, copy in the new one. + if (!CopyFileW(newServiceBinaryPath, + serviceConfig.lpBinaryPathName, FALSE)) + { + // It is best to leave the old service binary in this condition. + LOG_WARN(("The new service binary could not be copied in." + " The service will not be upgraded.")); + result = FALSE; + } + else + { + LOG(("The new service binary was copied in by first moving the" + " old one out of the way.")); + } + + // Attempt to get rid of the old service temp path. + if (DeleteFileW(oldServiceBinaryTempPath)) + { + LOG(("The old temp service path was deleted: %ls.", + oldServiceBinaryTempPath)); + } + else + { + // The old temp path could not be removed. It will be removed + // the next time the user can't copy the binary in or on uninstall. + LOG_WARN(("The old temp service path was not deleted.")); + } + } + else + { + // It is best to leave the old service binary in this condition. + LOG_WARN(("Could not move old service file out of the way from:" + " \"%ls\" to \"%ls\". Service will not be upgraded. (%d)", + serviceConfig.lpBinaryPathName, + oldServiceBinaryTempPath, GetLastError())); + result = FALSE; + } + delete[] oldServiceBinaryTempPath; + } + else + { + // It is best to leave the old service binary in this condition. + LOG_WARN(("Service binary path was less than 3, service will" + " not be updated. This should never happen.")); + result = FALSE; + } + } + else + { + LOG(("The new service binary was copied in.")); + } + + // We made a copy of ourselves to the existing location. + // The tmp file (the process of which we are executing right now) will be + // left over. Attempt to delete the file on the next reboot. + if (MoveFileExW(newServiceBinaryPath, nullptr, + MOVEFILE_DELAY_UNTIL_REBOOT)) + { + LOG(("Deleting the old file path on the next reboot: %ls.", + newServiceBinaryPath)); + } + else + { + LOG_WARN(("Call to delete the old file path failed: %ls.", + newServiceBinaryPath)); + } + + return result; + } + + // We don't need to copy ourselves to the existing location. + // The tmp file (the process of which we are executing right now) will be + // left over. Attempt to delete the file on the next reboot. + MoveFileExW(newServiceBinaryPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT); + + // nothing to do, we already have a newer service installed + return TRUE; + } + + // If the service does not exist and we are upgrading, don't install it. + if (UpgradeSvc == action) + { + // The service does not exist and we are upgrading, so don't install it + return TRUE; + } + + // Quote the path only if it contains spaces. + PathQuoteSpacesW(newServiceBinaryPath); + // The service does not already exist so create the service as on demand + schService.reset(CreateServiceW(schSCManager.get(), SVC_NAME, SVC_DISPLAY_NAME, + SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, + SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, + newServiceBinaryPath, nullptr, nullptr, + nullptr, nullptr, nullptr)); + if (!schService) + { + LOG_WARN(("Could not create Windows service. " + "This error should never happen since a service install " + "should only be called when elevated. (%d)", GetLastError())); + return FALSE; + } + + if (!SetUserAccessServiceDACL(schService.get())) + { + LOG_WARN(("Could not set security ACE on service handle, the service will not " + "be able to be started from unelevated processes. " + "This error should never happen. (%d)", + GetLastError())); + } + + UpdateServiceDescription(schService.get()); + + return TRUE; +} + +/** + * Stops the Maintenance service. + * + * @return TRUE if successful. + */ +BOOL +StopService() +{ + // Get a handle to the local computer SCM database with full access rights. + AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, + SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) + { + LOG_WARN(("Could not open service manager. (%d)", GetLastError())); + return FALSE; + } + + // Open the service + AutoServiceHandle schService(OpenServiceW(schSCManager.get(), SVC_NAME, + SERVICE_ALL_ACCESS)); + if (!schService) + { + LOG_WARN(("Could not open service. (%d)", GetLastError())); + return FALSE; + } + + LOG(("Sending stop request...")); + SERVICE_STATUS status; + SetLastError(ERROR_SUCCESS); + if (!ControlService(schService.get(), SERVICE_CONTROL_STOP, &status) && + GetLastError() != ERROR_SERVICE_NOT_ACTIVE) + { + LOG_WARN(("Error sending stop request. (%d)", GetLastError())); + } + + schSCManager.reset(); + schService.reset(); + + LOG(("Waiting for service stop...")); + DWORD lastState = WaitForServiceStop(SVC_NAME, 30); + + // The service can be in a stopped state but the exe still in use + // so make sure the process is really gone before proceeding + WaitForProcessExit(L"maintenanceservice.exe", 30); + LOG(("Done waiting for service stop, last service state: %d", lastState)); + + return lastState == SERVICE_STOPPED; +} + +/** + * Uninstalls the Maintenance service. + * + * @return TRUE if successful. + */ +BOOL +SvcUninstall() +{ + // Get a handle to the local computer SCM database with full access rights. + AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, + SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) + { + LOG_WARN(("Could not open service manager. (%d)", GetLastError())); + return FALSE; + } + + // Open the service + AutoServiceHandle schService(OpenServiceW(schSCManager.get(), SVC_NAME, + SERVICE_ALL_ACCESS)); + if (!schService) + { + LOG_WARN(("Could not open service. (%d)", GetLastError())); + return FALSE; + } + + //Stop the service so it deletes faster and so the uninstaller + // can actually delete its EXE. + DWORD totalWaitTime = 0; + SERVICE_STATUS status; + static const int maxWaitTime = 1000 * 60; // Never wait more than a minute + if (ControlService(schService.get(), SERVICE_CONTROL_STOP, &status)) + { + do + { + Sleep(status.dwWaitHint); + totalWaitTime += (status.dwWaitHint + 10); + if (status.dwCurrentState == SERVICE_STOPPED) + { + break; + } + else if (totalWaitTime > maxWaitTime) + { + break; + } + } + while (QueryServiceStatus(schService.get(), &status)); + } + + // Delete the service or mark it for deletion + BOOL deleted = DeleteService(schService.get()); + if (!deleted) + { + deleted = (GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE); + } + + return deleted; +} + +/** + * Sets the access control list for user access for the specified service. + * + * @param hService The service to set the access control list on + * @return TRUE if successful + */ +BOOL +SetUserAccessServiceDACL(SC_HANDLE hService) +{ + PACL pNewAcl = nullptr; + PSECURITY_DESCRIPTOR psd = nullptr; + DWORD lastError = SetUserAccessServiceDACL(hService, pNewAcl, psd); + if (pNewAcl) + { + LocalFree((HLOCAL)pNewAcl); + } + if (psd) + { + LocalFree((LPVOID)psd); + } + return ERROR_SUCCESS == lastError; +} + +/** + * Sets the access control list for user access for the specified service. + * + * @param hService The service to set the access control list on + * @param pNewAcl The out param ACL which should be freed by caller + * @param psd out param security descriptor, should be freed by caller + * @return ERROR_SUCCESS if successful + */ +DWORD +SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl, + PSECURITY_DESCRIPTOR psd) +{ + // Get the current security descriptor needed size + DWORD needed = 0; + if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, + &psd, 0, &needed)) + { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + LOG_WARN(("Could not query service object security size. (%d)", + GetLastError())); + return GetLastError(); + } + + DWORD size = needed; + psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size); + if (!psd) + { + LOG_WARN(("Could not allocate security descriptor. (%d)", + GetLastError())); + return ERROR_INSUFFICIENT_BUFFER; + } + + // Get the actual security descriptor now + if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, + psd, size, &needed)) + { + LOG_WARN(("Could not allocate security descriptor. (%d)", + GetLastError())); + return GetLastError(); + } + } + + // Get the current DACL from the security descriptor. + PACL pacl = nullptr; + BOOL bDaclPresent = FALSE; + BOOL bDaclDefaulted = FALSE; + if ( !GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl, + &bDaclDefaulted)) + { + LOG_WARN(("Could not obtain DACL. (%d)", GetLastError())); + return GetLastError(); + } + + PSID sid; + DWORD SIDSize = SECURITY_MAX_SID_SIZE; + sid = LocalAlloc(LMEM_FIXED, SIDSize); + if (!sid) + { + LOG_WARN(("Could not allocate SID memory. (%d)", GetLastError())); + return GetLastError(); + } + + if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sid, &SIDSize)) + { + DWORD lastError = GetLastError(); + LOG_WARN(("Could not create well known SID. (%d)", lastError)); + LocalFree(sid); + return lastError; + } + + // Lookup the account name, the function fails if you don't pass in + // a buffer for the domain name but it's not used since we're using + // the built in account Sid. + SID_NAME_USE accountType; + WCHAR accountName[UNLEN + 1] = { L'\0' }; + WCHAR domainName[DNLEN + 1] = { L'\0' }; + DWORD accountNameSize = UNLEN + 1; + DWORD domainNameSize = DNLEN + 1; + if (!LookupAccountSidW(nullptr, sid, accountName, + &accountNameSize, + domainName, &domainNameSize, &accountType)) + { + LOG_WARN(("Could not lookup account Sid, will try Users. (%d)", + GetLastError())); + wcsncpy(accountName, L"Users", UNLEN); + } + + // We already have the group name so we can get rid of the SID + FreeSid(sid); + sid = nullptr; + + // Build the ACE, BuildExplicitAccessWithName cannot fail so it is not logged. + EXPLICIT_ACCESS ea; + BuildExplicitAccessWithNameW(&ea, accountName, + SERVICE_START | SERVICE_STOP | GENERIC_READ, + SET_ACCESS, NO_INHERITANCE); + DWORD lastError = SetEntriesInAclW(1, (PEXPLICIT_ACCESS)&ea, pacl, &pNewAcl); + if (ERROR_SUCCESS != lastError) + { + LOG_WARN(("Could not set entries in ACL. (%d)", lastError)); + return lastError; + } + + // Initialize a new security descriptor. + SECURITY_DESCRIPTOR sd; + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) + { + LOG_WARN(("Could not initialize security descriptor. (%d)", + GetLastError())); + return GetLastError(); + } + + // Set the new DACL in the security descriptor. + if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE)) + { + LOG_WARN(("Could not set security descriptor DACL. (%d)", + GetLastError())); + return GetLastError(); + } + + // Set the new security descriptor for the service object. + if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &sd)) + { + LOG_WARN(("Could not set object security. (%d)", + GetLastError())); + return GetLastError(); + } + + // Woohoo, raise the roof + LOG(("User access was set successfully on the service.")); + return ERROR_SUCCESS; +} diff --git a/onlineupdate/source/service/serviceinstall.hxx b/onlineupdate/source/service/serviceinstall.hxx new file mode 100644 index 000000000..1afaada84 --- /dev/null +++ b/onlineupdate/source/service/serviceinstall.hxx @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "readstrings.h" + +#define SVC_DISPLAY_NAME L"Mozilla Maintenance Service" + +enum SvcInstallAction { UpgradeSvc, InstallSvc, ForceInstallSvc }; +BOOL SvcInstall(SvcInstallAction action); +BOOL SvcUninstall(); +BOOL StopService(); +BOOL SetUserAccessServiceDACL(SC_HANDLE hService); +DWORD SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl, + PSECURITY_DESCRIPTOR psd); + +struct MaintenanceServiceStringTable +{ + char serviceDescription[MAX_TEXT_LEN]; +}; + diff --git a/onlineupdate/source/service/windowsHelper.hxx b/onlineupdate/source/service/windowsHelper.hxx new file mode 100644 index 000000000..e17b40c61 --- /dev/null +++ b/onlineupdate/source/service/windowsHelper.hxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_ONLINEUPDATE_SERVICE_WINDOWSHELPER_HXX +#define INCLUDED_ONLINEUPDATE_SERVICE_WINDOWSHELPER_HXX + +struct AutoHandle +{ + AutoHandle(HANDLE handle): + mHandle(handle) + { + } + + ~AutoHandle() + { + release(mHandle); + } + + void release(HANDLE handle) + { + if (handle && handle != INVALID_HANDLE_VALUE) + { + CloseHandle(handle); + } + } + + HANDLE get() const + { + return mHandle; + } + + bool operator==(const AutoHandle& rhs) const + { + return mHandle == rhs.mHandle; + } + + HANDLE mHandle; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/onlineupdate/source/service/workmonitor.cxx b/onlineupdate/source/service/workmonitor.cxx new file mode 100644 index 000000000..6228b1e6c --- /dev/null +++ b/onlineupdate/source/service/workmonitor.cxx @@ -0,0 +1,770 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <shlobj.h> +#include <shlwapi.h> +#include <wtsapi32.h> +#include <userenv.h> +#include <shellapi.h> +#include <cstddef> + +#pragma comment(lib, "wtsapi32.lib") +#pragma comment(lib, "userenv.lib") +#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "rpcrt4.lib") + +#include "workmonitor.hxx" +#include "serviceinstall.hxx" +#include "servicebase.hxx" +#include "registrycertificates.hxx" +#include "uachelper.h" +#include "updatehelper.h" +#include "errors.h" +#include "windowsHelper.hxx" + +// Wait 15 minutes for an update operation to run at most. +// Updates usually take less than a minute so this seems like a +// significantly large and safe amount of time to wait. +static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000; +wchar_t* MakeCommandLine(int argc, wchar_t **argv); +BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode); +BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, + LPCWSTR newFileName); + +/* + * Read the update.status file and sets isApplying to true if + * the status is set to applying. + * + * @param updateDirPath The directory where update.status is stored + * @param isApplying Out parameter for specifying if the status + * is set to applying or not. + * @return TRUE if the information was filled. +*/ +static BOOL +IsStatusApplying(LPCWSTR updateDirPath, BOOL &isApplying) +{ + isApplying = FALSE; + WCHAR updateStatusFilePath[MAX_PATH + 1] = {L'\0'}; + wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); + if (!PathAppendSafe(updateStatusFilePath, L"update.status")) + { + LOG_WARN(("Could not append path for update.status file")); + return FALSE; + } + + AutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_READ, + FILE_SHARE_READ | + FILE_SHARE_WRITE | + FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, 0, nullptr)); + + if (statusFile == INVALID_HANDLE_VALUE) + { + LOG_WARN(("Could not open update.status file")); + return FALSE; + } + + char buf[32] = { 0 }; + DWORD read; + if (!ReadFile(statusFile.get(), buf, sizeof(buf), &read, nullptr)) + { + LOG_WARN(("Could not read from update.status file")); + return FALSE; + } + + LOG(("updater.exe returned status: %s", buf)); + + const char kApplying[] = "applying"; + isApplying = strncmp(buf, kApplying, + sizeof(kApplying) - 1) == 0; + return TRUE; +} + +/** + * Determines whether we're staging an update. + * + * @param argc The argc value normally sent to updater.exe + * @param argv The argv value normally sent to updater.exe + * @return boolean True if we're staging an update + */ +static bool +IsUpdateBeingStaged(int argc, LPWSTR *argv) +{ + // PID will be set to -1 if we're supposed to stage an update. + return argc == 4 && !wcscmp(argv[3], L"-1") || + argc == 5 && !wcscmp(argv[4], L"-1"); +} + +/** + * Determines whether the param only contains digits. + * + * @param str The string to check + * @param boolean True if the param only contains digits + */ +static bool +IsDigits(WCHAR *str) +{ + while (*str) + { + if (!iswdigit(*str++)) + { + return FALSE; + } + } + return TRUE; +} + +/** + * Determines whether the command line contains just the directory to apply the + * update to (old command line) or if it contains the installation directory and + * the directory to apply the update to. + * + * @param argc The argc value normally sent to updater.exe + * @param argv The argv value normally sent to updater.exe + * @param boolean True if the command line contains just the directory to apply + * the update to + */ +static bool +IsOldCommandline(int argc, LPWSTR *argv) +{ + return argc == 4 && !wcscmp(argv[3], L"-1") || + argc >= 4 && (wcsstr(argv[3], L"/replace") || IsDigits(argv[3])); +} + +/** + * Gets the installation directory from the arguments passed to updater.exe. + * + * @param argcTmp The argc value normally sent to updater.exe + * @param argvTmp The argv value normally sent to updater.exe + * @param aResultDir Buffer to hold the installation directory. + */ +static BOOL +GetInstallationDir(int argcTmp, LPWSTR *argvTmp, WCHAR aResultDir[MAX_PATH + 1]) +{ + int index = 3; + if (IsOldCommandline(argcTmp, argvTmp)) + { + index = 2; + } + + if (argcTmp < index) + { + return FALSE; + } + + wcsncpy(aResultDir, argvTmp[2], MAX_PATH); + WCHAR* backSlash = wcsrchr(aResultDir, L'\\'); + // Make sure that the path does not include trailing backslashes + if (backSlash && (backSlash[1] == L'\0')) + { + *backSlash = L'\0'; + } + + // The new command line's argv[2] is always the installation directory. + if (index == 2) + { + bool backgroundUpdate = IsUpdateBeingStaged(argcTmp, argvTmp); + bool replaceRequest = (argcTmp >= 4 && wcsstr(argvTmp[3], L"/replace")); + if (backgroundUpdate || replaceRequest) + { + return PathRemoveFileSpecW(aResultDir); + } + } + return TRUE; +} + +/** + * Runs an update process as the service using the SYSTEM account. + * + * @param argc The number of arguments in argv + * @param argv The arguments normally passed to updater.exe + * argv[0] must be the path to updater.exe + * @param processStarted Set to TRUE if the process was started. + * @return TRUE if the update process was run had a return code of 0. + */ +BOOL +StartUpdateProcess(int argc, + LPWSTR *argv, + LPCWSTR installDir, + BOOL &processStarted) +{ + LOG(("Starting update process as the service in session 0.")); + STARTUPINFO si = {0}; + si.cb = sizeof(STARTUPINFO); + si.lpDesktop = L"winsta0\\Default"; + PROCESS_INFORMATION pi = {0}; + + // The updater command line is of the form: + // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]] + LPWSTR cmdLine = MakeCommandLine(argc, argv); + + int index = 3; + if (IsOldCommandline(argc, argv)) + { + index = 2; + } + + // If we're about to start the update process from session 0, + // then we should not show a GUI. This only really needs to be done + // on Vista and higher, but it's better to keep everything consistent + // across all OS if it's of no harm. + if (argc >= index) + { + // Setting the desktop to blank will ensure no GUI is displayed + si.lpDesktop = L""; + si.dwFlags |= STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + } + + // We move the updater.ini file out of the way because we will handle + // executing PostUpdate through the service. We handle PostUpdate from + // the service because there are some per user things that happen that + // can't run in session 0 which we run updater.exe in. + // Once we are done running updater.exe we rename updater.ini back so + // that if there were any errors the next updater.exe will run correctly. + WCHAR updaterINI[MAX_PATH + 1]; + WCHAR updaterINITemp[MAX_PATH + 1]; + BOOL selfHandlePostUpdate = FALSE; + // We use the updater.ini from the same directory as the updater.exe + // because of background updates. + if (PathGetSiblingFilePath(updaterINI, argv[0], L"updater.ini") && + PathGetSiblingFilePath(updaterINITemp, argv[0], L"updater.tmp")) + { + selfHandlePostUpdate = MoveFileExW(updaterINI, updaterINITemp, + MOVEFILE_REPLACE_EXISTING); + } + + // Add an env var for USING_SERVICE so the updater.exe can + // do anything special that it needs to do for service updates. + // Search in updater.cpp for more info on USING_SERVICE. + putenv(const_cast<char*>("USING_SERVICE=1")); + LOG(("Starting service with cmdline: %ls", cmdLine)); + processStarted = CreateProcessW(argv[0], cmdLine, + nullptr, nullptr, FALSE, + CREATE_DEFAULT_ERROR_MODE, + nullptr, + nullptr, &si, &pi); + // Empty value on putenv is how you remove an env variable in Windows + putenv(const_cast<char*>("USING_SERVICE=")); + + BOOL updateWasSuccessful = FALSE; + if (processStarted) + { + // Wait for the updater process to finish + LOG(("Process was started... waiting on result.")); + DWORD waitRes = WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER); + if (WAIT_TIMEOUT == waitRes) + { + // We waited a long period of time for updater.exe and it never finished + // so kill it. + TerminateProcess(pi.hProcess, 1); + } + else + { + // Check the return code of updater.exe to make sure we get 0 + DWORD returnCode; + if (GetExitCodeProcess(pi.hProcess, &returnCode)) + { + LOG(("Process finished with return code %d.", returnCode)); + // updater returns 0 if successful. + updateWasSuccessful = (returnCode == 0); + } + else + { + LOG_WARN(("Process finished but could not obtain return code.")); + } + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + // Check just in case updater.exe didn't change the status from + // applying. If this is the case we report an error. + BOOL isApplying = FALSE; + if (IsStatusApplying(argv[1], isApplying) && isApplying) + { + if (updateWasSuccessful) + { + LOG(("update.status is still applying even know update " + " was successful.")); + if (!WriteStatusFailure(argv[1], + SERVICE_STILL_APPLYING_ON_SUCCESS)) + { + LOG_WARN(("Could not write update.status still applying on" + " success error.")); + } + // Since we still had applying we know updater.exe didn't do its + // job correctly. + updateWasSuccessful = FALSE; + } + else + { + LOG_WARN(("update.status is still applying and update was not successful.")); + if (!WriteStatusFailure(argv[1], + SERVICE_STILL_APPLYING_ON_FAILURE)) + { + LOG_WARN(("Could not write update.status still applying on" + " success error.")); + } + } + } + } + else + { + DWORD lastError = GetLastError(); + LOG_WARN(("Could not create process as current user, " + "updaterPath: %ls; cmdLine: %ls. (%d)", + argv[0], cmdLine, lastError)); + } + + // Now that we're done with the update, restore back the updater.ini file + // We use it ourselves, and also we want it back in case we had any type + // of error so that the normal update process can use it. + if (selfHandlePostUpdate) + { + MoveFileExW(updaterINITemp, updaterINI, MOVEFILE_REPLACE_EXISTING); + + // Only run the PostUpdate if the update was successful + if (updateWasSuccessful && argc > index) + { + LPCWSTR updateInfoDir = argv[1]; + bool stagingUpdate = IsUpdateBeingStaged(argc, argv); + + // Launch the PostProcess with admin access in session 0. This is + // actually launching the post update process but it takes in the + // callback app path to figure out where to apply to. + // The PostUpdate process with user only access will be done inside + // the unelevated updater.exe after the update process is complete + // from the service. We don't know here which session to start + // the user PostUpdate process from. + // Note that we don't need to do this if we're just staging the + // update in the background, as the PostUpdate step runs when + // performing the replacing in that case. + if (!stagingUpdate) + { + LOG(("Launching post update process as the service in session 0.")); + if (!LaunchWinPostProcess(installDir, updateInfoDir, true, nullptr)) + { + LOG_WARN(("The post update process could not be launched." + " installDir: %ls, updateInfoDir: %ls", + installDir, updateInfoDir)); + } + } + } + } + + free(cmdLine); + return updateWasSuccessful; +} + +/** + * Processes a software update command + * + * @param argc The number of arguments in argv + * @param argv The arguments normally passed to updater.exe + * argv[0] must be the path to updater.exe + * @return TRUE if the update was successful. + */ +BOOL +ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv) +{ + BOOL result = TRUE; + if (argc < 3) + { + LOG_WARN(("Not enough command line parameters specified. " + "Updating update.status.")); + + // We can only update update.status if argv[1] exists. argv[1] is + // the directory where the update.status file exists. + if (argc < 2 || + !WriteStatusFailure(argv[1], + SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) + { + LOG_WARN(("Could not write update.status service update failure. (%d)", + GetLastError())); + } + return FALSE; + } + + WCHAR installDir[MAX_PATH + 1] = {L'\0'}; + if (!GetInstallationDir(argc, argv, installDir)) + { + LOG_WARN(("Could not get the installation directory")); + if (!WriteStatusFailure(argv[1], + SERVICE_INSTALLDIR_ERROR)) + { + LOG_WARN(("Could not write update.status for GetInstallationDir failure.")); + } + return FALSE; + } + + // Make sure the path to the updater to use for the update is local. + // We do this check to make sure that file locking is available for + // race condition security checks. + BOOL isLocal = FALSE; + if (!IsLocalFile(argv[0], isLocal) || !isLocal) + { + LOG_WARN(("Filesystem in path %ls is not supported (%d)", + argv[0], GetLastError())); + if (!WriteStatusFailure(argv[1], + SERVICE_UPDATER_NOT_FIXED_DRIVE)) + { + LOG_WARN(("Could not write update.status service update failure. (%d)", + GetLastError())); + } + return FALSE; + } + + AutoHandle noWriteLock(CreateFileW(argv[0], GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (noWriteLock == INVALID_HANDLE_VALUE) + { + LOG_WARN(("Could not set no write sharing access on file. (%d)", + GetLastError())); + if (!WriteStatusFailure(argv[1], + SERVICE_COULD_NOT_LOCK_UPDATER)) + { + LOG_WARN(("Could not write update.status service update failure. (%d)", + GetLastError())); + } + return FALSE; + } + + // Verify that the updater.exe that we are executing is the same + // as the one in the installation directory which we are updating. + // The installation dir that we are installing to is installDir. + WCHAR installDirUpdater[MAX_PATH + 1] = { L'\0' }; + wcsncpy(installDirUpdater, installDir, MAX_PATH); + if (!PathAppendSafe(installDirUpdater, L"updater.exe")) + { + LOG_WARN(("Install directory updater could not be determined.")); + result = FALSE; + } + + BOOL updaterIsCorrect = FALSE; + if (result && !VerifySameFiles(argv[0], installDirUpdater, + updaterIsCorrect)) + { + LOG_WARN(("Error checking if the updaters are the same.\n" + "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater)); + result = FALSE; + } + + if (result && !updaterIsCorrect) + { + LOG_WARN(("The updaters do not match, updater will not run.\n" + "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater)); + result = FALSE; + } + + if (result) + { + LOG(("updater.exe was compared successfully to the installation directory" + " updater.exe.")); + } + else + { + if (!WriteStatusFailure(argv[1], + SERVICE_UPDATER_COMPARE_ERROR)) + { + LOG_WARN(("Could not write update.status updater compare failure.")); + } + return FALSE; + } + + // Check to make sure the updater.exe module has the unique updater identity. + // This is a security measure to make sure that the signed executable that + // we will run is actually an updater. + HMODULE updaterModule = LoadLibraryEx(argv[0], nullptr, + LOAD_LIBRARY_AS_DATAFILE); + if (!updaterModule) + { + LOG_WARN(("updater.exe module could not be loaded. (%d)", GetLastError())); + result = FALSE; + } + else + { + char updaterIdentity[64]; + if (!LoadStringA(updaterModule, IDS_UPDATER_IDENTITY, + updaterIdentity, sizeof(updaterIdentity))) + { + LOG_WARN(("The updater.exe application does not contain the Mozilla" + " updater identity.")); + result = FALSE; + } + + if (strcmp(updaterIdentity, UPDATER_IDENTITY_STRING)) + { + LOG_WARN(("The updater.exe identity string is not valid.")); + result = FALSE; + } + FreeLibrary(updaterModule); + } + + if (result) + { + LOG(("The updater.exe application contains the Mozilla" + " updater identity.")); + } + else + { + if (!WriteStatusFailure(argv[1], + SERVICE_UPDATER_IDENTITY_ERROR)) + { + LOG_WARN(("Could not write update.status no updater identity.")); + } + return TRUE; + } + + // Check for updater.exe sign problems + BOOL updaterSignProblem = FALSE; +#ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK + updaterSignProblem = !DoesBinaryMatchAllowedCertificates(installDir, + argv[0]); +#endif + + // Only proceed with the update if we have no signing problems + if (!updaterSignProblem) + { + BOOL updateProcessWasStarted = FALSE; + if (StartUpdateProcess(argc, argv, installDir, + updateProcessWasStarted)) + { + LOG(("updater.exe was launched and run successfully!")); + LogFlush(); + + // Don't attempt to update the service when the update is being staged. + if (!IsUpdateBeingStaged(argc, argv)) + { + // We might not execute code after StartServiceUpdate because + // the service installer will stop the service if it is running. + StartServiceUpdate(installDir); + } + } + else + { + result = FALSE; + LOG_WARN(("Error running update process. Updating update.status (%d)", + GetLastError())); + LogFlush(); + + // If the update process was started, then updater.exe is responsible for + // setting the failure code. If it could not be started then we do the + // work. We set an error instead of directly setting status pending + // so that the app.update.service.errors pref can be updated when + // the callback app restarts. + if (!updateProcessWasStarted) + { + if (!WriteStatusFailure(argv[1], + SERVICE_UPDATER_COULD_NOT_BE_STARTED)) + { + LOG_WARN(("Could not write update.status service update failure. (%d)", + GetLastError())); + } + } + } + } + else + { + result = FALSE; + LOG_WARN(("Could not start process due to certificate check error on " + "updater.exe. Updating update.status. (%d)", GetLastError())); + + // When there is a certificate check error on the updater.exe application, + // we want to write out the error. + if (!WriteStatusFailure(argv[1], + SERVICE_UPDATER_SIGN_ERROR)) + { + LOG_WARN(("Could not write pending state to update.status. (%d)", + GetLastError())); + } + } + + return result; +} + +/** + * Obtains the updater path alongside a subdir of the service binary. + * The purpose of this function is to return a path that is likely high + * integrity and therefore more safe to execute code from. + * + * @param serviceUpdaterPath Out parameter for the path where the updater + * should be copied to. + * @return TRUE if a file path was obtained. + */ +BOOL +GetSecureUpdaterPath(WCHAR serviceUpdaterPath[MAX_PATH + 1]) +{ + if (!GetModuleFileNameW(nullptr, serviceUpdaterPath, MAX_PATH)) + { + LOG_WARN(("Could not obtain module filename when attempting to " + "use a secure updater path. (%d)", GetLastError())); + return FALSE; + } + + if (!PathRemoveFileSpecW(serviceUpdaterPath)) + { + LOG_WARN(("Couldn't remove file spec when attempting to use a secure " + "updater path. (%d)", GetLastError())); + return FALSE; + } + + if (!PathAppendSafe(serviceUpdaterPath, L"update")) + { + LOG_WARN(("Couldn't append file spec when attempting to use a secure " + "updater path. (%d)", GetLastError())); + return FALSE; + } + + CreateDirectoryW(serviceUpdaterPath, nullptr); + + if (!PathAppendSafe(serviceUpdaterPath, L"updater.exe")) + { + LOG_WARN(("Couldn't append file spec when attempting to use a secure " + "updater path. (%d)", GetLastError())); + return FALSE; + } + + return TRUE; +} + +/** + * Deletes the passed in updater path and the associated updater.ini file. + * + * @param serviceUpdaterPath The path to delete. + * @return TRUE if a file was deleted. + */ +BOOL +DeleteSecureUpdater(WCHAR serviceUpdaterPath[MAX_PATH + 1]) +{ + BOOL result = FALSE; + if (serviceUpdaterPath[0]) + { + result = DeleteFileW(serviceUpdaterPath); + if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && + GetLastError() != ERROR_FILE_NOT_FOUND) + { + LOG_WARN(("Could not delete service updater path: '%ls'.", + serviceUpdaterPath)); + } + + WCHAR updaterINIPath[MAX_PATH + 1] = { L'\0' }; + if (PathGetSiblingFilePath(updaterINIPath, serviceUpdaterPath, + L"updater.ini")) + { + result = DeleteFileW(updaterINIPath); + if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && + GetLastError() != ERROR_FILE_NOT_FOUND) + { + LOG_WARN(("Could not delete service updater INI path: '%ls'.", + updaterINIPath)); + } + } + } + return result; +} + +/** + * Executes a service command. + * + * @param argc The number of arguments in argv + * @param argv The service command line arguments, argv[0] and argv[1] + * and automatically included by Windows. argv[2] is the + * service command. + * + * @return FALSE if there was an error executing the service command. + */ +BOOL +ExecuteServiceCommand(int argc, LPWSTR *argv) +{ + if (argc < 3) + { + LOG_WARN(("Not enough command line arguments to execute a service command")); + return FALSE; + } + + // The tests work by making sure the log has changed, so we put a + // unique ID in the log. + RPC_WSTR guidString = RPC_WSTR(L""); + GUID guid; + HRESULT hr = CoCreateGuid(&guid); + if (SUCCEEDED(hr)) + { + UuidToString(&guid, &guidString); + } + LOG(("Executing service command %ls, ID: %ls", + argv[2], reinterpret_cast<LPCWSTR>(guidString))); + RpcStringFree(&guidString); + + BOOL result = FALSE; + if (!lstrcmpi(argv[2], L"software-update")) + { + + // Use the passed in command line arguments for the update, except for the + // path to updater.exe. We copy updater.exe to the directory of the + // MozillaMaintenance service so that a low integrity process cannot + // replace the updater.exe at any point and use that for the update. + // It also makes DLL injection attacks harder. + LPWSTR oldUpdaterPath = argv[3]; + WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' }; + result = GetSecureUpdaterPath(secureUpdaterPath); // Does its own logging + if (result) + { + LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.", + oldUpdaterPath, secureUpdaterPath)); + DeleteSecureUpdater(secureUpdaterPath); + result = CopyFileW(oldUpdaterPath, secureUpdaterPath, FALSE); + } + + if (!result) + { + LOG_WARN(("Could not copy path to secure location. (%d)", + GetLastError())); + if (argc > 4 && !WriteStatusFailure(argv[4], + SERVICE_COULD_NOT_COPY_UPDATER)) + { + LOG_WARN(("Could not write update.status could not copy updater error")); + } + } + else + { + + // We obtained the path and copied it successfully, update the path to + // use for the service update. + argv[3] = secureUpdaterPath; + + WCHAR oldUpdaterINIPath[MAX_PATH + 1] = { L'\0' }; + WCHAR secureUpdaterINIPath[MAX_PATH + 1] = { L'\0' }; + if (PathGetSiblingFilePath(secureUpdaterINIPath, secureUpdaterPath, + L"updater.ini") && + PathGetSiblingFilePath(oldUpdaterINIPath, oldUpdaterPath, + L"updater.ini")) + { + // This is non fatal if it fails there is no real harm + if (!CopyFileW(oldUpdaterINIPath, secureUpdaterINIPath, FALSE)) + { + LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%d)", + oldUpdaterINIPath, secureUpdaterINIPath, GetLastError())); + } + } + + result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3); + DeleteSecureUpdater(secureUpdaterPath); + } + + // We might not reach here if the service install succeeded + // because the service self updates itself and the service + // installer will stop the service. + LOG(("Service command %ls complete.", argv[2])); + } + else + { + LOG_WARN(("Service command not recognized: %ls.", argv[2])); + // result is already set to FALSE + } + + LOG(("service command %ls complete with result: %ls.", + argv[1], (result ? L"Success" : L"Failure"))); + return TRUE; +} diff --git a/onlineupdate/source/service/workmonitor.hxx b/onlineupdate/source/service/workmonitor.hxx new file mode 100644 index 000000000..8fc2e51a7 --- /dev/null +++ b/onlineupdate/source/service/workmonitor.hxx @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +BOOL ExecuteServiceCommand(int argc, LPWSTR* argv); diff --git a/onlineupdate/source/update/common/errors.h b/onlineupdate/source/update/common/errors.h new file mode 100644 index 000000000..2216b5449 --- /dev/null +++ b/onlineupdate/source/update/common/errors.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Errors_h__ +#define Errors_h__ + +#define OK 0 + +// Error codes that are no longer used should not be used again unless they +// aren't used in client code (e.g. nsUpdateService.js, updates.js, +// UpdatePrompt.js, etc.). + +#define MAR_ERROR_EMPTY_ACTION_LIST 1 +#define LOADSOURCE_ERROR_WRONG_SIZE 2 + +// Error codes 3-16 are for general update problems. +#define USAGE_ERROR 3 +#define CRC_ERROR 4 +#define PARSE_ERROR 5 +#define READ_ERROR 6 +#define WRITE_ERROR 7 +// #define UNEXPECTED_ERROR 8 // Replaced with errors 38-42 +#define ELEVATION_CANCELED 9 +#define READ_STRINGS_MEM_ERROR 10 +#define ARCHIVE_READER_MEM_ERROR 11 +#define BSPATCH_MEM_ERROR 12 +#define UPDATER_MEM_ERROR 13 +#define UPDATER_QUOTED_PATH_MEM_ERROR 14 +#define BAD_ACTION_ERROR 15 +#define STRING_CONVERSION_ERROR 16 + +// Error codes 17-23 are related to security tasks for MAR +// signing and MAR protection. +#define CERT_LOAD_ERROR 17 +#define CERT_HANDLING_ERROR 18 +#define CERT_VERIFY_ERROR 19 +#define ARCHIVE_NOT_OPEN 20 +#define COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR 21 +#define MAR_CHANNEL_MISMATCH_ERROR 22 +#define VERSION_DOWNGRADE_ERROR 23 + +// Error codes 24-33 and 49-51 are for the Windows maintenance service. +#define SERVICE_UPDATER_COULD_NOT_BE_STARTED 24 +#define SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS 25 +#define SERVICE_UPDATER_SIGN_ERROR 26 +#define SERVICE_UPDATER_COMPARE_ERROR 27 +#define SERVICE_UPDATER_IDENTITY_ERROR 28 +#define SERVICE_STILL_APPLYING_ON_SUCCESS 29 +#define SERVICE_STILL_APPLYING_ON_FAILURE 30 +#define SERVICE_UPDATER_NOT_FIXED_DRIVE 31 +#define SERVICE_COULD_NOT_LOCK_UPDATER 32 +#define SERVICE_INSTALLDIR_ERROR 33 + +#define NO_INSTALLDIR_ERROR 34 +#define WRITE_ERROR_ACCESS_DENIED 35 +// #define WRITE_ERROR_SHARING_VIOLATION 36 // Replaced with errors 46-48 +#define WRITE_ERROR_CALLBACK_APP 37 +#define UNEXPECTED_BZIP_ERROR 39 +#define UNEXPECTED_MAR_ERROR 40 +#define UNEXPECTED_BSPATCH_ERROR 41 +#define UNEXPECTED_FILE_OPERATION_ERROR 42 +#define FILESYSTEM_MOUNT_READWRITE_ERROR 43 +#define DELETE_ERROR_EXPECTED_DIR 46 +#define DELETE_ERROR_EXPECTED_FILE 47 +#define RENAME_ERROR_EXPECTED_FILE 48 + +// Error codes 24-33 and 49-51 are for the Windows maintenance service. +#define SERVICE_COULD_NOT_COPY_UPDATER 49 +#define SERVICE_STILL_APPLYING_TERMINATED 50 +#define SERVICE_STILL_APPLYING_NO_EXIT_CODE 51 + +#define WRITE_ERROR_FILE_COPY 61 +#define WRITE_ERROR_DELETE_FILE 62 +#define WRITE_ERROR_OPEN_PATCH_FILE 63 +#define WRITE_ERROR_PATCH_FILE 64 +#define WRITE_ERROR_APPLY_DIR_PATH 65 +#define WRITE_ERROR_CALLBACK_PATH 66 +#define WRITE_ERROR_FILE_ACCESS_DENIED 67 +#define WRITE_ERROR_DIR_ACCESS_DENIED 68 +#define WRITE_ERROR_DELETE_BACKUP 69 +#define WRITE_ERROR_EXTRACT 70 + +// Error codes 80 through 99 are reserved for nsUpdateService.js + +// The following error codes are only used by updater.exe +// when a fallback key exists for tests. +#define FALLBACKKEY_UNKNOWN_ERROR 100 +#define FALLBACKKEY_REGPATH_ERROR 101 +#define FALLBACKKEY_NOKEY_ERROR 102 +#define FALLBACKKEY_SERVICE_NO_STOP_ERROR 103 +#define FALLBACKKEY_LAUNCH_ERROR 104 + +#endif // Errors_h__ diff --git a/onlineupdate/source/update/common/pathhash.cxx b/onlineupdate/source/update/common/pathhash.cxx new file mode 100644 index 000000000..67e53fa35 --- /dev/null +++ b/onlineupdate/source/update/common/pathhash.cxx @@ -0,0 +1,153 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#include <windows.h> +#include <wincrypt.h> +#include "pathhash.h" + + +/** + * Converts a binary sequence into a hex string + * + * @param hash The binary data sequence + * @param hashSize The size of the binary data sequence + * @param hexString A buffer to store the hex string, must be of + * size 2 * @hashSize +*/ +static void +BinaryDataToHexString(const BYTE *hash, DWORD &hashSize, + LPWSTR hexString) +{ + WCHAR *p = hexString; + for (DWORD i = 0; i < hashSize; ++i) + { + wsprintfW(p, L"%.2x", hash[i]); + p += 2; + } +} + +/** + * Calculates an MD5 hash for the given input binary data + * + * @param data Any sequence of bytes + * @param dataSize The number of bytes inside @data + * @param hash Output buffer to store hash, must be freed by the caller + * @param hashSize The number of bytes in the output buffer + * @return TRUE on success +*/ +static BOOL +CalculateMD5(const char *data, DWORD dataSize, + BYTE **hash, DWORD &hashSize) +{ + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + + if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) + { + if (NTE_BAD_KEYSET != GetLastError()) + { + return FALSE; + } + + // Maybe it doesn't exist, try to create it. + if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_NEWKEYSET)) + { + return FALSE; + } + } + + if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) + { + return FALSE; + } + + if (!CryptHashData(hHash, reinterpret_cast<const BYTE*>(data), + dataSize, 0)) + { + return FALSE; + } + + DWORD dwCount = sizeof(DWORD); + if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&hashSize, + &dwCount, 0)) + { + return FALSE; + } + + *hash = new BYTE[hashSize]; + ZeroMemory(*hash, hashSize); + if (!CryptGetHashParam(hHash, HP_HASHVAL, *hash, &hashSize, 0)) + { + return FALSE; + } + + if (hHash) + { + CryptDestroyHash(hHash); + } + + if (hProv) + { + CryptReleaseContext(hProv,0); + } + + return TRUE; +} + +/** + * Converts a file path into a unique registry location for cert storage + * + * @param filePath The input file path to get a registry path from + * @param registryPath A buffer to write the registry path to, must + * be of size in WCHARs MAX_PATH + 1 + * @return TRUE if successful +*/ +BOOL +CalculateRegistryPathFromFilePath(const LPCWSTR filePath, + LPWSTR registryPath) +{ + size_t filePathLen = wcslen(filePath); + if (!filePathLen) + { + return FALSE; + } + + // If the file path ends in a slash, ignore that character + if (filePath[filePathLen -1] == L'\\' || + filePath[filePathLen - 1] == L'/') + { + filePathLen--; + } + + // Copy in the full path into our own buffer. + // Copying in the extra slash is OK because we calculate the hash + // based on the filePathLen which excludes the slash. + // +2 to account for the possibly trailing slash and the null terminator. + WCHAR *lowercasePath = new WCHAR[filePathLen + 2]; + memset(lowercasePath, 0, (filePathLen + 2) * sizeof(WCHAR)); + wcsncpy(lowercasePath, filePath, filePathLen + 1); + _wcslwr(lowercasePath); + + BYTE *hash; + DWORD hashSize = 0; + if (!CalculateMD5(reinterpret_cast<const char*>(lowercasePath), + filePathLen * 2, + &hash, hashSize)) + { + delete[] lowercasePath; + return FALSE; + } + delete[] lowercasePath; + + LPCWSTR baseRegPath = L"SOFTWARE\\LibreOffice\\MaintenanceService\\"; + wcsncpy(registryPath, baseRegPath, MAX_PATH); + BinaryDataToHexString(hash, hashSize, + registryPath + wcslen(baseRegPath)); + delete[] hash; + return TRUE; +} +#endif diff --git a/onlineupdate/source/update/common/pathhash.h b/onlineupdate/source/update/common/pathhash.h new file mode 100644 index 000000000..9856b4cf4 --- /dev/null +++ b/onlineupdate/source/update/common/pathhash.h @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _PATHHASH_H_ +#define _PATHHASH_H_ + +/** + * Converts a file path into a unique registry location for cert storage + * + * @param filePath The input file path to get a registry path from + * @param registryPath A buffer to write the registry path to, must + * be of size in WCHARs MAX_PATH + 1 + * @return TRUE if successful +*/ +BOOL CalculateRegistryPathFromFilePath(const LPCWSTR filePath, + LPWSTR registryPath); + +#endif diff --git a/onlineupdate/source/update/common/readstrings.cxx b/onlineupdate/source/update/common/readstrings.cxx new file mode 100644 index 000000000..e1366cddd --- /dev/null +++ b/onlineupdate/source/update/common/readstrings.cxx @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <limits.h> +#include <string.h> +#include <stdio.h> +#include "readstrings.h" +#include "errors.h" + +#ifdef _WIN32 +# define NS_tfopen _wfopen +# define OPEN_MODE L"rb" +#else +# define NS_tfopen fopen +# define OPEN_MODE "r" +#endif + +// stack based FILE wrapper to ensure that fclose is called. +class AutoFILE +{ +public: + explicit AutoFILE(FILE *fp) : fp_(fp) {} + ~AutoFILE() + { + if (fp_) fclose(fp_); + } + operator FILE *() + { + return fp_; + } +private: + FILE *fp_; +}; + +class AutoCharArray +{ +public: + explicit AutoCharArray(size_t len) + { + ptr_ = new char[len]; + } + ~AutoCharArray() + { + delete[] ptr_; + } + operator char *() + { + return ptr_; + } +private: + char *ptr_; +}; + +static const char kNL[] = "\r\n"; +static const char kEquals[] = "="; +static const char kWhitespace[] = " \t"; +static const char kRBracket[] = "]"; + +static const char* +NS_strspnp(const char *delims, const char *str) +{ + const char *d; + do + { + for (d = delims; *d != '\0'; ++d) + { + if (*str == *d) + { + ++str; + break; + } + } + } + while (*d); + + return str; +} + +static char* +NS_strtok(const char *delims, char **str) +{ + if (!*str) + return nullptr; + + char *ret = (char*) NS_strspnp(delims, *str); + + if (!*ret) + { + *str = ret; + return nullptr; + } + + char *i = ret; + do + { + for (const char *d = delims; *d != '\0'; ++d) + { + if (*i == *d) + { + *i = '\0'; + *str = ++i; + return ret; + } + } + ++i; + } + while (*i); + + *str = nullptr; + return ret; +} + +/** + * Find a key in a keyList containing zero-delimited keys ending with "\0\0". + * Returns a zero-based index of the key in the list, or -1 if the key is not found. + */ +static int +find_key(const char *keyList, char* key) +{ + if (!keyList) + return -1; + + int index = 0; + const char *p = keyList; + while (*p) + { + if (strcmp(key, p) == 0) + return index; + + p += strlen(p) + 1; + index++; + } + + // The key was not found if we came here + return -1; +} + +/** + * A very basic parser for updater.ini taken mostly from nsINIParser.cpp + * that can be used by standalone apps. + * + * @param path Path to the .ini file to read + * @param keyList List of zero-delimited keys ending with two zero characters + * @param numStrings Number of strings to read into results buffer - must be equal to the number of keys + * @param results Two-dimensional array of strings to be filled in the same order as the keys provided + * @param section Optional name of the section to read; defaults to "Strings" + */ +int +ReadStrings(const NS_tchar *path, + const char *keyList, + unsigned int numStrings, + char results[][MAX_TEXT_LEN], + const char *section) +{ + AutoFILE fp(NS_tfopen(path, OPEN_MODE)); + + if (!fp) + return READ_ERROR; + + /* get file size */ + if (fseek(fp, 0, SEEK_END) != 0) + return READ_ERROR; + + long len = ftell(fp); + if (len <= 0) + return READ_ERROR; + + size_t flen = size_t(len); + AutoCharArray fileContents(flen + 1); + if (!fileContents) + return READ_STRINGS_MEM_ERROR; + + /* read the file in one swoop */ + if (fseek(fp, 0, SEEK_SET) != 0) + return READ_ERROR; + + size_t rd = fread(fileContents, sizeof(char), flen, fp); + if (rd != flen) + return READ_ERROR; + + fileContents[flen] = '\0'; + + char *buffer = fileContents; + bool inStringsSection = false; + + unsigned int read = 0; + + while (char *token = NS_strtok(kNL, &buffer)) + { + if (token[0] == '#' || token[0] == ';') // it's a comment + continue; + + token = (char*) NS_strspnp(kWhitespace, token); + if (!*token) // empty line + continue; + + if (token[0] == '[') // section header! + { + ++token; + char const * currSection = token; + + char *rb = NS_strtok(kRBracket, &token); + if (!rb || NS_strtok(kWhitespace, &token)) + { + // there's either an unclosed [Section or a [Section]Moretext! + // we could frankly decide that this INI file is malformed right + // here and stop, but we won't... keep going, looking for + // a well-formed [section] to continue working with + inStringsSection = false; + } + else + { + if (section) + inStringsSection = strcmp(currSection, section) == 0; + else + inStringsSection = strcmp(currSection, "Strings") == 0; + } + + continue; + } + + if (!inStringsSection) + { + // If we haven't found a section header (or we found a malformed + // section header), or this isn't the [Strings] section don't bother + // parsing this line. + continue; + } + + char *key = token; + char *e = NS_strtok(kEquals, &token); + if (!e) + continue; + + int keyIndex = find_key(keyList, key); + if (keyIndex >= 0 && (unsigned int)keyIndex < numStrings) + { + strncpy(results[keyIndex], token, MAX_TEXT_LEN - 1); + results[keyIndex][MAX_TEXT_LEN - 1] = '\0'; + read++; + } + } + + return (read == numStrings) ? OK : PARSE_ERROR; +} + +// A wrapper function to read strings for the updater. +// Added for compatibility with the original code. +int +ReadStrings(const NS_tchar *path, StringTable *results) +{ + const unsigned int kNumStrings = 2; + const char *kUpdaterKeys = "Title\0Info\0"; + char updater_strings[kNumStrings][MAX_TEXT_LEN]; + + int result = ReadStrings(path, kUpdaterKeys, kNumStrings, updater_strings); + + strncpy(results->title, updater_strings[0], MAX_TEXT_LEN - 1); + results->title[MAX_TEXT_LEN - 1] = '\0'; + strncpy(results->info, updater_strings[1], MAX_TEXT_LEN - 1); + results->info[MAX_TEXT_LEN - 1] = '\0'; + + return result; +} diff --git a/onlineupdate/source/update/common/readstrings.h b/onlineupdate/source/update/common/readstrings.h new file mode 100644 index 000000000..747081394 --- /dev/null +++ b/onlineupdate/source/update/common/readstrings.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef READSTRINGS_H__ +#define READSTRINGS_H__ + +#define MAX_TEXT_LEN 600 + +#ifdef _WIN32 +# include <windows.h> +#endif + +#include "types.hxx" + +struct StringTable +{ + char title[MAX_TEXT_LEN]; + char info[MAX_TEXT_LEN]; +}; + +/** + * This function reads in localized strings from updater.ini + */ +int ReadStrings(const NS_tchar *path, StringTable *results); + +/** + * This function reads in localized strings corresponding to the keys from a given .ini + */ +int ReadStrings(const NS_tchar *path, + const char *keyList, + unsigned int numStrings, + char results[][MAX_TEXT_LEN], + const char *section = nullptr); + +#endif // READSTRINGS_H__ diff --git a/onlineupdate/source/update/common/sources.mozbuild b/onlineupdate/source/update/common/sources.mozbuild new file mode 100644 index 000000000..3de907b32 --- /dev/null +++ b/onlineupdate/source/update/common/sources.mozbuild @@ -0,0 +1,19 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +sources = [] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + sources += [ + 'pathhash.cpp', + 'uachelper.cpp', + 'updatehelper.cpp', + ] + +sources += [ + 'readstrings.cpp', + 'updatelogging.cpp', +] + +SOURCES += sorted(['%s/%s' % (srcdir, s) for s in sources]) diff --git a/onlineupdate/source/update/common/uachelper.cxx b/onlineupdate/source/update/common/uachelper.cxx new file mode 100644 index 000000000..831b12680 --- /dev/null +++ b/onlineupdate/source/update/common/uachelper.cxx @@ -0,0 +1,237 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#include <windows.h> +#include <wtsapi32.h> +#include "uachelper.h" +#include "updatelogging.h" + +// See the MSDN documentation with title: Privilege Constants +// At the time of this writing, this documentation is located at: +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb530716%28v=vs.85%29.aspx +LPCTSTR UACHelper::PrivsToDisable[] = +{ + SE_ASSIGNPRIMARYTOKEN_NAME, + SE_AUDIT_NAME, + SE_BACKUP_NAME, + // CreateProcess will succeed but the app will fail to launch on some WinXP + // machines if SE_CHANGE_NOTIFY_NAME is disabled. In particular this happens + // for limited user accounts on those machines. The define is kept here as a + // reminder that it should never be re-added. + // This permission is for directory watching but also from MSDN: "This + // privilege also causes the system to skip all traversal access checks." + // SE_CHANGE_NOTIFY_NAME, + SE_CREATE_GLOBAL_NAME, + SE_CREATE_PAGEFILE_NAME, + SE_CREATE_PERMANENT_NAME, + SE_CREATE_SYMBOLIC_LINK_NAME, + SE_CREATE_TOKEN_NAME, + SE_DEBUG_NAME, + SE_ENABLE_DELEGATION_NAME, + SE_IMPERSONATE_NAME, + SE_INC_BASE_PRIORITY_NAME, + SE_INCREASE_QUOTA_NAME, + SE_INC_WORKING_SET_NAME, + SE_LOAD_DRIVER_NAME, + SE_LOCK_MEMORY_NAME, + SE_MACHINE_ACCOUNT_NAME, + SE_MANAGE_VOLUME_NAME, + SE_PROF_SINGLE_PROCESS_NAME, + SE_RELABEL_NAME, + SE_REMOTE_SHUTDOWN_NAME, + SE_RESTORE_NAME, + SE_SECURITY_NAME, + SE_SHUTDOWN_NAME, + SE_SYNC_AGENT_NAME, + SE_SYSTEM_ENVIRONMENT_NAME, + SE_SYSTEM_PROFILE_NAME, + SE_SYSTEMTIME_NAME, + SE_TAKE_OWNERSHIP_NAME, + SE_TCB_NAME, + SE_TIME_ZONE_NAME, + SE_TRUSTED_CREDMAN_ACCESS_NAME, + SE_UNDOCK_NAME, + SE_UNSOLICITED_INPUT_NAME +}; + +/** + * Opens a user token for the given session ID + * + * @param sessionID The session ID for the token to obtain + * @return A handle to the token to obtain which will be primary if enough + * permissions exist. Caller should close the handle. + */ +HANDLE +UACHelper::OpenUserToken(DWORD sessionID) +{ + HMODULE module = LoadLibraryW(L"wtsapi32.dll"); + HANDLE token = nullptr; + decltype(WTSQueryUserToken)* wtsQueryUserToken = + (decltype(WTSQueryUserToken)*) GetProcAddress(module, "WTSQueryUserToken"); + if (wtsQueryUserToken) + { + wtsQueryUserToken(sessionID, &token); + } + FreeLibrary(module); + return token; +} + +/** + * Opens a linked token for the specified token. + * + * @param token The token to get the linked token from + * @return A linked token or nullptr if one does not exist. + * Caller should close the handle. + */ +HANDLE +UACHelper::OpenLinkedToken(HANDLE token) +{ + // Magic below... + // UAC creates 2 tokens. One is the restricted token which we have. + // the other is the UAC elevated one. Since we are running as a service + // as the system account we have access to both. + TOKEN_LINKED_TOKEN tlt; + HANDLE hNewLinkedToken = nullptr; + DWORD len; + if (GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, + &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) + { + token = tlt.LinkedToken; + hNewLinkedToken = token; + } + return hNewLinkedToken; +} + + +/** + * Enables or disables a privilege for the specified token. + * + * @param token The token to adjust the privilege on. + * @param priv The privilege to adjust. + * @param enable Whether to enable or disable it + * @return TRUE if the token was adjusted to the specified value. + */ +BOOL +UACHelper::SetPrivilege(HANDLE token, LPCTSTR priv, BOOL enable) +{ + LUID luidOfPriv; + if (!LookupPrivilegeValue(nullptr, priv, &luidOfPriv)) + { + return FALSE; + } + + TOKEN_PRIVILEGES tokenPriv; + tokenPriv.PrivilegeCount = 1; + tokenPriv.Privileges[0].Luid = luidOfPriv; + tokenPriv.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; + + SetLastError(ERROR_SUCCESS); + if (!AdjustTokenPrivileges(token, false, &tokenPriv, + sizeof(tokenPriv), nullptr, nullptr)) + { + return FALSE; + } + + return GetLastError() == ERROR_SUCCESS; +} + +/** + * For each privilege that is specified, an attempt will be made to + * drop the privilege. + * + * @param token The token to adjust the privilege on. + * Pass nullptr for current token. + * @param unneededPrivs An array of unneeded privileges. + * @param count The size of the array + * @return TRUE if there were no errors + */ +BOOL +UACHelper::DisableUnneededPrivileges(HANDLE token, + LPCTSTR *unneededPrivs, + size_t count) +{ + HANDLE obtainedToken = nullptr; + if (!token) + { + // Note: This handle is a pseudo-handle and need not be closed + HANDLE process = GetCurrentProcess(); + if (!OpenProcessToken(process, TOKEN_ALL_ACCESS_P, &obtainedToken)) + { + LOG_WARN(("Could not obtain token for current process, no " + "privileges changed. (%d)", GetLastError())); + return FALSE; + } + token = obtainedToken; + } + + BOOL result = TRUE; + for (size_t i = 0; i < count; i++) + { + if (SetPrivilege(token, unneededPrivs[i], FALSE)) + { + LOG(("Disabled unneeded token privilege: %s.", + unneededPrivs[i])); + } + else + { + LOG(("Could not disable token privilege value: %s. (%d)", + unneededPrivs[i], GetLastError())); + result = FALSE; + } + } + + if (obtainedToken) + { + CloseHandle(obtainedToken); + } + return result; +} + +/** + * Disables privileges for the specified token. + * The privileges to disable are in PrivsToDisable. + * In the future there could be new privs and we are not sure if we should + * explicitly disable these or not. + * + * @param token The token to drop the privilege on. + * Pass nullptr for current token. + * @return TRUE if there were no errors + */ +BOOL +UACHelper::DisablePrivileges(HANDLE token) +{ + static const size_t PrivsToDisableSize = + sizeof(UACHelper::PrivsToDisable) / sizeof(UACHelper::PrivsToDisable[0]); + + return DisableUnneededPrivileges(token, UACHelper::PrivsToDisable, + PrivsToDisableSize); +} + +/** + * Check if the current user can elevate. + * + * @return true if the user can elevate. + * false otherwise. + */ +bool +UACHelper::CanUserElevate() +{ + HANDLE token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) + { + return false; + } + + TOKEN_ELEVATION_TYPE elevationType; + DWORD len; + bool canElevate = GetTokenInformation(token, TokenElevationType, + &elevationType, + sizeof(elevationType), &len) && + (elevationType == TokenElevationTypeLimited); + CloseHandle(token); + + return canElevate; +} +#endif diff --git a/onlineupdate/source/update/common/uachelper.h b/onlineupdate/source/update/common/uachelper.h new file mode 100644 index 000000000..4e8d1c842 --- /dev/null +++ b/onlineupdate/source/update/common/uachelper.h @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _UACHELPER_H_ +#define _UACHELPER_H_ + +class UACHelper +{ +public: + static HANDLE OpenUserToken(DWORD sessionID); + static HANDLE OpenLinkedToken(HANDLE token); + static BOOL DisablePrivileges(HANDLE token); + static bool CanUserElevate(); + +private: + static BOOL SetPrivilege(HANDLE token, LPCTSTR privs, BOOL enable); + static BOOL DisableUnneededPrivileges(HANDLE token, + LPCTSTR *unneededPrivs, size_t count); + static LPCTSTR PrivsToDisable[]; +}; + +#endif diff --git a/onlineupdate/source/update/common/updatedefines.h b/onlineupdate/source/update/common/updatedefines.h new file mode 100644 index 000000000..748f9e098 --- /dev/null +++ b/onlineupdate/source/update/common/updatedefines.h @@ -0,0 +1,135 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef UPDATEDEFINES_H +#define UPDATEDEFINES_H + +#include "readstrings.h" + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# elif defined(MAX_PATH) +# define MAXPATHLEN MAX_PATH +# elif defined(_MAX_PATH) +# define MAXPATHLEN _MAX_PATH +# elif defined(CCHMAXPATH) +# define MAXPATHLEN CCHMAXPATH +# else +# define MAXPATHLEN 1024 +# endif +#endif + +#if defined(_WIN32) +# include <windows.h> +# include <shlwapi.h> +# include <direct.h> +# include <io.h> +# include <stdio.h> +# include <stdarg.h> + +# define F_OK 00 +# define W_OK 02 +# define R_OK 04 +# define S_ISDIR(s) (((s) & _S_IFMT) == _S_IFDIR) +# define S_ISREG(s) (((s) & _S_IFMT) == _S_IFREG) + +# define access _access + +# define putenv _putenv +# define DELETE_DIR L"tobedeleted" +# define CALLBACK_BACKUP_EXT L".moz-callback" + +# define LOG_S "%S" +# define NS_T(str) L ## str +# define NS_SLASH NS_T('\\') + +static inline int mywcsprintf(WCHAR* dest, size_t count, const WCHAR* fmt, ...) +{ + size_t _count = count - 1; + va_list varargs; + va_start(varargs, fmt); + int result = _vsnwprintf(dest, count - 1, fmt, varargs); + va_end(varargs); + dest[_count] = L'\0'; + return result; +} +#define NS_tsnprintf mywcsprintf +# define NS_taccess _waccess +# define NS_tchdir _wchdir +# define NS_tchmod _wchmod +# define NS_tfopen _wfopen +# define NS_tmkdir(path, perms) _wmkdir(path) +# define NS_tremove _wremove +// _wrename is used to avoid the link tracking service. +# define NS_trename _wrename +# define NS_trmdir _wrmdir +# define NS_tstat _wstat +# define NS_tlstat _wstat // No symlinks on Windows +# define NS_tstat_t _stat +# define NS_tstrcat wcscat +# define NS_tstrcmp wcscmp +# define NS_tstrncmp wcsncmp +# define NS_tstricmp wcsicmp +# define NS_tstrcpy wcscpy +# define NS_tstrncpy wcsncpy +# define NS_tstrlen wcslen +# define NS_tstrchr wcschr +# define NS_tstrrchr wcsrchr +# define NS_tstrstr wcsstr +# include "win_dirent.h" +# define NS_tDIR DIR +# define NS_tdirent dirent +# define NS_topendir opendir +# define NS_tclosedir closedir +# define NS_treaddir readdir +#else +# include <sys/wait.h> +# include <unistd.h> + +#ifdef __sun +# include <sys/stat.h> +#else +# include <fts.h> +#endif +# include <dirent.h> + +#ifdef MACOSX +# include <sys/time.h> +#endif + +# define LOG_S "%s" +# define NS_T(str) str +# define NS_SLASH NS_T('/') +# define NS_tsnprintf snprintf +# define NS_taccess access +# define NS_tchdir chdir +# define NS_tchmod chmod +# define NS_tfopen fopen +# define NS_tmkdir mkdir +# define NS_tremove remove +# define NS_trename rename +# define NS_trmdir rmdir +# define NS_tstat stat +# define NS_tstat_t stat +# define NS_tlstat lstat +# define NS_tstrcat strcat +# define NS_tstrcmp strcmp +# define NS_tstrncmp strncmp +# define NS_tstricmp strcasecmp +# define NS_tstrcpy strcpy +# define NS_tstrncpy strncpy +# define NS_tstrlen strlen +# define NS_tstrrchr strrchr +# define NS_tstrstr strstr +# define NS_tDIR DIR +# define NS_tdirent dirent +# define NS_topendir opendir +# define NS_tclosedir closedir +# define NS_treaddir readdir +#endif + +#define BACKUP_EXT NS_T(".moz-backup") + +#endif diff --git a/onlineupdate/source/update/common/updatehelper.cxx b/onlineupdate/source/update/common/updatehelper.cxx new file mode 100644 index 000000000..09f857b1a --- /dev/null +++ b/onlineupdate/source/update/common/updatehelper.cxx @@ -0,0 +1,807 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#include <windows.h> + +// Needed for CreateToolhelp32Snapshot +#include <tlhelp32.h> +#ifndef ONLY_SERVICE_LAUNCHING + +#include <stdio.h> +#include "shlobj.h" +#include "updatehelper.h" +#include "uachelper.h" +#include "pathhash.h" + +#include <memory> + +// Needed for PathAppendW +#include <shlwapi.h> + +BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra); + +/** + * Obtains the path of a file in the same directory as the specified file. + * + * @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result. + * @param siblingFIlePath The path of another file in the same directory + * @param newFileName The filename of another file in the same directory + * @return TRUE if successful + */ +BOOL +PathGetSiblingFilePath(LPWSTR destinationBuffer, + LPCWSTR siblingFilePath, + LPCWSTR newFileName) +{ + if (wcslen(siblingFilePath) >= MAX_PATH) + { + return FALSE; + } + + wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH); + if (!PathRemoveFileSpecW(destinationBuffer)) + { + return FALSE; + } + + if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) + { + return FALSE; + } + + return PathAppendSafe(destinationBuffer, newFileName); +} + +/** + * Launch the post update application as the specified user (helper.exe). + * It takes in the path of the callback application to calculate the path + * of helper.exe. For service updates this is called from both the system + * account and the current user account. + * + * @param installationDir The path to the callback application binary. + * @param updateInfoDir The directory where update info is stored. + * @param forceSync If true even if the ini file specifies async, the + * process will wait for termination of PostUpdate. + * @param userToken The user token to run as, if nullptr the current + * user will be used. + * @return TRUE if there was no error starting the process. + */ +BOOL +LaunchWinPostProcess(const WCHAR *installationDir, + const WCHAR *updateInfoDir, + bool forceSync, + HANDLE userToken) +{ + WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' }; + wcsncpy(workingDirectory, installationDir, MAX_PATH); + + // Launch helper.exe to perform post processing (e.g. registry and log file + // modifications) for the update. + WCHAR inifile[MAX_PATH + 1] = { L'\0' }; + wcsncpy(inifile, installationDir, MAX_PATH); + if (!PathAppendSafe(inifile, L"updater.ini")) + { + return FALSE; + } + + WCHAR exefile[MAX_PATH + 1]; + WCHAR exearg[MAX_PATH + 1]; + WCHAR exeasync[10]; + bool async = true; + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr, + exefile, MAX_PATH + 1, inifile)) + { + return FALSE; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg, + MAX_PATH + 1, inifile)) + { + return FALSE; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE", + exeasync, + sizeof(exeasync)/sizeof(exeasync[0]), + inifile)) + { + return FALSE; + } + + WCHAR exefullpath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(exefullpath, installationDir, MAX_PATH); + if (!PathAppendSafe(exefullpath, exefile)) + { + return false; + } + + WCHAR dlogFile[MAX_PATH + 1]; + if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) + { + return FALSE; + } + + WCHAR slogFile[MAX_PATH + 1] = { L'\0' }; + wcsncpy(slogFile, updateInfoDir, MAX_PATH); + if (!PathAppendSafe(slogFile, L"update.log")) + { + return FALSE; + } + + WCHAR dummyArg[14] = { L'\0' }; + wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1); + + size_t len = wcslen(exearg) + wcslen(dummyArg); + WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR)); + if (!cmdline) + { + return FALSE; + } + + wcsncpy(cmdline, dummyArg, len); + wcscat(cmdline, exearg); + + if (forceSync || + !_wcsnicmp(exeasync, L"false", 6) || + !_wcsnicmp(exeasync, L"0", 2)) + { + async = false; + } + + // We want to launch the post update helper app to update the Windows + // registry even if there is a failure with removing the uninstall.update + // file or copying the update.log file. + CopyFileW(slogFile, dlogFile, false); + + STARTUPINFOW si = {sizeof(si), 0}; + si.lpDesktop = L""; + PROCESS_INFORMATION pi = {0}; + + bool ok; + if (userToken) + { + ok = CreateProcessAsUserW(userToken, + exefullpath, + cmdline, + nullptr, // no special security attributes + nullptr, // no special thread attributes + false, // don't inherit filehandles + 0, // No special process creation flags + nullptr, // inherit my environment + workingDirectory, + &si, + &pi); + } + else + { + ok = CreateProcessW(exefullpath, + cmdline, + nullptr, // no special security attributes + nullptr, // no special thread attributes + false, // don't inherit filehandles + 0, // No special process creation flags + nullptr, // inherit my environment + workingDirectory, + &si, + &pi); + } + free(cmdline); + if (ok) + { + if (!async) + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return ok; +} + +/** + * Starts the upgrade process for update of the service if it is + * already installed. + * + * @param installDir the installation directory where + * maintenanceservice_installer.exe is located. + * @return TRUE if successful + */ +BOOL +StartServiceUpdate(LPCWSTR installDir) +{ + // Get a handle to the local computer SCM database + SC_HANDLE manager = OpenSCManager(nullptr, nullptr, + SC_MANAGER_ALL_ACCESS); + if (!manager) + { + return FALSE; + } + + // Open the service + SC_HANDLE svc = OpenServiceW(manager, SVC_NAME, + SERVICE_ALL_ACCESS); + if (!svc) + { + CloseServiceHandle(manager); + return FALSE; + } + + // If we reach here, then the service is installed, so + // proceed with upgrading it. + + CloseServiceHandle(manager); + + // The service exists and we opened it, get the config bytes needed + DWORD bytesNeeded; + if (!QueryServiceConfigW(svc, nullptr, 0, &bytesNeeded) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + CloseServiceHandle(svc); + return FALSE; + } + + // Get the service config information, in particular we want the binary + // path of the service. + std::unique_ptr<char[]> serviceConfigBuffer = std::make_unique<char[]>(bytesNeeded); + if (!QueryServiceConfigW(svc, + reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), + bytesNeeded, &bytesNeeded)) + { + CloseServiceHandle(svc); + return FALSE; + } + + CloseServiceHandle(svc); + + QUERY_SERVICE_CONFIGW &serviceConfig = + *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()); + + PathUnquoteSpacesW(serviceConfig.lpBinaryPathName); + + // Obtain the temp path of the maintenance service binary + WCHAR tmpService[MAX_PATH + 1] = { L'\0' }; + if (!PathGetSiblingFilePath(tmpService, serviceConfig.lpBinaryPathName, + L"maintenanceservice_tmp.exe")) + { + return FALSE; + } + + // Get the new maintenance service path from the install dir + WCHAR newMaintServicePath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(newMaintServicePath, installDir, MAX_PATH); + PathAppendSafe(newMaintServicePath, + L"maintenanceservice.exe"); + + // Copy the temp file in alongside the maintenance service. + // This is a requirement for maintenance service upgrades. + if (!CopyFileW(newMaintServicePath, tmpService, FALSE)) + { + return FALSE; + } + + // Start the upgrade comparison process + STARTUPINFOW si = {0}; + si.cb = sizeof(STARTUPINFOW); + // No particular desktop because no UI + si.lpDesktop = L""; + PROCESS_INFORMATION pi = {0}; + WCHAR cmdLine[64] = { '\0' }; + wcsncpy(cmdLine, L"dummyparam.exe upgrade", + sizeof(cmdLine) / sizeof(cmdLine[0]) - 1); + BOOL svcUpdateProcessStarted = CreateProcessW(tmpService, + cmdLine, + nullptr, nullptr, FALSE, + 0, + nullptr, installDir, &si, &pi); + if (svcUpdateProcessStarted) + { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return svcUpdateProcessStarted; +} + +#endif + +/** + * Executes a maintenance service command + * + * @param argc The total number of arguments in argv + * @param argv An array of null terminated strings to pass to the service, + * @return ERROR_SUCCESS if the service command was started. + * Less than 16000, a windows system error code from StartServiceW + * More than 20000, 20000 + the last state of the service constant if + * the last state is something other than stopped. + * 17001 if the SCM could not be opened + * 17002 if the service could not be opened +*/ +DWORD +StartServiceCommand(int argc, LPCWSTR* argv) +{ + DWORD lastState = WaitForServiceStop(SVC_NAME, 5); + if (lastState != SERVICE_STOPPED) + { + return 20000 + lastState; + } + + // Get a handle to the SCM database. + SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr, + SC_MANAGER_CONNECT | + SC_MANAGER_ENUMERATE_SERVICE); + if (!serviceManager) + { + return 17001; + } + + // Get a handle to the service. + SC_HANDLE service = OpenServiceW(serviceManager, + SVC_NAME, + SERVICE_START); + if (!service) + { + CloseServiceHandle(serviceManager); + return 17002; + } + + // Wait at most 5 seconds trying to start the service in case of errors + // like ERROR_SERVICE_DATABASE_LOCKED or ERROR_SERVICE_REQUEST_TIMEOUT. + const DWORD maxWaitMS = 5000; + DWORD currentWaitMS = 0; + DWORD lastError = ERROR_SUCCESS; + while (currentWaitMS < maxWaitMS) + { + BOOL result = StartServiceW(service, argc, argv); + if (result) + { + lastError = ERROR_SUCCESS; + break; + } + else + { + lastError = GetLastError(); + } + Sleep(100); + currentWaitMS += 100; + } + CloseServiceHandle(service); + CloseServiceHandle(serviceManager); + return lastError; +} + +#ifndef ONLY_SERVICE_LAUNCHING + +/** + * Launch a service initiated action for a software update with the + * specified arguments. + * + * @param exePath The path of the executable to run + * @param argc The total number of arguments in argv + * @param argv An array of null terminated strings to pass to the exePath, + * argv[0] must be the path to the updater.exe + * @return ERROR_SUCCESS if successful + */ +DWORD +LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR* argv) +{ + // The service command is the same as the updater.exe command line except + // it has 2 extra args: 1) The Path to updater.exe, and 2) the command + // being executed which is "software-update" + LPCWSTR *updaterServiceArgv = new LPCWSTR[argc + 2]; + updaterServiceArgv[0] = L"MozillaMaintenance"; + updaterServiceArgv[1] = L"software-update"; + + for (int i = 0; i < argc; ++i) + { + updaterServiceArgv[i + 2] = argv[i]; + } + + // Execute the service command by starting the service with + // the passed in arguments. + DWORD ret = StartServiceCommand(argc + 2, updaterServiceArgv); + delete[] updaterServiceArgv; + return ret; +} + +/** + * Joins a base directory path with a filename. + * + * @param base The base directory path of size MAX_PATH + 1 + * @param extra The filename to append + * @return TRUE if the file name was successful appended to base + */ +BOOL +PathAppendSafe(LPWSTR base, LPCWSTR extra) +{ + if (wcslen(base) + wcslen(extra) >= MAX_PATH) + { + return FALSE; + } + + return PathAppendW(base, extra); +} + +/** + * Sets update.status to pending so that the next startup will not use + * the service and instead will attempt an update the with a UAC prompt. + * + * @param updateDirPath The path of the update directory + * @return TRUE if successful + */ +BOOL +WriteStatusPending(LPCWSTR updateDirPath) +{ + WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); + if (!PathAppendSafe(updateStatusFilePath, L"update.status")) + { + return FALSE; + } + + const char pending[] = "pending"; + HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, + nullptr, CREATE_ALWAYS, 0, nullptr); + if (statusFile == INVALID_HANDLE_VALUE) + { + return FALSE; + } + + DWORD wrote; + BOOL ok = WriteFile(statusFile, pending, + sizeof(pending) - 1, &wrote, nullptr); + CloseHandle(statusFile); + return ok && (wrote == sizeof(pending) - 1); +} + +/** + * Sets update.status to a specific failure code + * + * @param updateDirPath The path of the update directory + * @return TRUE if successful + */ +BOOL +WriteStatusFailure(LPCWSTR updateDirPath, int errorCode) +{ + WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); + if (!PathAppendSafe(updateStatusFilePath, L"update.status")) + { + return FALSE; + } + + HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, + nullptr, CREATE_ALWAYS, 0, nullptr); + if (statusFile == INVALID_HANDLE_VALUE) + { + return FALSE; + } + char failure[32]; + sprintf(failure, "failed: %d", errorCode); + + DWORD toWrite = strlen(failure); + DWORD wrote; + BOOL ok = WriteFile(statusFile, failure, + toWrite, &wrote, nullptr); + CloseHandle(statusFile); + return ok && wrote == toWrite; +} + +#endif + +/** + * Waits for a service to enter a stopped state. + * This function does not stop the service, it just blocks until the service + * is stopped. + * + * @param serviceName The service to wait for. + * @param maxWaitSeconds The maximum number of seconds to wait + * @return state of the service after a timeout or when stopped. + * A value of 255 is returned for an error. Typical values are: + * SERVICE_STOPPED 0x00000001 + * SERVICE_START_PENDING 0x00000002 + * SERVICE_STOP_PENDING 0x00000003 + * SERVICE_RUNNING 0x00000004 + * SERVICE_CONTINUE_PENDING 0x00000005 + * SERVICE_PAUSE_PENDING 0x00000006 + * SERVICE_PAUSED 0x00000007 + * last status not set 0x000000CF + * Could no query status 0x000000DF + * Could not open service, access denied 0x000000EB + * Could not open service, invalid handle 0x000000EC + * Could not open service, invalid name 0x000000ED + * Could not open service, does not exist 0x000000EE + * Could not open service, other error 0x000000EF + * Could not open SCM, access denied 0x000000FD + * Could not open SCM, database does not exist 0x000000FE; + * Could not open SCM, other error 0x000000FF; + * Note: The strange choice of error codes above SERVICE_PAUSED are chosen + * in case Windows comes out with other service stats higher than 7, they + * would likely call it 8 and above. JS code that uses this in TestAUSHelper + * only handles values up to 255 so that's why we don't use GetLastError + * directly. + */ +DWORD +WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds) +{ + // 0x000000CF is defined above to be not set + DWORD lastServiceState = 0x000000CF; + + // Get a handle to the SCM database. + SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr, + SC_MANAGER_CONNECT | + SC_MANAGER_ENUMERATE_SERVICE); + if (!serviceManager) + { + DWORD lastError = GetLastError(); + switch (lastError) + { + case ERROR_ACCESS_DENIED: + return 0x000000FD; + case ERROR_DATABASE_DOES_NOT_EXIST: + return 0x000000FE; + default: + return 0x000000FF; + } + } + + // Get a handle to the service. + SC_HANDLE service = OpenServiceW(serviceManager, + serviceName, + SERVICE_QUERY_STATUS); + if (!service) + { + DWORD lastError = GetLastError(); + CloseServiceHandle(serviceManager); + switch (lastError) + { + case ERROR_ACCESS_DENIED: + return 0x000000EB; + case ERROR_INVALID_HANDLE: + return 0x000000EC; + case ERROR_INVALID_NAME: + return 0x000000ED; + case ERROR_SERVICE_DOES_NOT_EXIST: + return 0x000000EE; + default: + return 0x000000EF; + } + } + + DWORD currentWaitMS = 0; + SERVICE_STATUS_PROCESS ssp; + ssp.dwCurrentState = lastServiceState; + while (currentWaitMS < maxWaitSeconds * 1000) + { + DWORD bytesNeeded; + if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, + sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) + { + DWORD lastError = GetLastError(); + switch (lastError) + { + case ERROR_INVALID_HANDLE: + ssp.dwCurrentState = 0x000000D9; + break; + case ERROR_ACCESS_DENIED: + ssp.dwCurrentState = 0x000000DA; + break; + case ERROR_INSUFFICIENT_BUFFER: + ssp.dwCurrentState = 0x000000DB; + break; + case ERROR_INVALID_PARAMETER: + ssp.dwCurrentState = 0x000000DC; + break; + case ERROR_INVALID_LEVEL: + ssp.dwCurrentState = 0x000000DD; + break; + case ERROR_SHUTDOWN_IN_PROGRESS: + ssp.dwCurrentState = 0x000000DE; + break; + // These 3 errors can occur when the service is not yet stopped but + // it is stopping. + case ERROR_INVALID_SERVICE_CONTROL: + case ERROR_SERVICE_CANNOT_ACCEPT_CTRL: + case ERROR_SERVICE_NOT_ACTIVE: + currentWaitMS += 50; + Sleep(50); + continue; + default: + ssp.dwCurrentState = 0x000000DF; + } + + // We couldn't query the status so just break out + break; + } + + // The service is already in use. + if (ssp.dwCurrentState == SERVICE_STOPPED) + { + break; + } + currentWaitMS += 50; + Sleep(50); + } + + lastServiceState = ssp.dwCurrentState; + CloseServiceHandle(service); + CloseServiceHandle(serviceManager); + return lastServiceState; +} + +#ifndef ONLY_SERVICE_LAUNCHING + +/** + * Determines if there is at least one process running for the specified + * application. A match will be found across any session for any user. + * + * @param process The process to check for existence + * @return ERROR_NOT_FOUND if the process was not found + * ERROR_SUCCESS if the process was found and there were no errors + * Other Win32 system error code for other errors +**/ +DWORD +IsProcessRunning(LPCWSTR filename) +{ + // Take a snapshot of all processes in the system. + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (INVALID_HANDLE_VALUE == snapshot) + { + return GetLastError(); + } + + PROCESSENTRY32W processEntry; + processEntry.dwSize = sizeof(PROCESSENTRY32W); + if (!Process32FirstW(snapshot, &processEntry)) + { + DWORD lastError = GetLastError(); + CloseHandle(snapshot); + return lastError; + } + + do + { + if (wcsicmp(filename, processEntry.szExeFile) == 0) + { + CloseHandle(snapshot); + return ERROR_SUCCESS; + } + } + while (Process32NextW(snapshot, &processEntry)); + CloseHandle(snapshot); + return ERROR_NOT_FOUND; +} + +/** + * Waits for the specified application to exit. + * + * @param filename The application to wait for. + * @param maxSeconds The maximum amount of seconds to wait for all + * instances of the application to exit. + * @return ERROR_SUCCESS if no instances of the application exist + * WAIT_TIMEOUT if the process is still running after maxSeconds. + * Any other Win32 system error code. +*/ +DWORD +WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds) +{ + DWORD applicationRunningError = WAIT_TIMEOUT; + for (DWORD i = 0; i < maxSeconds; i++) + { + applicationRunningError = IsProcessRunning(filename); + if (ERROR_NOT_FOUND == applicationRunningError) + { + return ERROR_SUCCESS; + } + Sleep(1000); + } + + if (ERROR_SUCCESS == applicationRunningError) + { + return WAIT_TIMEOUT; + } + + return applicationRunningError; +} + +/** + * Determines if the fallback key exists or not + * + * @return TRUE if the fallback key exists and there was no error checking +*/ +BOOL +DoesFallbackKeyExist() +{ + HKEY testOnlyFallbackKey; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + TEST_ONLY_FALLBACK_KEY_PATH, 0, + KEY_READ | KEY_WOW64_64KEY, + &testOnlyFallbackKey) != ERROR_SUCCESS) + { + return FALSE; + } + + RegCloseKey(testOnlyFallbackKey); + return TRUE; +} + +#endif + +/** + * Determines if the file system for the specified file handle is local + * @param file path to check the filesystem type for, must be at most MAX_PATH + * @param isLocal out parameter which will hold TRUE if the drive is local + * @return TRUE if the call succeeded +*/ +BOOL +IsLocalFile(LPCWSTR file, BOOL &isLocal) +{ + WCHAR rootPath[MAX_PATH + 1] = { L'\0' }; + if (wcslen(file) > MAX_PATH) + { + return FALSE; + } + + wcsncpy(rootPath, file, MAX_PATH); + PathStripToRootW(rootPath); + isLocal = GetDriveTypeW(rootPath) == DRIVE_FIXED; + return TRUE; +} + + +/** + * Determines the DWORD value of a registry key value + * + * @param key The base key to where the value name exists + * @param valueName The name of the value + * @param retValue Out parameter which will hold the value + * @return TRUE on success +*/ +static BOOL +GetDWORDValue(HKEY key, LPCWSTR valueName, DWORD &retValue) +{ + DWORD regDWORDValueSize = sizeof(DWORD); + LONG retCode = RegQueryValueExW(key, valueName, 0, nullptr, + reinterpret_cast<LPBYTE>(&retValue), + ®DWORDValueSize); + return ERROR_SUCCESS == retCode; +} + +/** + * Determines if the system's elevation type allows + * unprompted elevation. + * + * @param isUnpromptedElevation Out parameter which specifies if unprompted + * elevation is allowed. + * @return TRUE if the user can actually elevate and the value was obtained + * successfully. +*/ +BOOL +IsUnpromptedElevation(BOOL &isUnpromptedElevation) +{ + if (!UACHelper::CanUserElevate()) + { + return FALSE; + } + + LPCWSTR UACBaseRegKey = + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"; + HKEY baseKey; + LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + UACBaseRegKey, 0, + KEY_READ, &baseKey); + if (retCode != ERROR_SUCCESS) + { + return FALSE; + } + + DWORD consent; + DWORD secureDesktop = 0; + BOOL success = GetDWORDValue(baseKey, L"ConsentPromptBehaviorAdmin", + consent); + success = success && + GetDWORDValue(baseKey, L"PromptOnSecureDesktop", secureDesktop); + isUnpromptedElevation = !consent && !secureDesktop; + + RegCloseKey(baseKey); + return success; +} +#endif diff --git a/onlineupdate/source/update/common/updatehelper.h b/onlineupdate/source/update/common/updatehelper.h new file mode 100644 index 000000000..b8c557bd6 --- /dev/null +++ b/onlineupdate/source/update/common/updatehelper.h @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +BOOL LaunchWinPostProcess(const WCHAR *installationDir, + const WCHAR *updateInfoDir, + bool forceSync, + HANDLE userToken); +BOOL StartServiceUpdate(LPCWSTR installDir); +BOOL GetUpdateDirectoryPath(LPWSTR path); +DWORD LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR *argv); +BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode); +BOOL WriteStatusPending(LPCWSTR updateDirPath); +DWORD WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds); +DWORD WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds); +BOOL DoesFallbackKeyExist(); +BOOL IsLocalFile(LPCWSTR file, BOOL &isLocal); +DWORD StartServiceCommand(int argc, LPCWSTR* argv); +BOOL IsUnpromptedElevation(BOOL &isUnpromptedElevation); + +#define SVC_NAME L"LibreOfficeMaintenance" + +#define BASE_SERVICE_REG_KEY \ + L"SOFTWARE\\LibreOffice\\MaintenanceService" + +// The test only fallback key, as its name implies, is only present on machines +// that will use automated tests. Since automated tests always run from a +// different directory for each test, the presence of this key bypasses the +// "This is a valid installation directory" check. This key also stores +// the allowed name and issuer for cert checks so that the cert check +// code can still be run unchanged. +#define TEST_ONLY_FALLBACK_KEY_PATH \ + BASE_SERVICE_REG_KEY L"\\3932ecacee736d366d6436db0f55bce4" + diff --git a/onlineupdate/source/update/common/updatelogging.cxx b/onlineupdate/source/update/common/updatelogging.cxx new file mode 100644 index 000000000..ed055cd47 --- /dev/null +++ b/onlineupdate/source/update/common/updatelogging.cxx @@ -0,0 +1,85 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(_WIN32) +#include <windows.h> +#endif + + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> + +#include "updatelogging.h" + +UpdateLog::UpdateLog() + : logFP(nullptr) + , sourcePath(nullptr) +{ +} + +void UpdateLog::Init(NS_tchar* sourcePathParam, + const NS_tchar* fileName, + const NS_tchar* alternateFileName, + bool append) +{ + if (logFP) + return; + + sourcePath = sourcePathParam; + NS_tchar logFile[MAXPATHLEN]; + NS_tsnprintf(logFile, sizeof(logFile)/sizeof(logFile[0]), + NS_T("%s/%s"), sourcePathParam, fileName); + + if (alternateFileName && NS_taccess(logFile, F_OK)) + { + NS_tsnprintf(logFile, sizeof(logFile)/sizeof(logFile[0]), + NS_T("%s/%s"), sourcePathParam, alternateFileName); + } + + logFP = NS_tfopen(logFile, append ? NS_T("a") : NS_T("w")); +} + +void UpdateLog::Finish() +{ + if (!logFP) + return; + + fclose(logFP); + logFP = nullptr; +} + +void UpdateLog::Flush() +{ + if (!logFP) + return; + + fflush(logFP); +} + +void UpdateLog::Printf(const char *fmt, ... ) +{ + if (!logFP) + return; + + va_list ap; + va_start(ap, fmt); + vfprintf(logFP, fmt, ap); + fprintf(logFP, "\n"); + va_end(ap); +} + +void UpdateLog::WarnPrintf(const char *fmt, ... ) +{ + if (!logFP) + return; + + va_list ap; + va_start(ap, fmt); + fprintf(logFP, "*** Warning: "); + vfprintf(logFP, fmt, ap); + fprintf(logFP, "***\n"); + va_end(ap); +} diff --git a/onlineupdate/source/update/common/updatelogging.h b/onlineupdate/source/update/common/updatelogging.h new file mode 100644 index 000000000..591cb3f4f --- /dev/null +++ b/onlineupdate/source/update/common/updatelogging.h @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef UPDATELOGGING_H +#define UPDATELOGGING_H + +#include "updatedefines.h" +#include <stdio.h> + +class UpdateLog +{ +public: + static UpdateLog & GetPrimaryLog() + { + static UpdateLog primaryLog; + return primaryLog; + } + + void Init(NS_tchar* sourcePath, const NS_tchar* fileName, + const NS_tchar* alternateFileName, bool append); + void Finish(); + void Flush(); + void Printf(const char *fmt, ... ); + void WarnPrintf(const char *fmt, ... ); + + ~UpdateLog() + { + Finish(); + } + +protected: + UpdateLog(); + FILE *logFP; + NS_tchar* sourcePath; +}; + +#define LOG_WARN(args) UpdateLog::GetPrimaryLog().WarnPrintf args +#define LOG(args) UpdateLog::GetPrimaryLog().Printf args +#define LogInit(PATHNAME_, FILENAME_) \ + UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_, 0, false) +#define LogInitAppend(PATHNAME_, FILENAME_, ALTERNATE_) \ + UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_, ALTERNATE_, true) +#define LogFinish() UpdateLog::GetPrimaryLog().Finish() +#define LogFlush() UpdateLog::GetPrimaryLog().Flush() + +#endif diff --git a/onlineupdate/source/update/common/win_dirent.h b/onlineupdate/source/update/common/win_dirent.h new file mode 100644 index 000000000..fff13f9e0 --- /dev/null +++ b/onlineupdate/source/update/common/win_dirent.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WINDIRENT_H__ +#define WINDIRENT_H__ +#ifdef _WIN32 +#include <windows.h> + +struct DIR +{ + explicit DIR(const WCHAR* path); + ~DIR(); + HANDLE findHandle; + WCHAR name[MAX_PATH]; +}; + +struct dirent +{ + dirent(); + WCHAR d_name[MAX_PATH]; +}; + +DIR* opendir(const WCHAR* path); +int closedir(DIR* dir); +dirent* readdir(DIR* dir); + +#endif // WNT +#endif // WINDIRENT_H__ diff --git a/onlineupdate/source/update/updater/Makefile.in b/onlineupdate/source/update/updater/Makefile.in new file mode 100644 index 000000000..57813b36a --- /dev/null +++ b/onlineupdate/source/update/updater/Makefile.in @@ -0,0 +1,28 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# For changes here, also consider ./updater-xpcshell/Makefile.in + +# This is how the xpcshellCertificate.der file is generated, in case we ever +# have to regenerate it. +# ./certutil -L -d modules/libmar/tests/unit/data -n mycert -r > xpcshellCertificate.der +xpcshellCert.h: xpcshellCertificate.der + +ifdef MOZ_WIDGET_GTK +libs:: updater.png + $(NSINSTALL) -D $(DIST)/bin/icons + $(INSTALL) $(IFLAGS1) $^ $(DIST)/bin/icons +endif + +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) +libs:: + $(NSINSTALL) -D $(DIST)/bin/updater.app + rsync -a -C --exclude '*.in' $(srcdir)/macbuild/Contents $(DIST)/bin/updater.app + sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \ + iconv -f UTF-8 -t UTF-16 > $(DIST)/bin/updater.app/Contents/Resources/English.lproj/InfoPlist.strings + $(NSINSTALL) -D $(DIST)/bin/updater.app/Contents/MacOS + $(NSINSTALL) $(DIST)/bin/updater $(DIST)/bin/updater.app/Contents/MacOS + rm -f $(DIST)/bin/updater +endif diff --git a/onlineupdate/source/update/updater/archivereader.cxx b/onlineupdate/source/update/updater/archivereader.cxx new file mode 100644 index 000000000..d66921143 --- /dev/null +++ b/onlineupdate/source/update/updater/archivereader.cxx @@ -0,0 +1,349 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include "bzlib.h" +#include "archivereader.h" +#include "errors.h" +#ifdef _WIN32 +#include "updatehelper.h" +#endif + +// These are generated at compile time based on the DER file for the channel +// being used +#ifdef VERIFY_MAR_SIGNATURE +#ifdef TEST_UPDATER +#include "../xpcshellCert.h" +#else +#include "onlineupdate/primaryCert.h" +#include "onlineupdate/secondaryCert.h" +#endif +#endif + +#if defined(_WIN32) +#define UPDATER_NO_STRING_GLUE_STL +#endif +#include "nsVersionComparator.h" +#undef UPDATER_NO_STRING_GLUE_STL + +#if defined(UNIX) +# include <sys/types.h> +#elif defined(_WIN32) +# include <io.h> +#endif + +static int inbuf_size = 262144; +static int outbuf_size = 262144; +static char *inbuf = nullptr; +static char *outbuf = nullptr; + +/** + * Performs a verification on the opened MAR file with the passed in + * certificate name ID and type ID. + * + * @param archive The MAR file to verify the signature on. + * @param certData The certificate data. + * @return OK on success, CERT_VERIFY_ERROR on failure. +*/ +template<uint32_t SIZE> +int +VerifyLoadedCert(MarFile *archive, const uint8_t (&certData)[SIZE]) +{ + (void)archive; + (void)certData; + +#ifdef VERIFY_MAR_SIGNATURE + const uint32_t size = SIZE; + const uint8_t* const data = &certData[0]; + if (mar_verify_signatures(archive, &data, &size, 1)) + { + return CERT_VERIFY_ERROR; + } +#endif + + return OK; +} + +/** + * Performs a verification on the opened MAR file. Both the primary and backup + * keys stored are stored in the current process and at least the primary key + * will be tried. Success will be returned as long as one of the two + * signatures verify. + * + * @return OK on success +*/ +int +ArchiveReader::VerifySignature() +{ + if (!mArchive) + { + return ARCHIVE_NOT_OPEN; + } + +#ifndef VERIFY_MAR_SIGNATURE + return OK; +#else +#ifdef TEST_UPDATER + int rv = VerifyLoadedCert(mArchive, xpcshellCertData); +#else + int rv = VerifyLoadedCert(mArchive, primaryCertData); + if (rv != OK) + { + rv = VerifyLoadedCert(mArchive, secondaryCertData); + } +#endif + return rv; +#endif +} + +/** + * Verifies that the MAR file matches the current product, channel, and version + * + * @param MARChannelID The MAR channel name to use, only updates from MARs + * with a matching MAR channel name will succeed. + * If an empty string is passed, no check will be done + * for the channel name in the product information block. + * If a comma separated list of values is passed then + * one value must match. + * @param appVersion The application version to use, only MARs with an + * application version >= to appVersion will be applied. + * @return OK on success + * COULD_NOT_READ_PRODUCT_INFO_BLOCK if the product info block + * could not be read. + * MARCHANNEL_MISMATCH_ERROR if update-settings.ini's MAR + * channel ID doesn't match the MAR + * file's MAR channel ID. + * VERSION_DOWNGRADE_ERROR if the application version for + * this updater is newer than the + * one in the MAR. + */ +int +ArchiveReader::VerifyProductInformation(const char *MARChannelID, + const char *appVersion) +{ + if (!mArchive) + { + return ARCHIVE_NOT_OPEN; + } + + ProductInformationBlock productInfoBlock; + int rv = mar_read_product_info_block(mArchive, + &productInfoBlock); + if (rv != OK) + { + return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR; + } + + // Only check the MAR channel name if specified, it should be passed in from + // the update-settings.ini file. + if (MARChannelID && strlen(MARChannelID)) + { + // Check for at least one match in the comma separated list of values. + const char *delimiter = " ,\t"; + // Make a copy of the string in case a read only memory buffer + // was specified. strtok modifies the input buffer. + char channelCopy[512] = { 0 }; + strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1); + char *channel = strtok(channelCopy, delimiter); + rv = MAR_CHANNEL_MISMATCH_ERROR; + while (channel) + { + if (!strcmp(channel, productInfoBlock.MARChannelID)) + { + rv = OK; + break; + } + channel = strtok(nullptr, delimiter); + } + } + + if (rv == OK) + { + /* Compare both versions to ensure we don't have a downgrade + -1 if appVersion is older than productInfoBlock.productVersion + 1 if appVersion is newer than productInfoBlock.productVersion + 0 if appVersion is the same as productInfoBlock.productVersion + This even works with strings like: + - 12.0a1 being older than 12.0a2 + - 12.0a2 being older than 12.0b1 + - 12.0a1 being older than 12.0 + - 12.0 being older than 12.1a1 */ + int versionCompareResult = + mozilla::CompareVersions(appVersion, productInfoBlock.productVersion); + if (1 == versionCompareResult) + { + rv = VERSION_DOWNGRADE_ERROR; + } + } + + free((void *)productInfoBlock.MARChannelID); + free((void *)productInfoBlock.productVersion); + return rv; +} + +int +ArchiveReader::Open(const NS_tchar *path) +{ + if (mArchive) + Close(); + + if (!inbuf) + { + inbuf = (char *)malloc(inbuf_size); + if (!inbuf) + { + // Try again with a smaller buffer. + inbuf_size = 1024; + inbuf = (char *)malloc(inbuf_size); + if (!inbuf) + return ARCHIVE_READER_MEM_ERROR; + } + } + + if (!outbuf) + { + outbuf = (char *)malloc(outbuf_size); + if (!outbuf) + { + // Try again with a smaller buffer. + outbuf_size = 1024; + outbuf = (char *)malloc(outbuf_size); + if (!outbuf) + return ARCHIVE_READER_MEM_ERROR; + } + } + +#ifdef _WIN32 + mArchive = mar_wopen(path); +#else + mArchive = mar_open(path); +#endif + if (!mArchive) + return READ_ERROR; + + return OK; +} + +void +ArchiveReader::Close() +{ + if (mArchive) + { + mar_close(mArchive); + mArchive = nullptr; + } + + if (inbuf) + { + free(inbuf); + inbuf = nullptr; + } + + if (outbuf) + { + free(outbuf); + outbuf = nullptr; + } +} + +int +ArchiveReader::ExtractFile(const char *name, const NS_tchar *dest) +{ + const MarItem *item = mar_find_item(mArchive, name); + if (!item) + return READ_ERROR; + +#ifdef _WIN32 + FILE* fp = _wfopen(dest, L"wb+"); +#else + int fd = creat(dest, item->flags); + if (fd == -1) + return WRITE_ERROR; + + FILE *fp = fdopen(fd, "wb"); +#endif + if (!fp) + return WRITE_ERROR; + + int rv = ExtractItemToStream(item, fp); + + fclose(fp); + return rv; +} + +int +ArchiveReader::ExtractFileToStream(const char *name, FILE *fp) +{ + const MarItem *item = mar_find_item(mArchive, name); + if (!item) + return READ_ERROR; + + return ExtractItemToStream(item, fp); +} + +int +ArchiveReader::ExtractItemToStream(const MarItem *item, FILE *fp) +{ + /* decompress the data chunk by chunk */ + + bz_stream strm; + int offset, inlen, ret = OK; + + memset(&strm, 0, sizeof(strm)); + if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK) + return UNEXPECTED_BZIP_ERROR; + + offset = 0; + for (;;) + { + if (!item->length) + { + ret = UNEXPECTED_MAR_ERROR; + break; + } + + if (offset < (int) item->length && strm.avail_in == 0) + { + inlen = mar_read(mArchive, item, offset, inbuf, inbuf_size); + if (inlen <= 0) + return READ_ERROR; + offset += inlen; + strm.next_in = inbuf; + strm.avail_in = inlen; + } + + strm.next_out = outbuf; + strm.avail_out = outbuf_size; + + ret = BZ2_bzDecompress(&strm); + if (ret != BZ_OK && ret != BZ_STREAM_END) + { + ret = UNEXPECTED_BZIP_ERROR; + break; + } + + int outlen = outbuf_size - strm.avail_out; + if (outlen) + { + if (fwrite(outbuf, outlen, 1, fp) != 1) + { + ret = WRITE_ERROR_EXTRACT; + break; + } + } + + if (ret == BZ_STREAM_END) + { + ret = OK; + break; + } + } + + BZ2_bzDecompressEnd(&strm); + return ret; +} diff --git a/onlineupdate/source/update/updater/archivereader.h b/onlineupdate/source/update/updater/archivereader.h new file mode 100644 index 000000000..090b787f9 --- /dev/null +++ b/onlineupdate/source/update/updater/archivereader.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ArchiveReader_h__ +#define ArchiveReader_h__ + +#include <stdio.h> +#include <onlineupdate/mar.h> +#include "types.hxx" + +// This class provides an API to extract files from an update archive. +class ArchiveReader +{ +public: + ArchiveReader() : mArchive(nullptr) {} + ~ArchiveReader() + { + Close(); + } + + int Open(const NS_tchar *path); + int VerifySignature(); + int VerifyProductInformation(const char *MARChannelID, + const char *appVersion); + void Close(); + + int ExtractFile(const char *item, const NS_tchar *destination); + int ExtractFileToStream(const char *item, FILE *fp); + +private: + int ExtractItemToStream(const MarItem *item, FILE *fp); + + MarFile *mArchive; +}; + +#endif // ArchiveReader_h__ diff --git a/onlineupdate/source/update/updater/bspatch.cxx b/onlineupdate/source/update/updater/bspatch.cxx new file mode 100644 index 000000000..219c4d74c --- /dev/null +++ b/onlineupdate/source/update/updater/bspatch.cxx @@ -0,0 +1,198 @@ +/*- + * Copyright 2003,2004 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Changelog: + * 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to + * the header, and make all the types 32-bit. + * --Benjamin Smedberg <benjamin@smedbergs.us> + */ + +#include "bspatch.h" +#include "errors.h" + +#include <algorithm> +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <string.h> +#include <limits.h> + +#if defined(_WIN32) +# include <io.h> +#else +# include <unistd.h> +#endif + +#ifdef _WIN32 +# include <winsock2.h> +#else +# include <arpa/inet.h> +#endif + +#ifndef SSIZE_MAX +# define SSIZE_MAX LONG_MAX +#endif + +int +MBS_ReadHeader(FILE* file, MBSPatchHeader *header) +{ + size_t s = fread(header, 1, sizeof(MBSPatchHeader), file); + if (s != sizeof(MBSPatchHeader)) + return READ_ERROR; + + header->slen = ntohl(header->slen); + header->scrc32 = ntohl(header->scrc32); + header->dlen = ntohl(header->dlen); + header->cblen = ntohl(header->cblen); + header->difflen = ntohl(header->difflen); + header->extralen = ntohl(header->extralen); + + struct stat hs; + s = fstat(fileno(file), &hs); + if (s) + return READ_ERROR; + + if (memcmp(header->tag, "MBDIFF10", 8) != 0) + return UNEXPECTED_BSPATCH_ERROR; + + if (sizeof(MBSPatchHeader) + + header->cblen + + header->difflen + + header->extralen != uint32_t(hs.st_size)) + return UNEXPECTED_BSPATCH_ERROR; + + return OK; +} + +int +MBS_ApplyPatch(const MBSPatchHeader *header, FILE* patchFile, + unsigned char *fbuffer, FILE* file) +{ + unsigned char *fbufend = fbuffer + header->slen; + + unsigned char *buf = (unsigned char*) malloc(header->cblen + + header->difflen + + header->extralen); + if (!buf) + return BSPATCH_MEM_ERROR; + + int rv = OK; + + size_t r = header->cblen + header->difflen + header->extralen; + unsigned char *wb = buf; + while (r) + { + const size_t count = std::min(r, size_t(SSIZE_MAX)); + size_t c = fread(wb, 1, count, patchFile); + if (c != count) + { + rv = READ_ERROR; + goto end; + } + + r -= c; + wb += c; + } + + { + MBSPatchTriple *ctrlsrc = (MBSPatchTriple*) buf; + unsigned char *diffsrc = buf + header->cblen; + unsigned char *extrasrc = diffsrc + header->difflen; + + MBSPatchTriple *ctrlend = (MBSPatchTriple*) diffsrc; + unsigned char *diffend = extrasrc; + unsigned char *extraend = extrasrc + header->extralen; + + do + { + ctrlsrc->x = ntohl(ctrlsrc->x); + ctrlsrc->y = ntohl(ctrlsrc->y); + ctrlsrc->z = ntohl(ctrlsrc->z); + +#ifdef DEBUG_bsmedberg + printf("Applying block:\n" + " x: %u\n" + " y: %u\n" + " z: %i\n", + ctrlsrc->x, + ctrlsrc->y, + ctrlsrc->z); +#endif + + /* Add x bytes from oldfile to x bytes from the diff block */ + + if (fbuffer + ctrlsrc->x > fbufend || + diffsrc + ctrlsrc->x > diffend) + { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + for (uint32_t i = 0; i < ctrlsrc->x; ++i) + { + diffsrc[i] += fbuffer[i]; + } + if ((uint32_t) fwrite(diffsrc, 1, ctrlsrc->x, file) != ctrlsrc->x) + { + rv = WRITE_ERROR_PATCH_FILE; + goto end; + } + fbuffer += ctrlsrc->x; + diffsrc += ctrlsrc->x; + + /* Copy y bytes from the extra block */ + + if (extrasrc + ctrlsrc->y > extraend) + { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + if ((uint32_t) fwrite(extrasrc, 1, ctrlsrc->y, file) != ctrlsrc->y) + { + rv = WRITE_ERROR_PATCH_FILE; + goto end; + } + extrasrc += ctrlsrc->y; + + /* "seek" forwards in oldfile by z bytes */ + + if (fbuffer + ctrlsrc->z > fbufend) + { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + fbuffer += ctrlsrc->z; + + /* and on to the next control block */ + + ++ctrlsrc; + } + while (ctrlsrc < ctrlend); + } + +end: + free(buf); + return rv; +} diff --git a/onlineupdate/source/update/updater/gen_cert_header.py b/onlineupdate/source/update/updater/gen_cert_header.py new file mode 100755 index 000000000..3f3798cfb --- /dev/null +++ b/onlineupdate/source/update/updater/gen_cert_header.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +from __future__ import print_function +import os +import sys +import binascii + +try: + from configparser import ConfigParser +except ImportError: + from ConfigParser import SafeConfigParser as ConfigParser + +def file_byte_generator(filename): + with open(filename, "rb") as f: + block = f.read() + return block + +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + +def create_header(array_name, in_filename): + if sys.version_info >= (3,0): + hexified = ["0x" + binascii.hexlify(bytes([inp])).decode('ascii') for inp in file_byte_generator(in_filename)] + else: + hexified = ["0x" + binascii.hexlify(inp).decode('ascii') for inp in file_byte_generator(in_filename)] + print("const uint8_t " + array_name + "[] = {") + print(", ".join(hexified)) + print("};") + return 0 + +if __name__ == '__main__': + if len(sys.argv) < 3: + eprint('ERROR: usage: gen_cert_header.py array_name update_config_file') + sys.exit(1) + + if not os.path.exists(sys.argv[2]): + eprint('The config file %s does not exist'%(sys.argv[2])) + sys.exit(1) + + config = ConfigParser() + config.read(sys.argv[2]) + sys.exit(create_header(sys.argv[1], config.get('Updater', 'certificate-der'))) diff --git a/onlineupdate/source/update/updater/launchchild_osx.mm b/onlineupdate/source/update/updater/launchchild_osx.mm new file mode 100644 index 000000000..9e4e08d37 --- /dev/null +++ b/onlineupdate/source/update/updater/launchchild_osx.mm @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <Cocoa/Cocoa.h> +#include <CoreServices/CoreServices.h> +#include <crt_externs.h> +#include <stdlib.h> +#include <stdio.h> +#include <spawn.h> +#include "readstrings.h" + +// Prefer the currently running architecture (this is the same as the +// architecture that launched the updater) and fallback to CPU_TYPE_ANY if it +// is no longer available after the update. +static cpu_type_t pref_cpu_types[2] = { +#if defined(__i386__) + CPU_TYPE_X86, +#elif defined(__x86_64__) + CPU_TYPE_X86_64, +#elif defined(__ppc__) + CPU_TYPE_POWERPC, +#endif + CPU_TYPE_ANY }; + +void LaunchChild(int argc, char **argv) +{ + // Initialize spawn attributes. + posix_spawnattr_t spawnattr; + if (posix_spawnattr_init(&spawnattr) != 0) { + printf("Failed to init posix spawn attribute."); + return; + } + + // Set spawn attributes. + size_t attr_count = 2; + size_t attr_ocount = 0; + if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, &attr_ocount) != 0 || + attr_ocount != attr_count) { + printf("Failed to set binary preference on posix spawn attribute."); + posix_spawnattr_destroy(&spawnattr); + return; + } + + // "posix_spawnp" uses null termination for arguments rather than a count. + // Note that we are not duplicating the argument strings themselves. + char** argv_copy = (char**)malloc((argc + 1) * sizeof(char*)); + if (!argv_copy) { + printf("Failed to allocate memory for arguments."); + posix_spawnattr_destroy(&spawnattr); + return; + } + for (int i = 0; i < argc; i++) { + argv_copy[i] = argv[i]; + } + argv_copy[argc] = NULL; + + // Pass along our environment. + char** envp = NULL; + char*** cocoaEnvironment = _NSGetEnviron(); + if (cocoaEnvironment) { + envp = *cocoaEnvironment; + } + + int result = posix_spawnp(NULL, argv_copy[0], NULL, &spawnattr, argv_copy, envp); + + free(argv_copy); + posix_spawnattr_destroy(&spawnattr); + + if (result != 0) { + printf("Process spawn failed with code %d!", result); + } +} + +void +LaunchMacPostProcess(const char* aAppBundle) +{ + // Launch helper to perform post processing for the update; this is the Mac + // analogue of LaunchWinPostProcess (PostUpdateWin). + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString* iniPath = [NSString stringWithUTF8String:aAppBundle]; + iniPath = + [iniPath stringByAppendingPathComponent:@"Contents/Resources/updater.ini"]; + + NSFileManager* fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:iniPath]) { + // the file does not exist; there is nothing to run + [pool release]; + return; + } + + int readResult; + char values[2][MAX_TEXT_LEN]; + readResult = ReadStrings([iniPath UTF8String], + "ExeRelPath\0ExeArg\0", + 2, + values, + "PostUpdateMac"); + if (readResult) { + [pool release]; + return; + } + + NSString *exeRelPath = [NSString stringWithUTF8String:values[0]]; + NSString *exeArg = [NSString stringWithUTF8String:values[1]]; + if (!exeArg || !exeRelPath) { + [pool release]; + return; + } + + NSString* exeFullPath = [NSString stringWithUTF8String:aAppBundle]; + exeFullPath = [exeFullPath stringByAppendingPathComponent:exeRelPath]; + + char optVals[1][MAX_TEXT_LEN]; + readResult = ReadStrings([iniPath UTF8String], + "ExeAsync\0", + 1, + optVals, + "PostUpdateMac"); + + NSTask *task = [[NSTask alloc] init]; + [task setLaunchPath:exeFullPath]; + [task setArguments:[NSArray arrayWithObject:exeArg]]; + [task launch]; + if (!readResult) { + NSString *exeAsync = [NSString stringWithUTF8String:optVals[0]]; + if ([exeAsync isEqualToString:@"false"]) { + [task waitUntilExit]; + } + } + // ignore the return value of the task, there's nothing we can do with it + [task release]; + + [pool release]; +} diff --git a/onlineupdate/source/update/updater/loaddlls.cxx b/onlineupdate/source/update/updater/loaddlls.cxx new file mode 100644 index 000000000..6a0c8a61e --- /dev/null +++ b/onlineupdate/source/update/updater/loaddlls.cxx @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#ifdef _WIN32 +#ifndef UNICODE +#define UNICODE +#endif +#include <windows.h> + +// Delayed load libraries are loaded when the first symbol is used. +// The following ensures that we load the delayed loaded libraries from the +// system directory. +struct AutoLoadSystemDependencies +{ + AutoLoadSystemDependencies() + { + // Remove the current directory from the search path for dynamically loaded + // DLLs as a precaution. This call has no effect for delay load DLLs. + SetDllDirectory(L""); + + HMODULE module = ::GetModuleHandleW(L"kernel32.dll"); + if (module) + { + // SetDefaultDllDirectories is always available on Windows 8 and above. It + // is also available on Windows Vista, Windows Server 2008, and + // Windows 7 when MS KB2533623 has been applied. + decltype(SetDefaultDllDirectories)* setDefaultDllDirectories = + (decltype(SetDefaultDllDirectories)*) GetProcAddress(module, "SetDefaultDllDirectories"); + if (setDefaultDllDirectories) + { + setDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32); + return; + } + } + + // TODO: moggi: do we need all that code? + // When SetDefaultDllDirectories is not available, fallback to preloading + // dlls. The order that these are loaded does not matter since they are + // loaded using the LOAD_WITH_ALTERED_SEARCH_PATH flag. +#ifdef HAVE_64BIT_BUILD + // DLLs for Firefox x64 on Windows 7 (x64). + // Note: dwmapi.dll is preloaded since a crash will try to load it from the + // application's directory. + static LPCWSTR delayDLLs[] = { L"apphelp.dll", + L"cryptbase.dll", + L"cryptsp.dll", + L"dwmapi.dll", + L"mpr.dll", + L"ntmarta.dll", + L"profapi.dll", + L"propsys.dll", + L"sspicli.dll", + L"wsock32.dll" + }; + +#else + // DLLs for Firefox x86 on Windows XP through Windows 7 (x86 and x64). + // Note: dwmapi.dll is preloaded since a crash will try to load it from the + // application's directory. + static LPCWSTR delayDLLs[] = { L"apphelp.dll", + L"crypt32.dll", + L"cryptbase.dll", + L"cryptsp.dll", + L"dwmapi.dll", + L"mpr.dll", + L"msasn1.dll", + L"ntmarta.dll", + L"profapi.dll", + L"propsys.dll", + L"psapi.dll", + L"secur32.dll", + L"sspicli.dll", + L"userenv.dll", + L"uxtheme.dll", + L"ws2_32.dll", + L"ws2help.dll", + L"wsock32.dll" + }; +#endif + + WCHAR systemDirectory[MAX_PATH + 1] = { L'\0' }; + // If GetSystemDirectory fails we accept that we'll load the DLLs from the + // normal search path. + GetSystemDirectoryW(systemDirectory, MAX_PATH + 1); + size_t systemDirLen = wcslen(systemDirectory); + + // Make the system directory path terminate with a slash + if (systemDirectory[systemDirLen - 1] != L'\\' && systemDirLen) + { + systemDirectory[systemDirLen] = L'\\'; + ++systemDirLen; + // No need to re-null terminate + } + + // For each known DLL ensure it is loaded from the system32 directory + for (size_t i = 0; i < sizeof(delayDLLs) / sizeof(delayDLLs[0]); ++i) + { + size_t fileLen = wcslen(delayDLLs[i]); + wcsncpy(systemDirectory + systemDirLen, delayDLLs[i], + MAX_PATH - systemDirLen); + if (systemDirLen + fileLen <= MAX_PATH) + { + systemDirectory[systemDirLen + fileLen] = L'\0'; + } + else + { + systemDirectory[MAX_PATH] = L'\0'; + } + LPCWSTR fullModulePath = systemDirectory; // just for code readability + // LOAD_WITH_ALTERED_SEARCH_PATH makes a dll look in its own directory for + // dependencies and is only available on Win 7 and below. + LoadLibraryExW(fullModulePath, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + } + } +} loadDLLs; +#endif diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Info.plist b/onlineupdate/source/update/updater/macbuild/Contents/Info.plist new file mode 100644 index 000000000..f104b55b9 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Info.plist @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>updater</string> + <key>CFBundleIconFile</key> + <string>updater.icns</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.updater</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> + <key>LSMinimumSystemVersion</key> + <string>10.5</string> + <key>LSMinimumSystemVersionByArchitecture</key> + <dict> + <key>i386</key> + <string>10.5.0</string> + <key>x86_64</key> + <string>10.6.0</string> + </dict> +</dict> +</plist> diff --git a/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo b/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo new file mode 100644 index 000000000..bd04210fb --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo @@ -0,0 +1 @@ +APPL????
\ No newline at end of file diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in new file mode 100644 index 000000000..bca4022e7 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Localized versions of Info.plist keys */ + +CFBundleName = "%APP_NAME% Software Update"; diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib new file mode 100644 index 000000000..6cfb50406 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib @@ -0,0 +1,19 @@ +{ + IBClasses = ( + { + CLASS = FirstResponder; + LANGUAGE = ObjC; + SUPERCLASS = NSObject; +}, + { + CLASS = UpdaterUI; + LANGUAGE = ObjC; + OUTLETS = { + progressBar = NSProgressIndicator; + progressTextField = NSTextField; + }; + SUPERCLASS = NSObject; +} + ); + IBVersion = 1; +}
\ No newline at end of file diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib new file mode 100644 index 000000000..150917837 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBDocumentLocation</key> + <string>111 162 356 240 0 0 1440 878 </string> + <key>IBEditorPositions</key> + <dict> + <key>29</key> + <string>106 299 84 44 0 0 1440 878 </string> + </dict> + <key>IBFramework Version</key> + <string>489.0</string> + <key>IBOpenObjects</key> + <array> + <integer>21</integer> + <integer>29</integer> + </array> + <key>IBSystem Version</key> + <string>10J567</string> +</dict> +</plist> diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib Binary files differnew file mode 100644 index 000000000..61ff02600 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns b/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns Binary files differnew file mode 100644 index 000000000..d7499c669 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns diff --git a/onlineupdate/source/update/updater/progressui-unused/progressui_gonk.cxx b/onlineupdate/source/update/updater/progressui-unused/progressui_gonk.cxx new file mode 100644 index 000000000..2878aa2f0 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui-unused/progressui_gonk.cxx @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <assert.h> +#include <stdio.h> + +#include <string> + +#include "android/log.h" + +#include "progressui.h" + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoUpdater" , ## args) + +int InitProgressUI(int *argc, char ***argv) +{ + return 0; +} + +int ShowProgressUI() +{ + LOG("Starting to apply update ...\n"); + return 0; +} + +void QuitProgressUI() +{ + LOG("Finished applying update\n"); +} + +void UpdateProgressUI(float progress) +{ + assert(0.0f <= progress && progress <= 100.0f); + + static const size_t kProgressBarLength = 50; + static size_t sLastNumBars; + size_t numBars = size_t(float(kProgressBarLength) * progress / 100.0f); + if (numBars == sLastNumBars) + { + return; + } + sLastNumBars = numBars; + + size_t numSpaces = kProgressBarLength - numBars; + std::string bars(numBars, '='); + std::string spaces(numSpaces, ' '); + LOG("Progress [ %s%s ]\n", bars.c_str(), spaces.c_str()); +} diff --git a/onlineupdate/source/update/updater/progressui-unused/progressui_osx.mm b/onlineupdate/source/update/updater/progressui-unused/progressui_osx.mm new file mode 100644 index 000000000..8e3ce01eb --- /dev/null +++ b/onlineupdate/source/update/updater/progressui-unused/progressui_osx.mm @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#import <Cocoa/Cocoa.h> +#include <stdio.h> +#include <unistd.h> +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" + +#define TIMER_INTERVAL 0.2 + +static float sProgressVal; // between 0 and 100 +static BOOL sQuit = FALSE; +static StringTable sLabels; +static const char *sUpdatePath; + +@interface UpdaterUI : NSObject +{ + IBOutlet NSProgressIndicator *progressBar; + IBOutlet NSTextField *progressTextField; +} +@end + +@implementation UpdaterUI + +-(void)awakeFromNib +{ + NSWindow *w = [progressBar window]; + + [w setTitle:[NSString stringWithUTF8String:sLabels.title]]; + [progressTextField setStringValue:[NSString stringWithUTF8String:sLabels.info]]; + + NSRect origTextFrame = [progressTextField frame]; + [progressTextField sizeToFit]; + + int widthAdjust = progressTextField.frame.size.width - origTextFrame.size.width; + + if (widthAdjust > 0) { + NSRect f; + f.size.width = w.frame.size.width + widthAdjust; + f.size.height = w.frame.size.height; + [w setFrame:f display:YES]; + } + + [w center]; + + [progressBar setIndeterminate:NO]; + [progressBar setDoubleValue:0.0]; + + [[NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self + selector:@selector(updateProgressUI:) + userInfo:nil repeats:YES] retain]; + + // Make sure we are on top initially + [NSApp activateIgnoringOtherApps:YES]; +} + +// called when the timer goes off +-(void)updateProgressUI:(NSTimer *)aTimer +{ + if (sQuit) { + [aTimer invalidate]; + [aTimer release]; + + // It seems to be necessary to activate and hide ourselves before we stop, + // otherwise the "run" method will not return until the user focuses some + // other app. The activate step is necessary if we are not the active app. + // This is a big hack, but it seems to do the trick. + [NSApp activateIgnoringOtherApps:YES]; + [NSApp hide:self]; + [NSApp stop:self]; + } + + float progress = sProgressVal; + + [progressBar setDoubleValue:(double)progress]; +} + +// leave this as returning a BOOL instead of NSApplicationTerminateReply +// for backward compatibility +- (BOOL)applicationShouldTerminate:(NSApplication *)sender +{ + return sQuit; +} + +@end + +int +InitProgressUI(int *pargc, char ***pargv) +{ + sUpdatePath = (*pargv)[1]; + + return 0; +} + +int +ShowProgressUI() +{ + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + usleep(500000); + + if (sQuit || sProgressVal > 70.0f) + return 0; + + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/updater.ini", sUpdatePath); + if (ReadStrings(path, &sLabels) != OK) + return -1; + + // Continue the update without showing the Progress UI if any of the supplied + // strings are larger than MAX_TEXT_LEN (Bug 628829). + if (!(strlen(sLabels.title) < MAX_TEXT_LEN - 1 && + strlen(sLabels.info) < MAX_TEXT_LEN - 1)) + return -1; + + [NSApplication sharedApplication]; + [NSBundle loadNibNamed:@"MainMenu" owner:NSApp]; + [NSApp run]; + + return 0; +} + +// Called on a background thread +void +QuitProgressUI() +{ + sQuit = TRUE; +} + +// Called on a background thread +void +UpdateProgressUI(float progress) +{ + sProgressVal = progress; // 32-bit writes are atomic +} diff --git a/onlineupdate/source/update/updater/progressui.h b/onlineupdate/source/update/updater/progressui.h new file mode 100644 index 000000000..a3e4913fc --- /dev/null +++ b/onlineupdate/source/update/updater/progressui.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef PROGRESSUI_H__ +#define PROGRESSUI_H__ + +#include "updatedefines.h" +#include "types.hxx" + +#if defined(_WIN32) +#define NS_main wmain +#else +#define NS_main main +#endif + +// Called to perform any initialization of the widget toolkit +int InitProgressUI(int* argc, NS_tchar*** argv); + +#if defined(_WIN32) +// Called on the main thread at startup +int ShowProgressUI(bool indeterminate = false, bool initUIStrings = true); +int InitProgressUIStrings(); +#else +// Called on the main thread at startup +int ShowProgressUI(); +#endif +// May be called from any thread +void QuitProgressUI(); + +// May be called from any thread: progress is a number between 0 and 100 +void UpdateProgressUI(float progress); + +#endif // PROGRESSUI_H__ diff --git a/onlineupdate/source/update/updater/progressui_gtk.cxx b/onlineupdate/source/update/updater/progressui_gtk.cxx new file mode 100644 index 000000000..961e4cdd9 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_gtk.cxx @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(UNIX) || defined(MACOSX) +#include <stdio.h> +#include <gtk/gtk.h> +#include <unistd.h> +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" +#include <string.h> +#include "progressui_gtk_icon.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#define TIMER_INTERVAL 100 + +static float sProgressVal; // between 0 and 100 +static gboolean sQuit = FALSE; +static gboolean sEnableUI; +static guint sTimerID; + +static GtkWidget *sWin; +static GtkWidget *sLabel; +static GtkWidget *sProgressBar; + +static const char *sProgramPath; + +static gboolean +UpdateDialog(gpointer /*data*/) +{ + if (sQuit) + { + gtk_widget_hide(sWin); + gtk_main_quit(); + } + + float progress = sProgressVal; + + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(sProgressBar), + progress / 100.0); + + return TRUE; +} + +static gboolean +OnDeleteEvent(GtkWidget * /*widget*/, GdkEvent * /*event*/, gpointer /*user_data*/) +{ + return TRUE; +} + +int +InitProgressUI(int *pargc, char ***pargv) +{ + sProgramPath = (*pargv)[0]; + + sEnableUI = gtk_init_check(pargc, pargv); + return 0; +} + +int +ShowProgressUI() +{ + if (!sEnableUI) + return -1; + + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + usleep(500000); + + if (sQuit || sProgressVal > 70.0f) + return 0; + + char ini_path[PATH_MAX]; + snprintf(ini_path, sizeof(ini_path), "%s.ini", sProgramPath); + + StringTable strings; + if (ReadStrings(ini_path, &strings) != OK) + { + strcpy(strings.title, "LibreOffice Update"); + strcpy(strings.info, "Please wait while we update your installation."); + } + + sWin = gtk_window_new(GTK_WINDOW_TOPLEVEL); + if (!sWin) + return -1; + + static GdkPixbuf *pixbuf; + + g_signal_connect(G_OBJECT(sWin), "delete_event", + G_CALLBACK(OnDeleteEvent), nullptr); + + gtk_window_set_title(GTK_WINDOW(sWin), strings.title); + gtk_window_set_type_hint(GTK_WINDOW(sWin), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_position(GTK_WINDOW(sWin), GTK_WIN_POS_CENTER_ALWAYS); + gtk_window_set_resizable(GTK_WINDOW(sWin), FALSE); + gtk_window_set_decorated(GTK_WINDOW(sWin), TRUE); + gtk_window_set_deletable(GTK_WINDOW(sWin),FALSE); + pixbuf = gdk_pixbuf_new_from_xpm_data (icon_data); + gtk_window_set_icon(GTK_WINDOW(sWin), pixbuf); + g_object_unref(pixbuf); + + GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); + gtk_box_set_homogeneous(GTK_BOX(vbox), true); + sLabel = gtk_label_new(strings.info); + gtk_misc_set_alignment(GTK_MISC(sLabel), 0.0f, 0.0f); + sProgressBar = gtk_progress_bar_new(); + + gtk_box_pack_start(GTK_BOX(vbox), sLabel, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), sProgressBar, TRUE, TRUE, 0); + + sTimerID = g_timeout_add(TIMER_INTERVAL, UpdateDialog, nullptr); + + gtk_container_set_border_width(GTK_CONTAINER(sWin), 10); + gtk_container_add(GTK_CONTAINER(sWin), vbox); + gtk_widget_show_all(sWin); + + gtk_main(); + return 0; +} + +// Called on a background thread +void +QuitProgressUI() +{ + sQuit = TRUE; +} + +// Called on a background thread +void +UpdateProgressUI(float progress) +{ + sProgressVal = progress; // 32-bit writes are atomic +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif // defined(UNIX) || defined(MACOSX) diff --git a/onlineupdate/source/update/updater/progressui_gtk_icon.h b/onlineupdate/source/update/updater/progressui_gtk_icon.h new file mode 100644 index 000000000..6660c071f --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_gtk_icon.h @@ -0,0 +1,205 @@ +/* XPM */ +static const char *icon_data[] = { +/* columns rows colors chars-per-pixel */ +"32 32 167 2 ", +" c #915B05", +". c #955F0C", +"X c #95610D", +"o c #8F6013", +"O c #8C601D", +"+ c #966311", +"@ c #9A6615", +"# c #9A6817", +"$ c #9C6B1D", +"% c #8B652A", +"& c #966B26", +"* c #976D28", +"= c #9C7129", +"- c #8E6D34", +"; c #A17025", +": c #A2752E", +"> c #AB7A2B", +", c #A77A34", +"< c #A97C33", +"1 c #8D7751", +"2 c #8D7A57", +"3 c #8A7658", +"4 c #A17D42", +"5 c #8E7E63", +"6 c #AE823B", +"7 c #B88635", +"8 c #B2853B", +"9 c #BB8A3A", +"0 c #C08B36", +"q c #AA864A", +"w c #AF894D", +"e c #BC8D42", +"r c #AA8B57", +"t c #B38F56", +"y c #B79256", +"u c #968468", +"i c #958A73", +"p c #9B8970", +"a c #938D7D", +"s c #9D927A", +"d c #A3916F", +"f c #AD966D", +"g c #B1996D", +"h c #BF9F6E", +"j c #A89473", +"k c #B59D75", +"l c #BEA375", +"z c #C08F42", +"x c #D29E4B", +"c c #C69C5C", +"v c #DBA44D", +"b c #D5A351", +"n c #DCA651", +"m c #DFAA56", +"M c #D8A75A", +"N c #DCAA5B", +"B c #C79E60", +"V c #C09F6E", +"C c #C8A162", +"Z c #CCA66A", +"A c #DEAE62", +"S c #D6AD6E", +"D c #DEB16A", +"F c #C1A272", +"G c #C6AA7E", +"H c #D3AE73", +"J c #D8AF71", +"K c #DEB676", +"L c #D8B47C", +"P c #E2B266", +"I c #E3B36A", +"U c #E5B974", +"Y c #E3BB7C", +"T c #8B8C85", +"R c #8D8E88", +"E c #918F83", +"W c #8F908C", +"Q c #9A9483", +"! c #92938E", +"~ c #98968B", +"^ c #9E9788", +"/ c #939591", +"( c #969894", +") c #9C9A94", +"_ c #9C9E99", +"` c #A19C94", +"' c #A29E99", +"] c #9FA19C", +"[ c #ADA189", +"{ c #B9A486", +"} c #AEA695", +"| c #A1A29C", +" . c #A9A89F", +".. c #B7AE98", +"X. c #A4A6A2", +"o. c #A6A9A4", +"O. c #AAABA7", +"+. c #ACACA9", +"@. c #AEB1AB", +"#. c #B0B2AE", +"$. c #B3B5B1", +"%. c #B6B8B2", +"&. c #B9BBB7", +"*. c #BBBCBA", +"=. c #C3AD8A", +"-. c #CDB288", +";. c #C9B495", +":. c #D3BC96", +">. c #E3BD84", +",. c #E1BF88", +"<. c #D2BEA0", +"1. c #C8BFB0", +"2. c #BFC1BB", +"3. c #D8C095", +"4. c #E5C087", +"5. c #EAC385", +"6. c #E6C289", +"7. c #E8C288", +"8. c #E4C492", +"9. c #E6C99B", +"0. c #EBCD9E", +"q. c #D4C1A5", +"w. c #D8C5A7", +"e. c #CBC2B1", +"r. c #C1C4BE", +"t. c #D1C7B7", +"y. c #DFCEB4", +"u. c #E7CDA5", +"i. c #EDD0A3", +"p. c #E6D0AF", +"a. c #EED5AC", +"s. c #E6D2B0", +"d. c #EDD6B3", +"f. c #EBD7B9", +"g. c #ECD9BC", +"h. c #F3DBB5", +"j. c #C4C5C3", +"k. c #C6C9C3", +"l. c #C9CCC6", +"z. c #CCCDCA", +"x. c #D4CFC3", +"c. c #CFD0CC", +"v. c #D1D2CF", +"b. c #D3D4D3", +"n. c #D6D8D5", +"m. c #DADAD6", +"M. c #DADBDA", +"N. c #E7D8C0", +"B. c #ECDCC2", +"V. c #DFE1DE", +"C. c #F3E1C3", +"Z. c #F2E4CC", +"A. c #ECE2D4", +"S. c #EEE7DA", +"D. c #F1E5D2", +"F. c #F7E9D6", +"G. c #F0E8DC", +"H. c #E3E3E3", +"J. c #E7E8E7", +"K. c #E8E8E7", +"L. c #ECECEB", +"P. c #F4EFE6", +"I. c #F5F1EB", +"U. c #F3F4F3", +"Y. c #F8F5F1", +"T. c #FAF9F7", +"R. c #FFFFFF", +/* pixels */ +"R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.F X ; A.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.< u.H # S.R.R.R.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.T.y.F l R., d.a.Z # G.R.R.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.R.R.R.A.V , + w :., y.< f.Y 8.C # P.R.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.R.R.G : -.N.I.Y.T.t X . f.5.n 4.e # T.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.R.R.$ p.F.Z.Z.D.Y.F ..l s.h.U D 6.# :.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.U.l.* u.d.i.d.B.I.y k 3., C.5.N >.> h R.R.", +"R.R.R.R.R.R.R.R.R.U.%.W ] , 8.0.0.| R T T T T T T ~ Q >.< h R.R.", +"R.R.R.R.R.R.R.R.n.W ] 2.k.q 3.h.a.| v.U.U.L.L.L.L.U.o.9.< V R.R.", +"R.R.R.R.R.R.R.z.R @.j.k.z.q K 6 ) M.R.R.R.R.R.R.R.o.L > F R.R.", +"R.R.R.R.R.R.M.W $.2.k.l.j.r 0.8.6.! b.R.R.R.R.R.R.R.o.p.8 -.R.R.", +"R.R.R.R.R.T.! @.2.k.l.O.W 6 4.I N E j.R.R.R.R.R.$./ T J 9 w.R.R.", +"R.R.R.R.R.*._ 2.r.l.] X.U., Y A v a k.R.R.L.R.R.U.E 0 x 7 w.R.R.", +"R.R.R.R.R.! %.r.k.@.| T.R., Y N v a j.R.R.@.U.R.R.*.r b 7 <.R.R.", +"R.R.R.R.n.! r.k.k.W L.R.R., Y A v f M.R.R.O.&.R.R.H.a M 9 ;.T.R.", +"R.R.R.R.&.X.j.l.$.O.R.R.T., Y P n g M.R.R.o.W R.R.R.R D 9 { L.T.", +"R.R.w + 2 @.l.c. .1 + . q.: >.P m g ! ! / T E L.R.R._ K e j H.R.", +"R.R.6 Z. .%.c.b.' <.a.c =.= Y P m N M M M M s H.R.R.X.>.z u n.T.", +"R.R., Z.} $.v.n.O.=.i.c { * U 7.7.5.7.4.4.4.Q L.R.R.] ,.8 5 n.T.", +"R.R.6 Z...@.n.M.&.[ i.B =.5 . @ @ @ @ @ @ # R T.R.R.! @ o ] V.T.", +"R.R., A.x._ m.V.m.! G.:.;.z.+.' ` ` ` ^ ) W $.R.R.K.T ) $.b.U.R.", +"R.T., L K R m.H.K.#.[ y ;.U.J.H.H.H.H.H.b.W L.R.R.j.+.H.L.U.R.R.", +"R.M.& H A j #.K.L.L.| 1 + # # # # # $ t./ c.R.R.T./ H.R.T.R.R.R.", +"L.O.O S 8.6.W M.U.T.Y.O.) e.g.f.a.w.d ! b.R.R.R.&.+.R.R.R.R.R.R.", +"U.2.% - ( U.T.R.R.M.+.W R T _ *.U.R.R.R.b.( U.R.R.R.R.R.R.", +"R.U.V.n.b.b.b.o._ L.R.R.R.R.R.T.R.R.R.R.R.R.z./ J.R.R.R.R.R.R.R.", +"R.R.R.R.T.R.R.T.j./ c.R.R.R.R.R.R.R.R.R.U.#.( L.R.R.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.R.T.4 ^ ) j.J.T.R.R.U.M.$.! &.T.R.R.R.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.T.M.& H Z g a T R T E i Q L.R.R.R.R.R.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.U.+.O S ,.>.7.>.7.,.6.< 3 H.R.R.R.R.R.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.U.2.% # $ & $ $ $ $ & X p L.R.R.R.R.R.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.R.Y.m.e.1.1.1.1.1.1.e.t.L.R.R.R.R.R.R.R.R.R.R.R.R." +}; diff --git a/onlineupdate/source/update/updater/progressui_null.cxx b/onlineupdate/source/update/updater/progressui_null.cxx new file mode 100644 index 000000000..66d294a13 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_null.cxx @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(MACOSX) && !defined(UNIX) && !defined(_WIN32) +#include "progressui.h" + +int InitProgressUI(int *argc, char ***argv) +{ + return 0; +} + +int ShowProgressUI() +{ + return 0; +} + +void QuitProgressUI() +{ +} + +void UpdateProgressUI(float progress) +{ +} +#endif // !defined(MACOSX) && !defined(UNIX) && !defined(_WIN32) diff --git a/onlineupdate/source/update/updater/progressui_win.cxx b/onlineupdate/source/update/updater/progressui_win.cxx new file mode 100644 index 000000000..7ef23cfab --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_win.cxx @@ -0,0 +1,348 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#include <stdio.h> +#ifndef UNICODE +#define UNICODE +#endif +#include <windows.h> +#include <commctrl.h> +#include <process.h> +#include <io.h> + +#include "resource.h" +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" + +#define TIMER_ID 1 +#define TIMER_INTERVAL 100 + +#define RESIZE_WINDOW(hwnd, extrax, extray) \ + { \ + RECT windowSize; \ + GetWindowRect(hwnd, &windowSize); \ + SetWindowPos(hwnd, 0, 0, 0, windowSize.right - windowSize.left + extrax, \ + windowSize.bottom - windowSize.top + extray, \ + SWP_NOMOVE | SWP_NOZORDER); \ + } + +#define MOVE_WINDOW(hwnd, dx, dy) \ + { \ + RECT rc; \ + POINT pt; \ + GetWindowRect(hwnd, &rc); \ + pt.x = rc.left; \ + pt.y = rc.top; \ + ScreenToClient(GetParent(hwnd), &pt); \ + SetWindowPos(hwnd, 0, pt.x + dx, pt.y + dy, 0, 0, \ + SWP_NOSIZE | SWP_NOZORDER); \ + } + +static float sProgress; // between 0 and 100 +static BOOL sQuit = FALSE; +static BOOL sIndeterminate = FALSE; +static StringTable sUIStrings; + +static BOOL +GetStringsFile(WCHAR filename[MAX_PATH]) +{ + if (!GetModuleFileNameW(nullptr, filename, MAX_PATH)) + return FALSE; + + WCHAR *dot = wcsrchr(filename, '.'); + if (!dot || wcsicmp(dot + 1, L"exe")) + return FALSE; + + wcscpy(dot + 1, L"ini"); + return TRUE; +} + +static void +UpdateDialog(HWND hDlg) +{ + int pos = int(sProgress + 0.5f); + HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS); + SendMessage(hWndPro, PBM_SETPOS, pos, 0); +} + +// The code in this function is from MSDN: +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/dialogboxes/usingdialogboxes.asp +static void +CenterDialog(HWND hDlg) +{ + RECT rc, rcOwner, rcDlg; + + // Get the owner window and dialog box rectangles. + HWND desktop = GetDesktopWindow(); + + GetWindowRect(desktop, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + + // Offset the owner and dialog box rectangles so that + // right and bottom values represent the width and + // height, and then offset the owner again to discard + // space taken up by the dialog box. + + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + + // The new position is the sum of half the remaining + // space and the owner's original position. + + SetWindowPos(hDlg, + HWND_TOP, + rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2), + 0, 0, // ignores size arguments + SWP_NOSIZE); +} + +static void +InitDialog(HWND hDlg) +{ + WCHAR szwTitle[MAX_TEXT_LEN]; + WCHAR szwInfo[MAX_TEXT_LEN]; + + MultiByteToWideChar(CP_UTF8, 0, sUIStrings.title, -1, szwTitle, + sizeof(szwTitle)/sizeof(szwTitle[0])); + MultiByteToWideChar(CP_UTF8, 0, sUIStrings.info, -1, szwInfo, + sizeof(szwInfo)/sizeof(szwInfo[0])); + + SetWindowTextW(hDlg, szwTitle); + SetWindowTextW(GetDlgItem(hDlg, IDC_INFO), szwInfo); + + // Set dialog icon + HICON hIcon = LoadIcon(GetModuleHandle(nullptr), + MAKEINTRESOURCE(IDI_DIALOG)); + if (hIcon) + SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM) hIcon); + + HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS); + SendMessage(hWndPro, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); + if (sIndeterminate) + { + LONG_PTR val = GetWindowLongPtr(hWndPro, GWL_STYLE); + SetWindowLongPtr(hWndPro, GWL_STYLE, val|PBS_MARQUEE); + SendMessage(hWndPro,(UINT) PBM_SETMARQUEE,(WPARAM) TRUE,(LPARAM)50 ); + } + + // Resize the dialog to fit all of the text if necessary. + RECT infoSize, textSize; + HWND hWndInfo = GetDlgItem(hDlg, IDC_INFO); + + // Get the control's font for calculating the new size for the control + HDC hDCInfo = GetDC(hWndInfo); + HFONT hInfoFont; + HFONT hOldFont = 0; + hInfoFont = (HFONT)SendMessage(hWndInfo, WM_GETFONT, 0, 0); + + if (hInfoFont) + hOldFont = (HFONT)SelectObject(hDCInfo, hInfoFont); + + // Measure the space needed for the text on a single line. DT_CALCRECT means + // nothing is drawn. + if (DrawText(hDCInfo, szwInfo, -1, &textSize, + DT_CALCRECT | DT_NOCLIP | DT_SINGLELINE)) + { + GetClientRect(hWndInfo, &infoSize); + SIZE extra; + // Calculate the additional space needed for the text by subtracting from + // the rectangle returned by DrawText the existing client rectangle's width + // and height. + extra.cx = (textSize.right - textSize.left) - \ + (infoSize.right - infoSize.left); + extra.cy = (textSize.bottom - textSize.top) - \ + (infoSize.bottom - infoSize.top); + if (extra.cx < 0) + extra.cx = 0; + if (extra.cy < 0) + extra.cy = 0; + if ((extra.cx > 0) || (extra.cy > 0)) + { + RESIZE_WINDOW(hDlg, extra.cx, extra.cy); + RESIZE_WINDOW(hWndInfo, extra.cx, extra.cy); + RESIZE_WINDOW(hWndPro, extra.cx, 0); + MOVE_WINDOW(hWndPro, 0, extra.cy); + } + } + + if (hOldFont) + SelectObject(hDCInfo, hOldFont); + + ReleaseDC(hWndInfo, hDCInfo); + + CenterDialog(hDlg); // make dialog appear in the center of the screen + + SetTimer(hDlg, TIMER_ID, TIMER_INTERVAL, nullptr); +} + +// Message handler for update dialog. +static LRESULT CALLBACK +DialogProc(HWND hDlg, UINT message, WPARAM /*wParam*/, LPARAM /*lParam*/) +{ + switch (message) + { + case WM_INITDIALOG: + InitDialog(hDlg); + return TRUE; + + case WM_TIMER: + if (sQuit) + { + EndDialog(hDlg, 0); + } + else + { + UpdateDialog(hDlg); + } + return TRUE; + + case WM_COMMAND: + return TRUE; + } + return FALSE; +} + +int +InitProgressUI(int* /*argc*/, WCHAR*** /*argv*/) +{ + return 0; +} + +/** + * Initializes the progress UI strings + * + * @return 0 on success, -1 on error +*/ +int +InitProgressUIStrings() +{ + // If we do not have updater.ini, then we should not bother showing UI. + WCHAR filename[MAX_PATH]; + if (!GetStringsFile(filename)) + { + strcpy(sUIStrings.title, "LibreOffice Update"); + strcpy(sUIStrings.info, "Please wait while we update your installation."); + return 0; + } + + if (_waccess(filename, 04)) + { + strcpy(sUIStrings.title, "LibreOffice Update"); + strcpy(sUIStrings.info, "Please wait while we update your installation."); + return 0; + } + + // If the updater.ini doesn't have the required strings, then we should not + // bother showing UI. + if (ReadStrings(filename, &sUIStrings) != OK) + { + strcpy(sUIStrings.title, "LibreOffice Update"); + strcpy(sUIStrings.info, "Please wait while we update your installation."); + } + + return 0; +} + +int +ShowProgressUI(bool indeterminate, bool initUIStrings) +{ + sIndeterminate = indeterminate; + if (!indeterminate) + { + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + Sleep(500); + + if (sQuit || sProgress > 70.0f) + return 0; + } + + // Don't load the UI if there's an <exe_name>.Local directory for redirection. + WCHAR appPath[MAX_PATH + 1] = { L'\0' }; + if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) + { + return -1; + } + + if (wcslen(appPath) + wcslen(L".Local") >= MAX_PATH) + { + return -1; + } + + wcscat(appPath, L".Local"); + + if (!_waccess(appPath, 04)) + { + return -1; + } + + // Don't load the UI if the strings for the UI are not provided. + if (initUIStrings && InitProgressUIStrings() == -1) + { + return -1; + } + + if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) + { + return -1; + } + + // Use an activation context that supports visual styles for the controls. + ACTCTXW actx = {0}; + actx.cbSize = sizeof(ACTCTXW); + actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_HMODULE_VALID; + actx.hModule = GetModuleHandle(NULL); // Use the embedded manifest + // This is needed only for Win XP but doesn't cause a problem with other + // versions of Windows. + actx.lpSource = appPath; + actx.lpResourceName = MAKEINTRESOURCE(IDR_COMCTL32_MANIFEST); + + HANDLE hactx = CreateActCtxW(&actx); + ULONG_PTR actxCookie = NULL; + if (hactx != INVALID_HANDLE_VALUE) + { + // Push the specified activation context to the top of the activation stack. + ActivateActCtx(hactx, &actxCookie); + } + + INITCOMMONCONTROLSEX icc = + { + sizeof(INITCOMMONCONTROLSEX), + ICC_PROGRESS_CLASS + }; + InitCommonControlsEx(&icc); + + DialogBox(GetModuleHandle(nullptr), + MAKEINTRESOURCE(IDD_DIALOG), nullptr, + (DLGPROC) DialogProc); + + if (hactx != INVALID_HANDLE_VALUE) + { + // Deactivate the context now that the comctl32.dll is loaded. + DeactivateActCtx(0, actxCookie); + } + + return 0; +} + +void +QuitProgressUI() +{ + sQuit = TRUE; +} + +void +UpdateProgressUI(float progress) +{ + sProgress = progress; // 32-bit writes are atomic +} +#endif // _WIN32 diff --git a/onlineupdate/source/update/updater/resource.h b/onlineupdate/source/update/updater/resource.h new file mode 100644 index 000000000..6b6091c7b --- /dev/null +++ b/onlineupdate/source/update/updater/resource.h @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by updater.rc +// +#define IDD_DIALOG 101 +#define IDC_PROGRESS 1000 +#define IDC_INFO 1002 +#define IDI_DIALOG 1003 +#define TYPE_CERT 512 +#define IDR_PRIMARY_CERT 1004 +#define IDR_BACKUP_CERT 1005 +#define IDS_UPDATER_IDENTITY 1006 +#define IDR_XPCSHELL_CERT 1007 +#define IDR_COMCTL32_MANIFEST 17 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1008 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/onlineupdate/source/update/updater/updater-common.build b/onlineupdate/source/update/updater/updater-common.build new file mode 100644 index 000000000..6516a4843 --- /dev/null +++ b/onlineupdate/source/update/updater/updater-common.build @@ -0,0 +1,119 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +srcs = [ + 'archivereader.cpp', + 'bspatch.cpp', + 'updater.cpp', +] + +have_progressui = 0 + +if CONFIG['VERIFY_MAR_SIGNATURE']: + USE_LIBS += [ + 'verifymar', + ] + +if CONFIG['OS_ARCH'] == 'WINNT': + have_progressui = 1 + srcs += [ + 'loaddlls.cpp', + 'progressui_win.cpp', + 'win_dirent.cpp', + ] + RCINCLUDE = '%supdater.rc' % updater_rel_path + DEFINES['UNICODE'] = True + DEFINES['_UNICODE'] = True + DEFINES['NOMINMAX'] = True + USE_STATIC_LIBS = True + + # Pick up nsWindowsRestart.cpp + LOCAL_INCLUDES += [ + '/toolkit/xre', + ] + USE_LIBS += [ + 'updatecommon-standalone', + ] + OS_LIBS += [ + 'comctl32', + 'ws2_32', + 'shell32', + 'shlwapi', + 'crypt32', + 'advapi32', + ] +elif CONFIG['OS_ARCH'] == 'Linux' and CONFIG['VERIFY_MAR_SIGNATURE']: + USE_LIBS += [ + 'nss', + 'signmar', + 'updatecommon', + ] + OS_LIBS += CONFIG['NSPR_LIBS'] +else: + USE_LIBS += [ + 'updatecommon', + ] + +USE_LIBS += [ + 'mar', +] + +if CONFIG['MOZ_NATIVE_BZ2']: + OS_LIBS += CONFIG['MOZ_BZ2_LIBS'] +else: + USE_LIBS += [ + 'bz2', + ] + +if CONFIG['MOZ_ENABLE_GTK']: + have_progressui = 1 + srcs += [ + 'progressui_gtk.cpp', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + have_progressui = 1 + srcs += [ + 'launchchild_osx.mm', + 'progressui_osx.mm', + ] + OS_LIBS += [ + '-framework Cocoa', + '-framework Security', + ] + +if have_progressui == 0: + srcs += [ + 'progressui_null.cpp', + ] + +SOURCES += sorted(srcs) + +DEFINES['NS_NO_XPCOM'] = True +DISABLE_STL_WRAPPING = True +for var in ('MAR_CHANNEL_ID', 'MOZ_APP_VERSION'): + DEFINES[var] = '"%s"' % CONFIG[var] + +LOCAL_INCLUDES += [ + '/toolkit/mozapps/update/common', + '/xpcom/glue', +] + +DELAYLOAD_DLLS += [ + 'crypt32.dll', + 'comctl32.dll', + 'userenv.dll', + 'wsock32.dll', +] + +if CONFIG['_MSC_VER']: + WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup'] +elif CONFIG['OS_ARCH'] == 'WINNT': + WIN32_EXE_LDFLAGS += ['-municode'] + +if CONFIG['MOZ_WIDGET_GTK']: + CXXFLAGS += CONFIG['TK_CFLAGS'] + OS_LIBS += CONFIG['TK_LIBS'] diff --git a/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in b/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in new file mode 100644 index 000000000..213648834 --- /dev/null +++ b/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in @@ -0,0 +1,32 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# For changes here, also consider ../Makefile.in + +XPCSHELLTESTROOT = $(abspath $(DEPTH))/_tests/xpcshell/toolkit/mozapps/update/tests +MOCHITESTROOT = $(abspath $(DEPTH))/_tests/testing/mochitest/chrome/toolkit/mozapps/update/tests + +include $(topsrcdir)/config/rules.mk + +libs:: +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) + # Copy for xpcshell tests + $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app + rsync -a -C --exclude '*.in' $(srcdir)/../macbuild/Contents $(XPCSHELLTESTROOT)/data/updater-xpcshell.app + sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/../macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \ + iconv -f UTF-8 -t UTF-16 > $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/Resources/English.lproj/InfoPlist.strings + $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS/updater-xpcshell + $(NSINSTALL) $(PROGRAM) $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS + rm -f $(PROGRAM) + rm -Rf $(XPCSHELLTESTROOT)/data/updater.app + mv $(XPCSHELLTESTROOT)/data/updater-xpcshell.app $(XPCSHELLTESTROOT)/data/updater.app + mv $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/updater-xpcshell $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/updater + + # Copy for mochitest chrome tests + rsync -a -C $(XPCSHELLTESTROOT)/data/updater.app $(MOCHITESTROOT)/data/updater.app +else + cp $(PROGRAM) $(XPCSHELLTESTROOT)/data/updater$(BIN_SUFFIX) + cp $(PROGRAM) $(MOCHITESTROOT)/data/updater$(BIN_SUFFIX) +endif diff --git a/onlineupdate/source/update/updater/updater-xpcshell/moz.build b/onlineupdate/source/update/updater/updater-xpcshell/moz.build new file mode 100644 index 000000000..ba5c844cf --- /dev/null +++ b/onlineupdate/source/update/updater/updater-xpcshell/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Program('updater-xpcshell') + +updater_rel_path = '../' +DIST_INSTALL = False +DEFINES['TEST_UPDATER'] = True +include('../updater-common.build') +FAIL_ON_WARNINGS = True diff --git a/onlineupdate/source/update/updater/updater.cxx b/onlineupdate/source/update/updater/updater.cxx new file mode 100644 index 000000000..54750afb4 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.cxx @@ -0,0 +1,4591 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Manifest Format + * --------------- + * + * contents = 1*( line ) + * line = method LWS *( param LWS ) CRLF + * CRLF = "\r\n" + * LWS = 1*( " " | "\t" ) + * + * Available methods for the manifest file: + * + * updatev2.manifest + * ----------------- + * method = "add" | "add-if" | "patch" | "patch-if" | "remove" | + * "rmdir" | "rmrfdir" | type + * + * 'type' is the update type (e.g. complete or partial) and when present MUST + * be the first entry in the update manifest. The type is used to support + * downgrades by causing the actions defined in precomplete to be performed. + * + * updatev3.manifest + * ----------------- + * method = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" | + * "remove" | "rmdir" | "rmrfdir" | type + * + * 'add-if-not' adds a file if it doesn't exist. + * + * precomplete + * ----------- + * method = "remove" | "rmdir" + */ +#include "bspatch.h" +#include "progressui.h" +#include "archivereader.h" +#include "readstrings.h" +#include "errors.h" +#include "bzlib.h" +#include <thread> +#include <vector> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <limits.h> +#include <errno.h> +#include <algorithm> +#include <memory> + +#include <config_version.h> + +#include "updatelogging.h" + +#include <onlineupdate/mozilla/Compiler.h> +#include <onlineupdate/mozilla/Types.h> + +#ifdef _WIN32 +#include <comphelper/windowsStart.hxx> +#include "uachelper.h" +#include "pathhash.h" + +// TODO:moggi taken from the mozilla code -- find a better solution +#define INVALID_APPLYTO_DIR_ERROR 74 +#define REMOVE_FILE_SPEC_ERROR 71 +#define INVALID_APPLYTO_DIR_STAGED_ERROR 72 + +#endif + + +// Amount of the progress bar to use in each of the 3 update stages, +// should total 100.0. +#define PROGRESS_PREPARE_SIZE 20.0f +#define PROGRESS_EXECUTE_SIZE 75.0f +#define PROGRESS_FINISH_SIZE 5.0f + +// Amount of time in ms to wait for the parent process to close +#ifdef _WIN32 +#define PARENT_WAIT 5000 +#endif + +#if defined(MACOSX) +// These functions are defined in launchchild_osx.mm +void CleanupElevatedMacUpdate(bool aFailureOccurred); +bool IsOwnedByGroupAdmin(const char* aAppBundle); +bool IsRecursivelyWritable(const char* aPath); +void LaunchChild(int argc, const char** argv); +void LaunchMacPostProcess(const char* aAppBundle); +bool ObtainUpdaterArguments(int* argc, char*** argv); +bool ServeElevatedUpdate(int argc, const char** argv); +void SetGroupOwnershipAndPermissions(const char* aAppBundle); +struct UpdateServerThreadArgs +{ + int argc; + const NS_tchar** argv; +}; +#endif + +#ifndef SSIZE_MAX +# define SSIZE_MAX LONG_MAX +#endif + +// We want to use execv to invoke the callback executable on platforms where +// we were launched using execv. See nsUpdateDriver.cpp. +#if defined(UNIX) && !defined(MACOSX) +#define USE_EXECV +#endif + +#if defined(VERIFY_MAR_SIGNATURE) && !defined(_WIN32) && !defined(MACOSX) +#include <nss.h> +#include <nspr.h> +#endif + +#ifdef _WIN32 +#ifdef MAINTENANCE_SERVICE +#include "registrycertificates.h" +#endif +BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra); +BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, + LPCWSTR siblingFilePath, + LPCWSTR newFileName); +#include "updatehelper.h" + +// Closes the handle if valid and if the updater is elevated returns with the +// return code specified. This prevents multiple launches of the callback +// application by preventing the elevated process from launching the callback. +#define EXIT_WHEN_ELEVATED(path, handle, retCode) \ +{ \ + if (handle != INVALID_HANDLE_VALUE) { \ + CloseHandle(handle); \ + } \ + if (_waccess(path, F_OK) == 0 && NS_tremove(path) != 0) { \ + LogFinish(); \ + return retCode; \ + } \ +} +#endif + +//----------------------------------------------------------------------------- + +// This variable lives in libbz2. It's declared in bzlib_private.h, so we just +// declare it here to avoid including that entire header file. +#if defined __GNUC__ +extern "C" __attribute__((visibility("default"))) unsigned int BZ2_crc32Table[256]; +#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) +extern "C" __global unsigned int BZ2_crc32Table[256]; +#else +extern "C" unsigned int BZ2_crc32Table[256]; +#endif + +static unsigned int +crc32(const unsigned char *buf, unsigned int len) +{ + unsigned int crc = 0xffffffffL; + + const unsigned char *end = buf + len; + for (; buf != end; ++buf) + crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf]; + + crc = ~crc; + return crc; +} + +//----------------------------------------------------------------------------- + +// A simple stack based container for a FILE struct that closes the +// file descriptor from its destructor. +class AutoFile +{ +public: + explicit AutoFile(FILE* file = nullptr) + : mFile(file) + { + } + + ~AutoFile() + { + if (mFile != nullptr) + fclose(mFile); + } + + AutoFile &operator=(FILE* file) + { + if (mFile != 0) + fclose(mFile); + mFile = file; + return *this; + } + + operator FILE*() + { + return mFile; + } + + FILE* get() + { + return mFile; + } + +private: + FILE* mFile; +}; + +struct MARChannelStringTable +{ + MARChannelStringTable() + { + MARChannelID[0] = '\0'; + } + + char MARChannelID[MAX_TEXT_LEN]; +}; + +//----------------------------------------------------------------------------- + +static NS_tchar* gPatchDirPath; +static NS_tchar gInstallDirPath[MAXPATHLEN]; +static NS_tchar gWorkingDirPath[MAXPATHLEN]; +static bool gSucceeded = false; +static bool sStagedUpdate = false; +static bool sReplaceRequest = false; +static bool sUsingService = false; + +#ifdef _WIN32 +// The current working directory specified in the command line. +static NS_tchar* gDestPath; +static NS_tchar gCallbackRelPath[MAXPATHLEN]; +static NS_tchar gCallbackBackupPath[MAXPATHLEN]; +static NS_tchar gDeleteDirPath[MAXPATHLEN]; +#endif + +static const NS_tchar kWhitespace[] = NS_T(" \t"); +static const NS_tchar kNL[] = NS_T("\r\n"); +static const NS_tchar kQuote[] = NS_T("\""); + +static NS_tchar* +mstrtok(const NS_tchar *delims, NS_tchar **str) +{ + if (!*str || !**str) + { + *str = nullptr; + return nullptr; + } + + // skip leading "whitespace" + NS_tchar *ret = *str; + const NS_tchar *d; + do + { + for (d = delims; *d != NS_T('\0'); ++d) + { + if (*ret == *d) + { + ++ret; + break; + } + } + } + while (*d); + + if (!*ret) + { + *str = ret; + return nullptr; + } + + NS_tchar *i = ret; + do + { + for (d = delims; *d != NS_T('\0'); ++d) + { + if (*i == *d) + { + *i = NS_T('\0'); + *str = ++i; + return ret; + } + } + ++i; + } + while (*i); + + *str = nullptr; + return ret; +} + +#if defined(_WIN32) && defined(MAINTENANCE_SERVICE) +static bool +EnvHasValue(const char *name) +{ + const char *val = getenv(name); + return (val && *val); +} +#endif + +/** + * Converts a relative update path to an absolute path related to the working + * or install directory. Allocates a new NS_tchar[] based path! + * + * @param relpath + * The relative path to convert to a full path. + * @return valid filesystem full path or nullptr if memory allocation fails. + */ +static NS_tchar* +new_absolute_path(const NS_tchar *relpath) +{ + NS_tchar *destpath = sStagedUpdate ? gWorkingDirPath : gInstallDirPath; + size_t lendestpath = NS_tstrlen(destpath); + size_t lenrelpath = NS_tstrlen(relpath); + NS_tchar *s = new NS_tchar[lendestpath + lenrelpath + 2]; + + NS_tchar *c = s; + + NS_tstrcpy(c, destpath); + c += lendestpath; + NS_tstrcat(c, NS_T("/")); + c++; + + NS_tstrcat(c, relpath); + c += lenrelpath; + *c = NS_T('\0'); + return s; +} + +namespace { + +bool is_userprofile_in_instdir() +{ + return false; + /* + // the algorithm is: + // 1.) if userprofile path length is smaller than installation dir, + // the profile is surely not in instdir + // 2.) else comparing the two paths looking only at the installation dir + // characters should yield an equal string + NS_tchar userprofile[MAXPATHLEN]; + NS_tstrcpy(userprofile, gPatchDirPath); + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(userprofile, NS_T('/')); + if (slash) + *slash = NS_T('\0'); + + size_t userprofile_len = NS_tstrlen(userprofile); + size_t installdir_len = NS_tstrlen(gInstallDirPath); + + if (userprofile_len < installdir_len) + return false; + + return NS_tstrncmp(userprofile, gInstallDirPath, installdir_len) == 0; + */ +} + +} + +/** + * Get a pointer in the absolute path, relative to the working or install + * directory. Returns itself, if not absolute or outside of the directory. + * + * @param abs_path + * An absolute path. + * return pointer to the location within fullpath where the relative path starts + * or fullpath itself if it already looks relative. + */ +static const NS_tchar* +get_relative_offset(const NS_tchar *abs_path) +{ + // If the path isn't absolute, just return it as-is. +#ifdef _WIN32 + if (abs_path[1] != ':' && abs_path[2] != '\\') + { +#else + if (abs_path[0] != '/') + { +#endif + return abs_path; + } + + NS_tchar *prefix = sStagedUpdate ? gWorkingDirPath : gInstallDirPath; + + size_t len = NS_tstrlen(prefix); + if (NS_tstrlen(abs_path) <= len) + return abs_path; + if (0 != strncmp(abs_path, prefix, len)) + return abs_path; + return abs_path + len + 1; +} + +/** + * Gets the platform specific path and performs simple checks to the path. If + * the path checks don't pass nullptr will be returned. + * + * @param line + * The line from the manifest that contains the path. + * @param isdir + * Whether the path is a directory path. Defaults to false. + * @return valid filesystem path or nullptr if the path checks fail. + */ +static NS_tchar* +get_valid_path(NS_tchar **line, bool isdir = false) +{ + NS_tchar *path = mstrtok(kQuote, line); + if (!path) + { + LOG(("get_valid_path: unable to determine path: " LOG_S, line)); + return nullptr; + } + + // All paths must be relative from the current working directory + if (path[0] == NS_T('/')) + { + LOG(("get_valid_path: path must be relative: " LOG_S, path)); + return nullptr; + } + +#ifdef _WIN32 + // All paths must be relative from the current working directory + if (path[0] == NS_T('\\') || path[1] == NS_T(':')) + { + LOG(("get_valid_path: path must be relative: " LOG_S, path)); + return nullptr; + } +#endif + + if (isdir) + { + // Directory paths must have a trailing forward slash. + if (path[NS_tstrlen(path) - 1] != NS_T('/')) + { + LOG(("get_valid_path: directory paths must have a trailing forward " \ + "slash: " LOG_S, path)); + return nullptr; + } + + // Remove the trailing forward slash because stat on Windows will return + // ENOENT if the path has a trailing slash. + path[NS_tstrlen(path) - 1] = NS_T('\0'); + } + + // Don't allow relative paths that resolve to a parent directory. + if (NS_tstrstr(path, NS_T("..")) != nullptr) + { + LOG(("get_valid_path: paths must not contain '..': " LOG_S, path)); + return nullptr; + } + + return path; +} + +static NS_tchar* +get_quoted_path(const NS_tchar *path) +{ + size_t lenQuote = NS_tstrlen(kQuote); + size_t lenPath = NS_tstrlen(path); + size_t len = lenQuote + lenPath + lenQuote + 1; + + NS_tchar *s = (NS_tchar *) malloc(len * sizeof(NS_tchar)); + if (!s) + return nullptr; + + NS_tchar *c = s; + NS_tstrcpy(c, kQuote); + c += lenQuote; + NS_tstrcat(c, path); + c += lenPath; + NS_tstrcat(c, kQuote); + c += lenQuote; + *c = NS_T('\0'); + c++; + return s; +} + +static void ensure_write_permissions(const NS_tchar *path) +{ +#ifdef _WIN32 + (void) _wchmod(path, _S_IREAD | _S_IWRITE); +#else + struct stat fs; + if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR)) + { + (void)chmod(path, fs.st_mode | S_IWUSR); + } +#endif +} + +static int ensure_remove(const NS_tchar *path) +{ + ensure_write_permissions(path); + int rv = NS_tremove(path); + if (rv) + LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return rv; +} + +// Remove the directory pointed to by path and all of its files and sub-directories. +static int ensure_remove_recursive(const NS_tchar *path, + bool continueEnumOnFailure = false) +{ + // We use lstat rather than stat here so that we can successfully remove + // symlinks. + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) + { + // This error is benign + return rv; + } + if (!S_ISDIR(sInfo.st_mode)) + { + return ensure_remove(path); + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) + { + LOG(("ensure_remove_recursive: unable to open directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + return rv; + } + + while ((entry = NS_treaddir(dir)) != 0) + { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) + { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + rv = ensure_remove_recursive(childPath); + if (rv && !continueEnumOnFailure) + { + break; + } + } + } + + NS_tclosedir(dir); + + if (rv == OK) + { + ensure_write_permissions(path); + rv = NS_trmdir(path); + if (rv) + { + LOG(("ensure_remove_recursive: unable to remove directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + } + } + return rv; +} + +static bool is_read_only(const NS_tchar *flags) +{ + size_t length = NS_tstrlen(flags); + if (length == 0) + return false; + + // Make sure the string begins with "r" + if (flags[0] != NS_T('r')) + return false; + + // Look for "r+" or "r+b" + if (length > 1 && flags[1] == NS_T('+')) + return false; + + // Look for "rb+" + if (NS_tstrcmp(flags, NS_T("rb+")) == 0) + return false; + + return true; +} + +static FILE* ensure_open(const NS_tchar *path, const NS_tchar *flags, unsigned int options) +{ + ensure_write_permissions(path); + FILE* f = NS_tfopen(path, flags); + if (is_read_only(flags)) + { + // Don't attempt to modify the file permissions if the file is being opened + // in read-only mode. + return f; + } + if (NS_tchmod(path, options) != 0) + { + if (f != nullptr) + { + fclose(f); + } + return nullptr; + } + struct NS_tstat_t ss; + if (NS_tstat(path, &ss) != 0 || ss.st_mode != options) + { + if (f != nullptr) + { + fclose(f); + } + return nullptr; + } + return f; +} + +// Ensure that the directory containing this file exists. +static int ensure_parent_dir(const NS_tchar *path) +{ + int rv = OK; + + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(path, NS_T('/')); + if (slash) + { + *slash = NS_T('\0'); + rv = ensure_parent_dir(path); + // Only attempt to create the directory if we're not at the root + if (rv == OK && *path) + { + rv = NS_tmkdir(path, 0755); + // If the directory already exists, then ignore the error. + if (rv < 0 && errno != EEXIST) + { + LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", " \ + "err: %d", path, errno)); + rv = WRITE_ERROR; + } + else + { + rv = OK; + } + } + *slash = NS_T('/'); + } + return rv; +} + +#ifdef UNIX +static int ensure_copy_symlink(const NS_tchar *path, const NS_tchar *dest) +{ + // Copy symlinks by creating a new symlink to the same target + NS_tchar target[MAXPATHLEN + 1] = {NS_T('\0')}; + int rv = readlink(path, target, MAXPATHLEN); + if (rv == -1) + { + LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + rv = symlink(target, dest); + if (rv == -1) + { + LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S ", target: " LOG_S " err: %d", + dest, target, errno)); + return READ_ERROR; + } + return 0; +} +#endif + +// Copy the file named path onto a new file named dest. +static int ensure_copy(const NS_tchar *path, const NS_tchar *dest) +{ +#ifdef _WIN32 + // Fast path for Windows + bool result = CopyFileW(path, dest, false); + if (!result) + { + LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_S ", lasterr: %x", + path, dest, GetLastError())); + return WRITE_ERROR_FILE_COPY; + } + return OK; +#else + struct NS_tstat_t ss; + int rv = NS_tlstat(path, &ss); + if (rv) + { + LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + +#ifdef UNIX + if (S_ISLNK(ss.st_mode)) + { + return ensure_copy_symlink(path, dest); + } +#endif + + AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode)); + if (!infile) + { + LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + AutoFile outfile(ensure_open(dest, NS_T("wb"), ss.st_mode)); + if (!outfile) + { + LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d", + dest, errno)); + return WRITE_ERROR; + } + + // This block size was chosen pretty arbitrarily but seems like a reasonable + // compromise. For example, the optimal block size on a modern macOS machine + // is 100k */ + const int blockSize = 32 * 1024; + void* buffer = malloc(blockSize); + if (!buffer) + return UPDATER_MEM_ERROR; + + while (!feof(infile.get())) + { + size_t read = fread(buffer, 1, blockSize, infile); + if (ferror(infile.get())) + { + LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d", + path, errno)); + free(buffer); + return READ_ERROR; + } + + size_t written = 0; + + while (written < read) + { + size_t nCount = read - written; + size_t chunkWritten = fwrite(buffer, 1, nCount, outfile); + if (chunkWritten != nCount) + { + LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d", + dest, errno)); + free(buffer); + return WRITE_ERROR_FILE_COPY; + } + + written += chunkWritten; + } + } + + rv = NS_tchmod(dest, ss.st_mode); + + free(buffer); + return rv; +#endif +} + +template <unsigned N> +struct copy_recursive_skiplist +{ + NS_tchar paths[N][MAXPATHLEN]; + + void append(unsigned index, const NS_tchar *path, const NS_tchar *suffix) + { + NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix); + } + + void append(unsigned index, const NS_tchar* path) + { + NS_tstrcpy(paths[index], path); + } + + bool find(const NS_tchar *path) + { + for (int i = 0; i < static_cast<int>(N); ++i) + { + if (!NS_tstricmp(paths[i], path)) + { + return true; + } + } + return false; + } +}; + +// Copy all of the files and subdirectories under path to a new directory named dest. +// The path names in the skiplist will be skipped and will not be copied. +template <unsigned N> +static int ensure_copy_recursive(const NS_tchar *path, const NS_tchar *dest, + copy_recursive_skiplist<N>& skiplist) +{ + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) + { + LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return READ_ERROR; + } + +#ifdef UNIX + if (S_ISLNK(sInfo.st_mode)) + { + return ensure_copy_symlink(path, dest); + } +#endif + + if (!S_ISDIR(sInfo.st_mode)) + { + return ensure_copy(path, dest); + } + + rv = NS_tmkdir(dest, sInfo.st_mode); + if (rv < 0 && errno != EEXIST) + { + LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return WRITE_ERROR; + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) + { + LOG(("ensure_copy_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return READ_ERROR; + } + + while ((entry = NS_treaddir(dir)) != 0) + { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) + { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + if (skiplist.find(childPath)) + { + continue; + } + NS_tchar childPathDest[MAXPATHLEN]; + NS_tsnprintf(childPathDest, sizeof(childPathDest)/sizeof(childPathDest[0]), + NS_T("%s/%s"), dest, entry->d_name); + rv = ensure_copy_recursive(childPath, childPathDest, skiplist); + if (rv) + { + break; + } + } + } + NS_tclosedir(dir); + return rv; +} + +// Renames the specified file to the new file specified. If the destination file +// exists it is removed. +static int rename_file(const NS_tchar *spath, const NS_tchar *dpath, + bool allowDirs = false) +{ + int rv = ensure_parent_dir(dpath); + if (rv) + return rv; + + struct NS_tstat_t spathInfo; + rv = NS_tstat(spath, &spathInfo); + if (rv) + { + LOG(("rename_file: failed to read file status info: " LOG_S ", " \ + "err: %d", spath, errno)); + return READ_ERROR; + } + + if (!S_ISREG(spathInfo.st_mode)) + { + if (allowDirs && !S_ISDIR(spathInfo.st_mode)) + { + LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d", + spath, errno)); + return RENAME_ERROR_EXPECTED_FILE; + } + else + { + LOG(("rename_file: proceeding to rename the directory")); + } + } + + if (!NS_taccess(dpath, F_OK)) + { + if (ensure_remove(dpath)) + { + LOG(("rename_file: destination file exists and could not be " \ + "removed: " LOG_S, dpath)); + return WRITE_ERROR_DELETE_FILE; + } + } + + if (NS_trename(spath, dpath) != 0) + { + LOG(("rename_file: failed to rename file - src: " LOG_S ", " \ + "dst:" LOG_S ", err: %d", spath, dpath, errno)); + return WRITE_ERROR; + } + + return OK; +} + +#ifdef _WIN32 +// Remove the directory pointed to by path and all of its files and +// sub-directories. If a file is in use move it to the tobedeleted directory +// and attempt to schedule removal of the file on reboot +static int remove_recursive_on_reboot(const NS_tchar *path, const NS_tchar *deleteDir) +{ + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) + { + // This error is benign + return rv; + } + + if (!S_ISDIR(sInfo.st_mode)) + { + NS_tchar tmpDeleteFile[MAXPATHLEN]; + GetTempFileNameW(deleteDir, L"rep", 0, tmpDeleteFile); + NS_tremove(tmpDeleteFile); + rv = rename_file(path, tmpDeleteFile, false); + if (MoveFileEx(rv ? path : tmpDeleteFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) + { + LOG(("remove_recursive_on_reboot: file will be removed on OS reboot: " + LOG_S, rv ? path : tmpDeleteFile)); + } + else + { + LOG(("remove_recursive_on_reboot: failed to schedule OS reboot removal of " + "file: " LOG_S, rv ? path : tmpDeleteFile)); + } + return rv; + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) + { + LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_S + ", rv: %d, err: %d", + path, rv, errno)); + return rv; + } + + while ((entry = NS_treaddir(dir)) != 0) + { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) + { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + // There is no need to check the return value of this call since this + // function is only called after an update is successful and there is not + // much that can be done to recover if it isn't successful. There is also + // no need to log the value since it will have already been logged. + remove_recursive_on_reboot(childPath, deleteDir); + } + } + + NS_tclosedir(dir); + + if (rv == OK) + { + ensure_write_permissions(path); + rv = NS_trmdir(path); + if (rv) + { + LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + } + } + return rv; +} +#endif + +//----------------------------------------------------------------------------- + +// Create a backup of the specified file by renaming it. +static int backup_create(const NS_tchar *path) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + return rename_file(path, backup); +} + +// Rename the backup of the specified file that was created by renaming it back +// to the original file. +static int backup_restore(const NS_tchar *path, const NS_tchar *relPath) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + NS_tchar relBackup[MAXPATHLEN]; + NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]), + NS_T("%s") BACKUP_EXT, relPath); + + if (NS_taccess(backup, F_OK)) + { + LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup)); + return OK; + } + + return rename_file(backup, path); +} + +// Discard the backup of the specified file that was created by renaming it. +static int backup_discard(const NS_tchar *path, const NS_tchar *relPath) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + NS_tchar relBackup[MAXPATHLEN]; + NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]), + NS_T("%s") BACKUP_EXT, relPath); + + // Nothing to discard + if (NS_taccess(backup, F_OK)) + { + return OK; + } + + int rv = ensure_remove(backup); +#if defined(_WIN32) + if (rv && !sStagedUpdate && !sReplaceRequest) + { + LOG(("backup_discard: unable to remove: " LOG_S, relBackup)); + NS_tchar path[MAXPATHLEN]; + GetTempFileNameW(gDeleteDirPath, L"moz", 0, path); + if (rename_file(backup, path)) + { + LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S, + relBackup, relPath)); + return WRITE_ERROR_DELETE_BACKUP; + } + // The MoveFileEx call to remove the file on OS reboot will fail if the + // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key + // but this is ok since the installer / uninstaller will delete the + // directory containing the file along with its contents after an update is + // applied, on reinstall, and on uninstall. + if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) + { + LOG(("backup_discard: file renamed and will be removed on OS " \ + "reboot: " LOG_S, relPath)); + } + else + { + LOG(("backup_discard: failed to schedule OS reboot removal of " \ + "file: " LOG_S, relPath)); + } + } +#else + if (rv) + return WRITE_ERROR_DELETE_BACKUP; +#endif + + return OK; +} + +// Helper function for post-processing a temporary backup. +static void backup_finish(const NS_tchar *path, const NS_tchar *relPath, + int status) +{ + if (status == OK) + backup_discard(path, relPath); + else + backup_restore(path, relPath); +} + +//----------------------------------------------------------------------------- + +static int DoUpdate(ArchiveReader& ArchiveReader); + +class Action +{ +public: + Action() : mProgressCost(1), mNext(nullptr) { } + virtual ~Action() { } + + virtual int Parse(NS_tchar *line) = 0; + + // Do any preprocessing to ensure that the action can be performed. Execute + // will be called if this Action and all others return OK from this method. + virtual int Prepare() = 0; + + // Perform the operation. Return OK to indicate success. After all actions + // have been executed, Finish will be called. A requirement of Execute is + // that its operation be reversible from Finish. + virtual int Execute() = 0; + + // Finish is called after execution of all actions. If status is OK, then + // all actions were successfully executed. Otherwise, some action failed. + virtual void Finish(int status) = 0; + + int mProgressCost; +private: + Action* mNext; + + friend class ActionList; +}; + +class RemoveFile : public Action +{ +public: + RemoveFile() : mSkip(0) { } + + int Parse(NS_tchar *line); + int Prepare(); + int Execute(); + void Finish(int status); + +private: + std::unique_ptr<const NS_tchar[]> mFile; + std::unique_ptr<NS_tchar[]> mRelPath; + int mSkip; +}; + +int +RemoveFile::Parse(NS_tchar *line) +{ + // format "<deadfile>" + + NS_tchar* validPath = get_valid_path(&line); + if (!validPath) + return PARSE_ERROR; + + mRelPath.reset(new NS_tchar[MAXPATHLEN]); + NS_tstrcpy(mRelPath.get(), validPath); + + mFile.reset(new_absolute_path(validPath)); + if (!mFile) + { + return PARSE_ERROR; + } + + return OK; +} + +int +RemoveFile::Prepare() +{ + // Skip the file if it already doesn't exist. + int rv = NS_taccess(mFile.get(), F_OK); + if (rv) + { + mSkip = 1; + mProgressCost = 0; + return OK; + } + + LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get())); + + // Make sure that we're actually a file... + struct NS_tstat_t fileInfo; + rv = NS_tstat(mFile.get(), &fileInfo); + if (rv) + { + LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(), + errno)); + return READ_ERROR; + } + + if (!S_ISREG(fileInfo.st_mode)) + { + LOG(("path present, but not a file: " LOG_S, mFile.get())); + return DELETE_ERROR_EXPECTED_FILE; + } + + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile.get(), NS_T('/')); + if (slash) + { + *slash = NS_T('\0'); + rv = NS_taccess(mFile.get(), W_OK); + *slash = NS_T('/'); + } + else + { + rv = NS_taccess(NS_T("."), W_OK); + } + + if (rv) + { + LOG(("access failed: %d", errno)); + return WRITE_ERROR_FILE_ACCESS_DENIED; + } + + return OK; +} + +int +RemoveFile::Execute() +{ + if (mSkip) + return OK; + + LOG(("EXECUTE REMOVEFILE " LOG_S, mRelPath.get())); + + // The file is checked for existence here and in Prepare since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mFile.get(), F_OK); + if (rv) + { + LOG(("file cannot be removed because it does not exist; skipping")); + mSkip = 1; + return OK; + } + + // Rename the old file. It will be removed in Finish. + rv = backup_create(mFile.get()); + if (rv) + { + LOG(("backup_create failed: %d", rv)); + return rv; + } + + return OK; +} + +void +RemoveFile::Finish(int status) +{ + if (mSkip) + return; + + LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get())); + + backup_finish(mFile.get(), mRelPath.get(), status); +} + +class RemoveDir : public Action +{ +public: + RemoveDir() : mSkip(0) { } + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // check that the source dir exists + virtual int Execute(); + virtual void Finish(int status); + +private: + std::unique_ptr<NS_tchar[]> mDir; + std::unique_ptr<NS_tchar[]> mRelPath; + int mSkip; +}; + +int +RemoveDir::Parse(NS_tchar *line) +{ + // format "<deaddir>/" + + NS_tchar* validPath = get_valid_path(&line, true); + if (!validPath) + return PARSE_ERROR; + + mRelPath.reset(new NS_tchar[MAXPATHLEN]); + NS_tstrcpy(mRelPath.get(), validPath); + + mDir.reset(new_absolute_path(validPath)); + if (!mDir) + { + return PARSE_ERROR; + } + + return OK; +} + +int +RemoveDir::Prepare() +{ + // We expect the directory to exist if we are to remove it. + int rv = NS_taccess(mDir.get(), F_OK); + if (rv) + { + mSkip = 1; + mProgressCost = 0; + return OK; + } + + LOG(("PREPARE REMOVEDIR " LOG_S "/", mRelPath.get())); + + // Make sure that we're actually a dir. + struct NS_tstat_t dirInfo; + rv = NS_tstat(mDir.get(), &dirInfo); + if (rv) + { + LOG(("failed to read directory status info: " LOG_S ", err: %d", mRelPath.get(), + errno)); + return READ_ERROR; + } + + if (!S_ISDIR(dirInfo.st_mode)) + { + LOG(("path present, but not a directory: " LOG_S, mRelPath.get())); + return DELETE_ERROR_EXPECTED_DIR; + } + + rv = NS_taccess(mDir.get(), W_OK); + if (rv) + { + LOG(("access failed: %d, %d", rv, errno)); + return WRITE_ERROR_DIR_ACCESS_DENIED; + } + + return OK; +} + +int +RemoveDir::Execute() +{ + if (mSkip) + return OK; + + LOG(("EXECUTE REMOVEDIR " LOG_S "/", mRelPath.get())); + + // The directory is checked for existence at every step since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mDir.get(), F_OK); + if (rv) + { + LOG(("directory no longer exists; skipping")); + mSkip = 1; + } + + return OK; +} + +void +RemoveDir::Finish(int status) +{ + if (mSkip || status != OK) + return; + + LOG(("FINISH REMOVEDIR " LOG_S "/", mRelPath.get())); + + // The directory is checked for existence at every step since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mDir.get(), F_OK); + if (rv) + { + LOG(("directory no longer exists; skipping")); + return; + } + + + if (status == OK) + { + if (NS_trmdir(mDir.get())) + { + LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d", + mRelPath.get(), rv, errno)); + } + } +} + +class AddFile : public Action +{ +public: + AddFile(ArchiveReader& ar) : mAdded(false), mArchiveReader(ar) { } + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +private: + std::unique_ptr<NS_tchar[]> mFile; + std::unique_ptr<NS_tchar[]> mRelPath; + bool mAdded; + ArchiveReader& mArchiveReader; +}; + +int +AddFile::Parse(NS_tchar *line) +{ + // format "<newfile>" + + NS_tchar* validPath = get_valid_path(&line); + if (!validPath) + return PARSE_ERROR; + + mRelPath.reset(new NS_tchar[MAXPATHLEN]); + NS_tstrcpy(mRelPath.get(), validPath); + + mFile.reset(new_absolute_path(validPath)); + if (!mFile) + { + return PARSE_ERROR; + } + + return OK; +} + +int +AddFile::Prepare() +{ + LOG(("PREPARE ADD " LOG_S, mRelPath.get())); + + return OK; +} + +int +AddFile::Execute() +{ + LOG(("EXECUTE ADD " LOG_S, mRelPath.get())); + + int rv; + + // First make sure that we can actually get rid of any existing file. + rv = NS_taccess(mFile.get(), F_OK); + if (rv == 0) + { + rv = backup_create(mFile.get()); + if (rv) + return rv; + } + else + { + rv = ensure_parent_dir(mFile.get()); + if (rv) + return rv; + } + +#ifdef _WIN32 + char sourcefile[MAXPATHLEN]; + if (!WideCharToMultiByte(CP_UTF8, 0, mRelPath.get(), -1, sourcefile, + MAXPATHLEN, nullptr, nullptr)) + { + LOG(("error converting wchar to utf8: %d", GetLastError())); + return STRING_CONVERSION_ERROR; + } + + rv = mArchiveReader.ExtractFile(sourcefile, mFile.get()); +#else + rv = mArchiveReader.ExtractFile(mRelPath.get(), mFile.get()); +#endif + if (!rv) + { + mAdded = true; + } + return rv; +} + +void +AddFile::Finish(int status) +{ + LOG(("FINISH ADD " LOG_S, mRelPath.get())); + // When there is an update failure and a file has been added it is removed + // here since there might not be a backup to replace it. + if (status && mAdded) + NS_tremove(mFile.get()); + backup_finish(mFile.get(), mRelPath.get(), status); +} + +class PatchFile : public Action +{ +public: + PatchFile(ArchiveReader& ar) : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr), mArchiveReader(ar) { } + + virtual ~PatchFile(); + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // should check for patch file and for checksum here + virtual int Execute(); + virtual void Finish(int status); + +private: + int LoadSourceFile(FILE* ofile); + + static int sPatchIndex; + + const NS_tchar *mPatchFile; + std::unique_ptr<NS_tchar> mFile; + std::unique_ptr<NS_tchar> mFileRelPath; + int mPatchIndex; + MBSPatchHeader header; + unsigned char *buf; + NS_tchar spath[MAXPATHLEN]; + AutoFile mPatchStream; + ArchiveReader& mArchiveReader; +}; + +int PatchFile::sPatchIndex = 0; + +PatchFile::~PatchFile() +{ + // Make sure mPatchStream gets unlocked on Windows; the system will do that, + // but not until some indeterminate future time, and we want determinism. + // Normally this happens at the end of Execute, when we close the stream; + // this call is here in case Execute errors out. +#ifdef _WIN32 + if (mPatchStream) + { + UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1); + } +#endif + + // delete the temporary patch file + if (spath[0]) + NS_tremove(spath); + + if (buf) + free(buf); +} + +int +PatchFile::LoadSourceFile(FILE* ofile) +{ + struct stat os; + int rv = fstat(fileno((FILE *)ofile), &os); + if (rv) + { + LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \ + "err: %d", mFileRelPath.get(), errno)); + return READ_ERROR; + } + + if (uint32_t(os.st_size) != header.slen) + { + LOG(("LoadSourceFile: destination file size %d does not match expected size %d", + uint32_t(os.st_size), header.slen)); + return LOADSOURCE_ERROR_WRONG_SIZE; + } + + buf = (unsigned char *) malloc(header.slen); + if (!buf) + return UPDATER_MEM_ERROR; + + size_t r = header.slen; + unsigned char *rb = buf; + while (r) + { + const size_t count = std::min<size_t>(SSIZE_MAX, r); + size_t c = fread(rb, 1, count, ofile); + if (c != count) + { + LOG(("LoadSourceFile: error reading destination file: " LOG_S, + mFileRelPath.get())); + return READ_ERROR; + } + + r -= c; + rb += c; + } + + // Verify that the contents of the source file correspond to what we expect. + + unsigned int crc = crc32(buf, header.slen); + + if (crc != header.scrc32) + { + LOG(("LoadSourceFile: destination file crc %d does not match expected " \ + "crc %d", crc, header.scrc32)); + return CRC_ERROR; + } + + return OK; +} + +int +PatchFile::Parse(NS_tchar *line) +{ + // format "<patchfile>" "<filetopatch>" + + // Get the path to the patch file inside of the mar + mPatchFile = mstrtok(kQuote, &line); + if (!mPatchFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + NS_tchar* validPath = get_valid_path(&line); + if (!validPath) + return PARSE_ERROR; + mFileRelPath.reset(new NS_tchar[MAXPATHLEN]); + NS_tstrcpy(mFileRelPath.get(), validPath); + + mFile.reset(new_absolute_path(validPath)); + if (!mFile) + { + return PARSE_ERROR; + } + + return OK; +} + +int +PatchFile::Prepare() +{ + LOG(("PREPARE PATCH " LOG_S, mFileRelPath.get())); + + // extract the patch to a temporary file + mPatchIndex = sPatchIndex++; + + int nWrittenBytes = NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]), + NS_T("%s/updating/%d.patch"), gWorkingDirPath, mPatchIndex); + (void) nWrittenBytes; + + NS_tremove(spath); + + mPatchStream = NS_tfopen(spath, NS_T("wb+")); + if (!mPatchStream) + return WRITE_ERROR; + +#ifdef _WIN32 + // Lock the patch file, so it can't be messed with between + // when we're done creating it and when we go to apply it. + if (!LockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1)) + { + LOG(("Couldn't lock patch file: %d", GetLastError())); + // TODO: moggi: fix the build problem with LOCK_ERROR_PATCH_FILE + return WRITE_ERROR; //return LOCK_ERROR_PATCH_FILE; + } + char sourcefile[MAXPATHLEN]; + if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN, + nullptr, nullptr)) + { + LOG(("error converting wchar to utf8: %d", GetLastError())); + return STRING_CONVERSION_ERROR; + } + + int rv = mArchiveReader.ExtractFileToStream(sourcefile, mPatchStream); +#else + int rv = mArchiveReader.ExtractFileToStream(mPatchFile, mPatchStream); +#endif + + return rv; +} + +int +PatchFile::Execute() +{ + LOG(("EXECUTE PATCH " LOG_S, mFileRelPath.get())); + + fseek(mPatchStream, 0, SEEK_SET); + + int rv = MBS_ReadHeader(mPatchStream, &header); + if (rv) + return rv; + + FILE *origfile = nullptr; +#ifdef _WIN32 + if (NS_tstrcmp(mFileRelPath.get(), gCallbackRelPath) == 0) + { + // Read from the copy of the callback when patching since the callback can't + // be opened for reading to prevent the application from being launched. + origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb")); + } + else + { + origfile = NS_tfopen(mFile.get(), NS_T("rb")); + } +#else + origfile = NS_tfopen(mFile.get(), NS_T("rb")); +#endif + + if (!origfile) + { + LOG(("unable to open destination file: " LOG_S ", err: %d", + mFileRelPath.get(), errno)); + return READ_ERROR; + } + + rv = LoadSourceFile(origfile); + fclose(origfile); + if (rv) + { + LOG(("LoadSourceFile failed")); + return rv; + } + + // Rename the destination file if it exists before proceeding so it can be + // used to restore the file to its original state if there is an error. + struct NS_tstat_t ss; + rv = NS_tstat(mFile.get(), &ss); + if (rv) + { + LOG(("failed to read file status info: " LOG_S ", err: %d", + mFileRelPath.get(), errno)); + return READ_ERROR; + } + + rv = backup_create(mFile.get()); + if (rv) + return rv; + +#if defined(HAVE_POSIX_FALLOCATE) + AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode)); + posix_fallocate(fileno((FILE *)ofile), 0, header.dlen); +#elif defined(_WIN32) + bool shouldTruncate = true; + // Creating the file, setting the size, and then closing the file handle + // lessens fragmentation more than any other method tested. Other methods that + // have been tested are: + // 1. _chsize / _chsize_s reduced fragmentation though not completely. + // 2. _get_osfhandle and then setting the size reduced fragmentation though + // not completely. There are also reports of _get_osfhandle failing on + // mingw. + HANDLE hfile = CreateFileW(mFile.get(), + GENERIC_WRITE, + 0, + nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (hfile != INVALID_HANDLE_VALUE) + { + if (SetFilePointer(hfile, header.dlen, + nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER && + SetEndOfFile(hfile) != 0) + { + shouldTruncate = false; + } + CloseHandle(hfile); + } + + AutoFile ofile(ensure_open(mFile.get(), shouldTruncate ? NS_T("wb+") : NS_T("rb+"), + ss.st_mode)); +#elif defined(MACOSX) + AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode)); + // Modified code from FileUtils.cpp + fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen}; + // Try to get a continuous chunk of disk space + rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store); + if (rv == -1) + { + // OK, perhaps we are too fragmented, allocate non-continuous + store.fst_flags = F_ALLOCATEALL; + rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store); + } + + if (rv != -1) + { + ftruncate(fileno((FILE *)ofile), header.dlen); + } +#else + AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode)); +#endif + + if (ofile == nullptr) + { + LOG(("unable to create new file: " LOG_S ", err: %d", mFileRelPath.get(), + errno)); + return WRITE_ERROR_OPEN_PATCH_FILE; + } + +#ifdef _WIN32 + if (!shouldTruncate) + { + fseek(ofile, 0, SEEK_SET); + } +#endif + + rv = MBS_ApplyPatch(&header, mPatchStream, buf, ofile); + + // Go ahead and do a bit of cleanup now to minimize runtime overhead. + // Make sure mPatchStream gets unlocked on Windows; the system will do that, + // but not until some indeterminate future time, and we want determinism. +#ifdef _WIN32 + UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1); +#endif + // Set mPatchStream to nullptr to make AutoFile close the file, + // so it can be deleted on Windows. + mPatchStream = nullptr; + NS_tremove(spath); + spath[0] = NS_T('\0'); + free(buf); + buf = nullptr; + return rv; +} + +void +PatchFile::Finish(int status) +{ + LOG(("FINISH PATCH " LOG_S, mFileRelPath.get())); + + backup_finish(mFile.get(), mFileRelPath.get(), status); +} + +class AddIfFile : public AddFile +{ +public: + AddIfFile(ArchiveReader& archiveReader); + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +protected: + std::unique_ptr<NS_tchar[]> mTestFile; +}; + +AddIfFile::AddIfFile(ArchiveReader& archiveReader): + AddFile(archiveReader) +{ +} + +int +AddIfFile::Parse(NS_tchar *line) +{ + // format "<testfile>" "<newfile>" + + mTestFile.reset(new_absolute_path(get_valid_path(&line))); + if (!mTestFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + return AddFile::Parse(line); +} + +int +AddIfFile::Prepare() +{ + // If the test file does not exist, then skip this action. + if (NS_taccess(mTestFile.get(), F_OK)) + { + mTestFile = nullptr; + return OK; + } + + return AddFile::Prepare(); +} + +int +AddIfFile::Execute() +{ + if (!mTestFile) + return OK; + + return AddFile::Execute(); +} + +void +AddIfFile::Finish(int status) +{ + if (!mTestFile) + return; + + AddFile::Finish(status); +} + +class AddIfNotFile : public AddFile +{ +public: + AddIfNotFile(ArchiveReader& archiveReader); + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +protected: + std::unique_ptr<NS_tchar[]> mTestFile; +}; + +AddIfNotFile::AddIfNotFile(ArchiveReader& archiveReader): + AddFile(archiveReader) +{ +} + +int +AddIfNotFile::Parse(NS_tchar *line) +{ + // format "<testfile>" "<newfile>" + + mTestFile.reset(new_absolute_path(get_valid_path(&line))); + if (!mTestFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + return AddFile::Parse(line); +} + +int +AddIfNotFile::Prepare() +{ + // If the test file exists, then skip this action. + if (!NS_taccess(mTestFile.get(), F_OK)) + { + mTestFile = NULL; + return OK; + } + + return AddFile::Prepare(); +} + +int +AddIfNotFile::Execute() +{ + if (!mTestFile) + return OK; + + return AddFile::Execute(); +} + +void +AddIfNotFile::Finish(int status) +{ + if (!mTestFile) + return; + + AddFile::Finish(status); +} + +class PatchIfFile : public PatchFile +{ +public: + PatchIfFile(ArchiveReader& archiveReader); + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // should check for patch file and for checksum here + virtual int Execute(); + virtual void Finish(int status); + +private: + std::unique_ptr<NS_tchar[]> mTestFile; +}; + +PatchIfFile::PatchIfFile(ArchiveReader& archiveReader): + PatchFile(archiveReader) +{ +} + +int +PatchIfFile::Parse(NS_tchar *line) +{ + // format "<testfile>" "<patchfile>" "<filetopatch>" + + mTestFile.reset(new_absolute_path(get_valid_path(&line))); + if (!mTestFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + return PatchFile::Parse(line); +} + +int +PatchIfFile::Prepare() +{ + // If the test file does not exist, then skip this action. + if (NS_taccess(mTestFile.get(), F_OK)) + { + mTestFile = nullptr; + return OK; + } + + return PatchFile::Prepare(); +} + +int +PatchIfFile::Execute() +{ + if (!mTestFile) + return OK; + + return PatchFile::Execute(); +} + +void +PatchIfFile::Finish(int status) +{ + if (!mTestFile) + return; + + PatchFile::Finish(status); +} + +//----------------------------------------------------------------------------- + +#ifdef _WIN32 + +/** + * Launch the post update application (helper.exe). It takes in the path of the + * callback application to calculate the path of helper.exe. For service updates + * this is called from both the system account and the current user account. + * + * @param installationDir The path to the callback application binary. + * @param updateInfoDir The directory where update info is stored. + * @return true if there was no error starting the process. + */ +bool +LaunchWinPostProcess(const WCHAR *installationDir, + const WCHAR *updateInfoDir) +{ + WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' }; + wcsncpy(workingDirectory, installationDir, MAX_PATH); + + // TODO: moggi: needs adaptation for LibreOffice + // Most likely we don't have the helper method yet. Check if we really need it. + + // Launch helper.exe to perform post processing (e.g. registry and log file + // modifications) for the update. + WCHAR inifile[MAX_PATH + 1] = { L'\0' }; + wcsncpy(inifile, installationDir, MAX_PATH); + if (!PathAppendSafe(inifile, L"updater.ini")) + { + return false; + } + + WCHAR exefile[MAX_PATH + 1]; + WCHAR exearg[MAX_PATH + 1]; + WCHAR exeasync[10]; + bool async = true; + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr, + exefile, MAX_PATH + 1, inifile)) + { + return false; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg, + MAX_PATH + 1, inifile)) + { + return false; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE", + exeasync, + sizeof(exeasync)/sizeof(exeasync[0]), + inifile)) + { + return false; + } + + // Verify that exeFile doesn't contain relative paths + if (wcsstr(exefile, L"..") != nullptr) + { + return false; + } + + WCHAR exefullpath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(exefullpath, installationDir, MAX_PATH); + if (!PathAppendSafe(exefullpath, exefile)) + { + return false; + } + +#if !defined(TEST_UPDATER) && defined(MAINTENANCE_SERVICE) + if (sUsingService && + !DoesBinaryMatchAllowedCertificates(installationDir, exefullpath)) + { + return false; + } +#endif + + WCHAR dlogFile[MAX_PATH + 1]; + if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) + { + return false; + } + + WCHAR slogFile[MAX_PATH + 1] = { L'\0' }; + wcsncpy(slogFile, updateInfoDir, MAX_PATH); + if (!PathAppendSafe(slogFile, L"update.log")) + { + return false; + } + + WCHAR dummyArg[14] = { L'\0' }; + wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1); + + size_t len = wcslen(exearg) + wcslen(dummyArg); + WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR)); + if (!cmdline) + { + return false; + } + + wcsncpy(cmdline, dummyArg, len); + wcscat(cmdline, exearg); + + if (sUsingService || + !_wcsnicmp(exeasync, L"false", 6) || + !_wcsnicmp(exeasync, L"0", 2)) + { + async = false; + } + + // We want to launch the post update helper app to update the Windows + // registry even if there is a failure with removing the uninstall.update + // file or copying the update.log file. + CopyFileW(slogFile, dlogFile, false); + + STARTUPINFOW si = {sizeof(si), 0}; + si.lpDesktop = L""; + PROCESS_INFORMATION pi = {0}; + + bool ok = CreateProcessW(exefullpath, + cmdline, + nullptr, // no special security attributes + nullptr, // no special thread attributes + false, // don't inherit filehandles + 0, // No special process creation flags + nullptr, // inherit my environment + workingDirectory, + &si, + &pi); + free(cmdline); + if (ok) + { + if (!async) + { + WaitForSingleObject(pi.hProcess, INFINITE); + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return ok; +} + +#endif + +static void +LaunchCallbackApp(const NS_tchar *workingDir, + int argc, + NS_tchar **argv, + bool usingService) +{ + putenv(const_cast<char*>("NO_EM_RESTART=")); + putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1")); + + // Run from the specified working directory (see bug 312360). This is not + // necessary on Windows CE since the application that launches the updater + // passes the working directory as an --environ: command line argument. + if (NS_tchdir(workingDir) != 0) + { + LOG(("Warning: chdir failed")); + } + +#if defined(USE_EXECV) + (void) argc; + (void) usingService; // avoid warnings + execv(argv[0], argv); +#elif defined(MACOSX) + LaunchChild(argc, (const char**)argv); +#elif defined(_WIN32) + // Do not allow the callback to run when running an update through the + // service as session 0. The unelevated updater.exe will do the launching. + if (!usingService) + { + WinLaunchChild(argv[0], argc, argv, nullptr); + } +#else +# warning "Need implementation of LaunchCallbackApp" +#endif +} + +static bool +WriteStatusFile(const char* aStatus) +{ + NS_tchar filename[MAXPATHLEN] = {NS_T('\0')}; +#if defined(_WIN32) + // The temp file is not removed on failure since there is client code that + // will remove it. + GetTempFileNameW(gPatchDirPath, L"sta", 0, filename); +#else + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); +#endif + + // Make sure that the directory for the update status file exists + if (ensure_parent_dir(filename)) + return false; + + // This is scoped to make the AutoFile close the file so it is possible to + // move the temp file to the update.status file on Windows. + { + AutoFile file(NS_tfopen(filename, NS_T("wb+"))); + if (file == nullptr) + { + return false; + } + + if (fwrite(aStatus, strlen(aStatus), 1, file) != 1) + { + return false; + } + } + +#if defined(_WIN32) + NS_tchar dstfilename[MAXPATHLEN] = {NS_T('\0')}; + NS_tsnprintf(dstfilename, sizeof(dstfilename)/sizeof(dstfilename[0]), + NS_T("%s\\update.status"), gPatchDirPath); + if (MoveFileExW(filename, dstfilename, MOVEFILE_REPLACE_EXISTING) == 0) + { + return false; + } +#endif + + return true; +} + +static void +WriteStatusFile(int status) +{ + const char *text; + + char buf[32]; + if (status == OK) + { + if (sStagedUpdate) + { + text = "applied\n"; + } + else + { + text = "succeeded\n"; + } + } + else + { + snprintf(buf, sizeof(buf)/sizeof(buf[0]), "failed: %d\n", status); + text = buf; + } + + WriteStatusFile(text); +} + +#ifdef MAINTENANCE_SERVICE +/* + * Read the update.status file and sets isPendingService to true if + * the status is set to pending-service. + * + * @param isPendingService Out parameter for specifying if the status + * is set to pending-service or not. + * @return true if the information was retrieved and it is pending + * or pending-service. + */ +static bool +IsUpdateStatusPendingService() +{ + NS_tchar filename[MAXPATHLEN]; + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); + + AutoFile file(NS_tfopen(filename, NS_T("rb"))); + if (file == nullptr) + return false; + + char buf[32] = { 0 }; + fread(buf, sizeof(buf), 1, file); + + const char kPendingService[] = "pending-service"; + const char kAppliedService[] = "applied-service"; + + return (strncmp(buf, kPendingService, + sizeof(kPendingService) - 1) == 0) || + (strncmp(buf, kAppliedService, + sizeof(kAppliedService) - 1) == 0); +} +#endif + +#ifdef _WIN32 +/* + * Read the update.status file and sets isSuccess to true if + * the status is set to succeeded. + * + * @param isSucceeded Out parameter for specifying if the status + * is set to succeeded or not. + * @return true if the information was retrieved and it is succeeded. + */ +static bool +IsUpdateStatusSucceeded(bool &isSucceeded) +{ + isSucceeded = false; + NS_tchar filename[MAXPATHLEN]; + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); + + AutoFile file(NS_tfopen(filename, NS_T("rb"))); + if (file == nullptr) + return false; + + char buf[32] = { 0 }; + fread(buf, sizeof(buf), 1, file); + + const char kSucceeded[] = "succeeded"; + isSucceeded = strncmp(buf, kSucceeded, + sizeof(kSucceeded) - 1) == 0; + return true; +} +#endif + +/* + * Copy the entire contents of the application installation directory to the + * destination directory for the update process. + * + * @return 0 if successful, an error code otherwise. + */ +static int +CopyInstallDirToDestDir() +{ + // These files should not be copied over to the updated app +#ifdef _WIN32 +#define SKIPLIST_COUNT 4 +#elif defined(MACOSX) +#define SKIPLIST_COUNT 1 +#else +#define SKIPLIST_COUNT 3 +#endif + copy_recursive_skiplist<SKIPLIST_COUNT> skiplist; + + std::unique_ptr<NS_tchar[]> pUserProfile(new NS_tchar[MAXPATHLEN]); + NS_tstrcpy(pUserProfile.get(), gPatchDirPath); + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(pUserProfile.get(), NS_T('/')); + if (slash) + *slash = NS_T('\0'); + + LOG(("ignore user profile directory during copy: " LOG_S, pUserProfile.get())); + + skiplist.append(0, pUserProfile.get()); +#ifndef MACOSX + skiplist.append(1, gInstallDirPath, NS_T("updated")); + skiplist.append(2, gInstallDirPath, NS_T("updates/0")); +#ifdef _WIN32 + skiplist.append(4, gInstallDirPath, NS_T("updated.update_in_progress.lock")); +#endif +#endif + + return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist); +} + +/* + * Replace the application installation directory with the destination + * directory in order to finish a staged update task + * + * @return 0 if successful, an error code otherwise. + */ +static int +ProcessReplaceRequest() +{ + // TODO: moggi: handle the user profile in the installation dir also + // during the replacement request + // The replacement algorithm is like this: + // 1. Move destDir to tmpDir. In case of failure, abort. + // 2. Move newDir to destDir. In case of failure, revert step 1 and abort. + // 3. Delete tmpDir (or defer it to the next reboot). + +#ifdef MACOSX + NS_tchar destDir[MAXPATHLEN]; + NS_tsnprintf(destDir, sizeof(destDir)/sizeof(destDir[0]), + NS_T("%s/Contents"), gInstallDirPath); +#elif defined(_WIN32) + // Windows preserves the case of the file/directory names. We use the + // GetLongPathName API in order to get the correct case for the directory + // name, so that if the user has used a different case when launching the + // application, the installation directory's name does not change. + NS_tchar destDir[MAXPATHLEN]; + if (!GetLongPathNameW(gInstallDirPath, destDir, + sizeof(destDir)/sizeof(destDir[0]))) + { + return NO_INSTALLDIR_ERROR; + } +#else + NS_tchar* destDir = gInstallDirPath; +#endif + + NS_tchar tmpDir[MAXPATHLEN]; + NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]), + NS_T("%s.bak"), destDir); + + // First try to remove the possibly existing temp directory, because if this + // directory exists, we will fail to rename destDir. + // No need to error check here because if this fails, we will fail in the + // next step anyways. + ensure_remove_recursive(tmpDir); + + LOG(("Begin moving destDir (" LOG_S ") to tmpDir (" LOG_S ")", + destDir, tmpDir)); + LogFlush(); + int rv = rename_file(destDir, tmpDir, true); +#ifdef _WIN32 + // On Windows, if Firefox is launched using the shortcut, it will hold a handle + // to its installation directory open, which might not get released in time. + // Therefore we wait a little bit here to see if the handle is released. + // If it's not released, we just fail to perform the replace request. + const int max_retries = 10; + int retries = 0; + while (rv == WRITE_ERROR && (retries++ < max_retries)) + { + LOG(("PerformReplaceRequest: destDir rename attempt %d failed. " \ + "File: " LOG_S ". Last error: %d, err: %d", retries, + destDir, GetLastError(), rv)); + + Sleep(100); + + rv = rename_file(destDir, tmpDir, true); + } +#endif + if (rv) + { + // The status file will have 'pending' written to it so there is no value in + // returning an error specific for this failure. + LOG(("Moving destDir to tmpDir failed, err: %d", rv)); + return rv; + } + + NS_tchar newDir[MAXPATHLEN]; + if (is_userprofile_in_instdir()) + { + LOG(("user profile in instdir")); + NS_tstrcpy(newDir, tmpDir); + NS_tstrcat(newDir, gWorkingDirPath + NS_tstrlen(gInstallDirPath)); + LOG((LOG_S, newDir)); + } + else + { + NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]), + NS_T("%s"), + gWorkingDirPath); + } + + LOG(("Begin moving newDir (" LOG_S ") to destDir (" LOG_S ")", + newDir, destDir)); + rv = rename_file(newDir, destDir, true); +#ifdef MACOSX + if (rv) + { + LOG(("Moving failed. Begin copying newDir (" LOG_S ") to destDir (" LOG_S ")", + newDir, destDir)); + copy_recursive_skiplist<0> skiplist; + rv = ensure_copy_recursive(newDir, destDir, skiplist); + } +#endif + if (rv) + { + LOG(("Moving newDir to destDir failed, err: %d", rv)); + LOG(("Now, try to move tmpDir back to destDir")); + ensure_remove_recursive(destDir); + int rv2 = rename_file(tmpDir, destDir, true); + if (rv2) + { + LOG(("Moving tmpDir back to destDir failed, err: %d", rv2)); + } + // The status file will be have 'pending' written to it so there is no value + // in returning an error specific for this failure. + return rv; + } + + if (is_userprofile_in_instdir()) + { + // 1.) calculate path of the user profile in the backup directory + // 2.) move the user profile from the backup to the install directory + NS_tchar backup_user_profile[MAXPATHLEN]; + NS_tchar userprofile[MAXPATHLEN]; + + NS_tstrcpy(userprofile, gPatchDirPath); + NS_tchar* slash = (NS_tchar *) NS_tstrrchr(userprofile, NS_T('/')); + if (slash) + *slash = NS_T('\0'); + NS_tstrcpy(backup_user_profile, tmpDir); + size_t installdir_len = NS_tstrlen(destDir); + + NS_tstrcat(backup_user_profile, userprofile + installdir_len); + LOG(("copy user profile back from " LOG_S " to " LOG_S, backup_user_profile, userprofile)); + int rv2 = rename_file(backup_user_profile, userprofile); + if (rv2) + { + LOG(("failed to copy user profile back")); + } + if (slash) + *slash = NS_T('/'); + } + +#if !defined(_WIN32) && !defined(MACOSX) + // Platforms that have their updates directory in the installation directory + // need to have the last-update.log and backup-update.log files moved from the + // old installation directory to the new installation directory. + NS_tchar tmpLog[MAXPATHLEN]; + int nWrittenBytes = NS_tsnprintf(tmpLog, sizeof(tmpLog)/sizeof(tmpLog[0]), + NS_T("%s/updates/last-update.log"), tmpDir); + (void) nWrittenBytes; + if (!NS_taccess(tmpLog, F_OK)) + { + NS_tchar destLog[MAXPATHLEN]; + NS_tsnprintf(destLog, sizeof(destLog)/sizeof(destLog[0]), + NS_T("%s/updates/last-update.log"), destDir); + NS_tremove(destLog); + NS_trename(tmpLog, destLog); + } +#endif + + LOG(("Now, remove the tmpDir")); + rv = ensure_remove_recursive(tmpDir, true); + if (rv) + { + LOG(("Removing tmpDir failed, err: %d", rv)); +#ifdef _WIN32 + NS_tchar deleteDir[MAXPATHLEN]; + NS_tsnprintf(deleteDir, sizeof(deleteDir)/sizeof(deleteDir[0]), + NS_T("%s\\%s"), destDir, DELETE_DIR); + // Attempt to remove the tobedeleted directory and then recreate it if it + // was successfully removed. + _wrmdir(deleteDir); + if (NS_taccess(deleteDir, F_OK)) + { + NS_tmkdir(deleteDir, 0755); + } + remove_recursive_on_reboot(tmpDir, deleteDir); +#endif + } + +#ifdef MACOSX + // On macOS, we need to remove the staging directory after its Contents + // directory has been moved. + NS_tchar updatedAppDir[MAXPATHLEN]; + NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]), + NS_T("%s/Updated.app"), gPatchDirPath); + ensure_remove_recursive(updatedAppDir); +#endif + + gSucceeded = true; + + return 0; +} + +#ifdef _WIN32 +static void +WaitForServiceFinishThread(void* /*param*/) +{ + // We wait at most 10 minutes, we already waited 5 seconds previously + // before deciding to show this UI. + WaitForServiceStop(SVC_NAME, 595); + QuitProgressUI(); +} +#endif + +#ifdef VERIFY_MAR_SIGNATURE +/** + * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini + * + * @param path The path to the ini file that is to be read + * @param results A pointer to the location to store the read strings + * @return OK on success + */ +static int +ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results) +{ + // TODO: moggi: needs adaptation for LibreOffice + // Check where this function gets its parameters from + const unsigned int kNumStrings = 1; + const char *kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0"; + char updater_strings[kNumStrings][MAX_TEXT_LEN]; + + int result = ReadStrings(path, kUpdaterKeys, kNumStrings, + updater_strings, "Settings"); + + strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1); + results->MARChannelID[MAX_TEXT_LEN - 1] = 0; + + return result; +} +#endif + +static int +GetUpdateFileNames(std::vector<tstring>& fileNames) +{ + NS_tchar fileName[MAXPATHLEN]; + NS_tsnprintf(fileName, MAXPATHLEN, + NS_T("%s/update.mar"), gPatchDirPath); + fileNames.push_back(fileName); + + // add the language packs + NS_tDIR* dir = NS_topendir(gPatchDirPath); + if (!dir) + { + LOG(("Could not open directory " LOG_S, gPatchDirPath)); + return READ_ERROR; + } + + NS_tdirent* entry; + while ((entry = NS_treaddir(dir)) != nullptr) + { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T("..")) && + NS_tstrcmp(entry->d_name, NS_T("update.mar"))) + { + if (NS_tstrncmp(entry->d_name, NS_T("update"), 6) == 0) + { + NS_tchar *dot = NS_tstrrchr(entry->d_name, NS_T('.')); + if (dot && !NS_tstrcmp(dot, NS_T(".mar"))) + { + NS_tchar updatePath[MAXPATHLEN]; + NS_tsnprintf(updatePath, sizeof(updatePath)/sizeof(updatePath[0]), + NS_T("%s/%s"), gPatchDirPath, entry->d_name); + + LOG (("Found language update file: " LOG_S, updatePath)); + fileNames.push_back(updatePath); + } + } + } + } + return OK; +} + +static int +CheckSignature(ArchiveReader& archiveReader) +{ +#ifdef VERIFY_MAR_SIGNATURE +#ifdef _WIN32 + HKEY baseKey = nullptr; + wchar_t valueName[] = L"Image Path"; + wchar_t rasenh[] = L"rsaenh.dll"; + bool reset = false; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider\\Microsoft Enhanced Cryptographic Provider v1.0", + 0, KEY_READ | KEY_WRITE, + &baseKey) == ERROR_SUCCESS) + { + wchar_t path[MAX_PATH + 1]; + DWORD size = sizeof(path); + DWORD type; + if (RegQueryValueExW(baseKey, valueName, 0, &type, + (LPBYTE)path, &size) == ERROR_SUCCESS) + { + if (type == REG_SZ && wcscmp(path, rasenh) == 0) + { + wchar_t rasenhFullPath[] = L"%SystemRoot%\\System32\\rsaenh.dll"; + if (RegSetValueExW(baseKey, valueName, 0, REG_SZ, + (const BYTE*)rasenhFullPath, + sizeof(rasenhFullPath)) == ERROR_SUCCESS) + { + reset = true; + } + } + } + } +#endif + int rv = archiveReader.VerifySignature(); +#ifdef _WIN32 + if (baseKey) + { + if (reset) + { + RegSetValueExW(baseKey, valueName, 0, REG_SZ, + (const BYTE*)rasenh, + sizeof(rasenh)); + } + RegCloseKey(baseKey); + } +#endif + + + if (rv == OK) + { + if (rv == OK) + { + NS_tchar updateSettingsPath[MAX_TEXT_LEN]; + + // TODO: moggi: needs adaptation for LibreOffice + // These paths need to be adapted for us. + int nWrittenBytes = NS_tsnprintf(updateSettingsPath, + sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]), +#ifdef MACOSX + NS_T("%s/Contents/Resources/update-settings.ini"), +#else + NS_T("%s/update-settings.ini"), +#endif + gWorkingDirPath); + (void) nWrittenBytes; + MARChannelStringTable MARStrings; + if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) + { + // If we can't read from update-settings.ini then we shouldn't impose + // a MAR restriction. Some installations won't even include this file. + MARStrings.MARChannelID[0] = '\0'; + } + + rv = archiveReader.VerifyProductInformation(MARStrings.MARChannelID, + LIBO_VERSION_DOTTED); + } + } +#endif + + return rv; +} + +static void +UpdateThreadFunc(void * /*param*/) +{ + // open ZIP archive and process... + int rv = OK; + if (sReplaceRequest) + { + rv = ProcessReplaceRequest(); + } + else + { + std::vector<tstring> fileNames; + GetUpdateFileNames(fileNames); + + for (auto& fileName: fileNames) + { + ArchiveReader archiveReader; + rv = archiveReader.Open(fileName.c_str()); + if (rv != OK) + { + LOG(("Could not open " LOG_S, fileName.c_str())); + break; + } + + rv = CheckSignature(archiveReader); + if (rv != OK) + { + LOG(("Could not verify the signature of " LOG_S, fileName.c_str())); + break; + } + } + + if (rv == OK && sStagedUpdate) + { + rv = CopyInstallDirToDestDir(); + } + + if (rv == OK) + { + for (auto& fileName: fileNames) + { + ArchiveReader archiveReader; + archiveReader.Open(fileName.c_str()); + rv = DoUpdate(archiveReader); + } + NS_tchar updatingDir[MAXPATHLEN]; + int nWrittenBytes = NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]), + NS_T("%s/updating"), gWorkingDirPath); + (void) nWrittenBytes; + ensure_remove_recursive(updatingDir); + } + } + + if (rv && (sReplaceRequest || sStagedUpdate)) + { +#ifdef _WIN32 + // On Windows, the current working directory of the process should be changed + // so that it's not locked. + if (sStagedUpdate) + { + NS_tchar sysDir[MAX_PATH + 1] = { L'\0' }; + if (GetSystemDirectoryW(sysDir, MAX_PATH + 1)) + { + NS_tchdir(sysDir); + } + } +#endif + ensure_remove_recursive(gWorkingDirPath); + // When attempting to replace the application, we should fall back + // to non-staged updates in case of a failure. We do this by + // setting the status to pending, exiting the updater, and + // launching the callback application. The callback application's + // startup path will see the pending status, and will start the + // updater application again in order to apply the update without + // staging. + if (sReplaceRequest) + { + WriteStatusFile(sUsingService ? "pending-service" : "pending"); + } + else + { + WriteStatusFile(rv); + } +#ifdef TEST_UPDATER + // Some tests need to use --test-process-updates again. + putenv(const_cast<char*>("MOZ_TEST_PROCESS_UPDATES=")); +#endif + } + else + { + if (rv) + { + LOG(("failed: %d", rv)); + } + else + { +#ifdef MACOSX + // If the update was successful we need to update the timestamp on the + // top-level macOS bundle directory so that macOS's Launch Services + // picks up any major changes when the bundle is updated. + if (!sStagedUpdate && utimes(gInstallDirPath, nullptr) != 0) + { + LOG(("Couldn't set access/modification time on application bundle.")); + } +#endif + + LOG(("succeeded")); + } + WriteStatusFile(rv); + } + + LOG(("calling QuitProgressUI")); + QuitProgressUI(); +} + +#ifdef MACOSX +static void +ServeElevatedUpdateThreadFunc(void* param) +{ + UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param; + gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv); + if (!gSucceeded) + { + WriteStatusFile(ELEVATION_CANCELED); + } + QuitProgressUI(); +} + +void freeArguments(int argc, char** argv) +{ + for (int i = 0; i < argc; i++) + { + free(argv[i]); + } + free(argv); +} +#endif + +int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv, + int callbackIndex +#ifdef _WIN32 + , const WCHAR* elevatedLockFilePath + , HANDLE updateLockFileHandle +#elif defined(MACOSX) + , bool isElevated +#endif + ) +{ + if (argc > callbackIndex) + { +#if defined(_WIN32) + if (gSucceeded) + { + if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) + { + fprintf(stderr, "The post update process was not launched"); + } + + // The service update will only be executed if it is already installed. + // For first time installs of the service, the install will happen from + // the PostUpdate process. We do the service update process here + // because it's possible we are updating with updater.exe without the + // service if the service failed to apply the update. We want to update + // the service to a newer version in that case. If we are not running + // through the service, then USING_SERVICE will not exist. + if (!sUsingService) + { + StartServiceUpdate(gInstallDirPath); + } + } + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0); +#elif defined(MACOSX) + if (!isElevated) + { + if (gSucceeded) + { + LaunchMacPostProcess(gInstallDirPath); + } +#endif + + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); +#ifdef XP_MACOSX + } // if (!isElevated) +#endif /* XP_MACOSX */ +} +return 0; +} + +int NS_main(int argc, NS_tchar **argv) +{ + // The callback is the remaining arguments starting at callbackIndex. + // The argument specified by callbackIndex is the callback executable and the + // argument prior to callbackIndex is the working directory. + const int callbackIndex = 6; + +#ifdef MACOSX + // TODO: moggi: needs adaptation for LibreOffice + bool isElevated = + strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0; + if (isElevated) + { + if (!ObtainUpdaterArguments(&argc, &argv)) + { + // Won't actually get here because ObtainUpdaterArguments will terminate + // the current process on failure. + return 1; + } + } +#endif + +#if defined(VERIFY_MAR_SIGNATURE) && !defined(_WIN32) && !defined(MACOSX) + // On Windows and Mac we rely on native APIs to do verifications so we don't + // need to initialize NSS at all there. + // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS + // databases. + if (NSS_NoDB_Init(NULL) != SECSuccess) + { + PRErrorCode error = PR_GetError(); + fprintf(stderr, "Could not initialize NSS: %s (%d)", + PR_ErrorToName(error), (int) error); + _exit(1); + } +#endif + +#ifdef MACOSX + if (!isElevated) + { +#endif + InitProgressUI(&argc, &argv); +#ifdef MACOSX + } +#endif + + // To process an update the updater command line must at a minimum have the + // directory path containing the updater.mar file to process as the first + // argument, the install directory as the second argument, and the directory + // to apply the update to as the third argument. When the updater is launched + // by another process the PID of the parent process should be provided in the + // optional fourth argument and the updater will wait on the parent process to + // exit if the value is non-zero and the process is present. This is necessary + // due to not being able to update files that are in use on Windows. The + // optional fifth argument is the callback's working directory and the + // optional sixth argument is the callback path. The callback is the + // application to launch after updating and it will be launched when these + // arguments are provided whether the update was successful or not. All + // remaining arguments are optional and are passed to the callback when it is + // launched. + if (argc < 4) + { + fprintf(stderr, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n"); +#ifdef MACOSX + if (isElevated) + { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + + // The directory containing the update information. + gPatchDirPath = argv[1]; + + // The directory we're going to update to. + // We copy this string because we need to remove trailing slashes. The C++ + // standard says that it's always safe to write to strings pointed to by argv + // elements, but I don't necessarily believe it. + NS_tstrncpy(gInstallDirPath, argv[2], MAXPATHLEN); + gInstallDirPath[MAXPATHLEN - 1] = NS_T('\0'); + NS_tchar *slash = NS_tstrrchr(gInstallDirPath, NS_SLASH); + if (slash && !slash[1]) + { + *slash = NS_T('\0'); + } + +#ifdef _WIN32 + bool useService = false; + bool testOnlyFallbackKeyExists = false; + bool noServiceFallback = false; + + // We never want the service to be used unless we build with + // the maintenance service. +#ifdef MAINTENANCE_SERVICE + useService = IsUpdateStatusPendingService(); + // Our tests run with a different apply directory for each test. + // We use this registry key on our test slaves to store the + // allowed name/issuers. + testOnlyFallbackKeyExists = DoesFallbackKeyExist(); +#endif + + // Remove everything except close window from the context menu + { + // TODO: moggi: needs adaptation for LibreOffice + HKEY hkApp = nullptr; + RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", + 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr, + &hkApp, nullptr); + RegCloseKey(hkApp); + if (RegCreateKeyExW(HKEY_CURRENT_USER, + L"Software\\Classes\\Applications\\updater.exe", + 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr, + &hkApp, nullptr) == ERROR_SUCCESS) + { + RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0); + RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0); + RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0); + RegCloseKey(hkApp); + } + } +#endif + + // If there is a PID specified and it is not '0' then wait for the process to exit. +#ifdef _WIN32 + __int64 pid = 0; +#else + int pid = 0; +#endif + if (argc > 4) + { +#ifdef _WIN32 + pid = _wtoi64(argv[4]); +#else + pid = atoi(argv[4]); +#endif + if (pid == -1) + { + // This is a signal from the parent process that the updater should stage + // the update. + sStagedUpdate = true; + } + else if (NS_tstrstr(argv[4], NS_T("/replace"))) + { + // We're processing a request to replace the application with a staged + // update. + sReplaceRequest = true; + } + } + + // The directory we're going to update to. + // We copy this string because we need to remove trailing slashes. The C++ + // standard says that it's always safe to write to strings pointed to by argv + // elements, but I don't necessarily believe it. + NS_tstrncpy(gWorkingDirPath, argv[3], MAXPATHLEN); + gWorkingDirPath[MAXPATHLEN - 1] = NS_T('\0'); + slash = NS_tstrrchr(gWorkingDirPath, NS_SLASH); + if (slash && !slash[1]) + { + *slash = NS_T('\0'); + } + +#ifdef MACOSX + if (!isElevated && !IsRecursivelyWritable(argv[2])) + { + // If the app directory isn't recursively writeable, an elevated update is + // required. + UpdateServerThreadArgs threadArgs; + threadArgs.argc = argc; + threadArgs.argv = const_cast<const NS_tchar**>(argv); + + Thread t1; + if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0) + { + // Show an indeterminate progress bar while an elevated update is in + // progress. + ShowProgressUI(true); + } + t1.Join(); + + LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex, false); + return gSucceeded ? 0 : 1; + } +#endif + + LogInit(gPatchDirPath, NS_T("update.log")); + + if (!WriteStatusFile("applying")) + { + LOG(("failed setting status to 'applying'")); +#ifdef MACOSX + if (isElevated) + { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + + if (sStagedUpdate) + { + LOG(("Performing a staged update")); + } + else if (sReplaceRequest) + { + LOG(("Performing a replace request")); + } + + LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath)); + LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath)); + LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath)); + +#ifdef _WIN32 + if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0) + { + if (!sStagedUpdate && !sReplaceRequest) + { + WriteStatusFile(INVALID_APPLYTO_DIR_ERROR); + LOG(("Installation directory and working directory must be the same " + "for non-staged updates. Exiting.")); + LogFinish(); + return 1; + } + + NS_tchar workingDirParent[MAX_PATH]; + NS_tsnprintf(workingDirParent, + sizeof(workingDirParent) / sizeof(workingDirParent[0]), + NS_T("%s"), gWorkingDirPath); + if (!PathRemoveFileSpecW(workingDirParent)) + { + WriteStatusFile(REMOVE_FILE_SPEC_ERROR); + LOG(("Error calling PathRemoveFileSpecW: %d", GetLastError())); + LogFinish(); + return 1; + } + + if (_wcsnicmp(workingDirParent, gInstallDirPath, MAX_PATH) != 0) + { + WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR); + LOG(("The apply-to directory must be the same as or " + "a child of the installation directory! Exiting.")); + LogFinish(); + return 1; + } + } +#endif + + +#ifdef _WIN32 + if (pid > 0) + { + HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD) pid); + // May return nullptr if the parent process has already gone away. + // Otherwise, wait for the parent process to exit before starting the + // update. + if (parent) + { + DWORD waitTime = PARENT_WAIT; + DWORD result = WaitForSingleObject(parent, waitTime); + CloseHandle(parent); + if (result != WAIT_OBJECT_0) + return 1; + } + } +#else + if (pid > 0) + waitpid(pid, nullptr, 0); +#endif + +#if defined(_WIN32) +#ifdef MAINTENANCE_SERVICE + sUsingService = EnvHasValue("USING_SERVICE"); + putenv(const_cast<char*>("USING_SERVICE=")); +#endif + // lastFallbackError keeps track of the last error for the service not being + // used, in case of an error when fallback is not enabled we write the + // error to the update.status file. + // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then + // we will instead fallback to not using the service and display a UAC prompt. + int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR; + + // Launch a second instance of the updater with the runas verb on Windows + // when write access is denied to the installation directory. + HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE; + NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')}; + if (!sUsingService && + (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) + { + NS_tchar updateLockFilePath[MAXPATHLEN]; + if (sStagedUpdate) + { + // When staging an update, the lock file is: + // <install_dir>\updated.update_in_progress.lock + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s/updated.update_in_progress.lock"), gInstallDirPath); + } + else if (sReplaceRequest) + { + // When processing a replace request, the lock file is: + // <install_dir>\..\moz_update_in_progress.lock + NS_tchar installDir[MAXPATHLEN]; + NS_tstrcpy(installDir, gInstallDirPath); + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH); + *slash = NS_T('\0'); + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s\\moz_update_in_progress.lock"), installDir); + } + else + { + // In the non-staging update case, the lock file is: + // <install_dir>\<app_name>.exe.update_in_progress.lock + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s.update_in_progress.lock"), argv[callbackIndex]); + } + + // The update_in_progress.lock file should only exist during an update. In + // case it exists attempt to remove it and exit if that fails to prevent + // simultaneous updates occurring. + if (!_waccess(updateLockFilePath, F_OK) && + NS_tremove(updateLockFilePath) != 0) + { + // Try to fall back to the old way of doing updates if a staged + // update fails. + if (sStagedUpdate || sReplaceRequest) + { + // Note that this could fail, but if it does, there isn't too much we + // can do in order to recover anyways. + WriteStatusFile("pending"); + } + LOG(("Update already in progress! Exiting")); + return 1; + } + + updateLockFileHandle = CreateFileW(updateLockFilePath, + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + + NS_tsnprintf(elevatedLockFilePath, + sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]), + NS_T("%s/update_elevated.lock"), gPatchDirPath); + + // Even if a file has no sharing access, you can still get its attributes + bool startedFromUnelevatedUpdater = + GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES; + + // If we're running from the service, then we were started with the same + // token as the service so the permissions are already dropped. If we're + // running from an elevated updater that was started from an unelevated + // updater, then we drop the permissions here. We do not drop the + // permissions on the originally called updater because we use its token + // to start the callback application. + if (startedFromUnelevatedUpdater) + { + // Disable every privilege we don't need. Processes started using + // CreateProcess will use the same token as this process. + UACHelper::DisablePrivileges(nullptr); + } + + if (updateLockFileHandle == INVALID_HANDLE_VALUE || + (useService && testOnlyFallbackKeyExists && noServiceFallback)) + { + if (!_waccess(elevatedLockFilePath, F_OK) && + NS_tremove(elevatedLockFilePath) != 0) + { + fprintf(stderr, "Unable to create elevated lock file! Exiting\n"); + return 1; + } + + HANDLE elevatedFileHandle; + elevatedFileHandle = CreateFileW(elevatedLockFilePath, + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + + if (elevatedFileHandle == INVALID_HANDLE_VALUE) + { + LOG(("Unable to create elevated lock file! Exiting")); + return 1; + } + + wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1); + if (!cmdLine) + { + CloseHandle(elevatedFileHandle); + return 1; + } + + // Make sure the path to the updater to use for the update is on local. + // We do this check to make sure that file locking is available for + // race condition security checks. + if (useService) + { + BOOL isLocal = FALSE; + useService = IsLocalFile(argv[0], isLocal) && isLocal; + } + + // If we have unprompted elevation we should NOT use the service + // for the update. Service updates happen with the SYSTEM account + // which has more privs than we need to update with. + // Windows 8 provides a user interface so users can configure this + // behavior and it can be configured in the registry in all Windows + // versions that support UAC. + if (useService) + { + BOOL unpromptedElevation; + if (IsUnpromptedElevation(unpromptedElevation)) + { + useService = !unpromptedElevation; + } + } + + // Make sure the service registry entries for the installation path + // are available. If not don't use the service. + if (useService) + { + WCHAR maintenanceServiceKey[MAX_PATH + 1]; + // TODO: moggi: needs adaptation for LibreOffice + // Most likely the registry part is not correct yet + if (CalculateRegistryPathFromFilePath(gInstallDirPath, + maintenanceServiceKey)) + { + HKEY baseKey = nullptr; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + maintenanceServiceKey, 0, + KEY_READ | KEY_WOW64_64KEY, + &baseKey) == ERROR_SUCCESS) + { + RegCloseKey(baseKey); + } + else + { +#ifdef TEST_UPDATER + useService = testOnlyFallbackKeyExists; +#endif + if (!useService) + { + lastFallbackError = FALLBACKKEY_NOKEY_ERROR; + } + } + } + else + { + useService = false; + lastFallbackError = FALLBACKKEY_REGPATH_ERROR; + } + } + + // Originally we used to write "pending" to update.status before + // launching the service command. This is no longer needed now + // since the service command is launched from updater.exe. If anything + // fails in between, we can fall back to using the normal update process + // on our own. + + // If we still want to use the service try to launch the service + // command for the update. + if (useService) + { + // If the update couldn't be started, then set useService to false so + // we do the update the old way. + DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv); + useService = (ret == ERROR_SUCCESS); + // If the command was launched then wait for the service to be done. + if (useService) + { + bool showProgressUI = false; + // Never show the progress UI when staging updates. + if (!sStagedUpdate) + { + // We need to call this separately instead of allowing ShowProgressUI + // to initialize the strings because the service will move the + // ini file out of the way when running updater. + showProgressUI = !InitProgressUIStrings(); + } + + // Wait for the service to stop for 5 seconds. If the service + // has still not stopped then show an indeterminate progress bar. + DWORD lastState = WaitForServiceStop(SVC_NAME, 5); + if (lastState != SERVICE_STOPPED) + { + std::thread waitThread(WaitForServiceFinishThread, nullptr); + if (showProgressUI) + { + ShowProgressUI(true, false); + } + waitThread.join(); + } + + lastState = WaitForServiceStop(SVC_NAME, 1); + if (lastState != SERVICE_STOPPED) + { + // If the service doesn't stop after 10 minutes there is + // something seriously wrong. + lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR; + useService = false; + } + } + else + { + lastFallbackError = FALLBACKKEY_LAUNCH_ERROR; + } + } + + // If the service can't be used when staging and update, make sure that + // the UAC prompt is not shown! In this case, just set the status to + // pending and the update will be applied during the next startup. + if (!useService && sStagedUpdate) + { + if (updateLockFileHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(updateLockFileHandle); + } + WriteStatusFile("pending"); + return 0; + } + + // If we started the service command, and it finished, check the + // update.status file to make sure it succeeded, and if it did + // we need to manually start the PostUpdate process from the + // current user's session of this unelevated updater.exe the + // current process is running as. + // Note that we don't need to do this if we're just staging the update, + // as the PostUpdate step runs when performing the replacing in that case. + if (useService && !sStagedUpdate) + { + bool updateStatusSucceeded = false; + if (IsUpdateStatusSucceeded(updateStatusSucceeded) && + updateStatusSucceeded) + { + if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) + { + fprintf(stderr, "The post update process which runs as the user" + " for service update could not be launched."); + } + } + } + + // If we didn't want to use the service at all, or if an update was + // already happening, or launching the service command failed, then + // launch the elevated updater.exe as we do without the service. + // We don't launch the elevated updater in the case that we did have + // write access all along because in that case the only reason we're + // using the service is because we are testing. + if (!useService && !noServiceFallback && + updateLockFileHandle == INVALID_HANDLE_VALUE) + { + SHELLEXECUTEINFO sinfo; + memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO)); + sinfo.cbSize = sizeof(SHELLEXECUTEINFO); + sinfo.fMask = SEE_MASK_FLAG_NO_UI | + SEE_MASK_FLAG_DDEWAIT | + SEE_MASK_NOCLOSEPROCESS; + sinfo.hwnd = nullptr; + sinfo.lpFile = argv[0]; + sinfo.lpParameters = cmdLine; + sinfo.lpVerb = L"runas"; + sinfo.nShow = SW_SHOWNORMAL; + + bool result = ShellExecuteEx(&sinfo); + free(cmdLine); + + if (result) + { + WaitForSingleObject(sinfo.hProcess, INFINITE); + CloseHandle(sinfo.hProcess); + } + else + { + WriteStatusFile(ELEVATION_CANCELED); + } + } + + if (argc > callbackIndex) + { + LaunchCallbackApp(argv[5], argc - callbackIndex, + argv + callbackIndex, sUsingService); + } + + CloseHandle(elevatedFileHandle); + + if (!useService && !noServiceFallback && + INVALID_HANDLE_VALUE == updateLockFileHandle) + { + // We didn't use the service and we did run the elevated updater.exe. + // The elevated updater.exe is responsible for writing out the + // update.status file. + return 0; + } + else if (useService) + { + // The service command was launched. The service is responsible for + // writing out the update.status file. + if (updateLockFileHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(updateLockFileHandle); + } + return 0; + } + else + { + // Otherwise the service command was not launched at all. + // We are only reaching this code path because we had write access + // all along to the directory and a fallback key existed, and we + // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists). + // We only currently use this env var from XPCShell tests. + CloseHandle(updateLockFileHandle); + WriteStatusFile(lastFallbackError); + return 0; + } + } + } +#endif + + if (sStagedUpdate) + { + // When staging updates, blow away the old installation directory and create + // it from scratch. + ensure_remove_recursive(gWorkingDirPath); + } + if (!sReplaceRequest) + { + // Try to create the destination directory if it doesn't exist + int rv = NS_tmkdir(gWorkingDirPath, 0755); + if (rv != OK && errno != EEXIST) + { +#ifdef MACOSX + if (isElevated) + { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + } + +#ifdef _WIN32 + // For replace requests, we don't need to do any real updates, so this is not + // necessary. + if (!sReplaceRequest) + { + // Allocate enough space for the length of the path an optional additional + // trailing slash and null termination. + NS_tchar *destpath = (NS_tchar *) malloc((NS_tstrlen(gWorkingDirPath) + 2) * sizeof(NS_tchar)); + if (!destpath) + return 1; + + NS_tchar *c = destpath; + NS_tstrcpy(c, gWorkingDirPath); + c += NS_tstrlen(gWorkingDirPath); + if (gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('/') && + gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('\\')) + { + NS_tstrcat(c, NS_T("/")); + c += NS_tstrlen(NS_T("/")); + } + *c = NS_T('\0'); + c++; + + gDestPath = destpath; + } + + NS_tchar applyDirLongPath[MAXPATHLEN]; + if (!GetLongPathNameW(gWorkingDirPath, applyDirLongPath, + sizeof(applyDirLongPath)/sizeof(applyDirLongPath[0]))) + { + LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath)); + LogFinish(); + WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + if (argc > callbackIndex) + { + LaunchCallbackApp(argv[5], argc - callbackIndex, + argv + callbackIndex, sUsingService); + } + return 1; + } + + HANDLE callbackFile = INVALID_HANDLE_VALUE; + if (argc > callbackIndex) + { + // If the callback executable is specified it must exist for a successful + // update. It is important we null out the whole buffer here because later + // we make the assumption that the callback application is inside the + // apply-to dir. If we don't have a fully null'ed out buffer it can lead + // to stack corruption which causes crashes and other problems. + NS_tchar callbackLongPath[MAXPATHLEN]; + ZeroMemory(callbackLongPath, sizeof(callbackLongPath)); + NS_tchar *targetPath = argv[callbackIndex]; + NS_tchar buffer[MAXPATHLEN * 2] = { NS_T('\0') }; + size_t bufferLeft = MAXPATHLEN * 2; + if (sReplaceRequest) + { + // In case of replace requests, we should look for the callback file in + // the destination directory. + size_t commonPrefixLength = PathCommonPrefixW(argv[callbackIndex], + gInstallDirPath, + nullptr); + NS_tchar *p = buffer; + NS_tstrncpy(p, argv[callbackIndex], commonPrefixLength); + p += commonPrefixLength; + bufferLeft -= commonPrefixLength; + NS_tstrncpy(p, gInstallDirPath + commonPrefixLength, bufferLeft); + + size_t len = NS_tstrlen(gInstallDirPath + commonPrefixLength); + p += len; + bufferLeft -= len; + *p = NS_T('\\'); + ++p; + bufferLeft--; + *p = NS_T('\0'); + NS_tchar installDir[MAXPATHLEN]; + NS_tstrcpy(installDir, gInstallDirPath); + size_t callbackPrefixLength = PathCommonPrefixW(argv[callbackIndex], + installDir, + nullptr); + NS_tstrncpy(p, argv[callbackIndex] + std::max(callbackPrefixLength, + commonPrefixLength), bufferLeft); + targetPath = buffer; + } + if (!GetLongPathNameW(targetPath, callbackLongPath, + sizeof(callbackLongPath)/sizeof(callbackLongPath[0]))) + { + LOG(("NS_main: unable to find callback file: " LOG_S, targetPath)); + LogFinish(); + WriteStatusFile(WRITE_ERROR_CALLBACK_PATH); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + if (argc > callbackIndex) + { + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + } + return 1; + } + + // Doing this is only necessary when we're actually applying a patch. + if (!sReplaceRequest) + { + int len = NS_tstrlen(applyDirLongPath); + NS_tchar *s = callbackLongPath; + NS_tchar *d = gCallbackRelPath; + // advance to the apply to directory and advance past the trailing backslash + // if present. + s += len; + if (*s == NS_T('\\')) + ++s; + + // Copy the string and replace backslashes with forward slashes along the + // way. + do + { + if (*s == NS_T('\\')) + *d = NS_T('/'); + else + *d = *s; + ++s; + ++d; + } + while (*s); + *d = NS_T('\0'); + ++d; + + // Make a copy of the callback executable so it can be read when patching. + NS_tsnprintf(gCallbackBackupPath, + sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]), + NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]); + NS_tremove(gCallbackBackupPath); + if (!CopyFileW(argv[callbackIndex], gCallbackBackupPath, true)) + { + DWORD copyFileError = GetLastError(); + LOG(("NS_main: failed to copy callback file " LOG_S + " into place at " LOG_S, argv[callbackIndex], gCallbackBackupPath)); + LogFinish(); + if (copyFileError == ERROR_ACCESS_DENIED) + { + WriteStatusFile(WRITE_ERROR_ACCESS_DENIED); + } + else + { + WriteStatusFile(WRITE_ERROR_CALLBACK_APP); + } + + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + LaunchCallbackApp(argv[callbackIndex], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + return 1; + } + + // Since the process may be signaled as exited by WaitForSingleObject before + // the release of the executable image try to lock the main executable file + // multiple times before giving up. If we end up giving up, we won't + // fail the update. + const int max_retries = 10; + int retries = 1; + DWORD lastWriteError = 0; + do + { + // By opening a file handle without FILE_SHARE_READ to the callback + // executable, the OS will prevent launching the process while it is + // being updated. + callbackFile = CreateFileW(targetPath, + DELETE | GENERIC_WRITE, + // allow delete, rename, and write + FILE_SHARE_DELETE | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, 0, nullptr); + if (callbackFile != INVALID_HANDLE_VALUE) + break; + + lastWriteError = GetLastError(); + LOG(("NS_main: callback app file open attempt %d failed. " \ + "File: " LOG_S ". Last error: %d", retries, + targetPath, lastWriteError)); + + Sleep(100); + } + while (++retries <= max_retries); + + // CreateFileW will fail if the callback executable is already in use. + if (callbackFile == INVALID_HANDLE_VALUE) + { + // Only fail the update if the last error was not a sharing violation. + if (lastWriteError != ERROR_SHARING_VIOLATION) + { + LOG(("NS_main: callback app file in use, failed to exclusively open " \ + "executable file: " LOG_S, argv[callbackIndex])); + LogFinish(); + if (lastWriteError == ERROR_ACCESS_DENIED) + { + WriteStatusFile(WRITE_ERROR_ACCESS_DENIED); + } + else + { + WriteStatusFile(WRITE_ERROR_CALLBACK_APP); + } + + NS_tremove(gCallbackBackupPath); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + return 1; + } + LOG(("NS_main: callback app file in use, continuing without " \ + "exclusive access for executable file: " LOG_S, + argv[callbackIndex])); + } + } + } + + // DELETE_DIR is not required when performing a staged update or replace + // request; it can be used during a replace request but then it doesn't + // use gDeleteDirPath. + if (!sStagedUpdate && !sReplaceRequest) + { + // The directory to move files that are in use to on Windows. This directory + // will be deleted after the update is finished, on OS reboot using + // MoveFileEx if it contains files that are in use, or by the post update + // process after the update finishes. On Windows when performing a normal + // update (e.g. the update is not a staged update and is not a replace + // request) gWorkingDirPath is the same as gInstallDirPath and + // gWorkingDirPath is used because it is the destination directory. + NS_tsnprintf(gDeleteDirPath, + sizeof(gDeleteDirPath) / sizeof(gDeleteDirPath[0]), + NS_T("%s/%s"), gWorkingDirPath, DELETE_DIR); + + if (NS_taccess(gDeleteDirPath, F_OK)) + { + NS_tmkdir(gDeleteDirPath, 0755); + } + } +#endif /* _WIN32 */ + + // Run update process on a background thread. ShowProgressUI may return + // before QuitProgressUI has been called, so wait for UpdateThreadFunc to + // terminate. Avoid showing the progress UI when staging an update, or if this + // is an elevated process on OSX. + std::thread t(UpdateThreadFunc, nullptr); + if (!sStagedUpdate && !sReplaceRequest +#ifdef XP_MACOSX + && !isElevated +#endif + ) + { + ShowProgressUI(); + } + t.join(); + +#ifdef _WIN32 + if (argc > callbackIndex && !sReplaceRequest) + { + if (callbackFile != INVALID_HANDLE_VALUE) + { + CloseHandle(callbackFile); + } + // Remove the copy of the callback executable. + NS_tremove(gCallbackBackupPath); + } + + if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath)) + { + LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d", + DELETE_DIR, errno)); + // The directory probably couldn't be removed due to it containing files + // that are in use and will be removed on OS reboot. The call to remove the + // directory on OS reboot is done after the calls to remove the files so the + // files are removed first on OS reboot since the directory must be empty + // for the directory removal to be successful. The MoveFileEx call to remove + // the directory on OS reboot will fail if the process doesn't have write + // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the + // installer / uninstaller will delete the directory along with its contents + // after an update is applied, on reinstall, and on uninstall. + if (MoveFileEx(gDeleteDirPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) + { + LOG(("NS_main: directory will be removed on OS reboot: " LOG_S, + DELETE_DIR)); + } + else + { + LOG(("NS_main: failed to schedule OS reboot removal of " \ + "directory: " LOG_S, DELETE_DIR)); + } + } +#endif /* _WIN32 */ + + +#ifdef MACOSX + // When the update is successful remove the precomplete file in the root of + // the application bundle and move the distribution directory from + // Contents/MacOS to Contents/Resources and if both exist delete the + // directory under Contents/MacOS (see Bug 1068439). + if (gSucceeded && !sStagedUpdate) + { + NS_tchar oldPrecomplete[MAXPATHLEN]; + NS_tsnprintf(oldPrecomplete, sizeof(oldPrecomplete)/sizeof(oldPrecomplete[0]), + NS_T("%s/precomplete"), gInstallDirPath); + NS_tremove(oldPrecomplete); + + NS_tchar oldDistDir[MAXPATHLEN]; + NS_tsnprintf(oldDistDir, sizeof(oldDistDir)/sizeof(oldDistDir[0]), + NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath); + int rv = NS_taccess(oldDistDir, F_OK); + if (!rv) + { + NS_tchar newDistDir[MAXPATHLEN]; + NS_tsnprintf(newDistDir, sizeof(newDistDir)/sizeof(newDistDir[0]), + NS_T("%s/Contents/Resources/distribution"), gInstallDirPath); + rv = NS_taccess(newDistDir, F_OK); + if (!rv) + { + LOG(("New distribution directory already exists... removing old " \ + "distribution directory: " LOG_S, oldDistDir)); + rv = ensure_remove_recursive(oldDistDir); + if (rv) + { + LOG(("Removing old distribution directory failed - err: %d", rv)); + } + } + else + { + LOG(("Moving old distribution directory to new location. src: " LOG_S \ + ", dst:" LOG_S, oldDistDir, newDistDir)); + rv = rename_file(oldDistDir, newDistDir, true); + if (rv) + { + LOG(("Moving old distribution directory to new location failed - " \ + "err: %d", rv)); + } + } + } + } + + if (isElevated) + { + SetGroupOwnershipAndPermissions(gInstallDirPath); + freeArguments(argc, argv); + CleanupElevatedMacUpdate(false); + } + else if (IsOwnedByGroupAdmin(gInstallDirPath)) + { + // If the group ownership of the Firefox .app bundle was set to the "admin" + // group during a previous elevated update, we need to ensure that all files + // in the bundle have group ownership of "admin" as well as write permission + // for the group to not break updates in the future. + SetGroupOwnershipAndPermissions(gInstallDirPath); + } +#endif /* MACOSX */ + + LogFinish(); + + int retVal = LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex +#ifdef _WIN32 + , elevatedLockFilePath + , updateLockFileHandle +#elif defined(MACOSX) + , isElevated +#endif + ); + + return retVal ? retVal : (gSucceeded ? 0 : 1); +} + +class ActionList +{ +public: + ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { } + ~ActionList(); + + void Append(Action* action); + int Prepare(); + int Execute(); + void Finish(int status); + +private: + Action *mFirst; + Action *mLast; + int mCount; +}; + +ActionList::~ActionList() +{ + Action* a = mFirst; + while (a) + { + Action *b = a; + a = a->mNext; + delete b; + } +} + +void +ActionList::Append(Action *action) +{ + if (mLast) + mLast->mNext = action; + else + mFirst = action; + + mLast = action; + mCount++; +} + +int +ActionList::Prepare() +{ + // If the action list is empty then we should fail in order to signal that + // something has gone wrong. Otherwise we report success when nothing is + // actually done. See bug 327140. + if (mCount == 0) + { + LOG(("empty action list")); + return MAR_ERROR_EMPTY_ACTION_LIST; + } + + Action *a = mFirst; + int i = 0; + while (a) + { + int rv = a->Prepare(); + if (rv) + return rv; + + float percent = float(++i) / float(mCount); + UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent); + + a = a->mNext; + } + + return OK; +} + +int +ActionList::Execute() +{ + int currentProgress = 0, maxProgress = 0; + Action *a = mFirst; + while (a) + { + maxProgress += a->mProgressCost; + a = a->mNext; + } + + a = mFirst; + while (a) + { + int rv = a->Execute(); + if (rv) + { + LOG(("### execution failed")); + return rv; + } + + currentProgress += a->mProgressCost; + float percent = float(currentProgress) / float(maxProgress); + UpdateProgressUI(PROGRESS_PREPARE_SIZE + + PROGRESS_EXECUTE_SIZE * percent); + + a = a->mNext; + } + + return OK; +} + +void +ActionList::Finish(int status) +{ + Action *a = mFirst; + int i = 0; + while (a) + { + a->Finish(status); + + float percent = float(++i) / float(mCount); + UpdateProgressUI(PROGRESS_PREPARE_SIZE + + PROGRESS_EXECUTE_SIZE + + PROGRESS_FINISH_SIZE * percent); + + a = a->mNext; + } + + if (status == OK) + gSucceeded = true; +} + + +#ifdef _WIN32 +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + WIN32_FIND_DATAW finddata; + HANDLE hFindFile; + NS_tchar searchspec[MAXPATHLEN]; + NS_tchar foundpath[MAXPATHLEN]; + + NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]), + NS_T("%s*"), dirpath); + std::unique_ptr<const NS_tchar[]> pszSpec(new_absolute_path(searchspec)); + + hFindFile = FindFirstFileW(pszSpec.get(), &finddata); + if (hFindFile != INVALID_HANDLE_VALUE) + { + do + { + // Don't process the current or parent directory. + if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 || + NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0) + continue; + + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s%s"), dirpath, finddata.cFileName); + if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), foundpath); + // Recurse into the directory. + rv = add_dir_entries(foundpath, list); + if (rv) + { + LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv)); + return rv; + } + } + else + { + // Add the file to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(foundpath); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveFile(); + rv = action->Parse(quotedpath); + if (rv) + { + LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", + quotedpath, rv)); + return rv; + } + free(quotedpath); + + list->Append(action); + } + } + while (FindNextFileW(hFindFile, &finddata) != 0); + + FindClose(hFindFile); + { + // Add the directory to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(dirpath); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveDir(); + rv = action->Parse(quotedpath); + if (rv) + LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", + quotedpath, rv)); + else + list->Append(action); + free(quotedpath); + } + } + + return rv; +} + +#elif defined(__sun) +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + NS_tchar foundpath[MAXPATHLEN]; + struct + { + dirent dent_buffer; + char chars[MAXNAMLEN]; + } ent_buf; + struct dirent* ent; + std::unique_ptr<NS_tchar[]> searchpath(new_absolute_path(dirpath)); + + DIR* dir = opendir(searchpath.get()); + if (!dir) + { + LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath.get(), + errno)); + return UNEXPECTED_FILE_OPERATION_ERROR; + } + + while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent) + { + if ((strcmp(ent->d_name, ".") == 0) || + (strcmp(ent->d_name, "..") == 0)) + continue; + + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s%s"), searchpath.get(), ent->d_name); + struct stat64 st_buf; + int test = stat64(foundpath, &st_buf); + if (test) + { + closedir(dir); + return UNEXPECTED_FILE_OPERATION_ERROR; + } + if (S_ISDIR(st_buf.st_mode)) + { + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), foundpath); + // Recurse into the directory. + rv = add_dir_entries(foundpath, list); + if (rv) + { + LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv)); + closedir(dir); + return rv; + } + } + else + { + // Add the file to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(get_relative_offset(foundpath)); + if (!quotedpath) + { + closedir(dir); + return PARSE_ERROR; + } + + Action *action = new RemoveFile(); + rv = action->Parse(quotedpath); + if (rv) + { + LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", + quotedpath, rv)); + closedir(dir); + return rv; + } + + list->Append(action); + } + } + closedir(dir); + + // Add the directory to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(get_relative_offset(dirpath)); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveDir(); + rv = action->Parse(quotedpath); + if (rv) + { + LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", + quotedpath, rv)); + } + else + { + list->Append(action); + } + + return rv; +} + +#else + +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + FTS *ftsdir; + FTSENT *ftsdirEntry; + std::unique_ptr<NS_tchar[]> searchpath(new_absolute_path(dirpath)); + + // Remove the trailing slash so the paths don't contain double slashes. The + // existence of the slash has already been checked in DoUpdate. + searchpath.get()[NS_tstrlen(searchpath.get()) - 1] = NS_T('\0'); + char* const pathargv[] = {searchpath.get(), nullptr}; + + // FTS_NOCHDIR is used so relative paths from the destination directory are + // returned. + if (!(ftsdir = fts_open(pathargv, + FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR, + nullptr))) + return UNEXPECTED_FILE_OPERATION_ERROR; + + while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) + { + NS_tchar foundpath[MAXPATHLEN]; + NS_tchar *quotedpath = nullptr; + Action *action = nullptr; + + switch (ftsdirEntry->fts_info) + { + // Filesystem objects that shouldn't be in the application's directories + case FTS_SL: + case FTS_SLNONE: + case FTS_DEFAULT: + LOG(("add_dir_entries: found a non-standard file: " LOG_S, + ftsdirEntry->fts_path)); + /* Fall through */ // and try to remove as a file + + // Files + case FTS_F: + case FTS_NSOK: + // Add the file to be removed to the ActionList. + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s"), ftsdirEntry->fts_accpath); + quotedpath = get_quoted_path(get_relative_offset(foundpath)); + if (!quotedpath) + { + rv = UPDATER_QUOTED_PATH_MEM_ERROR; + break; + } + action = new RemoveFile(); + rv = action->Parse(quotedpath); + free(quotedpath); + if (!rv) + list->Append(action); + break; + + // Directories + case FTS_DP: + rv = OK; + // Add the directory to be removed to the ActionList. + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), ftsdirEntry->fts_accpath); + quotedpath = get_quoted_path(get_relative_offset(foundpath)); + if (!quotedpath) + { + rv = UPDATER_QUOTED_PATH_MEM_ERROR; + break; + } + + action = new RemoveDir(); + rv = action->Parse(quotedpath); + free(quotedpath); + if (!rv) + list->Append(action); + break; + + // Errors + case FTS_DNR: + case FTS_NS: + // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that + // we're racing with ourselves. Though strange, the entry will be + // removed anyway. + if (ENOENT == ftsdirEntry->fts_errno) + { + rv = OK; + break; + } + // Fall through + + case FTS_ERR: + rv = UNEXPECTED_FILE_OPERATION_ERROR; + LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d", + ftsdirEntry->fts_path, ftsdirEntry->fts_errno)); + break; + + case FTS_DC: + rv = UNEXPECTED_FILE_OPERATION_ERROR; + LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S, + ftsdirEntry->fts_path)); + break; + + default: + // FTS_D is ignored and FTS_DP is used instead (post-order). + rv = OK; + break; + } + + if (rv != OK) + break; + } + + fts_close(ftsdir); + + return rv; +} +#endif + +static NS_tchar* +GetManifestContents(const NS_tchar *manifest) +{ + AutoFile mfile(NS_tfopen(manifest, NS_T("rb"))); + if (mfile == nullptr) + { + LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest)); + return nullptr; + } + + struct stat ms; + int rv = fstat(fileno((FILE *)mfile), &ms); + if (rv) + { + LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest)); + return nullptr; + } + + char *mbuf = (char *) malloc(ms.st_size + 1); + if (!mbuf) + return nullptr; + + size_t r = ms.st_size; + char *rb = mbuf; + while (r) + { + const size_t count = std::min<size_t>(SSIZE_MAX, r); + size_t c = fread(rb, 1, count, mfile); + if (c != count) + { + LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest)); + free(mbuf); + return nullptr; + } + + r -= c; + rb += c; + } + mbuf[ms.st_size] = '\0'; + rb = mbuf; + +#ifndef _WIN32 + return rb; +#else + NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar)); + if (!wrb) + { + free(mbuf); + return nullptr; + } + + if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb, + ms.st_size + 1)) + { + LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError())); + free(mbuf); + free(wrb); + return nullptr; + } + free(mbuf); + + return wrb; +#endif +} + +int AddPreCompleteActions(ActionList *list) +{ +#ifdef MACOSX + std::unique_ptr<NS_tchar[]> manifestPath(new_absolute_path( + NS_T("Contents/Resources/precomplete"))); +#else + std::unique_ptr<NS_tchar[]> manifestPath(new_absolute_path( + NS_T("precomplete"))); +#endif + + NS_tchar *rb = GetManifestContents(manifestPath.get()); + if (rb == nullptr) + { + LOG(("AddPreCompleteActions: error getting contents of precomplete " \ + "manifest")); + // Applications aren't required to have a precomplete manifest. The mar + // generation scripts enforce the presence of a precomplete manifest. + return OK; + } + + int rv; + NS_tchar *line; + while ((line = mstrtok(kNL, &rb)) != 0) + { + // skip comments + if (*line == NS_T('#')) + continue; + + NS_tchar *token = mstrtok(kWhitespace, &line); + if (!token) + { + LOG(("AddPreCompleteActions: token not found in manifest")); + return PARSE_ERROR; + } + + Action *action = nullptr; + if (NS_tstrcmp(token, NS_T("remove")) == 0) // rm file + { + action = new RemoveFile(); + } + else if (NS_tstrcmp(token, NS_T("remove-cc")) == 0) // no longer supported + { + continue; + } + else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) // rmdir if empty + { + action = new RemoveDir(); + } + else + { + LOG(("AddPreCompleteActions: unknown token: " LOG_S, token)); + return PARSE_ERROR; + } + + if (!action) + return BAD_ACTION_ERROR; + + rv = action->Parse(line); + if (rv) + return rv; + + list->Append(action); + } + + return OK; +} + +int DoUpdate(ArchiveReader& archiveReader) +{ + NS_tchar manifest[MAXPATHLEN]; + int nWrittenBytes = NS_tsnprintf(manifest, sizeof(manifest)/sizeof(manifest[0]), + NS_T("%s/updating/update.manifest"), gWorkingDirPath); + (void) nWrittenBytes; + ensure_parent_dir(manifest); + + // extract the manifest + // TODO: moggi: needs adaptation for LibreOffice + // Why would we need the manifest? Even if we need it why would we need 2? + int rv = archiveReader.ExtractFile("updatev3.manifest", manifest); + if (rv) + { + rv = archiveReader.ExtractFile("updatev2.manifest", manifest); + if (rv) + { + LOG(("DoUpdate: error extracting manifest file")); + return rv; + } + } + + NS_tchar *rb = GetManifestContents(manifest); + NS_tremove(manifest); + if (rb == nullptr) + { + LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest)); + return READ_ERROR; + } + + ActionList list; + NS_tchar *line; + bool isFirstAction = true; + + while ((line = mstrtok(kNL, &rb)) != 0) + { + // skip comments + if (*line == NS_T('#')) + continue; + + NS_tchar *token = mstrtok(kWhitespace, &line); + if (!token) + { + LOG(("DoUpdate: token not found in manifest")); + return PARSE_ERROR; + } + + if (isFirstAction) + { + isFirstAction = false; + // The update manifest isn't required to have a type declaration. The mar + // generation scripts enforce the presence of the type declaration. + if (NS_tstrcmp(token, NS_T("type")) == 0) + { + const NS_tchar *type = mstrtok(kQuote, &line); + LOG(("UPDATE TYPE " LOG_S, type)); + if (NS_tstrcmp(type, NS_T("complete")) == 0) + { + rv = AddPreCompleteActions(&list); + if (rv) + return rv; + } + continue; + } + } + + Action *action = nullptr; + if (NS_tstrcmp(token, NS_T("remove")) == 0) // rm file + { + action = new RemoveFile(); + } + else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) // rmdir if empty + { + action = new RemoveDir(); + } + else if (NS_tstrcmp(token, NS_T("rmrfdir")) == 0) // rmdir recursive + { + const NS_tchar *reldirpath = mstrtok(kQuote, &line); + if (!reldirpath) + return PARSE_ERROR; + + if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/')) + return PARSE_ERROR; + + rv = add_dir_entries(reldirpath, &list); + if (rv) + return rv; + + continue; + } + else if (NS_tstrcmp(token, NS_T("add")) == 0) + { + action = new AddFile(archiveReader); + } + else if (NS_tstrcmp(token, NS_T("patch")) == 0) + { + action = new PatchFile(archiveReader); + } + else if (NS_tstrcmp(token, NS_T("add-if")) == 0) // Add if exists + { + action = new AddIfFile(archiveReader); + } + else if (NS_tstrcmp(token, NS_T("add-if-not")) == 0) // Add if not exists + { + action = new AddIfNotFile(archiveReader); + } + else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) // Patch if exists + { + action = new PatchIfFile(archiveReader); + } + else + { + LOG(("DoUpdate: unknown token: " LOG_S, token)); + return PARSE_ERROR; + } + + if (!action) + return BAD_ACTION_ERROR; + + rv = action->Parse(line); + if (rv) + return rv; + + list.Append(action); + } + + rv = list.Prepare(); + if (rv) + return rv; + + rv = list.Execute(); + + list.Finish(rv); + return rv; +} diff --git a/onlineupdate/source/update/updater/updater.exe.comctl32.manifest b/onlineupdate/source/update/updater/updater.exe.comctl32.manifest new file mode 100644 index 000000000..9a6cdb565 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.exe.comctl32.manifest @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity + version="1.0.0.0" + processorArchitecture="*" + name="Updater" + type="win32" +/> +<description>Updater</description> +<dependency> + <dependentAssembly> + <assemblyIdentity + type="win32" + name="Microsoft.Windows.Common-Controls" + version="6.0.0.0" + processorArchitecture="*" + publicKeyToken="6595b64144ccf1df" + language="*" + /> + </dependentAssembly> +</dependency> +<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3"> + <ms_asmv3:security> + <ms_asmv3:requestedPrivileges> + <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" /> + </ms_asmv3:requestedPrivileges> + </ms_asmv3:security> +</ms_asmv3:trustInfo> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> + </application> + </compatibility> +</assembly> diff --git a/onlineupdate/source/update/updater/updater.exe.manifest b/onlineupdate/source/update/updater/updater.exe.manifest new file mode 100644 index 000000000..cd229c954 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.exe.manifest @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity + version="1.0.0.0" + processorArchitecture="*" + name="Updater" + type="win32" +/> +<description>Updater</description> +<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3"> + <ms_asmv3:security> + <ms_asmv3:requestedPrivileges> + <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" /> + </ms_asmv3:requestedPrivileges> + </ms_asmv3:security> +</ms_asmv3:trustInfo> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> + </application> + </compatibility> +</assembly> diff --git a/onlineupdate/source/update/updater/updater.ico b/onlineupdate/source/update/updater/updater.ico Binary files differnew file mode 100644 index 000000000..7b20ba3ec --- /dev/null +++ b/onlineupdate/source/update/updater/updater.ico diff --git a/onlineupdate/source/update/updater/updater.png b/onlineupdate/source/update/updater/updater.png Binary files differnew file mode 100644 index 000000000..9d8c6df77 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.png diff --git a/onlineupdate/source/update/updater/updater.rc b/onlineupdate/source/update/updater/updater.rc new file mode 100644 index 000000000..d77cea2fe --- /dev/null +++ b/onlineupdate/source/update/updater/updater.rc @@ -0,0 +1,137 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Microsoft Visual C++ generated resource script. +// +#ifdef TEST_UPDATER +#include "../resource.h" +#define MANIFEST_PATH "../updater.exe.manifest" +#define COMCTL32_MANIFEST_PATH "../updater.exe.comctl32.manifest" +#define ICON_PATH "../updater.ico" +#else +#include "resource.h" +#define MANIFEST_PATH "updater.exe.manifest" +#define COMCTL32_MANIFEST_PATH "updater.exe.comctl32.manifest" +#define ICON_PATH "updater.ico" +#endif + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winresrc.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// RT_MANIFEST +// + +1 RT_MANIFEST MANIFEST_PATH +IDR_COMCTL32_MANIFEST RT_MANIFEST COMCTL32_MANIFEST_PATH + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +IDI_DIALOG ICON ICON_PATH + + +///////////////////////////////////////////////////////////////////////////// +// +// Embedded an identifier to uniquely identify this as a Mozilla updater. +// + +STRINGTABLE +{ + IDS_UPDATER_IDENTITY, "libreoffice-updater.exe-7bab28a0-0599-4f37-9efe-f7f8b71f05e3" +} + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG DIALOGEX 0, 0, 253, 41 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER,7,24,239,10 + LTEXT "",IDC_INFO,7,8,239,13,SS_NOPREFIX +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 246 + TOPMARGIN, 7 + BOTTOMMARGIN, 39 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winresrc.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/onlineupdate/source/update/updater/updater.svg b/onlineupdate/source/update/updater/updater.svg new file mode 100644 index 000000000..6a3e88563 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.svg @@ -0,0 +1 @@ +<svg height="32" viewBox="0 0 32 32.000001" width="32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><radialGradient id="a" cx="5" cy="13.375" gradientTransform="matrix(2.6502653 .00000002 0 1.1249099 -5.937606 7.116442)" gradientUnits="userSpaceOnUse" r="5" xlink:href="#b"/><linearGradient id="b"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity=".487603"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="312.625" x2="312.0625" xlink:href="#d" y1="196.541335" y2="191.797335"/><linearGradient id="d"><stop offset="0"/><stop offset="1" stop-opacity="0"/></linearGradient><filter id="e" color-interpolation-filters="sRGB" height="1.588154" width="1.259382" x="-.129691" y="-.294077"><feGaussianBlur stdDeviation=".86460582"/></filter><linearGradient id="f" gradientUnits="userSpaceOnUse" x1="17.92817" x2="17.92817" y1="8.43984" y2="5.29526"><stop offset="0" stop-color="#e9b96e"/><stop offset="1" stop-color="#eeeeec"/></linearGradient><linearGradient id="g" gradientTransform="matrix(.06998908 0 0 .06896555 12.18409 2.80365)" gradientUnits="userSpaceOnUse" x1="174.01562" x2="30" y1="236" y2="64"><stop offset="0" stop-color="#c17d11"/><stop offset="1" stop-color="#e9b96e"/></linearGradient><radialGradient id="h" cx="321.86331" cy="70.499977" gradientTransform="matrix(.6227245 0 0 .09677408 -177.2342 3.75694)" gradientUnits="userSpaceOnUse" r="15.500023"><stop offset="0" stop-color="#faefde"/><stop offset=".68321055" stop-color="#e9b96e"/><stop offset="1" stop-color="#e9b96e" stop-opacity="0"/></radialGradient><radialGradient id="i" cx="321.7785" cy="91.857727" gradientTransform="matrix(2.0837843 0 0 .3006166 -644.48535 -10.03444)" gradientUnits="userSpaceOnUse" r="15.5"><stop offset="0" stop-color="#e9b96e"/><stop offset="1" stop-color="#e9b96e" stop-opacity="0"/></radialGradient><radialGradient id="j" cx="323.89844" cy="70.5" gradientTransform="matrix(.3715153 0 0 .3516126 -97.26516 -14.20919)" gradientUnits="userSpaceOnUse" r="15.5" xlink:href="#b"/><linearGradient id="k" gradientTransform="matrix(.3791614 0 0 .6106884 -99.96394 -32.72591)" gradientUnits="userSpaceOnUse" x1="316.90683" x2="322.41666" xlink:href="#d" y1="69.553268" y2="69.553268"/><linearGradient id="l" gradientTransform="matrix(.970062 -.2428574 0 1 -362.71624 -176.92048)" gradientUnits="userSpaceOnUse" x1="394.59244" x2="389.02057" y1="277.23956" y2="280.54391"><stop offset="0" stop-color="#eeeeec"/><stop offset="1" stop-color="#e9b96e"/></linearGradient><linearGradient id="m" gradientTransform="matrix(.3925197 0 0 .6106884 -104.79786 -32.72591)" gradientUnits="userSpaceOnUse" x1="333.88629" x2="326.32867" xlink:href="#d" y1="69.490768" y2="69.490768"/><linearGradient id="n" gradientTransform="matrix(1.0000001 1.0190348 0 1.0165713 -342.71627 -558.00916)" gradientUnits="userSpaceOnUse" x1="373.8385" x2="375.07596" y1="187.7484" y2="181.79832"><stop offset="0" stop-color="#e9b96e"/><stop offset="1" stop-color="#c17d11"/></linearGradient><linearGradient id="o" gradientUnits="userSpaceOnUse" x1="19.53376" x2="22.28376" xlink:href="#b" y1="14.70452" y2="5.98577"/><linearGradient id="p" gradientUnits="userSpaceOnUse" x1="24.81501" x2="27.63508" xlink:href="#b" y1="5.51702" y2="7.37133"/><linearGradient id="q" gradientTransform="matrix(.970062 -.2428574 0 1 -362.71624 -176.92048)" gradientUnits="userSpaceOnUse" x1="391.0188" x2="389.73026" xlink:href="#b" y1="277.72043" y2="279.4614"/><filter id="r" color-interpolation-filters="sRGB" height="1.86087" width="1.2475" x="-.12375" y="-.430435"><feGaussianBlur stdDeviation=".515625"/></filter><radialGradient id="s" cx="5" cy="13.375" gradientTransform="matrix(2.6502653 .00000002 0 1.1249099 1.062394 13.293218)" gradientUnits="userSpaceOnUse" r="5" xlink:href="#b"/><linearGradient id="t" gradientTransform="matrix(1.2807984 0 0 1.2807976 -376.53809 -266.21095)" gradientUnits="userSpaceOnUse" x1="305.62964" x2="299.38138" y1="222.08656" y2="217.11935"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#babdb6"/></linearGradient><rect fill="url(#c)" filter="url(#e)" height="7.056155" opacity=".753425" rx="1.3125" transform="matrix(1.0441942 0 0 1 -306.59321 -176.92048)" width="16" x="306" y="190.17233"/><rect filter="url(#r)" height="2.875" opacity=".579208" rx="1.25" ry="1.4375" transform="matrix(1.15 0 0 1 -591.83312 5.636224)" width="10" x="515.875" y="16.625"/><rect fill="#d9a452" fill-rule="evenodd" height="8.000001" rx=".09427" ry=".094274" stroke="#8f5902" width="9.015612" x="2.798108" y="16.474611"/><rect fill="#e8c48d" fill-rule="evenodd" height="4.1875" rx=".09427" ry=".094274" transform="scale(-1 1)" width="8.125" x="-11.37622" y="16.912111"/><path d="m3.75122 17.412112v6.125h7.125v-6.125z" fill="none" opacity=".439759" stroke="url(#a)"/><path d="m4.2582637 17.108049 7.3125003-.151651-.170181.91895-6.9783737.173896-.1639456 2.064009 7.0710683-.08839.08839 1.149048-8.1317282-.08839-.088388-4.154253z" fill="#f5efe4" fill-rule="evenodd"/><rect filter="url(#r)" height="2.875" opacity=".579208" rx="1.25" ry="1.4375" transform="matrix(1.15 0 0 1 -584.83312 11.636224)" width="10" x="515.875" y="16.625"/><rect fill="#d9a452" fill-rule="evenodd" height="8.000001" rx=".09427" ry=".094274" stroke="#8f5902" width="9.015612" x="9.798108" y="22.651388"/><rect fill="#e8c48d" fill-rule="evenodd" height="4.1875" rx=".09427" ry=".094274" transform="scale(-1 1)" width="8.125" x="-18.376221" y="23.088888"/><path d="m10.75122 23.588888v6.125h7.125v-6.125z" fill="none" opacity=".439759" stroke="url(#s)"/><path d="m11.258264 23.284825 7.3125-.151651-7.18713.986747-.12537 2.170108 7.071068-.08839.08839 1.149048-8.131727-.08839-.08839-4.154253 1.060661.176777z" fill="#f5efe4" fill-rule="evenodd"/><rect fill="url(#f)" height="14.999985" rx="1.016466" stroke="#8f5902" stroke-dashoffset=".5" stroke-linecap="round" stroke-linejoin="round" width="15" x="13.78376" y="4.579536"/><path d="m18.28175 8.07952h7.90593v1.00676h-7.90593z" fill="#fff" opacity=".2"/><path d="m14.7037 10.07952c-.23265 0-.41994.18455-.41994.41378v7.89657.27586c0 .22924.18729.41379.41994.41379h13.16012c.23265 0 .41994-.18455.41994-.41379v-8.17243c0-.22923-.18729-.41378-.41994-.41378z" fill="url(#g)" fill-rule="evenodd"/><path d="m14.78376 5.57952h13v13.00002h-13z" style="opacity:.342342;fill:none;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:.5;stroke:url(#o)"/><path d="m14.34513 9.07952-.0562 2.96875 13.98983.0312-.0562-3h-13.87753z" fill="url(#h)" fill-rule="evenodd" opacity=".5"/><path d="m14.7037 10.07952c-.23265 0-.41994.18454-.41994.41379v7.89655.27587c0 .22925.18729.41379.41994.41379h13.16012c.23265 0 .41994-.18455.41994-.41379v-8.17242c0-.22925-.18729-.41379-.41994-.41379z" fill="url(#i)" fill-rule="evenodd" opacity=".815315"/><path d="m14.28376 10.07952h14v1h-14z" fill="url(#j)" opacity=".747748"/><path d="m18.28175 7.07952h6.18718v1.00676h-6.18718z" fill="#c17d11" opacity=".40991"/><path d="m16.3849 5.07952c-.0485 0-.0757.0294-.11659.11449-.0409.0851-.0772.2227-.10363.3626l-.88092 4.52291h7v-5z" fill="url(#k)" fill-rule="evenodd" opacity=".468468"/><path d="m13.783748 9.4403349v-3.97196c0-.56176.453339-1.1275045 1.01646-1.2684833l4.955804-1.2406977c.563131-.1409811 1.01647.1977743 1.01647.7595343v3.97196c0 .56176-.453339 1.1275046-1.01647 1.2684857l-4.03474.6503971" fill="url(#l)" stroke="#8f5902" stroke-dashoffset=".5" stroke-linecap="round" stroke-linejoin="round"/><path d="m22.28376 5.07952v5h5l-.99706-4.59925c-.0288-.13456-.0614-.26033-.10264-.32442-.04126-.06408-.0783-.07633-.14664-.07633z" fill="url(#m)" fill-rule="evenodd" opacity=".468468"/><path d="m28.78376 10.14202v-4.03125c0-.57245-.46814-1.48866-1.03125-2.0625l-2.9375-3.03125s-.48496-.76809-1.71875-.4375c-.18373-.00045-.3125.15128-.3125.4375v4.03125c0 .57245.46812 1.48866 1.03125 2.0625l3.00562 2.51312" fill="url(#n)" stroke="#8f5902" stroke-dashoffset=".5" stroke-linecap="round" stroke-linejoin="round"/><path d="m13.271484 6.2226562c-5.2651384 1.0467841-9.271484 5.7180798-9.271484 11.2773438 0 6.321666 5.1783326 11.5 11.5 11.5 6.321667 0 11.5-5.178334 11.5-11.5 0-4.428937-2.546314-8.2880167-6.242188-10.2070312v6.3710932c.779838 1.072341 1.242188 2.393081 1.242188 3.835938 0 3.619464-2.880531 6.5-6.5 6.5s-6.5-2.880536-6.5-6.5c0-2.833183 1.768714-5.206891 4.271484-6.107422z" fill="#888a85"/><g fill="none"><g stroke-linecap="round"><path d="m23.75251 1.52093v3.52734c0 .0439.36048.96696.74609 1.37109l2.77344 2.31055.54297.14649v-2.76563c0-.0443-.36385-.98728-.75391-1.38477a.96884687.96884687 0 0 1 -.004-.004l-2.9375-3.03125a.96884687.96884687 0 0 1 -.12305-.15625s.0335.0321-.043-.01c-.0253-.0139-.15681.009-.20117-.004z" opacity=".572072" stroke="url(#p)" stroke-dashoffset=".5" stroke-linejoin="round"/><path d="m23.69001 1.32952c.14068 1.70518-.31678 3.76924.5625 5.03125" opacity=".414414" stroke="#fff"/><path d="m14.819765 5.2499556v3.16797l1.115949.097569 3.794989-.6258648v-3.86914z" opacity=".716216" stroke="url(#q)" stroke-dashoffset=".5"/></g><path d="m24.78174 9.58626h-5v5" stroke="#888a85" stroke-linecap="square" stroke-width="5"/></g><path d="m13.322266 7.2519531c-4.740376 1.0081786-8.322266 5.2153889-8.322266 10.2480469 0 5.781225 4.7187725 10.5 10.5 10.5 5.781228 0 10.5-4.718775 10.5-10.5 0-4.918766-3.420176-9.0588504-8-10.1875v3.119141c2.91831 1.024849 5 3.787879 5 7.068359 0 4.159904-3.340092 7.499999-7.5 7.5-4.159908 0-7.5-3.340096-7.5-7.5 0-3.399893 2.235404-6.239226 5.322266-7.166016z" fill="url(#t)"/><path d="m24.78174 9.58626h-5v5" fill="none" stroke="#fff" stroke-linecap="square" stroke-width="3"/><path d="m17.281748 7.0863v1 6h1v-6h7v-1h-7z" fill="#888a85"/></svg>
\ No newline at end of file diff --git a/onlineupdate/source/update/updater/win_dirent.cxx b/onlineupdate/source/update/updater/win_dirent.cxx new file mode 100644 index 000000000..2368613ee --- /dev/null +++ b/onlineupdate/source/update/updater/win_dirent.cxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#include "win_dirent.h" +#include <errno.h> +#include <string.h> + +// This file implements the minimum set of dirent APIs used by updater.cpp on +// Windows. If updater.cpp is modified to use more of this API, we need to +// implement those parts here too. + +static dirent gDirEnt; + +DIR::DIR(const WCHAR* path) + : findHandle(INVALID_HANDLE_VALUE) +{ + memset(name, 0, sizeof(name)); + wcsncpy(name, path, sizeof(name)/sizeof(name[0])); + wcsncat(name, L"\\*", sizeof(name)/sizeof(name[0]) - wcslen(name) - 1); +} + +DIR::~DIR() +{ + if (findHandle != INVALID_HANDLE_VALUE) + { + FindClose(findHandle); + } +} + +dirent::dirent() +{ + d_name[0] = L'\0'; +} + +DIR* +opendir(const WCHAR* path) +{ + return new DIR(path); +} + +int +closedir(DIR* dir) +{ + delete dir; + return 0; +} + +dirent* readdir(DIR* dir) +{ + WIN32_FIND_DATAW data; + if (dir->findHandle != INVALID_HANDLE_VALUE) + { + BOOL result = FindNextFileW(dir->findHandle, &data); + if (!result) + { + if (GetLastError() != ERROR_FILE_NOT_FOUND) + { + errno = ENOENT; + } + return 0; + } + } + else + { + // Reading the first directory entry + dir->findHandle = FindFirstFileW(dir->name, &data); + if (dir->findHandle == INVALID_HANDLE_VALUE) + { + if (GetLastError() == ERROR_FILE_NOT_FOUND) + { + errno = ENOENT; + } + else + { + errno = EBADF; + } + return 0; + } + } + memset(gDirEnt.d_name, 0, sizeof(gDirEnt.d_name)); + wcsncpy(gDirEnt.d_name, data.cFileName, + sizeof(gDirEnt.d_name)/sizeof(gDirEnt.d_name[0])); + return &gDirEnt; +} +#endif diff --git a/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.cxx b/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.cxx new file mode 100644 index 000000000..17ee57ec4 --- /dev/null +++ b/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.cxx @@ -0,0 +1,429 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsVersionComparator.h" + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#if defined(_WIN32) && !defined(UPDATER_NO_STRING_GLUE_STL) +#include <wchar.h> +#include "Char16.h" +#endif + +#ifdef _WIN32 +// from Mozilla's nsAlgorithm.h +template <class T> inline const T& XPCOM_MIN(const T& aA, const T& aB) { return aB < aA ? aB : aA; } +#endif + +struct VersionPart +{ + int32_t numA; + + const char* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + char* extraD; // null-terminated +}; + +#ifdef _WIN32 +struct VersionPartW +{ + int32_t numA; + + wchar_t* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + wchar_t* extraD; // null-terminated +}; +#endif + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +static char* ParseVP(char* aPart, VersionPart& aResult) +{ + char* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) + { + return aPart; + } + + dot = strchr(aPart, '.'); + if (dot) + { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') + { + aResult.numA = INT32_MAX; + aResult.strB = ""; + } + else + { + aResult.numA = strtol(aPart, const_cast<char**>(&aResult.strB), 10); + } + + if (!*aResult.strB) + { + aResult.strB = nullptr; + aResult.strBlen = 0; + } + else + { + if (aResult.strB[0] == '+') + { + static const char kPre[] = "pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } + else + { + const char* numstart = strpbrk(aResult.strB, "0123456789+-"); + if (!numstart) + { + aResult.strBlen = strlen(aResult.strB); + } + else + { + aResult.strBlen = numstart - aResult.strB; + + aResult.numC = strtol(numstart, &aResult.extraD, 10); + if (!*aResult.extraD) + { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) + { + ++dot; + + if (!*dot) + { + dot = nullptr; + } + } + + return dot; +} + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +#ifdef _WIN32 +static wchar_t* ParseVP(wchar_t* aPart, VersionPartW& aResult) +{ + wchar_t* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) + { + return aPart; + } + + dot = wcschr(aPart, '.'); + if (dot) + { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') + { + aResult.numA = INT32_MAX; + aResult.strB = L""; + } + else + { + aResult.numA = wcstol(aPart, const_cast<wchar_t**>(&aResult.strB), 10); + } + + if (!*aResult.strB) + { + aResult.strB = nullptr; + aResult.strBlen = 0; + } + else + { + if (aResult.strB[0] == '+') + { + static wchar_t kPre[] = L"pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } + else + { + const wchar_t* numstart = wcspbrk(aResult.strB, L"0123456789+-"); + if (!numstart) + { + aResult.strBlen = wcslen(aResult.strB); + } + else + { + aResult.strBlen = numstart - aResult.strB; + + aResult.numC = wcstol(numstart, &aResult.extraD, 10); + if (!*aResult.extraD) + { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) + { + ++dot; + + if (!*dot) + { + dot = nullptr; + } + } + + return dot; +} +#endif + +// compare two null-terminated strings, which may be null pointers +static int32_t ns_strcmp(const char* aStr1, const char* aStr2) +{ + // any string is *before* no string + if (!aStr1) + { + return aStr2 != 0; + } + + if (!aStr2) + { + return -1; + } + + return strcmp(aStr1, aStr2); +} + +// compare two length-specified string, which may be null pointers +static int32_t ns_strnncmp(const char* aStr1, uint32_t aLen1, const char* aStr2, uint32_t aLen2) +{ + // any string is *before* no string + if (!aStr1) + { + return aStr2 != 0; + } + + if (!aStr2) + { + return -1; + } + + for (; aLen1 && aLen2; --aLen1, --aLen2, ++aStr1, ++aStr2) + { + if (*aStr1 < *aStr2) + { + return -1; + } + + if (*aStr1 > *aStr2) + { + return 1; + } + } + + if (aLen1 == 0) + { + return aLen2 == 0 ? 0 : -1; + } + + return 1; +} + +// compare two int32_t +static int32_t ns_cmp(int32_t aNum1, int32_t aNum2) +{ + if (aNum1 < aNum2) + { + return -1; + } + + return aNum1 != aNum2; +} + +/** + * Compares two VersionParts + */ +static int32_t CompareVP(VersionPart& aVer1, VersionPart& aVer2) +{ + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) + { + return r; + } + + r = ns_strnncmp(aVer1.strB, aVer1.strBlen, aVer2.strB, aVer2.strBlen); + if (r) + { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) + { + return r; + } + + return ns_strcmp(aVer1.extraD, aVer2.extraD); +} + +/** + * Compares two VersionParts + */ +#ifdef _WIN32 +static int32_t CompareVP(VersionPartW& aVer1, VersionPartW& aVer2) +{ + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) + { + return r; + } + + r = wcsncmp(aVer1.strB, aVer2.strB, XPCOM_MIN(aVer1.strBlen, aVer2.strBlen)); + if (r) + { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) + { + return r; + } + + if (!aVer1.extraD) + { + return aVer2.extraD != 0; + } + + if (!aVer2.extraD) + { + return -1; + } + + return wcscmp(aVer1.extraD, aVer2.extraD); +} +#endif + +namespace mozilla +{ +#ifdef _WIN32 +int32_t CompareVersions(const wchar_t* aStrA, const wchar_t* aStrB) +{ + wchar_t* A2 = wcsdup(aStrA); + if (!A2) + { + return 1; + } + + wchar_t* B2 = wcsdup(aStrB); + if (!B2) + { + free(A2); + return 1; + } + + int32_t result; + wchar_t* a = A2; + wchar_t* b = B2; + + do + { + VersionPartW va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) + { + break; + } + + } while (a || b); + + free(A2); + free(B2); + + return result; +} +#endif + +int32_t CompareVersions(const char* aStrA, const char* aStrB) +{ + char* A2 = strdup(aStrA); + if (!A2) + { + return 1; + } + + char* B2 = strdup(aStrB); + if (!B2) + { + free(A2); + return 1; + } + + int32_t result; + char* a = A2; + char* b = B2; + + do + { + VersionPart va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) + { + break; + } + + } while (a || b); + + free(A2); + free(B2); + + return result; +} + +} // namespace mozilla diff --git a/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.h b/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.h new file mode 100644 index 000000000..d793e345e --- /dev/null +++ b/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsVersionComparator_h__ +#define nsVersionComparator_h__ + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#if defined(_WIN32) && !defined(UPDATER_NO_STRING_GLUE_STL) +#include <wchar.h> +#include <stdint.h> + +#endif + +/** + * In order to compare version numbers in Mozilla, you need to use the + * mozilla::Version class. You can construct an object of this type by passing + * in a string version number to the constructor. Objects of this type can be + * compared using the standard comparison operators. + * + * For example, let's say that you want to make sure that a given version + * number is not older than 15.a2. Here's how you would write a function to + * do that. + * + * bool IsVersionValid(const char* version) { + * return mozilla::Version("15.a2") <= mozilla::Version(version); + * } + * + * Or, since Version's constructor is implicit, you can simplify this code: + * + * bool IsVersionValid(const char* version) { + * return mozilla::Version("15.a2") <= version; + * } + * + * On Windows, if your version strings are wide characters, you should use the + * mozilla::VersionW variant instead. The semantics of that class is the same + * as Version. + */ + +namespace mozilla { + +int32_t CompareVersions(const char* aStrA, const char* aStrB); + +#ifdef _WIN32 +int32_t CompareVersions(const wchar_t* aStrA, const wchar_t* aStrB); +#endif + +struct Version +{ + explicit Version(const char* aVersionString) + { + versionContent = strdup(aVersionString); + } + + const char* ReadContent() const + { + return versionContent; + } + + ~Version() + { + free(versionContent); + } + + bool operator<(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) == -1; + } + bool operator<=(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) < 1; + } + bool operator>(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) == 1; + } + bool operator>=(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) > -1; + } + bool operator==(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) == 0; + } + bool operator!=(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) != 0; + } + bool operator<(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) == -1; + } + bool operator<=(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) < 1; + } + bool operator>(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) == 1; + } + bool operator>=(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) > -1; + } + bool operator==(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) == 0; + } + bool operator!=(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) != 0; + } + +private: + char* versionContent; +}; + +#ifdef _WIN32 +struct VersionW +{ + explicit VersionW(const wchar_t* aVersionStringW) + { + versionContentW = + reinterpret_cast<wchar_t*>(wcsdup(aVersionStringW)); + } + + const wchar_t* ReadContentW() const + { + return versionContentW; + } + + ~VersionW() + { + free(versionContentW); + } + + bool operator<(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == -1; + } + bool operator<=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) < 1; + } + bool operator>(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == 1; + } + bool operator>=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) > -1; + } + bool operator==(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == 0; + } + bool operator!=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) != 0; + } + +private: + wchar_t* versionContentW; +}; +#endif + +} // namespace mozilla + +#endif // nsVersionComparator_h__ + |