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" andre@1084: #include "binverify.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) { andre@1082: syslog_info_printf ("Selected certificate: %s\n", b64data.toLatin1().constData()); andre@1082: if (choicesFile->write(b64data.toLatin1()) == -1) { andre@1082: return false; andre@1082: } andre@1082: if (choicesFile->write("\n") == -1) { andre@1082: return false; andre@1082: } 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 andre@1084: bin_verify_result vres = verify_binary(cinstFileName.toUtf8().constData(), andre@1084: cinstFileName.toUtf8().size()); andre@1084: andre@1084: if (vres.result != VerifyValid) { andre@1084: emit error(tr("Integrity check of the certificate installation process failed. ") emanuel@1104: + "\n" + tr("Please reinstall the software.")); andre@1084: return; andre@1084: } andre@1084: 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; andre@1159: SHELLEXECUTEINFOA shExecInfo; aheinecke@364: andre@1159: memset (&shExecInfo, 0, sizeof(SHELLEXECUTEINFOA)); aheinecke@256: andre@385: /* Windows needs each parameter packed in " */ andre@385: QString parameters = "\"list=" + mCertListFile + andre@385: "\" \"choices=" + choicesFile.fileName() + "\""; andre@385: andre@1060: if (g_debug) { andre@1060: parameters += " --debug"; andre@1060: } andre@1060: andre@1159: shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOA); aheinecke@285: shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; andre@1003: if (!is_system_install()) { andre@1159: shExecInfo.lpVerb = "open"; aheinecke@511: } else { andre@1159: shExecInfo.lpVerb = "runas"; aheinecke@511: } andre@1159: shExecInfo.lpFile = strdup(cinstFileName.toLocal8Bit().constData()); andre@1159: shExecInfo.lpParameters = strdup(parameters.toUtf8().constData()); aheinecke@256: aheinecke@256: qDebug() << "Starting process " << cinstFileName <<" params: " << parameters; aheinecke@256: andre@1159: if (!ShellExecuteExA(&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)); andre@1084: fclose(vres.fptr); andre@1159: free((void*)shExecInfo.lpFile); andre@1159: free((void*)shExecInfo.lpParameters); aheinecke@285: return; aheinecke@256: } aheinecke@285: aheinecke@285: retval = WaitForSingleObject(shExecInfo.hProcess, INSTALL_TIMEOUT); aheinecke@285: andre@1159: free((void*)shExecInfo.lpFile); andre@1159: free((void*)shExecInfo.lpParameters); andre@1159: shExecInfo.lpFile = NULL; andre@1159: shExecInfo.lpParameters = NULL; andre@1159: 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)); andre@1084: fclose(vres.fptr); aheinecke@285: return; aheinecke@285: } else { aheinecke@285: emit error (tr("Certificate installation timed out.")); andre@1084: fclose(vres.fptr); 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@1084: fclose(vres.fptr); andre@387: return; aheinecke@285: } aheinecke@285: CloseHandle(shExecInfo.hProcess); andre@1084: fclose(vres.fptr); 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@1060: if (g_debug) { andre@1060: parameters << "--debug"; andre@1060: } andre@1060: 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: }