view cinst/nssstore_win.c @ 677:85c5aa9aba2b

Improve error handling and use unicode function for unload
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 30 Jun 2014 11:26:05 +0200
parents cb40af11ec3a
children a511c1f45c70
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 <sddl.h>
#include <stdio.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"

#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"

/**@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 Read (and expand if necessary) a registry string.
 *
 * Reads a registry string and calls ExpandEnvironmentString
 * if necessary on it. Returns a newly allocated string array
 * with the expanded registry value converted to UTF-8
 *
 * Caller has to free return value with free.
 *
 * @param [in] root the root key (e.g. HKEY_LOCAL_MACHINE)
 * @param [in] key the key
 * @param [in] name the name of the value to read.
 *
 * @returns the expanded, null terminated utf-8 string of the value.
 *          or NULL on error.
 */
static char*
read_registry_string (const HKEY root, const wchar_t *key,
                      const wchar_t *name)
{
  HKEY key_handle = NULL;
  DWORD size = 0,
        type = 0,
        ex_size = 0,
        dwRet = 0;
  LONG ret = 0;
  char *retval = NULL;
  wchar_t *buf = NULL,
          *ex_buf = NULL;
  if (root == NULL || key == NULL || name == NULL)
    {
      ERRORPRINTF ("Invalid call to read_registry_string");
      return NULL;
    }

  ret = RegOpenKeyExW (root, key, 0, KEY_READ, &key_handle);
  if (ret != ERROR_SUCCESS)
    {
      ERRORPRINTF ("Failed to open key.");
      return NULL;
    }

  /* Get the size */
  ret = RegQueryValueExW (key_handle, name, 0, NULL, NULL, &size);
  if (ret != ERROR_MORE_DATA && !(ret == ERROR_SUCCESS && size != 0))
    {
      ERRORPRINTF ("Failed to get required registry size.");
      return retval;
    }

  /* Size is size in bytes not in characters */
  buf = xmalloc (size + sizeof(wchar_t));

  /* If the stored value is not zero terminated the returned value also
     is not zero terminated. That's why we reserve more and ensure it's
     initialized. */
  memset (buf, 0, size + sizeof(wchar_t));

  ret = RegQueryValueExW (key_handle, name, 0, &type, (LPBYTE) buf, &size);
  if (ret != ERROR_SUCCESS)
    {
      ERRORPRINTF ("Failed get registry value.");
      return retval;
    }

  if (type == REG_SZ || (type == REG_EXPAND_SZ && wcschr (buf, '%') == NULL))
    {
      /* Nothing to expand, we are done */
      retval = wchar_to_utf8 (buf, wcslen (buf));
      goto done;
    }

  if (type != REG_EXPAND_SZ)
    {
      ERRORPRINTF ("Unhandled registry type %i", type);
      goto done;
    }

  /* Expand the registry string */
  ex_size = ExpandEnvironmentStringsW (buf, NULL, 0);

  if (ex_size == 0)
    {
      PRINTLASTERROR ("Failed to determine expanded environment size.");
      goto done;
    }

  ex_buf = xmalloc ((ex_size + 1) * sizeof(wchar_t));

  dwRet = ExpandEnvironmentStringsW (buf, ex_buf, ex_size);

  ex_buf[ex_size] = '\0'; /* Make sure it's a string */

  if (dwRet == 0 || dwRet != ex_size)
    {
      PRINTLASTERROR ("Failed to expand environment variables.");
      goto done;
    }

  retval = wchar_to_utf8 (ex_buf, ex_size);

done:
  xfree (ex_buf);
  xfree (buf);

  RegCloseKey (key_handle);
  return retval;
}
/**@brief Get the path to all users default registry hive
 *
 * Enumerates the keys in #PROFILE_LIST and retuns a
 * strv array with the utf-8 encoded paths to their suggested
 * registry hive location.
 *
 * Users with an SID not starting with S-1-5-21- are ignored
 * as is the current user.
 *
 * Use strv_free to free that array.
 *
 * @returns a newly allocated strv of the paths to the registry hives or NULL
 */
static char**
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;
  char **retval = 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_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);

      strv_append (&retval, profile_path, profile_path_len);
      DEBUGPRINTF ("Trying to access registry hive: %s", profile_path);

      xfree (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)
    {
      strv_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)
{
  char **hives = locate_other_hives();
  int i = 0;
  wchar_t *run_command = NULL;

  if (hives == 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 (i = 0; hives[i] != NULL; i++)
    {
      LONG ret = 0;
      wchar_t *hivepath = utf8_to_wchar (hives[i], strlen(hives[i]));
      HKEY key_handle = NULL;

      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. Failure is accepted in that case. */
          SetLastError((DWORD)ret);
          PRINTLASTERROR ("Failed to load hive.");
          continue;
        }

      ret = RegOpenKeyExW (HKEY_LOCAL_MACHINE,
                           APPNAME L"_tmphive\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce",
                           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);
      ret = RegUnLoadKeyW (HKEY_LOCAL_MACHINE, APPNAME L"_tmphive");
      if (ret != ERROR_SUCCESS)
        {
          SetLastError ((DWORD)ret);
          PRINTLASTERROR ("Failed to unload hive.");
        }
    }

  xfree (run_command);
  strv_free (hives);
}

/**@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.
*
* @returns true on success, false on error.
*/
static bool
start_procces_for_user (wchar_t *selection_file)
{
  HANDLE hToken = NULL;/*,
         hChildToken = NULL;*/
  /* TODO get this as absolute path based on current module location */
  LPWSTR lpApplicationName = L"mozilla.exe",
         lpCommandLine;
  PROCESS_INFORMATION piProcInfo = {0};
  STARTUPINFOW siStartInfo = {0};
  BOOL success = FALSE;

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

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

  if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken))
    {
      PRINTLASTERROR("Failed to get current handle.");
      return false;
    }
  /* TODO! if (is_elevated())
     restrict token -> hChildToken
  */

  lpCommandLine = get_command_line (selection_file);

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

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

  success = CreateProcessAsUserW (hToken,
                                  lpApplicationName,
                                  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 (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 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 version of mkdir path -m 644
  *
  * 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 */
    }

  result = SHGetKnownFolderPath (&FOLDERID_ProgramData, /* Get program data dir */
                                 KF_FLAG_CREATE | /* Create if it does not exist */
                                 KF_FLAG_INIT, /* Initialize it if created */
                                 INVALID_HANDLE_VALUE, /* Get it for the default user */
                                 &folder_name);

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

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

#endif

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