view ui/installwrapper.cpp @ 648:e41a2537b84d

Implement root installation We now iterate over all users that do not obviously have their login shell disabled and look for NSS directories in their home directory, dropping our privileges to do so.
author Andre Heinecke <andre.heinecke@intevation.de>
date Wed, 25 Jun 2014 12:44:47 +0200
parents 51830f4912c2
children 216a65d7fc4b
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"

#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) {
       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
    /* 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() + "\"";

    shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
    shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
    if (!is_admin()) {
        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));
        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));
            return;
        } else {
            emit error (tr("Certificate installation timed out."));
            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);
        return;
    }
    CloseHandle(shExecInfo.hProcess);

    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();

    bool sudo_started = false;
    bool use_sudo = is_admin();
    if (use_sudo) {
        QStringList sudoPrograms;
        sudoPrograms << "gksudo" << "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/