Browse Source

Support virtual service accounts.

Specify the account name as "NT Service\<servicename>" or use the
virtual account radio button in the GUI to run the service as a virtual
service account.

Thanks Christian Long.
Iain Patterson 5 years ago
parent
commit
71764fe41e
8 changed files with 92 additions and 31 deletions
  1. 2 0
      README.txt
  2. 24 4
      account.cpp
  3. 3 0
      account.h
  4. 36 17
      gui.cpp
  5. BIN
      nssm.rc
  6. 2 1
      resource.h
  7. 22 8
      service.cpp
  8. 3 1
      settings.cpp

+ 2 - 0
README.txt

@@ -790,6 +790,7 @@ parameter can be omitted when using them:
   "LocalSystem" aka "System" aka "NT Authority\System"
   "LocalService" aka "Local Service" aka "NT Authority\Local Service"
   "NetworkService" aka "Network Service" aka "NT Authority\Network Service"
+  Virtual service account "NT Service\<servicename>"
 
 
 The Start parameter is used to query or set the startup type of the service.
@@ -1003,6 +1004,7 @@ Thanks to Mathias Breiner for help with Visual Studio and some registry fixes.
 Thanks to David Bremner for general tidyups.
 Thanks to Nabil Redmann for suggesting redirecting hooks' output.
 Thanks to Bader Aldurai for suggesting the process tree.
+Thanks to Christian Long for suggesting virtual accounts.
 
 Licence
 -------

+ 24 - 4
account.cpp

@@ -89,10 +89,12 @@ int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
   }
 
   if (translated_sid->Use != SidTypeUser && translated_sid->Use != SidTypeWellKnownGroup) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_sid);
-    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
-    return 6;
+    if (translated_sid->Use != SidTypeUnknown || _tcsnicmp(NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN _T("\\"), username, _tcslen(NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN) + 1)) {
+      LsaFreeMemory(translated_domains);
+      LsaFreeMemory(translated_sid);
+      print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
+      return 6;
+    }
   }
 
   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
@@ -232,6 +234,24 @@ int is_localsystem(const TCHAR *username) {
   return ret;
 }
 
+/* Does the username represent a virtual account for the service? */
+int is_virtual_account(const TCHAR *service_name, const TCHAR *username) {
+  if (! imports.IsWellKnownSid) return 0;
+  if (! service_name) return 0;
+  if (! username) return 0;
+
+  size_t len = _tcslen(NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN) + _tcslen(service_name) + 2;
+  TCHAR *canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
+  if (! canon) {
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("is_virtual_account"));
+    return 0;
+  }
+  _sntprintf_s(canon, len, _TRUNCATE, _T("%s\\%s"), NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN, service_name);
+  int ret = str_equiv(canon, username);
+  HeapFree(GetProcessHeap(), 0, canon);
+  return ret;
+}
+
 /*
   Get well-known alias for LocalSystem and friends.
   Returns a pointer to a static string.  DO NOT try to free it.

+ 3 - 0
account.h

@@ -8,6 +8,8 @@
 /* Other well-known accounts which can start a service without a password. */
 #define NSSM_LOCALSERVICE_ACCOUNT _T("NT Authority\\LocalService")
 #define NSSM_NETWORKSERVICE_ACCOUNT _T("NT Authority\\NetworkService")
+/* Virtual service accounts. */
+#define NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN _T("NT Service")
 /* This is explicitly a wide string. */
 #define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"
 
@@ -17,6 +19,7 @@ int username_sid(const TCHAR *, SID **);
 int username_equiv(const TCHAR *, const TCHAR *);
 int canonicalise_username(const TCHAR *, TCHAR **);
 int is_localsystem(const TCHAR *);
+int is_virtual_account(const TCHAR *, const TCHAR *);
 const TCHAR *well_known_sid(SID *);
 const TCHAR *well_known_username(const TCHAR *);
 int grant_logon_as_service(const TCHAR *);

+ 36 - 17
gui.cpp

@@ -26,6 +26,13 @@ static HWND dialog(const TCHAR *templ, HWND parent, DLGPROC function) {
   return dialog(templ, parent, function, 0);
 }
 
+static inline void set_logon_enabled(unsigned char interact_enabled, unsigned char credentials_enabled) {
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_INTERACT), interact_enabled);
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_USERNAME), credentials_enabled);
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD1), credentials_enabled);
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2), credentials_enabled);
+}
+
 int nssm_gui(int resource, nssm_service_t *service) {
   /* Create window */
   HWND dlg = dialog(MAKEINTRESOURCE(resource), 0, nssm_dlg, (LPARAM) service);
@@ -86,15 +93,18 @@ int nssm_gui(int resource, nssm_service_t *service) {
 
     /* Log on tab. */
     if (service->username) {
-      CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_ACCOUNT, IDC_ACCOUNT);
-      SetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_USERNAME, service->username);
-      EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_INTERACT), 0);
-      EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_USERNAME), 1);
-      EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD1), 1);
-      EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2), 1);
+      if (is_virtual_account(service->name, service->username)) {
+        CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_VIRTUAL_SERVICE, IDC_VIRTUAL_SERVICE);
+        set_logon_enabled(0, 0);
+      }
+      else {
+        CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_VIRTUAL_SERVICE, IDC_ACCOUNT);
+        SetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_USERNAME, service->username);
+        set_logon_enabled(0, 1);
+      }
     }
     else {
-      CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_ACCOUNT, IDC_LOCALSYSTEM);
+      CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_VIRTUAL_SERVICE, IDC_LOCALSYSTEM);
       if (service->type & SERVICE_INTERACTIVE_PROCESS) SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_INTERACT, BM_SETCHECK, BST_CHECKED, 0);
     }
 
