aheinecke@324: #ifdef WIN32 aheinecke@324: aheinecke@324: /* @file aheinecke@324: @brief Windows implementation of nssstore process control. andre@360: andre@360: The windows process will write an instructions file for andre@360: the mozilla process into the current users temp directory andre@360: (%APPDATA%/Local/Temp/) and start the NSS installation process to andre@360: exectute those instructions. If the current process is elevated andre@360: the NSS process is run with a restricted token. andre@360: The execution of the mozilla process is not monitored. andre@360: You have to refer to the system log to check which certificates were andre@360: installed / removed by it. andre@360: andre@360: If the installation process is running elevated it andre@360: will create the file in the ProgramData directory in andre@360: a subdirectory with the defined application name. andre@360: %PROGRAMDATA%/$APPLICATION_NAME andre@360: with the file name: andre@360: current_selection.txt andre@360: The folder will have restricted permissions so andre@360: that only Administrators are allowed to access it. andre@360: andre@360: Additionally if this process is Elevated it also starts the andre@360: NSS installation process in default profile mode once to change andre@360: the default NSS certificate databases for new profiles. andre@360: andre@360: The process then adds a new RunOnce registry key andre@360: for each user on the system that executes the NSS installation andre@360: process on login to make sure it is launched once in the andre@360: security context of that user. aheinecke@324: */ aheinecke@324: aheinecke@324: #include aheinecke@324: #include aheinecke@324: #include aheinecke@324: #include aheinecke@329: #include andre@360: #include andre@360: #include andre@360: #include aheinecke@324: aheinecke@324: #include "logging.h" aheinecke@324: #include "util.h" andre@360: #include "strhelp.h" aheinecke@324: andre@360: #ifndef APPNAME andre@360: #define APPNAME L"cinst" andre@360: #endif aheinecke@324: andre@360: #ifndef SELECTION_FILE_NAME andre@360: #define SELECTION_FILE_NAME L"currently_selected.txt" andre@360: #endif andre@360: andre@360: #define PROCESS_TIMEOUT 30000 aheinecke@324: aheinecke@324: #define PRINTLASTERROR(msg) \ aheinecke@324: char *my_error = getLastErrorMsg(); \ aheinecke@324: if (my_error) { \ aheinecke@329: DEBUGPRINTF(msg " : %s\n", my_error); \ aheinecke@324: ERRORPRINTF(msg" : %s\n", my_error); \ aheinecke@324: free (my_error); \ aheinecke@324: } \ aheinecke@329: DEBUGPRINTF ("Failed to get error information\n"); aheinecke@324: aheinecke@324: /**@brief Write strv of instructions to a handle aheinecke@324: * aheinecke@324: * Writes the null terminated list of instructions to aheinecke@324: * the handle. aheinecke@324: * aheinecke@330: * @param [in] base64 encoded der certificates to write aheinecke@324: * @param [in] write_handle to write to aheinecke@330: * @param [in] remove weather the certificate should be installed or removed aheinecke@324: * aheinecke@324: * @returns true on success, false on failure aheinecke@324: */ aheinecke@324: static bool aheinecke@330: write_instructions(char **certificates, HANDLE write_handle, aheinecke@330: bool remove) aheinecke@324: { aheinecke@324: int i = 0; aheinecke@330: int cHandle = -1; aheinecke@330: FILE *write_stream = NULL; aheinecke@324: aheinecke@330: if (!certificates) aheinecke@324: { aheinecke@324: return true; aheinecke@324: } aheinecke@324: aheinecke@330: cHandle = _open_osfhandle ((intptr_t)write_handle, 0); aheinecke@330: aheinecke@330: if (cHandle == -1) aheinecke@324: { aheinecke@330: ERRORPRINTF ("Failed to open write handle.\n"); aheinecke@330: } aheinecke@330: aheinecke@330: write_stream = _fdopen(cHandle, "w"); aheinecke@330: for (i = 0; certificates[i]; i++) aheinecke@330: { aheinecke@330: int ret = 0; aheinecke@330: DEBUGPRINTF("Writing \n"); aheinecke@330: if (remove) aheinecke@330: ret = fprintf (write_stream, "R:%s\n", certificates[i]); aheinecke@330: else aheinecke@330: ret = fprintf (write_stream, "I:%s\n", certificates[i]); aheinecke@330: aheinecke@330: if (ret <= 0) aheinecke@324: { aheinecke@330: DEBUGPRINTF ("Failed to write everything.\n"); aheinecke@330: break; aheinecke@324: } aheinecke@324: } aheinecke@330: aheinecke@330: DEBUGPRINTF("Write done\n"); aheinecke@324: return true; aheinecke@324: } aheinecke@324: aheinecke@324: /**@brief Start the process to install / remove aheinecke@324: * andre@360: * Starts the NSS installation process for the current user aheinecke@324: * andre@360: * @param [in] selection_file filename of the file containing andre@360: * the users installall / remove selection. aheinecke@324: * aheinecke@324: * @returns true on success, false on error. aheinecke@324: */ aheinecke@324: static bool andre@360: start_procces_for_user (wchar_t *selection_file) aheinecke@324: { andre@360: HANDLE hToken = NULL;/*, andre@360: hChildToken = NULL;*/ aheinecke@324: /* TODO get this as absolute path based on current module location */ andre@360: LPWSTR lpApplicationName = L"mozilla.exe", andre@360: lpCommandLine; aheinecke@324: PROCESS_INFORMATION piProcInfo = {0}; aheinecke@324: STARTUPINFOW siStartInfo = {0}; aheinecke@324: BOOL success = FALSE; andre@360: size_t cmd_line_len = 0; aheinecke@324: andre@360: if (!selection_file) aheinecke@324: { andre@360: ERRORPRINTF ("Invalid call\n"); andre@360: return false; aheinecke@324: } aheinecke@324: aheinecke@324: /* set up handles. stdin and stdout go to the same stdout*/ aheinecke@324: siStartInfo.cb = sizeof (STARTUPINFO); andre@360: andre@360: if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) andre@360: { andre@360: PRINTLASTERROR("Failed to get current handle."); andre@360: return false; andre@360: } andre@360: /* TODO! if (is_elevated()) andre@360: restrict token -> hChildToken andre@360: */ andre@360: andre@360: cmd_line_len = wcslen (lpApplicationName) + wcslen(selection_file) + 1; andre@360: lpCommandLine = xmalloc (cmd_line_len * sizeof(wchar_t)); andre@360: andre@360: wcscpy (lpCommandLine, lpApplicationName); andre@360: wcscat (lpCommandLine, selection_file); aheinecke@324: aheinecke@324: success = CreateProcessAsUserW (hToken, aheinecke@324: lpApplicationName, andre@360: lpCommandLine, /* Commandline */ aheinecke@324: NULL, /* Process attributes. Take hToken */ aheinecke@324: NULL, /* Thread attribues. Take hToken */ andre@360: FALSE, /* Inherit Handles */ andre@360: 0, /* Creation flags. */ andre@360: NULL, /* Inherit environment */ aheinecke@324: NULL, /* Current working directory */ aheinecke@324: &siStartInfo, aheinecke@324: &piProcInfo); andre@360: xfree (lpCommandLine); aheinecke@324: if (!success) aheinecke@324: { aheinecke@324: PRINTLASTERROR ("Failed to create process.\n"); andre@360: return false; aheinecke@324: } aheinecke@324: aheinecke@324: if (WaitForSingleObject (piProcInfo.hProcess, PROCESS_TIMEOUT) != WAIT_OBJECT_0) aheinecke@324: { aheinecke@324: /* Should not happen... */ aheinecke@324: ERRORPRINTF ("Failed to wait for process.\n"); andre@360: if (piProcInfo.hProcess) andre@360: CloseHandle (piProcInfo.hProcess); andre@360: if (piProcInfo.hThread) andre@360: CloseHandle (piProcInfo.hThread); andre@360: return false; aheinecke@324: } aheinecke@324: if (piProcInfo.hProcess) aheinecke@330: CloseHandle (piProcInfo.hProcess); aheinecke@324: if (piProcInfo.hThread) aheinecke@330: CloseHandle (piProcInfo.hThread); andre@360: return true; andre@360: } aheinecke@324: andre@360: /**@brief Create a directory with restricted access rights andre@360: * andre@360: * This creates a security attributes structure that restricts andre@360: * write access to the Administrators group but allows everyone to read files andre@360: * in that directory. andre@360: * Basically a very complicated vetrsion of mkdir path -m 604 andre@360: * andre@360: * If the directory exists the permissions of that directory are checked if andre@360: * they are acceptable and true or false is returned accordingly. andre@360: * andre@360: * Code based on msdn example: andre@360: * http://msdn.microsoft.com/en-us/library/windows/desktop/aa446595%28v=vs.85%29.aspx andre@360: * andre@360: * @param[in] path Path of the directory to create andre@360: * andre@360: * @returns true on success of if the directory exists, false on error andre@360: */ andre@360: bool andre@360: create_restricted_directory (LPWSTR path) andre@360: { andre@360: bool retval = false; andre@360: PSID everyone_SID = NULL, andre@360: admin_SID = NULL; andre@360: PACL access_control_list = NULL; andre@360: PSECURITY_DESCRIPTOR descriptor = NULL; andre@360: EXPLICIT_ACCESS explicit_access[2]; andre@360: SID_IDENTIFIER_AUTHORITY world_identifier = {SECURITY_WORLD_SID_AUTHORITY}, andre@360: admin_identifier = {SECURITY_NT_AUTHORITY}; andre@360: SECURITY_ATTRIBUTES security_attributes; andre@360: andre@360: ZeroMemory(&security_attributes, sizeof(security_attributes)); andre@360: ZeroMemory(&explicit_access, 2 * sizeof(EXPLICIT_ACCESS)); andre@360: andre@360: /* Create a well-known SID for the Everyone group. */ andre@360: if(!AllocateAndInitializeSid(&world_identifier, /* top-level identifier */ andre@360: 1, /* subauthorties count */ andre@360: SECURITY_WORLD_RID, /* Only one authority */ andre@360: 0, 0, 0, 0, 0, 0, 0, /* No other authorities*/ andre@360: &everyone_SID)) andre@360: { andre@360: PRINTLASTERROR ("Failed to allocate world sid.\n"); andre@360: return false; andre@360: } andre@360: andre@360: /* Initialize the first EXPLICIT_ACCESS structure for an ACE. andre@360: to allow everyone read access */ andre@360: explicit_access[0].grfAccessPermissions = GENERIC_READ; /* Give read access */ andre@360: explicit_access[0].grfAccessMode = SET_ACCESS; /* Overwrite other access for all users */ andre@360: explicit_access[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; /* make it stick */ andre@360: explicit_access[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; andre@360: explicit_access[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; andre@360: explicit_access[0].Trustee.ptstrName = (LPTSTR) everyone_SID; andre@360: andre@360: /* Create the SID for the BUILTIN\Administrators group. */ andre@360: if(!AllocateAndInitializeSid(&admin_identifier, andre@360: 2, andre@360: SECURITY_BUILTIN_DOMAIN_RID, /*BUILTIN\ */ andre@360: DOMAIN_ALIAS_RID_ADMINS, /*\Administrators */ andre@360: 0, 0, 0, 0, 0, 0, /* No other */ andre@360: &admin_SID)) andre@360: { andre@360: PRINTLASTERROR ("Failed to allocate admin sid."); andre@360: goto done; andre@360: } andre@360: andre@360: /* explicit_access[1] grants admins full rights for this object and inherits andre@360: it to the children */ andre@360: explicit_access[1].grfAccessPermissions = GENERIC_ALL; andre@360: explicit_access[1].grfAccessMode = SET_ACCESS; andre@360: explicit_access[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; andre@360: explicit_access[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; andre@360: explicit_access[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP; andre@360: explicit_access[1].Trustee.ptstrName = (LPTSTR) admin_SID; andre@360: andre@360: /* Set up the ACL structure. */ andre@360: if (ERROR_SUCCESS != SetEntriesInAcl(2, explicit_access, NULL, &access_control_list)) andre@360: { andre@360: PRINTLASTERROR ("Failed to set up Acl."); andre@360: goto done; andre@360: } andre@360: andre@360: /* Initialize a security descriptor */ andre@360: descriptor = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, andre@360: SECURITY_DESCRIPTOR_MIN_LENGTH); andre@360: if (descriptor == NULL) andre@360: { andre@360: PRINTLASTERROR("Failed to allocate descriptor."); andre@360: goto done; andre@360: } andre@360: andre@360: if (!InitializeSecurityDescriptor(descriptor, andre@360: SECURITY_DESCRIPTOR_REVISION)) andre@360: { andre@360: PRINTLASTERROR("Failed to initialize descriptor."); andre@360: goto done; andre@360: } andre@360: andre@360: /* Now we add the ACL to the the descriptor */ andre@360: if (!SetSecurityDescriptorDacl(descriptor, andre@360: TRUE, /* bDaclPresent flag */ andre@360: access_control_list, andre@360: FALSE)) /* not a default DACL */ andre@360: { andre@360: PRINTLASTERROR("Failed to set security descriptor."); andre@360: goto done; andre@360: } andre@360: andre@360: /* Finally set up the security attributes structure */ andre@360: security_attributes.nLength = sizeof (SECURITY_ATTRIBUTES); andre@360: security_attributes.lpSecurityDescriptor = descriptor; andre@360: security_attributes.bInheritHandle = FALSE; andre@360: andre@360: /* Use the security attributes to create the directory */ andre@360: if (!CreateDirectoryW(path, &security_attributes)) andre@360: { andre@360: DWORD err = GetLastError(); andre@360: if (err == ERROR_ALREADY_EXISTS) andre@360: { andre@360: /* Verify that the directory has the correct rights */ andre@360: // TODO andre@360: retval = true; andre@360: goto done; andre@360: } andre@360: ERRORPRINTF ("Failed to create directory. Err: %lu", err); andre@360: } andre@360: retval = true; andre@360: andre@360: done: andre@360: andre@360: if (everyone_SID) andre@360: FreeSid(everyone_SID); andre@360: if (admin_SID) andre@360: FreeSid(admin_SID); andre@360: if (access_control_list) andre@360: LocalFree(access_control_list); andre@360: if (descriptor) andre@360: LocalFree(descriptor); aheinecke@324: aheinecke@324: return retval; aheinecke@324: } aheinecke@324: andre@360: /**@brief Writes the selection file containing the instructions andre@360: * andre@360: * If the process is running elevated the instructions are andre@360: * written to the global ProgramData directory otherwise andre@360: * they are written in the temporary directory of the current user. andre@360: * andre@360: * If the return value is not NULL it needs to be freed by the caller. andre@360: * The returned path will contain backslashes as directory seperators. andre@360: * andre@360: * @param[in] to_install Certificates that should be installed andre@360: * @param[in] to_remove Certificates that should be removed andre@360: * @returns pointer to the absolute filename of the selection file or NULL andre@360: */ andre@360: wchar_t * andre@360: write_selection_file (char **to_install, char **to_remove) andre@360: { andre@360: wchar_t *folder_name = NULL, andre@360: *path = NULL; andre@360: bool elevated = is_elevated(); andre@360: HRESULT result = E_FAIL; andre@360: HANDLE hFile = NULL; andre@360: size_t path_len; andre@360: andre@360: if (!elevated) andre@360: { andre@360: /* TODO */ andre@360: } andre@360: andre@360: #if 0 andre@360: result = SHGetKnownFolderPath (FOLDERID_ProgramData, /* Get program data dir */ andre@360: KF_FLAG_CREATE | /* Create if it does not exist */ andre@360: KF_FLAG_INIT, /* Initialize it if created */ andre@360: -1, /* Get it for the default user */ andre@360: &folder_name); andre@360: #endif andre@360: /* TODO figure out how to do this with mingw */ andre@360: result = S_OK; andre@360: folder_name = L"C:\\ProgramData"; andre@360: andre@360: if (result != S_OK) andre@360: { andre@360: PRINTLASTERROR ("Failed to get folder path"); andre@360: return NULL; andre@360: } andre@360: andre@360: path_len = wcslen (folder_name) + wcslen (APPNAME) + 2; /* path + dirsep + \0 */ andre@360: path_len += wcslen (SELECTION_FILE_NAME) + 1; /* filename + dirsep */ andre@360: andre@360: if (path_len >= MAX_PATH) andre@360: { andre@360: /* We could go and use the full 32,767 characters but this andre@360: should be a very weird setup if this is neccessary. */ andre@360: ERRORPRINTF ("Path too long.\n"); andre@360: return NULL; andre@360: } andre@360: andre@360: path = xmalloc (path_len * sizeof (wchar_t)); andre@360: if (wcscpy (path, folder_name) != 0) andre@360: { andre@360: ERRORPRINTF ("Failed to copy folder name.\n"); andre@360: #if 0 andre@360: CoTaskMemFree (folder_name); andre@360: #endif andre@360: return NULL; andre@360: } andre@360: andre@360: #if 0 andre@360: CoTaskMemFree (folder_name); andre@360: #endif andre@360: andre@360: if (wcscat (path, L"\\") != 0) andre@360: { andre@360: ERRORPRINTF ("Failed to cat dirsep.\n"); andre@360: xfree(path); andre@360: return NULL; andre@360: } andre@360: andre@360: if (wcscat (path, APPNAME) != 0) andre@360: { andre@360: ERRORPRINTF ("Failed to cat appname.\n"); andre@360: xfree(path); andre@360: return NULL; andre@360: } andre@360: andre@360: /* Security: if someone has created this directory before andre@360: it might be a symlink to another place that a users andre@360: wants us to grant read access to or makes us overwrite andre@360: something */ andre@360: if(!create_restricted_directory (path)) andre@360: { andre@360: ERRORPRINTF ("Failed to create directory\n"); andre@360: xfree(path); andre@360: return NULL; andre@360: } andre@360: andre@360: if (wcscat (path, L"\\") != 0) andre@360: { andre@360: ERRORPRINTF ("Failed to cat dirsep.\n"); andre@360: xfree(path); andre@360: return NULL; andre@360: } andre@360: andre@360: if (wcscat (path, SELECTION_FILE_NAME) != 0) andre@360: { andre@360: ERRORPRINTF ("Failed to cat filename.\n"); andre@360: xfree(path); andre@360: return NULL; andre@360: } andre@360: andre@360: hFile = CreateFileW(path, andre@360: GENERIC_WRITE, andre@360: 0, /* don't share */ andre@360: NULL, /* use the security attributes from the folder */ andre@360: OPEN_ALWAYS, andre@360: 0, andre@360: NULL); andre@360: andre@360: if (hFile == INVALID_HANDLE_VALUE) andre@360: { andre@360: ERRORPRINTF ("Failed to create file\n"); andre@360: xfree(path); andre@360: return NULL; andre@360: } andre@360: if (!write_instructions (to_install, hFile, false)) andre@360: { andre@360: ERRORPRINTF ("Failed to write install instructions.\n"); andre@360: CloseHandle(hFile); andre@360: xfree(path); andre@360: return NULL; andre@360: } andre@360: if (!write_instructions (to_remove, hFile, true)) andre@360: { andre@360: ERRORPRINTF ("Failed to write remove instructions.\n"); andre@360: CloseHandle(hFile); andre@360: xfree(path); andre@360: return NULL; andre@360: } andre@360: CloseHandle(hFile); andre@360: andre@360: return path; andre@360: } andre@360: aheinecke@324: int aheinecke@324: write_stores_nss (char **to_install, char **to_remove) aheinecke@324: { andre@360: wchar_t *selection_file_name = NULL; aheinecke@324: andre@360: selection_file_name = write_selection_file (to_install, to_remove); andre@360: if (!selection_file_name) andre@360: { andre@360: ERRORPRINTF ("Failed to write instructions.\n"); andre@360: return -1; andre@360: } andre@360: andre@360: DEBUGPRINTF ("Wrote selection file. Loc: %S\n", selection_file_name); andre@360: andre@360: /* TODO loop over all users create startup entries for them*/ andre@360: andre@360: if (!start_procces_for_user (selection_file_name)) aheinecke@324: { aheinecke@324: ERRORPRINTF ("Failed to run NSS installation process.\n"); andre@360: xfree(selection_file_name); aheinecke@324: return -1; aheinecke@324: } andre@360: xfree(selection_file_name); aheinecke@324: return 0; aheinecke@324: } aheinecke@324: aheinecke@324: #endif