File indexing completed on 2024-06-23 05:14:17

0001 /*
0002     kuniqueservice_win.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
0006     SPDX-FileContributor: Intevation GmbH
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "kuniqueservice.h"
0012 
0013 #include <QCoreApplication>
0014 #include <QDataStream>
0015 #include <QDir>
0016 
0017 #include "kleopatra_debug.h"
0018 #include <windows.h>
0019 
0020 #define MY_DATA_TYPE 12
0021 
0022 class KUniqueService::KUniqueServicePrivate
0023 {
0024     Q_DECLARE_PUBLIC(KUniqueService)
0025     Q_DISABLE_COPY(KUniqueServicePrivate)
0026 
0027 private:
0028     KUniqueService *q_ptr;
0029     HWND mResponder;
0030     HANDLE mCurrentProcess;
0031 
0032     const QString getWindowName() const
0033     {
0034         return QCoreApplication::applicationName() + QStringLiteral("Responder");
0035     }
0036 
0037     HWND getForeignResponder() const
0038     {
0039         const QString qWndName = getWindowName();
0040         wchar_t *wndName = (wchar_t *)qWndName.utf16();
0041         HWND ret = FindWindow(wndName, wndName);
0042         qCDebug(KLEOPATRA_LOG) << "Responder handle:" << ret;
0043         return ret;
0044     }
0045 
0046     void createResponder()
0047     {
0048         WNDCLASS windowClass;
0049         const QString qWndName = getWindowName();
0050         wchar_t *wndName = (wchar_t *)qWndName.utf16();
0051         windowClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
0052         windowClass.lpfnWndProc = windowProc;
0053         windowClass.hInstance = (HINSTANCE)GetModuleHandle(NULL);
0054         windowClass.lpszClassName = wndName;
0055         windowClass.hIcon = nullptr;
0056         windowClass.hCursor = nullptr;
0057         windowClass.hbrBackground = nullptr;
0058         windowClass.lpszMenuName = nullptr;
0059         windowClass.cbClsExtra = 0;
0060         windowClass.cbWndExtra = 0;
0061         RegisterClass(&windowClass);
0062         mResponder = CreateWindow(wndName, wndName, 0, 0, 0, 10, 10, nullptr, nullptr, (HINSTANCE)GetModuleHandle(NULL), nullptr);
0063         qCDebug(KLEOPATRA_LOG) << "Created responder: " << qWndName << " with handle: " << mResponder;
0064     }
0065 
0066     void handleRequest(const COPYDATASTRUCT *cds)
0067     {
0068         Q_Q(KUniqueService);
0069         if (cds->dwData != MY_DATA_TYPE) {
0070             qCDebug(KLEOPATRA_LOG) << "Responder called with invalid data type:" << cds->dwData;
0071             return;
0072         }
0073         if (mCurrentProcess) {
0074             qCDebug(KLEOPATRA_LOG) << "Already serving. Terminating process: " << mCurrentProcess;
0075             setExitValue(42);
0076         }
0077         const QByteArray serialized(static_cast<const char *>(cds->lpData), cds->cbData);
0078         QDataStream ds(serialized);
0079         quint32 curProc;
0080         ds >> curProc;
0081         mCurrentProcess = (HANDLE)curProc;
0082         QString workDir;
0083         ds >> workDir;
0084         QStringList args;
0085         ds >> args;
0086         qCDebug(KLEOPATRA_LOG) << "Process handle: " << mCurrentProcess << " requests activate with args " << args;
0087         q->emitActivateRequested(args, workDir);
0088         return;
0089     }
0090 
0091     KUniqueServicePrivate(KUniqueService *q)
0092         : q_ptr(q)
0093         , mResponder(nullptr)
0094         , mCurrentProcess(nullptr)
0095     {
0096         HWND responder = getForeignResponder();
0097         if (!responder) {
0098             // We are the responder
0099             createResponder();
0100             return;
0101         }
0102         // We are the client
0103 
0104         QByteArray serialized;
0105         QDataStream ds(&serialized, QIODevice::WriteOnly);
0106         DWORD targetId = 0;
0107         GetWindowThreadProcessId(responder, &targetId);
0108         if (!targetId) {
0109             qCDebug(KLEOPATRA_LOG) << "No process id of responder window";
0110             return;
0111         }
0112         HANDLE targetHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, targetId);
0113         if (!targetHandle) {
0114             qCDebug(KLEOPATRA_LOG) << "No target handle. Err: " << GetLastError();
0115         }
0116 
0117         // To allow the other process to terminate the process
0118         // needs a handle to us with the required access.
0119         if (!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), targetHandle, &mCurrentProcess, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
0120             qCDebug(KLEOPATRA_LOG) << "Failed to duplicate handle";
0121         }
0122         CloseHandle(targetHandle);
0123 
0124         ds << (qint32)mCurrentProcess << QDir::currentPath() << QCoreApplication::arguments();
0125         COPYDATASTRUCT cds;
0126         cds.dwData = MY_DATA_TYPE;
0127         cds.cbData = serialized.size();
0128         cds.lpData = serialized.data();
0129 
0130         qCDebug(KLEOPATRA_LOG) << "Sending message to existing Window.";
0131         SendMessage(responder, WM_COPYDATA, 0, (LPARAM)&cds);
0132         // Usually we should be terminated while sending the message.
0133         qCDebug(KLEOPATRA_LOG) << "Send message returned.";
0134     }
0135 
0136     static KUniqueServicePrivate *instance(KUniqueService *q)
0137     {
0138         static KUniqueServicePrivate *self;
0139         if (self) {
0140             return self;
0141         }
0142 
0143         self = new KUniqueServicePrivate(q);
0144         return self;
0145     }
0146 
0147     static LRESULT CALLBACK windowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
0148     {
0149         if (message == WM_COPYDATA) {
0150             const COPYDATASTRUCT *cds = (COPYDATASTRUCT *)lParam;
0151             // windowProc must be static so the singleton pattern although
0152             // it doesn't make much sense in here.
0153             instance(nullptr)->handleRequest(cds);
0154             return 0;
0155         }
0156         return DefWindowProc(hWnd, message, wParam, lParam);
0157     }
0158 
0159     ~KUniqueServicePrivate()
0160     {
0161         if (mResponder) {
0162             DestroyWindow(mResponder);
0163         }
0164     }
0165 
0166     void setExitValue(int code)
0167     {
0168         TerminateProcess(mCurrentProcess, (unsigned int)code);
0169         mCurrentProcess = nullptr;
0170     }
0171 };
0172 
0173 KUniqueService::KUniqueService()
0174     : d_ptr(KUniqueServicePrivate::instance(this))
0175 {
0176 }
0177 
0178 KUniqueService::~KUniqueService()
0179 {
0180     delete d_ptr;
0181 }
0182 
0183 void KUniqueService::setExitValue(int code)
0184 {
0185     Q_D(KUniqueService);
0186     d->setExitValue(code);
0187 }