view cinst/nssstore_win.c @ 1044:57e8218d4f41

Removed tag 0.9.1
author Andre Heinecke <andre.heinecke@intevation.de>
date Thu, 04 Sep 2014 16:13:05 +0200
parents 6684e5012b7a
children 317ee9dc4684
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#ifdef WIN32

/* @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 <winsafer.h>
#include <sddl.h>
#include <stdio.h>
#include <stdbool.h>
#include <userenv.h>
#include <io.h>
#include <accctrl.h>
#include <aclapi.h>

#include "logging.h"
#include "util.h"
#include "strhelp.h"

#ifndef APPNAME
#define APPNAME L"cinst"
#endif

/**@def The name of the nss installation process */
#define NSS_APP_NAME L"mozilla.exe"

#ifndef SELECTION_FILE_NAME
#define SELECTION_FILE_NAME L"currently_selected.txt"
#endif

/**@def The maximum time to wait for the NSS Process */
#define PROCESS_TIMEOUT 30000

/**@def The registry key to look for user profile directories */
#define PROFILE_LIST L"Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"
#define RUNONCE_PATH L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce"

struct profile_key_path
{
  char *sid;
  char *hive_path;
  struct profile_key_path *next;
};

/**
 * @brief combination of sid and hive path
 */
typedef struct profile_key_path pkp_t;

static void
pkp_t_free (pkp_t *item)
{
  if (!item)
    {
      return;
    }
  xfree (item->sid);
  xfree (item->hive_path);
  if (item->next)
    {
      pkp_t_free (item->next);
    }
  xfree (item);
}

/** @brief get a restricted access token to execute nss process
  *
  * This function uses the Software Restriction API to obtain the
  * access token for a process run als normal user.
  *
  * @returns A restricted handle or NULL on error.
  */
static HANDLE
get_restricted_token()
{
  SAFER_LEVEL_HANDLE user_level = NULL;
  HANDLE retval = NULL;
  SID_IDENTIFIER_AUTHORITY medium_identifier = {SECURITY_MANDATORY_LABEL_AUTHORITY};
  PSID medium_sid = NULL;
  TOKEN_MANDATORY_LABEL integrity_label;

  memset (&integrity_label, 0, sizeof (integrity_label));

  if (!SaferCreateLevel(SAFER_SCOPEID_USER,
                        SAFER_LEVELID_NORMALUSER,
                        SAFER_LEVEL_OPEN, &user_level, NULL))
    {
      PRINTLASTERROR ("Failed to create user level.\n");
      return NULL;
    }

  if (!SaferComputeTokenFromLevel(user_level, NULL, &retval, 0, NULL))
    {
      SaferCloseLevel(user_level);
      return NULL;
    }

  SaferCloseLevel(user_level);

  /* Set the SID to medium it will still be high otherwise. Even if
  there is no high access allowed. */
  if (!AllocateAndInitializeSid(&medium_identifier,
                               1,
                               SECURITY_MANDATORY_MEDIUM_RID,
                               0,
                               0,
                               0,
                               0,
                               0,
                               0,
                               0,
                               &medium_sid))
    {
      PRINTLASTERROR ("Failed to initialize sid.\n");
      return NULL;
    }

  integrity_label.Label.Attributes = SE_GROUP_INTEGRITY;
  integrity_label.Label.Sid        = medium_sid;

  if (!SetTokenInformation(retval,
                           TokenIntegrityLevel,
                           &integrity_label,
                           sizeof(TOKEN_MANDATORY_LABEL)))
    {
      PRINTLASTERROR ("Failed to set token integrity.\n");
      return NULL;
    }

  return retval;
}

