changeset 360:c0eac5c8c245

Keep working on nssstore_win and clarify its specification
author Andre Heinecke <andre.heinecke@intevation.de>
date Thu, 10 Apr 2014 20:20:02 +0200
parents f6ce186cebc2
children b67dd46cd4a9
files cinst/nssstore_win.c
diffstat 1 files changed, 373 insertions(+), 124 deletions(-) [+]
line wrap: on
line diff
--- a/cinst/nssstore_win.c	Thu Apr 10 17:50:44 2014 +0200
+++ b/cinst/nssstore_win.c	Thu Apr 10 20:20:02 2014 +0200
@@ -2,21 +2,57 @@
 
 /* @file
    @brief Windows implementation of nssstore process control.
+
+   The windows process will write an instructions file for
+   the mozilla process into the current users temp directory
+   (%APPDATA%/Local/Temp/) and start the NSS installation process to
+   exectute those instructions. If the current process is elevated
+   the NSS process is run with a restricted token.
+   The execution of the mozilla process is not monitored.
+   You have to refer to the system log to check which certificates were
+   installed / removed by it.
+
+   If the installation process is running elevated it
+   will create the file in the ProgramData directory in
+   a subdirectory with the defined application name.
+   %PROGRAMDATA%/$APPLICATION_NAME
+   with the file name:
+    current_selection.txt
+   The folder will have restricted permissions so
+   that only Administrators are allowed to access it.
+
+   Additionally if this process is Elevated it also starts the
+   NSS installation process in default profile mode once to change
+   the default NSS certificate databases for new profiles.
+
+   The process then adds a new RunOnce registry key
+   for each user on the system that executes the NSS installation
+   process on login to make sure it is launched once in the
+   security context of that user.
 */
 
 #include <windows.h>
 #include <stdio.h>
-#include <strsafe.h>
 #include <stdbool.h>
 #include <userenv.h>
 #include <io.h>
+#include <accctrl.h>
+#include <aclapi.h>
+#include <shlobj.h>
 
 #include "logging.h"
 #include "util.h"
+#include "strhelp.h"
 
-#define PROCESS_TIMEOUT 30000 /* In milliseconds */
+#ifndef APPNAME
+#define APPNAME L"cinst"
+#endif
 
-#define BUFSIZE 4096 /* used for reading childs stdout */
+#ifndef SELECTION_FILE_NAME
+#define SELECTION_FILE_NAME L"currently_selected.txt"
+#endif
+
+#define PROCESS_TIMEOUT 30000
 
 #define PRINTLASTERROR(msg) \
   char *my_error = getLastErrorMsg(); \
@@ -81,178 +117,391 @@
 
 /**@brief Start the process to install / remove
 *
-* Creates a child process with the Security handle specified in hToken
-* sends the instructions and then waits for the process to finish.
+* Starts the NSS installation process for the current user
 *
-* If the process is not done in PROCESS_TIMEOUT seconds this assumes an
-* unknown error happened.
-*
-* @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] hToken handle to the primary token that is used to install
-* certificates.
+* @param [in] selection_file filename of the file containing
+*             the users installall / remove selection.
 *
 * @returns true on success, false on error.
 */
 static bool
