changeset 310:f758460ca437

Merged
author Sascha Wilde <wilde@intevation.de>
date Fri, 04 Apr 2014 09:54:19 +0200
parents fa37384b86b6 (current diff) 2fd69803d219 (diff)
children 4ffc9f31b61a
files
diffstat 23 files changed, 861 insertions(+), 242 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Fri Apr 04 09:53:55 2014 +0200
+++ b/CMakeLists.txt	Fri Apr 04 09:54:19 2014 +0200
@@ -20,6 +20,8 @@
 
 find_package(Qt5Widgets)
 
+find_package(NSS)
+
 # Use cmake's automoc and make sure the generated files are included
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
--- a/build.sh	Fri Apr 04 09:53:55 2014 +0200
+++ b/build.sh	Fri Apr 04 09:54:19 2014 +0200
@@ -8,7 +8,7 @@
 cd `dirname $0`
 
 # build requirements need to be installed:
-#   polarssl, qt5, (opt) doxygen, (opt) hiawatha-webserver
+#   polarssl, qt5, (opt) doxygen, (opt) hiawatha-webserver, (opt) libnss3-dev
 
 mkdir -p build-linux
 cd build-linux
--- a/cinst/CMakeLists.txt	Fri Apr 04 09:53:55 2014 +0200
+++ b/cinst/CMakeLists.txt	Fri Apr 04 09:54:19 2014 +0200
@@ -6,6 +6,7 @@
 
 set(CINST_SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/windowsstore.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/nssstore_linux.c
     ${CMAKE_CURRENT_SOURCE_DIR}/main.c
 )
 add_executable(cinst ${CINST_SOURCES})
@@ -25,8 +26,6 @@
 # ----------------------------------------------------------------------
 # Mozilla nss store specific certificate installer:
 
