Use well-known account aliases.
authorIain Patterson <me@iain.cx>
Thu, 30 Jan 2014 20:49:43 +0000 (20:49 +0000)
committerIain Patterson <me@iain.cx>
Thu, 30 Jan 2014 21:01:18 +0000 (21:01 +0000)
Jump through a few hoops to make the Windows service control panel to
localise service ObjectNames if they are set to LocalSystem,
LocalService or NetworkService.

Those aren't the canonical names of the accounts as returned by
LsaLookupSids() so attempting to call ChangeServiceConfig() with a
username of, eg, NetworkService will fail, even though writing
NetworkService directly to the registry would work.  Calling
ChangeServiceConfig() with a username of "NT Authority\Network Service"
will work, since that's the canonical name returned by LsaLookupSids()
but it won't be localised in the control panel.

The solution is to write "NT Authority\NetworkService" to the registry
and it will appear correctly.

At this point I'm not sure whether to label it a bug or a feature that
"nssm get <service> ObjectName" will return the actual value in the
registry and not the localised name.

Thanks Marco Certelli.

README.txt
account.cpp
account.h
gui.cpp
service.cpp
settings.cpp

index df918bd..60ea6a1 100644 (file)
@@ -520,8 +520,8 @@ The following well-known usernames do not need a password.  The password
 parameter can be omitted when using them:\r
 \r
   "LocalSystem" aka "System" aka "NT Authority\System"\r
-  "Local Service" aka "NT Authority\Local Service"\r
-  "Network Service" aka "NT Authority\Network Service"\r
+  "LocalService" aka "Local Service" aka "NT Authority\Local Service"\r
+  "NetworkService" aka "Network Service" aka "NT Authority\Network Service"\r
 \r
 \r
 The Start parameter is used to query or set the startup type of the service.\r
index 9c2ff70..5e6523f 100644 (file)
@@ -103,7 +103,7 @@ int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
   }
 
   int ret = 0;
