Browse Source

Fixed running service under local username.

Trying to set a local account to run the service would fail with
LsaLookupNames() complaining that the name could not be looked up.

Thanks Allen Vailliencourt.
Iain Patterson 8 years ago
parent
commit
f5e50181eb
4 changed files with 118 additions and 17 deletions
  1. 2 0
      README.txt
  2. 106 12
      account.cpp
  3. 1 0
      account.h
  4. 9 5
      service.cpp

+ 2 - 0
README.txt

@@ -682,6 +682,8 @@ Thanks to Czenda Czendov for help with Visual Studio 2013 and Server 2012R2.
 Thanks to Alessandro Gherardi for reporting and draft fix of the bug whereby
 the second restart of the application would have a corrupted environment.
 Thanks to Hadrien Kohl for suggesting to disable the console window's menu.
+Thanks to Allen Vailliencourt for noticing bugs with configuring the service to
+run under a local user account.
 
 Licence
 -------

+ 106 - 12
account.cpp

@@ -26,22 +26,58 @@ int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
     if (open_lsa_policy(policy)) return 1;
   }
 
+  /*
+    LsaLookupNames() can't look up .\username but can look up
+    %COMPUTERNAME%\username.  ChangeServiceConfig() writes .\username to the
+    registry when %COMPUTERNAME%\username is a passed as a parameter.  We
+    need to preserve .\username when calling ChangeServiceConfig() without
+    changing the username, but expand to %COMPUTERNAME%\username when calling
+    LsaLookupNames().
+  */
+  TCHAR *expanded;
+  unsigned long expandedlen;
+  if (_tcsnicmp(_T(".\\"), username, 2)) {
+    expandedlen = (unsigned long) (_tcslen(username) + 1) * sizeof(TCHAR);
+    expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen);
+    if (! expanded) {
+      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid"));
+      if (policy == &handle) LsaClose(handle);
+      return 2;
+    }
+    memmove(expanded, username, expandedlen);
+  }
+  else {
+    TCHAR computername[MAX_COMPUTERNAME_LENGTH + 1];
+    expandedlen = _countof(computername);
+    GetComputerName(computername, &expandedlen);
+    expandedlen += (unsigned long) _tcslen(username);
+
+    expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen * sizeof(TCHAR));
+    if (! expanded) {
+      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid"));
+      if (policy == &handle) LsaClose(handle);
+      return 2;
+    }
+    _sntprintf_s(expanded, expandedlen, _TRUNCATE, _T("%s\\%s"), computername, username + 2);
+  }
+
   LSA_UNICODE_STRING lsa_username;
 #ifdef UNICODE
-  lsa_username.Buffer = (wchar_t *) username;
-  lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);
+  lsa_username.Buffer = (wchar_t *) expanded;
+  lsa_username.Length = (unsigned short) _tcslen(expanded) * sizeof(TCHAR);
   lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);
 #else
   size_t buflen;
-  mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);
+  mbstowcs_s(&buflen, NULL, 0, expanded, _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);
+  if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, expanded, _TRUNCATE);
   else {
     if (policy == &handle) LsaClose(handle);
+    HeapFree(GetProcessHeap(), 0, expanded);
     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("username_sid()"));
-    return 2;
+    return 4;
   }
 #endif
 
@@ -51,19 +87,20 @@ int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
 #ifndef UNICODE
   HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);
 #endif
+  HeapFree(GetProcessHeap(), 0, expanded);
   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;
+    return 5;
   }
 
   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;
+    return 6;
   }
 
   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
@@ -71,7 +108,7 @@ int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
     LsaFreeMemory(translated_domains);
     LsaFreeMemory(translated_sid);
     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
-    return 5;
+    return 7;
   }
 
   /* GetSidSubAuthority*() return pointers! */
@@ -82,8 +119,8 @@ int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
   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;
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("username_sid"));
+    return 8;
   }
 
   unsigned long error;
@@ -93,7 +130,7 @@ int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
     LsaFreeMemory(translated_domains);
     LsaFreeMemory(translated_sid);
     print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));
