view ui/installwrapper.cpp @ 1242:e4aff35ef8fd

(issue143) Make uninstallation of nss-installer conditional on Windows
author Andre Heinecke <andre.heinecke@intevation.de>
date Wed, 24 Sep 2014 17:55:14 +0200
parents c8f698ca6355
children
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 = "trustbridge-certificate-installer";
    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()
{
    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;
    SHELLEXECUTEINFOA shExecInfo;

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

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

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

    shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOA);
    shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
    if (!is_system_install()) {
        shExecInfo.lpVerb = "open";
    } else {
        shExecInfo.lpVerb = "runas";
    }
    shExecInfo.lpFile = strdup(cinstFileName.toLocal8Bit().constData());
    shExecInfo.lpParameters = strdup(parameters.toUtf8().constData());

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

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

    retval = WaitForSingleObject(shExecInfo.hProcess, INSTALL_TIMEOUT);

    free((void*)shExecInfo.lpFile);
    free((void*)shExecInfo.lpParameters);
    shExecInfo.lpFile = NULL;
    shExecInfo.lpParameters = NULL;

    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 (issue135) 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 (issue135) 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/