-find_package(NSS)
-
 if(NSS_FOUND)
   include_directories(${NSS_INCLUDE_DIRS})
   set(MOZILLA_SOURCES
--- a/cinst/main.c	Fri Apr 04 09:53:55 2014 +0200
+++ b/cinst/main.c	Fri Apr 04 09:54:19 2014 +0200
@@ -7,23 +7,26 @@
  *  process will modify system wide certificate stores.
  *  Otherwise only the users certificate stores are modified.
  *
- *  It expects a certificatelist on stdin enclosed in a
- *  -----BEGIN CERTIFICATE LIST-----
- *  ...
- *  -----END CERTIFICATE LIST-----
+ *  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
  *
- *  Followed by additional instruction lines of:
+ *  instruction_file_name should be the absolute path to an
+ *  instructions file formatted as:
+ *
  *  I:<certificate>
  *  R:<certificate>
  *
+ *  Line breaks can be system dependent in the Instructions file.
+ *
  *  It will only execute the instructions if the
  *  I and R instructions are also part of the signed
  *  certificate list. The signature is validated with the
  *  built in key.
  *
- *  The special instruction "UNINSTALL" will cause the installer
+ *  The special instruction "uninstall" will cause the installer
  *  to remove all certificates (Even those marked with I) that
- *  are part of the list to be removed.
+ *  are part of the list.
  *
  **/
 #include <stdio.h>
@@ -34,8 +37,10 @@
 
 #include "strhelp.h"
 #include "listutil.h"
+#include "logging.h"
 #include "errorcodes.h"
 #include "windowsstore.h"
+#include "nssstore.h"
 
 /* The certificate list + instructions may only be so long as
  * twice the accepted certificatelist size */
@@ -43,40 +48,62 @@
 
 /* @brief Read stdin into data structures.
  *
- * Reads from stdin and sorts the input into the respective
- * variables. The pointers returned need to be freed by the caller.
+ * Reads instructions 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.
+ * Other lines are ignored.
+ *
  * Terminates in OOM conditions.
  *
  * The caller needs to free the memory allocated by this function
  * even when an error is returned.
  *
- * Uninstall certificates are all certificates that are pa
- *
- * @param[out] certificate_list the parsed certificate list
+ * @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[out] all_certs strv of uninstallation instructions or NULL
  *
  * @returns: 0 on success. An error code otherwise.
  */
-int
-readInput (char **certificate_list, char ***to_install,
-           char ***to_remove, char ***all_certs)
+static int
+read_instructions_file (char *file_name, char ***to_install,
+                        char ***to_remove)
 {
   int lines_read = 0;
-  int readingList = 0;
-  size_t list_size = 0;
   char buf[MAX_LINE_LENGTH + 2];
+  FILE *f = NULL;
+  long file_size;
 
-  if (*certificate_list || *to_install || *to_remove)
+  if (*to_install || *to_remove)
     {
-      printf ("Error invalid parameters\n");
+      printf ("Error invalid parameters.\n");
       return -1;
     }
 
-  while (fgets (buf, MAX_LINE_LENGTH + 1, stdin) )
+  f = fopen (file_name, "rb");
+  if (f == NULL)
+    return ERR_NO_INSTRUCTIONS;
+
+  fseek (f, 0, SEEK_END);
+  file_size = ftell (f);
+  if (file_size <= 0)
     {
-      size_t len = strlen (buf);	/* fgets ensures buf is terminated */
+      fclose (f);
+      return ERR_NO_INSTRUCTIONS;
+    }
+
+  fseek (f, 0, SEEK_SET);
+
+  if (file_size + 1 == 0)
+    {
+      fclose (f);
+      return ERR_INVALID_INSTRUCTIONS;
+    }
+
+  while (fgets (buf, MAX_LINE_LENGTH + 1, f) )
+    {
+      size_t len = strlen (buf); /* fgets ensures buf is terminated */
       if (len <= 3)
         {
           printf ("Line too short.\n");
@@ -87,50 +114,20 @@
           printf ("Too many lines\n");
           return ERR_TOO_MUCH_INPUT;
         }
-
-      if (buf[len - 2] != '\r')
-        {
-          if (buf[len - 1] != '\n')
-            {
-              printf ("Line too long.\n");
-              return ERR_INVALID_INPUT;
-            }
-          buf[len - 1] = '\r';
-          buf[len] = '\n';
-          buf[len + 1] = '\0';
-          len++;
-        }
-
-      if (strcmp ("-----BEGIN CERTIFICATE LIST-----\r\n", buf) == 0)
-        {
-          readingList = 1;
-          continue;
-        }
-      if (strcmp ("-----END CERTIFICATE LIST-----\r\n", buf) == 0)
-        {
-          readingList = 0;
-          continue;
-        }
-      if (readingList)
-        {
-          str_append_str (certificate_list, &list_size, buf, len);
-        }
-      else if (strcmp ("UNINSTALL\r\n", buf) == 0)
-        {
-          /* Remove trailing \r\n */
-          strv_append (to_remove, buf, len - 2);
-          continue;
-        }
       if (*buf == 'I')
         {
-          /* Remove leading I: and trailing \r\n */
-          strv_append (readingList ? all_certs : to_install, buf + 2, len - 4);
+          char *trimmed = buf+2;
+          /* Remove leading I: and trailing whitespace */
+          str_trim(&trimmed);
+          strv_append (to_install, trimmed, strlen(trimmed));
           continue;
         }
       if (*buf == 'R')
         {
-          /* Remove leading R: and trailing \r\n */
-          strv_append (readingList ? all_certs : to_remove, buf + 2, len - 4);
+          char *trimmed = buf+2;
+          /* Remove leading R: and trailing whitespace */
+          str_trim(&trimmed);
+          strv_append (to_remove, trimmed, strlen(trimmed));
           continue;
         }
     }
@@ -170,7 +167,7 @@
       bool found = false;
       for (j = 0; all_certs[j]; j++)
         {
-          if (strncmp (to_validate[i], all_certs[j], MAX_LINE_LENGTH - 2) ==
+          if (strncmp (to_validate[i], all_certs[j], MAX_LINE_LENGTH) ==
               0)
             {
               found = true;
@@ -179,7 +176,7 @@
         }
       if (!found)
         {
-          printf ("Install instruction with invalid certificate\n.");
+          DEBUGPRINTF ("Failed to find certificate; \n%s\n", to_validate[i]);
           return ERR_INVALID_INSTRUCTIONS;
         }
     }
@@ -189,47 +186,103 @@
 
 
 int
-main ()
+main (int argc, char **argv)
 {
-  char **to_install = NULL;
-  char **to_remove = NULL;
-  char **all_certs = NULL;
-  char *certificate_list = NULL;
+  /* TODO handle wchar arguments on Windows or do conversion dance */
+  char **to_install = NULL,
+       **to_remove = NULL,
+       **all_valid_certs = NULL;
+  int ret = -1;
+
+  char *certificate_list = NULL,
+       *certificate_file_name = NULL,
+       *instruction_file_name = NULL;
   size_t list_len = 0;
-  int ret = -1;
-  bool uninstall = false;
+  list_status_t list_status;
+  bool do_uninstall = false;
 
-  ret = readInput (&certificate_list, &to_install, &to_remove, &all_certs);
+  /* Some very static argument parsing. list= and instructions= 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)
+    {
+      ERRORPRINTF ("Invalid arguments.\n"
+                   "Expected arguments: list=<certificate_list> \n"
+                   "                    instructions=<instructions_file>|uninstall\n");
+      return ERR_INVALID_PARAMS;
+    }
+
+  certificate_file_name = strchr(argv[1], '=') + 1;
+  instruction_file_name = strchr(argv[2], '=') + 1;
+
+  if (!certificate_file_name || !instruction_file_name)
+    {
+      ERRORPRINTF ("Invalid arguments.\n"
+                   "Expected arguments: list=<certificate_list> \n"
+                   "                    instructions=<instructions_file>|uninstall\n");
+      return ERR_INVALID_PARAMS;
+    }
+
+  if (strncmp(instruction_file_name, "uninstall", 9) == 0)
+    {
+      do_uninstall = true;
+      instruction_file_name = NULL;
+    }
+
+  list_status = read_and_verify_list (certificate_file_name, &certificate_list,
+                                      &list_len);
+
+  if (list_status != Valid)
+    {
+      if (list_status == InvalidSignature)
+        {
+          return ERR_INVALID_SIGNATURE;
+        }
+
+      return ERR_INVALID_INPUT_NO_LIST;
+    }
+
+  all_valid_certs = get_certs_from_list (certificate_list, list_len);
+
+  if (!all_valid_certs)
+    {
+      /* Impossible */
+      return -1;
+    }
+
+
+  /* For uninstall we are done now */
+  if (do_uninstall)
+    {
+#ifdef WIN32
+      ret = write_stores_win (NULL, all_valid_certs);
+      if (ret != 0)
+        {
+          ERRORPRINTF ("Failed to write windows stores retval: %i\n", ret);
+        }
+#endif
+      ret = write_stores_nss (NULL, all_valid_certs);
+      return ret;
+    }
+
+  ret = read_instructions_file (instruction_file_name, &to_install,
+                                &to_remove);
 
   if (ret)
     {
       return ret;
     }
 
-  if (!certificate_list)
-    {
-      return ERR_INVALID_INPUT_NO_LIST;
-    }
-
-  list_len = strnlen (certificate_list, MAX_INPUT_SIZE);
-
-  ret = verify_list (certificate_list, list_len);
-
-  if (ret)
-    {
-      return ERR_INVALID_SIGNATURE;
-    }
-
   if (!strv_length (to_install) && !strv_length (to_remove) )
     {
       return ERR_NO_INSTRUCTIONS;
     }
 
-
   /* Check that the instructions are ok to execute */
   if (to_install)
     {
-      ret = validate_instructions (all_certs, to_install);
+      ret = validate_instructions (all_valid_certs, to_install);
       if (ret)
         {
           return ret;
@@ -238,39 +291,27 @@
 
   if (to_remove)
     {
-      if (to_remove[0]
-          && strncmp ("UNINSTALL", to_remove[0], MAX_LINE_LENGTH) == 0)
+      ret = validate_instructions (all_valid_certs, to_remove);
+      if (ret)
         {
-          uninstall = true;
-          strv_free (to_remove);
-          to_remove = NULL;
-        }
-      else
-        {
-          ret = validate_instructions (all_certs, to_remove);
-          if (ret)
-            {
-              return ret;
-            }
+          return ret;
         }
     }
 
-  if (uninstall)
+#ifdef WIN32
+  ret = write_stores_win (to_install, to_remove);
+  if (ret != 0)
     {
-      /* To uninstall does not have to be verified as it part of the
-       * signed list.*/
-      to_remove = all_certs;
+      ERRORPRINTF ("Failed to write windows stores retval: %i\n", ret);
     }
-  else
+#endif
+  ret = write_stores_nss (to_install, to_remove);
+
+  if (ret != 0)
     {
-      strv_free (all_certs);
-      all_certs = NULL;
+      ERRORPRINTF ("Failed to write nss stores");
     }
 
-#ifdef WIN32
-  return write_stores_win (to_install, to_remove);
-#endif
-
   /* Make valgrind happy */
   strv_free (to_install);
   strv_free (to_remove);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cinst/nssstore.h	Fri Apr 04 09:54:19 2014 +0200
@@ -0,0 +1,27 @@
+#ifndef NSSSTORE_H
+#define NSSSTORE_H
+/** @file
+ * @brief Helper functions controlling the NSS installation process.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @brief Write into NSS stores
+ *
+ * Starts the nss installation process for all users
+ * we have the right to impersonate and installs / removes
+ * the certificates in their stores.
+
+ * @param [in] to_install strv of DER encoded certificates to be added.
+ * @param [in] to_remove strv of DER encoded certificates to be remvoed.
+ * @returns 0 on success an errorcode otherwise.
+ */
+int write_stores_nss (char **to_install, char **to_remove);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // NSSSTORE_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cinst/nssstore_linux.c	Fri Apr 04 09:54:19 2014 +0200
@@ -0,0 +1,178 @@
+#ifndef WIN32
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "nssstore.h"
+#include "logging.h"
+#include "strhelp.h"
+
+/**@brief Start the process to install / remove
+ *
+ * This forks the process and executes the NSS installation
+ * process. It also writes the Instructions to that process.
+ *
+ * @param [in] to_install strv of DER encoded certificates to be added.
+ * @param [in] to_remove strv of DER encoded certificates to be remvoed.
+ * @param [in] uid_t uid of the user to install certificates for.
+ * @param [in] gid_t the gid of the user to install certificates for.
+ * @param [in] homedir the homedir of the user.
+ *
+ * @returns childs pid on success. -1 on failure
+ */
+static int
+start_procces_for_user (char **to_install, char **to_remove,
+                        uid_t uid, gid_t gid, char *homedir)
+{
+  int pipe_fd[2];
+  pid_t pid = 0;
+  char *argv[] = {"mozilla", NULL},
+       *envp[2];
+  size_t homedir_len = 0;
+  int ret = -1,
+      i = 0;
+  FILE *stream = NULL;
+  bool success = false;
+
+  if (homedir == NULL)
+    {
+      ERRORPRINTF ("Invalid call to start_process_for_user\n");
+      return -1;
+    }
+
+  homedir_len = strlen (homedir);
+
+  /* Allocate space for HOME=homedir\0 */
+  envp[0] = xmalloc (homedir_len + 6);
+  envp[1] = NULL;
+
+  ret = snprintf (envp[0], homedir_len + 6, "HOME=%s", homedir);
+
+  if (ret < 0 || (size_t) ret != homedir_len + 5)
+    {
+      ERRORPRINTF ("Error setting home env variable.\n");
+      xfree (envp[0]);
+      return -1;
+    }
+
+  DEBUGPRINTF ("Home: %s \n", envp[0]);
+
+  if (pipe (pipe_fd))
+    {
+      ERRORPRINTF ("Failed to create pipe.\n");
+      return -1;
+    }
+
+  pid = fork();
+
+  if (pid == (pid_t) -1)
+    {
+      ERRORPRINTF ("Failed to fork child.\n");
+      return -1;
+    }
+
+  if (pid == (pid_t) 0)
+    {
+      /* Drop privileges */
+      if (setuid (uid) || setgid (gid))
+        {
+          exit(-1);
+        }
+
+      close (pipe_fd[1]);
+      dup2 (pipe_fd[0], 0);
+      close (pipe_fd[0]);
+      /* TODO find path based on current executable */
+      execve ("mozilla", argv, envp);
+      exit (127);
+    }
+
+  close (pipe_fd[0]);
+  stream = fdopen(pipe_fd[1], "w");
+  if (stream == NULL)
+    {
+      ERRORPRINTF ("Failed to open pipe for writing\n");
+      goto done;
+    }
+
+  /* Send the instructions */
+  for (i = 0; to_install && to_install[i]; i++)
+    {
+      if (fprintf (stream, "I:%s\n", to_install[i]) <= 3)
+        {
+          ERRORPRINTF ("Write failed \n");
+          goto done;
+        }
+    }
+
+  for (i = 0; to_remove && to_remove[i]; i++)
+    {
+      if (fprintf (stream, "R:%s\n", to_remove[i]) <= 3)
+        {
+          ERRORPRINTF ("Write failed \n");
+          goto done;
+        }
+    }
+
+  success = true;
+
+done:
+  if (stream) {
+    fclose (stream);
+  }
+  xfree (envp[0]);
+  close(pipe_fd[0]);
+  close(pipe_fd[1]);
+
+  if (success)
+    {
+      return pid;
+    }
+  return -1;
+}
+
+int
+write_stores_nss (char **to_install, char **to_remove)
+{
+  uid_t my_uid = getuid();
+
+  if (my_uid != 0)
+    {
+      /* Running as a user */
+      char *homedir = getenv ("HOME");
+      pid_t childprocess = -1; /* Only one child for single user installation */
+      int status = -1;
+      if (!homedir)
+        {
+          ERRORPRINTF ("Failed to find home directory\n");
+        }
+
+      childprocess = start_procces_for_user (to_install, to_remove,
+                                             my_uid, getgid(), homedir);
+
+      if (childprocess == -1)
+        {
+          ERRORPRINTF ("Failed to start childprocess!\n");
+          return -1;
+        }
+
+      childprocess = waitpid (childprocess, &status, 0);
+      if (childprocess == -1 || !WIFEXITED(status))
+        {
+          ERRORPRINTF ("Waitpid failed.\n");
+          return -1;
+        }
+      DEBUGPRINTF ("Child returned status: %i\n", WEXITSTATUS(status));
+
+      return 0;
+    }
+  printf ("Installation as root is not yet implemented\n");
+  /* TODO root parse /etc/passwd for users with a home directory */
+  return 0;
+}
+#endif
--- a/common/errorcodes.h	Fri Apr 04 09:53:55 2014 +0200
+++ b/common/errorcodes.h	Fri Apr 04 09:54:19 2014 +0200
@@ -3,7 +3,7 @@
 
 /* No error */
 #define ERR_NO_ERROR 0
-/* No begin certificate / end certificate could be found */
+/* Failed to read / verify the certificate list */
 #define ERR_INVALID_INPUT_NO_LIST 2
 /* Too much input for the installer process */
 #define ERR_TOO_MUCH_INPUT 3
@@ -21,6 +21,8 @@
 #define ERR_INVALID_INPUT 9
 /* Generic invalid certificate */
 #define ERR_INVALID_CERT 10
+/* Invalid parameters in call */
+#define ERR_INVALID_PARAMS 11
 
 /***********************************************************************
  * mozilla specific errors and warnings
--- a/common/listutil.c	Fri Apr 04 09:53:55 2014 +0200
+++ b/common/listutil.c	Fri Apr 04 09:54:19 2014 +0200
@@ -9,6 +9,8 @@
 #include <sys/stat.h>
 #include <string.h>
 
+#include "strhelp.h"
+
 #ifdef RELEASE
 #include "pubkey-release.h"
 #else
@@ -232,22 +234,30 @@
     return retval;
 }
 
-char **get_certs_to_remove(const char *data, const size_t size) {
+char **
+get_certs_from_list (char *data, const size_t size)
+{
+  char *cur = data;
+  char **retval = NULL;
 
-    /* TODO */
-    if (!data || !size) {
-        printf ("Invalid call to get_certs_to_remove \n");
-        return NULL;
+  if (!data || !size)
+    {
+      printf ("Invalid call to get_certs_to_remove \n");
+      return NULL;
     }
-    return NULL;
+
+  while (cur)
+    {
+      char *next = strchr(cur, '\n');
+      if (strlen(cur) > 3 && (cur[0] == 'I' || cur[0] == 'R') &&
+          next - cur > 4)
+        {
+          size_t len = (size_t) (next - cur - 3);
+          /* Remove I: or R: at the beginning and \r\n at the end */
+          strv_append(&retval, cur + 2, len);
+        }
+      cur = next ? (next + 1) : NULL;
+    }
+  return retval;
 }
 
-char **get_certs_to_install(const char *data, const size_t size) {
-
-    /* TODO */
-    if (!data || !size) {
-        printf ("Invalid call to get_certs_to_install \n");
-        return NULL;
-    }
-    return NULL;
-}
--- a/common/listutil.h	Fri Apr 04 09:53:55 2014 +0200
+++ b/common/listutil.h	Fri Apr 04 09:54:19 2014 +0200
@@ -59,9 +59,9 @@
  */
 int verify_list(const char *data, const size_t size);
 
-/** @brief get a list of the certificates marked with I:
+/** @brief get a list of the certificates marked with I: or R:
  *
- * Get a list of certificates that should be installed by the
+ * Get a list of certificates that are contained in the
  * certificatelist pointed to by data.
  * On Success this function makes a copy of the certificates
  * and the certificates need to be freed by the caller.
@@ -72,23 +72,7 @@
  * @returns a newly allocated array of strings containing the encoded
  * certificates or NULL on error.
  * */
-char **get_certs_to_install(const char *data, const size_t size);
-
-/** @brief get a list of the certificates marked with R:
- *
- * Get a list of certificates that should be removed by the
- * certificatelist pointed to by data.
- * On Success this function makes a copy of the certificates
- * and the certificates need to be freed by the caller.
- *
- * @param [in] data the certificatelist to parse
- * @param [in] size the size of the certificatelist
- *
- * @returns a newly allocated array of strings containing the encoded
- * certificates or NULL on error.
- * */
-char **get_certs_to_remove(const char *data, const size_t size);
-
+char **get_certs_from_list (char *data, const size_t size);
 
 #ifdef __cplusplus
 }
--- a/common/logging.h	Fri Apr 04 09:53:55 2014 +0200
+++ b/common/logging.h	Fri Apr 04 09:54:19 2014 +0200
@@ -56,6 +56,34 @@
 #define DEBUGPRINTF(fmt, ...)
 #endif
 
+/**
+ * @def DEBUGMSG(msg)
+ * @brief Prints a static debug message
+ *
+ * If DEBUGOUTPUT is defined this. Prints a debug message
+ * to stdout on Unix systems. On Windows OutputDebugString is used.
+ */
+#ifdef DEBUGOUTPUT
+# ifdef WIN32
+#  define DEBUGMSG(msg) OutputDebugString (msg);
+# else
+#  define DEBUGMSG(msg) printf (DEBUGPREFIX "DEBUG: " msg "\n");
+# endif
+#else
+# define DEBUGPRINTF(fmt, ...)
+#endif
+
+
+/**
+ * @def ERRORPRINTF(fmt, ...)
+ * @brief Debug printf
+ *
+ * Prints an error to stderr
+ */
+#define ERRORPRINTF(fmt, ...) fprintf(stderr, DEBUGPREFIX "ERROR: " fmt, ##__VA_ARGS__);
+
+
+
 #ifdef __cplusplus
 }
 #endif
--- a/common/strhelp.c	Fri Apr 04 09:53:55 2014 +0200
+++ b/common/strhelp.c	Fri Apr 04 09:54:19 2014 +0200
@@ -219,6 +219,7 @@
       xfree (result);
       return NULL;
     }
+  result[n] = 0;
   return result;
 }
 
--- a/ui/installwrapper.cpp	Fri Apr 04 09:53:55 2014 +0200
+++ b/ui/installwrapper.cpp	Fri Apr 04 09:54:19 2014 +0200
@@ -8,6 +8,8 @@
 
 #include "logging.h"
 
+#define INSTALL_TIMEOUT 3600000 /* Wait up to an hour */
+
 InstallWrapper::InstallWrapper(QObject* parent,
         const QString& path, const QStringList& instructions):
     QThread(parent),
@@ -27,32 +29,26 @@
 }
 
 #ifdef WIN32
-extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
-
 void InstallWrapper::run()
 {
+    /* TODO: We need errorcodes here so that we can see if a user
+     * cancled the UAC elevation */
     QTemporaryFile instructionsFile;
     QFileInfo cinstProcInfo = getCinstProcInfo();
+    DWORD retval = 0;
+    SHELLEXECUTEINFOW shExecInfo;
+    memset (&shExecInfo, 0, sizeof(SHELLEXECUTEINFOW));
 
     QString cinstFileName = QDir::toNativeSeparators(
             getCinstProcInfo().absoluteFilePath());
 
     if (!cinstProcInfo.isExecutable()) {
-        emit error (tr("Could not find certificate installation process."));
+        emit error(tr("Could not find certificate installation process."));
         return;
     }
 
     instructionsFile.open();
 
-    qt_ntfs_permission_lookup++;
-    if (instructionsFile.permissions() ^ (
-                QFileDevice::ReadUser |
-                QFileDevice::WriteUser |
-                QFileDevice::ReadOwner |
-                QFileDevice::WriteOwner)) {
-        emit error (tr("Invalid permissions on temporary file."));
-    }
-
     foreach (const QString &b64data, mInstructions) {
        instructionsFile.write(b64data.toLatin1());
        instructionsFile.write("\n");
@@ -60,25 +56,56 @@
 
     instructionsFile.close();
 
-    QString parameters = "\"" + mCertListFile + "\" \"" +instructionsFile.fileName() + "\"";
+    QString parameters = "\"list=" + mCertListFile + 
+        "\" \"instructions=" +instructionsFile.fileName() + "\"";
 
-    memset (&mExecInfo, 0, sizeof(SHELLEXECUTEINFOW));
-    mExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
-    mExecInfo.fMask = SEE_MASK_FLAG_NO_UI |
-                      SEE_MASK_NOASYNC;
-    mExecInfo.lpVerb = L"runas";
-    mExecInfo.lpFile = reinterpret_cast<LPCWSTR> (cinstFileName.utf16());
-    mExecInfo.lpParameters =  reinterpret_cast<LPCWSTR> (parameters.utf16());
+    shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
+    shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
+    shExecInfo.lpVerb = L"runas";
+    shExecInfo.lpFile = reinterpret_cast<LPCWSTR> (cinstFileName.utf16());
+    shExecInfo.lpParameters =  reinterpret_cast<LPCWSTR> (parameters.utf16());
 
     qDebug() << "Starting process " << cinstFileName <<" params: " << parameters;
 
-    if (!ShellExecuteExW(&mExecInfo)) {
+    if (!ShellExecuteExW(&shExecInfo)) {
         char* errmsg = getLastErrorMsg();
         QString qerrmsg = QString::fromUtf8(errmsg);
         free(errmsg);
-        emit(tr("Error executing process: %1").arg(qerrmsg));
+        emit error(tr("Error executing process: %1").arg(qerrmsg));
+        return;
     }
-    qt_ntfs_permission_lookup--;
+
+    retval = WaitForSingleObject(shExecInfo.hProcess, INSTALL_TIMEOUT);
+
+    if (retval != WAIT_OBJECT_0) {
+        if (retval == WAIT_FAILED) {
+            char* errmsg = getLastErrorMsg();
+            QString qerrmsg = QString::fromUtf8(errmsg);
+            free(errmsg);
+            emit error (tr("Error monitoring process: %1").arg(qerrmsg));
+            return;
+        } else {
+            emit error (tr("Certificate installation timed out."));
+            return;
+        }
+    }
+
+    if (GetExitCodeProcess(shExecInfo.hProcess, &retval)) {
+        if (retval == STILL_ACTIVE) {
+            qDebug() << "Process still running, huh..";
+        }
+    } else {
+        char* errmsg = getLastErrorMsg();
+        QString qerrmsg = QString::fromUtf8(errmsg);
+        free(errmsg);
+        emit error (tr("Failed to check process status: %1").arg(qerrmsg));
+    }
+    CloseHandle(shExecInfo.hProcess);
+
+    if (retval != 0) {
+        /* TODO make this nicer */
+        emit error (tr("The process failed with return code. %1").arg(retval));
+    }
 }
 #else
 void InstallWrapper::run()
--- a/ui/installwrapper.h	Fri Apr 04 09:53:55 2014 +0200
+++ b/ui/installwrapper.h	Fri Apr 04 09:54:19 2014 +0200
@@ -3,7 +3,6 @@
 
 #include <QString>
 #include <QStringList>
-#include <QProcess>
 #include <QThread>
 
 #include "certificate.h"
@@ -45,11 +44,6 @@
 private:
     const QString mCertListFile;
     const QStringList mInstructions;
-#ifdef WIN32
-    SHELLEXECUTEINFOW mExecInfo;
-#else
-    QProcess cinstProc;
-#endif
 
 protected:
     void run();
--- a/ui/tests/CMakeLists.txt	Fri Apr 04 09:53:55 2014 +0200
+++ b/ui/tests/CMakeLists.txt	Fri Apr 04 09:54:19 2014 +0200
@@ -1,6 +1,9 @@
 set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR})
 
