Browse Source

Handle well-known account names.

Certain well-known accounts can be used to start a service without
entering a password.

"LocalSystem" is the default value of ObjectName in the registry.  It is
not a real account but is an alias for the "NT Authority\System"
account.

"NT Authority\Local Service" has minimal local and no network
privileges.

"NT Authority\Network Service" has minimal local and network privileges.

NSSM now recognises these well-known accounts and will accept them as
ObjectName parameters without complaining that no password was enter.
With the exception of LocalSystem, each can be specified without the "NT
Authority" pseudo-domain prefix.
Iain Patterson 8 years ago
parent
commit
cca8d28295
11 changed files with 493 additions and 180 deletions
  1. 7 0
      README.txt
  2. 388 0
      account.cpp
  3. 22 0
      account.h
  4. 25 5
      gui.cpp
  5. BIN
      messages.mc
  6. 3 3
      nssm.cpp
  7. 1 0
      nssm.h
  8. 8 0
      nssm.vcproj
  9. 6 154
      service.cpp
  10. 0 4
      service.h
  11. 33 14
      settings.cpp

+ 7 - 0
README.txt

@@ -516,6 +516,13 @@ invocation is valid and will have the expected effect.
 
     nssm set <servicename> ObjectName <username> correct horse battery staple
 
+The following well-known usernames do not need a password.  The password
+parameter can be omitted when using them:
+
+  "LocalSystem" aka "System" aka "NT Authority\System"
+  "Local Service" aka "NT Authority\Local Service"
+  "Network Service" aka "NT Authority\Network Service"
+
 
 The Start parameter is used to query or set the startup type of the service.
 Valid service startup types are as follows:

+ 388 - 0
account.cpp

@@ -0,0 +1,388 @@
+#include "nssm.h"
+
+#include <sddl.h>
+
+extern imports_t imports;
+
+/* Open Policy object. */
+int open_lsa_policy(LSA_HANDLE *policy) {
+  LSA_OBJECT_ATTRIBUTES attributes;
+  ZeroMemory(&attributes, sizeof(attributes));
+
+  NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, policy);
+  if (status) {
+    print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));
+    return 1;
+  }
+
+  return 0;
+}
+
+/* Look up SID for an account. */
+int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
+  LSA_HANDLE handle;
+  if (! policy) {
+    policy = &handle;
+    if (open_lsa_policy(policy)) return 1;
+  }
+
+  LSA_UNICODE_STRING lsa_username;
+#ifdef UNICODE
+  lsa_username.Buffer = (wchar_t *) username;
+  lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);
+  lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);
+#else
+  size_t buflen;
+  mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);
+  lsa_username.MaximumLength = (unsigned short) buflen * sizeof(wchar_t);
+  lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);
+  lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);
+  if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);
+  else {
+    if (policy == &handle) LsaClose(handle);
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("username_sid()"));
+    return 2;
+  }
+#endif
+
+  LSA_REFERENCED_DOMAIN_LIST *translated_domains;
+  LSA_TRANSLATED_SID *translated_sid;
+  NTSTATUS status = LsaLookupNames(*policy, 1, &lsa_username, &translated_domains, &translated_sid);
+#ifndef UNICODE
+  HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);
+#endif
+  if (policy == &handle) LsaClose(handle);
+  if (status) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_sid);
+    print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));
+    return 3;
+  }
+
+  if (translated_sid->Use != SidTypeUser && translated_sid->Use != SidTypeWellKnownGroup) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_sid);
+    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
+    return 4;
+  }
+
+  LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
+  if (! trust || ! IsValidSid(trust->Sid)) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_sid);
+    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
+    return 5;
+  }
+
+  /* GetSidSubAuthority*() return pointers! */
+  unsigned char *n = GetSidSubAuthorityCount(trust->Sid);
+
+  /* Convert translated SID to SID. */
+  *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));
+  if (! *sid) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_sid);
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));
+    return 6;
+  }
+
+  unsigned long error;
+  if (! InitializeSid(*sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {
+    error = GetLastError();
+    HeapFree(GetProcessHeap(), 0, *sid);
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_sid);
+    print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));
+    return 7;
+  }
+
+  for (unsigned char i = 0; i <= *n; i++) {
+    unsigned long *sub = GetSidSubAuthority(*sid, i);
+    if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);
+    else *sub = translated_sid->RelativeId;
+  }
+
+  int ret = 0;
+  if (translated_sid->Use == SidTypeWellKnownGroup && requires_password(*sid)) {
+    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
+    ret = 8;
+  }
+
+  LsaFreeMemory(translated_domains);
+  LsaFreeMemory(translated_sid);
+
+  return ret;
+}
+
+int username_sid(const TCHAR *username, SID **sid) {
+  return username_sid(username, sid, 0);
+}
+
+/* Do two usernames map to the same SID? */
+int username_equiv(const TCHAR *a, const TCHAR *b) {
+  SID *sid_a, *sid_b;
+  if (username_sid(a, &sid_a)) return 0;
+
+  if (username_sid(b, &sid_b)) {
+    FreeSid(sid_a);
+    return 0;
+  }
+
+  int ret = 0;
+  if (EqualSid(sid_a, sid_b)) ret = 1;
+
+  FreeSid(sid_a);
+  FreeSid(sid_b);
+
+  return ret;
+}
+
+/* Does the username represent the LocalSystem account? */
+int is_localsystem(const TCHAR *username) {
+  if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 1;
+  if (! imports.IsWellKnownSid) return 0;
+
+  SID *sid;
+  if (username_sid(username, &sid)) return 0;
+
+  int ret = 0;
+  if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) ret = 1;
+
+  FreeSid(sid);
+
+  return ret;
+}
+
+/*
+  Find the canonical name for a well-known account name.
+  MUST ONLY BE USED for well-known account names.
+  Must call LocalFree() on result.
+*/
+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;
+}
+
+/* Does the username require a password? */
+int requires_password(const TCHAR *username) {
+  if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;
+
+  SID *sid;
+  int r = username_sid(username, &sid);
+  if (username_sid(username, &sid)) return 0;
+
+  int ret = 0;
+  ret = requires_password(sid);
+
+  FreeSid(sid);
+
+  return ret;
+}
+
+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;
+  ZeroMemory(&attributes, sizeof(attributes));
+
+  LSA_HANDLE policy;
+  NTSTATUS status;
+
+  if (open_lsa_policy(&policy)) return 1;
+
+  /* Look up SID for the account. */
+  SID *sid;
+  if (username_sid(username, &sid, &policy)) {
+    LsaClose(policy);
+    return 2;
+  }
+
+  /* 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;
+  lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);
+  lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);
+
+  LSA_UNICODE_STRING *rights;
+  unsigned long count = ~0;
+  status = LsaEnumerateAccountRights(policy, sid, &rights, &count);
+  if (status) {
+    /*
+      If the account has no rights set LsaEnumerateAccountRights() will return
+      STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.
+    */
+    unsigned long error = LsaNtStatusToWinError(status);
+    if (error != ERROR_FILE_NOT_FOUND) {
+      FreeSid(sid);
+      LsaClose(policy);
+      print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));
+      return 4;
+    }
+  }
+
+  for (unsigned long i = 0; i < count; i++) {
+    if (rights[i].Length != lsa_right.Length) continue;
+    if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;
+    /* The SID has the right. */
+    FreeSid(sid);
+    LsaFreeMemory(rights);
+    LsaClose(policy);
+    return 0;
+  }
+  LsaFreeMemory(rights);
+
+  /* Add the right. */
+  status = LsaAddAccountRights(policy, sid, &lsa_right, 1);
+  FreeSid(sid);
+  LsaClose(policy);
+  if (status) {
+    print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));
+    return 5;
+  }
+
+  print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);
+  return 0;
+}