/**@brief Write strv of instructions to a handle
*
* Writes the null terminated list of instructions to
* the handle.
*
* @param [in] certificates base64 encoded der certificate to write
* @param [in] write_handle handle to write to
* @param [in] remove weather the certificate should be installed or removed
*
* @returns true on success, false on failure
*/
static bool
write_instructions(char **certificates, HANDLE write_handle,
                   bool remove)
{
  bool retval = false;
  int i = 0;
  const char *line_end = "\r\n";
  char *line_start = NULL;

  if (!certificates)
    {
      return true;
    }

  line_start = remove ? "R:" : "I:";

  for (i = 0; certificates[i]; i++)
    {
      DWORD written = 0;
      DWORD inst_len = strlen (certificates[i]);
      retval = WriteFile (write_handle, (LPCVOID) line_start, 2, &written, NULL);
      if (!retval)
        {
          PRINTLASTERROR ("Failed to write line start\n");
          return false;
        }
      if (written != 2)
        {
          ERRORPRINTF ("Failed to write line start\n");
          retval = false;
          return false;
        }
      written = 0;
      retval = WriteFile (write_handle, (LPCVOID) certificates[i], inst_len, &written, NULL);
      if (!retval)
        {
          PRINTLASTERROR ("Failed to write certificate\n");
          return false;
        }
      if (inst_len != written)
        {
          ERRORPRINTF ("Failed to write everything\n");
          retval = false;
          return false;
        }
      written = 0;
      retval = WriteFile (write_handle, (LPCVOID) line_end, 2, &written, NULL);
      if (!retval)
        {
          PRINTLASTERROR ("Failed to write line end\n");
          return false;
        }
      if (written != 2)
        {
          ERRORPRINTF ("Failed to write full line end\n");
          retval = false;
          return false;
        }
    }
  return true;
}
/**@brief Get the path to all users default registry hive
 *
 * Enumerates the keys in #PROFILE_LIST and retuns a
 * list of their profile path / sid pairs with the utf-8 encoded paths to
 * their suggestedregistry hive location.
 *
 * Users with an SID not starting with S-1-5-21- are ignored
 * as is the current user.
 *
 * The return value should be freed with pkp_t_free
 *
 * @returns a newly allocated strv of the paths to the registry hives or NULL
 */
static pkp_t*
locate_other_hives()
{
  HKEY profile_list = NULL;
  int ret = 0;
  DWORD index = 0,
        key_len = 257;
  /* According to
     http://msdn.microsoft.com/en-us/library/windows/desktop/ms724872%28v=vs.85%29.aspx
     a registry key is limited to 255 characters. But according to
     http://www.sepago.de/e/holger/2010/07/20/how-long-can-a-registry-key-name-really-be
     the actual limit is 256 + \0 thus we create a buffer for 257 wchar_t's*/
  wchar_t key_name[257],
          *current_user_sid = NULL;
  pkp_t *retval = NULL,
         *cur_item = NULL;
  bool error = true;
  PSID current_user = NULL;

  ret = RegOpenKeyExW (HKEY_LOCAL_MACHINE, PROFILE_LIST, 0,
                       KEY_READ, &profile_list);
  if (ret != ERROR_SUCCESS)
    {
      ERRORPRINTF ("Failed to open profile list. Error: %i", ret);
      return NULL;
    }

  /* Obtain the current user sid to prevent it from being returned. */
  current_user = get_process_owner (GetCurrentProcess());

  if (!current_user)
    {
      ERRORPRINTF ("Failed to get the current user.");
      goto done;
    }

  if (!ConvertSidToStringSidW (current_user, &current_user_sid))
    {
      PRINTLASTERROR ("Failed to convert sid to string.");
      goto done;
    }

  while ((ret = RegEnumKeyExW (profile_list, index++,
                               key_name, &key_len,
                               NULL, NULL, NULL, NULL)) == ERROR_SUCCESS)
    {
      char *profile_path = NULL;
      wchar_t *key_path = NULL;
      size_t key_path_len = 0,
             profile_path_len = 0;

      if (key_len == 257)
        {
          ERRORPRINTF ("Registry key too long.");
          goto done;
        }

      /* Reset key_len to buffer size */
      key_len = 257;

      if (wcsncmp (L"S-1-5-21-", key_name, 9) != 0 ||
          wcscmp (current_user_sid, key_name) == 0)
        {
          /* S-1-5-21 is the well known prefix for local users. Skip all
             others and the current user*/
          continue;
        }

      key_path_len = key_len + wcslen(PROFILE_LIST L"\\") + 1;
      key_path = xmalloc (key_path_len * sizeof (wchar_t));

      wcscpy_s (key_path, key_path_len, PROFILE_LIST L"\\");
      wcscat_s (key_path, key_path_len, key_name);
      key_path[key_path_len - 1] = '\0';

      DEBUGPRINTF ("Key : %S", key_name);
      profile_path = read_registry_string (HKEY_LOCAL_MACHINE,
                                           key_path, L"ProfileImagePath");
      xfree (key_path);

      if (profile_path == NULL)
        {
          ERRORPRINTF ("Failed to get profile path.");
          continue;
        }
      profile_path_len = strlen (profile_path);
      str_append_str (&profile_path, &profile_path_len, "\\ntuser.dat", 11);
      if (retval == NULL)
        {
          retval = xmalloc (sizeof (pkp_t));
          cur_item = retval;
        }
      else
        {
          cur_item->next = xmalloc (sizeof(pkp_t));
          cur_item = cur_item->next;
        }
      cur_item->hive_path = profile_path;
      cur_item->sid = wchar_to_utf8 (key_name, wcslen(key_name));
      cur_item->next = NULL;

      DEBUGPRINTF ("Trying to access registry hive: %s", profile_path);
    }

  if (ret != ERROR_NO_MORE_ITEMS)
    {
      ERRORPRINTF ("Failed to enumeratre profile list. Error: %i", ret);
      goto done;
    }

  error = false;

done:
  xfree (current_user);

  RegCloseKey (profile_list);

  if (current_user_sid)
    {
      LocalFree (current_user_sid);
    }

  if (error)
    {
      pkp_t_free (retval);
      retval = NULL;
    }

  return retval;
}

