view ui/installwrapper.cpp @ 1119:5349e2354c48

(issue54) Merge branch runafterinstall There is now an NSIS Plugin that executes the Software after installation using COM in the shell of the current user. With the way over the shell there is no inheritance / token management required. As it is impossible to drop all privileges of a token granted by UAC and still be able to reelevate the Token again with another RunAs call later this round trip over the Shell was necessary.
author Andre Heinecke <andre.heinecke@intevation.de>
date Tue, 16 Sep 2014 19:48:22 +0200
parents a7a72353d6d2
children 0ddb173bcd8b
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include "installwrapper.h"

#include <QFileInfo>
#include <QProcess>
#include <QTemporaryFile>
#include <QApplication>
#include <QDir>
#include <QDebug>

#include "logging.h"
#include "util.h"
#include "binverify.h"

#define INSTALL_TIMEOUT 3600000 /* Wait up to an hour */

InstallWrapper::InstallWrapper(QObject* parent,
        const QString& path, const QStringList& choices):
    QThread(parent),
    mCertListFile(path),
    mChoices(choices)
{
}

QFileInfo getCinstProcInfo() {
    QFileInfo fi(QCoreApplication::applicationFilePath());
    QDir myDir = fi.absoluteDir();
    QString instProcName = "cinst";
    if (!fi.suffix().isEmpty()) {
        instProcName += "." + fi.suffix();
    }
    return QFileInfo(myDir.absoluteFilePath(instProcName));
}

bool InstallWrapper::writeChoices(QTemporaryFile* choicesFile) const
{
    if (!choicesFile->open()) {
        return false;
    }

    foreach (const QString &b64data, mChoices) {
        syslog_info_printf ("Selected certificate: %s\n", b64data.toLatin1().constData());
        if (choicesFile->write(b64data.toLatin1()) == -1) {
            return false;
        }
        if (choicesFile->write("\n") == -1) {
            return false;
        }
    }

    choicesFile->close();

    return true;
}

