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 <QFileInfo>
aheinecke@364: #include <QProcess>
aheinecke@256: #include <QTemporaryFile>
aheinecke@256: #include <QApplication>
aheinecke@256: #include <QDir>
aheinecke@256: #include <QDebug>
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. ") 
andre@1084:                 + "\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;
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: 
andre@1060:     if (g_debug) {
andre@1060:         parameters += " --debug";
andre@1060:     }
andre@1060: 
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<LPCWSTR> (cinstFileName.utf16());
aheinecke@285:     shExecInfo.lpParameters =  reinterpret_cast<LPCWSTR> (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));
andre@1084:         fclose(vres.fptr);
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));
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: }