-  if (translated_sid->Use == SidTypeWellKnownGroup && requires_password(*sid)) {
+  if (translated_sid->Use == SidTypeWellKnownGroup && ! well_known_sid(*sid)) {
     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
     ret = 8;
   }
@@ -154,175 +154,31 @@ int is_localsystem(const TCHAR *username) {
 }
 
 /*
-  Find the canonical name for a well-known account name.
-  MUST ONLY BE USED for well-known account names.
-  Must call LocalFree() on result.
+  Get well-known alias for LocalSystem and friends.
+  Returns a pointer to a static string.  DO NOT try to free it.
 */
-TCHAR *canonical_username(const TCHAR *username) {
-  SID *user_sid;
-  TCHAR *canon;
-  size_t len;
-
-  if (is_localsystem(username)) {
-    len = (_tcslen(NSSM_LOCALSYSTEM_ACCOUNT) + 1) * sizeof(TCHAR);
-    canon = (TCHAR *) LocalAlloc(LPTR, len);
-    if (! canon) {
-      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, NSSM_LOCALSYSTEM_ACCOUNT, _T("canonical_username"));
-      return 0;
-    }
-    memmove(canon, NSSM_LOCALSYSTEM_ACCOUNT, len);
-    _tprintf(_T("it's localsystem = %s!\n"), canon);
-    return canon;
-  }
-
-  if (! imports.CreateWellKnownSid) return 0;
-
-  if (username_sid(username, &user_sid)) return 0;
-
-  /*
-    LsaLookupSids will return the canonical username but the result won't
-    include the NT Authority part.  Thus we must look that up as well.
-  */
-  unsigned long ntsidsize = SECURITY_MAX_SID_SIZE;
-  SID *ntauth_sid = (SID *) HeapAlloc(GetProcessHeap(), 0, ntsidsize);
-  if (! ntauth_sid) {
-    HeapFree(GetProcessHeap(), 0, user_sid);
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("NT Authority"), _T("canonical_username"));
-    return 0;
-  }
-
-  if (! imports.CreateWellKnownSid(WinNtAuthoritySid, NULL, ntauth_sid, &ntsidsize)) {
-    HeapFree(GetProcessHeap(), 0, ntauth_sid);
-    print_message(stderr, NSSM_MESSAGE_CREATEWELLKNOWNSID_FAILED, _T("WinNtAuthoritySid"));
-    return 0;
-  }
-
-  LSA_HANDLE policy;
-  if (open_lsa_policy(&policy)) return 0;
-
-  LSA_REFERENCED_DOMAIN_LIST *translated_domains;
-  LSA_TRANSLATED_NAME *translated_names;
-
-  unsigned long n = 2;
-  PSID *sids = (PSID *) HeapAlloc(GetProcessHeap(), 0, n * sizeof(PSID));
-  sids[0] = user_sid;
-  sids[1] = ntauth_sid;
-
-  NTSTATUS status = LsaLookupSids(policy, n, (PSID *) sids, &translated_domains, &translated_names);
-  HeapFree(GetProcessHeap(), 0, user_sid);
-  HeapFree(GetProcessHeap(), 0, ntauth_sid);
-  HeapFree(GetProcessHeap(), 0, sids);
-  LsaClose(policy);
-  if (status) {
-    print_message(stderr, NSSM_MESSAGE_LSALOOKUPSIDS_FAILED);
-    return 0;
-  }
-
-  /* Look up the account name. */
-  LSA_TRANSLATED_NAME *translated_name = &(translated_names[0]);
-  if (translated_name->Use != SidTypeWellKnownGroup) {
-    print_message(stderr, NSSM_GUI_INVALID_USERNAME);
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_names);
-    return 0;
-  }
-
-  LSA_UNICODE_STRING *lsa_group = &translated_name->Name;
-
-  /* Look up NT Authority. */
-  translated_name = &(translated_names[1]);
-  if (translated_name->Use != SidTypeDomain) {
-    print_message(stderr, NSSM_GUI_INVALID_USERNAME);
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_names);
-    return 0;
-  }
-
-  /* In theory these pointers should all be valid if we got this far... */
-  LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_name->DomainIndex];
-  LSA_UNICODE_STRING *lsa_domain = &trust->Name;
-
-  TCHAR *domain, *group;
-  unsigned long lsa_domain_len = lsa_domain->Length;
-  unsigned long lsa_group_len = lsa_group->Length;
-  len = lsa_domain_len + lsa_group_len + 2;
-
-#ifdef UNICODE
-  domain = lsa_domain->Buffer;
-  group = lsa_group->Buffer;
-#else
-  size_t buflen;
-
-  wcstombs_s(&buflen, NULL, 0, lsa_domain->Buffer, _TRUNCATE);
-  domain = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
-  if (! domain) {
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("domain"), _T("canonical_username"));
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_names);
-    return 0;
-  }
-  wcstombs_s(&buflen, (char *) domain, buflen, lsa_domain->Buffer, _TRUNCATE);
-
-  wcstombs_s(&buflen, NULL, 0, lsa_group->Buffer, _TRUNCATE);
-  group = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
-  if (! group) {
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("group"), _T("canonical_username"));
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_names);
-    return 0;
-  }
-  wcstombs_s(&buflen, (char *) group, buflen, lsa_group->Buffer, _TRUNCATE);
-#endif
-
-  canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
-  if (! canon) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_names);
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("canonical_username"));
-    return 0;
-  }
-
-  _sntprintf_s(canon, len, _TRUNCATE, _T("%s\\%s"), domain, group);
-
-#ifndef UNICODE
-  HeapFree(GetProcessHeap(), 0, domain);
-  HeapFree(GetProcessHeap(), 0, group);
-#endif
-
-  LsaFreeMemory(translated_domains);
-  LsaFreeMemory(translated_names);
-
-  return canon;
-}
-
-/* Does the SID type require a password? */
-int requires_password(SID *sid) {
-  if (! imports.IsWellKnownSid) return -1;
-  if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return 0;
-  if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return 0;
-  if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return 0;;
-  return 1;
+const TCHAR *well_known_sid(SID *sid) {
+  if (! imports.IsWellKnownSid) return 0;
+  if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return NSSM_LOCALSYSTEM_ACCOUNT;
+  if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return NSSM_LOCALSERVICE_ACCOUNT;
+  if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return NSSM_NETWORKSERVICE_ACCOUNT;
+  return 0;
 }
 
-/* Does the username require a password? */
-int requires_password(const TCHAR *username) {
-  if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;
-
+const TCHAR *well_known_username(const TCHAR *username) {
+  if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return NSSM_LOCALSYSTEM_ACCOUNT;
   SID *sid;
   int r = username_sid(username, &sid);
   if (username_sid(username, &sid)) return 0;
 
-  int ret = 0;
-  ret = requires_password(sid);
-
+  const TCHAR *well_known = well_known_sid(sid);
   FreeSid(sid);
 
-  return ret;
+  return well_known;
 }
 
 int grant_logon_as_service(const TCHAR *username) {
   if (! username) return 0;
-  if (! requires_password(username)) return 0;
 
   /* Open Policy object. */
   LSA_OBJECT_ATTRIBUTES attributes;
@@ -340,6 +196,14 @@ int grant_logon_as_service(const TCHAR *username) {
     return 2;
   }
 
+  /*
+    Shouldn't happen because it should have been checked before callling this function.
+  */
+  if (well_known_sid(sid)) {
+    LsaClose(policy);
+    return 3;
+  }
+
   /* Check if the SID has the "Log on as a service" right. */
   LSA_UNICODE_STRING lsa_right;
   lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;
index f16a899..1719e6d 100644 (file)
--- a/account.h
+++ b/account.h
@@ -5,18 +5,19 @@
 
 /* Not really an account.  The canonical name is NT Authority\System. */
 #define NSSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem")
+/* 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")
 /* This is explicitly a wide string. */
 #define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"
 
-
 int open_lsa_policy(LSA_HANDLE *);
 int username_sid(const TCHAR *, SID **, LSA_HANDLE *);
 int username_sid(const TCHAR *, SID **);
 int username_equiv(const TCHAR *, const TCHAR *);
 int is_localsystem(const TCHAR *);
-TCHAR *canonical_username(const TCHAR *);
-int requires_password(SID *);
-int requires_password(const TCHAR *);
+const TCHAR *well_known_sid(SID *);
+const TCHAR *well_known_username(const TCHAR *);
 int grant_logon_as_service(const TCHAR *);
 
 #endif
diff --git a/gui.cpp b/gui.cpp
index 9f6f9ba..4ab19d7 100644 (file)
--- a/gui.cpp
+++ b/gui.cpp
@@ -370,29 +370,21 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
       Special case for well-known accounts.\r
       Ignore the password if we're editing and the username hasn't changed.\r
     */\r
-    if (! requires_password(service->username)) {\r
-      if (is_localsystem(service->username)) {
+    const TCHAR *well_known = well_known_username(service->username);
+    if (well_known) {\r
+      if (str_equiv(well_known, NSSM_LOCALSYSTEM_ACCOUNT)) {
         HeapFree(GetProcessHeap(), 0, service->username);\r
         service->username = 0;\r
         service->usernamelen = 0;\r
       }
       else {
-        TCHAR *canon = canonical_username(service->username);
-        HeapFree(GetProcessHeap(), 0, service->username);\r
-        service->username = 0;\r
-        service->usernamelen = 0;\r
-        if (canon) {
-          service->usernamelen = _tcslen(canon) + 1;
-          service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->usernamelen * sizeof(TCHAR));
-          if (! service->username) {
-            LocalFree(canon);
-            print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("install()"));
-            return 6;
-          }
-          memmove(service->username, canon, service->usernamelen * sizeof(TCHAR));
-          LocalFree(canon);
+        service->usernamelen = _tcslen(well_known) + 1;
+        service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->usernamelen * sizeof(TCHAR));
+        if (! service->username) {
+          print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("install()"));
+          return 6;
         }
-        else return 6;
+        memmove(service->username, well_known, service->usernamelen * sizeof(TCHAR));
       }
     }\r
     else {\r
index d01ece0..ecd1419 100644 (file)
@@ -934,7 +934,8 @@ int edit_service(nssm_service_t *service, bool editing) {
   }\r
   else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;\r
 \r
-  if (requires_password(username)) {\r
+  if (well_known_username(username)) password = _T("");\r
+  else {\r
     if (grant_logon_as_service(username)) {\r
       print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
       return 5;\r
index 907524d..172eeba 100644 (file)
@@ -530,31 +530,25 @@ int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *n
   bool localsystem = false;
   TCHAR *username = NSSM_LOCALSYSTEM_ACCOUNT;
   TCHAR *password = 0;
-  TCHAR *canon = 0;
   if (additional) {
     username = (TCHAR *) additional;
     if (value && value->string) password = value->string;
   }
   else if (value && value->string) username = value->string;
 
-  if (requires_password(username)) {
-    if (! password) {
-      /* We need a password if the account requires it. */
-      print_message(stderr, NSSM_MESSAGE_MISSING_PASSWORD, name);
-      return -1;
-    }
+  const TCHAR *well_known = well_known_username(username);
+  size_t passwordsize = 0;
+  if (well_known) {
+    if (str_equiv(well_known, NSSM_LOCALSYSTEM_ACCOUNT)) localsystem = true;
+    username = (TCHAR *) well_known;
+    password = _T("");
   }
-  else {
-    password = 0;
-    if (is_localsystem(username)) {
-      localsystem = true;
-      username = NSSM_LOCALSYSTEM_ACCOUNT;
-    }
-    else {
-      canon = canonical_username(username);
-      if (canon) username = canon;
-    }
+  else if (! password) {
+    /* We need a password if the account requires it. */
+    print_message(stderr, NSSM_MESSAGE_MISSING_PASSWORD, name);
+    return -1;
   }
+  else passwordsize = _tcslen(password) * sizeof(TCHAR);
 
   /*
     ChangeServiceConfig() will fail to set the username if the service is set
@@ -564,8 +558,7 @@ int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *n
   if (! localsystem) {
     QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
     if (! qsc) {
-      if (password) SecureZeroMemory(password, _tcslen(password) * sizeof(TCHAR));
-      if (canon) LocalFree(canon);
+      if (passwordsize) SecureZeroMemory(password, passwordsize);
       return -1;
     }
 
@@ -573,24 +566,22 @@ int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *n
     HeapFree(GetProcessHeap(), 0, qsc);
   }
 
-  if (password) {
+  if (! well_known) {
     if (grant_logon_as_service(username)) {
-      if (password) SecureZeroMemory(password, _tcslen(password) * sizeof(TCHAR));
-      if (canon) LocalFree(canon);
+      if (passwordsize) SecureZeroMemory(password, passwordsize);
       print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
       return -1;
     }
   }
 
   if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, 0)) {
-    if (password) SecureZeroMemory(password, _tcslen(password) * sizeof(TCHAR));
-    if (canon) LocalFree(canon);
+    if (passwordsize) SecureZeroMemory(password, passwordsize);
     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
     return -1;
   }
-  if (password) SecureZeroMemory(password, _tcslen(password) * sizeof(TCHAR));
 
-  if (canon) LocalFree(canon);
+  if (passwordsize) SecureZeroMemory(password, passwordsize);
+
   if (localsystem) return 0;
 
   return 1;