summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update/common
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/update/common')
-rw-r--r--toolkit/mozapps/update/common/certificatecheck.cpp250
-rw-r--r--toolkit/mozapps/update/common/certificatecheck.h22
-rw-r--r--toolkit/mozapps/update/common/errors.h110
-rw-r--r--toolkit/mozapps/update/common/moz.build32
-rw-r--r--toolkit/mozapps/update/common/pathhash.cpp139
-rw-r--r--toolkit/mozapps/update/common/pathhash.h19
-rw-r--r--toolkit/mozapps/update/common/readstrings.cpp236
-rw-r--r--toolkit/mozapps/update/common/readstrings.h43
-rw-r--r--toolkit/mozapps/update/common/registrycertificates.cpp154
-rw-r--r--toolkit/mozapps/update/common/registrycertificates.h14
-rw-r--r--toolkit/mozapps/update/common/sources.mozbuild28
-rw-r--r--toolkit/mozapps/update/common/uachelper.cpp222
-rw-r--r--toolkit/mozapps/update/common/uachelper.h23
-rw-r--r--toolkit/mozapps/update/common/updatecommon.cpp213
-rw-r--r--toolkit/mozapps/update/common/updatecommon.h47
-rw-r--r--toolkit/mozapps/update/common/updatedefines.h142
-rw-r--r--toolkit/mozapps/update/common/updatehelper.cpp609
-rw-r--r--toolkit/mozapps/update/common/updatehelper.h29
-rw-r--r--toolkit/mozapps/update/common/win_dirent.h32
19 files changed, 2364 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/common/certificatecheck.cpp b/toolkit/mozapps/update/common/certificatecheck.cpp
new file mode 100644
index 000000000..afb2e7ee3
--- /dev/null
+++ b/toolkit/mozapps/update/common/certificatecheck.cpp
@@ -0,0 +1,250 @@
+/* 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.h"
+#include "updatecommon.h"
+
+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;
+ LPWSTR 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.
+ szName = (LPWSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR));
+ if (!szName) {
+ LOG_WARN(("Unable to allocate memory for issuer name. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ // Get Issuer name.
+ if (!CertGetNameStringW(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 = (LPWSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR));
+ if (!szName) {
+ LOG_WARN(("Unable to allocate memory for subject name. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ // Obtain the name.
+ if (!(CertGetNameStringW(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;
+}
+
+/**
+ * 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/toolkit/mozapps/update/common/certificatecheck.h b/toolkit/mozapps/update/common/certificatecheck.h
new file mode 100644
index 000000000..43a7c85b6
--- /dev/null
+++ b/toolkit/mozapps/update/common/certificatecheck.h
@@ -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/toolkit/mozapps/update/common/errors.h b/toolkit/mozapps/update/common/errors.h
new file mode 100644
index 000000000..aac029175
--- /dev/null
+++ b/toolkit/mozapps/update/common/errors.h
@@ -0,0 +1,110 @@
+/* -*- 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-57 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-57 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 SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR 52
+#define SERVICE_CALC_REG_PATH_ERROR 53
+#define SERVICE_INVALID_APPLYTO_DIR_ERROR 54
+#define SERVICE_INVALID_INSTALL_DIR_PATH_ERROR 55
+#define SERVICE_INVALID_WORKING_DIR_PATH_ERROR 56
+#define SERVICE_INSTALL_DIR_REG_ERROR 57
+
+#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
+#define REMOVE_FILE_SPEC_ERROR 71
+#define INVALID_APPLYTO_DIR_STAGED_ERROR 72
+#define LOCK_ERROR_PATCH_FILE 73
+#define INVALID_APPLYTO_DIR_ERROR 74
+#define INVALID_INSTALL_DIR_PATH_ERROR 75
+#define INVALID_WORKING_DIR_PATH_ERROR 76
+#define INVALID_CALLBACK_PATH_ERROR 77
+#define INVALID_CALLBACK_DIR_ERROR 78
+
+// 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/toolkit/mozapps/update/common/moz.build b/toolkit/mozapps/update/common/moz.build
new file mode 100644
index 000000000..cacb0bad2
--- /dev/null
+++ b/toolkit/mozapps/update/common/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; 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/.
+
+EXPORTS += [
+ 'readstrings.h',
+ 'updatecommon.h',
+ 'updatedefines.h',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ EXPORTS += [
+ 'pathhash.h',
+ 'uachelper.h',
+ 'updatehelper.cpp',
+ 'updatehelper.h',
+ ]
+ if CONFIG['MOZ_MAINTENANCE_SERVICE']:
+ EXPORTS += [
+ 'certificatecheck.h',
+ 'registrycertificates.h',
+ ]
+
+Library('updatecommon')
+
+DEFINES['NS_NO_XPCOM'] = True
+
+srcdir = '.'
+
+include('sources.mozbuild')
diff --git a/toolkit/mozapps/update/common/pathhash.cpp b/toolkit/mozapps/update/common/pathhash.cpp
new file mode 100644
index 000000000..89a004cde
--- /dev/null
+++ b/toolkit/mozapps/update/common/pathhash.cpp
@@ -0,0 +1,139 @@
+/* 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 <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\\Mozilla\\"
+ L"MaintenanceService\\";
+ wcsncpy(registryPath, baseRegPath, MAX_PATH);
+ BinaryDataToHexString(hash, hashSize,
+ registryPath + wcslen(baseRegPath));
+ delete[] hash;
+ return TRUE;
+}
diff --git a/toolkit/mozapps/update/common/pathhash.h b/toolkit/mozapps/update/common/pathhash.h
new file mode 100644
index 000000000..a238317e1
--- /dev/null
+++ b/toolkit/mozapps/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/toolkit/mozapps/update/common/readstrings.cpp b/toolkit/mozapps/update/common/readstrings.cpp
new file mode 100644
index 000000000..428c91c0b
--- /dev/null
+++ b/toolkit/mozapps/update/common/readstrings.cpp
@@ -0,0 +1,236 @@
+/* -*- 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 XP_WIN
+# 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/toolkit/mozapps/update/common/readstrings.h b/toolkit/mozapps/update/common/readstrings.h
new file mode 100644
index 000000000..cd1909fc9
--- /dev/null
+++ b/toolkit/mozapps/update/common/readstrings.h
@@ -0,0 +1,43 @@
+/* -*- 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 XP_WIN
+# include <windows.h>
+ typedef WCHAR NS_tchar;
+#else
+ typedef char NS_tchar;
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+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/toolkit/mozapps/update/common/registrycertificates.cpp b/toolkit/mozapps/update/common/registrycertificates.cpp
new file mode 100644
index 000000000..1c9c44619
--- /dev/null
+++ b/toolkit/mozapps/update/common/registrycertificates.cpp
@@ -0,0 +1,154 @@
+/* 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 "registrycertificates.h"
+#include "pathhash.h"
+#include "updatecommon.h"
+#include "updatehelper.h"
+#define MAX_KEY_LENGTH 255
+
+/**
+ * Verifies if the file path matches any certificate stored in the registry.
+ *
+ * @param filePath The file path of the application to check if allowed.
+ * @param allowFallbackKeySkip when this is TRUE the fallback registry key will
+ * be used to skip the certificate check. This is the default since the
+ * fallback registry key is located under HKEY_LOCAL_MACHINE which can't be
+ * written to by a low integrity process.
+ * Note: the maintenance service binary can be used to perform this check for
+ * testing or troubleshooting.
+ * @return TRUE if the binary matches any of the allowed certificates.
+ */
+BOOL
+DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate, LPCWSTR filePath,
+ BOOL allowFallbackKeySkip)
+{
+ 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 baseKey;
+ LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ maintenanceServiceKey, 0,
+ KEY_READ | KEY_WOW64_64KEY, &baseKey);
+ 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, &baseKey);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not open fallback key. (%d)", retCode));
+ return FALSE;
+ } else if (allowFallbackKeySkip) {
+ LOG_WARN(("Fallback key present, skipping VerifyCertificateTrustForFile "
+ "check and the certificate attribute registry matching "
+ "check."));
+ RegCloseKey(baseKey);
+ return TRUE;
+ }
+ }
+
+ // Get the number of subkeys.
+ DWORD subkeyCount = 0;
+ retCode = RegQueryInfoKeyW(baseKey, nullptr, nullptr, nullptr, &subkeyCount,
+ nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not query info key. (%d)", retCode));
+ RegCloseKey(baseKey);
+ 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, i, subkeyBuffer,
+ &subkeyBufferCount, nullptr,
+ nullptr, nullptr, nullptr);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not enum certs. (%d)", retCode));
+ RegCloseKey(baseKey);
+ return FALSE;
+ }
+
+ // Open the subkey for the current certificate
+ HKEY subKey;
+ retCode = RegOpenKeyExW(baseKey,
+ subkeyBuffer,
+ 0,
+ KEY_READ | KEY_WOW64_64KEY,
+ &subKey);
+ 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, L"name", 0, nullptr,
+ (LPBYTE)name, &valueBufSize);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not obtain name from registry. (%d)", retCode));
+ RegCloseKey(subKey);
+ continue; // Try the next subkey
+ }
+
+ // Get the issuer from the registry
+ valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR);
+ retCode = RegQueryValueExW(subKey, L"issuer", 0, nullptr,
+ (LPBYTE)issuer, &valueBufSize);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not obtain issuer from registry. (%d)", retCode));
+ RegCloseKey(subKey);
+ 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));
+ RegCloseKey(subKey);
+ continue; // Try the next subkey
+ }
+
+ retCode = VerifyCertificateTrustForFile(filePath);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Error on certificate trust check. (%d)", retCode));
+ RegCloseKey(subKey);
+ continue; // Try the next subkey
+ }
+
+ RegCloseKey(baseKey);
+ // Raise the roof, we found a match!
+ return TRUE;
+ }
+
+ RegCloseKey(baseKey);
+ // No certificates match, :'(
+ return FALSE;
+}
diff --git a/toolkit/mozapps/update/common/registrycertificates.h b/toolkit/mozapps/update/common/registrycertificates.h
new file mode 100644
index 000000000..9f68d1a8d
--- /dev/null
+++ b/toolkit/mozapps/update/common/registrycertificates.h
@@ -0,0 +1,14 @@
+/* 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.h"
+
+BOOL DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate,
+ LPCWSTR filePath,
+ BOOL allowFallbackKeySkip = TRUE);
+
+#endif
diff --git a/toolkit/mozapps/update/common/sources.mozbuild b/toolkit/mozapps/update/common/sources.mozbuild
new file mode 100644
index 000000000..cde51e09b
--- /dev/null
+++ b/toolkit/mozapps/update/common/sources.mozbuild
@@ -0,0 +1,28 @@
+# 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',
+ ]
+ if CONFIG['MOZ_MAINTENANCE_SERVICE']:
+ sources += [
+ 'certificatecheck.cpp',
+ 'registrycertificates.cpp',
+ ]
+ OS_LIBS += [
+ 'crypt32',
+ 'wintrust',
+ ]
+
+sources += [
+ 'readstrings.cpp',
+ 'updatecommon.cpp',
+]
+
+SOURCES += sorted(['%s/%s' % (srcdir, s) for s in sources])
diff --git a/toolkit/mozapps/update/common/uachelper.cpp b/toolkit/mozapps/update/common/uachelper.cpp
new file mode 100644
index 000000000..11647aa72
--- /dev/null
+++ b/toolkit/mozapps/update/common/uachelper.cpp
@@ -0,0 +1,222 @@
+/* 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 <wtsapi32.h>
+#include "uachelper.h"
+#include "updatecommon.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;
+}
diff --git a/toolkit/mozapps/update/common/uachelper.h b/toolkit/mozapps/update/common/uachelper.h
new file mode 100644
index 000000000..6481ff5b5
--- /dev/null
+++ b/toolkit/mozapps/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/toolkit/mozapps/update/common/updatecommon.cpp b/toolkit/mozapps/update/common/updatecommon.cpp
new file mode 100644
index 000000000..aff7d7260
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatecommon.cpp
@@ -0,0 +1,213 @@
+/* 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(XP_WIN)
+#include <windows.h>
+#endif
+
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "updatecommon.h"
+
+UpdateLog::UpdateLog() : logFP(nullptr)
+{
+}
+
+void UpdateLog::Init(NS_tchar* sourcePath,
+ const NS_tchar* fileName)
+{
+ if (logFP) {
+ return;
+ }
+
+ int dstFilePathLen = NS_tsnprintf(mDstFilePath,
+ sizeof(mDstFilePath)/sizeof(mDstFilePath[0]),
+ NS_T("%s/%s"), sourcePath, fileName);
+ // If the destination path was over the length limit,
+ // disable logging by skipping opening the file and setting logFP.
+ if ((dstFilePathLen > 0) &&
+ (dstFilePathLen <
+ static_cast<int>(sizeof(mDstFilePath)/sizeof(mDstFilePath[0])))) {
+#ifdef XP_WIN
+ if (GetTempFileNameW(sourcePath, L"log", 0, mTmpFilePath) != 0) {
+ logFP = NS_tfopen(mTmpFilePath, NS_T("w"));
+
+ // Delete this file now so it is possible to tell from the unelevated
+ // updater process if the elevated updater process has written the log.
+ DeleteFileW(mDstFilePath);
+ }
+#elif XP_MACOSX
+ logFP = NS_tfopen(mDstFilePath, NS_T("w"));
+#else
+ // On platforms that have an updates directory in the installation directory
+ // (e.g. platforms other than Windows and Mac) the update log is written to
+ // a temporary file and then to the update log file. This is needed since
+ // the installation directory is moved during a replace request. This can be
+ // removed when the platform's updates directory is located outside of the
+ // installation directory.
+ logFP = tmpfile();
+#endif
+ }
+}
+
+void UpdateLog::Finish()
+{
+ if (!logFP) {
+ return;
+ }
+
+#if !defined(XP_WIN) && !defined(XP_MACOSX)
+ const int blockSize = 1024;
+ char buffer[blockSize];
+ fflush(logFP);
+ rewind(logFP);
+
+ FILE *updateLogFP = NS_tfopen(mDstFilePath, NS_T("wb+"));
+ while (!feof(logFP)) {
+ size_t read = fread(buffer, 1, blockSize, logFP);
+ if (ferror(logFP)) {
+ fclose(logFP);
+ logFP = nullptr;
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+ return;
+ }
+
+ size_t written = 0;
+
+ while (written < read) {
+ size_t chunkWritten = fwrite(buffer, 1, read - written, updateLogFP);
+ if (chunkWritten <= 0) {
+ fclose(logFP);
+ logFP = nullptr;
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+ return;
+ }
+
+ written += chunkWritten;
+ }
+ }
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+#endif
+
+ fclose(logFP);
+ logFP = nullptr;
+
+#ifdef XP_WIN
+ // When the log file already exists then the elevated updater has already
+ // written the log file and the temp file for the log should be discarded.
+ if (!NS_taccess(mDstFilePath, F_OK)) {
+ DeleteFileW(mTmpFilePath);
+ } else {
+ MoveFileW(mTmpFilePath, mDstFilePath);
+ }
+#endif
+}
+
+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);
+}
+
+/**
+ * Performs checks of a full path for validity for this application.
+ *
+ * @param origFullPath
+ * The full path to check.
+ * @return true if the path is valid for this application and false otherwise.
+ */
+bool
+IsValidFullPath(NS_tchar* origFullPath)
+{
+ // Subtract 1 from MAXPATHLEN for null termination.
+ if (NS_tstrlen(origFullPath) > MAXPATHLEN - 1) {
+ // The path is longer than acceptable for this application.
+ return false;
+ }
+
+#ifdef XP_WIN
+ NS_tchar testPath[MAXPATHLEN] = {NS_T('\0')};
+ // GetFullPathNameW will replace / with \ which PathCanonicalizeW requires.
+ if (GetFullPathNameW(origFullPath, MAXPATHLEN, testPath, nullptr) == 0) {
+ // Unable to get the full name for the path (e.g. invalid path).
+ return false;
+ }
+
+ NS_tchar canonicalPath[MAXPATHLEN] = {NS_T('\0')};
+ if (!PathCanonicalizeW(canonicalPath, testPath)) {
+ // Path could not be canonicalized (e.g. invalid path).
+ return false;
+ }
+
+ // Check if the path passed in resolves to a differerent path.
+ if (NS_tstricmp(origFullPath, canonicalPath) != 0) {
+ // Case insensitive string comparison between the supplied path and the
+ // canonical path are not equal. This will prevent directory traversal and
+ // the use of / in paths since they are converted to \.
+ return false;
+ }
+
+ NS_tstrncpy(testPath, origFullPath, MAXPATHLEN);
+ if (!PathStripToRootW(testPath)) {
+ // It should always be possible to strip a valid path to its root.
+ return false;
+ }
+
+ if (origFullPath[0] == NS_T('\\')) {
+ // Only allow UNC server share paths.
+ if (!PathIsUNCServerShareW(testPath)) {
+ return false;
+ }
+ }
+#else
+ // Only allow full paths.
+ if (origFullPath[0] != NS_T('/')) {
+ return false;
+ }
+
+ // The path must not traverse directories
+ if (NS_tstrstr(origFullPath, NS_T("..")) != nullptr ||
+ NS_tstrstr(origFullPath, NS_T("./")) != nullptr) {
+ return false;
+ }
+#endif
+ return true;
+}
diff --git a/toolkit/mozapps/update/common/updatecommon.h b/toolkit/mozapps/update/common/updatecommon.h
new file mode 100644
index 000000000..3055851d8
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatecommon.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 UPDATECOMMON_H
+#define UPDATECOMMON_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);
+ void Finish();
+ void Flush();
+ void Printf(const char *fmt, ... );
+ void WarnPrintf(const char *fmt, ... );
+
+ ~UpdateLog()
+ {
+ Finish();
+ }
+
+protected:
+ UpdateLog();
+ FILE *logFP;
+ NS_tchar mTmpFilePath[MAXPATHLEN];
+ NS_tchar mDstFilePath[MAXPATHLEN];
+};
+
+bool IsValidFullPath(NS_tchar* fullPath);
+
+#define LOG_WARN(args) UpdateLog::GetPrimaryLog().WarnPrintf args
+#define LOG(args) UpdateLog::GetPrimaryLog().Printf args
+#define LogInit(PATHNAME_, FILENAME_) \
+ UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_)
+#define LogFinish() UpdateLog::GetPrimaryLog().Finish()
+#define LogFlush() UpdateLog::GetPrimaryLog().Flush()
+
+#endif
diff --git a/toolkit/mozapps/update/common/updatedefines.h b/toolkit/mozapps/update/common/updatedefines.h
new file mode 100644
index 000000000..760d2c4c4
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatedefines.h
@@ -0,0 +1,142 @@
+/* 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(XP_WIN)
+# include <windows.h>
+# include <shlwapi.h>
+# include <direct.h>
+# include <io.h>
+# include <stdio.h>
+# include <stdarg.h>
+
+# ifndef F_OK
+# define F_OK 00
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef R_OK
+# define R_OK 04
+# endif
+# define S_ISDIR(s) (((s) & _S_IFMT) == _S_IFDIR)
+# define S_ISREG(s) (((s) & _S_IFMT) == _S_IFREG)
+
+# define access _access
+
+# define putenv _putenv
+# if defined(_MSC_VER) && _MSC_VER < 1900
+# define stat _stat
+# endif
+# 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_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 SOLARIS
+# include <sys/stat.h>
+#else
+# include <fts.h>
+#endif
+# include <dirent.h>
+
+#ifdef XP_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_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/toolkit/mozapps/update/common/updatehelper.cpp b/toolkit/mozapps/update/common/updatehelper.cpp
new file mode 100644
index 000000000..afb89bacf
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatehelper.cpp
@@ -0,0 +1,609 @@
+/* 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>
+
+// 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 "mozilla/UniquePtr.h"
+
+// Needed for PathAppendW
+#include <shlwapi.h>
+
+using mozilla::MakeUnique;
+using mozilla::UniquePtr;
+
+BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
+BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer,
+ LPCWSTR siblingFilePath,
+ LPCWSTR newFileName);
+
+/**
+ * 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);
+}
+
+/**
+ * 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.
+ UniquePtr<char[]> serviceConfigBuffer = MakeUnique<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 maintenace 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 udpater.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 a specific failure code
+ *
+ * @param updateDirPath The path of the update directory
+ * @return TRUE if successful
+ */
+BOOL
+WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)
+{
+ // The temp file is not removed on failure since there is client code that
+ // will remove it.
+ WCHAR tmpUpdateStatusFilePath[MAX_PATH + 1] = { L'\0' };
+ if (GetTempFileNameW(updateDirPath, L"svc", 0, tmpUpdateStatusFilePath) == 0) {
+ return FALSE;
+ }
+
+ HANDLE tmpStatusFile = CreateFileW(tmpUpdateStatusFilePath, GENERIC_WRITE, 0,
+ nullptr, CREATE_ALWAYS, 0, nullptr);
+ if (tmpStatusFile == INVALID_HANDLE_VALUE) {
+ return FALSE;
+ }
+
+ char failure[32];
+ sprintf(failure, "failed: %d", errorCode);
+ DWORD toWrite = strlen(failure);
+ DWORD wrote;
+ BOOL ok = WriteFile(tmpStatusFile, failure,
+ toWrite, &wrote, nullptr);
+ CloseHandle(tmpStatusFile);
+
+ if (!ok || wrote != toWrite) {
+ return FALSE;
+ }
+
+ WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' };
+ wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
+ if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
+ return FALSE;
+ }
+
+ if (MoveFileExW(tmpUpdateStatusFilePath, updateStatusFilePath,
+ MOVEFILE_REPLACE_EXISTING) == 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#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 existance
+ * @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 applicaiton 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++) {
+ DWORD 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 the system's elevation type allows
+ * unprmopted 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, secureDesktop;
+ BOOL success = GetDWORDValue(baseKey, L"ConsentPromptBehaviorAdmin",
+ consent);
+ success = success &&
+ GetDWORDValue(baseKey, L"PromptOnSecureDesktop", secureDesktop);
+ isUnpromptedElevation = !consent && !secureDesktop;
+
+ RegCloseKey(baseKey);
+ return success;
+}
diff --git a/toolkit/mozapps/update/common/updatehelper.h b/toolkit/mozapps/update/common/updatehelper.h
new file mode 100644
index 000000000..3f010df49
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatehelper.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/. */
+
+BOOL StartServiceUpdate(LPCWSTR installDir);
+DWORD LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR *argv);
+BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
+DWORD WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds);
+DWORD WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds);
+DWORD IsProcessRunning(LPCWSTR filename);
+BOOL DoesFallbackKeyExist();
+BOOL IsLocalFile(LPCWSTR file, BOOL &isLocal);
+DWORD StartServiceCommand(int argc, LPCWSTR* argv);
+BOOL IsUnpromptedElevation(BOOL &isUnpromptedElevation);
+
+#define SVC_NAME L"MozillaMaintenance"
+
+#define BASE_SERVICE_REG_KEY \
+ L"SOFTWARE\\Mozilla\\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/toolkit/mozapps/update/common/win_dirent.h b/toolkit/mozapps/update/common/win_dirent.h
new file mode 100644
index 000000000..28f5317ff
--- /dev/null
+++ b/toolkit/mozapps/update/common/win_dirent.h
@@ -0,0 +1,32 @@
+/* -*- 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__
+
+#ifndef XP_WIN
+#error This library should only be used on Windows
+#endif
+
+#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 // WINDIRENT_H__