Process Hacker
srvprp.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * service properties
4  *
5  * Copyright (C) 2010-2015 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include <phapp.h>
24 #include <secedit.h>
25 #include <phplug.h>
26 #include <phsvccl.h>
27 #include <windowsx.h>
28 
29 typedef struct _SERVICE_PROPERTIES_CONTEXT
30 {
31  PPH_SERVICE_ITEM ServiceItem;
32  BOOLEAN Ready;
33  BOOLEAN Dirty;
34 
35  BOOLEAN OldDelayedStart;
37 
38 INT_PTR CALLBACK PhpServiceGeneralDlgProc(
39  _In_ HWND hwndDlg,
40  _In_ UINT uMsg,
41  _In_ WPARAM wParam,
42  _In_ LPARAM lParam
43  );
44 
45 static NTSTATUS PhpOpenService(
46  _Out_ PHANDLE Handle,
47  _In_ ACCESS_MASK DesiredAccess,
48  _In_opt_ PVOID Context
49  )
50 {
51  SC_HANDLE serviceHandle;
52 
53  if (!(serviceHandle = PhOpenService(
54  ((PPH_SERVICE_ITEM)Context)->Name->Buffer,
55  DesiredAccess
56  )))
58 
59  *Handle = serviceHandle;
60 
61  return STATUS_SUCCESS;
62 }
63 
64 static _Callback_ NTSTATUS PhpSetServiceSecurity(
65  _In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
66  _In_ SECURITY_INFORMATION SecurityInformation,
67  _In_opt_ PVOID Context
68  )
69 {
70  NTSTATUS status;
71  PPH_STD_OBJECT_SECURITY stdObjectSecurity;
72 
73  stdObjectSecurity = (PPH_STD_OBJECT_SECURITY)Context;
74 
75  status = PhStdSetObjectSecurity(SecurityDescriptor, SecurityInformation, Context);
76 
77  if ((status == STATUS_ACCESS_DENIED || status == NTSTATUS_FROM_WIN32(ERROR_ACCESS_DENIED)) && !PhElevated)
78  {
79  // Elevate using phsvc.
80  if (PhUiConnectToPhSvc(NULL, FALSE))
81  {
83  ((PPH_SERVICE_ITEM)stdObjectSecurity->Context)->Name->Buffer,
84  SecurityInformation,
85  SecurityDescriptor
86  );
88  }
89  }
90 
91  return status;
92 }
93 
95  _In_ HWND ParentWindowHandle,
96  _In_ PPH_SERVICE_ITEM ServiceItem
97  )
98 {
99  PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) };
100  PROPSHEETPAGE propSheetPage;
101  HPROPSHEETPAGE pages[32];
103  PH_STD_OBJECT_SECURITY stdObjectSecurity;
104  PPH_ACCESS_ENTRY accessEntries;
105  ULONG numberOfAccessEntries;
106 
107  propSheetHeader.dwFlags =
108  PSH_NOAPPLYNOW |
109  PSH_NOCONTEXTHELP |
110  PSH_PROPTITLE;
111  propSheetHeader.hwndParent = ParentWindowHandle;
112  propSheetHeader.pszCaption = ServiceItem->Name->Buffer;
113  propSheetHeader.nPages = 0;
114  propSheetHeader.nStartPage = 0;
115  propSheetHeader.phpage = pages;
116 
117  // General
118 
119  memset(&context, 0, sizeof(SERVICE_PROPERTIES_CONTEXT));
120  context.ServiceItem = ServiceItem;
121  context.Ready = FALSE;
122  context.Dirty = FALSE;
123 
124  memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE));
125  propSheetPage.dwSize = sizeof(PROPSHEETPAGE);
126  propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVGENERAL);
127  propSheetPage.pfnDlgProc = PhpServiceGeneralDlgProc;
128  propSheetPage.lParam = (LPARAM)&context;
129  pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage);
130 
131  // Security
132 
133  stdObjectSecurity.OpenObject = PhpOpenService;
134  stdObjectSecurity.ObjectType = L"Service";
135  stdObjectSecurity.Context = ServiceItem;
136 
137  if (PhGetAccessEntries(L"Service", &accessEntries, &numberOfAccessEntries))
138  {
139  pages[propSheetHeader.nPages++] = PhCreateSecurityPage(
140  ServiceItem->Name->Buffer,
142  PhpSetServiceSecurity,
143  &stdObjectSecurity,
144  accessEntries,
145  numberOfAccessEntries
146  );
147  PhFree(accessEntries);
148  }
149 
150  if (PhPluginsEnabled)
151  {
152  PH_PLUGIN_OBJECT_PROPERTIES objectProperties;
153 
154  objectProperties.Parameter = ServiceItem;
155  objectProperties.NumberOfPages = propSheetHeader.nPages;
156  objectProperties.MaximumNumberOfPages = sizeof(pages) / sizeof(HPROPSHEETPAGE);
157  objectProperties.Pages = pages;
158 
160 
161  propSheetHeader.nPages = objectProperties.NumberOfPages;
162  }
163 
164  PropertySheet(&propSheetHeader);
165 }
166 
167 static VOID PhpRefreshControls(
168  _In_ HWND hwndDlg
169  )
170 {
171  if (
173  PhEqualString2(PhaGetDlgItemText(hwndDlg, IDC_STARTTYPE), L"Auto Start", FALSE)
174  )
175  {
176  EnableWindow(GetDlgItem(hwndDlg, IDC_DELAYEDSTART), TRUE);
177  }
178  else
179  {
180  EnableWindow(GetDlgItem(hwndDlg, IDC_DELAYEDSTART), FALSE);
181  }
182 }
183 
184 INT_PTR CALLBACK PhpServiceGeneralDlgProc(
185  _In_ HWND hwndDlg,
186  _In_ UINT uMsg,
187  _In_ WPARAM wParam,
188  _In_ LPARAM lParam
189  )
190 {
191  switch (uMsg)
192  {
193  case WM_INITDIALOG:
194  {
195  LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam;
196  PSERVICE_PROPERTIES_CONTEXT context = (PSERVICE_PROPERTIES_CONTEXT)propSheetPage->lParam;
197  PPH_SERVICE_ITEM serviceItem = context->ServiceItem;
198  SC_HANDLE serviceHandle;
199  ULONG startType;
200  ULONG errorControl;
201  PPH_STRING serviceDll;
202 
203  // HACK
204  PhCenterWindow(GetParent(hwndDlg), GetParent(GetParent(hwndDlg)));
205 
206  SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context);
207 
208  PhAddComboBoxStrings(GetDlgItem(hwndDlg, IDC_TYPE), PhServiceTypeStrings,
209  sizeof(PhServiceTypeStrings) / sizeof(WCHAR *));
211  sizeof(PhServiceStartTypeStrings) / sizeof(WCHAR *));
213  sizeof(PhServiceErrorControlStrings) / sizeof(WCHAR *));
214 
215  SetDlgItemText(hwndDlg, IDC_DESCRIPTION, serviceItem->DisplayName->Buffer);
216  PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_TYPE),
217  PhGetServiceTypeString(serviceItem->Type), FALSE);
218 
219  startType = serviceItem->StartType;
220  errorControl = serviceItem->ErrorControl;
221  serviceHandle = PhOpenService(serviceItem->Name->Buffer, SERVICE_QUERY_CONFIG);
222 
223  if (serviceHandle)
224  {
225  LPQUERY_SERVICE_CONFIG config;
226  PPH_STRING description;
227  BOOLEAN delayedStart;
228 
229  if (config = PhGetServiceConfig(serviceHandle))
230  {
231  SetDlgItemText(hwndDlg, IDC_GROUP, config->lpLoadOrderGroup);
232  SetDlgItemText(hwndDlg, IDC_BINARYPATH, config->lpBinaryPathName);
233  SetDlgItemText(hwndDlg, IDC_USERACCOUNT, config->lpServiceStartName);
234 
235  if (startType != config->dwStartType || errorControl != config->dwErrorControl)
236  {
237  startType = config->dwStartType;
238  errorControl = config->dwErrorControl;
240  }
241 
242  PhFree(config);
243  }
244 
245  if (description = PhGetServiceDescription(serviceHandle))
246  {
247  SetDlgItemText(hwndDlg, IDC_DESCRIPTION, description->Buffer);
248  PhDereferenceObject(description);
249  }
250 
251  if (
253  PhGetServiceDelayedAutoStart(serviceHandle, &delayedStart)
254  )
255  {
256  context->OldDelayedStart = delayedStart;
257 
258  if (delayedStart)
259  Button_SetCheck(GetDlgItem(hwndDlg, IDC_DELAYEDSTART), BST_CHECKED);
260  }
261 
262  CloseServiceHandle(serviceHandle);
263  }
264 
265  PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_STARTTYPE),
266  PhGetServiceStartTypeString(startType), FALSE);
267  PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_ERRORCONTROL),
268  PhGetServiceErrorControlString(errorControl), FALSE);
269 
270  SetDlgItemText(hwndDlg, IDC_PASSWORD, L"password");
271  Button_SetCheck(GetDlgItem(hwndDlg, IDC_PASSWORDCHECK), BST_UNCHECKED);
272 
273  if (NT_SUCCESS(PhGetServiceDllParameter(&serviceItem->Name->sr, &serviceDll)))
274  {
275  SetDlgItemText(hwndDlg, IDC_SERVICEDLL, serviceDll->Buffer);
276  PhDereferenceObject(serviceDll);
277  }
278  else
279  {
280  SetDlgItemText(hwndDlg, IDC_SERVICEDLL, L"N/A");
281  }
282 
283  PhpRefreshControls(hwndDlg);
284 
285  context->Ready = TRUE;
286  }
287  break;
288  case WM_DESTROY:
289  {
290  RemoveProp(hwndDlg, PhMakeContextAtom());
291  }
292  break;
293  case WM_COMMAND:
294  {
296  (PSERVICE_PROPERTIES_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
297 
298  switch (LOWORD(wParam))
299  {
300  case IDCANCEL:
301  {
302  // Workaround for property sheet + multiline edit: http://support.microsoft.com/kb/130765
303 
304  SendMessage(GetParent(hwndDlg), uMsg, wParam, lParam);
305  }
306  break;
307  case IDC_PASSWORD:
308  {
309  if (HIWORD(wParam) == EN_CHANGE)
310  {
311  Button_SetCheck(GetDlgItem(hwndDlg, IDC_PASSWORDCHECK), BST_CHECKED);
312  }
313  }
314  break;
315  case IDC_DELAYEDSTART:
316  {
317  context->Dirty = TRUE;
318  }
319  break;
320  case IDC_BROWSE:
321  {
322  static PH_FILETYPE_FILTER filters[] =
323  {
324  { L"Executable files (*.exe;*.sys)", L"*.exe;*.sys" },
325  { L"All files (*.*)", L"*.*" }
326  };
327  PVOID fileDialog;
328  PPH_STRING commandLine;
329  PPH_STRING fileName;
330 
331  fileDialog = PhCreateOpenFileDialog();
332  PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER));
333 
334  commandLine = PhaGetDlgItemText(hwndDlg, IDC_BINARYPATH);
335 
336  if (context->ServiceItem->Type & SERVICE_WIN32)
337  {
338  PH_STRINGREF dummyFileName;
339  PH_STRINGREF dummyArguments;
340 
341  if (!PhParseCommandLineFuzzy(&commandLine->sr, &dummyFileName, &dummyArguments, &fileName))
342  fileName = NULL;
343 
344  if (!fileName)
345  PhSwapReference(&fileName, commandLine);
346  }
347  else
348  {
349  fileName = PhGetFileName(commandLine);
350  }
351 
352  PhSetFileDialogFileName(fileDialog, fileName->Buffer);
353  PhDereferenceObject(fileName);
354 
355  if (PhShowFileDialog(hwndDlg, fileDialog))
356  {
357  fileName = PhGetFileDialogFileName(fileDialog);
358  SetDlgItemText(hwndDlg, IDC_BINARYPATH, fileName->Buffer);
359  PhDereferenceObject(fileName);
360  }
361 
362  PhFreeFileDialog(fileDialog);
363  }
364  break;
365  }
366 
367  switch (HIWORD(wParam))
368  {
369  case EN_CHANGE:
370  case CBN_SELCHANGE:
371  {
372  PhpRefreshControls(hwndDlg);
373 
374  if (context->Ready)
375  context->Dirty = TRUE;
376  }
377  break;
378  }
379  }
380  break;
381  case WM_NOTIFY:
382  {
383  LPNMHDR header = (LPNMHDR)lParam;
384 
385  switch (header->code)
386  {
387  case PSN_QUERYINITIALFOCUS:
388  {
389  SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)GetDlgItem(hwndDlg, IDC_STARTTYPE));
390  }
391  return TRUE;
392  case PSN_KILLACTIVE:
393  {
394  SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, FALSE);
395  }
396  return TRUE;
397  case PSN_APPLY:
398  {
399  NTSTATUS status;
401  (PSERVICE_PROPERTIES_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
402  PPH_SERVICE_ITEM serviceItem = context->ServiceItem;
403  SC_HANDLE serviceHandle;
404  PPH_STRING newServiceTypeString;
405  PPH_STRING newServiceStartTypeString;
406  PPH_STRING newServiceErrorControlString;
407  ULONG newServiceType;
408  ULONG newServiceStartType;
409  ULONG newServiceErrorControl;
410  PPH_STRING newServiceGroup;
411  PPH_STRING newServiceBinaryPath;
412  PPH_STRING newServiceUserAccount;
413  PPH_STRING newServicePassword;
414 
415  SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
416 
417  if (!context->Dirty)
418  {
419  return TRUE;
420  }
421 
422  newServiceTypeString = PhAutoDereferenceObject(PhGetWindowText(GetDlgItem(hwndDlg, IDC_TYPE)));
423  newServiceStartTypeString = PhAutoDereferenceObject(PhGetWindowText(GetDlgItem(hwndDlg, IDC_STARTTYPE)));
424  newServiceErrorControlString = PhAutoDereferenceObject(PhGetWindowText(GetDlgItem(hwndDlg, IDC_ERRORCONTROL)));
425  newServiceType = PhGetServiceTypeInteger(newServiceTypeString->Buffer);
426  newServiceStartType = PhGetServiceStartTypeInteger(newServiceStartTypeString->Buffer);
427  newServiceErrorControl = PhGetServiceErrorControlInteger(newServiceErrorControlString->Buffer);
428 
429  newServiceGroup = PhAutoDereferenceObject(PhGetWindowText(GetDlgItem(hwndDlg, IDC_GROUP)));
430  newServiceBinaryPath = PhAutoDereferenceObject(PhGetWindowText(GetDlgItem(hwndDlg, IDC_BINARYPATH)));
431  newServiceUserAccount = PhAutoDereferenceObject(PhGetWindowText(GetDlgItem(hwndDlg, IDC_USERACCOUNT)));
432 
433  if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_PASSWORDCHECK)) == BST_CHECKED)
434  {
435  newServicePassword = PhGetWindowText(GetDlgItem(hwndDlg, IDC_PASSWORD));
436  }
437  else
438  {
439  newServicePassword = NULL;
440  }
441 
442  if (newServiceType == SERVICE_KERNEL_DRIVER && newServiceUserAccount->Length == 0)
443  {
444  newServiceUserAccount = NULL;
445  }
446 
447  serviceHandle = PhOpenService(serviceItem->Name->Buffer, SERVICE_CHANGE_CONFIG);
448 
449  if (serviceHandle)
450  {
451  if (ChangeServiceConfig(
452  serviceHandle,
453  newServiceType,
454  newServiceStartType,
455  newServiceErrorControl,
456  newServiceBinaryPath->Buffer,
457  newServiceGroup->Buffer,
458  NULL,
459  NULL,
460  PhGetString(newServiceUserAccount),
461  PhGetString(newServicePassword),
462  NULL
463  ))
464  {
466  {
467  BOOLEAN newDelayedStart;
468 
469  newDelayedStart = Button_GetCheck(GetDlgItem(hwndDlg, IDC_DELAYEDSTART)) == BST_CHECKED;
470 
471  if (newDelayedStart != context->OldDelayedStart)
472  {
473  PhSetServiceDelayedAutoStart(serviceHandle, newDelayedStart);
474  }
475  }
476 
478 
479  CloseServiceHandle(serviceHandle);
480  }
481  else
482  {
483  CloseServiceHandle(serviceHandle);
484  goto ErrorCase;
485  }
486  }
487  else
488  {
489  if (GetLastError() == ERROR_ACCESS_DENIED && !PhElevated)
490  {
491  // Elevate using phsvc.
492  if (PhUiConnectToPhSvc(hwndDlg, FALSE))
493  {
495  serviceItem->Name->Buffer,
496  newServiceType,
497  newServiceStartType,
498  newServiceErrorControl,
499  newServiceBinaryPath->Buffer,
500  newServiceGroup->Buffer,
501  NULL,
502  NULL,
503  PhGetString(newServiceUserAccount),
504  PhGetString(newServicePassword),
505  NULL
506  )))
507  {
509  {
510  BOOLEAN newDelayedStart;
511 
512  newDelayedStart = Button_GetCheck(GetDlgItem(hwndDlg, IDC_DELAYEDSTART)) == BST_CHECKED;
513 
514  if (newDelayedStart != context->OldDelayedStart)
515  {
516  SERVICE_DELAYED_AUTO_START_INFO info;
517 
518  info.fDelayedAutostart = newDelayedStart;
520  serviceItem->Name->Buffer,
521  SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
522  &info
523  );
524  }
525  }
526 
528  }
529 
531 
532  if (!NT_SUCCESS(status))
533  {
534  SetLastError(PhNtStatusToDosError(status));
535  goto ErrorCase;
536  }
537  }
538  else
539  {
540  // User cancelled elevation.
541  SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID);
542  }
543  }
544  else
545  {
546  goto ErrorCase;
547  }
548  }
549 
550  goto Cleanup;
551 ErrorCase:
552  if (PhShowMessage(
553  hwndDlg,
554  MB_ICONERROR | MB_RETRYCANCEL,
555  L"Unable to change service configuration: %s",
556  ((PPH_STRING)PhAutoDereferenceObject(PhGetWin32Message(GetLastError())))->Buffer
557  ) == IDRETRY)
558  {
559  SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID);
560  }
561 
562 Cleanup:
563  if (newServicePassword)
564  {
565  RtlSecureZeroMemory(newServicePassword->Buffer, newServicePassword->Length);
566  PhDereferenceObject(newServicePassword);
567  }
568  }
569  return TRUE;
570  }
571  }
572  break;
573  }
574 
575  return FALSE;
576 }