changeset 399:55cbe0a482ce

merged.
author Raimund Renkert <rrenkert@intevation.de>
date Wed, 16 Apr 2014 10:01:02 +0200
parents 9e6a2c2033ed (current diff) ae2ef965a41b (diff)
children d481b1d0956f
files packaging/renameme_installer.ico
diffstat 18 files changed, 279 insertions(+), 76 deletions(-) [+]
line wrap: on
line diff
--- a/cinst/main.c	Wed Apr 16 10:00:17 2014 +0200
+++ b/cinst/main.c	Wed Apr 16 10:01:02 2014 +0200
@@ -9,18 +9,18 @@
  *
  *  The first parameter to this process should be list=<file_name>
  *  of the certificate list to work on. The second parameter should
- *  be instruction=<instruction_file_name>|uninstall
+ *  be choices=<choices_file_name>|uninstall
  *
- *  instruction_file_name should be the absolute path to an
- *  instructions file formatted as:
+ *  choices_file_name should be the absolute path to an
+ *  choices file formatted as:
  *
  *  I:<certificate>
  *  R:<certificate>
  *
- *  Line breaks can be system dependent in the Instructions file.
+ *  Line breaks can be system dependent in the Choices file.
  *
- *  It will only execute the instructions if the
- *  I and R instructions are also part of the signed
+ *  It will only execute the choices if the
+ *  I and R choices are also part of the signed
  *  certificate list. The signature is validated with the
  *  built in key.
  *
@@ -42,17 +42,17 @@
 #include "windowsstore.h"
 #include "nssstore.h"
 
-/* The certificate list + instructions may only be so long as
+/* The certificate list + choices may only be so long as
  * twice the accepted certificatelist size */
 #define MAX_INPUT_SIZE MAX_LINE_LENGTH * MAX_LINES * 2
 
 /* @brief Read stdin into data structures.
  *
- * Reads instructions from an input file into the to_install
+ * Reads choices from an input file into the to_install
  * and to_remove buffers.
  *
- * Lines starting with I: are treated as install instructions.
- * Lines starting with R: are treated as remove instructions.
+ * Lines starting with I: are treated as install choices.
+ * Lines starting with R: are treated as remove choices.
  * Other lines are ignored.
  *
  * Terminates in OOM conditions.
@@ -60,14 +60,14 @@
  * The caller needs to free the memory allocated by this function
  * even when an error is returned.
  *
- * @param[in] file_name absolute path to the instructions file.
- * @param[out] to_install strv of installation instructions or NULL
- * @param[out] to_remove strv of remove instructions or NULL
+ * @param[in] file_name absolute path to the choices file.
+ * @param[out] to_install strv of installation choices or NULL
+ * @param[out] to_remove strv of remove choices or NULL
  *
  * @returns: 0 on success. An error code otherwise.
  */
 static int
-read_instructions_file (char *file_name, char ***to_install,
+read_choices_file (char *file_name, char ***to_install,
                         char ***to_remove)
 {
   int lines_read = 0;
@@ -144,12 +144,12 @@
  * for installation.
  *
  * @param[in] all_certs strv of all valid certificates in a list
- * @param[in] to_validate strv of instructions
+ * @param[in] to_validate strv of choices
  *
  * @returns 0 on success, an error otherwise
  */
 int
-validate_instructions (char **all_certs, char **to_validate)
+validate_choices (char **all_certs, char **to_validate)
 {
   int i = 0, j = 0;
 
@@ -199,38 +199,38 @@
 
   char *certificate_list = NULL,
        *certificate_file_name = NULL,
-       *instruction_file_name = NULL;
+       *choices_file_name = NULL;
   size_t list_len = 0;
   list_status_t list_status;
   bool do_uninstall = false;
 
-  /* Some very static argument parsing. list= and instructions= is only
+  /* Some very static argument parsing. list= and choices= is only
      added to make it more transparent how this programm is called if
      a user looks at the detailed uac dialog. */
   if (argc != 3 || strncmp(argv[1], "list=", 5) != 0 ||
-                   strncmp(argv[2], "instructions=", 13) != 0)
+                   strncmp(argv[2], "choices=", 8) != 0)
     {
       ERRORPRINTF ("Invalid arguments.\n"
                    "Expected arguments: list=<certificate_list> \n"
-                   "                    instructions=<instructions_file>|uninstall\n");
+                   "                    choices=<choices_file>|uninstall\n");
       return ERR_INVALID_PARAMS;
     }
 
   certificate_file_name = strchr(argv[1], '=') + 1;
-  instruction_file_name = strchr(argv[2], '=') + 1;
+  choices_file_name = strchr(argv[2], '=') + 1;
 
-  if (!certificate_file_name || !instruction_file_name)
+  if (!certificate_file_name || !choices_file_name)
     {
       ERRORPRINTF ("Invalid arguments.\n"
                    "Expected arguments: list=<certificate_list> \n"
-                   "                    instructions=<instructions_file>|uninstall\n");
+                   "                    choices=<choices_file>|uninstall\n");
       return ERR_INVALID_PARAMS;
     }
 