void InstallWrapper::run()
{
    /* TODO: We need errorcodes here so that we can see if a user
     * cancled the UAC elevation */
    QTemporaryFile choicesFile;
    QFileInfo cinstProcInfo = getCinstProcInfo();

    QString cinstFileName = QDir::toNativeSeparators(
            getCinstProcInfo().absoluteFilePath());

    if (!cinstProcInfo.isExecutable()) {
        emit error(tr("Could not find certificate installation process."));
        return;
    }

    if (!writeChoices(&choicesFile)) {
        emit error(tr("Failed to write temporary file."));
        return;
    }

#ifdef WIN32
    bin_verify_result vres = verify_binary(cinstFileName.toUtf8().constData(),
            cinstFileName.toUtf8().size());

    if (vres.result != VerifyValid) {
        emit error(tr("Integrity check of the certificate installation process failed. ") 
                + "\n" + tr("Please reinstall the software."));
        return;
    }

    /* QProcess on Windows uses CreateProcess but we have to
     * use the runas shell command to get the UAC prompt if necessary.
     * So we have to handle the process ourself. Starting with
     * shell execute also means that we can not have stdout and stderr
     * redirection. This is the reason we use command line parameters
     * and not a pipe for communication. In debug mode the installer
     * also makes use of output debug string. */
    DWORD retval = 0;
    SHELLEXECUTEINFOW shExecInfo;

    memset (&shExecInfo, 0, sizeof(SHELLEXECUTEINFOW));

    /* Windows needs each parameter packed in " */
    QString parameters = "\"list=" + mCertListFile +
        "\" \"choices=" + choicesFile.fileName() + "\"";

    if (g_debug) {
        parameters += " --debug";
    }

    shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
    shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
    if (!is_system_install()) {
        shExecInfo.lpVerb = L"open";
    } else {
        shExecInfo.lpVerb = L"runas";
    }
    shExecInfo.lpFile = reinterpret_cast<LPCWSTR> (cinstFileName.utf16());
    shExecInfo.lpParameters =  reinterpret_cast<LPCWSTR> (parameters.utf16());

    qDebug() << "Starting process " << cinstFileName <<" params: " << parameters;

    if (!ShellExecuteExW(&shExecInfo)) {
        char* errmsg = getLastErrorMsg();
        QString qerrmsg = QString::fromUtf8(errmsg);
        free(errmsg);
        emit error(tr("Error executing process: %1").arg(qerrmsg));
        fclose(vres.fptr);
        return;
    }

    retval = WaitForSingleObject(shExecInfo.hProcess, INSTALL_TIMEOUT);

    if (retval != WAIT_OBJECT_0) {
        if (retval == WAIT_FAILED) {
            char* errmsg = getLastErrorMsg();
            QString qerrmsg = QString::fromUtf8(errmsg);
            free(errmsg);
            emit error (tr("Error monitoring process: %1").arg(qerrmsg));
            fclose(vres.fptr);
            return;
        } else {
            emit error (tr("Certificate installation timed out."));
            fclose(vres.fptr);
            return;
        }
    }

    if (GetExitCodeProcess(shExecInfo.hProcess, &retval)) {
        if (retval == STILL_ACTIVE) {
            qDebug() << "Process still running, huh..";
        }
    } else {
        char* errmsg = getLastErrorMsg();
        QString qerrmsg = QString::fromUtf8(errmsg);
        free(errmsg);
        emit error (tr("Failed to check process status: %1").arg(qerrmsg));
        CloseHandle(shExecInfo.hProcess);
        fclose(vres.fptr);
        return;
    }
    CloseHandle(shExecInfo.hProcess);
    fclose(vres.fptr);

    if (retval != 0) {
        /* TODO make this nicer */
        emit error (tr("The process failed with return code. %1").arg(retval));
        return;
    }
#else /* WIN32 */
    QProcess installerProcess;
    QStringList parameters;

    choicesFile.setAutoRemove(false);
    parameters << "list=" + mCertListFile << "choices=" + choicesFile.fileName();

    if (g_debug) {
        parameters << "--debug";
    }

    bool sudo_started = false;
    bool use_sudo = is_admin() && is_system_install();
    if (use_sudo) {
        QStringList sudoPrograms;
        sudoPrograms << "pkexec" << "kdesudo" << "sudo";
        QStringList sudoParams;
        sudoParams << cinstProcInfo.absoluteFilePath() << parameters;
        installerProcess.setArguments(sudoParams);

        foreach (const QString &sProg, sudoPrograms) {
            installerProcess.setProgram(sProg);
            qDebug() << "Starting process " << sProg <<" params: " << sudoParams;
            installerProcess.start();
            if (!installerProcess.waitForStarted() ||
                installerProcess.state() == QProcess::NotRunning) {
                continue;
            } else {
                sudo_started = true;
                break;
            }
        }
    }

    /* Fallback to start without sudo */
    if (!use_sudo || !sudo_started) {
        installerProcess.setProgram(cinstProcInfo.absoluteFilePath());
        installerProcess.setArguments(parameters);

        qDebug() << "Starting process " << cinstFileName <<" params: " << parameters;
        installerProcess.start();
        if (!installerProcess.waitForStarted() ||
            installerProcess.state() == QProcess::NotRunning) {
            emit error (tr("Failed to start installer process."));
            return;
        }
    }

    installerProcess.waitForFinished();

    if (installerProcess.exitStatus() == QProcess::CrashExit) {
        /* Woops */
        emit error (tr("Failed to complete installation."));
        return;
    } else if (installerProcess.exitStatus() != QProcess::NormalExit) {
        /* Can not Happen. there are only those two values but maybe
         * qt changed.. */
        emit error (tr("Failed to complete installation."));
        return;
    }

    if (installerProcess.exitCode() == 0) {
        qDebug() << "output: " << installerProcess.readAllStandardOutput();
    } else {
        /* TODO handle errors defined by errorcodes.h */
        qDebug() << "Installer Process returned: " << installerProcess.exitCode();
        qDebug() << "output: " << installerProcess.readAllStandardOutput();
        return;
    }
#endif
    emit installationSuccessful();
}

http://wald.intevation.org/projects/trustbridge/