RegisterPowerSettingNotification is unnecessary.
[nssm.git] / account.cpp
index 9c2ff70..70bfa3e 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++) {
@@ -103,9 +140,9 @@ 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;
+    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;
@@ -154,175 +248,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 (! username) return NSSM_LOCALSYSTEM_ACCOUNT;
+  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 +290,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;