aheinecke@404: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik aheinecke@404: * Software engineering by Intevation GmbH aheinecke@404: * aheinecke@404: * This file is Free Software under the GNU GPL (v>=2) aheinecke@404: * and comes with ABSOLUTELY NO WARRANTY! aheinecke@404: * See LICENSE.txt for details. aheinecke@404: */ aheinecke@256: #include "installwrapper.h" aheinecke@256: aheinecke@256: #include aheinecke@364: #include aheinecke@256: #include aheinecke@256: #include aheinecke@256: #include aheinecke@256: #include aheinecke@256: aheinecke@256: #include "logging.h" aheinecke@511: #include "util.h" aheinecke@256: aheinecke@285: #define INSTALL_TIMEOUT 3600000 /* Wait up to an hour */ aheinecke@285: aheinecke@256: InstallWrapper::InstallWrapper(QObject* parent, aheinecke@364: const QString& path, const QStringList& choices): aheinecke@256: QThread(parent), aheinecke@256: mCertListFile(path), aheinecke@364: mChoices(choices) aheinecke@256: { aheinecke@256: } aheinecke@256: aheinecke@256: QFileInfo getCinstProcInfo() { aheinecke@256: QFileInfo fi(QCoreApplication::applicationFilePath()); aheinecke@256: QDir myDir = fi.absoluteDir(); aheinecke@256: QString instProcName = "cinst"; aheinecke@256: if (!fi.suffix().isEmpty()) { aheinecke@256: instProcName += "." + fi.suffix(); aheinecke@256: } aheinecke@256: return QFileInfo(myDir.absoluteFilePath(instProcName)); aheinecke@256: } aheinecke@256: aheinecke@364: bool InstallWrapper::writeChoices(QTemporaryFile* choicesFile) const aheinecke@364: { aheinecke@364: if (!choicesFile->open()) { aheinecke@364: return false; aheinecke@364: } aheinecke@364: aheinecke@364: foreach (const QString &b64data, mChoices) { rrenkert@440: if (choicesFile->write(b64data.toLatin1()) == -1) { aheinecke@364: return false; aheinecke@364: } rrenkert@440: if (choicesFile->write("\n") == -1) { aheinecke@364: return false; aheinecke@364: } aheinecke@364: } aheinecke@364: aheinecke@364: choicesFile->close(); aheinecke@364: aheinecke@364: return true; aheinecke@364: } aheinecke@364: aheinecke@256: void InstallWrapper::run() aheinecke@256: { aheinecke@285: /* TODO: We need errorcodes here so that we can see if a user aheinecke@285: * cancled the UAC elevation */ aheinecke@364: QTemporaryFile choicesFile; aheinecke@256: QFileInfo cinstProcInfo = getCinstProcInfo(); aheinecke@256: aheinecke@256: QString cinstFileName = QDir::toNativeSeparators( aheinecke@256: getCinstProcInfo().absoluteFilePath()); aheinecke@256: aheinecke@256: if (!cinstProcInfo.isExecutable()) { aheinecke@285: emit error(tr("Could not find certificate installation process.")); aheinecke@256: return; aheinecke@256: } aheinecke@256: aheinecke@364: if (!writeChoices(&choicesFile)) { aheinecke@364: emit error(tr("Failed to write temporary file.")); aheinecke@364: return; aheinecke@256: } aheinecke@256: aheinecke@364: #ifdef WIN32 aheinecke@364: /* QProcess on Windows uses CreateProcess but we have to aheinecke@364: * use the runas shell command to get the UAC prompt if necessary. aheinecke@364: * So we have to handle the process ourself. Starting with aheinecke@364: * shell execute also means that we can not have stdout and stderr aheinecke@364: * redirection. This is the reason we use command line parameters aheinecke@364: * and not a pipe for communication. In debug mode the installer aheinecke@364: * also makes use of output debug string. */ aheinecke@364: DWORD retval = 0; aheinecke@364: SHELLEXECUTEINFOW shExecInfo; aheinecke@364: aheinecke@364: memset (&shExecInfo, 0, sizeof(SHELLEXECUTEINFOW)); aheinecke@256: andre@385: /* Windows needs each parameter packed in " */ andre@385: QString parameters = "\"list=" + mCertListFile + andre@385: "\" \"choices=" + choicesFile.fileName() + "\""; andre@385: aheinecke@285: shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW); aheinecke@285: shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; andre@1003: if (!is_system_install()) { aheinecke@511: shExecInfo.lpVerb = L"open"; aheinecke@511: } else { aheinecke@511: shExecInfo.lpVerb = L"runas"; aheinecke@511: } aheinecke@285: shExecInfo.lpFile = reinterpret_cast (cinstFileName.utf16()); aheinecke@285: shExecInfo.lpParameters = reinterpret_cast (parameters.utf16()); aheinecke@256: aheinecke@256: qDebug() << "Starting process " << cinstFileName <<" params: " << parameters; aheinecke@256: aheinecke@285: if (!ShellExecuteExW(&shExecInfo)) { aheinecke@256: char* errmsg = getLastErrorMsg(); aheinecke@256: QString qerrmsg = QString::fromUtf8(errmsg); aheinecke@256: free(errmsg); aheinecke@285: emit error(tr("Error executing process: %1").arg(qerrmsg)); aheinecke@285: return; aheinecke@256: } aheinecke@285: aheinecke@285: retval = WaitForSingleObject(shExecInfo.hProcess, INSTALL_TIMEOUT); aheinecke@285: aheinecke@285: if (retval != WAIT_OBJECT_0) { aheinecke@285: if (retval == WAIT_FAILED) { aheinecke@285: char* errmsg = getLastErrorMsg(); aheinecke@285: QString qerrmsg = QString::fromUtf8(errmsg); aheinecke@285: free(errmsg); aheinecke@285: emit error (tr("Error monitoring process: %1").arg(qerrmsg)); aheinecke@285: return; aheinecke@285: } else { aheinecke@285: emit error (tr("Certificate installation timed out.")); aheinecke@285: return; aheinecke@285: } aheinecke@285: } aheinecke@285: aheinecke@285: if (GetExitCodeProcess(shExecInfo.hProcess, &retval)) { aheinecke@285: if (retval == STILL_ACTIVE) { aheinecke@285: qDebug() << "Process still running, huh.."; aheinecke@285: } aheinecke@285: } else { aheinecke@285: char* errmsg = getLastErrorMsg(); aheinecke@285: QString qerrmsg = QString::fromUtf8(errmsg); aheinecke@285: free(errmsg); aheinecke@285: emit error (tr("Failed to check process status: %1").arg(qerrmsg)); andre@387: CloseHandle(shExecInfo.hProcess); andre@387: return; aheinecke@285: } aheinecke@285: CloseHandle(shExecInfo.hProcess); aheinecke@285: aheinecke@285: if (retval != 0) { aheinecke@285: /* TODO make this nicer */ aheinecke@285: emit error (tr("The process failed with return code. %1").arg(retval)); andre@388: return; aheinecke@285: } aheinecke@364: #else /* WIN32 */ aheinecke@364: QProcess installerProcess; andre@385: QStringList parameters; andre@385: andre@385: choicesFile.setAutoRemove(false); andre@385: parameters << "list=" + mCertListFile << "choices=" + choicesFile.fileName(); andre@385: andre@646: bool sudo_started = false; andre@841: bool use_sudo = is_admin() && is_system_install(); andre@646: if (use_sudo) { andre@646: QStringList sudoPrograms; andre@987: sudoPrograms << "pkexec" << "kdesudo" << "sudo"; andre@646: QStringList sudoParams; andre@647: sudoParams << cinstProcInfo.absoluteFilePath() << parameters; andre@646: installerProcess.setArguments(sudoParams); andre@646: andre@646: foreach (const QString &sProg, sudoPrograms) { andre@646: installerProcess.setProgram(sProg); andre@646: qDebug() << "Starting process " << sProg <<" params: " << sudoParams; andre@646: installerProcess.start(); andre@646: if (!installerProcess.waitForStarted() || andre@646: installerProcess.state() == QProcess::NotRunning) { andre@646: continue; andre@646: } else { andre@646: sudo_started = true; andre@646: break; andre@646: } andre@646: } andre@646: } andre@646: andre@646: /* Fallback to start without sudo */ andre@646: if (!use_sudo || !sudo_started) { andre@646: installerProcess.setProgram(cinstProcInfo.absoluteFilePath()); andre@646: installerProcess.setArguments(parameters); andre@646: andre@646: qDebug() << "Starting process " << cinstFileName <<" params: " << parameters; andre@646: installerProcess.start(); andre@646: if (!installerProcess.waitForStarted() || andre@646: installerProcess.state() == QProcess::NotRunning) { andre@646: emit error (tr("Failed to start installer process.")); andre@646: return; andre@646: } aheinecke@364: } aheinecke@364: aheinecke@364: installerProcess.waitForFinished(); aheinecke@364: aheinecke@364: if (installerProcess.exitStatus() == QProcess::CrashExit) { aheinecke@364: /* Woops */ andre@385: emit error (tr("Failed to complete installation.")); andre@385: return; aheinecke@364: } else if (installerProcess.exitStatus() != QProcess::NormalExit) { aheinecke@364: /* Can not Happen. there are only those two values but maybe aheinecke@364: * qt changed.. */ andre@385: emit error (tr("Failed to complete installation.")); aheinecke@364: return; aheinecke@364: } aheinecke@364: aheinecke@364: if (installerProcess.exitCode() == 0) { aheinecke@364: qDebug() << "output: " << installerProcess.readAllStandardOutput(); aheinecke@364: } else { aheinecke@364: /* TODO handle errors defined by errorcodes.h */ aheinecke@364: qDebug() << "Installer Process returned: " << installerProcess.exitCode(); aheinecke@364: qDebug() << "output: " << installerProcess.readAllStandardOutput(); aheinecke@364: return; aheinecke@364: } aheinecke@364: #endif andre@388: emit installationSuccessful(); aheinecke@256: }