-include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/../../common)
+include_directories(${CMAKE_CURRENT_BINARY_DIR}
+   ${CMAKE_SOURCE_DIR}/ui
+   ${CMAKE_SOURCE_DIR}/common
+   ${CMAKE_SOURCE_DIR}/cinst)
 
 find_package(Qt5Test)
 include_directories(${Qt5Test_INCLUDE_DIRS})
@@ -15,7 +18,7 @@
 macro(add_m13_test _source _additional_sources)
   set(_test ${_source})
   get_filename_component(_name ${_source} NAME_WE)
-  set(_test_sources_with_resources ${_test} ${_additional_sources})
+  set(_test_sources_with_resources ${_test} "${_additional_sources};${CMAKE_CURRENT_SOURCE_DIR}/common.cpp")
   qt5_add_resources(_test_sources_with_resources
      ${CMAKE_CURRENT_SOURCE_DIR}/data/testdata.qrc)
   add_executable(${_name} ${_test_sources_with_resources})
@@ -29,22 +32,30 @@
 # Add the current source dir to the definition
 # so that it can be used in file names in the tests.
 add_definitions(-DSOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
-add_m13_test(certlistparsertest.cpp "${CERTIFICATELIST_SOURCES};${CMAKE_CURRENT_SOURCE_DIR}/common.cpp")
+add_m13_test(certlistparsertest.cpp "${CERTIFICATELIST_SOURCES}")
 
 # Downloader
 if (HIAWATHA_EXECUTABLE)
   set(DOWNLOADER_SOURCES_WITH_RESOURCES ${DOWNLOADER_SOURCES})
   qt5_add_resources(DOWNLOADER_SOURCES_WITH_RESOURCES ${M13UI_RESOURCES})
-  add_m13_test(downloadertest.cpp "${DOWNLOADER_SOURCES_WITH_RESOURCES};${CMAKE_CURRENT_SOURCE_DIR}/common.cpp")
+  add_m13_test(downloadertest.cpp "${DOWNLOADER_SOURCES_WITH_RESOURCES}")
 endif()
 
 # Cinstprocess
 add_m13_test(cinstprocesstest.cpp "${CERTIFICATELIST_SOURCES}")
+add_dependencies(cinstprocesstest cinst)
 add_m13_test(commontest.cpp "")
 
 if (WIN32)
    add_m13_test(windowsstoretest.cpp "${CERTIFICATELIST_SOURCES};${CMAKE_SOURCE_DIR}/cinst/windowsstore.c")
 endif (WIN32)
 
+if (NSS_FOUND)
+    include_directories(${NSS_INCLUDE_DIRS})
+    add_m13_test(nsstest.cpp "${CERTIFICATELIST_SOURCES};${CMAKE_SOURCE_DIR}/cinst/nssstore_linux.c")
+    target_link_libraries(nsstest ${NSS_LIBRARIES})
+    add_dependencies(nsstest mozilla)
+endif()
+
 #add_m13_test(${CMAKE_SOURCE_DIR}/ui/main.cpp "${M13UI_SOURCES}")
 
--- a/ui/tests/cinstprocesstest.cpp	Fri Apr 04 09:53:55 2014 +0200
+++ b/ui/tests/cinstprocesstest.cpp	Fri Apr 04 09:54:19 2014 +0200
@@ -1,17 +1,31 @@
 #include "cinstprocesstest.h"
 #include "certificatelist.h"
 #include "errorcodes.h"
+#include "common.h"
 
 #include <QDebug>
 #include <QDir>
 #include <QFile>
 #include <QProcess>
 
-#define RELATIVE_CINST_PATH "../../cinst/cinst"
+#define CINST_PATH_CANDIDATES "../../cinst/cinst" << \
+    "cinst" << "../../cinst/cinst.exe" << "cinst.exe";
 
-QProcess *CinstProcessTest::startCinstProcess() {
+QProcess *CinstProcessTest::startCinstProcess(const QStringList& args) {
+    QStringList cinstCandidates;
+    cinstCandidates << CINST_PATH_CANDIDATES;
+    QString processPath;
+    foreach (const QString& candidate, cinstCandidates) {
+        QFileInfo fi(candidate);
+        if (fi.isExecutable()) {
+            processPath = candidate;
+            break;
+        }
+    }
+
     QProcess *installerProcess = new QProcess();
-    installerProcess->setProgram(RELATIVE_CINST_PATH);
+    installerProcess->setArguments(args);
+    installerProcess->setProgram(processPath);
     installerProcess->start();
     installerProcess->waitForStarted();
     return installerProcess;
@@ -22,6 +36,7 @@
         qDebug() << "Stdout:" << proc->readAllStandardOutput(); \
         qDebug() << "Stderr:" << proc->readAllStandardError(); \
         qDebug() << "Exit code: " << proc->exitCode(); \
+        qDebug() << "Exit status: " << proc->exitStatus(); \
     } \
     QVERIFY(x)
 
@@ -34,104 +49,158 @@
 }
 
 void CinstProcessTest::testValidInput() {
-    QProcess* installerProcess = startCinstProcess();
-    QVERIFY(installerProcess->state() == QProcess::Running);
+    QStringList args;
+    args << "list=" + validListFile.fileName();
 
-    installerProcess->write("-----BEGIN CERTIFICATE LIST-----\r\n");
-    installerProcess->write(validList.rawData().toLatin1());
-    installerProcess->write("-----END CERTIFICATE LIST-----\r\n");
+    QTemporaryFile instructions;
+    instructions.open();
+    foreach (const Certificate &cert, validList.getCertificates()) {
+        instructions.write(cert.base64Line().toLatin1());
+        instructions.write("\n");
+    }
+    instructions.close();
 
-    foreach (const Certificate &cert, validList.getCertificates()) {
-        installerProcess->write(cert.base64Line().toLatin1());
-        installerProcess->write("\r\n");
-    }
+    args << "instructions=" + instructions.fileName();
 
+    QProcess* installerProcess = startCinstProcess(args);
     finishVerify(installerProcess, ERR_NO_ERROR);
 }
 
 void CinstProcessTest::initTestCase() {
-    QDir dataDir = QDir(SOURCE_DIR"/data/");
-    QString fileName = dataDir.absoluteFilePath("list-valid-signed.txt");
-    validList = CertificateList(fileName.toLocal8Bit().data());
+    QFile valid(":/list-valid-signed.txt");
+    valid.open(QIODevice::ReadOnly);
+    validListFile.open();
+    validListFile.write(valid.readAll());
+    valid.close();
+    validListFile.close();
+    validList = CertificateList(validListFile.fileName().toLocal8Bit().data());
+
+    QVERIFY(validList.isValid());
+
+    QFile invalid(":/list-invalid-signed.txt");
+    invalid.open(QIODevice::ReadOnly);
+    invalidListFile.open();
+    invalidListFile.write(invalid.readAll());
+    invalid.close();
+    invalidListFile.close();
+    invalidList = CertificateList(invalidListFile.fileName().toLocal8Bit().data());
+
+    QVERIFY(!invalidList.isValid());
+
+    QFile other(":/list-valid-other-signature.txt");
+    other.open(QIODevice::ReadOnly);
+    otherListFile.open();
+    otherListFile.write(other.readAll());
+    other.close();
+    otherListFile.close();
+    otherList = CertificateList(otherListFile.fileName().toLocal8Bit().data());
+
+    QVERIFY(!otherList.isValid());
+
+/* Set HOME or APPDATA so that nss stores are not touched 
+ * see nsstest for the real test of that code */
+#ifdef WIN32
+    QVERIFY(!setenv ("APPDATA", fakeHome.path().toLocal8Bit().constData(), 1));
+#else
+    QVERIFY(!setenv ("HOME", fakeHome.path().toLocal8Bit().constData(), 1));
+#endif
 }
 
 void CinstProcessTest::testNoList() {
     /* No list */
-    QProcess* installerProcess = startCinstProcess();
-    QVERIFY(installerProcess->state() == QProcess::Running);
-    installerProcess->write("-----BEGIN CERTIFICATE LIST-----\r\n");
-    installerProcess->write("-----END CERTIFICATE LIST-----\r\n");
+    QTemporaryFile emptyFile;
+    emptyFile.open();
+    emptyFile.close();
 
+    QStringList args;
+    args << "list=" + emptyFile.fileName();
+
+    QTemporaryFile instructions;
+    instructions.open();
     foreach (const Certificate &cert, validList.getCertificates()) {
-        installerProcess->write(cert.base64Line().toLatin1());
-        installerProcess->write("\r\n");
+        instructions.write(cert.base64Line().toLatin1());
+        instructions.write("\n");
     }
+    instructions.close();
+
+    args << "instructions=" + instructions.fileName();
+
+    QProcess* installerProcess = startCinstProcess(args);
     finishVerify(installerProcess, ERR_INVALID_INPUT_NO_LIST);
 }
 
 void CinstProcessTest::testGarbageInput() {
-    QProcess* installerProcess = startCinstProcess();
-    QVERIFY(installerProcess->state() == QProcess::Running);
-    /* Garbage */
-    installerProcess = startCinstProcess();
-    installerProcess->write("-----BEGIN CERTIFICATE LIST-----\r\n");
-    int retval=0;
-    int bytesWritten=0;
-    do {
-        char garbage[1030];
-        for (int i = 0; i < 1030; i++) {
-            garbage[i] = (char) qrand() % 255;
-        }
-        retval = installerProcess->write(garbage, 1030);
-        bytesWritten += retval;
-    } while (retval != -1 && bytesWritten < 15 *1024 *1024 );
+    QStringList args;
 
-    finishVerify(installerProcess, ERR_INVALID_INPUT);
+    QString garbage = getRandomDataFile(21*1024*1024);
+    args << "list=" + garbage;
+
+    QTemporaryFile instructions;
+    instructions.open();
+    foreach (const Certificate &cert, validList.getCertificates()) {
+        instructions.write(cert.base64Line().toLatin1());
+        instructions.write("\n");
+    }
+    instructions.close();
+
+    args << "instructions=" + instructions.fileName();
+
+    QProcess* installerProcess = startCinstProcess(args);
+    /* If the following failed there may be leftovers in /tmp */
+    finishVerify(installerProcess, ERR_INVALID_INPUT_NO_LIST);
+    QVERIFY(QFile::remove(garbage));
 }
 
 void CinstProcessTest::testNoInput() {
-    QProcess* installerProcess = startCinstProcess();
-    QVERIFY(installerProcess->state() == QProcess::Running);
-
-    /* Nothing */
-    installerProcess = startCinstProcess();
+    QStringList args;
+    args << "list=foobazbuf";
+    args << "instructions=bazbuffoo";
+    QProcess* installerProcess;
+    installerProcess = startCinstProcess(args);
     finishVerify(installerProcess, ERR_INVALID_INPUT_NO_LIST);
 }
 
 
 void CinstProcessTest::testNoInstructions() {
     /* No instructions */
-    QProcess* installerProcess = startCinstProcess();
-    QVERIFY(installerProcess->state() == QProcess::Running);
-    installerProcess->write("-----BEGIN CERTIFICATE LIST-----\r\n");
-    installerProcess->write(validList.rawData().toLatin1());
-    installerProcess->write("-----END CERTIFICATE LIST-----\r\n");
+    QTemporaryFile emptyFile;
+    emptyFile.open();
+    emptyFile.close();
 
+    QStringList args;
+    args << "list=" + validListFile.fileName();
+    args << "instructions=" + emptyFile.fileName();
+
+    QProcess* installerProcess = startCinstProcess(args);
     finishVerify(installerProcess, ERR_NO_INSTRUCTIONS);
 }
 
 void CinstProcessTest::testInvalidInstruction() {
-    QProcess* installerProcess = startCinstProcess();
-    QVERIFY(installerProcess->state() == QProcess::Running);
+    QStringList args;
+    args << "list=" + validListFile.fileName();
 
-    installerProcess->write("-----BEGIN CERTIFICATE LIST-----\r\n");
-    installerProcess->write(validList.rawData().toLatin1());
-    installerProcess->write("-----END CERTIFICATE LIST-----\r\n");
+    QTemporaryFile instructions;
+    instructions.open();
+    foreach (const Certificate &cert, validList.getCertificates()) {
+        instructions.write(cert.base64Line().toLatin1());
+        instructions.write("\n");
+    }
+    instructions.write("I:ABCDEF\n");
+    instructions.close();
 
-    installerProcess->write("I:ABCDEF\r\n");
+    args << "instructions=" + instructions.fileName();
+
+    QProcess* installerProcess = startCinstProcess(args);
 
     finishVerify(installerProcess, ERR_INVALID_INSTRUCTIONS);
 }
 
 void CinstProcessTest::testUninstall() {
-    QProcess* installerProcess = startCinstProcess();
-    QVERIFY(installerProcess->state() == QProcess::Running);
+    QStringList args;
+    args << "list=" + validListFile.fileName();
+    args << "instructions=uninstall";
 
-    installerProcess->write("-----BEGIN CERTIFICATE LIST-----\r\n");
-    installerProcess->write(validList.rawData().toLatin1());
-    installerProcess->write("-----END CERTIFICATE LIST-----\r\n");
-
-    installerProcess->write("UNINSTALL\r\n");
+    QProcess* installerProcess = startCinstProcess(args);
 
     finishVerify(installerProcess, ERR_NO_ERROR);
 }
--- a/ui/tests/cinstprocesstest.h	Fri Apr 04 09:53:55 2014 +0200
+++ b/ui/tests/cinstprocesstest.h	Fri Apr 04 09:54:19 2014 +0200
@@ -6,7 +6,10 @@
  */
 
 #include <QObject>
+#include <QStringList>
 #include <QProcess>
+#include <QTemporaryFile>
+#include <QTemporaryDir>
 #include <QTest>
 #include "certificatelist.h"
 
@@ -15,7 +18,11 @@
     Q_OBJECT
 
 private:
-    QProcess* startCinstProcess();
+    QProcess* startCinstProcess(const QStringList& args = QStringList());
+    QTemporaryFile validListFile;
+    QTemporaryFile otherListFile;
+    QTemporaryFile invalidListFile;
+    QTemporaryDir fakeHome;
     CertificateList validList;
     CertificateList otherList;
     CertificateList invalidList;
--- a/ui/tests/data/NOTES	Fri Apr 04 09:53:55 2014 +0200
+++ b/ui/tests/data/NOTES	Fri Apr 04 09:54:19 2014 +0200
@@ -60,3 +60,8 @@
     CERT=$(cat valid_ssl_rsa.pem | grep -v "\-\-\-\-" | tr -d "\\n")
     echo -e R:${CERT}\\r >> list-valid.txt
 done
+
+# NSS
+mkdir nss
+certutil -d nss -A -i valid_ssl_rsa.pem -n "test" -t c,C
+certutil -d nss -D -n "test"
Binary file ui/tests/data/nss/cert8.db has changed
Binary file ui/tests/data/nss/key3.db has changed
Binary file ui/tests/data/nss/secmod.db has changed
--- a/ui/tests/data/testdata.qrc	Fri Apr 04 09:53:55 2014 +0200
+++ b/ui/tests/data/testdata.qrc	Fri Apr 04 09:54:19 2014 +0200
@@ -1,6 +1,11 @@
 <!DOCTYPE RCC><RCC version="1.0">
 <qresource>
     <file>list-valid-signed.txt</file>
+    <file>list-invalid-signed.txt</file>
+    <file>list-valid-other-signature.txt</file>
+    <file>nss/cert8.db</file>
+    <file>nss/key3.db</file>
+    <file>nss/secmod.db</file>
 </qresource>
 </RCC>
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/tests/nsstest.cpp	Fri Apr 04 09:54:19 2014 +0200
@@ -0,0 +1,197 @@
+#include <cert.h>
+#include <certdb.h>
+#include <certt.h>
+
+#include <nss.h>
+#include <pk11pub.h>
+
+#include "nsstest.h"
+#include "nssstore.h"
+#include "strhelp.h"
+
+#include <QTest>
+
+QList<QByteArray> NSSTest::get_nss_certs (QTemporaryDir *nssDir)
+{
+  CERTCertList *list;
+  CERTCertListNode *node;
+  QList<QByteArray> retval;
+
+  if (NSS_Initialize(nssDir->path().toLocal8Bit().constData(),
+              "", "", "secmod.db", NSS_INIT_READONLY)
+      == SECSuccess)
+    {
+      list = PK11_ListCerts(PK11CertListAll, NULL);
+      for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list);
+           node = CERT_LIST_NEXT(node)) {
+        retval << QByteArray((const char*)node->cert->derCert.data,
+                (int)node->cert->derCert.len);
+      }
+      CERT_DestroyCertList(list);
+      NSS_Shutdown();
+    }
+  else
+    {
+      qDebug("Could not open nss certificate store!\n");
+    }
+  return retval;
+}
+
+void NSSTest::setupTestDir(QTemporaryDir *nssDir)
+{
+    /* Copy the empty nss db in the temporary dir */
+    QFile::copy(":/nss/cert8.db", nssDir->path() + "/" +"cert8.db");
+    QFile::copy(":/nss/key3.db", nssDir->path() + "/" +"key3.db");
+    QFile::copy(":/nss/secmod.db", nssDir->path() + "/" +"secmod.db");
+
+    QVERIFY(QFile::setPermissions(nssDir->path() + "/" +"cert8.db",
+                QFileDevice::ReadOwner | QFileDevice::WriteOwner));
+    QVERIFY(QFile::setPermissions(nssDir->path() + "/" +"key3.db",
+                QFileDevice::ReadOwner | QFileDevice::WriteOwner));
+    QVERIFY(QFile::setPermissions(nssDir->path() + "/" +"secmod.db",
+                QFileDevice::ReadOwner | QFileDevice::WriteOwner));
+}
+
+void NSSTest::initTestCase() {
+
+    /* Set up a temporary list */
+    QFile res(":/list-valid-signed.txt");
+    res.open(QIODevice::ReadOnly);
+    validListFile.open();
+    validListFile.write(res.readAll());
+    validListFile.close();
+
+    setupTestDir(&ffNSSDir);
+    setupTestDir(&tbNSSDir);
+
+    validList = CertificateList(validListFile.fileName().toLocal8Bit().data());
+
+    /* Create the profiles.ini `s set environment variables*/
+#ifndef WIN32
+    QVERIFY(!setenv ("HOME", fakeHome.path().toLocal8Bit().constData(), 1));
+    fakeFirefoxDir = QDir(fakeHome.path() + "/.mozilla/firefox");
+    fakeThunderbirdDir = QDir(fakeHome.path() + "/.thunderbird");
+#else
+    QVERIFY(!setenv ("APPDATA", fakeHome.path().toLocal8Bit().constData(), 1));
+    fakeFirefoxDir = QDir(fakeHome.path() + "/Mozilla/firefox");
+    fakeThunderbirdDir = QDir(fakeHome.path() + "/Thunderbird");
+#endif
+    QVERIFY(fakeFirefoxDir.mkpath(fakeFirefoxDir.path()));
+    QVERIFY(fakeThunderbirdDir.mkpath(fakeThunderbirdDir.path()));
+
+    QFile mozProfile(fakeFirefoxDir.absoluteFilePath("profiles.ini"));
+    QFile tbProfile(fakeThunderbirdDir.absoluteFilePath("profiles.ini"));
+
+    /* Write profiles */
+    QVERIFY(mozProfile.open(QIODevice::WriteOnly));
+    QTextStream ffStream(&mozProfile);
+    ffStream << endl << "[General]"<<
+        "StartWithLastProfile=1" << endl <<
+        "[Profile0]" << endl <<
+        "Name=default" << endl <<
+        "IsRelative=1" << endl <<
+        "Path=" << fakeFirefoxDir.relativeFilePath(ffNSSDir.path()) << endl;
+    ffStream.flush();
+    mozProfile.close();
+
+    QVERIFY(tbProfile.open(QIODevice::WriteOnly));
+    QTextStream tbStream(&tbProfile);
+    tbStream << endl << "[General]"<<
+        "StartWithLastProfile=1" << endl <<
+        "[Profile102]" << endl <<
+        "Name=default" << endl <<
+        "IsRelative=0" << endl <<
+        "Path=" << tbNSSDir.path() << endl;
+    tbStream.flush();
+    tbProfile.close();
+}
+
+void NSSTest::testInstRemove() {
+    char ** to_install = NULL,
+         ** to_remove = NULL;
+
+    QList<Certificate> instList;
+
+    /* Install all certificates */
+    foreach (const Certificate &cert, validList.getCertificates()) {
+        if (!cert.isInstallCert())
+            continue;
+        instList << cert;
+        strv_append (&to_install, cert.base64Line().toLatin1().constData() + 2,
+                cert.base64Line().size() - 2);
+    }
+    QVERIFY((size_t) instList.size() == strv_length(to_install));
+
+    QVERIFY(write_stores_nss(to_install, to_remove) == 0);
+
+    {
+        /* Verify that everything is installed */
+        QList<QByteArray> installedCertsFF = get_nss_certs(&ffNSSDir);
+        QList<QByteArray> installedCertsTB = get_nss_certs(&tbNSSDir);
+
+        QVERIFY(installedCertsFF.size() == instList.size());
+        QVERIFY(installedCertsFF == installedCertsTB);
+
+        for (int i = 0; to_install[i]; i++) {
+            QByteArray bai = QByteArray::fromBase64(to_install[i]);
+            QVERIFY(installedCertsFF.contains(bai));
+        }
+    }
+
+    {
+        /* Remove one certificate */
+        QVERIFY(instList.size() > 2);
+        strv_append (&to_remove, to_install[1], qstrlen(to_install[1]));
+
+        QVERIFY(write_stores_nss(NULL, to_remove) == 0);
+
+        QList<QByteArray> installedCertsFF = get_nss_certs(&ffNSSDir);
+        QList<QByteArray> installedCertsTB = get_nss_certs(&tbNSSDir);
+
+        QVERIFY(installedCertsFF == installedCertsTB);
+
+        QByteArray bai = QByteArray::fromBase64(to_install[1]);
+        QVERIFY(!installedCertsTB.contains(bai));
+
+        QVERIFY((size_t)installedCertsTB.size() == strv_length(to_install) - 1);
+
+        for (int i = 0; to_install[i]; i++) {
+            if (i == 1) {
+                continue;
+            }
+            QByteArray bai = QByteArray::fromBase64(to_install[i]);
+            QVERIFY(installedCertsTB.contains(bai));
+        }
+    }
+
+    {
+        /* Readd all certificates check for duplication*/
+        QVERIFY(write_stores_nss(to_install, NULL) == 0);
+
+        QList<QByteArray> installedCertsFF = get_nss_certs(&ffNSSDir);
+        QList<QByteArray> installedCertsTB = get_nss_certs(&tbNSSDir);
+
+        QVERIFY(installedCertsFF == installedCertsTB);
+
+        QVERIFY((size_t)installedCertsTB.size() == strv_length(to_install));
+
+        for (int i = 0; to_install[i]; i++) {
+            QByteArray bai = QByteArray::fromBase64(to_install[i]);
+            QVERIFY(installedCertsTB.contains(bai));
+        }
+    }
+
+    {
+        /* Remove all certificates */
+        QVERIFY(write_stores_nss(NULL, to_install) == 0);
+
+        QList<QByteArray> installedCertsFF = get_nss_certs(&ffNSSDir);
+        QList<QByteArray> installedCertsTB = get_nss_certs(&tbNSSDir);
+
+        QVERIFY(installedCertsFF == installedCertsTB);
+
+        QVERIFY(installedCertsTB.size() == 0);
+    }
+}
+
+QTEST_GUILESS_MAIN (NSSTest);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/tests/nsstest.h	Fri Apr 04 09:54:19 2014 +0200
@@ -0,0 +1,32 @@
+#ifndef NSSTEST_H
+#define NSSTEST_H
+
+#include <QTemporaryFile>
+#include <QTemporaryDir>
+#include <QObject>
+#include <QList>
+#include <QByteArray>
+
+#include "certificatelist.h"
+
+class NSSTest: public QObject
+{
+    Q_OBJECT
+
+    QTemporaryDir fakeHome;
+    QDir fakeFirefoxDir;
+    QDir fakeThunderbirdDir;
+    QTemporaryDir ffNSSDir;
+    QTemporaryDir tbNSSDir;
+    CertificateList validList;
+    QTemporaryFile validListFile;
+private:
+    QList<QByteArray> get_nss_certs(QTemporaryDir *nssDir);
+    void setupTestDir(QTemporaryDir *nssDir);
+
+private Q_SLOTS:
+    void initTestCase();
+    void testInstRemove();
+};
+
+#endif // NSSTEST_H

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