/** @brief Build the command line for the NSS installation process
  *
  * Caller has to free the return value
  *
  * @param [in] selection_file the certificates to install
  *
  * @returns the command line to install the certificates. */
static wchar_t*
get_command_line(wchar_t *selection_file)
{
  LPWSTR retval;
  char *install_dir = get_install_dir();
  wchar_t *w_inst_dir;
  size_t cmd_line_len = 0;

  if (install_dir == NULL)
    {
      ERRORPRINTF ("Failed to get installation directory");
      return NULL;
    }

  w_inst_dir = utf8_to_wchar (install_dir, strlen(install_dir));
  xfree (install_dir);

  if (w_inst_dir == NULL)
    {
      ERRORPRINTF ("Failed to convert installation directory");
      return NULL;
    }

  /* installdir + dirsep +  quotes + process name + space + quotes + selection_file
     + NULL */
  cmd_line_len = wcslen (w_inst_dir) + 1 + 2 + wcslen (NSS_APP_NAME) +
                 + 1 + 2 + wcslen(selection_file) + 1;
  retval = xmalloc (cmd_line_len * sizeof(wchar_t));

  wcscpy_s (retval, cmd_line_len, L"\"");
  wcscat_s (retval, cmd_line_len, w_inst_dir);
  wcscat_s (retval, cmd_line_len, L"\\");
  wcscat_s (retval, cmd_line_len, NSS_APP_NAME);
  wcscat_s (retval, cmd_line_len, L"\" \"");
  wcscat_s (retval, cmd_line_len, selection_file);
  wcscat_s (retval, cmd_line_len, L"\"");

  return retval;
}

/** @brief Increase the privileges of the current token to allow registry access
  *
  * To load another users registry you need SE_BACKUP_NAME and SE_RESTORE_NAME
  * privileges. Normally if we are running elevated we can obtain them.
  *
  * @returns true if the privileges could be obtained. False otherwise
  */