@@ -268,13 +278,6 @@ static inline void set_timeout_enabled(unsigned long control, unsigned long depe
   EnableWindow(GetDlgItem(tablist[NSSM_TAB_SHUTDOWN], dependent), enabled);
 }
 
-static inline void set_logon_enabled(unsigned char enabled) {
-  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_INTERACT), ! enabled);
-  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_USERNAME), enabled);
-  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD1), enabled);
-  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2), enabled);
-}
-
 static inline void set_affinity_enabled(unsigned char enabled) {
   EnableWindow(GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY), enabled);
 }
@@ -460,6 +463,18 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
     service->password = 0;
     service->passwordlen = 0;
   }
+  else if (SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_VIRTUAL_SERVICE, BM_GETCHECK, 0, 0) & BST_CHECKED) {
+    if (service->username) HeapFree(GetProcessHeap(), 0, service->username);
+    service->usernamelen = _tcslen(NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN) + _tcslen(service->name) + 2;
+    service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->usernamelen * sizeof(TCHAR));
+    if (! service->username) {
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("account name"), _T("install()"));
+      return 6;
+    }
+    _sntprintf_s(service->username, service->usernamelen, _TRUNCATE, _T("%s\\%s"), NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN, service->name);
+    service->password = 0;
+    service->passwordlen = 0;
+  }
   else {
     /* Username. */
     service->usernamelen = SendMessage(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_USERNAME), WM_GETTEXTLENGTH, 0, 0);
@@ -976,11 +991,15 @@ INT_PTR CALLBACK tab_dlg(HWND tab, UINT message, WPARAM w, LPARAM l) {
 
         /* Log on. */
         case IDC_LOCALSYSTEM:
-          set_logon_enabled(0);
+          set_logon_enabled(1, 0);
+          break;
+
+        case IDC_VIRTUAL_SERVICE:
+          set_logon_enabled(0, 0);
           break;
 
         case IDC_ACCOUNT:
-          set_logon_enabled(1);
+          set_logon_enabled(0, 1);
           break;
 
         /* Affinity. */
@@ -1127,7 +1146,7 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
 
       /* Set defaults. */
       CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_ACCOUNT, IDC_LOCALSYSTEM);
-      set_logon_enabled(0);
+      set_logon_enabled(1, 0);
 
       /* Dependencies tab. */
       tab.pszText = message_string(NSSM_GUI_TAB_DEPENDENCIES);

BIN
nssm.rc


+ 2 - 1
resource.h

@@ -71,6 +71,7 @@
 #define IDC_HOOK                        1050
 #define IDC_BROWSE_HOOK                 1051
 #define IDC_REDIRECT_HOOK               1052
+#define IDC_VIRTUAL_SERVICE             1053
 
 // Next default values for new objects
 // 
@@ -78,7 +79,7 @@
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_NEXT_RESOURCE_VALUE        117
 #define _APS_NEXT_COMMAND_VALUE         40001
-#define _APS_NEXT_CONTROL_VALUE         1053
+#define _APS_NEXT_CONTROL_VALUE         1054
 #define _APS_NEXT_SYMED_VALUE           101
 #endif
 #endif

+ 22 - 8
service.cpp

@@ -1274,19 +1274,33 @@ int edit_service(nssm_service_t *service, bool editing) {
   TCHAR *username = 0;
   TCHAR *canon = 0;
   TCHAR *password = 0;
+  boolean virtual_account = false;
   if (service->usernamelen) {
     username = service->username;
-    if (canonicalise_username(username, &canon)) return 5;
-    if (service->passwordlen) password = service->password;
+    if (is_virtual_account(service->name, username)) {
+      virtual_account = true;
+      canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (service->usernamelen + 1) * sizeof(TCHAR));
+      if (! canon) {
+        print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("edit_service()"));
+        return 5;
+      }
+      memmove(canon, username, (service->usernamelen + 1) * sizeof(TCHAR));
+    }
+    else {
+      if (canonicalise_username(username, &canon)) return 5;
+      if (service->passwordlen) password = service->password;
+    }
   }
   else if (editing) username = canon = NSSM_LOCALSYSTEM_ACCOUNT;
 
-  if (well_known_username(canon)) password = _T("");
-  else {
-    if (grant_logon_as_service(canon)) {
-      if (canon != username) HeapFree(GetProcessHeap(), 0, canon);
-      print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
-      return 5;
+  if (! virtual_account) {
+    if (well_known_username(canon)) password = _T("");
+    else {
+      if (grant_logon_as_service(canon)) {
+        if (canon != username) HeapFree(GetProcessHeap(), 0, canon);
+        print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
+        return 5;
+      }
     }
   }
 

+ 3 - 1
settings.cpp

@@ -1074,6 +1074,7 @@ int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *n
     That means the username is actually passed in the additional parameter.
   */
   bool localsystem = false;
+  bool virtual_account = false;
   TCHAR *username = NSSM_LOCALSYSTEM_ACCOUNT;
   TCHAR *password = 0;
   if (additional) {
@@ -1089,6 +1090,7 @@ int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *n
     username = (TCHAR *) well_known;
     password = _T("");
   }
+  else if (is_virtual_account(service_name, username)) virtual_account = true;
   else if (! password) {
     /* We need a password if the account requires it. */
     print_message(stderr, NSSM_MESSAGE_MISSING_PASSWORD, name);
@@ -1112,7 +1114,7 @@ int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *n
     HeapFree(GetProcessHeap(), 0, qsc);
   }
 
-  if (! well_known) {
+  if (! well_known && ! virtual_account) {
     if (grant_logon_as_service(username)) {
       if (passwordsize) SecureZeroMemory(password, passwordsize);
       print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);