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@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 andre@824: #include andre@670: #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@675: /**@def The name of the nss installation process */ andre@675: #define NSS_APP_NAME L"mozilla.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@668: andre@824: /** @brief get a restricted access token to execute nss process andre@824: * andre@824: * This function uses the Software Restriction API to obtain the andre@824: * access token for a process run als normal user. andre@824: * andre@824: * @returns A restricted handle or NULL on error. andre@824: */ andre@824: static HANDLE andre@824: get_restricted_token() andre@824: { andre@824: SAFER_LEVEL_HANDLE user_level = NULL; andre@824: HANDLE retval = NULL; andre@824: if (!SaferCreateLevel(SAFER_SCOPEID_USER, andre@824: SAFER_LEVELID_NORMALUSER, andre@824: SAFER_LEVEL_OPEN, &user_level, NULL)) andre@824: { andre@824: PRINTLASTERROR ("Failed to create user level.\n"); andre@824: return NULL; andre@824: } andre@824: andre@824: if (!SaferComputeTokenFromLevel(user_level, NULL, &retval, 0, NULL)) andre@824: { andre@824: SaferCloseLevel(user_level); andre@824: return NULL; andre@824: } andre@824: andre@824: return retval; andre@824: } andre@824: 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: } aheinecke@324: andre@674: /**@brief Read (and expand if necessary) a registry string. andre@674: * andre@674: * Reads a registry string and calls ExpandEnvironmentString andre@674: * if necessary on it. Returns a newly allocated string array andre@674: * with the expanded registry value converted to UTF-8 andre@674: * andre@674: * Caller has to free return value with free. andre@674: * andre@674: * @param [in] root the root key (e.g. HKEY_LOCAL_MACHINE) andre@674: * @param [in] key the key andre@674: * @param [in] name the name of the value to read. andre@674: * andre@674: * @returns the expanded, null terminated utf-8 string of the value. andre@674: * or NULL on error. andre@674: */ andre@674: static char* andre@674: read_registry_string (const HKEY root, const wchar_t *key, andre@674: const wchar_t *name) andre@674: { andre@674: HKEY key_handle = NULL; andre@674: DWORD size = 0, andre@674: type = 0, andre@674: ex_size = 0, andre@674: dwRet = 0; andre@674: LONG ret = 0; andre@674: char *retval = NULL; andre@674: wchar_t *buf = NULL, andre@674: *ex_buf = NULL; andre@674: if (root == NULL || key == NULL || name == NULL) andre@674: { andre@674: ERRORPRINTF ("Invalid call to read_registry_string"); andre@674: return NULL; andre@674: } andre@674: andre@674: ret = RegOpenKeyExW (root, key, 0, KEY_READ, &key_handle); andre@674: if (ret != ERROR_SUCCESS) andre@674: { andre@674: ERRORPRINTF ("Failed to open key."); andre@674: return NULL; andre@674: } andre@674: andre@674: /* Get the size */ andre@674: ret = RegQueryValueExW (key_handle, name, 0, NULL, NULL, &size); andre@674: if (ret != ERROR_MORE_DATA && !(ret == ERROR_SUCCESS && size != 0)) andre@674: { andre@674: ERRORPRINTF ("Failed to get required registry size."); andre@674: return retval; andre@674: } andre@674: andre@674: /* Size is size in bytes not in characters */ andre@674: buf = xmalloc (size + sizeof(wchar_t)); andre@674: andre@674: /* If the stored value is not zero terminated the returned value also andre@674: is not zero terminated. That's why we reserve more and ensure it's andre@674: initialized. */ andre@674: memset (buf, 0, size + sizeof(wchar_t)); andre@674: andre@674: ret = RegQueryValueExW (key_handle, name, 0, &type, (LPBYTE) buf, &size); andre@674: if (ret != ERROR_SUCCESS) andre@674: { andre@674: ERRORPRINTF ("Failed get registry value."); andre@674: return retval; andre@674: } andre@674: andre@674: if (type == REG_SZ || (type == REG_EXPAND_SZ && wcschr (buf, '%') == NULL)) andre@674: { andre@674: /* Nothing to expand, we are done */ andre@674: retval = wchar_to_utf8 (buf, wcslen (buf)); andre@674: goto done; andre@674: } andre@674: andre@674: if (type != REG_EXPAND_SZ) andre@674: { andre@674: ERRORPRINTF ("Unhandled registry type %i", type); andre@674: goto done; andre@674: } andre@674: andre@674: /* Expand the registry string */ andre@674: ex_size = ExpandEnvironmentStringsW (buf, NULL, 0); andre@674: andre@674: if (ex_size == 0) andre@674: { andre@674: PRINTLASTERROR ("Failed to determine expanded environment size."); andre@674: goto done; andre@674: } andre@674: andre@674: ex_buf = xmalloc ((ex_size + 1) * sizeof(wchar_t)); andre@674: andre@674: dwRet = ExpandEnvironmentStringsW (buf, ex_buf, ex_size); andre@674: andre@674: ex_buf[ex_size] = '\0'; /* Make sure it's a string */ andre@674: andre@674: if (dwRet == 0 || dwRet != ex_size) andre@674: { andre@674: PRINTLASTERROR ("Failed to expand environment variables."); andre@674: goto done; andre@674: } andre@674: andre@674: retval = wchar_to_utf8 (ex_buf, ex_size); andre@674: andre@674: done: andre@674: xfree (ex_buf); andre@674: xfree (buf); andre@674: andre@674: RegCloseKey (key_handle); andre@674: return retval; andre@674: } 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@668: * strv array with the utf-8 encoded paths to their suggested andre@668: * 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@668: * Use strv_free to free that array. andre@668: * andre@668: * @returns a newly allocated strv of the paths to the registry hives or NULL andre@668: */ andre@668: static char** 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@668: char **retval = 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@674: key_path[key_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@674: andre@674: strv_append (&retval, profile_path, profile_path_len); andre@674: DEBUGPRINTF ("Trying to access registry hive: %s", profile_path); andre@674: andre@674: xfree (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@668: strv_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@675: + 1 + 2 + wcslen(selection_file) + 1; 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@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@668: char **hives = locate_other_hives(); andre@675: int i = 0; andre@675: wchar_t *run_command = NULL; andre@668: andre@675: if (hives == 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@675: for (i = 0; hives[i] != NULL; i++) andre@675: { andre@675: LONG ret = 0; andre@675: wchar_t *hivepath = utf8_to_wchar (hives[i], strlen(hives[i])); andre@675: HKEY key_handle = NULL; 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@675: in the standard location. Failure is accepted in that case. */ andre@677: SetLastError((DWORD)ret); andre@675: PRINTLASTERROR ("Failed to load hive."); andre@675: continue; andre@675: } andre@675: andre@675: ret = RegOpenKeyExW (HKEY_LOCAL_MACHINE, andre@675: APPNAME L"_tmphive\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce", andre@675: 0, andre@675: KEY_WRITE, andre@675: &key_handle); andre@675: andre@675: if (ret != ERROR_SUCCESS) andre@675: { andre@675: ERRORPRINTF ("Failed to find RunOnce key in other registry."); andre@675: RegUnLoadKey (HKEY_LOCAL_MACHINE, APPNAME L"_tmphive"); andre@675: continue; 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@677: ret = RegUnLoadKeyW (HKEY_LOCAL_MACHINE, APPNAME L"_tmphive"); andre@677: if (ret != ERROR_SUCCESS) andre@677: { andre@677: SetLastError ((DWORD)ret); andre@677: PRINTLASTERROR ("Failed to unload hive."); andre@677: } andre@675: } andre@675: andre@675: xfree (run_command); andre@668: strv_free (hives); 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: * 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@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; 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@824: if (is_elevated()) 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@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@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 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. aheinecke@363: * Basically a very complicated version of mkdir path -m 644 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@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@360: *path = NULL; andre@360: HRESULT result = E_FAIL; andre@360: HANDLE hFile = NULL; andre@360: size_t path_len; andre@360: aheinecke@363: 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 */ aheinecke@363: INVALID_HANDLE_VALUE, /* Get it for the default user */ andre@360: &folder_name); 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)); 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@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: 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, 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 */ 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@360: if (hFile == INVALID_HANDLE_VALUE) andre@360: { aheinecke@502: PRINTLASTERROR ("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@668: if (is_elevated()) andre@668: { andre@668: register_proccesses_for_others (selection_file_name); andre@668: } 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