static bool
get_backup_restore_priv()
{
  HANDLE hToken = NULL;
  PTOKEN_PRIVILEGES psToken = NULL;
  DWORD token_size = 0,
        dwI = 0,
        token_size_new = 0,
        privilege_size = 128;
  char privilege_name[128];
  bool retval = false;
  bool backup_found = false;
  bool restore_found = false;


  if (!OpenProcessToken (GetCurrentProcess(),
                         TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
      PRINTLASTERROR ("Failed to get process token.");
      return false;
    }

  /* Get the size for the token */
  GetTokenInformation (hToken, TokenPrivileges, NULL, 0, &token_size);
  if (token_size == 0)
    {
      PRINTLASTERROR ("Failed to get token size.");
      goto done;
    }

  psToken = xmalloc(token_size);

  if (!GetTokenInformation (hToken, TokenPrivileges, psToken, token_size, &token_size_new))
    {
      PRINTLASTERROR ("Failed to get token information.");
      goto done;
    }

  if (token_size != token_size_new)
    {
      ERRORPRINTF ("Size changed.");
      goto done;
    }

  for(dwI = 0; dwI < psToken->PrivilegeCount; dwI++)
    {
      privilege_size = sizeof (privilege_name);
      if (!LookupPrivilegeNameA (NULL, &psToken->Privileges[dwI].Luid,
                                 privilege_name, &privilege_size))
        {
          PRINTLASTERROR ("Failed to lookup privilege name");
        }

      if(strcmp(privilege_name, "SeRestorePrivilege") == 0)
        {
          psToken->Privileges[dwI].Attributes |= SE_PRIVILEGE_ENABLED;
          restore_found = true;
          continue;
        }
      if(strcmp(privilege_name, "SeBackupPrivilege") == 0)
        {
          psToken->Privileges[dwI].Attributes |= SE_PRIVILEGE_ENABLED;
          backup_found = true;
          continue;
        }
      if (backup_found && restore_found)
        {
          break;
        }
    }

  if (backup_found && restore_found)
    {
      if(!AdjustTokenPrivileges (hToken, 0, psToken, token_size, NULL, NULL))
        {
          PRINTLASTERROR ("Failed to adjust token privileges.");
        }
      else
        {
          retval = true;
        }
    }

done:
  if (hToken != NULL)
    {
      CloseHandle(hToken);
    }
  xfree(psToken);
  return retval;
}

/**@brief Register NSS process as runOnce for other users
*
* Loads the registry hives of other users on the system and
* adds a RunOnce registry key to start the NSS process to
* install the current selection on their next login.
*
* This should avoid conflicts with their firefox / thunderbird
* while making the certificates available for their applications.
*
* This function needs SE_BACKUP_NAME and SE_RESTORE_NAME
* privileges.
*
* @param [in] selection_file filename of the file containing
*             the users install / remove selection.
*/
static void
register_proccesses_for_others (wchar_t *selection_file)
{
  pkp_t *pkplist = locate_other_hives(),
         *cur = NULL;
  wchar_t *run_command = NULL;

  if (pkplist == NULL)
    {
      DEBUGPRINTF ("No hives found.");
      return;
    }

  if (!get_backup_restore_priv())
    {
      ERRORPRINTF ("Failed to obtain backup / restore privileges.");
      return;
    }

  run_command = get_command_line (selection_file);
  for (cur = pkplist; cur != NULL; cur = cur->next)
    {
      LONG ret = 0;
      wchar_t *hivepath = utf8_to_wchar (cur->hive_path, strlen(cur->hive_path));
      HKEY key_handle = NULL;
      bool key_loaded = false;

      if (hivepath == NULL)
        {
          ERRORPRINTF ("Failed to read hive path");
          continue;
        }
      ret = RegLoadKeyW (HKEY_LOCAL_MACHINE, APPNAME L"_tmphive", hivepath);

      xfree (hivepath);
      hivepath = NULL;

      if (ret != ERROR_SUCCESS)
        {
          /* This is somewhat expected if the registry is not located
             in the standard location or already loaded. Try to access
             the loaded registry in that case*/
          wchar_t *user_key = NULL,
                   *w_sid = NULL;
          size_t user_key_len = 0;

          SetLastError((DWORD)ret);
          PRINTLASTERROR ("Failed to load hive. Trying to access already loaded hive.");

          w_sid = utf8_to_wchar (cur->sid, strlen(cur->sid));
          if (!w_sid)
            {
              ERRORPRINTF ("Failed to read sid.");
              continue;
            }
          user_key_len = wcslen (L"\\" RUNONCE_PATH) + wcslen(w_sid) + 1;
          user_key = xmalloc (user_key_len * sizeof (wchar_t));
          wcscpy_s (user_key, user_key_len, w_sid);
          wcscat_s (user_key, user_key_len, L"\\" RUNONCE_PATH);
          user_key[user_key_len - 1] = '\0';
          xfree (w_sid);
          w_sid = NULL;

          ret = RegOpenKeyExW (HKEY_USERS,
                               user_key,
                               0,
                               KEY_WRITE,
                               &key_handle);
          xfree (user_key);
          if (ret != ERROR_SUCCESS)
            {
              ERRORPRINTF ("Failed to find RunOnce key for sid: %s in HKEY_USERS.", cur->sid);
              continue;
            }
        }
      else
        {
          key_loaded = true;
          ret = RegOpenKeyExW (HKEY_LOCAL_MACHINE,
                               APPNAME L"_tmphive\\" RUNONCE_PATH,
                               0,
                               KEY_WRITE,
                               &key_handle);

          if (ret != ERROR_SUCCESS)
            {
              ERRORPRINTF ("Failed to find RunOnce key in other registry.");
              RegUnLoadKey (HKEY_LOCAL_MACHINE, APPNAME L"_tmphive");
              continue;
            }

        }

      ret = RegSetValueExW (key_handle, APPNAME, 0, REG_SZ, (LPBYTE) run_command,
                            (wcslen(run_command) + 1) * sizeof(wchar_t));

      if (ret != ERROR_SUCCESS)
        {
          ERRORPRINTF ("Failed to write RunOnce key.");
        }

      RegCloseKey (key_handle);
      if (key_loaded)
        {
          ret = RegUnLoadKeyW (HKEY_LOCAL_MACHINE, APPNAME L"_tmphive");
          if (ret != ERROR_SUCCESS)
            {
              SetLastError ((DWORD)ret);
              PRINTLASTERROR ("Failed to unload hive.");
            }
        }
    }

  xfree (run_command);
  pkp_t_free (pkplist);
}

/**@brief Start the process to install / remove
*
* Starts the NSS installation process for the current user
*
* @param [in] selection_file filename of the file containing
*             the users install / remove selection.
*
* @param [in] drop_privileges weather or not elevated privileges
*             should be dropped before starting the process.
*
* @returns true on success, false on error.
*/
static bool
start_procces_for_user (wchar_t *selection_file, bool drop_privileges)
{
  HANDLE hToken = NULL;
  LPWSTR lpApplicationPath = NULL,
         lpCommandLine = NULL;
  PROCESS_INFORMATION piProcInfo = {0};
  STARTUPINFOW siStartInfo = {0};
  BOOL success = FALSE;
  char *install_dir = get_install_dir();
  wchar_t *w_inst_dir;
  size_t w_path_len = 0;

  if (!selection_file)
    {
      ERRORPRINTF ("Invalid call\n");
      return false;
    }

  /* Set up the application path. It's installdir + NSS_APP_NAME */
  if (install_dir == NULL)
    {
      ERRORPRINTF ("Failed to get installation directory");
      return FALSE;
    }

  w_inst_dir = utf8_to_wchar (install_dir, strlen(install_dir));
  xfree (install_dir);
  install_dir = NULL;

  w_path_len = wcslen(w_inst_dir) + wcslen(L"\\" NSS_APP_NAME) + 1;
  lpApplicationPath = xmalloc(w_path_len * sizeof (wchar_t));
  wcscpy_s (lpApplicationPath, w_path_len, w_inst_dir);
  xfree (w_inst_dir);
  w_inst_dir = NULL;
  wcscat_s (lpApplicationPath, w_path_len, L"\\" NSS_APP_NAME);

  /* set up handles. stdin and stdout go to the same stdout*/
  siStartInfo.cb = sizeof (STARTUPINFO);

  if (is_elevated() && drop_privileges)
    {
      /* Start the child process as normal user */
      hToken = get_restricted_token ();
      if (hToken == NULL)
        {
          ERRORPRINTF ("Failed to get user level token.");
          return false;
        }
    }
  else if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken))
    {
      PRINTLASTERROR("Failed to get current handle.");
      xfree (lpApplicationPath);
      return false;
    }

  lpCommandLine = get_command_line (selection_file);

  if (lpCommandLine == NULL)
    {
      ERRORPRINTF ("Failed to build command line.");
      xfree (lpApplicationPath);
      return false;
    }

  DEBUGPRINTF ("Starting %S with command line %S\n", lpApplicationPath, lpCommandLine);

  success = CreateProcessAsUserW (hToken,
                                  lpApplicationPath,
                                  lpCommandLine, /* Commandline */
                                  NULL, /* Process attributes. Take hToken */
                                  NULL, /* Thread attribues. Take hToken */
                                  FALSE, /* Inherit Handles */
                                  0, /* Creation flags. */
                                  NULL, /* Inherit environment */
                                  NULL, /* Current working directory */
                                  &siStartInfo,
                                  &piProcInfo);
  xfree (lpApplicationPath);
  xfree (lpCommandLine);
  if (!success)
    {
      PRINTLASTERROR ("Failed to create process.\n");
      return false;
    }

  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;
    }
  if (piProcInfo.hProcess)
    CloseHandle (piProcInfo.hProcess);
  if (piProcInfo.hThread)
    CloseHandle (piProcInfo.hThread);
  return true;
}

