Fixed running service under local username.
authorIain Patterson <me@iain.cx>
Sun, 29 Jun 2014 10:09:07 +0000 (11:09 +0100)
committerIain Patterson <me@iain.cx>
Sun, 29 Jun 2014 10:10:25 +0000 (11:10 +0100)
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.

README.txt
account.cpp
account.h
service.cpp

index 37639ba..f74100e 100644 (file)
@@ -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\r
 the second restart of the application would have a corrupted environment.\r
 Thanks to Hadrien Kohl for suggesting to disable the console window's menu.\r
+Thanks to Allen Vailliencourt for noticing bugs with configuring the service to\r
+run under a local user account.\r
 \r
 Licence\r
 -------\r
index 19dc66e..fc626b4 100644 (file)
@@ -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;
index 1719e6d..e6b40f0 100644 (file)
--- a/account.h
+++ b/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 *);
index 8d1c0fe..58ca264 100644 (file)
@@ -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.\r
   */\r
   TCHAR *username = 0;\r
+  TCHAR *canon = 0;\r
   TCHAR *password = 0;\r
   if (service->usernamelen) {\r
     username = service->username;\r
+    if (canonicalise_username(username, &canon)) return 5;\r
     if (service->passwordlen) password = service->password;\r
-    else password = _T("");\r
   }\r
-  else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;\r
+  else if (editing) username = canon = NSSM_LOCALSYSTEM_ACCOUNT;\r
 \r
-  if (well_known_username(username)) password = _T("");\r
+  if (well_known_username(canon)) password = _T("");\r
   else {\r
-    if (grant_logon_as_service(username)) {\r
+    if (grant_logon_as_service(canon)) {\r
+      if (canon != username) HeapFree(GetProcessHeap(), 0, canon);\r
       print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
       return 5;\r
     }\r
@@ -1138,10 +1140,12 @@ int edit_service(nssm_service_t *service, bool editing) {
   TCHAR *dependencies = _T("");\r
   if (service->dependencieslen) dependencies = 0; /* Change later. */\r
 \r
-  if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, dependencies, username, password, service->displayname)) {\r
+  if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, dependencies, canon, password, service->displayname)) {\r
+    if (canon != username) HeapFree(GetProcessHeap(), 0, canon);\r
     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
     return 5;\r
   }\r
+  if (canon != username) HeapFree(GetProcessHeap(), 0, canon);\r
 \r
   if (service->dependencieslen) {\r
     if (set_service_dependencies(service->name, service->handle, service->dependencies)) return 5;\r