+ 22 - 0
account.h

@@ -0,0 +1,22 @@
+#ifndef ACCOUNT_H
+#define ACCOUNT_H
+
+#include <ntsecapi.h>
+
+/* Not really an account.  The canonical name is NT Authority\System. */
+#define NSSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem")
+/* 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 *);
+int grant_logon_as_service(const TCHAR *);
+
+#endif

+ 25 - 5
gui.cpp

@@ -367,13 +367,33 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
     }
 
     /*
-      Special case LOCALSYSTEM.
+      Special case for well-known accounts.
       Ignore the password if we're editing and the username hasn't changed.
     */
-    if (str_equiv(service->username, NSSM_LOCALSYSTEM_ACCOUNT)) {
-      HeapFree(GetProcessHeap(), 0, service->username);
-      service->username = 0;
-      service->usernamelen = 0;
+    if (! requires_password(service->username)) {
+      if (is_localsystem(service->username)) {
+        HeapFree(GetProcessHeap(), 0, service->username);
+        service->username = 0;
+        service->usernamelen = 0;
+      }
+      else {
+        TCHAR *canon = canonical_username(service->username);
+        HeapFree(GetProcessHeap(), 0, service->username);
+        service->username = 0;
+        service->usernamelen = 0;
+        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);
+        }
+        else return 6;
+      }
     }
     else {
       /* Password. */

BIN
messages.mc


+ 3 - 3
nssm.cpp

@@ -77,6 +77,9 @@ int _tmain(int argc, TCHAR **argv) {
   /* Remember if we are admin */
   check_admin();
 
+  /* Set up function pointers. */
+  if (get_imports()) exit(111);
+
   /* Elevate */
   if (argc > 1) {
     /*
@@ -137,9 +140,6 @@ int _tmain(int argc, TCHAR **argv) {
     This will save time when running with no arguments from a command prompt.
   */
   if (! GetStdHandle(STD_INPUT_HANDLE)) {
-    /* Set up function pointers. */
-    if (get_imports()) exit(111);
-
     /* Start service magic */
     SERVICE_TABLE_ENTRY table[] = { { NSSM, service_main }, { 0, 0 } };
     if (! StartServiceCtrlDispatcher(table)) {

+ 1 - 0
nssm.h

@@ -41,6 +41,7 @@
 #include <tchar.h>
 #include <windows.h>
 #include "service.h"
+#include "account.h"
 #include "console.h"
 #include "env.h"
 #include "event.h"

+ 8 - 0
nssm.vcproj

@@ -402,6 +402,10 @@
 			Name="Source Files"
 			Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
 			>
+			<File
+				RelativePath="account.cpp"
+				>
+			</File>
 			<File
 				RelativePath="console.cpp"
 				>
@@ -643,6 +647,10 @@
 			Name="Header Files"
 			Filter="h;hpp;hxx;hm;inl"
 			>
+			<File
+				RelativePath="account.h"
+				>
+			</File>
 			<File
 				RelativePath="console.h"
 				>

+ 6 - 154
service.cpp

@@ -1,8 +1,5 @@
 #include "nssm.h"
 
-/* This is explicitly a wide string. */
-#define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"
-
 bool is_admin;
 bool use_critical_section;
 
@@ -475,7 +472,7 @@ int get_service_username(const TCHAR *service_name, const QUERY_SERVICE_CONFIG *
 
   if (! qsc) return 1;
 
-  if (str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;
+  if (is_localsystem(qsc->lpServiceStartName)) return 0;
 
   size_t len = _tcslen(qsc->lpServiceStartName);
   *username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));
@@ -490,153 +487,6 @@ int get_service_username(const TCHAR *service_name, const QUERY_SERVICE_CONFIG *
   return 0;
 }
 
-int grant_logon_as_service(const TCHAR *username) {
-  if (! username) return 0;
-  if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;
-
-  /* Open Policy object. */
-  LSA_OBJECT_ATTRIBUTES attributes;
-  ZeroMemory(&attributes, sizeof(attributes));
-
-  LSA_HANDLE policy;
-
-  NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, &policy);
-  if (status) {
-    print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));
-    return 1;
-  }
-
-  /* Look up SID for the account. */
-  LSA_UNICODE_STRING lsa_username;
-#ifdef UNICODE
-  lsa_username.Buffer = (wchar_t *) username;
-  lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);
-  lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);
-#else
-  size_t buflen;
-  mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);
-  lsa_username.MaximumLength = (unsigned short) buflen * sizeof(wchar_t);
-  lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);
-  lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);
-  if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);
-  else {
-    LsaClose(policy);
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("grant_logon_as_service()"));
-    return 2;
-  }
-#endif
-
-  LSA_REFERENCED_DOMAIN_LIST *translated_domains;
-  LSA_TRANSLATED_SID *translated_sid;
-  status = LsaLookupNames(policy, 1, &lsa_username, &translated_domains, &translated_sid);
-#ifndef UNICODE
-  HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);
-#endif
-  if (status) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_sid);
-    LsaClose(policy);
-    print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));
-    return 3;
-  }
-
-  if (translated_sid->Use != SidTypeUser) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_sid);
-    LsaClose(policy);
-    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
-    return 4;
-  }
-
-  LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
-  if (! trust || ! IsValidSid(trust->Sid)) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_sid);
-    LsaClose(policy);
-    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
-    return 4;
-  }
-
-  /* GetSidSubAuthority*() return pointers! */
-  unsigned char *n = GetSidSubAuthorityCount(trust->Sid);
-
-  /* Convert translated SID to SID. */
-  SID *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));
-  if (! sid) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_sid);
-    LsaClose(policy);
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));
-    return 4;
-  }
-
-  unsigned long error;
-  if (! InitializeSid(sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {
-    error = GetLastError();
-    HeapFree(GetProcessHeap(), 0, sid);
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_sid);
-    LsaClose(policy);
-    print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));
-    return 5;
-  }
-
-  for (unsigned char i = 0; i <= *n; i++) {
-    unsigned long *sub = GetSidSubAuthority(sid, i);
-    if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);
-    else *sub = translated_sid->RelativeId;
-  }
-
-  LsaFreeMemory(translated_domains);
-  LsaFreeMemory(translated_sid);
-
-  /* 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;
-  lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);
-  lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);
-
-  LSA_UNICODE_STRING *rights;
-  unsigned long count = ~0;
-  status = LsaEnumerateAccountRights(policy, sid, &rights, &count);
-  if (status) {
-    /*
-      If the account has no rights set LsaEnumerateAccountRights() will return
-      STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.
-    */
-    error = LsaNtStatusToWinError(status);
-    if (error != ERROR_FILE_NOT_FOUND) {
-      HeapFree(GetProcessHeap(), 0, sid);
-      LsaClose(policy);
-      print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));
-      return 4;
-    }
-  }
-
-  for (unsigned long i = 0; i < count; i++) {
-    if (rights[i].Length != lsa_right.Length) continue;
-    if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;
-    /* The SID has the right. */
-    HeapFree(GetProcessHeap(), 0, sid);
-    LsaFreeMemory(rights);
-    LsaClose(policy);
-    return 0;
-  }
-  LsaFreeMemory(rights);
-
-  /* Add the right. */
-  status = LsaAddAccountRights(policy, sid, &lsa_right, 1);
-  HeapFree(GetProcessHeap(), 0, sid);
-  LsaClose(policy);
-  if (status) {
-    print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));
-    return 5;
-  }
-
-  print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);
-  return 0;
-}
-
 /* Set default values which aren't zero. */
 void set_nssm_service_defaults(nssm_service_t *service) {
   if (! service) return;
@@ -1084,9 +934,11 @@ int edit_service(nssm_service_t *service, bool editing) {
   }
   else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
 
-  if (grant_logon_as_service(username)) {
-    print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
-    return 5;
+  if (requires_password(username)) {
+    if (grant_logon_as_service(username)) {
+      print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
+      return 5;
+    }
   }
 
   if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {

+ 0 - 4
service.h

@@ -1,8 +1,6 @@
 #ifndef SERVICE_H
 #define SERVICE_H
 
-#include <ntsecapi.h>
-
 /*
   MSDN says the commandline in CreateProcess() is limited to 32768 characters
   and the application name to MAX_PATH.
@@ -19,7 +17,6 @@
 
 #define ACTION_LEN 16
 
-#define NSSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem")
 #define NSSM_KERNEL_DRIVER _T("SERVICE_KERNEL_DRIVER")
 #define NSSM_FILE_SYSTEM_DRIVER _T("SERVICE_FILE_SYSTEM_DRIVER")
 #define NSSM_WIN32_OWN_PROCESS _T("SERVICE_WIN32_OWN_PROCESS")
@@ -126,7 +123,6 @@ int set_service_description(const TCHAR *, SC_HANDLE, TCHAR *);
 int get_service_description(const TCHAR *, SC_HANDLE, unsigned long, TCHAR *);
 int get_service_startup(const TCHAR *, SC_HANDLE, const QUERY_SERVICE_CONFIG *, unsigned long *);
 int get_service_username(const TCHAR *, const QUERY_SERVICE_CONFIG *, TCHAR **, size_t *);
-int grant_logon_as_service(const TCHAR *);
 int pre_install_service(int, TCHAR **);
 int pre_remove_service(int, TCHAR **);
 int pre_edit_service(int, TCHAR **);

+ 33 - 14
settings.cpp

@@ -527,19 +527,32 @@ int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *n
     Logical syntax is: nssm set <service> ObjectName <username> <password>
     That means the username is actually passed in the additional parameter.
   */
-  bool localsystem = true;
+  bool localsystem = false;
   TCHAR *username = NSSM_LOCALSYSTEM_ACCOUNT;
   TCHAR *password = 0;
+  TCHAR *canon = 0;
   if (additional) {
-    if (! str_equiv(additional, NSSM_LOCALSYSTEM_ACCOUNT)) {
-      localsystem = false;
-      username = (TCHAR *) additional;
-      if (value && value->string) password = value->string;
-      else {
-        /* We need a password if the account is not LOCALSYSTEM. */
-        print_message(stderr, NSSM_MESSAGE_MISSING_PASSWORD, name);
-        return -1;
-      }
+    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;
+    }
+  }
+  else {
+    password = 0;
+    if (is_localsystem(username)) {
+      localsystem = true;
+      username = NSSM_LOCALSYSTEM_ACCOUNT;
+    }
+    else {
+      canon = canonical_username(username);
+      if (canon) username = canon;
     }
   }
 
@@ -552,6 +565,7 @@ int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *n
     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);
       return -1;
     }
 
@@ -559,19 +573,24 @@ int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *n
     HeapFree(GetProcessHeap(), 0, qsc);
   }
 
-  if (grant_logon_as_service(username)) {
-    if (password) SecureZeroMemory(password, _tcslen(password) * sizeof(TCHAR));
-    print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
-    return -1;
+  if (password) {
+    if (grant_logon_as_service(username)) {
+      if (password) SecureZeroMemory(password, _tcslen(password) * sizeof(TCHAR));
+      if (canon) LocalFree(canon);
+      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);
     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 (localsystem) return 0;
 
   return 1;