-    return 7;
+    return 9;
   }
 
   for (unsigned char i = 0; i <= *n; i++) {
@@ -105,7 +142,7 @@ int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
   int ret = 0;
   if (translated_sid->Use == SidTypeWellKnownGroup && ! well_known_sid(*sid)) {
     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
-    ret = 8;
+    ret = 10;
   }
 
   LsaFreeMemory(translated_domains);
@@ -118,6 +155,63 @@ int username_sid(const TCHAR *username, SID **sid) {
   return username_sid(username, sid, 0);
 }
 
+int canonicalise_username(const TCHAR *username, TCHAR **canon) {
+  LSA_HANDLE policy;
+  if (open_lsa_policy(&policy)) return 1;
+
+  SID *sid;
+  if (username_sid(username, &sid, &policy)) return 2;
+  PSID sids = { sid };
+
+  LSA_REFERENCED_DOMAIN_LIST *translated_domains;
+  LSA_TRANSLATED_NAME *translated_name;
+  NTSTATUS status = LsaLookupSids(policy, 1, &sids, &translated_domains, &translated_name);
+  if (status) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_name);
+    print_message(stderr, NSSM_MESSAGE_LSALOOKUPSIDS_FAILED, error_string(LsaNtStatusToWinError(status)));
+    return 3;
+  }
+
+  LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_name->DomainIndex];
+  LSA_UNICODE_STRING lsa_canon;
+  lsa_canon.Length = translated_name->Name.Length + trust->Name.Length + sizeof(wchar_t);
+  lsa_canon.MaximumLength = lsa_canon.Length + sizeof(wchar_t);
+  lsa_canon.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lsa_canon.MaximumLength);
+  if (! lsa_canon.Buffer) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_name);
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("lsa_canon"), _T("username_sid"));
+    return 9;
+  }
+
+  /* Buffer is wchar_t but Length is in bytes. */
+  memmove((char *) lsa_canon.Buffer, trust->Name.Buffer, trust->Name.Length);
+  memmove((char *) lsa_canon.Buffer + trust->Name.Length, L"\\", sizeof(wchar_t));
+  memmove((char *) lsa_canon.Buffer + trust->Name.Length + sizeof(wchar_t), translated_name->Name.Buffer, translated_name->Name.Length);
+
+#ifdef UNICODE
+  *canon = lsa_canon.Buffer;
+#else
+  size_t buflen;
+  wcstombs_s(&buflen, NULL, 0, lsa_canon.Buffer, _TRUNCATE);
+  *canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
+  if (! *canon) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_name);
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("username_sid"));
+    return 10;
+  }
+  wcstombs_s(&buflen, *canon, buflen, lsa_canon.Buffer, _TRUNCATE);
+  HeapFree(GetProcessHeap(), 0, lsa_canon.Buffer);
+#endif
+
+  LsaFreeMemory(translated_domains);
+  LsaFreeMemory(translated_name);
+
+  return 0;
+}
+
 /* Do two usernames map to the same SID? */
 int username_equiv(const TCHAR *a, const TCHAR *b) {
   SID *sid_a, *sid_b;

+ 1 - 0
account.h

@@ -15,6 +15,7 @@ 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 canonicalise_username(const TCHAR *, TCHAR **);
 int is_localsystem(const TCHAR *);
 const TCHAR *well_known_sid(SID *);
 const TCHAR *well_known_username(const TCHAR *);

+ 9 - 5
service.cpp

@@ -1119,17 +1119,19 @@ int edit_service(nssm_service_t *service, bool editing) {
     Empty passwords are valid but we won't allow them in the GUI.
   */
   TCHAR *username = 0;
+  TCHAR *canon = 0;
   TCHAR *password = 0;
   if (service->usernamelen) {
     username = service->username;
+    if (canonicalise_username(username, &canon)) return 5;
     if (service->passwordlen) password = service->password;
-    else password = _T("");
   }
-  else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
+  else if (editing) username = canon = NSSM_LOCALSYSTEM_ACCOUNT;
 
-  if (well_known_username(username)) password = _T("");
+  if (well_known_username(canon)) password = _T("");
   else {
-    if (grant_logon_as_service(username)) {
+    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;
     }
@@ -1138,10 +1140,12 @@ int edit_service(nssm_service_t *service, bool editing) {
   TCHAR *dependencies = _T("");
   if (service->dependencieslen) dependencies = 0; /* Change later. */
 
-  if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, dependencies, username, password, service->displayname)) {
+  if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, dependencies, canon, password, service->displayname)) {
+    if (canon != username) HeapFree(GetProcessHeap(), 0, canon);
     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
     return 5;
   }
+  if (canon != username) HeapFree(GetProcessHeap(), 0, canon);
 
   if (service->dependencieslen) {
     if (set_service_dependencies(service->name, service->handle, service->dependencies)) return 5;