-start_procces_for_user (char **to_install, char **to_remove,
-                        HANDLE hToken)
+start_procces_for_user (wchar_t *selection_file)
 {
-  HANDLE h_stdin_child_r = NULL,
-         h_stdin_child_w = NULL,
-         h_stdout_child_r = NULL,
-         h_stdout_child_w = NULL;
-  SECURITY_ATTRIBUTES saAttr = {0};
+  HANDLE hToken = NULL;/*,
+         hChildToken = NULL;*/
   /* TODO get this as absolute path based on current module location */
-  LPWSTR lpApplicationName = L"mozilla.exe";
+  LPWSTR lpApplicationName = L"mozilla.exe",
+         lpCommandLine;
   PROCESS_INFORMATION piProcInfo = {0};
   STARTUPINFOW siStartInfo = {0};
-  LPVOID lpEnvironment = NULL;
   BOOL success = FALSE;
-  int retval = -1;
-
-  saAttr.nLength = sizeof (SECURITY_ATTRIBUTES);
-  saAttr.bInheritHandle = TRUE;
-  saAttr.lpSecurityDescriptor = NULL; /* Use default */
-
-  /* Create a pipe for the child process's STDIN. */
-  if (!CreatePipe (&h_stdin_child_r, &h_stdin_child_w, &saAttr, 0))
-    {
-      PRINTLASTERROR ("Create pipe failed.\n");
-      return -1;
-    }
+  size_t cmd_line_len = 0;
 
-  /* Create a pipe for the child process's STDOUT. */
-  if (!CreatePipe (&h_stdout_child_r, &h_stdout_child_w, &saAttr, 0))
+  if (!selection_file)
     {
-      PRINTLASTERROR ("Create pipe failed.\n");
-      return -1;
-    }
-
-  /* Ensure that read/write is properly inherited */
-  if (!SetHandleInformation (h_stdin_child_w, HANDLE_FLAG_INHERIT, 0) ||
-      !SetHandleInformation (h_stdout_child_r, HANDLE_FLAG_INHERIT, 0))
-    {
-      PRINTLASTERROR ("SetHandleInformation failed.\n");
-      goto closepipes;
-    }
-
-  /* Create the environment for the user */
-  if (!CreateEnvironmentBlock (&lpEnvironment, hToken, FALSE))
-    {
-      PRINTLASTERROR ("Failed to create the environment.\n");
-      goto closepipes;
+      ERRORPRINTF ("Invalid call\n");
+      return false;
     }
 
   /* set up handles. stdin and stdout go to the same stdout*/
   siStartInfo.cb = sizeof (STARTUPINFO);
-  siStartInfo.hStdError = h_stdout_child_w;
-  siStartInfo.hStdOutput = h_stdout_child_w;
-  siStartInfo.hStdInput = h_stdin_child_r;
-  siStartInfo.dwFlags = STARTF_USESTDHANDLES;
+
+  if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken))
+    {
+      PRINTLASTERROR("Failed to get current handle.");
+      return false;
+    }
+  /* TODO! if (is_elevated())
+     restrict token -> hChildToken
+  */
+
+  cmd_line_len = wcslen (lpApplicationName) + wcslen(selection_file) + 1;
+  lpCommandLine = xmalloc (cmd_line_len * sizeof(wchar_t));
+
+  wcscpy (lpCommandLine, lpApplicationName);
+  wcscat (lpCommandLine, selection_file);
 
   success = CreateProcessAsUserW (hToken,
                                   lpApplicationName,
-                                  NULL, /* Commandline */
+                                  lpCommandLine, /* Commandline */
                                   NULL, /* Process attributes. Take hToken */
                                   NULL, /* Thread attribues. Take hToken */
-                                  TRUE, /* Inherit Handles */
-                                  CREATE_UNICODE_ENVIRONMENT, /* Creation flags. */
-                                  lpEnvironment,
+                                  FALSE, /* Inherit Handles */
+                                  0, /* Creation flags. */
+                                  NULL, /* Inherit environment */
                                   NULL, /* Current working directory */
                                   &siStartInfo,
                                   &piProcInfo);
+  xfree (lpCommandLine);
   if (!success)
     {
       PRINTLASTERROR ("Failed to create process.\n");
-      goto closepipes;
-    }
-
-  if (!write_instructions (to_install, h_stdin_child_w, false))
-    {
-      ERRORPRINTF ("Failed to write install instructions.\n");
-      goto closepipes;
-    }
-  if (!write_instructions (to_remove, h_stdin_child_w, true))
-    {
-      ERRORPRINTF ("Failed to write remove instructions.\n");
-      goto closepipes;
+      return false;
     }
 