-  if (strncmp(instruction_file_name, "uninstall", 9) == 0)
+  if (strncmp(choices_file_name, "uninstall", 9) == 0)
     {
       do_uninstall = true;
-      instruction_file_name = NULL;
+      choices_file_name = NULL;
     }
 
   list_status = read_and_verify_list (certificate_file_name, &certificate_list,
@@ -240,9 +240,11 @@
     {
       if (list_status == InvalidSignature)
         {
+          ERRORPRINTF ("Failed to verify signature.\n");
           return ERR_INVALID_SIGNATURE;
         }
 
+      ERRORPRINTF ("Failed to read certificate list.\n");
       return ERR_INVALID_INPUT_NO_LIST;
     }
 
@@ -269,34 +271,38 @@
       return ret;
     }
 
-  ret = read_instructions_file (instruction_file_name, &to_install,
+  ret = read_choices_file (choices_file_name, &to_install,
                                 &to_remove);
 
   if (ret)
     {
+      ERRORPRINTF ("Failed to read choices file\n");
       return ret;
     }
 
   if (!strv_length (to_install) && !strv_length (to_remove) )
     {
+      ERRORPRINTF ("Failed to read choices file\n");
       return ERR_NO_INSTRUCTIONS;
     }
 
-  /* Check that the instructions are ok to execute */
+  /* Check that the choices are ok to execute */
   if (to_install)
     {
-      ret = validate_instructions (all_valid_certs, to_install);
+      ret = validate_choices (all_valid_certs, to_install);
       if (ret)
         {
+          ERRORPRINTF ("Failed to validate choices\n");
           return ret;
         }
     }
 
   if (to_remove)
     {
-      ret = validate_instructions (all_valid_certs, to_remove);
+      ret = validate_choices (all_valid_certs, to_remove);
       if (ret)
         {
+          ERRORPRINTF ("Failed to validate removal choices\n");
           return ret;
         }
     }
@@ -312,6 +318,7 @@
   if (ret != 0)
     {
       ERRORPRINTF ("Failed to write nss stores");
+      DEBUGPRINTF ("Hello World");
     }
 
   /* Make valgrind happy */
--- a/cinst/mozilla.c	Wed Apr 16 10:00:17 2014 +0200
+++ b/cinst/mozilla.c	Wed Apr 16 10:01:02 2014 +0200
@@ -366,13 +366,15 @@
  */
 static char *
 nss_cert_name(SECItem *secitemp)
-{ char *cn_str, *o_str, *name;
+{
+  char *cn_str, *o_str, *name;
   size_t name_len;
   cn_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_CN);
   o_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_O);
   if (!cn_str || !o_str)
     {
       DEBUGPRINTF("FATAL: Could not parse certificate!");
+      DEBUGPRINTF("data len: %u \n", secitemp->len);
       exit(ERR_INVALID_CERT);
     }
   name_len = strlen(cn_str) + strlen(o_str) + 4;
--- a/cinst/nssstore_linux.c	Wed Apr 16 10:00:17 2014 +0200
+++ b/cinst/nssstore_linux.c	Wed Apr 16 10:01:02 2014 +0200
@@ -11,11 +11,57 @@
 #include <sys/wait.h>
 #include <string.h>
 #include <stdlib.h>
+#include <limits.h>
 
 #include "nssstore.h"
 #include "logging.h"
 #include "strhelp.h"
 
+#define NSS_PROCESS_NAME "mozilla"
+
+/**@brief get the current path of the executable
+ *
+ * Looks up the current executables directory. The caller
+ * has to free the return value.
+ *
+ * The returned value includes the last /
+ *
+ * @returns the absolute directory of the currently executed executable or NULL
+ */
+char *
+get_exe_dir()
+{
+  char *retval = NULL,
+       *p = NULL,
+       buf[PATH_MAX];
+  ssize_t ret;
+  size_t path_len = 0;
+
+  ret = readlink ("/proc/self/exe", buf, PATH_MAX);
+  if (ret <= 0)
+    {
+      ERRORPRINTF ("readlink failed\n");
+      return NULL;
+    }
+
+  buf[ret] = '\0';
+
+  /* cut off the filename */
+  p = strrchr (buf, '/');
+  if (p == NULL)
+    {
+      ERRORPRINTF ("No filename found.\n");
+      return NULL;
+    }
+  *(p + 1) = '\0';
+
+  path_len = strlen (buf);
+  retval = xmalloc (path_len + 1);
+  strncpy (retval, buf, path_len);
+
+  return retval;
+}
+
 /**@brief Start the process to install / remove
  *
  * This forks the process and executes the NSS installation
@@ -35,9 +81,11 @@
 {
   int pipe_fd[2];
   pid_t pid = 0;
-  char *argv[] = {"mozilla", NULL},
-       *envp[2];
-  size_t homedir_len = 0;
+  char *argv[2] = {NULL, NULL},
+       *envp[2] = {NULL, NULL},
+       *inst_dir = NULL;
+  size_t homedir_len = 0,
+         exe_path_len = 0;
   int ret = -1,
       i = 0;
   FILE *stream = NULL;
@@ -64,7 +112,25 @@
       return -1;
     }
 
-  DEBUGPRINTF ("Home: %s \n", envp[0]);
+  /* Set up the file name of the installer process */
+  inst_dir = get_exe_dir();
+  if (inst_dir == NULL)
+    {
+      ERRORPRINTF ("Failed to find installation directory.\n");
+      xfree (envp[0]);
+      return -1;
+    }
+
+  exe_path_len = strlen(inst_dir) + strlen(NSS_PROCESS_NAME);
+  argv[0] = xmalloc (exe_path_len + 1);
+
+  ret = snprintf(argv[0], exe_path_len + 1, "%s%s", inst_dir, NSS_PROCESS_NAME);
+  if (ret < 0 || (size_t) ret != exe_path_len)
+    {
+      ERRORPRINTF ("Error setting executable variable.\n");
+      xfree (argv[0]);
+      return -1;
+    }
 
   if (pipe (pipe_fd))
     {
@@ -92,7 +158,7 @@
       dup2 (pipe_fd[0], 0);
       close (pipe_fd[0]);
       /* TODO find path based on current executable */
-      execve ("mozilla", argv, envp);
+      execve (argv[0], argv, envp);
       exit (127);
     }
 
@@ -104,6 +170,11 @@
       goto done;
     }
 
