aheinecke@404: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik aheinecke@404: * Software engineering by Intevation GmbH aheinecke@404: * aheinecke@404: * This file is Free Software under the GNU GPL (v>=2) aheinecke@404: * and comes with ABSOLUTELY NO WARRANTY! aheinecke@404: * See LICENSE.txt for details. aheinecke@404: */ 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@1175: the nss-installer 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@1175: The execution of the nss-installer 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 andre@670: #include aheinecke@324: #include aheinecke@324: #include aheinecke@324: #include aheinecke@329: #include andre@360: #include andre@360: #include aheinecke@324: aheinecke@324: #include "logging.h" aheinecke@324: #include "util.h" andre@360: #include "strhelp.h" andre@1084: #include "binverify.h" aheinecke@324: andre@360: #ifndef APPNAME andre@1176: #define APPNAME L"trustbridge-certificate-installer" andre@360: #endif aheinecke@324: andre@675: /**@def The name of the nss installation process */ andre@1175: #define NSS_APP_NAME L"trustbridge-nss-installer.exe" andre@675: andre@360: #ifndef SELECTION_FILE_NAME andre@360: #define SELECTION_FILE_NAME L"currently_selected.txt" andre@360: #endif andre@360: andre@668: /**@def The maximum time to wait for the NSS Process */ andre@360: #define PROCESS_TIMEOUT 30000 aheinecke@324: andre@668: /**@def The registry key to look for user profile directories */ andre@668: #define PROFILE_LIST L"Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList" andre@856: #define RUNONCE_PATH L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce" andre@856: andre@905: struct profile_key_path andre@905: { andre@856: char *sid; andre@856: char *hive_path; andre@856: struct profile_key_path *next; andre@856: }; andre@856: andre@856: /** andre@856: * @brief combination of sid and hive path andre@856: */ andre@856: typedef struct profile_key_path pkp_t; andre@856: andre@856: static void andre@856: pkp_t_free (pkp_t *item) andre@856: { andre@856: if (!item) andre@856: { andre@856: return; andre@856: } andre@856: xfree (item->sid); andre@856: xfree (item->hive_path); andre@856: if (item->next) andre@856: { andre@856: pkp_t_free (item->next); andre@856: } andre@856: xfree (item); andre@856: } andre@668: 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@489: * @param [in] certificates base64 encoded der certificate to write aheinecke@489: * @param [in] write_handle 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@489: bool retval = false; aheinecke@324: int i = 0; aheinecke@489: const char *line_end = "\r\n"; aheinecke@489: char *line_start = NULL; aheinecke@324: aheinecke@330: if (!certificates) aheinecke@324: { aheinecke@324: return true; aheinecke@324: } aheinecke@324: aheinecke@489: line_start = remove ? "R:" : "I:"; aheinecke@330: aheinecke@330: for (i = 0; certificates[i]; i++) aheinecke@330: { aheinecke@489: DWORD written = 0; aheinecke@489: DWORD inst_len = strlen (certificates[i]); aheinecke@489: retval = WriteFile (write_handle, (LPCVOID) line_start, 2, &written, NULL); aheinecke@489: if (!retval) aheinecke@324: { aheinecke@489: PRINTLASTERROR ("Failed to write line start\n"); aheinecke@489: return false; aheinecke@489: } aheinecke@489: if (written != 2) aheinecke@489: { aheinecke@489: ERRORPRINTF ("Failed to write line start\n"); aheinecke@489: retval = false; aheinecke@489: return false; aheinecke@489: } aheinecke@489: written = 0; aheinecke@489: retval = WriteFile (write_handle, (LPCVOID) certificates[i], inst_len, &written, NULL); aheinecke@489: if (!retval) aheinecke@489: { aheinecke@489: PRINTLASTERROR ("Failed to write certificate\n"); aheinecke@489: return false; aheinecke@489: } aheinecke@489: if (inst_len != written) aheinecke@489: { aheinecke@489: ERRORPRINTF ("Failed to write everything\n"); aheinecke@489: retval = false; aheinecke@489: return false; aheinecke@489: } aheinecke@489: written = 0; aheinecke@489: retval = WriteFile (write_handle, (LPCVOID) line_end, 2, &written, NULL); aheinecke@489: if (!retval) aheinecke@489: { aheinecke@489: PRINTLASTERROR ("Failed to write line end\n"); aheinecke@489: return false; aheinecke@489: } aheinecke@489: if (written != 2) aheinecke@489: { aheinecke@489: ERRORPRINTF ("Failed to write full line end\n"); aheinecke@489: retval = false; aheinecke@489: return false; aheinecke@324: } aheinecke@324: } aheinecke@324: return true; aheinecke@324: } andre@668: /**@brief Get the path to all users default registry hive andre@668: * andre@668: * Enumerates the keys in #PROFILE_LIST and retuns a andre@856: * list of their profile path / sid pairs with the utf-8 encoded paths to andre@1247: * their suggested registry hive location. andre@668: * andre@668: * Users with an SID not starting with S-1-5-21- are ignored andre@668: * as is the current user. andre@668: * andre@856: * The return value should be freed with pkp_t_free andre@668: * andre@668: * @returns a newly allocated strv of the paths to the registry hives or NULL andre@668: */ andre@856: static pkp_t* andre@668: locate_other_hives() andre@668: { andre@668: HKEY profile_list = NULL; andre@668: int ret = 0; andre@668: DWORD index = 0, andre@668: key_len = 257; andre@668: /* According to andre@668: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724872%28v=vs.85%29.aspx andre@668: a registry key is limited to 255 characters. But according to andre@668: http://www.sepago.de/e/holger/2010/07/20/how-long-can-a-registry-key-name-really-be andre@668: the actual limit is 256 + \0 thus we create a buffer for 257 wchar_t's*/ andre@670: wchar_t key_name[257], andre@670: *current_user_sid = NULL; andre@856: pkp_t *retval = NULL, andre@905: *cur_item = NULL; andre@668: bool error = true; andre@670: PSID current_user = NULL; andre@668: andre@668: ret = RegOpenKeyExW (HKEY_LOCAL_MACHINE, PROFILE_LIST, 0, andre@668: KEY_READ, &profile_list); andre@668: if (ret != ERROR_SUCCESS) andre@668: { andre@668: ERRORPRINTF ("Failed to open profile list. Error: %i", ret); andre@668: return NULL; andre@668: } andre@668: andre@670: /* Obtain the current user sid to prevent it from being returned. */ andre@670: current_user = get_process_owner (GetCurrentProcess()); andre@670: andre@670: if (!current_user) andre@670: { andre@670: ERRORPRINTF ("Failed to get the current user."); andre@670: goto done; andre@670: } andre@670: andre@670: if (!ConvertSidToStringSidW (current_user, ¤t_user_sid)) andre@670: { andre@670: PRINTLASTERROR ("Failed to convert sid to string."); andre@670: goto done; andre@670: } andre@670: andre@668: while ((ret = RegEnumKeyExW (profile_list, index++, andre@668: key_name, &key_len, andre@668: NULL, NULL, NULL, NULL)) == ERROR_SUCCESS) andre@668: { andre@674: char *profile_path = NULL; andre@674: wchar_t *key_path = NULL; andre@674: size_t key_path_len = 0, andre@674: profile_path_len = 0; andre@674: andre@668: if (key_len == 257) andre@668: { andre@668: ERRORPRINTF ("Registry key too long."); andre@668: goto done; andre@668: } andre@668: andre@668: /* Reset key_len to buffer size */ andre@668: key_len = 257; andre@668: andre@670: if (wcsncmp (L"S-1-5-21-", key_name, 9) != 0 || andre@670: wcscmp (current_user_sid, key_name) == 0) andre@668: { andre@670: /* S-1-5-21 is the well known prefix for local users. Skip all andre@670: others and the current user*/ andre@668: continue; andre@668: } andre@670: andre@674: key_path_len = key_len + wcslen(PROFILE_LIST L"\\") + 1; andre@674: key_path = xmalloc (key_path_len * sizeof (wchar_t)); andre@674: andre@674: wcscpy_s (key_path, key_path_len, PROFILE_LIST L"\\"); andre@674: wcscat_s (key_path, key_path_len, key_name); andre@856: key_path[key_path_len - 1] = '\0'; andre@674: andre@670: DEBUGPRINTF ("Key : %S", key_name); andre@674: profile_path = read_registry_string (HKEY_LOCAL_MACHINE, andre@674: key_path, L"ProfileImagePath"); andre@674: xfree (key_path); andre@674: andre@674: if (profile_path == NULL) andre@674: { andre@674: ERRORPRINTF ("Failed to get profile path."); andre@674: continue; andre@674: } andre@674: profile_path_len = strlen (profile_path); andre@674: str_append_str (&profile_path, &profile_path_len, "\\ntuser.dat", 11); andre@856: if (retval == NULL) andre@856: { andre@856: retval = xmalloc (sizeof (pkp_t)); andre@856: cur_item = retval; andre@856: } andre@856: else andre@856: { andre@856: cur_item->next = xmalloc (sizeof(pkp_t)); andre@856: cur_item = cur_item->next; andre@856: } andre@856: cur_item->hive_path = profile_path; andre@856: cur_item->sid = wchar_to_utf8 (key_name, wcslen(key_name)); andre@856: cur_item->next = NULL; andre@674: andre@674: DEBUGPRINTF ("Trying to access registry hive: %s", profile_path); andre@668: } andre@668: andre@668: if (ret != ERROR_NO_MORE_ITEMS) andre@668: { andre@668: ERRORPRINTF ("Failed to enumeratre profile list. Error: %i", ret); andre@668: goto done; andre@668: } andre@668: andre@675: error = false; andre@675: andre@668: done: andre@670: xfree (current_user); andre@670: andre@668: RegCloseKey (profile_list); andre@668: andre@670: if (current_user_sid) andre@670: { andre@670: LocalFree (current_user_sid); andre@670: } andre@670: andre@668: if (error) andre@668: { andre@856: pkp_t_free (retval); andre@668: retval = NULL; andre@668: } andre@668: andre@668: return retval; andre@668: } andre@668: andre@675: /** @brief Build the command line for the NSS installation process andre@675: * andre@675: * Caller has to free the return value andre@675: * andre@675: * @param [in] selection_file the certificates to install andre@675: * andre@675: * @returns the command line to install the certificates. */ andre@675: static wchar_t* andre@675: get_command_line(wchar_t *selection_file) andre@675: { andre@675: LPWSTR retval; andre@675: char *install_dir = get_install_dir(); andre@675: wchar_t *w_inst_dir; andre@675: size_t cmd_line_len = 0; andre@675: andre@675: if (install_dir == NULL) andre@675: { andre@675: ERRORPRINTF ("Failed to get installation directory"); andre@675: return NULL; andre@675: } andre@675: andre@675: w_inst_dir = utf8_to_wchar (install_dir, strlen(install_dir)); andre@675: xfree (install_dir); andre@675: andre@675: if (w_inst_dir == NULL) andre@675: { andre@675: ERRORPRINTF ("Failed to convert installation directory"); andre@675: return NULL; andre@675: } andre@675: andre@675: /* installdir + dirsep + quotes + process name + space + quotes + selection_file andre@675: + NULL */ andre@675: cmd_line_len = wcslen (w_inst_dir) + 1 + 2 + wcslen (NSS_APP_NAME) + andre@905: + 1 + 2 + wcslen(selection_file) + 1; andre@1060: if (g_debug) andre@1060: { andre@1060: /* Add space for whitespace and --debug*/ andre@1060: cmd_line_len += 8; andre@1060: } andre@675: retval = xmalloc (cmd_line_len * sizeof(wchar_t)); andre@675: andre@675: wcscpy_s (retval, cmd_line_len, L"\""); andre@675: wcscat_s (retval, cmd_line_len, w_inst_dir); andre@675: wcscat_s (retval, cmd_line_len, L"\\"); andre@675: wcscat_s (retval, cmd_line_len, NSS_APP_NAME); andre@675: wcscat_s (retval, cmd_line_len, L"\" \""); andre@675: wcscat_s (retval, cmd_line_len, selection_file); andre@675: wcscat_s (retval, cmd_line_len, L"\""); andre@675: andre@1060: if (g_debug) andre@1060: { andre@1060: wcscat_s (retval, cmd_line_len, L" --debug"); andre@1060: } andre@1060: andre@675: return retval; andre@675: } andre@675: andre@676: /** @brief Increase the privileges of the current token to allow registry access andre@676: * andre@676: * To load another users registry you need SE_BACKUP_NAME and SE_RESTORE_NAME andre@676: * privileges. Normally if we are running elevated we can obtain them. andre@676: * andre@676: * @returns true if the privileges could be obtained. False otherwise andre@676: */ andre@676: static bool andre@676: get_backup_restore_priv() andre@676: { andre@676: HANDLE hToken = NULL; andre@676: PTOKEN_PRIVILEGES psToken = NULL; andre@676: DWORD token_size = 0, andre@676: dwI = 0, andre@676: token_size_new = 0, andre@676: privilege_size = 128; andre@676: char privilege_name[128]; andre@676: bool retval = false; andre@676: bool backup_found = false; andre@676: bool restore_found = false; andre@676: andre@676: andre@676: if (!OpenProcessToken (GetCurrentProcess(), andre@676: TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) andre@676: { andre@676: PRINTLASTERROR ("Failed to get process token."); andre@676: return false; andre@676: } andre@676: andre@676: /* Get the size for the token */ andre@676: GetTokenInformation (hToken, TokenPrivileges, NULL, 0, &token_size); andre@676: if (token_size == 0) andre@676: { andre@676: PRINTLASTERROR ("Failed to get token size."); andre@676: goto done; andre@676: } andre@676: andre@676: psToken = xmalloc(token_size); andre@676: andre@676: if (!GetTokenInformation (hToken, TokenPrivileges, psToken, token_size, &token_size_new)) andre@676: { andre@676: PRINTLASTERROR ("Failed to get token information."); andre@676: goto done; andre@676: } andre@676: andre@676: if (token_size != token_size_new) andre@676: { andre@676: ERRORPRINTF ("Size changed."); andre@676: goto done; andre@676: } andre@676: andre@676: for(dwI = 0; dwI < psToken->PrivilegeCount; dwI++) andre@676: { andre@676: privilege_size = sizeof (privilege_name); andre@676: if (!LookupPrivilegeNameA (NULL, &psToken->Privileges[dwI].Luid, andre@676: privilege_name, &privilege_size)) andre@676: { andre@676: PRINTLASTERROR ("Failed to lookup privilege name"); andre@676: } andre@676: andre@676: if(strcmp(privilege_name, "SeRestorePrivilege") == 0) andre@676: { andre@676: psToken->Privileges[dwI].Attributes |= SE_PRIVILEGE_ENABLED; andre@676: restore_found = true; andre@676: continue; andre@676: } andre@676: if(strcmp(privilege_name, "SeBackupPrivilege") == 0) andre@676: { andre@676: psToken->Privileges[dwI].Attributes |= SE_PRIVILEGE_ENABLED; andre@676: backup_found = true; andre@676: continue; andre@676: } andre@676: if (backup_found && restore_found) andre@676: { andre@676: break; andre@676: } andre@676: } andre@676: andre@676: if (backup_found && restore_found) andre@676: { andre@676: if(!AdjustTokenPrivileges (hToken, 0, psToken, token_size, NULL, NULL)) andre@676: { andre@676: PRINTLASTERROR ("Failed to adjust token privileges."); andre@676: } andre@676: else andre@676: { andre@676: retval = true; andre@676: } andre@676: } andre@676: andre@676: done: andre@676: if (hToken != NULL) andre@676: { andre@676: CloseHandle(hToken); andre@676: } andre@676: xfree(psToken); andre@676: return retval; andre@676: } andre@676: andre@668: /**@brief Register NSS process as runOnce for other users andre@668: * andre@668: * Loads the registry hives of other users on the system and andre@668: * adds a RunOnce registry key to start the NSS process to andre@668: * install the current selection on their next login. andre@668: * andre@668: * This should avoid conflicts with their firefox / thunderbird andre@668: * while making the certificates available for their applications. andre@668: * andre@668: * This function needs SE_BACKUP_NAME and SE_RESTORE_NAME andre@668: * privileges. andre@668: * andre@668: * @param [in] selection_file filename of the file containing andre@668: * the users install / remove selection. andre@668: */ andre@676: static void andre@668: register_proccesses_for_others (wchar_t *selection_file) andre@668: { andre@856: pkp_t *pkplist = locate_other_hives(), andre@905: *cur = NULL; andre@675: wchar_t *run_command = NULL; andre@668: andre@856: if (pkplist == NULL) andre@675: { andre@675: DEBUGPRINTF ("No hives found."); andre@675: return; andre@675: } andre@676: andre@676: if (!get_backup_restore_priv()) andre@676: { andre@676: ERRORPRINTF ("Failed to obtain backup / restore privileges."); andre@676: return; andre@676: } andre@676: andre@675: run_command = get_command_line (selection_file); andre@856: for (cur = pkplist; cur != NULL; cur = cur->next) andre@675: { andre@675: LONG ret = 0; andre@856: wchar_t *hivepath = utf8_to_wchar (cur->hive_path, strlen(cur->hive_path)); andre@675: HKEY key_handle = NULL; andre@856: bool key_loaded = false; andre@675: andre@675: if (hivepath == NULL) andre@675: { andre@675: ERRORPRINTF ("Failed to read hive path"); andre@675: continue; andre@675: } andre@675: ret = RegLoadKeyW (HKEY_LOCAL_MACHINE, APPNAME L"_tmphive", hivepath); andre@675: andre@675: xfree (hivepath); andre@675: hivepath = NULL; andre@675: andre@675: if (ret != ERROR_SUCCESS) andre@675: { andre@675: /* This is somewhat expected if the registry is not located andre@856: in the standard location or already loaded. Try to access andre@856: the loaded registry in that case*/ andre@856: wchar_t *user_key = NULL, andre@905: *w_sid = NULL; andre@856: size_t user_key_len = 0; andre@675: andre@856: SetLastError((DWORD)ret); andre@856: PRINTLASTERROR ("Failed to load hive. Trying to access already loaded hive."); andre@675: andre@856: w_sid = utf8_to_wchar (cur->sid, strlen(cur->sid)); andre@856: if (!w_sid) andre@856: { andre@856: ERRORPRINTF ("Failed to read sid."); andre@856: continue; andre@856: } andre@856: user_key_len = wcslen (L"\\" RUNONCE_PATH) + wcslen(w_sid) + 1; andre@856: user_key = xmalloc (user_key_len * sizeof (wchar_t)); andre@856: wcscpy_s (user_key, user_key_len, w_sid); andre@856: wcscat_s (user_key, user_key_len, L"\\" RUNONCE_PATH); andre@856: user_key[user_key_len - 1] = '\0'; andre@856: xfree (w_sid); andre@856: w_sid = NULL; andre@856: andre@856: ret = RegOpenKeyExW (HKEY_USERS, andre@856: user_key, andre@856: 0, andre@856: KEY_WRITE, andre@856: &key_handle); andre@856: xfree (user_key); andre@856: if (ret != ERROR_SUCCESS) andre@856: { andre@856: ERRORPRINTF ("Failed to find RunOnce key for sid: %s in HKEY_USERS.", cur->sid); andre@856: continue; andre@856: } andre@856: } andre@856: else andre@675: { andre@856: key_loaded = true; andre@856: ret = RegOpenKeyExW (HKEY_LOCAL_MACHINE, andre@856: APPNAME L"_tmphive\\" RUNONCE_PATH, andre@856: 0, andre@856: KEY_WRITE, andre@856: &key_handle); andre@856: andre@856: if (ret != ERROR_SUCCESS) andre@856: { andre@856: ERRORPRINTF ("Failed to find RunOnce key in other registry."); andre@856: RegUnLoadKey (HKEY_LOCAL_MACHINE, APPNAME L"_tmphive"); andre@856: continue; andre@856: } andre@856: andre@675: } andre@675: andre@675: ret = RegSetValueExW (key_handle, APPNAME, 0, REG_SZ, (LPBYTE) run_command, andre@675: (wcslen(run_command) + 1) * sizeof(wchar_t)); andre@675: andre@675: if (ret != ERROR_SUCCESS) andre@675: { andre@675: ERRORPRINTF ("Failed to write RunOnce key."); andre@675: } andre@675: andre@675: RegCloseKey (key_handle); andre@856: if (key_loaded) andre@677: { andre@856: ret = RegUnLoadKeyW (HKEY_LOCAL_MACHINE, APPNAME L"_tmphive"); andre@856: if (ret != ERROR_SUCCESS) andre@856: { andre@856: SetLastError ((DWORD)ret); andre@856: PRINTLASTERROR ("Failed to unload hive."); andre@856: } andre@677: } andre@675: } andre@675: andre@675: xfree (run_command); andre@856: pkp_t_free (pkplist); andre@668: } andre@668: 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@668: * the users install / remove selection. aheinecke@324: * andre@985: * @param [in] drop_privileges weather or not elevated privileges andre@985: * should be dropped before starting the process. andre@985: * aheinecke@324: * @returns true on success, false on error. aheinecke@324: */ aheinecke@324: static bool andre@985: start_procces_for_user (wchar_t *selection_file, bool drop_privileges) aheinecke@324: { andre@825: HANDLE hToken = NULL; andre@825: LPWSTR lpApplicationPath = NULL, andre@825: lpCommandLine = NULL; aheinecke@324: PROCESS_INFORMATION piProcInfo = {0}; aheinecke@324: STARTUPINFOW siStartInfo = {0}; aheinecke@324: BOOL success = FALSE; andre@825: char *install_dir = get_install_dir(); andre@825: wchar_t *w_inst_dir; andre@825: size_t w_path_len = 0; andre@1084: bin_verify_result v_res; aheinecke@324: andre@360: if (!selection_file) aheinecke@324: { andre@360: ERRORPRINTF ("Invalid call\n"); andre@360: return false; aheinecke@324: } aheinecke@324: andre@825: /* Set up the application path. It's installdir + NSS_APP_NAME */ andre@825: if (install_dir == NULL) andre@825: { andre@825: ERRORPRINTF ("Failed to get installation directory"); andre@825: return FALSE; andre@825: } andre@825: andre@825: w_inst_dir = utf8_to_wchar (install_dir, strlen(install_dir)); andre@825: xfree (install_dir); andre@825: install_dir = NULL; andre@825: andre@825: w_path_len = wcslen(w_inst_dir) + wcslen(L"\\" NSS_APP_NAME) + 1; andre@825: lpApplicationPath = xmalloc(w_path_len * sizeof (wchar_t)); andre@825: wcscpy_s (lpApplicationPath, w_path_len, w_inst_dir); andre@825: xfree (w_inst_dir); andre@825: w_inst_dir = NULL; andre@825: wcscat_s (lpApplicationPath, w_path_len, L"\\" NSS_APP_NAME); andre@825: aheinecke@324: /* set up handles. stdin and stdout go to the same stdout*/ aheinecke@324: siStartInfo.cb = sizeof (STARTUPINFO); andre@360: andre@985: if (is_elevated() && drop_privileges) andre@824: { andre@824: /* Start the child process as normal user */ andre@824: hToken = get_restricted_token (); andre@824: if (hToken == NULL) andre@824: { andre@824: ERRORPRINTF ("Failed to get user level token."); andre@824: return false; andre@824: } andre@824: } andre@824: else if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) andre@360: { andre@360: PRINTLASTERROR("Failed to get current handle."); andre@825: xfree (lpApplicationPath); andre@360: return false; andre@360: } andre@360: andre@675: lpCommandLine = get_command_line (selection_file); andre@360: andre@675: if (lpCommandLine == NULL) andre@675: { andre@675: ERRORPRINTF ("Failed to build command line."); andre@825: xfree (lpApplicationPath); andre@675: return false; andre@675: } aheinecke@324: andre@1084: /* Verify the binary */ andre@1084: { andre@1084: char *utf8_name = wchar_to_utf8 (lpApplicationPath, wcslen(lpApplicationPath)); andre@1084: v_res = verify_binary (utf8_name, strlen(utf8_name)); andre@1084: xfree(utf8_name); andre@1084: } andre@1084: andre@1084: if (v_res.result != VerifyValid) andre@1084: { andre@1084: ERRORPRINTF ("Failed to verify the NSS installer.\n"); andre@1084: syslog_error_printf ("Integrity check of the certificate installation subprocess for NSS failed.\n"); andre@1084: xfree (lpApplicationPath); andre@1084: xfree (lpCommandLine); andre@1084: return false; andre@1084: } andre@1084: andre@825: DEBUGPRINTF ("Starting %S with command line %S\n", lpApplicationPath, lpCommandLine); andre@392: aheinecke@324: success = CreateProcessAsUserW (hToken, andre@825: lpApplicationPath, 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@1084: fclose (v_res.fptr); andre@825: xfree (lpApplicationPath); 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 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@826: * they are written in the 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@905: *path = NULL; andre@360: HANDLE hFile = NULL; andre@360: size_t path_len; andre@1208: PACL access_control_list = NULL; andre@360: andre@983: folder_name = get_program_data_folder(); andre@983: if (!folder_name) andre@360: { andre@983: ERRORPRINTF("Failed to look up ProgramData folder.\n"); 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)); aheinecke@363: if (wcscpy_s (path, path_len, folder_name) != 0) andre@360: { andre@360: ERRORPRINTF ("Failed to copy folder name.\n"); aheinecke@363: andre@360: CoTaskMemFree (folder_name); aheinecke@363: andre@360: return NULL; andre@360: } andre@360: andre@360: CoTaskMemFree (folder_name); andre@360: aheinecke@363: if (wcscat_s (path, path_len, L"\\") != 0) andre@360: { andre@360: ERRORPRINTF ("Failed to cat dirsep.\n"); andre@360: xfree(path); andre@360: return NULL; andre@360: } andre@360: aheinecke@363: if (wcscat_s (path, path_len, 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@1306: something so we take the acl that would have been used to andre@1306: create the directory and apply it later on if the directory andre@1306: exists. */ andre@1306: if (is_elevated()) andre@360: { andre@1306: if(!create_restricted_directory (path, true, &access_control_list)) andre@1306: { andre@1306: ERRORPRINTF ("Failed to create directory\n"); andre@1306: xfree(path); andre@1306: return NULL; andre@1306: } andre@1306: } andre@1306: else andre@1306: { andre@1306: /* We are not elevated so we do not have to care about andre@1306: restricting access and just create the directory with andre@1306: default access rights. */ andre@1306: if (!CreateDirectoryW(path, NULL)) andre@1306: { andre@1306: DWORD err = GetLastError(); andre@1306: if (err != ERROR_ALREADY_EXISTS) andre@1306: { andre@1306: PRINTLASTERROR ("Failed to create directory"); andre@1306: DEBUGPRINTF ("Directory path is: %S ", path); andre@1306: xfree (path); andre@1306: return NULL; andre@1306: } andre@1306: } andre@360: } andre@360: aheinecke@363: if (wcscat_s (path, path_len, L"\\") != 0) andre@360: { andre@360: ERRORPRINTF ("Failed to cat dirsep.\n"); andre@360: xfree(path); andre@1306: if (access_control_list) andre@1306: { andre@1306: LocalFree(access_control_list); andre@1306: } andre@360: return NULL; andre@360: } andre@360: aheinecke@363: if (wcscat_s (path, path_len, SELECTION_FILE_NAME) != 0) andre@360: { andre@360: ERRORPRINTF ("Failed to cat filename.\n"); andre@360: xfree(path); andre@1306: if (access_control_list) andre@1306: { andre@1306: LocalFree(access_control_list); andre@1306: } 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 */ aheinecke@489: OPEN_ALWAYS | TRUNCATE_EXISTING, andre@360: 0, andre@360: NULL); andre@360: aheinecke@502: if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_FILE_NOT_FOUND) aheinecke@502: { aheinecke@502: hFile = CreateFileW(path, aheinecke@502: GENERIC_WRITE, aheinecke@502: 0, /* don't share */ aheinecke@502: NULL, /* use the security attributes from the folder */ aheinecke@502: CREATE_NEW, aheinecke@502: 0, aheinecke@502: NULL); aheinecke@502: } andre@1306: else if (access_control_list) andre@1208: { andre@1306: /* Opened existing file so set our ACL on it if andre@1306: we created a restricted directory where andre@1306: we obtained the access_control_list */ andre@1208: PSID admin_SID = NULL; andre@1208: SID_IDENTIFIER_AUTHORITY admin_identifier = {SECURITY_NT_AUTHORITY}; andre@1208: andre@1208: /* Create the SID for the BUILTIN\Administrators group. */ andre@1208: if(!AllocateAndInitializeSid(&admin_identifier, andre@1208: 2, andre@1208: SECURITY_BUILTIN_DOMAIN_RID, /*BUILTIN\ */ andre@1208: DOMAIN_ALIAS_RID_ADMINS, /*\Administrators */ andre@1208: 0, 0, 0, 0, 0, 0, /* No other */ andre@1208: &admin_SID)) andre@1208: { andre@1208: PRINTLASTERROR ("Failed to allocate admin sid."); andre@1208: syslog_error_printf ( "Failed to allocate admin sid."); andre@1208: if (hFile) andre@1208: { andre@1208: CloseHandle (hFile); andre@1208: } andre@1208: xfree (path); andre@1208: LocalFree(access_control_list); andre@1208: return NULL; andre@1208: } andre@1208: andre@1208: if (SetNamedSecurityInfoW (path, andre@1208: SE_FILE_OBJECT, andre@1208: DACL_SECURITY_INFORMATION | andre@1208: OWNER_SECURITY_INFORMATION | andre@1208: GROUP_SECURITY_INFORMATION, andre@1208: admin_SID, /* owner */ andre@1208: admin_SID, /* group */ andre@1208: access_control_list, /* the dacl */ andre@1208: NULL) != ERROR_SUCCESS) andre@1208: { andre@1208: ERRORPRINTF ("Failed to set the ACL on the NSS instruction file."); andre@1208: if (hFile) andre@1208: { andre@1208: CloseHandle (hFile); andre@1208: } andre@1208: FreeSid(admin_SID); andre@1208: LocalFree(access_control_list); andre@1208: xfree (path); andre@1208: return NULL; andre@1208: } andre@1208: FreeSid(admin_SID); andre@1208: } andre@1208: andre@1306: if (access_control_list) andre@1306: { andre@1306: LocalFree(access_control_list); andre@1306: } andre@1208: andre@360: if (hFile == INVALID_HANDLE_VALUE) andre@360: { andre@1306: DEBUGPRINTF("Failed to create or open file: %S", path); andre@1306: PRINTLASTERROR ("ERROR"); andre@1208: syslog_error_printf ( "Failed to create nss instruction file."); 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@1208: syslog_error_printf ( "Failed to write nss instruction file."); 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@1208: syslog_error_printf ( "Failed to write nss instruction file removal entries."); 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@668: if (is_elevated()) andre@668: { andre@668: register_proccesses_for_others (selection_file_name); andre@985: /* Start the NSS process once with elevated rights to andre@985: install into the default profile directories. */ andre@985: if (!start_procces_for_user (selection_file_name, false)) andre@985: { andre@985: ERRORPRINTF ("Failed to run NSS installation process for default folders.\n"); andre@985: xfree(selection_file_name); andre@985: return -1; andre@985: } andre@668: } andre@360: andre@985: if (!start_procces_for_user (selection_file_name, true)) 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