-  /* Close the Handle so that the child knows we are finished
-     telling it what to do */
-  CloseHandle (h_stdin_child_w);
-  h_stdin_child_w = NULL;
-
-#ifndef RELEASE_BUILD
-  /* print childs stdout / stderr to parents stdout */
-    {
-      HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
-      DWORD read_bytes = 0;
-      DWORD written = 0;
-      bool success;
-      char buf[BUFSIZE];
-
-      if (!stdout_handle || stdout_handle == INVALID_HANDLE_VALUE)
-        {
-          /* Should not happen */
-          ERRORPRINTF("Failed to get stdout handle.\n");
-          goto closeprocess;
-        }
-
-      for (;;)
-        {
-          success = ReadFile (h_stdout_child_r, buf, BUFSIZE, &read_bytes, NULL);
-          if(!success || read_bytes == 0)
-            break;
-
-          success = WriteFile (stdout_handle, buf,
-                               read_bytes, &written, NULL);
-          if (!success || written != read_bytes)
-            break;
-        }
-    }
-#endif
-
   if (WaitForSingleObject (piProcInfo.hProcess, PROCESS_TIMEOUT) != WAIT_OBJECT_0)
     {
       /* Should not happen... */
       ERRORPRINTF ("Failed to wait for process.\n");
+      if (piProcInfo.hProcess)
+        CloseHandle (piProcInfo.hProcess);
+      if (piProcInfo.hThread)
+        CloseHandle (piProcInfo.hThread);
+      return false;
     }
-
-closeprocess:
   if (piProcInfo.hProcess)
     CloseHandle (piProcInfo.hProcess);
   if (piProcInfo.hThread)
     CloseHandle (piProcInfo.hThread);
+  return true;
+}
 
