comparison cinst/nssstore_win.c @ 360:c0eac5c8c245

Keep working on nssstore_win and clarify its specification
author Andre Heinecke <andre.heinecke@intevation.de>
date Thu, 10 Apr 2014 20:20:02 +0200
parents 1e6d1eab8395
children d10d9bc2e84f
comparison
equal deleted inserted replaced
359:f6ce186cebc2 360:c0eac5c8c245
1 #ifdef WIN32 1 #ifdef WIN32
2 2
3 /* @file 3 /* @file
4 @brief Windows implementation of nssstore process control. 4 @brief Windows implementation of nssstore process control.
5
6 The windows process will write an instructions file for
7 the mozilla process into the current users temp directory
8 (%APPDATA%/Local/Temp/) and start the NSS installation process to
9 exectute those instructions. If the current process is elevated
10 the NSS process is run with a restricted token.
11 The execution of the mozilla process is not monitored.
12 You have to refer to the system log to check which certificates were
13 installed / removed by it.
14
15 If the installation process is running elevated it
16 will create the file in the ProgramData directory in
17 a subdirectory with the defined application name.
18 %PROGRAMDATA%/$APPLICATION_NAME
19 with the file name:
20 current_selection.txt
21 The folder will have restricted permissions so
22 that only Administrators are allowed to access it.
23
24 Additionally if this process is Elevated it also starts the
25 NSS installation process in default profile mode once to change
26 the default NSS certificate databases for new profiles.
27
28 The process then adds a new RunOnce registry key
29 for each user on the system that executes the NSS installation
30 process on login to make sure it is launched once in the
31 security context of that user.
5 */ 32 */
6 33
7 #include <windows.h> 34 #include <windows.h>
8 #include <stdio.h> 35 #include <stdio.h>
9 #include <strsafe.h>
10 #include <stdbool.h> 36 #include <stdbool.h>
11 #include <userenv.h> 37 #include <userenv.h>
12 #include <io.h> 38 #include <io.h>
39 #include <accctrl.h>
40 #include <aclapi.h>
41 #include <shlobj.h>
13 42
14 #include "logging.h" 43 #include "logging.h"
15 #include "util.h" 44 #include "util.h"
16 45 #include "strhelp.h"
17 #define PROCESS_TIMEOUT 30000 /* In milliseconds */ 46
18 47 #ifndef APPNAME
19 #define BUFSIZE 4096 /* used for reading childs stdout */ 48 #define APPNAME L"cinst"
49 #endif
50
51 #ifndef SELECTION_FILE_NAME
52 #define SELECTION_FILE_NAME L"currently_selected.txt"
53 #endif
54
55 #define PROCESS_TIMEOUT 30000
20 56
21 #define PRINTLASTERROR(msg) \ 57 #define PRINTLASTERROR(msg) \
22 char *my_error = getLastErrorMsg(); \ 58 char *my_error = getLastErrorMsg(); \
23 if (my_error) { \ 59 if (my_error) { \
24 DEBUGPRINTF(msg " : %s\n", my_error); \ 60 DEBUGPRINTF(msg " : %s\n", my_error); \
79 return true; 115 return true;
80 } 116 }
81 117
82 /**@brief Start the process to install / remove 118 /**@brief Start the process to install / remove
83 * 119 *
84 * Creates a child process with the Security handle specified in hToken 120 * Starts the NSS installation process for the current user
85 * sends the instructions and then waits for the process to finish.
86 * 121 *
87 * If the process is not done in PROCESS_TIMEOUT seconds this assumes an 122 * @param [in] selection_file filename of the file containing
88 * unknown error happened. 123 * the users installall / remove selection.
89 *
90 * @param [in] to_install strv of DER encoded certificates to be added.
91 * @param [in] to_remove strv of DER encoded certificates to be remvoed.
92 * @param [in] hToken handle to the primary token that is used to install
93 * certificates.
94 * 124 *
95 * @returns true on success, false on error. 125 * @returns true on success, false on error.
96 */ 126 */
97 static bool 127 static bool
98 start_procces_for_user (char **to_install, char **to_remove, 128 start_procces_for_user (wchar_t *selection_file)
99 HANDLE hToken)
100 { 129 {
101 HANDLE h_stdin_child_r = NULL, 130 HANDLE hToken = NULL;/*,
102 h_stdin_child_w = NULL, 131 hChildToken = NULL;*/
103 h_stdout_child_r = NULL,
104 h_stdout_child_w = NULL;
105 SECURITY_ATTRIBUTES saAttr = {0};
106 /* TODO get this as absolute path based on current module location */ 132 /* TODO get this as absolute path based on current module location */
107 LPWSTR lpApplicationName = L"mozilla.exe"; 133 LPWSTR lpApplicationName = L"mozilla.exe",
134 lpCommandLine;
108 PROCESS_INFORMATION piProcInfo = {0}; 135 PROCESS_INFORMATION piProcInfo = {0};
109 STARTUPINFOW siStartInfo = {0}; 136 STARTUPINFOW siStartInfo = {0};
110 LPVOID lpEnvironment = NULL;
111 BOOL success = FALSE; 137 BOOL success = FALSE;
112 int retval = -1; 138 size_t cmd_line_len = 0;
113 139
114 saAttr.nLength = sizeof (SECURITY_ATTRIBUTES); 140 if (!selection_file)
115 saAttr.bInheritHandle = TRUE; 141 {
116 saAttr.lpSecurityDescriptor = NULL; /* Use default */ 142 ERRORPRINTF ("Invalid call\n");
117 143 return false;
118 /* Create a pipe for the child process's STDIN. */
119 if (!CreatePipe (&h_stdin_child_r, &h_stdin_child_w, &saAttr, 0))
120 {
121 PRINTLASTERROR ("Create pipe failed.\n");
122 return -1;
123 }
124
125 /* Create a pipe for the child process's STDOUT. */
126 if (!CreatePipe (&h_stdout_child_r, &h_stdout_child_w, &saAttr, 0))
127 {
128 PRINTLASTERROR ("Create pipe failed.\n");
129 return -1;
130 }
131
132 /* Ensure that read/write is properly inherited */
133 if (!SetHandleInformation (h_stdin_child_w, HANDLE_FLAG_INHERIT, 0) ||
134 !SetHandleInformation (h_stdout_child_r, HANDLE_FLAG_INHERIT, 0))
135 {
136 PRINTLASTERROR ("SetHandleInformation failed.\n");
137 goto closepipes;
138 }
139
140 /* Create the environment for the user */
141 if (!CreateEnvironmentBlock (&lpEnvironment, hToken, FALSE))
142 {
143 PRINTLASTERROR ("Failed to create the environment.\n");
144 goto closepipes;
145 } 144 }
146 145
147 /* set up handles. stdin and stdout go to the same stdout*/ 146 /* set up handles. stdin and stdout go to the same stdout*/
148 siStartInfo.cb = sizeof (STARTUPINFO); 147 siStartInfo.cb = sizeof (STARTUPINFO);
149 siStartInfo.hStdError = h_stdout_child_w; 148
150 siStartInfo.hStdOutput = h_stdout_child_w; 149 if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken))
151 siStartInfo.hStdInput = h_stdin_child_r; 150 {
152 siStartInfo.dwFlags = STARTF_USESTDHANDLES; 151 PRINTLASTERROR("Failed to get current handle.");
152 return false;
153 }
154 /* TODO! if (is_elevated())
155 restrict token -> hChildToken
156 */
157
158 cmd_line_len = wcslen (lpApplicationName) + wcslen(selection_file) + 1;
159 lpCommandLine = xmalloc (cmd_line_len * sizeof(wchar_t));
160
161 wcscpy (lpCommandLine, lpApplicationName);
162 wcscat (lpCommandLine, selection_file);
153 163
154 success = CreateProcessAsUserW (hToken, 164 success = CreateProcessAsUserW (hToken,
155 lpApplicationName, 165 lpApplicationName,
156 NULL, /* Commandline */ 166 lpCommandLine, /* Commandline */
157 NULL, /* Process attributes. Take hToken */ 167 NULL, /* Process attributes. Take hToken */
158 NULL, /* Thread attribues. Take hToken */ 168 NULL, /* Thread attribues. Take hToken */
159 TRUE, /* Inherit Handles */ 169 FALSE, /* Inherit Handles */
160 CREATE_UNICODE_ENVIRONMENT, /* Creation flags. */ 170 0, /* Creation flags. */
161 lpEnvironment, 171 NULL, /* Inherit environment */
162 NULL, /* Current working directory */ 172 NULL, /* Current working directory */
163 &siStartInfo, 173 &siStartInfo,
164 &piProcInfo); 174 &piProcInfo);
175 xfree (lpCommandLine);
165 if (!success) 176 if (!success)
166 { 177 {
167 PRINTLASTERROR ("Failed to create process.\n"); 178 PRINTLASTERROR ("Failed to create process.\n");
168 goto closepipes; 179 return false;
169 } 180 }
170
171 if (!write_instructions (to_install, h_stdin_child_w, false))
172 {
173 ERRORPRINTF ("Failed to write install instructions.\n");
174 goto closepipes;
175 }
176 if (!write_instructions (to_remove, h_stdin_child_w, true))
177 {
178 ERRORPRINTF ("Failed to write remove instructions.\n");
179 goto closepipes;
180 }
181
182 /* Close the Handle so that the child knows we are finished
183 telling it what to do */
184 CloseHandle (h_stdin_child_w);
185 h_stdin_child_w = NULL;
186
187 #ifndef RELEASE_BUILD
188 /* print childs stdout / stderr to parents stdout */
189 {
190 HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
191 DWORD read_bytes = 0;
192 DWORD written = 0;
193 bool success;
194 char buf[BUFSIZE];
195
196 if (!stdout_handle || stdout_handle == INVALID_HANDLE_VALUE)
197 {
198 /* Should not happen */
199 ERRORPRINTF("Failed to get stdout handle.\n");
200 goto closeprocess;
201 }
202
203 for (;;)
204 {
205 success = ReadFile (h_stdout_child_r, buf, BUFSIZE, &read_bytes, NULL);
206 if(!success || read_bytes == 0)
207 break;
208
209 success = WriteFile (stdout_handle, buf,
210 read_bytes, &written, NULL);
211 if (!success || written != read_bytes)
212 break;
213 }
214 }
215 #endif
216 181
217 if (WaitForSingleObject (piProcInfo.hProcess, PROCESS_TIMEOUT) != WAIT_OBJECT_0) 182 if (WaitForSingleObject (piProcInfo.hProcess, PROCESS_TIMEOUT) != WAIT_OBJECT_0)
218 { 183 {
219 /* Should not happen... */ 184 /* Should not happen... */
220 ERRORPRINTF ("Failed to wait for process.\n"); 185 ERRORPRINTF ("Failed to wait for process.\n");
221 } 186 if (piProcInfo.hProcess)
222 187 CloseHandle (piProcInfo.hProcess);
223 closeprocess: 188 if (piProcInfo.hThread)
189 CloseHandle (piProcInfo.hThread);
190 return false;
191 }
224 if (piProcInfo.hProcess) 192 if (piProcInfo.hProcess)
225 CloseHandle (piProcInfo.hProcess); 193 CloseHandle (piProcInfo.hProcess);
226 if (piProcInfo.hThread) 194 if (piProcInfo.hThread)
227 CloseHandle (piProcInfo.hThread); 195 CloseHandle (piProcInfo.hThread);
228 196 return true;
229 closepipes: 197 }
230 if (lpEnvironment) 198
231 DestroyEnvironmentBlock (lpEnvironment); 199 /**@brief Create a directory with restricted access rights
232 if (h_stdin_child_w) 200 *
233 CloseHandle (h_stdin_child_w); 201 * This creates a security attributes structure that restricts
234 if (h_stdin_child_r) 202 * write access to the Administrators group but allows everyone to read files
235 CloseHandle (h_stdin_child_r); 203 * in that directory.
236 if (h_stdout_child_w) 204 * Basically a very complicated vetrsion of mkdir path -m 604
237 CloseHandle (h_stdout_child_w); 205 *
238 if (h_stdout_child_r) 206 * If the directory exists the permissions of that directory are checked if
239 CloseHandle (h_stdout_child_r); 207 * they are acceptable and true or false is returned accordingly.
208 *
209 * Code based on msdn example:
210 * http://msdn.microsoft.com/en-us/library/windows/desktop/aa446595%28v=vs.85%29.aspx
211 *
212 * @param[in] path Path of the directory to create
213 *
214 * @returns true on success of if the directory exists, false on error
215 */
216 bool
217 create_restricted_directory (LPWSTR path)
218 {
219 bool retval = false;
220 PSID everyone_SID = NULL,
221 admin_SID = NULL;
222 PACL access_control_list = NULL;
223 PSECURITY_DESCRIPTOR descriptor = NULL;
224 EXPLICIT_ACCESS explicit_access[2];
225 SID_IDENTIFIER_AUTHORITY world_identifier = {SECURITY_WORLD_SID_AUTHORITY},
226 admin_identifier = {SECURITY_NT_AUTHORITY};
227 SECURITY_ATTRIBUTES security_attributes;
228
229 ZeroMemory(&security_attributes, sizeof(security_attributes));
230 ZeroMemory(&explicit_access, 2 * sizeof(EXPLICIT_ACCESS));
231
232 /* Create a well-known SID for the Everyone group. */
233 if(!AllocateAndInitializeSid(&world_identifier, /* top-level identifier */
234 1, /* subauthorties count */
235 SECURITY_WORLD_RID, /* Only one authority */
236 0, 0, 0, 0, 0, 0, 0, /* No other authorities*/
237 &everyone_SID))
238 {
239 PRINTLASTERROR ("Failed to allocate world sid.\n");
240 return false;
241 }
242
243 /* Initialize the first EXPLICIT_ACCESS structure for an ACE.
244 to allow everyone read access */
245 explicit_access[0].grfAccessPermissions = GENERIC_READ; /* Give read access */
246 explicit_access[0].grfAccessMode = SET_ACCESS; /* Overwrite other access for all users */
247 explicit_access[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; /* make it stick */
248 explicit_access[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
249 explicit_access[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
250 explicit_access[0].Trustee.ptstrName = (LPTSTR) everyone_SID;
251
252 /* Create the SID for the BUILTIN\Administrators group. */
253 if(!AllocateAndInitializeSid(&admin_identifier,
254 2,
255 SECURITY_BUILTIN_DOMAIN_RID, /*BUILTIN\ */
256 DOMAIN_ALIAS_RID_ADMINS, /*\Administrators */
257 0, 0, 0, 0, 0, 0, /* No other */
258 &admin_SID))
259 {
260 PRINTLASTERROR ("Failed to allocate admin sid.");
261 goto done;
262 }
263
264 /* explicit_access[1] grants admins full rights for this object and inherits
265 it to the children */
266 explicit_access[1].grfAccessPermissions = GENERIC_ALL;
267 explicit_access[1].grfAccessMode = SET_ACCESS;
268 explicit_access[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
269 explicit_access[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
270 explicit_access[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
271 explicit_access[1].Trustee.ptstrName = (LPTSTR) admin_SID;
272
273 /* Set up the ACL structure. */
274 if (ERROR_SUCCESS != SetEntriesInAcl(2, explicit_access, NULL, &access_control_list))
275 {
276 PRINTLASTERROR ("Failed to set up Acl.");
277 goto done;
278 }
279
280 /* Initialize a security descriptor */
281 descriptor = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR,
282 SECURITY_DESCRIPTOR_MIN_LENGTH);
283 if (descriptor == NULL)
284 {
285 PRINTLASTERROR("Failed to allocate descriptor.");
286 goto done;
287 }
288
289 if (!InitializeSecurityDescriptor(descriptor,
290 SECURITY_DESCRIPTOR_REVISION))
291 {
292 PRINTLASTERROR("Failed to initialize descriptor.");
293 goto done;
294 }
295
296 /* Now we add the ACL to the the descriptor */
297 if (!SetSecurityDescriptorDacl(descriptor,
298 TRUE, /* bDaclPresent flag */
299 access_control_list,
300 FALSE)) /* not a default DACL */
301 {
302 PRINTLASTERROR("Failed to set security descriptor.");
303 goto done;
304 }
305
306 /* Finally set up the security attributes structure */
307 security_attributes.nLength = sizeof (SECURITY_ATTRIBUTES);
308 security_attributes.lpSecurityDescriptor = descriptor;
309 security_attributes.bInheritHandle = FALSE;
310
311 /* Use the security attributes to create the directory */
312 if (!CreateDirectoryW(path, &security_attributes))
313 {
314 DWORD err = GetLastError();
315 if (err == ERROR_ALREADY_EXISTS)
316 {
317 /* Verify that the directory has the correct rights */
318 // TODO
319 retval = true;
320 goto done;
321 }
322 ERRORPRINTF ("Failed to create directory. Err: %lu", err);
323 }
324 retval = true;
325
326 done:
327
328 if (everyone_SID)
329 FreeSid(everyone_SID);
330 if (admin_SID)
331 FreeSid(admin_SID);
332 if (access_control_list)
333 LocalFree(access_control_list);
334 if (descriptor)
335 LocalFree(descriptor);
240 336
241 return retval; 337 return retval;
338 }
339
340 /**@brief Writes the selection file containing the instructions
341 *
342 * If the process is running elevated the instructions are
343 * written to the global ProgramData directory otherwise
344 * they are written in the temporary directory of the current user.
345 *
346 * If the return value is not NULL it needs to be freed by the caller.
347 * The returned path will contain backslashes as directory seperators.
348 *
349 * @param[in] to_install Certificates that should be installed
350 * @param[in] to_remove Certificates that should be removed
351 * @returns pointer to the absolute filename of the selection file or NULL
352 */
353 wchar_t *
354 write_selection_file (char **to_install, char **to_remove)
355 {
356 wchar_t *folder_name = NULL,
357 *path = NULL;
358 bool elevated = is_elevated();
359 HRESULT result = E_FAIL;
360 HANDLE hFile = NULL;
361 size_t path_len;
362
363 if (!elevated)
364 {
365 /* TODO */
366 }
367
368 #if 0
369 result = SHGetKnownFolderPath (FOLDERID_ProgramData, /* Get program data dir */
370 KF_FLAG_CREATE | /* Create if it does not exist */
371 KF_FLAG_INIT, /* Initialize it if created */
372 -1, /* Get it for the default user */
373 &folder_name);
374 #endif
375 /* TODO figure out how to do this with mingw */
376 result = S_OK;
377 folder_name = L"C:\\ProgramData";
378
379 if (result != S_OK)
380 {
381 PRINTLASTERROR ("Failed to get folder path");
382 return NULL;
383 }
384
385 path_len = wcslen (folder_name) + wcslen (APPNAME) + 2; /* path + dirsep + \0 */
386 path_len += wcslen (SELECTION_FILE_NAME) + 1; /* filename + dirsep */
387
388 if (path_len >= MAX_PATH)
389 {
390 /* We could go and use the full 32,767 characters but this
391 should be a very weird setup if this is neccessary. */
392 ERRORPRINTF ("Path too long.\n");
393 return NULL;
394 }
395
396 path = xmalloc (path_len * sizeof (wchar_t));
397 if (wcscpy (path, folder_name) != 0)
398 {
399 ERRORPRINTF ("Failed to copy folder name.\n");
400 #if 0
401 CoTaskMemFree (folder_name);
402 #endif
403 return NULL;
404 }
405
406 #if 0
407 CoTaskMemFree (folder_name);
408 #endif
409
410 if (wcscat (path, L"\\") != 0)
411 {
412 ERRORPRINTF ("Failed to cat dirsep.\n");
413 xfree(path);
414 return NULL;
415 }
416
417 if (wcscat (path, APPNAME) != 0)
418 {
419 ERRORPRINTF ("Failed to cat appname.\n");
420 xfree(path);
421 return NULL;
422 }
423
424 /* Security: if someone has created this directory before
425 it might be a symlink to another place that a users
426 wants us to grant read access to or makes us overwrite
427 something */
428 if(!create_restricted_directory (path))
429 {
430 ERRORPRINTF ("Failed to create directory\n");
431 xfree(path);
432 return NULL;
433 }
434
435 if (wcscat (path, L"\\") != 0)
436 {
437 ERRORPRINTF ("Failed to cat dirsep.\n");
438 xfree(path);
439 return NULL;
440 }
441
442 if (wcscat (path, SELECTION_FILE_NAME) != 0)
443 {
444 ERRORPRINTF ("Failed to cat filename.\n");
445 xfree(path);
446 return NULL;
447 }
448
449 hFile = CreateFileW(path,
450 GENERIC_WRITE,
451 0, /* don't share */
452 NULL, /* use the security attributes from the folder */
453 OPEN_ALWAYS,
454 0,
455 NULL);
456
457 if (hFile == INVALID_HANDLE_VALUE)
458 {
459 ERRORPRINTF ("Failed to create file\n");
460 xfree(path);
461 return NULL;
462 }
463 if (!write_instructions (to_install, hFile, false))
464 {
465 ERRORPRINTF ("Failed to write install instructions.\n");
466 CloseHandle(hFile);
467 xfree(path);
468 return NULL;
469 }
470 if (!write_instructions (to_remove, hFile, true))
471 {
472 ERRORPRINTF ("Failed to write remove instructions.\n");
473 CloseHandle(hFile);
474 xfree(path);
475 return NULL;
476 }
477 CloseHandle(hFile);
478
479 return path;
242 } 480 }
243 481
244 int 482 int
245 write_stores_nss (char **to_install, char **to_remove) 483 write_stores_nss (char **to_install, char **to_remove)
246 { 484 {
247 HANDLE hToken = NULL; 485 wchar_t *selection_file_name = NULL;
248 OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken); 486
249 487 selection_file_name = write_selection_file (to_install, to_remove);
250 /* TODO loop over all users */ 488 if (!selection_file_name)
251 if (!start_procces_for_user (to_install, to_remove, hToken)) 489 {
490 ERRORPRINTF ("Failed to write instructions.\n");
491 return -1;
492 }
493
494 DEBUGPRINTF ("Wrote selection file. Loc: %S\n", selection_file_name);
495
496 /* TODO loop over all users create startup entries for them*/
497
498 if (!start_procces_for_user (selection_file_name))
252 { 499 {
253 ERRORPRINTF ("Failed to run NSS installation process.\n"); 500 ERRORPRINTF ("Failed to run NSS installation process.\n");
501 xfree(selection_file_name);
254 return -1; 502 return -1;
255 } 503 }
504 xfree(selection_file_name);
256 return 0; 505 return 0;
257 } 506 }
258 507
259 #endif 508 #endif

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