summaryrefslogtreecommitdiffstats
path: root/onlineupdate/source
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /onlineupdate/source
parentInitial commit. (diff)
downloadlibreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz
libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'onlineupdate/source')
-rw-r--r--onlineupdate/source/libmar/README6
-rw-r--r--onlineupdate/source/libmar/sign/Makefile.in10
-rw-r--r--onlineupdate/source/libmar/sign/mar_sign.c1160
-rw-r--r--onlineupdate/source/libmar/sign/nss_secutil.c238
-rw-r--r--onlineupdate/source/libmar/sign/nss_secutil.h43
-rw-r--r--onlineupdate/source/libmar/src/Makefile.in13
-rw-r--r--onlineupdate/source/libmar/src/mar_create.c398
-rw-r--r--onlineupdate/source/libmar/src/mar_extract.c85
-rw-r--r--onlineupdate/source/libmar/src/mar_read.c572
-rw-r--r--onlineupdate/source/libmar/tool/Makefile.in23
-rw-r--r--onlineupdate/source/libmar/tool/mar.c463
-rw-r--r--onlineupdate/source/libmar/verify/MacVerifyCrypto.cpp414
-rw-r--r--onlineupdate/source/libmar/verify/cryptox.c282
-rw-r--r--onlineupdate/source/libmar/verify/cryptox.h172
-rw-r--r--onlineupdate/source/libmar/verify/mar_verify.c466
-rw-r--r--onlineupdate/source/mbsdiff/bsdiff.cxx405
-rw-r--r--onlineupdate/source/service/certificatecheck.cxx292
-rw-r--r--onlineupdate/source/service/certificatecheck.hxx22
-rw-r--r--onlineupdate/source/service/maintenanceservice.cxx438
-rw-r--r--onlineupdate/source/service/maintenanceservice.hxx10
-rw-r--r--onlineupdate/source/service/registrycertificates.cxx181
-rw-r--r--onlineupdate/source/service/registrycertificates.hxx13
-rw-r--r--onlineupdate/source/service/resource.hxx20
-rw-r--r--onlineupdate/source/service/servicebase.cxx97
-rw-r--r--onlineupdate/source/service/servicebase.hxx22
-rw-r--r--onlineupdate/source/service/serviceinstall.cxx850
-rw-r--r--onlineupdate/source/service/serviceinstall.hxx21
-rw-r--r--onlineupdate/source/service/windowsHelper.hxx48
-rw-r--r--onlineupdate/source/service/workmonitor.cxx770
-rw-r--r--onlineupdate/source/service/workmonitor.hxx5
-rw-r--r--onlineupdate/source/update/common/errors.h96
-rw-r--r--onlineupdate/source/update/common/pathhash.cxx153
-rw-r--r--onlineupdate/source/update/common/pathhash.h19
-rw-r--r--onlineupdate/source/update/common/readstrings.cxx267
-rw-r--r--onlineupdate/source/update/common/readstrings.h38
-rw-r--r--onlineupdate/source/update/common/sources.mozbuild19
-rw-r--r--onlineupdate/source/update/common/uachelper.cxx237
-rw-r--r--onlineupdate/source/update/common/uachelper.h23
-rw-r--r--onlineupdate/source/update/common/updatedefines.h135
-rw-r--r--onlineupdate/source/update/common/updatehelper.cxx807
-rw-r--r--onlineupdate/source/update/common/updatehelper.h34
-rw-r--r--onlineupdate/source/update/common/updatelogging.cxx85
-rw-r--r--onlineupdate/source/update/common/updatelogging.h47
-rw-r--r--onlineupdate/source/update/common/win_dirent.h31
-rw-r--r--onlineupdate/source/update/updater/Makefile.in28
-rw-r--r--onlineupdate/source/update/updater/archivereader.cxx349
-rw-r--r--onlineupdate/source/update/updater/archivereader.h39
-rw-r--r--onlineupdate/source/update/updater/bspatch.cxx198
-rwxr-xr-xonlineupdate/source/update/updater/gen_cert_header.py42
-rw-r--r--onlineupdate/source/update/updater/launchchild_osx.mm138
-rw-r--r--onlineupdate/source/update/updater/loaddlls.cxx119
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Info.plist35
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/PkgInfo1
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in7
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib19
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib22
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nibbin0 -> 5567 bytes
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icnsbin0 -> 55969 bytes
-rw-r--r--onlineupdate/source/update/updater/progressui-unused/progressui_gonk.cxx54
-rw-r--r--onlineupdate/source/update/updater/progressui-unused/progressui_osx.mm141
-rw-r--r--onlineupdate/source/update/updater/progressui.h36
-rw-r--r--onlineupdate/source/update/updater/progressui_gtk.cxx146
-rw-r--r--onlineupdate/source/update/updater/progressui_gtk_icon.h205
-rw-r--r--onlineupdate/source/update/updater/progressui_null.cxx27
-rw-r--r--onlineupdate/source/update/updater/progressui_win.cxx348
-rw-r--r--onlineupdate/source/update/updater/resource.h29
-rw-r--r--onlineupdate/source/update/updater/updater-common.build119
-rw-r--r--onlineupdate/source/update/updater/updater-xpcshell/Makefile.in32
-rw-r--r--onlineupdate/source/update/updater/updater-xpcshell/moz.build13
-rw-r--r--onlineupdate/source/update/updater/updater.cxx4591
-rw-r--r--onlineupdate/source/update/updater/updater.exe.comctl32.manifest38
-rw-r--r--onlineupdate/source/update/updater/updater.exe.manifest26
-rw-r--r--onlineupdate/source/update/updater/updater.icobin0 -> 2238 bytes
-rw-r--r--onlineupdate/source/update/updater/updater.pngbin0 -> 2181 bytes
-rw-r--r--onlineupdate/source/update/updater/updater.rc137
-rw-r--r--onlineupdate/source/update/updater/updater.svg1
-rw-r--r--onlineupdate/source/update/updater/win_dirent.cxx89
-rw-r--r--onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.cxx450
-rw-r--r--onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.h174
79 files changed, 17183 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..5cda191de
--- /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
+CyprtoAPI_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..eb8548fa7
--- /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 CyprtoAPI_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) \
+ CyprtoAPI_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..c47f966ad
--- /dev/null
+++ b/onlineupdate/source/service/servicebase.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/. */
+
+#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..ac4cd679b
--- /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..95bdcdfa7
--- /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),
+ &regDWORDValueSize);
+ 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..ee2ab6c67
--- /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
new file mode 100644
index 000000000..61ff02600
--- /dev/null
+++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
Binary files differ
diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns b/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns
new file mode 100644
index 000000000..d7499c669
--- /dev/null
+++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns
Binary files differ
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..c855e548e
--- /dev/null
+++ b/onlineupdate/source/update/updater/progressui-unused/progressui_gonk.cxx
@@ -0,0 +1,54 @@
+/* -*- 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)
+
+using namespace std;
+
+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;
+ string bars(numBars, '=');
+ 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..cb1468d39
--- /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..7c3bcfcd8
--- /dev/null
+++ b/onlineupdate/source/update/updater/progressui_gtk.cxx
@@ -0,0 +1,146 @@
+/* -*- 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_vbox_new(TRUE, 6);
+ 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..0b8017823
--- /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, 0L);
+}
+
+// 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
new file mode 100644
index 000000000..7b20ba3ec
--- /dev/null
+++ b/onlineupdate/source/update/updater/updater.ico
Binary files differ
diff --git a/onlineupdate/source/update/updater/updater.png b/onlineupdate/source/update/updater/updater.png
new file mode 100644
index 000000000..9d8c6df77
--- /dev/null
+++ b/onlineupdate/source/update/updater/updater.png
Binary files differ
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..def164d38
--- /dev/null
+++ b/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.cxx
@@ -0,0 +1,450 @@
+/* -*- 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__
+