-closepipes:
-  if (lpEnvironment)
-    DestroyEnvironmentBlock (lpEnvironment);
-  if (h_stdin_child_w)
-    CloseHandle (h_stdin_child_w);
-  if (h_stdin_child_r)
-    CloseHandle (h_stdin_child_r);
-  if (h_stdout_child_w)
-    CloseHandle (h_stdout_child_w);
-  if (h_stdout_child_r)
-    CloseHandle (h_stdout_child_r);
+/**@brief Create a directory with restricted access rights
+  *
+  * This creates a security attributes structure that restricts
+  * write access to the Administrators group but allows everyone to read files
+  * in that directory.
+  * Basically a very complicated vetrsion of mkdir path -m 604
+  *
+  * If the directory exists the permissions of that directory are checked if
+  * they are acceptable and true or false is returned accordingly.
+  *
+  * Code based on msdn example:
+  * http://msdn.microsoft.com/en-us/library/windows/desktop/aa446595%28v=vs.85%29.aspx
+  *
+  * @param[in] path Path of the directory to create
+  *
+  * @returns true on success of if the directory exists, false on error
+  */
+bool
+create_restricted_directory (LPWSTR path)
+{
+  bool retval = false;
+  PSID everyone_SID = NULL,
+       admin_SID = NULL;
+  PACL access_control_list = NULL;
+  PSECURITY_DESCRIPTOR descriptor = NULL;
+  EXPLICIT_ACCESS explicit_access[2];
+  SID_IDENTIFIER_AUTHORITY world_identifier = {SECURITY_WORLD_SID_AUTHORITY},
+                           admin_identifier = {SECURITY_NT_AUTHORITY};
+  SECURITY_ATTRIBUTES security_attributes;
+
+  ZeroMemory(&security_attributes, sizeof(security_attributes));
+  ZeroMemory(&explicit_access, 2 * sizeof(EXPLICIT_ACCESS));
+
+  /* Create a well-known SID for the Everyone group. */
+  if(!AllocateAndInitializeSid(&world_identifier, /* top-level identifier */
+                               1, /* subauthorties count */
+                               SECURITY_WORLD_RID, /* Only one authority */
+                               0, 0, 0, 0, 0, 0, 0, /* No other authorities*/
+                               &everyone_SID))
+    {
+      PRINTLASTERROR ("Failed to allocate world sid.\n");
+      return false;
+    }
+
+  /* Initialize the first EXPLICIT_ACCESS structure for an ACE.
+     to allow everyone read access */
+  explicit_access[0].grfAccessPermissions = GENERIC_READ; /* Give read access */
+  explicit_access[0].grfAccessMode = SET_ACCESS; /* Overwrite other access for all users */
+  explicit_access[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; /* make it stick */
+  explicit_access[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+  explicit_access[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
+  explicit_access[0].Trustee.ptstrName  = (LPTSTR) everyone_SID;
+
+  /* Create the SID for the BUILTIN\Administrators group. */
+  if(!AllocateAndInitializeSid(&admin_identifier,
+                               2,
+                               SECURITY_BUILTIN_DOMAIN_RID, /*BUILTIN\ */
+                               DOMAIN_ALIAS_RID_ADMINS, /*\Administrators */
+                               0, 0, 0, 0, 0, 0, /* No other */
+                               &admin_SID))
+    {
+      PRINTLASTERROR ("Failed to allocate admin sid.");
+      goto done;
+    }
+
+  /* explicit_access[1] grants admins full rights for this object and inherits
+     it to the children */
+  explicit_access[1].grfAccessPermissions = GENERIC_ALL;
+  explicit_access[1].grfAccessMode = SET_ACCESS;
+  explicit_access[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+  explicit_access[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+  explicit_access[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
+  explicit_access[1].Trustee.ptstrName = (LPTSTR) admin_SID;
+
+  /* Set up the ACL structure. */
+  if (ERROR_SUCCESS != SetEntriesInAcl(2, explicit_access, NULL, &access_control_list))
+    {
+      PRINTLASTERROR ("Failed to set up Acl.");
+      goto done;
+    }
+
+  /* Initialize a security descriptor */
+  descriptor = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR,
+                                                 SECURITY_DESCRIPTOR_MIN_LENGTH);
+  if (descriptor == NULL)
+    {
+      PRINTLASTERROR("Failed to allocate descriptor.");
+      goto done;
+    }
+
+  if (!InitializeSecurityDescriptor(descriptor,
+                                    SECURITY_DESCRIPTOR_REVISION))
+    {
+      PRINTLASTERROR("Failed to initialize descriptor.");
+      goto done;
+    }
+
+  /* Now we add the ACL to the the descriptor */
+  if (!SetSecurityDescriptorDacl(descriptor,
+                                 TRUE,     /* bDaclPresent flag */
+                                 access_control_list,
+                                 FALSE))   /* not a default DACL */
+    {
+      PRINTLASTERROR("Failed to set security descriptor.");
+      goto done;
+    }
+
+  /* Finally set up the security attributes structure */
+  security_attributes.nLength = sizeof (SECURITY_ATTRIBUTES);
+  security_attributes.lpSecurityDescriptor = descriptor;
+  security_attributes.bInheritHandle = FALSE;
+
+  /* Use the security attributes to create the directory */
+  if (!CreateDirectoryW(path, &security_attributes))
+    {
+      DWORD err = GetLastError();
+      if (err == ERROR_ALREADY_EXISTS)
+        {
+          /* Verify that the directory has the correct rights */
+          // TODO
+          retval = true;
+          goto done;
+        }
+      ERRORPRINTF ("Failed to create directory. Err: %lu", err);
+    }
+  retval = true;
+
+done:
+
+  if (everyone_SID)
+    FreeSid(everyone_SID);
+  if (admin_SID)
+    FreeSid(admin_SID);
+  if (access_control_list)
+    LocalFree(access_control_list);
+  if (descriptor)
+    LocalFree(descriptor);
 
   return retval;
 }
 
+/**@brief Writes the selection file containing the instructions
+ *
+ * If the process is running elevated the instructions are
+ * written to the global ProgramData directory otherwise
+ * they are written in the temporary directory of the current user.
+ *
+ * If the return value is not NULL it needs to be freed by the caller.
+ * The returned path will contain backslashes as directory seperators.
+ *
+ * @param[in] to_install Certificates that should be installed
+ * @param[in] to_remove Certificates that should be removed
+ * @returns pointer to the absolute filename of the selection file or NULL
+ */
+wchar_t *
+write_selection_file (char **to_install, char **to_remove)
+{
+  wchar_t *folder_name = NULL,
+          *path = NULL;
+  bool elevated = is_elevated();
+  HRESULT result = E_FAIL;
+  HANDLE hFile = NULL;
+  size_t path_len;
+
+  if (!elevated)
+    {
+      /* TODO */
+    }
+
+#if 0
+  result = SHGetKnownFolderPath (FOLDERID_ProgramData, /* Get program data dir */
+                                 KF_FLAG_CREATE | /* Create if it does not exist */
+                                 KF_FLAG_INIT, /* Initialize it if created */
+                                 -1, /* Get it for the default user */
+                                 &folder_name);
+#endif
+  /* TODO figure out how to do this with mingw */
+  result = S_OK;
+  folder_name = L"C:\\ProgramData";
+
+  if (result != S_OK)
+    {
+      PRINTLASTERROR ("Failed to get folder path");
+      return NULL;
+    }
+
+  path_len = wcslen (folder_name) + wcslen (APPNAME) + 2; /* path + dirsep + \0 */
+  path_len += wcslen (SELECTION_FILE_NAME) + 1; /* filename + dirsep */
+
+  if (path_len >= MAX_PATH)
+    {
+      /* We could go and use the full 32,767 characters but this
+         should be a very weird setup if this is neccessary. */
+      ERRORPRINTF ("Path too long.\n");
+      return NULL;
+    }
+
+  path = xmalloc (path_len * sizeof (wchar_t));
+  if (wcscpy (path, folder_name) != 0)
+    {
+      ERRORPRINTF ("Failed to copy folder name.\n");
+#if 0
+      CoTaskMemFree (folder_name);
+#endif
+      return NULL;
+    }
+
+#if 0
+  CoTaskMemFree (folder_name);
+#endif
+
+  if (wcscat (path, L"\\") != 0)
+    {
+      ERRORPRINTF ("Failed to cat dirsep.\n");
+      xfree(path);
+      return NULL;
+    }
+
+  if (wcscat (path, APPNAME) != 0)
+    {
+      ERRORPRINTF ("Failed to cat appname.\n");
+      xfree(path);
+      return NULL;
+    }
+
+  /* Security: if someone has created this directory before
+     it might be a symlink to another place that a users
+     wants us to grant read access to or makes us overwrite
+     something */
+  if(!create_restricted_directory (path))
+    {
+      ERRORPRINTF ("Failed to create directory\n");
+      xfree(path);
+      return NULL;
+    }
+
+  if (wcscat (path, L"\\") != 0)
+    {
+      ERRORPRINTF ("Failed to cat dirsep.\n");
+      xfree(path);
+      return NULL;
+    }
+
+  if (wcscat (path, SELECTION_FILE_NAME) != 0)
+    {
+      ERRORPRINTF ("Failed to cat filename.\n");
+      xfree(path);
+      return NULL;
+    }
+
+  hFile = CreateFileW(path,
+                      GENERIC_WRITE,
+                      0, /* don't share */
+                      NULL, /* use the security attributes from the folder */
+                      OPEN_ALWAYS,
+                      0,
+                      NULL);
+
+  if (hFile == INVALID_HANDLE_VALUE)
+    {
+      ERRORPRINTF ("Failed to create file\n");
+      xfree(path);
+      return NULL;
+    }
+  if (!write_instructions (to_install, hFile, false))
+    {
+      ERRORPRINTF ("Failed to write install instructions.\n");
+      CloseHandle(hFile);
+      xfree(path);
+      return NULL;
+    }
+  if (!write_instructions (to_remove, hFile, true))
+    {
+      ERRORPRINTF ("Failed to write remove instructions.\n");
+      CloseHandle(hFile);
+      xfree(path);
+      return NULL;
+    }
+  CloseHandle(hFile);
+
+  return path;
+}
+
 int
 write_stores_nss (char **to_install, char **to_remove)
 {
-  HANDLE hToken = NULL;
-  OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken);
+  wchar_t *selection_file_name = NULL;
 
-  /* TODO loop over all users */
-  if (!start_procces_for_user (to_install, to_remove, hToken))
+  selection_file_name = write_selection_file (to_install, to_remove);
+  if (!selection_file_name)
+    {
+      ERRORPRINTF ("Failed to write instructions.\n");
+      return -1;
+    }
+
+  DEBUGPRINTF ("Wrote selection file. Loc: %S\n", selection_file_name);
+
+  /* TODO loop over all users create startup entries for them*/
+
+  if (!start_procces_for_user (selection_file_name))
     {
       ERRORPRINTF ("Failed to run NSS installation process.\n");
+      xfree(selection_file_name);
       return -1;
     }
+  xfree(selection_file_name);
   return 0;
 }
 

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