// Copyright 2005-2020 The Mumble Developers. All rights reserved. // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file at the root of the // Mumble source tree or at . #include "Overlay.h" #include "User.h" #include "Channel.h" #include "OverlayConfig.h" #include "MainWindow.h" #include "MumbleApplication.h" #include "Overlay_win.h" #include "../../overlay/overlay_exe/overlay_exe.h" // We define a global macro called 'g'. This can lead to issues when included code uses 'g' as a type or parameter name (like protobuf 3.7 does). As such, for now, we have to make this our last include. #include "Global.h" // Used by the overlay to detect whether we injected into ourselves. // // A similar declaration can be found in mumble_exe's Overlay.cpp, // for the overlay's self-detection checks to continue working in a // mumble_app.dll world. extern "C" __declspec(dllexport) void mumbleSelfDetection() {}; // Determine if the current Mumble client is able to host // x64 programs. // // If we're on x86, we use use the IsWoW64Process function // to determine this. If we're on x64, we already know we're // capable, so we simply return TRUE. static bool canRun64BitPrograms() { #if defined(_M_X64) return TRUE; #elif defined(_M_IX86) typedef BOOL (WINAPI *IsWow64ProcessPtr)(HANDLE, BOOL *); IsWow64ProcessPtr wow64check = (IsWow64ProcessPtr)GetProcAddress(GetModuleHandle(L"kernel32"), "IsWow64Process"); if (wow64check) { BOOL isWoW64 = FALSE; wow64check(GetCurrentProcess(), &isWoW64); return isWoW64; } return FALSE; #endif } OverlayPrivateWin::OverlayPrivateWin(QObject *p) : OverlayPrivate(p) , m_helper_enabled(true) , m_helper64_enabled(true) , m_mumble_handle(0) , m_active(false) { // Acquire a handle to ourselves and duplicate it. We duplicate it because // want it to be inheritable by our helper processes, and the handle returned // by GetCurrentProcess is not inheritable. Duplicating it makes it inheritable. // This allows our helper processes to access the handle. // // The helper processes need a handle to us, their parent, to be able to listen // detect when our process dies. // // The value of the handle is passed as an argument to the helper processes via // the command line as a number. The HANDLE type in Windows is typedef'd to LPVOID, // but for handles that are supposed to be shared between processes (like a process // handle that we are using), only the lower 32-bits of the HANDLE are considered: // // "When sharing a handle between 32-bit and 64-bit applications, only the lower // 32 bits are significant [...]" // // from https://msdn.microsoft.com/en-us/library/aa384203.aspx HANDLE curproc = GetCurrentProcess(); if (!DuplicateHandle(curproc, curproc, curproc, &m_mumble_handle, 0, TRUE, DUPLICATE_SAME_ACCESS)) { qFatal("OverlayPrivateWin: unable to duplicate handle to the Mumble process."); return; } m_helper_exe_path = QString::fromLatin1("%1/mumble_ol_helper.exe").arg(MumbleApplication::instance()->applicationVersionRootPath()); m_helper_exe_args << QString::number(OVERLAY_MAGIC_NUMBER) << QString::number(reinterpret_cast(m_mumble_handle)); m_helper_process = new QProcess(this); connect(m_helper_process, SIGNAL(started()), this, SLOT(onHelperProcessStarted())); connect(m_helper_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onHelperProcessError(QProcess::ProcessError))); connect(m_helper_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onHelperProcessExited(int, QProcess::ExitStatus))); m_helper_restart_timer = new QTimer(this); m_helper_restart_timer->setSingleShot(true); connect(m_helper_restart_timer, SIGNAL(timeout()), this, SLOT(onDelayedRestartTimerTriggered())); if (!g.s.bOverlayWinHelperX86Enable) { qWarning("OverlayPrivateWin: mumble_ol_helper.exe (32-bit overlay helper) disabled via 'overlay_win/enable_x86_helper' config option."); m_helper_enabled = false; } m_helper64_exe_path = QString::fromLatin1("%1/mumble_ol_helper_x64.exe").arg(MumbleApplication::instance()->applicationVersionRootPath()); m_helper64_exe_args = m_helper_exe_args; m_helper64_process = new QProcess(this); connect(m_helper64_process, SIGNAL(started()), this, SLOT(onHelperProcessStarted())); connect(m_helper64_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onHelperProcessError(QProcess::ProcessError))); connect(m_helper64_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onHelperProcessExited(int, QProcess::ExitStatus))); m_helper64_restart_timer = new QTimer(this); m_helper64_restart_timer->setSingleShot(true); connect(m_helper64_restart_timer, SIGNAL(timeout()), this, SLOT(onDelayedRestartTimerTriggered())); if (!canRun64BitPrograms()) { qWarning("OverlayPrivateWin: mumble_ol_helper_x64.exe (64-bit overlay helper) disabled because the host is not x64 capable."); m_helper64_enabled = false; } else if (!g.s.bOverlayWinHelperX64Enable) { qWarning("OverlayPrivateWin: mumble_ol_helper_x64.exe (64-bit overlay helper) disabled via 'overlay_win/enable_x64_helper' config option."); m_helper64_enabled = false; } } OverlayPrivateWin::~OverlayPrivateWin() { m_active = false; if (!CloseHandle(m_mumble_handle)) { qFatal("OverlayPrivateWin: unable to close Mumble process handle."); return; } // Remove all signals, so they don't // interfere with our calls to waitForFinished // below. m_helper_process->disconnect(); m_helper64_process->disconnect(); m_helper_process->terminate(); m_helper64_process->terminate(); m_helper_process->waitForFinished(); m_helper64_process->waitForFinished(); } void OverlayPrivateWin::startHelper(QProcess *helper) { if (helper->state() == QProcess::NotRunning) { if (helper == m_helper_process) { helper->start(m_helper_exe_path, m_helper_exe_args); m_helper_start_time.restart(); } else if (helper == m_helper64_process) { helper->start(m_helper64_exe_path, m_helper64_exe_args); m_helper64_start_time.restart(); } else { qFatal("OverlayPrivateWin: invalid helper passed to startHelper()."); } } else { qWarning("OverlayPrivateWin: startHelper() called while process is already running. skipping."); } } void OverlayPrivateWin::setActive(bool active) { if (m_active != active) { m_active = active; if (m_active) { if (m_helper_enabled) { startHelper(m_helper_process); } if (m_helper64_enabled) { startHelper(m_helper64_process); } } else { if (m_helper_enabled) { m_helper_process->terminate(); } if (m_helper64_enabled) { m_helper64_process->terminate(); } } } } static const char *exitStatusString(QProcess::ExitStatus exitStatus) { switch (exitStatus) { case QProcess::NormalExit: return "normal exit"; case QProcess::CrashExit: return "crash"; } return "unknown"; } static const char *processErrorString(QProcess::ProcessError processError) { switch (processError) { case QProcess::FailedToStart: return "process failed to start"; case QProcess::Crashed: return "process crashed"; case QProcess::Timedout: return "process wait operation timed out"; case QProcess::WriteError: return "an error occurred when attempting to write to the process"; case QProcess::ReadError: return "an error occurred when attempting to read from the process"; case QProcess::UnknownError: return "an unknown error occurred"; } return "unknown"; } void OverlayPrivateWin::onHelperProcessStarted() { QProcess *helper = qobject_cast(sender()); QString path; if (helper == m_helper_process) { path = m_helper_exe_path; } else if (helper == m_helper64_process) { path = m_helper64_exe_path; } else { qFatal("OverlayPrivateWin: unknown QProcess found in onHelperProcessStarted()."); } PROCESS_INFORMATION *pi = helper->pid(); qWarning("OverlayPrivateWin: overlay helper process '%s' started with PID %llu.", qPrintable(path), static_cast(pi->dwProcessId)); } void OverlayPrivateWin::onHelperProcessError(QProcess::ProcessError processError) { QProcess *helper = qobject_cast(sender()); QString path; if (helper == m_helper_process) { path = m_helper_exe_path; } else if (helper == m_helper64_process) { path = m_helper64_exe_path; } else { qFatal("OverlayPrivateWin: unknown QProcess found in onHelperProcessError()."); } qWarning("OverlayPrivateWin: an error occured for overlay helper process '%s': %s", qPrintable(path), processErrorString(processError)); } void OverlayPrivateWin::onHelperProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { QProcess *helper = qobject_cast(sender()); QString path; qint64 elapsedMsec = 0; QTimer *restartTimer = NULL; if (helper == m_helper_process) { path = m_helper_exe_path; elapsedMsec = m_helper_start_time.elapsed(); restartTimer = m_helper_restart_timer; } else if (helper == m_helper64_process) { path = m_helper64_exe_path; elapsedMsec = m_helper64_start_time.elapsed(); restartTimer = m_helper64_restart_timer; } else { qFatal("OverlayPrivateWin: unknown QProcess found in onHelperProcessExited()."); } const char *helperErrString = OverlayHelperErrorToString(static_cast(exitCode)); qWarning("OverlayPrivateWin: overlay helper process '%s' exited (%s) with status code %s.", qPrintable(path), exitStatusString(exitStatus), helperErrString ? helperErrString : qPrintable(QString::number(exitCode))); // If the helper process exited while we're in 'active' // mode, restart it. if (m_active) { // If the helper was only recently started, be // a little more patient with restarting it. // We could be hitting a crash bug in the helper, // and we don't want to do too much harm in that // case by spawning thousands of processes. qint64 cooldownMsec = (qint64) g.s.iOverlayWinHelperRestartCooldownMsec; if (elapsedMsec < cooldownMsec) { qint64 delayMsec = cooldownMsec - elapsedMsec; qWarning("OverlayPrivateWin: waiting %llu seconds until restarting helper process '%s'. last restart was %llu seconds ago.", (unsigned long long) delayMsec/1000ULL, qPrintable(path), (unsigned long long) elapsedMsec/1000ULL); if (!restartTimer->isActive()) { restartTimer->start(delayMsec); } } else { startHelper(helper); } } } void OverlayPrivateWin::onDelayedRestartTimerTriggered() { if (!m_active) { return; } QTimer *timer = qobject_cast(sender()); QProcess *helper = NULL; if (timer == m_helper_restart_timer) { helper = m_helper_process; } else if (timer == m_helper64_restart_timer) { helper = m_helper64_process; } else { qFatal("OverlayPrivateWin: unknown timer found in onDelayedRestartTimerTriggered()."); } if (helper->state() == QProcess::NotRunning) { startHelper(helper); } } void Overlay::platformInit() { d = new OverlayPrivateWin(this); } void Overlay::setActiveInternal(bool act) { if (d) { // Only act if the private instance has been created already static_cast(d)->setActive(act); } } bool OverlayConfig::supportsInstallableOverlay() { return false; } bool OverlayConfig::isInstalled() { return true; } bool OverlayConfig::needsUpgrade() { return false; } bool OverlayConfig::installFiles() { return false; } bool OverlayConfig::uninstallFiles() { return false; }