/**@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 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;
  HANDLE hFile = NULL;
  size_t path_len;

  folder_name = get_program_data_folder();
  if (!folder_name)
    {
      ERRORPRINTF("Failed to look up ProgramData folder.\n");
      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_s (path, path_len, folder_name) != 0)
    {
      ERRORPRINTF ("Failed to copy folder name.\n");

      CoTaskMemFree (folder_name);

      return NULL;
    }

  CoTaskMemFree (folder_name);

  if (wcscat_s (path, path_len, L"\\") != 0)
    {
      ERRORPRINTF ("Failed to cat dirsep.\n");
      xfree(path);
      return NULL;
    }

  if (wcscat_s (path, path_len, 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_s (path, path_len, L"\\") != 0)
    {
      ERRORPRINTF ("Failed to cat dirsep.\n");
      xfree(path);
      return NULL;
    }

  if (wcscat_s (path, path_len, 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 | TRUNCATE_EXISTING,
                      0,
                      NULL);

  if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_FILE_NOT_FOUND)
    {
      hFile = CreateFileW(path,
                          GENERIC_WRITE,
                          0, /* don't share */
                          NULL, /* use the security attributes from the folder */
                          CREATE_NEW,
                          0,
                          NULL);
    }
  if (hFile == INVALID_HANDLE_VALUE)
    {
      PRINTLASTERROR ("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)
{
  wchar_t *selection_file_name = NULL;

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

  if (is_elevated())
    {
      register_proccesses_for_others (selection_file_name);
      /* Start the NSS process once with elevated rights to
         install into the default profile directories. */
      if (!start_procces_for_user (selection_file_name, false))
        {
          ERRORPRINTF ("Failed to run NSS installation process for default folders.\n");
          xfree(selection_file_name);
          return -1;
        }
    }

  if (!start_procces_for_user (selection_file_name, true))
    {
      ERRORPRINTF ("Failed to run NSS installation process.\n");
      xfree(selection_file_name);
      return -1;
    }
  xfree(selection_file_name);
  return 0;
}

#endif

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