+  /* The NSS installer may exit on error before we are done
+   * telling it what to do. We want to handle that rather
+   * then die unexpectedly. */
+  signal(SIGPIPE, SIG_IGN);
+
   /* Send the instructions */
   for (i = 0; to_install && to_install[i]; i++)
     {
@@ -129,9 +200,10 @@
   if (stream) {
     fclose (stream);
   }
+  xfree (argv[0]);
   xfree (envp[0]);
-  close(pipe_fd[0]);
-  close(pipe_fd[1]);
+  close (pipe_fd[0]);
+  close (pipe_fd[1]);
 
   if (success)
     {
@@ -171,7 +243,6 @@
           ERRORPRINTF ("Waitpid failed.\n");
           return -1;
         }
-      DEBUGPRINTF ("Child returned status: %i\n", WEXITSTATUS(status));
 
       return 0;
     }
--- a/cinst/nssstore_win.c	Wed Apr 16 10:00:17 2014 +0200
+++ b/cinst/nssstore_win.c	Wed Apr 16 10:01:02 2014 +0200
@@ -98,7 +98,6 @@
   for (i = 0; certificates[i]; i++)
     {
       int ret = 0;
-      DEBUGPRINTF("Writing \n");
       if (remove)
         ret = fprintf (write_stream, "R:%s\n", certificates[i]);
       else
@@ -111,7 +110,6 @@
         }
     }
 
-  DEBUGPRINTF("Write done\n");
   return true;
 }
 
@@ -155,12 +153,15 @@
      restrict token -> hChildToken
   */
 
-  cmd_line_len = wcslen (lpApplicationName) + wcslen(selection_file) + 1;
+  cmd_line_len = wcslen (lpApplicationName) + wcslen(selection_file) + 2;
   lpCommandLine = xmalloc (cmd_line_len * sizeof(wchar_t));
 
   wcscpy_s (lpCommandLine, cmd_line_len, lpApplicationName);
