File indexing completed on 2024-06-23 05:14:19
0001 /* utils/windowsprocessdevice.cpp 0002 This file is part of Kleopatra, the KDE keymanager 0003 SPDX-FileCopyrightText: 2019 g 10code GmbH 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #ifdef WIN32 0009 #include "windowsprocessdevice.h" 0010 0011 #include "kleopatra_debug.h" 0012 0013 #include <Libkleo/GnuPG> 0014 0015 #include <windows.h> 0016 0017 #include <QDir> 0018 #include <QRegularExpression> 0019 0020 /* This is the amount of data GPGME reads at once */ 0021 #define PIPEBUF_SIZE 16384 0022 0023 using namespace Kleo; 0024 0025 static void CloseHandleX(HANDLE &h) 0026 { 0027 if (h && h != INVALID_HANDLE_VALUE) { 0028 if (!CloseHandle(h)) { 0029 qCWarning(KLEOPATRA_LOG) << "CloseHandle failed!"; 0030 } 0031 h = nullptr; 0032 } 0033 } 0034 0035 class WindowsProcessDevice::Private 0036 { 0037 public: 0038 ~Private(); 0039 0040 Private(const QString &path, const QStringList &args, const QString &wd) 0041 : mPath(path) 0042 , mArgs(args) 0043 , mWorkingDirectory(wd) 0044 , mStdInRd(nullptr) 0045 , mStdInWr(nullptr) 0046 , mStdOutRd(nullptr) 0047 , mStdOutWr(nullptr) 0048 , mStdErrRd(nullptr) 0049 , mStdErrWr(nullptr) 0050 , mProc(nullptr) 0051 , mThread(nullptr) 0052 , mEnded(false) 0053 { 0054 } 0055 0056 bool start(QIODevice::OpenMode mode); 0057 0058 qint64 write(const char *data, qint64 size) 0059 { 0060 if (size < 0 || (size >> 32)) { 0061 qCDebug(KLEOPATRA_LOG) << "Invalid write"; 0062 return -1; 0063 } 0064 0065 if (!mStdInWr) { 0066 qCDebug(KLEOPATRA_LOG) << "Write to closed or read only device"; 0067 return -1; 0068 } 0069 0070 DWORD dwWritten; 0071 if (!WriteFile(mStdInWr, data, (DWORD)size, &dwWritten, nullptr)) { 0072 qCDebug(KLEOPATRA_LOG) << "Failed to write"; 0073 return -1; 0074 } 0075 if (dwWritten != size) { 0076 qCDebug(KLEOPATRA_LOG) << "Failed to write everything"; 0077 return -1; 0078 } 0079 return size; 0080 } 0081 0082 qint64 read(char *data, qint64 maxSize) 0083 { 0084 if (!mStdOutRd) { 0085 qCDebug(KLEOPATRA_LOG) << "Read of closed or write only device"; 0086 return -1; 0087 } 0088 0089 if (!maxSize) { 0090 return 0; 0091 } 0092 0093 DWORD exitCode = 0; 0094 if (GetExitCodeProcess(mProc, &exitCode)) { 0095 if (exitCode != STILL_ACTIVE) { 0096 if (exitCode) { 0097 qCDebug(KLEOPATRA_LOG) << "Non zero exit code"; 0098 mError = readAllStdErr(); 0099 return -1; 0100 } 0101 mEnded = true; 0102 qCDebug(KLEOPATRA_LOG) << "Process finished with code " << exitCode; 0103 } 0104 } else { 0105 qCDebug(KLEOPATRA_LOG) << "GetExitCodeProcess Failed"; 0106 } 0107 0108 if (mEnded) { 0109 DWORD avail = 0; 0110 if (!PeekNamedPipe(mStdOutRd, nullptr, 0, nullptr, &avail, nullptr)) { 0111 qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe"; 0112 return -1; 0113 } 0114 if (!avail) { 0115 qCDebug(KLEOPATRA_LOG) << "Process ended and nothing more in pipe"; 0116 return 0; 0117 } 0118 } 0119 0120 DWORD dwRead; 0121 if (!ReadFile(mStdOutRd, data, (DWORD)maxSize, &dwRead, nullptr)) { 0122 qCDebug(KLEOPATRA_LOG) << "Failed to read"; 0123 return -1; 0124 } 0125 0126 return dwRead; 0127 } 0128 0129 QString readAllStdErr() 0130 { 0131 QString ret; 0132 if (!mStdErrRd) { 0133 qCDebug(KLEOPATRA_LOG) << "Read of closed stderr"; 0134 } 0135 DWORD dwRead = 0; 0136 do { 0137 char buf[4096]; 0138 DWORD avail; 0139 if (!PeekNamedPipe(mStdErrRd, nullptr, 0, nullptr, &avail, nullptr)) { 0140 qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe"; 0141 return ret; 0142 } 0143 if (!avail) { 0144 return ret; 0145 } 0146 ReadFile(mStdErrRd, buf, 4096, &dwRead, nullptr); 0147 if (dwRead) { 0148 QByteArray ba(buf, dwRead); 0149 ret += QString::fromLocal8Bit(ba); 0150 } 0151 } while (dwRead); 0152 return ret; 0153 } 0154 0155 void close() 0156 { 0157 if (mProc && mProc != INVALID_HANDLE_VALUE) { 0158 TerminateProcess(mProc, 0xf291); 0159 CloseHandleX(mProc); 0160 } 0161 } 0162 0163 QString errorString() 0164 { 0165 return mError; 0166 } 0167 0168 void closeWriteChannel() 0169 { 0170 CloseHandleX(mStdInWr); 0171 } 0172 0173 private: 0174 QString mPath; 0175 QStringList mArgs; 0176 QString mWorkingDirectory; 0177 QString mError; 0178 HANDLE mStdInRd; 0179 HANDLE mStdInWr; 0180 HANDLE mStdOutRd; 0181 HANDLE mStdOutWr; 0182 HANDLE mStdErrRd; 0183 HANDLE mStdErrWr; 0184 HANDLE mProc; 0185 HANDLE mThread; 0186 bool mEnded; 0187 }; 0188 0189 WindowsProcessDevice::WindowsProcessDevice(const QString &path, const QStringList &args, const QString &wd) 0190 : d(new Private(path, args, wd)) 0191 { 0192 } 0193 0194 bool WindowsProcessDevice::open(QIODevice::OpenMode mode) 0195 { 0196 bool ret = d->start(mode); 0197 if (ret) { 0198 setOpenMode(mode); 0199 } 0200 return ret; 0201 } 0202 0203 qint64 WindowsProcessDevice::readData(char *data, qint64 maxSize) 0204 { 0205 return d->read(data, maxSize); 0206 } 0207 0208 qint64 WindowsProcessDevice::writeData(const char *data, qint64 maxSize) 0209 { 0210 return d->write(data, maxSize); 0211 } 0212 0213 bool WindowsProcessDevice::isSequential() const 0214 { 0215 return true; 0216 } 0217 0218 void WindowsProcessDevice::closeWriteChannel() 0219 { 0220 d->closeWriteChannel(); 0221 } 0222 0223 void WindowsProcessDevice::close() 0224 { 0225 d->close(); 0226 QIODevice::close(); 0227 } 0228 0229 QString getLastErrorString() 0230 { 0231 wchar_t *lpMsgBuf = nullptr; 0232 DWORD dw = GetLastError(); 0233 0234 FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0235 NULL, 0236 dw, 0237 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 0238 (wchar_t *)&lpMsgBuf, 0239 0, 0240 NULL); 0241 0242 QString ret = QString::fromWCharArray(lpMsgBuf); 0243 0244 LocalFree(lpMsgBuf); 0245 return ret; 0246 } 0247 0248 WindowsProcessDevice::Private::~Private() 0249 { 0250 if (mProc && mProc != INVALID_HANDLE_VALUE) { 0251 close(); 0252 } 0253 CloseHandleX(mThread); 0254 CloseHandleX(mStdInRd); 0255 CloseHandleX(mStdInWr); 0256 CloseHandleX(mStdOutRd); 0257 CloseHandleX(mStdOutWr); 0258 CloseHandleX(mStdErrRd); 0259 CloseHandleX(mStdErrWr); 0260 } 0261 0262 static QString qt_create_commandline(const QString &program, const QStringList &arguments, const QString &nativeArguments) 0263 { 0264 QString args; 0265 if (!program.isEmpty()) { 0266 QString programName = program; 0267 if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' '))) 0268 programName = QLatin1Char('\"') + programName + QLatin1Char('\"'); 0269 programName.replace(QLatin1Char('/'), QLatin1Char('\\')); 0270 0271 // add the prgram as the first arg ... it works better 0272 args = programName + QLatin1Char(' '); 0273 } 0274 0275 for (int i = 0; i < arguments.size(); ++i) { 0276 QString tmp = arguments.at(i); 0277 // Quotes are escaped and their preceding backslashes are doubled. 0278 tmp.replace(QRegularExpression(QLatin1StringView(R"--((\\*)")--")), QLatin1String(R"--(\1\1\")--")); 0279 if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) { 0280 // The argument must not end with a \ since this would be interpreted 0281 // as escaping the quote -- rather put the \ behind the quote: e.g. 0282 // rather use "foo"\ than "foo\" 0283 int i = tmp.length(); 0284 while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\')) 0285 --i; 0286 tmp.insert(i, QLatin1Char('"')); 0287 tmp.prepend(QLatin1Char('"')); 0288 } 0289 args += QLatin1Char(' ') + tmp; 0290 } 0291 0292 if (!nativeArguments.isEmpty()) { 0293 if (!args.isEmpty()) 0294 args += QLatin1Char(' '); 0295 args += nativeArguments; 0296 } 0297 0298 return args; 0299 } 0300 0301 bool WindowsProcessDevice::Private::start(QIODevice::OpenMode mode) 0302 { 0303 if (mode != QIODevice::ReadOnly // 0304 && mode != QIODevice::WriteOnly // 0305 && mode != QIODevice::ReadWrite) { 0306 qCDebug(KLEOPATRA_LOG) << "Unsupported open mode " << mode; 0307 return false; 0308 } 0309 0310 SECURITY_ATTRIBUTES saAttr; 0311 ZeroMemory(&saAttr, sizeof(SECURITY_ATTRIBUTES)); 0312 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 0313 saAttr.bInheritHandle = TRUE; 0314 saAttr.lpSecurityDescriptor = NULL; 0315 0316 // Create the pipes 0317 if (!CreatePipe(&mStdOutRd, &mStdOutWr, &saAttr, PIPEBUF_SIZE) // 0318 || !CreatePipe(&mStdErrRd, &mStdErrWr, &saAttr, 0) // 0319 || !CreatePipe(&mStdInRd, &mStdInWr, &saAttr, PIPEBUF_SIZE)) { 0320 qCDebug(KLEOPATRA_LOG) << "Failed to create pipes"; 0321 mError = getLastErrorString(); 0322 return false; 0323 } 0324 0325 // Ensure only the proper handles are inherited 0326 if (!SetHandleInformation(mStdOutRd, HANDLE_FLAG_INHERIT, 0) // 0327 || !SetHandleInformation(mStdErrRd, HANDLE_FLAG_INHERIT, 0) // 0328 || !SetHandleInformation(mStdInWr, HANDLE_FLAG_INHERIT, 0)) { 0329 qCDebug(KLEOPATRA_LOG) << "Failed to set inherit flag"; 0330 mError = getLastErrorString(); 0331 return false; 0332 } 0333 0334 PROCESS_INFORMATION piProcInfo; 0335 ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); 0336 0337 STARTUPINFO siStartInfo; 0338 ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); 0339 siStartInfo.cb = sizeof(STARTUPINFO); 0340 siStartInfo.hStdError = mStdErrWr; 0341 siStartInfo.hStdOutput = mStdOutWr; 0342 siStartInfo.hStdInput = mStdInRd; 0343 siStartInfo.dwFlags |= STARTF_USESTDHANDLES; 0344 0345 const auto args = qt_create_commandline(mPath, mArgs, QString()); 0346 wchar_t *cmdLine = wcsdup(reinterpret_cast<const wchar_t *>(args.utf16())); 0347 const wchar_t *proc = reinterpret_cast<const wchar_t *>(mPath.utf16()); 0348 const QString nativeWorkingDirectory = QDir::toNativeSeparators(mWorkingDirectory); 0349 const wchar_t *wd = reinterpret_cast<const wchar_t *>(nativeWorkingDirectory.utf16()); 0350 0351 /* Filter out the handles to inherit only the three handles which we want to 0352 * inherit. As a Qt Application we have a multitude of open handles which 0353 * may or may not be inheritable. Testing has shown that this reduced about 0354 * thirty handles. Open File handles in our child application can also cause the 0355 * read pipe not to be closed correcly on exit. */ 0356 SIZE_T size; 0357 bool suc = InitializeProcThreadAttributeList(NULL, 1, 0, &size) || GetLastError() == ERROR_INSUFFICIENT_BUFFER; 0358 if (!suc) { 0359 qCDebug(KLEOPATRA_LOG) << "Failed to get Attribute List size"; 0360 mError = getLastErrorString(); 0361 return false; 0362 } 0363 LPPROC_THREAD_ATTRIBUTE_LIST attributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(HeapAlloc(GetProcessHeap(), 0, size)); 0364 if (!attributeList) { 0365 qCDebug(KLEOPATRA_LOG) << "Failed to Allocate Attribute List"; 0366 return false; 0367 } 0368 suc = InitializeProcThreadAttributeList(attributeList, 1, 0, &size); 0369 if (!suc) { 0370 qCDebug(KLEOPATRA_LOG) << "Failed to Initalize Attribute List"; 0371 mError = getLastErrorString(); 0372 return false; 0373 } 0374 0375 HANDLE handles[3]; 0376 handles[0] = mStdOutWr; 0377 handles[1] = mStdErrWr; 0378 handles[2] = mStdInRd; 0379 suc = UpdateProcThreadAttribute(attributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, handles, 3 * sizeof(HANDLE), NULL, NULL); 0380 if (!suc) { 0381 qCDebug(KLEOPATRA_LOG) << "Failed to Update Attribute List"; 0382 mError = getLastErrorString(); 0383 return false; 0384 } 0385 STARTUPINFOEX info; 0386 ZeroMemory(&info, sizeof(info)); 0387 info.StartupInfo = siStartInfo; 0388 info.StartupInfo.cb = sizeof(info); // You have to know this,.. 0389 info.lpAttributeList = attributeList; 0390 0391 // Now lets start 0392 qCDebug(KLEOPATRA_LOG) << "Spawning:" << args; 0393 suc = CreateProcessW(proc, 0394 cmdLine, // command line 0395 NULL, // process security attributes 0396 NULL, // primary thread security attributes 0397 TRUE, // handles are inherited 0398 CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT, // creation flags 0399 NULL, // use parent's environment 0400 wd, // use parent's current directory 0401 &info.StartupInfo, // STARTUPINFO pointer 0402 &piProcInfo); // receives PROCESS_INFORMATION 0403 DeleteProcThreadAttributeList(attributeList); 0404 HeapFree(GetProcessHeap(), 0, attributeList); 0405 CloseHandleX(mStdOutWr); 0406 CloseHandleX(mStdErrWr); 0407 CloseHandleX(mStdInRd); 0408 0409 free(cmdLine); 0410 if (!suc) { 0411 qCDebug(KLEOPATRA_LOG) << "Failed to create process"; 0412 mError = getLastErrorString(); 0413 return false; 0414 } 0415 0416 mProc = piProcInfo.hProcess; 0417 mThread = piProcInfo.hThread; 0418 if (mode == QIODevice::WriteOnly) { 0419 CloseHandleX(mStdOutRd); 0420 } 0421 0422 if (mode == QIODevice::ReadOnly) { 0423 CloseHandleX(mStdInWr); 0424 } 0425 0426 return true; 0427 } 0428 0429 QString WindowsProcessDevice::errorString() 0430 { 0431 return d->errorString(); 0432 } 0433 0434 #endif 0435 0436 #include "moc_windowsprocessdevice.cpp"