Mercurial > trustbridge
view ui/mainwindow.cpp @ 1373:00fcb9c4d16b
(issue179) Handle SW verify failures and try to redownload the update
author | Andre Heinecke <andre.heinecke@intevation.de> |
---|---|
date | Mon, 24 Nov 2014 16:46:08 +0100 |
parents | 23df332b2a4c |
children | 831d28c2291d |
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 "mainwindow.h" #include <QDebug> #include <QProcess> #include <QProgressDialog> #include <QMessageBox> #include <QAction> #include <QDialog> #include <QDir> #include <QMenu> #include <QApplication> #include <QFile> #include <QTemporaryDir> #include <QTimer> #include <QHBoxLayout> #include <QVBoxLayout> #include <QGridLayout> #include <QGroupBox> #include <QSplitter> #include <QLabel> #include <QImage> #include <QCheckBox> #include <QButtonGroup> #include <QToolButton> #include <QStandardPaths> #include <QDesktopServices> #include "certificatelist.h" #include "certificateitemwidget.h" #include "downloader.h" #include "helpdialog.h" #include "aboutdialog.h" #include "separatoritemdelegate.h" #include "installwrapper.h" #include "util.h" #include "logging.h" #include "binverify.h" #include "processhelp.h" #include "processwaitdialog.h" #include "trayicon.h" #include "proxysettingsdlg.h" // The amount of time in minutes stay silent if we have // something to say #define NAG_INTERVAL_MINUTES 70 #define DATETIME_FORMAT "d. MMM yyyy HH:mm" #ifndef APPNAME #define APPNAME "TrustBridge" #endif #ifndef SERVER_URL #error "DOWNLOAD_SERVER option not set or invalid." #endif #if defined(_X86_) || defined(__i386__) #define TB_ARCH_STRING "-i386" #else #define TB_ARCH_STRING "-amd64" #endif #ifdef IS_TAG_BUILD # define LIST_RESOURCE "/zertifikatsliste.txt" # ifdef Q_OS_WIN # define SW_RESOURCE_VERSION "/TrustBridge-%1.exe" # define SW_RESOURCE "/TrustBridge.exe" # else # define SW_RESOURCE_VERSION "/TrustBridge-%1" TB_ARCH_STRING ".sh" # define SW_RESOURCE "/TrustBridge" TB_ARCH_STRING ".sh" # endif #else // Not tag build means develpment build # define LIST_RESOURCE "/zertifikatsliste.txt" # ifdef Q_OS_WIN # define SW_RESOURCE_VERSION "/development/TrustBridge-%1.exe" # define SW_RESOURCE "/development/TrustBridge.exe" # else # define SW_RESOURCE_VERSION "/development/TrustBridge-%1" TB_ARCH_STRING ".sh" # define SW_RESOURCE "/development/TrustBridge" TB_ARCH_STRING ".sh" # endif #endif /* Help installation path the path relative to the installation directory where * the help is placed.*/ #ifdef WIN32 #define HELP_PATH "/doc/index.html" #else #define HELP_PATH "/../share/doc/trustbridge/index.html" #endif static void activateDetailsButton(QPushButton *); static void deactivateDetailsButton(QPushButton *); /** @brief get the interval in milliseconds until the next update try. * * @param [in] tries the number of unsuccessful tries already done. * * @returns a positive value for the time in ms that should be waited. * -1 in case the software should abort. */ static int getNextUpdateInterval(int tries) { if (tries < 2) { // 5-10 minutes for the first two checks return ((5 + (qrand() % 5)) * 60 * 1000); } if (tries < 4) { // 15 minutes return 15 * 60 * 1000; } if (tries < 6) { // 30 minutes return 30 * 60 * 1000; } if (tries < 7) { return 60; } return -1; } MainWindow::MainWindow(bool trayMode, const QDateTime& sigDt): mTrayMode(trayMode), mManualDetailsShown(false), mFailedConnections(0), mSigDt(sigDt), mDownloadSWAccepted(false) { createActions(); createTrayIcon(); setupGUI(); resize(900, 600); setMinimumWidth(760); qRegisterMetaType<SSLConnection::ErrorCode>("SSLConnection::ErrorCode"); qRegisterMetaType<Certificate::Status>("Certificate::Status"); connect(mTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); mMessageTimer = new QTimer(this); connect(mMessageTimer, SIGNAL(timeout()), this, SLOT(showMessage())); mMessageTimer->setInterval(NAG_INTERVAL_MINUTES * 60 * 1000); mMessageTimer->start(); checkUpdates(); loadUnselectedCertificates(); loadCertificateList(); if (mSettings.value("ShowOnNextStart").toBool()) { mSettings.remove("ShowOnNextStart"); show(); } else if (!trayMode) { show(); } } void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) { switch (reason) { case QSystemTrayIcon::Trigger: case QSystemTrayIcon::MiddleClick: showMessage(); break; case QSystemTrayIcon::DoubleClick: show(); break; default: ; } } void MainWindow::messageClicked() { if (mCurState == NewSoftwareAvailable) { mDownloadSWAccepted = true; verifySWData(); if (mFailedConnections == 0) { /* If we have a failed connection cound verifySWData has * triggered an invalid software recovery and an update is already * scheduled. */ QString swFileName = mSettings.value("Software/available").toString(); if (swFileName.isEmpty()) { checkUpdates(); mCurState = DownloadingSW; return; } installNewSW(swFileName, mSettings.value("Software/availableDate").toDateTime()); } } else { show(); } } void MainWindow::showMessage() { if (mCurMessage.isEmpty()) { return; } if (!mTrayIcon->isVisible() && !mTrayIcon->isAlternative()) { mTrayIcon->show(); /* When the message is shown before the tray icon is fully visble * and becoming visible may be delayed on some desktop environments * the message will pop up somehere on the screen and not over * the trayicon. So we delay here.*/ QTimer::singleShot(2000, this, SLOT(showMessage())); } else if (mCurState == NewSoftwareAvailable || !isVisible()) { if (mCurState == NewListAvailable) { mTrayIcon->showMessage(QApplication::applicationName(), mCurMessage, QSystemTrayIcon::Information, 10000, tr("Show recommendations")); } else if (mCurState == NewSoftwareAvailable || !mTrayIcon->isAlternative()) { /* Only show new list or new software in alternative as * the current tray icon alternative is too invasive for pure * informational messages. */ mTrayIcon->showMessage(QApplication::applicationName(), mCurMessage, QSystemTrayIcon::Information, 10000); } mMessageTimer->start(); // Restart the timer so that we don't spam } } void MainWindow::verifyListData() { QString availableFileName = mSettings.value("List/available").toString(); QString installedFileName = mSettings.value("List/installed").toString(); if (!availableFileName.isEmpty()) { mListToInstall.readList(availableFileName.toUtf8().constData()); if (!mListToInstall.isValid()) { handleLTE(lteInvalidList); mCurState = TransferError; QFile::remove(availableFileName); mSettings.remove("List/available"); mSettings.remove("List/availableDate"); } else { handleLTE(lteInvalidList, true); } } else { // Make sure the available notation is also removed mSettings.remove("List/available"); mSettings.remove("List/availableDate"); } if (!installedFileName.isEmpty()) { mInstalledList.readList(installedFileName.toUtf8().constData()); if (!mInstalledList.isValid()) { // Probably a bug when Qt fileName is encoded and cFileName // fails because of this. This needs a unit test! // Maybe check that the file is in our data directory QFile::remove(installedFileName); mSettings.remove("List/installed"); mSettings.remove("List/installedDate"); } } else { mSettings.remove("List/installed"); mSettings.remove("List/installedDate"); } } void MainWindow::verifySWData() { QString swFileName = mSettings.value("Software/available").toString(); if (swFileName.isEmpty()) { qDebug() << "Date set but no fileName"; mSettings.remove("Software/availableDate"); return; } QFileInfo fi(swFileName); if (!fi.exists()) { mSettings.remove("Software/available"); mSettings.remove("Software/availableDate"); qDebug() << "Software does not yet exist."; return; } if (!fi.isExecutable()) { qWarning() << "Downloaded file: " << swFileName << " is not executable."; QFile::remove(swFileName); mSettings.remove("Software/available"); mSettings.remove("Software/availableDate"); return; } bin_verify_result verifyResult = verify_binary(swFileName.toUtf8().constData(), swFileName.toUtf8().size()); QDateTime sigDt = QDateTime::fromTime_t (verifyResult.sig_time); if (verifyResult.result != VerifyValid || (mSigDt.isValid() && sigDt <= mSigDt)) { handleLTE(lteInvalidSoftware); if (verifyResult.result != VerifyValid) { qDebug() << "Failed to verify downloaded data."; } else { qDebug() << "Software update was signed at: " << sigDt; qDebug() << "But the installed software was signed on: " << mSigDt; if (verifyResult.fptr) { fclose(verifyResult.fptr); } scheduleFailureRetryOrClose(!isVisible()); } QFile::remove(swFileName); mSettings.remove("Software/available"); mSettings.remove("Software/availableDate"); return; } handleLTE(lteInvalidSoftware, true); /* Reset error state */ fclose(verifyResult.fptr); } void MainWindow::handleNewList(const QString& fileName, const QDateTime& modDate) { if (mSettings.value("List/available").toString() == fileName && getState() == NewListAvailable) { qDebug() << "List already handled"; return; } else { qDebug() << "Handling list"; } mSettings.setValue("List/available", fileName); mSettings.setValue("List/availableDate", modDate); verifyListData(); if (!mListToInstall.isValid() || mListToInstall.date() <= mInstalledList.date()) { if ( mListToInstall.date() <= mInstalledList.date()) { qDebug() << "Newest list on the server is older then the installed list. "; qDebug() << "Installed: " << mInstalledList.date(); qDebug() << "Available: " << mListToInstall.date(); } else { qDebug() << "Failed to verify list."; } handleLTE(lteInvalidList); /* Downloader provided invalid files */ /* Retry the download again in 10 - 20 minutes */ QTimer::singleShot(600000 + (qrand() % 60000), this, SLOT(checkUpdates())); } else { if (mTrayIcon->isAlternative()) { mCurMessage = tr("An updated certificate list is available."); } else { mCurMessage = tr("An updated certificate list is available.") +" " + tr("Click here to install."); } setState(NewListAvailable); showMessage(); loadCertificateList(); } } void MainWindow::handleNewSW(const QString& fileName, const QDateTime& modDate) { if (mTrayIcon->isAlternative()) { mCurMessage = tr("An update for %1 is available.").arg( QApplication::applicationName()); } else { mCurMessage = QString(tr("An update for %1 is available.") + "\n" + tr("Click here to download and install the update.")).arg( QApplication::applicationName()); } setState(NewSoftwareAvailable); mSettings.setValue("Software/available", fileName); mSettings.setValue("Software/availableDate", modDate); mSettings.sync(); showMessage(); } QString MainWindow::getPrettyInstallerName(QString realFileName) { QTemporaryDir tDir; if (!tDir.isValid()) { qDebug () << "Failed to create temporary directory."; showErrorMessage (tr("Failed to create temporary directory.") + "\n" + tr("Please ensure that you have the access rights to write in " "the temporary directory and that there is at least 20MB free " "disk space available.")); return QString(); } QString targetPath = tDir.path() + "/" + QObject::tr("TrustBridge-Updater", "Used as filename for the updater. Only use ASCII please."); tDir.setAutoRemove(false); #ifdef WIN32 targetPath += ".exe"; #endif if (!QFile::copy(realFileName, targetPath)) { showErrorMessage (tr("Failed to create a temporary copy of the installer.") + "\n" + tr("Please ensure that you have the access rights to write in " "the temporary directory and that there is at least 20MB free " "disk space available.")); qDebug() << "Failed to create temporary copy of installer."; } return targetPath; } void MainWindow::updaterFinished(int exitCode, QProcess::ExitStatus status) { if (status != QProcess::NormalExit) { syslog_error_printf("Update failed.\n"); qDebug() << "Failed to install update."; return; } if (exitCode != 0) { qDebug() << "Update not installed with error: " << exitCode; return; } qDebug() << "Restarting"; ProcessHelp::cleanUp(); QProcess::startDetached(qApp->applicationFilePath()); qApp->quit(); } void MainWindow::installNewSW(const QString& fileName, const QDateTime& modDate) { QFileInfo instProcInfo = QFileInfo(fileName); QString filePath = QDir::toNativeSeparators(instProcInfo.absoluteFilePath()); /* Reset available information. */ mSettings.setValue("Software/available", fileName); mSettings.setValue("Software/availableDate", modDate); /* Copy the file to a temporary name for installation */ filePath = getPrettyInstallerName(filePath); if (filePath.isEmpty()) { qDebug() << "Failed to copy updater to temporary location."; return; } mSettings.setValue("Software/Updater", filePath); /* So it can be deleted on next start */ mSettings.sync(); bin_verify_result vres = verify_binary(filePath.toUtf8().constData(), filePath.toUtf8().size()); QDateTime sigDt = QDateTime::fromTime_t (vres.sig_time); if (vres.result != VerifyValid || (mSigDt.isValid() && sigDt <= mSigDt)) { handleLTE(lteInvalidSoftware); if (vres.result != VerifyValid) { qDebug() << "Failed to verify installer."; } else { qDebug() << "Software update was signed at: " << sigDt; qDebug() << "But the installed software was signed on: " << mSigDt; if (vres.fptr) { fclose(vres.fptr); } scheduleFailureRetryOrClose(!isVisible()); } QFile::remove(filePath); mSettings.remove("Software/available"); mSettings.remove("Software/availableDate"); return; } handleLTE(lteInvalidSoftware, true); QFileInfo fi(QCoreApplication::applicationFilePath()); QDir installDir = fi.absoluteDir(); #ifdef WIN32 QString parameters = QString::fromLatin1("/S /UPDATE=1 /D=") + installDir.path().replace("/", "\\") + ""; SHELLEXECUTEINFOW shExecInfo; memset (&shExecInfo, 0, sizeof(SHELLEXECUTEINFOW)); shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW); shExecInfo.lpFile = reinterpret_cast<LPCWSTR> (filePath.utf16()); shExecInfo.lpParameters = reinterpret_cast<LPCWSTR> (parameters.utf16()); // shExecInfo.fMask = SEE_MASK_NOASYNC; shExecInfo.nShow = SW_SHOWDEFAULT; if (!is_system_install()) { shExecInfo.lpVerb = L"open"; } else { shExecInfo.lpVerb = L"runas"; } qDebug() << "Starting process: " << filePath << " with arguments: " << parameters; if (!ShellExecuteExW(&shExecInfo)) { /* Execution failed, maybe the user aborted the UAC check? */ char* errmsg = getLastErrorMsg(); QString qerrmsg = QString::fromUtf8(errmsg); free(errmsg); qDebug() << "Failed to start process: " << qerrmsg; setState(NewSoftwareAvailable); fclose(vres.fptr); return; } #else /* WIN32 */ /* On linux installDir is <prefix>/bin */ QStringList parameters; installDir.cdUp(); parameters << "--prefix" << installDir.path(); parameters << "--update"; bool sudo_started = false; bool use_sudo = is_admin() && is_system_install(); QProcess *updaterProcess = new QProcess(); if (use_sudo) { QStringList sudoPrograms; sudoPrograms << "pkexec" << "kdesudo" << "sudo"; QStringList sudoParams; sudoParams << filePath; sudoParams << parameters; updaterProcess->setArguments(sudoParams); #if 0 updaterProcess->setStandardErrorFile("/tmp/tb-inst-err.log"); updaterProcess->setStandardOutputFile("/tmp/tb-inst-out.log"); #endif foreach (const QString &sProg, sudoPrograms) { qDebug() << "Starting process " << sProg <<" params: " << sudoParams; updaterProcess->setProgram(sProg); updaterProcess->start(); if (!updaterProcess->waitForStarted() || updaterProcess->state() == QProcess::NotRunning) { continue; } else { sudo_started = true; connect(updaterProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(updaterFinished(int, QProcess::ExitStatus))); break; } } } if (!sudo_started) { qDebug() << "Starting process " << filePath <<" params: " << parameters; updaterProcess->setArguments(parameters); updaterProcess->setProgram(filePath); updaterProcess->start(); if (!updaterProcess->waitForStarted() || updaterProcess->state() == QProcess::NotRunning) { qDebug() << "Failed to start process."; } connect(updaterProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(updaterFinished(int, QProcess::ExitStatus))); return; } #endif if (isVisible()) { mSettings.setValue("ShowOnNextStart", true); mSettings.sync(); } syslog_info_printf ("Installing update: %s\n", fileName.toUtf8().constData()); /* Installer process should now be running. We exit */ fclose(vres.fptr); #ifdef WIN32 closeApp(); #endif } void MainWindow::checkUpdates() { verifyListData(); bool downloadSW = false; if (mDownloadSWAccepted == true) { downloadSW = true; } /* Delete old temporary installers if they exist */ QString oldUpdater = mSettings.value("Software/Updater").toString(); if (!oldUpdater.isEmpty()) { qDebug() << "Removing old updater: " << oldUpdater; QFileInfo fiUpdater(oldUpdater); if (!QFile::remove(fiUpdater.absoluteFilePath())) { qDebug() << "Failed to remove file"; } else { if (!fiUpdater.absoluteDir().rmdir(fiUpdater.absoluteDir().absolutePath())) { qDebug() << "Failed to remove temporary directory."; } } mSettings.remove("Software/Updater"); } if (!mSettings.contains("Software/installedDate") || mSettings.value("Software/installedVersion").toString() != QApplication::applicationVersion()) { /* This should only happen on initial startup and after an update has * been installed */ getLastModForCurrentVersion(); return; } QDateTime listInstalledLastMod = mSettings.value("List/installedDate").toDateTime(); QDateTime swInstalledLastMod = mSettings.value("Software/installedDate").toDateTime(); QString listResource = QString::fromLatin1(LIST_RESOURCE); QString swResource = QString::fromLatin1(SW_RESOURCE); #ifndef RELEASE_BUILD /* Use this for testing to specify another file name for updates */ listResource = mSettings.value("List/resource", listResource).toString(); swResource = mSettings.value("Software/resource", swResource).toString(); #endif Downloader* downloader = new Downloader(this, QString::fromLatin1(SERVER_URL), QByteArray(), swInstalledLastMod, listInstalledLastMod, swResource, listResource, downloadSW); connect(downloader, SIGNAL(newListAvailable(const QString&, const QDateTime&)), this, SLOT(handleNewList(const QString&, const QDateTime&))); if (!downloadSW) { connect(downloader, SIGNAL(newSoftwareAvailable(const QString&, const QDateTime&)), this, SLOT(handleNewSW(const QString&, const QDateTime&))); } else { setState(DownloadingSW); connect(downloader, SIGNAL(newSoftwareAvailable(const QString&, const QDateTime&)), this, SLOT(installNewSW(const QString&, const QDateTime&))); } connect(downloader, SIGNAL(finished()), downloader, SLOT(deleteLater())); connect(downloader, SIGNAL(finished()), this, SLOT(updateCheckSuccess())); connect(downloader, SIGNAL(error(const QString &, SSLConnection::ErrorCode)), this, SLOT(downloaderError(const QString &, SSLConnection::ErrorCode))); downloader->start(); if (downloadSW && getState() == DownloadingSW && isVisible()) { QProgressDialog *progDlg = new QProgressDialog(this); progDlg->setCancelButton(0); progDlg->setRange(0,0); progDlg->setMinimumDuration(0); progDlg->setLabelText(tr("Downloading update...")); progDlg->show(); connect(downloader, SIGNAL(finished()), progDlg, SLOT(deleteLater())); connect(downloader, SIGNAL(finished()), progDlg, SLOT(cancel())); } } void MainWindow::getLastModForCurrentVersion() { QString softwareVersion = QString::fromLatin1(SW_RESOURCE_VERSION).arg( QApplication::applicationVersion()); qDebug() << "Getting last modified date for: " << softwareVersion; QString listResource = QString::fromLatin1(LIST_RESOURCE); Downloader* downloader = new Downloader(this, QString::fromLatin1(SERVER_URL), QByteArray(), QDateTime::currentDateTime(), QDateTime::currentDateTime(), softwareVersion, listResource, false); connect(downloader, SIGNAL(finished()), downloader, SLOT(deleteLater())); connect(downloader, SIGNAL(error(const QString &, SSLConnection::ErrorCode)), this, SLOT(downloaderError(const QString &, SSLConnection::ErrorCode))); connect(downloader, SIGNAL(lastModifiedDate(const QDateTime&)), this, SLOT(setLastModifiedSWDate(const QDateTime&))); connect(this, SIGNAL(destroyed(QObject*)), downloader, SLOT(quit())); downloader->start(); } void MainWindow::setLastModifiedSWDate(const QDateTime &date) { QDateTime swAvailableLastMod = mSettings.value("Software/availableDate").toDateTime(); if (swAvailableLastMod.isValid() && date.isValid()) { if (date >= swAvailableLastMod) { qDebug() << "Installed an update: " << date << " available was " << swAvailableLastMod; syslog_info_printf ("Software has been updated to version: %s\n", QApplication::applicationVersion().toUtf8().constData()); QString fileName = mSettings.value("Software/available").toString(); if (fileName.isEmpty()) { qDebug() << "Software marked as available but no filename set."; } else { if (QFile::remove(fileName)) { qDebug() << "Removed: " << fileName; } else { qDebug() << "Failed to remove: " << fileName; } } /* Clear out available data. */ mSettings.remove("Software/available"); mSettings.remove("Software/availableDate"); } } mSettings.beginGroup("Software"); #ifdef IS_TAG_BUILD /* We accept an invalid date to force installing any avialable update * in release mode. Otherwise we default to current datetime when we * did not find out version.*/ mSettings.setValue("installedDate", date); #else mSettings.setValue("installedDate", date.isValid() ? date : QDateTime::currentDateTime()); #endif mSettings.setValue("installedVersion", QApplication::applicationVersion()); mSettings.endGroup(); checkUpdates(); } void MainWindow::downloaderError(const QString &message, SSLConnection::ErrorCode error) { syslog_error_printf ("Failed to check for updates: %s", message.toUtf8().constData()); qDebug() << "Downloader error: " << error; if (error == SSLConnection::InvalidCertificate) { handleLTE(lteInvalidCertificate); } else { handleLTE(lteNoConnection); } setState(TransferError); #ifdef IS_TAG_BUILD /* During tag build it should never happen that an url checked is not available * during development this is normal as each revision produces a new url. */ if (!isVisible()) { mCurMessage = message; mTrayIcon->show(); showMessage(); } else { showErrorMessage(tr("Failed to check for updates:") + "\n" + message); } #endif } void MainWindow::createActions() { mCheckUpdates = new QAction(tr("Check for Updates"), this); connect(mCheckUpdates, SIGNAL(triggered()), this, SLOT(checkUpdates())); mQuitAction = new QAction(tr("Quit"), this); connect(mQuitAction, SIGNAL(triggered()), qApp, SLOT(quit())); } void MainWindow::createTrayIcon() { QIcon trayImg(":/img/tray_22.png"); trayImg.addFile(":/img/tray_48.png", QSize(48,48)); mTrayMenu = new QMenu(this); mTrayMenu->addAction(mCheckUpdates); mTrayMenu->addAction(mQuitAction); mTrayIcon = new TrayIcon(this); mTrayIcon->setContextMenu(mTrayMenu); mTrayIcon->setIcon(trayImg); if (mTrayIcon->isAlternative()) { /* On unity (the alternative notification usage) * we want to use the logo as window icon.*/ setWindowIcon(QIcon(":/img/logo.png")); } else { setWindowIcon(trayImg); } mTrayIcon->setToolTip(tr("TrustBridge")); connect(mTrayIcon, SIGNAL(messageClicked()), this, SLOT(messageClicked())); } QWidget * MainWindow::createInfoWidget() { QWidget *theWidget = new QWidget; QVBoxLayout *infoPanelLayout = new QVBoxLayout; QHBoxLayout *infoHeaderLayout = new QHBoxLayout; QVBoxLayout *infoHeaderTextLayout = new QVBoxLayout; QVBoxLayout *infoCenterLayout = new QVBoxLayout; QString infoVersion = tr("Version: "); infoVersion.append(QApplication::applicationVersion()); QLabel *appVersion = new QLabel(infoVersion); appVersion->setTextInteractionFlags( Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); QFrame *infoHeaderSeparator = new QFrame(); infoHeaderSeparator->setFrameShape(QFrame::HLine); infoHeaderSeparator->setFrameShadow(QFrame::Sunken); infoHeaderTextLayout->addWidget(appVersion); infoHeaderLayout->addLayout(infoHeaderTextLayout); infoHeaderLayout->insertStretch(2, 10); QLabel *textDesc = new QLabel(tr("TrustBridge is a root certificate" " installer for Windows and GNU/Linux.<br/>") + tr("The root certificate lists are managed" " by the German <a href=\"https://www.bsi.bund.de\">" "Federal Office for Information Security (BSI)</a>.<br/><br/>") + tr("The software was developed by the companies" " <a href=\"http://www.intevation.de\">Intevation GmbH</a> and " " <a href=\"http://www.dn-systems.de\">DN-Systems GmbH</a>, <br>" " contracted by the BSI.<br/><br/>") + tr("TrustBridge is Free Software licensed" " under GNU GPL v>=3.<br/>Copyright (C) 2014 by Bundesamt für Sicherheit" " in der Informationstechnik<br/><br/>") + tr("TrustBridge uses several Free Software components with different licenses:") + "<ul><li>TrustBridge source code (GPL v>=2)" + "<li>Qt (LGPL v==2.1)" + "<li>PolarSSL (GPL v>=2)" + "<li>Oxygen-Icons (LGPL v==3)" + "<li>Mozilla NSS (Mozilla Public License v2)" + "<li>libcurl (The curl license)</ul>" + tr("You will find the legally binding details in the 'licenses' directory " "where TrustBridge is installed<br/>" "or in the corresponding revision of the " "<a href=\"https://wald.intevation.org/hg/trustbridge/file/tip/licenses\">TrustBridge code repository</a>.")); textDesc->setTextFormat(Qt::RichText); textDesc->setTextInteractionFlags( Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard | Qt::LinksAccessibleByMouse); textDesc->setOpenExternalLinks(true); infoCenterLayout->addWidget(infoHeaderSeparator); infoCenterLayout->addWidget(textDesc); infoCenterLayout->insertSpacing(2, 10); infoCenterLayout->insertSpacing(4, 10); infoCenterLayout->insertSpacing(6, 10); QHBoxLayout *helpButtonLayout = new QHBoxLayout(); QPushButton *helpButton = new QPushButton(" " + tr("Show Help")); helpButton->setIcon(QIcon(":/img/show-help_16.png")); connect(helpButton, SIGNAL(clicked()), this, SLOT(showHelp())); helpButtonLayout->addWidget(helpButton); #ifdef USE_CURL QPushButton *proxySettingsButton = new QPushButton(" " + tr("Proxy settings")); proxySettingsButton->setIcon(QIcon(":/img/preferences-network_16.png")); connect(proxySettingsButton, SIGNAL(clicked()), this, SLOT(showProxySettings())); helpButtonLayout->addWidget(proxySettingsButton); #endif helpButtonLayout->addStretch(); infoCenterLayout->addLayout(helpButtonLayout); infoCenterLayout->insertStretch(8, 10); infoPanelLayout->addLayout(infoHeaderLayout); infoPanelLayout->addLayout(infoCenterLayout); theWidget->setLayout(infoPanelLayout); return theWidget; } QWidget * MainWindow::createUpdatesWidget() { QWidget * theWidget = new QWidget; QVBoxLayout *updatesMainLayout = new QVBoxLayout; /* The header */ QVBoxLayout *updatesHeaderLayout = new QVBoxLayout; QGridLayout *detailsLayout = new QGridLayout; /* Header 1: Action buttons and summary*/ mUpdatesHeader = new QLabel("<h2>" + tr("Certificates unchanged")+ "</h2>"); updatesHeaderLayout->addWidget(mUpdatesHeader); QHBoxLayout *updatesHeaderActionButtonLayout = new QHBoxLayout; mQuitButton = new QPushButton(" " + tr("Quit without saving")); mQuitButton->setIcon(QIcon(":/img/application-exit.png")); mQuitButton->setFixedHeight(30); mInstallButton = new QPushButton(" " + tr("Install certificates again")); mInstallButton->setFixedHeight(30); #ifdef Q_OS_WIN if (is_system_install()) { QIcon uacShield = QApplication::style()->standardIcon(QStyle::SP_VistaShield); mInstallButton->setIcon(uacShield); } else { mInstallButton->setIcon(QIcon(":/img/do-changes-16.png")); } #else mInstallButton->setIcon(QIcon(":/img/do-changes-16.png")); #endif connect(mQuitButton, SIGNAL(clicked()), this, SLOT(closeApp())); connect(mInstallButton, SIGNAL(clicked()), this, SLOT(checkAndInstallCerts())); updatesHeaderActionButtonLayout->addWidget(mInstallButton); updatesHeaderActionButtonLayout->addWidget(mQuitButton); updatesHeaderActionButtonLayout->addStretch(-1); updatesHeaderLayout->addLayout(updatesHeaderActionButtonLayout); updatesHeaderLayout->addSpacing(20); /* The splitter line */ QFrame *line = new QFrame(); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); updatesHeaderLayout->addWidget(line); updatesMainLayout->addLayout(updatesHeaderLayout); /* Central Header Details and update button. Part of the scroll area */ QScrollArea *centralScrollArea = new QScrollArea; QVBoxLayout *updatesCenterLayout = new QVBoxLayout; mUpdatesDetailsHeader = new QLabel(QString()); QHBoxLayout *updateDateAndSearchButton = new QHBoxLayout; mCertListVersion = new QLabel(QString()); mCertListVersionContents = new QLabel(QString()); const QDateTime lastCheck = mSettings.value("lastUpdateCheck").toDateTime().toLocalTime(); mLastUpdateCheck = new QLabel(tr("Last update check:")); if (lastCheck.isValid()) { const QString lastUpdateCheck = QLocale::system().toString(lastCheck, DATETIME_FORMAT); mLastUpdateCheckContents = new QLabel(lastUpdateCheck); } else { mLastUpdateCheckContents = new QLabel(tr("No connection with the updateserver.")); } QPushButton *searchUpdates = new QPushButton(" " + tr("Update")); searchUpdates->setFixedHeight(22); searchUpdates->setToolTip(tr("Check for Updates")); searchUpdates->setStyleSheet("font-size: 10px;"); searchUpdates->setIcon(QIcon(":/img/update-list.png")); connect(searchUpdates, SIGNAL(clicked()), this, SLOT(checkUpdates())); updateDateAndSearchButton->addWidget(mLastUpdateCheckContents); updateDateAndSearchButton->addWidget(searchUpdates); mUpdatesTip = new QLabel(QString()); mUpdatesTip->setWordWrap(true); // addWidget(*Widget, row, column, rowspan, colspan) updatesCenterLayout->addWidget(mUpdatesDetailsHeader); detailsLayout->addWidget(mLastUpdateCheck, 0, 0, 1, 1); detailsLayout->addLayout(updateDateAndSearchButton, 0, 1, 1, 1); detailsLayout->addWidget(mCertListVersion, 1, 0, 1, 1); detailsLayout->addWidget(mCertListVersionContents, 1, 1, 1, 1); detailsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum), 2, 2, 1, 1); detailsLayout->setColumnStretch(2, 1); updatesCenterLayout->addLayout(detailsLayout); updatesCenterLayout->addItem(new QSpacerItem(100, 10)); updatesCenterLayout->addWidget(mUpdatesTip); /* The central panels. */ QHBoxLayout *updatesNewLayout = new QHBoxLayout; QHBoxLayout *updatesRemoveLayout = new QHBoxLayout; QHBoxLayout *updatesManualLayout = new QHBoxLayout; mUpdatesNewCertificates = new QLabel("<h3>" + tr("Install new trusted certificates (%1/%2)").arg(0).arg(0) + "</h3>"); mUpdatesDetailsNew = new QPushButton(); mUpdatesDetailsNew->setText(" " + tr("Details")); mUpdatesDetailsNew->setToolTip(tr("Show details")); mUpdatesDetailsNew->setStyleSheet("font-size: 10px;"); mUpdatesDetailsNew->setFixedHeight(22); mUpdatesDetailsNew->setIcon(QIcon(":/img/dialog-information_16px.png")); connect(mUpdatesDetailsNew, SIGNAL(clicked()), this, SLOT(toggleUpdatesNew())); updatesNewLayout->addWidget(mUpdatesNewCertificates); updatesNewLayout->addWidget(mUpdatesDetailsNew); updatesNewLayout->addStretch(1); mUpdatesNew = new CertificateListWidget(this); connect(mUpdatesNew, SIGNAL(certListChanged(int)), this, SLOT(listChanged(int))); mUpdatesNew->hide(); mUpdatesRemoveCertificates = new QLabel("<h3>" + tr("Remove revoked certificates (%1/%2)").arg(0).arg(0) + "</h3>"); mUpdatesDetailsRemove = new QPushButton(); mUpdatesDetailsRemove->setText(" " + tr("Details")); mUpdatesDetailsRemove->setToolTip(tr("Show details")); mUpdatesDetailsRemove->setStyleSheet("font-size: 10px;"); mUpdatesDetailsRemove->setFixedHeight(22); mUpdatesDetailsRemove->setIcon(QIcon(":/img/dialog-information_16px.png")); connect(mUpdatesDetailsRemove, SIGNAL(clicked()), this, SLOT(toggleUpdatesRemove())); updatesRemoveLayout->addWidget(mUpdatesRemoveCertificates); updatesRemoveLayout->addWidget(mUpdatesDetailsRemove); updatesRemoveLayout->addStretch(1); mUpdatesRemove = new CertificateListWidget(this); connect(mUpdatesRemove, SIGNAL(certListChanged(int)), this, SLOT(listChanged(int))); mUpdatesRemove->hide(); mUpdatesManualCertificates = new QLabel(QString()); mUpdatesDetailsManual = new QPushButton(); mUpdatesDetailsManual->setText(" " + tr("Details")); mUpdatesDetailsManual->setToolTip(tr("Show details")); mUpdatesDetailsManual->setStyleSheet("font-size: 10px;"); mUpdatesDetailsManual->setFixedHeight(22); mUpdatesDetailsManual->setIcon(QIcon(":/img/dialog-information_16px.png")); connect(mUpdatesDetailsManual, SIGNAL(clicked()), this, SLOT(toggleUpdatesManual())); mUpdatesDetailsManual->hide(); updatesManualLayout->addWidget(mUpdatesManualCertificates); updatesManualLayout->addWidget(mUpdatesDetailsManual); updatesManualLayout->addStretch(1); mUpdatesManual = new CertificateListWidget(this); mUpdatesManual->hide(); connect(mUpdatesManual, SIGNAL(certChanged(bool, const Certificate&)), this, SLOT(removeFromManual(bool, const Certificate&))); connect(mUpdatesManual, SIGNAL(certListChanged(int)), this, SLOT(listChanged(int))); updatesNewLayout->setAlignment(Qt::AlignTop); updatesRemoveLayout->setAlignment(Qt::AlignTop); updatesManualLayout->setAlignment(Qt::AlignTop); updatesCenterLayout->addLayout(updatesNewLayout); updatesCenterLayout->addWidget(mUpdatesNew); updatesCenterLayout->addLayout(updatesRemoveLayout); updatesCenterLayout->addWidget(mUpdatesRemove); updatesCenterLayout->addSpacing(10); updatesCenterLayout->addLayout(updatesManualLayout); updatesCenterLayout->addWidget(mUpdatesManual); QWidget *dummyWidget = new QWidget; dummyWidget->setLayout(updatesCenterLayout); centralScrollArea->setWidgetResizable(true); centralScrollArea->setWidget(dummyWidget); centralScrollArea->setFrameShape(QFrame::NoFrame); centralScrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); updatesMainLayout->addWidget(centralScrollArea); updatesCenterLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); theWidget->setLayout(updatesMainLayout); return theWidget; } QWidget *MainWindow::createInstallWidget() { QWidget *theWidget = new QWidget; QScrollArea *scrollArea = new QScrollArea; QVBoxLayout *installPanelLayout = new QVBoxLayout; QVBoxLayout *installHeaderLayout = new QVBoxLayout; QVBoxLayout *installCenterLayout = new QVBoxLayout; QLabel *installHeaderLabel = new QLabel("<h2>" + tr("Trusted certificates") + "</h2>"); QLabel *installHeaderText = new QLabel(tr("The following list of trusted root" " certificates is managed by the BSI. The BSI validates independently the" " authenticity, security and actuality of these certificates.")); installHeaderText->setWordWrap(true); installHeaderLayout->addWidget(installHeaderLabel); installHeaderLayout->addWidget(installHeaderText); QLabel *installCenterText = new QLabel(tr("Please choose the certificates" " you want to trust or untrust. TrustBridge will install these certificates for your" " secure communication for email and internet.")); installCenterText->setWordWrap(true); installCenterLayout->addWidget(installCenterText); installPanelLayout->addLayout(installHeaderLayout); installPanelLayout->addLayout(installCenterLayout); mInstallList = new CertificateListWidget(this); connect(mInstallList, SIGNAL(certChanged(bool, const Certificate&)), this, SLOT(toggleInManual(bool, const Certificate&))); scrollArea->setWidgetResizable(true); scrollArea->setWidget(mInstallList); scrollArea->setFrameShape(QFrame::NoFrame); installPanelLayout->addWidget(scrollArea); theWidget->setLayout(installPanelLayout); return theWidget; } QWidget *MainWindow::createRemoveWidget() { QWidget * theWidget = new QWidget; QScrollArea *scrollArea = new QScrollArea; QVBoxLayout *removePanelLayout = new QVBoxLayout; QVBoxLayout *removeHeaderLayout = new QVBoxLayout; QVBoxLayout *removeCenterLayout = new QVBoxLayout; QLabel *removeHeaderLabel = new QLabel("<h2>" + tr("Revoked certificates") + "</h2>"); QLabel *removeHeaderText = new QLabel(tr("Certificates can be corrupted" " or stolen and misused in many ways. Therefore the BSI recommends" " to remove all revoked certificates from your system.")); removeHeaderText->setWordWrap(true); removeHeaderLayout->addWidget(removeHeaderLabel); removeHeaderLayout->addWidget(removeHeaderText); QLabel *removeCenterText = new QLabel(tr("The following unsecure certificates were" " revoked by the BSI. Already uninstalled certificates cannot be reinstalled." " It is recommended that you select all certificates to uninstall if you still" " have revoked certificates installed.")); removeCenterText->setWordWrap(true); removeCenterLayout->addWidget(removeCenterText); mRemoveList = new CertificateListWidget(this); connect(mRemoveList, SIGNAL(certChanged(bool, const Certificate&)), this, SLOT(toggleInManual(bool, const Certificate&))); removePanelLayout->addLayout(removeHeaderLayout); removePanelLayout->addLayout(removeCenterLayout); scrollArea->setWidgetResizable(true); scrollArea->setWidget(mRemoveList); scrollArea->setFrameShape(QFrame::NoFrame); removePanelLayout->addWidget(scrollArea); theWidget->setLayout(removePanelLayout); return theWidget; } void MainWindow::setupGUI() { // Create a central widget containing the main layout. QWidget *base = new QWidget; // Layouts and Container QVBoxLayout *mainLayout = new QVBoxLayout; QHBoxLayout *headerLayout = new QHBoxLayout; QVBoxLayout *headerTextLayout = new QVBoxLayout; QHBoxLayout *headerSubtitleLayout = new QHBoxLayout; QHBoxLayout *centerLayout = new QHBoxLayout; QVBoxLayout *buttonBarLayout = new QVBoxLayout; QHBoxLayout *bottomLayout = new QHBoxLayout; QHBoxLayout *containerLayout = new QHBoxLayout; // The header (icon, about text) QImage *logoImage = new QImage(":/img/logo.png"); QLabel *logo = new QLabel; logo->setBackgroundRole(QPalette::Base); logo->setPixmap(QPixmap::fromImage(*logoImage)); QLabel *title = new QLabel("<h1>" + QString::fromLatin1(APPNAME) + "</h1>"); QLabel *subTitle = new QLabel(tr("Trust in your digital communication")); QLabel *swVersion = new QLabel(QString::fromLatin1("<i>") + tr("Version") + " " + QApplication::applicationVersion() + QString::fromLatin1(" </i>")); swVersion->setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); swVersion->setTextFormat(Qt::RichText); headerSubtitleLayout->addWidget(subTitle); headerSubtitleLayout->addStretch(-1); headerSubtitleLayout->addWidget(swVersion); headerTextLayout->addWidget(title); headerTextLayout->addLayout(headerSubtitleLayout); headerLayout->addWidget(logo); headerLayout->addLayout(headerTextLayout); headerLayout->setStretch(0, 0); headerLayout->setStretch(1, 10); /*********************************** * The Buttonbar on the left side. ***********************************/ mButtonGroup = new QButtonGroup; TextOverlayButton *updatesButton = new TextOverlayButton; updatesButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); updatesButton->setBackgroundIcon(":/img/red-circle.png"); updatesButton->setIcon(QIcon(":/img/pending-changes-overview-48.png")); updatesButton->setIconSize(QSize(48, 48)); updatesButton->setText(tr("Pending\nchanges")); updatesButton->setFixedWidth(120); updatesButton->setFixedHeight(90); updatesButton->setCheckable(true); updatesButton->setChecked(true); connect(this, SIGNAL(changesChanged(const QString&)), updatesButton, SLOT(setOverlay(const QString&))); QToolButton *allInstallButton = new QToolButton; allInstallButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); allInstallButton->setIcon(QIcon(":/img/document-encrypt.png")); allInstallButton->setIconSize(QSize(48, 48)); allInstallButton->setText(tr("Trusted\ncertificates")); allInstallButton->setFixedWidth(120); allInstallButton->setFixedHeight(90); allInstallButton->setCheckable(true); QToolButton *allRemoveButton = new QToolButton; allRemoveButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); allRemoveButton->setIcon(QIcon(":/img/dialog-warning.png")); allRemoveButton->setIconSize(QSize(48, 48)); allRemoveButton->setText(tr("Revoked\ncertificates")); allRemoveButton->setFixedWidth(120); allRemoveButton->setFixedHeight(90); allRemoveButton->setCheckable(true); QToolButton *infoButton = new QToolButton; infoButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); infoButton->setIcon(QIcon(":/img/dialog-information.png")); infoButton->setIconSize(QSize(48, 48)); infoButton->setText(tr("Information\nand help")); infoButton->setFixedWidth(120); infoButton->setFixedHeight(90); infoButton->setCheckable(true); mButtonGroup->addButton(updatesButton); mButtonGroup->addButton(allInstallButton); mButtonGroup->addButton(allRemoveButton); mButtonGroup->addButton(infoButton); mButtonGroup->setId(updatesButton, 0); mButtonGroup->setId(allInstallButton, 1); mButtonGroup->setId(allRemoveButton, 2); mButtonGroup->setId(infoButton, 3); connect(mButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(togglePages(int))); buttonBarLayout->addWidget(updatesButton); buttonBarLayout->addWidget(allInstallButton); buttonBarLayout->addWidget(allRemoveButton); buttonBarLayout->insertStretch(3, 10); buttonBarLayout->addWidget(infoButton); /* The main pages.*/ /* The updates page. */ mUpdatesWidget = createUpdatesWidget(); /* Install (trusted certs) Page */ mInstallWidget = createInstallWidget(); /********************************** * Page for certificates to be removed. **********************************/ mRemoveWidget = createRemoveWidget(); /********************************** * The info page. **********************************/ mInfoWidget = createInfoWidget(); /******************************** * The main layout for pages. ********************************/ mInstallWidget->hide(); mRemoveWidget->hide(); mInfoWidget->hide(); containerLayout->addWidget(mUpdatesWidget); containerLayout->addWidget(mInstallWidget); containerLayout->addWidget(mRemoveWidget); containerLayout->addWidget(mInfoWidget); centerLayout->addLayout(buttonBarLayout); centerLayout->addLayout(containerLayout); mainLayout->addLayout(headerLayout); mainLayout->addLayout(centerLayout); mainLayout->addLayout(bottomLayout); base->setLayout(mainLayout); setCentralWidget(base); } void MainWindow::listChanged(int selected) { Q_UNUSED (selected); setChangeCount(mUpdatesRemove->selectedCertCount() + mUpdatesNew->selectedCertCount() + mUpdatesManual->certificates().size()); /* Show a different tip in case of manual changes, updates aviailable, updates and manual * changes available */ if (changeCount() && !mUpdatesManual->certificates().size()) { mUpdatesTip->setText( tr("You should apply the following, recommended changes to your certificate stores:")); } else { mUpdatesTip->setText(""); } if (!changeCount()) { /* No changes */ mQuitButton->setText(" " + tr("Quit")); mUpdatesHeader->setText("<h2>" + tr("Certificates unchanged") + "</h2>"); mInstallButton->setText(" " + tr("Install certificates again")); } else { mQuitButton->setText(" " + tr("Quit without saving")); mUpdatesHeader->setText("<h2>" + tr("Pending changes (%1)") .arg(changeCount()) + "</h2>"); mInstallButton->setText(" " + tr("Apply changes")); } if (mUpdatesManual->certificates().size()) { mUpdatesDetailsManual->show(); if (mManualDetailsShown) { mUpdatesManual->show(); deactivateDetailsButton(mUpdatesDetailsManual); } else { activateDetailsButton(mUpdatesDetailsManual); } } else { mUpdatesDetailsManual->hide(); mUpdatesManual->hide(); } mUpdatesManualCertificates->setText("<h2>" + tr("Manual changes (%1)").arg(mUpdatesManual->certificates().size()) + "</h2>"); if (mUpdatesNew->certificates().size()) { mUpdatesNewCertificates->setText("<h3>" + tr("Install new trusted certificates (%1/%2)") .arg(mUpdatesNew->selectedCertCount()) .arg(mUpdatesNew->certificates().size()) + "</h3>"); if (!mUpdatesNew->isVisible()) { activateDetailsButton(mUpdatesDetailsNew); } mUpdatesNewCertificates->show(); } else { mUpdatesDetailsNew->hide(); mUpdatesNew->hide(); mUpdatesNewCertificates->hide(); } if (mUpdatesRemove->certificates().size()) { mUpdatesRemoveCertificates->setText("<h3>" + tr("Remove revoked certificates (%1/%2)") .arg(mUpdatesRemove->selectedCertCount()) .arg(mUpdatesRemove->certificates().size()) + "</h3>"); mUpdatesRemoveCertificates->show(); if (!mUpdatesRemove->isVisible()) { activateDetailsButton(mUpdatesDetailsRemove); } } else { mUpdatesRemoveCertificates->hide(); mUpdatesDetailsRemove->hide(); mUpdatesRemove->hide(); } /* Update the details header */ if (mUpdatesRemove->certificates().size() || mUpdatesNew->certificates().size()) { mUpdatesDetailsHeader->setText("<h2>" + tr("New, recommended changes (%1/%2)") .arg(mUpdatesRemove->selectedCertCount() + mUpdatesNew->selectedCertCount()) .arg(mUpdatesRemove->certificates().size() + mUpdatesNew->certificates().size()) + "</h2>"); } else { mUpdatesDetailsHeader->setText(QString::fromLatin1("<h2>") + tr("No new recommendations") + QString::fromLatin1("</h2>")); } if (mListToInstall.isValid()) { mCertListVersion->setText(tr("Certificate list from:")); mCertListVersionContents->setText(QLocale::system().toString( mListToInstall.date().toLocalTime(), DATETIME_FORMAT)); } else { if (mInstalledList.isValid()) { mCertListVersion->setText(tr("Currently installed certificate list:")); mCertListVersionContents->setText(QLocale::system().toString( mInstalledList.date().toLocalTime(), DATETIME_FORMAT)); } else { mCertListVersion->setText(tr("No certificate list installed.")); mCertListVersionContents->setText(""); } } } void MainWindow::loadCertificateList() { /* TODO (issue134): if nothing is available (neither old nor new) add some progress * indication */ setUpdatesEnabled(false); mInstallList->clear(); mRemoveList->clear(); mUpdatesNew->clear(); mUpdatesRemove->clear(); QList<Certificate> newInstallCerts; QList<Certificate> newRemoveCerts; QList<Certificate> oldInstallCerts; QList<Certificate> oldRemoveCerts; if (mListToInstall.getCertificates().isEmpty()) { // No new list available, add old certificates. foreach (const Certificate &cert, mInstalledList.getCertificates()) { bool state = !mPreviouslyUnselected.contains(cert.base64Line()); if (cert.isInstallCert()) { oldInstallCerts.append(cert); CheckLessToolBtn* actionBtn = new CheckLessToolBtn(); QIcon btnIcon; if (!state) { btnIcon.addFile(":/img/cert-to-be-installed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off); btnIcon.addFile(":/img/cert-not-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On); actionBtn->setProperty("ToolTip_Off", tr("Certificate will be installed.")); actionBtn->setProperty("ToolTip_On", tr("Certifcate is not installed.")); } else { actionBtn->setProperty("ToolTip_Off", tr("Certificate is installed.")); actionBtn->setProperty("ToolTip_On", tr("Certificate will be removed.")); btnIcon.addFile(":/img/cert-is-installed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off); btnIcon.addFile(":/img/cert-to-be-removed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On); } actionBtn->setIcon(btnIcon); mInstallList->addCertificate(cert, state, actionBtn); } else { oldRemoveCerts.append(cert); CheckLessToolBtn* actionBtn = new CheckLessToolBtn(); QIcon btnIcon; actionBtn->setProperty("ToolTip_Off", tr("Certificate will be removed.")); actionBtn->setProperty("ToolTip_On", tr("Certificate has not been removed.")); btnIcon.addFile(":/img/cert-to-be-removed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off); btnIcon.addFile(":/img/cert-is-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On); btnIcon.addFile(":/img/cert-not-installed-good-48.png", QSize(48, 48), QIcon::Disabled, QIcon::Off); actionBtn->setIcon(btnIcon); if (state) { actionBtn->setEnabled(false); actionBtn->setToolTip(tr("Certificate has been removed.")); } mRemoveList->addCertificate(cert, state, actionBtn); } } } else { // Sort and filter both lists. foreach (const Certificate &cert, mListToInstall.getCertificates()) { bool state = !mPreviouslyUnselected.contains(cert.base64Line()); if (cert.isInstallCert()) { // Certificate with status "install". if (mInstalledList.getCertificates().contains(cert)) { // Was in the old list. oldInstallCerts.append(cert); CheckLessToolBtn* actionBtn = new CheckLessToolBtn(); QIcon btnIcon; actionBtn->setProperty("ToolTip_Off", tr("Certificate is installed.")); actionBtn->setProperty("ToolTip_On", tr("Certifcate is not installed.")); btnIcon.addFile(":/img/cert-is-installed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off); btnIcon.addFile(":/img/cert-not-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On); actionBtn->setIcon(btnIcon); mInstallList->addCertificate(cert, state, actionBtn); } else { // Is a brand new certificate newInstallCerts.append(cert); CheckLessToolBtn* actionBtn = new CheckLessToolBtn(); QIcon btnIcon; actionBtn->setProperty("ToolTip_Off", tr("Certificate will be installed.")); actionBtn->setProperty("ToolTip_On", tr("Certificate will not be installed.")); btnIcon.addFile(":/img/cert-to-be-installed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off); btnIcon.addFile(":/img/cert-not-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On); actionBtn->setIcon(btnIcon); mUpdatesNew->addCertificate(cert, state, actionBtn); } } else { // Certificate with status "remove". if (mInstalledList.getCertificates().contains(cert)) { // Was in the old list. oldRemoveCerts.append(cert); // Is removed, so set editable to false. CheckLessToolBtn* actionBtn = new CheckLessToolBtn(); QIcon btnIcon; actionBtn->setProperty("ToolTip_Off", tr("Certificate will be removed.")); actionBtn->setProperty("ToolTip_On", tr("Certificate has not been removed.")); btnIcon.addFile(":/img/cert-to-be-removed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off); btnIcon.addFile(":/img/cert-is-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On); btnIcon.addFile(":/img/cert-not-installed-good-48.png", QSize(48, 48), QIcon::Disabled, QIcon::Off); actionBtn->setIcon(btnIcon); if (state) { actionBtn->setEnabled(false); actionBtn->setToolTip(tr("Certificate has been removed.")); } mRemoveList->addCertificate(cert, state, actionBtn); } else { // Was in the old list with status "install" and now has the // status "remove". newRemoveCerts.append(cert); CheckLessToolBtn* actionBtn = new CheckLessToolBtn(); QIcon btnIcon; actionBtn->setProperty("ToolTip_Off", tr("Certificate will be removed.")); actionBtn->setProperty("ToolTip_On", tr("Certificate will not be removed.")); btnIcon.addFile(":/img/cert-to-be-removed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off); btnIcon.addFile(":/img/cert-is-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On); actionBtn->setIcon(btnIcon); mUpdatesRemove->addCertificate(cert, state, actionBtn); } } } } listChanged(0); setUpdatesEnabled(true); } void MainWindow::installerError(const QString& errMsg) { QMessageBox::warning(this, tr("Error executing update"), errMsg); } void MainWindow::installerSuccess() { if (mCurState == NewListAvailable ) { mCurState = NothingChanged; mCurMessage = QString(); } QString listFileName = mSettings.value("List/available").toString(); QDateTime listFileDate = mSettings.value("List/availableDate").toDateTime(); if (!listFileName.isEmpty() && listFileDate.isValid()) { mSettings.remove("List/available"); mSettings.remove("List/availableDate"); /* Rename the installed list to list-installed.txt so that external * programs (like the uninstaller can easily recognize it). */ QString dataLoc = QStandardPaths::writableLocation(QStandardPaths::DataLocation); QDir dataDir(dataLoc); if (!dataDir.exists()) { /* Should not happen */ qWarning() << "Data dir removed."; return; } QFileInfo oldList (dataDir.absoluteFilePath("list-installed.txt")); if (oldList.exists()) { qDebug() << "Removing old list: " << oldList.filePath(); if (!QFile::remove(oldList.filePath())) { qWarning() << "Removal of old list failed."; return; } } QFile newList(listFileName); if (!newList.rename(oldList.filePath())) { qWarning() << "Failed to rename new list."; return; } mSettings.setValue("List/installed", oldList.filePath()); mSettings.setValue("List/installedDate", listFileDate); mInstalledList = CertificateList(oldList.filePath().toUtf8().constData()); if (!mInstalledList.isValid()) { /* Something went wrong. Go back to square one. */ qWarning () << "List corrupted after installation"; mInstalledList = CertificateList(); QFile::remove(oldList.filePath()); mSettings.remove("List/installed"); mSettings.remove("List/installedDate"); } } mListToInstall = CertificateList(); mUpdatesManual->clear(); loadCertificateList(); } void MainWindow::installCerts() { QStringList choices; QStringList unselected; choices << mUpdatesNew->selectedCertificates(); choices << mUpdatesRemove->selectedCertificates(); choices << mUpdatesManual->unselectedCertificates(); /* Also include the old certificates */ choices << mInstallList->selectedCertificates(); choices << mRemoveList->selectedCertificates(); QStringList selectedManuals = mUpdatesManual->selectedCertificates(); for(int i = 0; i < selectedManuals.size(); i++) { if (selectedManuals.at(i).startsWith("I:")) { QString certLine = selectedManuals.at(i); certLine[0] = 'R'; choices << certLine; } } unselected << mUpdatesNew->unselectedCertificates(); unselected << mUpdatesRemove->unselectedCertificates(); unselected << mInstallList->unselectedCertificates(); unselected << mRemoveList->unselectedCertificates(); #ifdef Q_OS_WIN if (!is_system_install()) { QMessageBox::warning(this, tr("Installation with standard user account"), tr("Windows will now ask you to confirm each root certificate modification " "because TrustBridge does not have the necessary privileges to install " "root certificates into the Windows certificate store silently.")); } #endif QProgressDialog *progress = new QProgressDialog(this); progress->setWindowModality(Qt::WindowModal); progress->setLabelText(tr("Installing certificates...")); progress->setCancelButton(0); progress->setRange(0,0); progress->setMinimumDuration(0); progress->show(); CertificateList *instList = mListToInstall.isValid() ? &mListToInstall : &mInstalledList; InstallWrapper *instWrap = new InstallWrapper(this, instList->fileName(), choices); syslog_info_printf ("Installing certificate list: '%s' Version '%s'\n", instList->fileName().toUtf8().constData(), instList->date().toString().toUtf8().constData()); /* Clean up object and progress dialog */ connect(instWrap, SIGNAL(finished()), instWrap, SLOT(deleteLater())); connect(instWrap, SIGNAL(finished()), progress, SLOT(deleteLater())); connect(instWrap, SIGNAL(finished()), progress, SLOT(cancel())); connect(instWrap, SIGNAL(installationSuccessful()), this, SLOT(installerSuccess())); connect(instWrap, SIGNAL(error(const QString &)), this, SLOT(installerError(const QString &))); instWrap->start(); if (!saveUnselectedCertificates(unselected)) { qWarning() << "Failed to save previosly unselected certificates."; } } void MainWindow::loadUnselectedCertificates() { mPreviouslyUnselected.clear(); mSettings.beginGroup("unselected"); QStringList keys = mSettings.allKeys(); foreach (const QString &key, keys) { mPreviouslyUnselected << mSettings.value(key, QString()).toString(); } mSettings.endGroup(); } bool MainWindow::saveUnselectedCertificates(QStringList unselected) { mPreviouslyUnselected.clear(); mSettings.beginGroup("unselected"); mSettings.remove(""); /* Clears old choices */ for (int i = 0; i < unselected.size(); i++) { QString key = QString::fromLatin1("cert%1").arg(i); QString value = unselected.at(i); mSettings.setValue(key, value); mPreviouslyUnselected << value; } mSettings.endGroup(); mSettings.sync(); return mSettings.status() == QSettings::NoError; } void MainWindow::toggleInManual(bool state, const Certificate &cert) { if (!mUpdatesManual->contains(cert)) { QToolButton* actionBtn = new QToolButton(); QIcon btnIcon; if (mRemoveList->contains(cert)) { btnIcon.addFile(":/img/cert-to-be-removed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::On); btnIcon.addFile(":/img/cert-is-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off); actionBtn->setProperty("ToolTip_On", tr("Certificate will be removed.")); /* Off should never be possible here * As the manual change of removed certificates is disabled */ actionBtn->setProperty("ToolTip_Off", tr("Certificate will not be removed.")); } else { btnIcon.addFile(":/img/cert-to-be-installed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::On); btnIcon.addFile(":/img/cert-to-be-removed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off); actionBtn->setProperty("ToolTip_On", tr("Certificate will be installed.")); actionBtn->setProperty("ToolTip_Off", tr("Certificate will be removed.")); } actionBtn->setIcon(btnIcon); mUpdatesManual->addCertificate(cert, state, actionBtn); } else { mUpdatesManual->removeCertificate(cert); } } void MainWindow::removeFromManual(bool state, const Certificate &cert) { mUpdatesManual->removeCertificate(cert); if (cert.isInstallCert()) { mInstallList->setCertState(state, cert); } else { mRemoveList->setCertState(state, cert); } } void MainWindow::closeApp() { ProcessHelp::cleanUp(); qApp->quit(); } void MainWindow::checkAndInstallCerts() { #ifdef WIN32 /* Checking before opening the dialog should be cheaper */ QList<int> pids = ProcessHelp::getProcessesIdForName("firefox"); pids.append(ProcessHelp::getProcessesIdForName("thunderbird")); if (pids.isEmpty()) { installCerts(); return; } QStringList pNames; pNames << "firefox" << "thunderbird"; ProcessWaitDialog *waitDialog = new ProcessWaitDialog(this, pNames); connect(waitDialog, SIGNAL(accepted()), this, SLOT(installCerts())); connect(waitDialog, SIGNAL(accepted()), waitDialog, SLOT(deleteLater())); waitDialog->exec(); return; #else /* On GNU Linux finding all firefox / thunderbird instances which * could be found on the filesystem is more difficult. So * for now we just show a generic warning dialog. */ QMessageBox* warnMessage = new QMessageBox (QMessageBox::Information, tr("Firefox and Thunderbird certificate installation."), tr("Please close all running Firefox and Thunderbird instances " "before continuing with the installation."), QMessageBox::Ok | QMessageBox::No, this); warnMessage->button(QMessageBox::Ok)->setText(tr("Continue")); warnMessage->setDefaultButton(QMessageBox::Ok); warnMessage->button(QMessageBox::No)->setText(tr("Cancel")); warnMessage->setWindowIcon(windowIcon()); warnMessage->setIconPixmap(QIcon(":/img/dialog-warning.png").pixmap(QSize(48,48))); connect(warnMessage->button(QMessageBox::Ok), SIGNAL(clicked()), this, SLOT(installCerts())); warnMessage->exec(); return; #endif } void MainWindow::togglePages(int button) { mUpdatesWidget->hide(); mInstallWidget->hide(); mRemoveWidget->hide(); mInfoWidget->hide(); switch(button) { case 0: mUpdatesWidget->show(); break; case 1: mInstallWidget->show(); break; case 2: mRemoveWidget->show(); break; case 3: mInfoWidget->show(); break; default: mUpdatesWidget->show(); break; } return; } static void deactivateDetailsButton(QPushButton *btn) { btn->setToolTip(QObject::tr("Hide details")); btn->setText(" " + QObject::tr("Less")); btn->setIcon(QIcon(":/img/dialog-information_grey_16px.png")); } static void activateDetailsButton(QPushButton *btn) { btn->setToolTip(QObject::tr("Show details")); btn->setText(" " + QObject::tr("Details")); btn->setIcon(QIcon(":/img/dialog-information_16px.png")); btn->show(); } void MainWindow::toggleUpdatesNew() { if (!mUpdatesNew->isVisible()) { mUpdatesNew->show(); deactivateDetailsButton(mUpdatesDetailsNew); } else { mUpdatesNew->hide(); activateDetailsButton(mUpdatesDetailsNew); } } void MainWindow::toggleUpdatesRemove() { if (!mUpdatesRemove->isVisible()) { mUpdatesRemove->show(); deactivateDetailsButton(mUpdatesDetailsRemove); } else { mUpdatesRemove->hide(); activateDetailsButton(mUpdatesDetailsRemove); } } void MainWindow::toggleUpdatesManual() { if (!mUpdatesManual->isVisible()) { mUpdatesManual->show(); mManualDetailsShown = true; deactivateDetailsButton(mUpdatesDetailsManual); } else { mUpdatesManual->hide(); mManualDetailsShown = false; activateDetailsButton(mUpdatesDetailsManual); } } void MainWindow::closeEvent(QCloseEvent *event) { if (getState() == NewListAvailable) { /* Only minimize to tray if there is a new list */ QMainWindow::closeEvent(event); mTrayIcon->show(); return; } return closeApp(); } void MainWindow::scheduleFailureRetryOrClose(bool close) { int waitInterval = getNextUpdateInterval(mFailedConnections++); if (waitInterval <= 0 && close) { qDebug() << "Shutting down after " << mFailedConnections << " tries to get an update connection."; closeApp(); } else if (waitInterval > 0) { qDebug() << "Waiting for " << waitInterval / 1000 << " seconds until the next try."; QTimer::singleShot(waitInterval, this, SLOT(checkUpdates())); } } void MainWindow::updateCheckSuccess() { if (getState() != TransferError) { const QDateTime now = QDateTime::currentDateTime(); mSettings.setValue("lastUpdateCheck", now); mLastUpdateCheckContents->setText(QLocale::system().toString(now, DATETIME_FORMAT)); mLastUpdateCheckContents->show(); mLastUpdateCheck->show(); syslog_info_printf(tr("Sucessfully checked for updates.").toUtf8().constData()); handleLTE(lteNoConnection, true); /* Reset error state */ handleLTE(lteInvalidCertificate, true); mFailedConnections = 0; } else { scheduleFailureRetryOrClose(!isVisible()); } if ((getState() != NewSoftwareAvailable && getState() != NewListAvailable && mTrayMode) && !isVisible()) { qDebug() << "Shutting down as no list or Software is available."; closeApp(); } else { mTrayIcon->show(); } } int MainWindow::changeCount() { return mChangeCount; } void MainWindow::setChangeCount(int cnt) { if (mChangeCount != cnt) { mChangeCount = cnt; emit changesChanged(QString("%1").arg(cnt)); } } void MainWindow::showProxySettings() { ProxySettingsDlg *dlg = new ProxySettingsDlg(this); dlg->exec(); } void MainWindow::showHelp() { char *inst_dir = get_install_dir(); if (!inst_dir) { qDebug() << "Failed to find install dir"; return; } QString helpPath = QString::fromUtf8(inst_dir); helpPath += HELP_PATH; QFileInfo fiHelp(helpPath); qDebug() << "Opening help: " << fiHelp.absoluteFilePath(); if (!fiHelp.exists()) { QMessageBox::warning(this, tr("Error!"), tr ("Failed to find the manual")); return; } #ifdef Q_OS_WIN QDesktopServices::openUrl(QUrl("file:///" + fiHelp.absoluteFilePath())); #else QDesktopServices::openUrl(QUrl(fiHelp.absoluteFilePath())); #endif free (inst_dir); return; } void MainWindow::showErrorMessage(const QString &msg) { QMessageBox::warning(this, tr("TrustBridge error"), msg); } void MainWindow::handleLTE(LongTimeErrors lte, bool reset) { QString settingPrefix; // qDebug() << "Handle LTE for " << lte << " Reset? : " << reset; switch (lte) { case lteInvalidSoftware: settingPrefix = "LTE/invalidSW"; break; case lteInvalidList: settingPrefix = "LTE/invalidList"; break; case lteInvalidCertificate: settingPrefix = "LTE/invalidCertificate"; break; case lteNoConnection: settingPrefix = "LTE/noConnection"; break; default: qDebug() << "Unhandled error. " << lte; } if (reset) { /* delete all values and be done */ mSettings.remove(settingPrefix + "_lastSaved"); mSettings.remove(settingPrefix + "_count"); mSettings.remove(settingPrefix + "_lastShown"); return; } QDateTime lastSaved = mSettings.value(settingPrefix + "_lastSaved").toDateTime(); bool cnt_valid; int cnt = mSettings.value(settingPrefix + "_count").toInt(&cnt_valid); if (!cnt_valid) { cnt = 0; } if (!lastSaved.isValid() || lastSaved.daysTo(QDateTime::currentDateTime()) >= 1) { /* The error count is increased at most once a day */ mSettings.setValue(settingPrefix + "_lastSaved", QDateTime::currentDateTime()); mSettings.setValue(settingPrefix + "_count", ++cnt); } if (cnt < 7) { /* We are done */ return; } /* A week has passed. Start showing the error. */ QDateTime lastShown = mSettings.value(settingPrefix + "_lastShown").toDateTime(); if (lastShown.isValid() && lastShown.daysTo(QDateTime::currentDateTime()) < 1) { /* Only show the error message once a day */ return; } mSettings.setValue(settingPrefix + "_lastShown", QDateTime::currentDateTime()); switch (lte) { case lteInvalidSoftware: showErrorMessage(tr("The integrity check for the available software update has " "failed repeatedly.") + "\n" + tr("Please contact your Support or the publisher of the Software.")); break; case lteInvalidList: showErrorMessage(tr("The integrity check of the available certificates has " "failed repeatedly.") + "\n" + tr("Please contact your Support or the publisher of the Software.")); break; case lteInvalidCertificate: showErrorMessage(tr("The authentication of the update server has " "failed repeatedly.") + "\n" + tr("Please contact your Support or the publisher of the Software.")); break; case lteNoConnection: bool useProxy = mSettings.value("UseProxy", false).toBool(); if (useProxy) { showErrorMessage(tr("The connection to the update server has " "failed repeatedly.") + "\n" + tr("Please check that the Proxy Server \"%1\" is available.").arg( mSettings.value("ProxyURL").toString())); } else { showErrorMessage(tr("The connection to the update server has " "failed repeatedly.") + "\n" + tr("Please check your internet connection.")); } break; } }