+  wcscpy_s (lpCommandLine, cmd_line_len, L" ");
   wcscat_s (lpCommandLine, cmd_line_len, selection_file);
 
+  DEBUGPRINTF ("Starting %S with command line %S\n", lpApplicationName, lpCommandLine);
+
   success = CreateProcessAsUserW (hToken,
                                   lpApplicationName,
                                   lpCommandLine, /* Commandline */
--- a/packaging/renameme.nsi	Wed Apr 16 10:00:17 2014 +0200
+++ b/packaging/renameme.nsi	Wed Apr 16 10:01:02 2014 +0200
@@ -46,43 +46,72 @@
 Name "${productname}"
 OutFile "${setupname}"
 InstallDir "$PROGRAMFILES\${productname_short}"
-InstType "Standard"
+BrandingText "${company} - ${productname}"
 
 ;--------------------------------
-;Interface Settings
+; Interface Settings
 
-BrandingText "${company} - ${productname}"
-; MUI Settings / Header
-!define MUI_WELCOMEPAGE_TITLE  "Willkommen bei der Installation von ${productname_short}."
-!define MUI_WELCOMEPAGE_TEXT "Sie sind im Begriff ${productname} \r\n\
-zu installieren. ${productname} ist eine Anwendung um Zertifikate TODO \r\n\
-auf ihrem System zu installieren und aktualisieren."
-;!define MUI_HEADERIMAGE_BITMAP "" ;TODO
-;!define MUI_WELCOMEFINISHPAGE_BITMAP "ressources\welcome_left.bmp" ;TODO
-!define MUI_ICON "renameme_installer.ico"
+; MUI Images / Icons
+!define MUI_HEADERIMAGE
+!define MUI_HEADERIMAGE_BITMAP "resources\header-install.bmp"
+!define MUI_HEADERIMAGE_UNBITMAP "resources\header-uninstall.bmp"
+!define MUI_WELCOMEFINISHPAGE_BITMAP "resources\wizard-install.bmp"
+!define MUI_UNWELCOMEFINISHPAGE_BITMAP "resources\wizard-uninstall.bmp"
+!define MUI_ICON "resources\install.ico"
+!define MUI_UNICON "resources\uninstall.ico"
+
+; MUI welcome page text
+!define MUI_WELCOMEPAGE_TITLE  "Willkommen bei der Installation des ${productname}"
+!define MUI_WELCOMEPAGE_TEXT "Dieser Assistent wird Sie durch die Installation von \
+${productname} begleiten. $\r$\n$\r$\n\
+Der ${productname} ist eine Anwendung um Wurzelzertifikate auf ihrem System \
+zu installieren und aktuell zu halten. $\r$\n$\r$\n\
+Klicken Sie auf Weiter, um fortzufahren."
+
+!define MUI_FINISHPAGE_NOAUTOCLOSE
+!define MUI_UNFINISHPAGE_NOAUTOCLOSE
 
 !define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX"
 !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\${productname_short}"
 !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
 
 
-!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
+;--------------------------------
+; Pages
 
+!define MUI_FINISHPAGE_TITLE  "Die Installation war erfolgreich"
+!define MUI_FINISHPAGE_TEXT  "Der ${productname} wurde auf Ihrem \
+Computer installliert. $\r$\n$\r$\n\
+Klicken Sie auf 'Fertig stellen', um den Installations-Assistenten\
+zu schließen."
 !define MUI_FINISHPAGE_RUN $INSTDIR\m13ui.exe
+!define MUI_FINISHPAGE_RUN_TEXT "Anwendung starten"
+!define MUI_FINISHPAGE_LINK "Mehr unter http://www.bsi.bund.de" 
+!define MUI_FINISHPAGE_LINK_LOCATION "http://www.bsi.bund.de"
 
 !insertmacro MUI_PAGE_WELCOME
+!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
 !insertmacro MUI_PAGE_DIRECTORY
 !insertmacro MUI_PAGE_INSTFILES
 !insertmacro MUI_PAGE_FINISH
 
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+
+
+!insertmacro MUI_LANGUAGE "German"
+
+
 ;--------------------------------
-;Install Functions
+; Install Functions
+
 Function ".onInit"
   !insertmacro MULTIUSER_INIT
 FunctionEnd
 
 ;--------------------------------
-;UnInstall Functions
+; UnInstall Functions
+
 Function "un.onInit"
   !insertmacro MULTIUSER_UNINIT
 FunctionEnd
Binary file packaging/renameme_installer.ico has changed
Binary file packaging/resources/certificate.ico has changed
Binary file packaging/resources/header-install.bmp has changed
Binary file packaging/resources/header-uninstall.bmp has changed
Binary file packaging/resources/install.ico has changed
Binary file packaging/resources/uninstall.ico has changed
Binary file packaging/resources/wizard-install.bmp has changed
Binary file packaging/resources/wizard-uninstall.bmp has changed
--- a/ui/certificate.h	Wed Apr 16 10:00:17 2014 +0200
+++ b/ui/certificate.h	Wed Apr 16 10:01:02 2014 +0200
@@ -128,6 +128,9 @@
      **/
     static QList<Certificate> fromFileName (const QString& file_name);
 
+    friend inline bool operator==(const Certificate& lhs, const Certificate& rhs) {
+        return lhs.base64Line() == rhs.base64Line();
+    }
 private:
     /** @brief Helper function to parse the details of a certificate **/
     void parseDetails(const QByteArray& cert);
--- a/ui/installwrapper.cpp	Wed Apr 16 10:00:17 2014 +0200
+++ b/ui/installwrapper.cpp	Wed Apr 16 10:01:02 2014 +0200
@@ -69,9 +69,6 @@
         return;
     }
 
-    QString parameters = "\"list=" + mCertListFile +
-        "\" \"choices=" + choicesFile.fileName() + "\"";
-
 #ifdef WIN32
     /* QProcess on Windows uses CreateProcess but we have to
      * use the runas shell command to get the UAC prompt if necessary.
@@ -85,6 +82,10 @@
 
     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;
     shExecInfo.lpVerb = L"runas";
@@ -125,19 +126,30 @@
         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;
     }
-    return;
 #else /* WIN32 */
     QProcess installerProcess;
     installerProcess.setProgram(cinstProcInfo.absoluteFilePath());
-    installerProcess.waitForStarted();
-    if (installerProcess.state() == QProcess::NotRunning) {
+    QStringList parameters;
+
+    choicesFile.setAutoRemove(false);
+    parameters << "list=" + mCertListFile << "choices=" + choicesFile.fileName();
+    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;
     }
@@ -146,11 +158,12 @@
 
     if (installerProcess.exitStatus() == QProcess::CrashExit) {
         /* Woops */
-        qWarning() << "Installer process crashed";
+        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.. */
-        qWarning() << "Exit status neither normal nor crash.";
+        emit error (tr("Failed to complete installation."));
         return;
     }
 
@@ -163,4 +176,5 @@
         return;
     }
 #endif
+    emit installationSuccessful();
 }
--- a/ui/installwrapper.h	Wed Apr 16 10:00:17 2014 +0200
+++ b/ui/installwrapper.h	Wed Apr 16 10:01:02 2014 +0200
@@ -73,6 +73,14 @@
      * @param[out] message: A localized message to show. Can be empty.
      */
     void error(const QString &message);
+
+    /**
+     * @brief The installation was successful
+     *
+     * This only means the installation was successful as far as we
+     * can tell as there is no backchannel from the installation processes.
+     */
+    void installationSuccessful();
 };
 
 #endif // UI_INSTALLWRAPPER_H
--- a/ui/mainwindow.cpp	Wed Apr 16 10:00:17 2014 +0200
+++ b/ui/mainwindow.cpp	Wed Apr 16 10:01:02 2014 +0200
@@ -53,10 +53,11 @@
     mMessageTimer->setInterval(NAG_INTERVAL_MINUTES * 60 * 1000);
     mMessageTimer->start();
     checkUpdates();
+    loadUnselectedCertificates();
+    loadCertificateList();
     if (!trayMode) {
         show();
     }
-    loadUnselectedCertificates();
 }
 
 void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
@@ -92,17 +93,18 @@
 
 void MainWindow::verifyAvailableData()
 {
-    QString listFileName = mSettings.value("List/available").toString();
+    QString availableFileName = mSettings.value("List/available").toString();
+    QString installedFileName = mSettings.value("List/installed").toString();
     QString swFileName = mSettings.value("Software/available").toString();
 
-    if (!listFileName.isEmpty()) {
-        mListToInstall.readList(listFileName.toLocal8Bit().constData());
+    if (!availableFileName.isEmpty()) {
+        mListToInstall.readList(availableFileName.toLocal8Bit().constData());
         if (!mListToInstall.isValid()) {
             mCurState = TransferError;
             // 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(listFileName);
+            QFile::remove(availableFileName);
             mSettings.remove("List/available");
             mSettings.remove("List/availableDate");
         }
@@ -112,6 +114,22 @@
         mSettings.remove("List/availableDate");
     }
 
+    if (!installedFileName.isEmpty()) {
+        mInstalledList.readList(installedFileName.toLocal8Bit().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 {
+        // Make sure the available notation is also removed
+        mSettings.remove("List/installed");
+        mSettings.remove("List/installedDate");
+    }
+
     if (!swFileName.isEmpty()) {
         // TODO
     } else {
@@ -158,11 +176,21 @@
     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(),
                                             QDateTime::currentDateTime(),
-//                                            swInstalledLastMod,
+// TODO                                       swInstalledLastMod,
                                             listInstalledLastMod,
                                             QString::fromLatin1(SW_RESOURCE),
                                             QString::fromLatin1(LIST_RESOURCE));
@@ -319,7 +347,13 @@
 {
     mCertListWidget->clear();
     int i = 0;
-    foreach (const Certificate &cert, mListToInstall.getCertificates()) {
+
+    /* TODO: if nothing is available (neither old nor new) add some progress
+     * indication */
+
+    foreach (const Certificate &cert, mListToInstall.isValid() ?
+                                      mListToInstall.getCertificates() :
+                                      mInstalledList.getCertificates()) {
         if (!cert.isValid()) {
             qWarning() << "Invalid certificate in list";
             continue;
@@ -330,9 +364,14 @@
         item->setData(CertificateItemDelegate::B64LineRole, cert.base64Line());
         Qt::CheckState checkedState = mPreviouslyUnselected.contains(cert.base64Line()) ?
             Qt::Unchecked : Qt::Checked;
+
+        bool isOld = mInstalledList.getCertificates().contains(cert);
+        qDebug() << "Found old certificate.";
+        /* TODO properly work with that information. */
+
         if (cert.isInstallCert()) {
             // This if statements is for testing! @TODO Remove this!
-            if (i <= 2) {
+            if (isOld) {
                 item->setData(CertificateItemDelegate::StatusRole, Certificate::InstallOld);
                 item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
             }
@@ -407,6 +446,27 @@
     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();
+
+        mSettings.remove("List/available");
+        mSettings.remove("List/availableDate");
+
+        if (listFileName.isEmpty() || !listFileDate.isValid()) {
+            qWarning() << "Error accessing settings";
+            return; /* Try again with next check */
+        }
+
+        mSettings.setValue("List/installed", listFileName);
+        mSettings.setValue("List/installedDate", listFileDate);
+    }
+}
+
 void MainWindow::installCerts() {
     QStringList choices;
 
@@ -432,14 +492,18 @@
     progress->show();
 
     InstallWrapper *instWrap = new InstallWrapper(this,
-                                                  mListToInstall.fileName(),
+                                                  mListToInstall.isValid() ?
+                                                  mListToInstall.fileName() :
+                                                  mInstalledList.fileName(),
                                                   choices);
     /* 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 &)));
+            this, SLOT(installerError(const QString &)));
     instWrap->start();
 
     if (!saveUnselectedCertificates()) {
--- a/ui/mainwindow.h	Wed Apr 16 10:00:17 2014 +0200
+++ b/ui/mainwindow.h	Wed Apr 16 10:01:02 2014 +0200
@@ -43,7 +43,8 @@
         BeforeDownload,
         NewListAvailable,
         NewSoftwareAvailable,
-        TransferError
+        TransferError,
+        NothingChanged
     };
     CurrentState getState() {return mCurState;}
     void setState(CurrentState state) {mCurState = state;}
@@ -64,6 +65,7 @@
     void showDetails(QListWidgetItem*);
     void resizeButtons();
     void installerError(const QString& errMsg);
+    void installerSuccess();
     void installCerts();
 
     /** @brief saves the currently unselected certificates
@@ -93,8 +95,8 @@
      * Do not use this as a trust check as this only works on
      * FileNames where the underlying files can change. This
      * is just meant to check if the downloaded data was somehow
-     * removed or corrupted.
-     *
+     * removed or corrupted. It also initializes mListToInstall
+     * and mInstalledList.
      */
     void verifyAvailableData();
     void createTrayIcon();
@@ -122,6 +124,8 @@
 
     /* The current list that should be installed */
     CertificateList mListToInstall;
+    /* The last list that we installed */
+    CertificateList mInstalledList;
     /* Previously made "unselect" choices in the form of
      * base64lines with I:/R: prefix */
     QStringList mPreviouslyUnselected;

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