Support virtual service accounts.
[nssm.git] / account.cpp
1 #include "nssm.h"\r
2 \r
3 #include <sddl.h>\r
4 \r
5 #ifndef STATUS_SUCCESS\r
6 #define STATUS_SUCCESS ERROR_SUCCESS\r
7 #endif\r
8 \r
9 extern imports_t imports;\r
10 \r
11 /* Open Policy object. */\r
12 int open_lsa_policy(LSA_HANDLE *policy) {\r
13   LSA_OBJECT_ATTRIBUTES attributes;\r
14   ZeroMemory(&attributes, sizeof(attributes));\r
15 \r
16   NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, policy);\r
17   if (status != STATUS_SUCCESS) {\r
18     print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));\r
19     return 1;\r
20   }\r
21 \r
22   return 0;\r
23 }\r
24 \r
25 /* Look up SID for an account. */\r
26 int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {\r
27   LSA_HANDLE handle;\r
28   if (! policy) {\r
29     policy = &handle;\r
30     if (open_lsa_policy(policy)) return 1;\r
31   }\r
32 \r
33   /*\r
34     LsaLookupNames() can't look up .\username but can look up\r
35     %COMPUTERNAME%\username.  ChangeServiceConfig() writes .\username to the\r
36     registry when %COMPUTERNAME%\username is a passed as a parameter.  We\r
37     need to preserve .\username when calling ChangeServiceConfig() without\r
38     changing the username, but expand to %COMPUTERNAME%\username when calling\r
39     LsaLookupNames().\r
40   */\r
41   TCHAR *expanded;\r
42   unsigned long expandedlen;\r
43   if (_tcsnicmp(_T(".\\"), username, 2)) {\r
44     expandedlen = (unsigned long) (_tcslen(username) + 1) * sizeof(TCHAR);\r
45     expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen);\r
46     if (! expanded) {\r
47       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid"));\r
48       if (policy == &handle) LsaClose(handle);\r
49       return 2;\r
50     }\r
51     memmove(expanded, username, expandedlen);\r
52   }\r
53   else {\r
54     TCHAR computername[MAX_COMPUTERNAME_LENGTH + 1];\r
55     expandedlen = _countof(computername);\r
56     GetComputerName(computername, &expandedlen);\r
57     expandedlen += (unsigned long) _tcslen(username);\r
58 \r
59     expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen * sizeof(TCHAR));\r
60     if (! expanded) {\r
61       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid"));\r
62       if (policy == &handle) LsaClose(handle);\r
63       return 2;\r
64     }\r
65     _sntprintf_s(expanded, expandedlen, _TRUNCATE, _T("%s\\%s"), computername, username + 2);\r
66   }\r
67 \r
68   LSA_UNICODE_STRING lsa_username;\r
69   int ret = to_utf16(expanded, &lsa_username.Buffer, (unsigned long *) &lsa_username.Length);\r
70   HeapFree(GetProcessHeap(), 0, expanded);\r
71   if (ret) {\r
72     if (policy == &handle) LsaClose(handle);\r
73     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("username_sid()"));\r
74     return 4;\r
75   }\r
76   lsa_username.Length *= sizeof(wchar_t);\r
77   lsa_username.MaximumLength = lsa_username.Length + sizeof(wchar_t);\r
78 \r
79   LSA_REFERENCED_DOMAIN_LIST *translated_domains;\r
80   LSA_TRANSLATED_SID *translated_sid;\r
81   NTSTATUS status = LsaLookupNames(*policy, 1, &lsa_username, &translated_domains, &translated_sid);\r
82   HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);\r
83   if (policy == &handle) LsaClose(handle);\r
84   if (status != STATUS_SUCCESS) {\r
85     LsaFreeMemory(translated_domains);\r
86     LsaFreeMemory(translated_sid);\r
87     print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));\r
88     return 5;\r
89   }\r
90 \r
91   if (translated_sid->Use != SidTypeUser && translated_sid->Use != SidTypeWellKnownGroup) {\r
92     if (translated_sid->Use != SidTypeUnknown || _tcsnicmp(NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN _T("\\"), username, _tcslen(NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN) + 1)) {\r
93       LsaFreeMemory(translated_domains);\r
94       LsaFreeMemory(translated_sid);\r
95       print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
96       return 6;\r
97     }\r
98   }\r
99 \r
100   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];\r
101   if (! trust || ! IsValidSid(trust->Sid)) {\r
102     LsaFreeMemory(translated_domains);\r
103     LsaFreeMemory(translated_sid);\r
104     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
105     return 7;\r
106   }\r
107 \r
108   /* GetSidSubAuthority*() return pointers! */\r
109   unsigned char *n = GetSidSubAuthorityCount(trust->Sid);\r
110 \r
111   /* Convert translated SID to SID. */\r
112   *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));\r
113   if (! *sid) {\r
114     LsaFreeMemory(translated_domains);\r
115     LsaFreeMemory(translated_sid);\r
116     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("username_sid"));\r
117     return 8;\r
118   }\r
119 \r
120   unsigned long error;\r
121   if (! InitializeSid(*sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {\r
122     error = GetLastError();\r
123     HeapFree(GetProcessHeap(), 0, *sid);\r
124     LsaFreeMemory(translated_domains);\r
125     LsaFreeMemory(translated_sid);\r
126     print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));\r
127     return 9;\r
128   }\r
129 \r
130   for (unsigned char i = 0; i <= *n; i++) {\r
131     unsigned long *sub = GetSidSubAuthority(*sid, i);\r
132     if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);\r
133     else *sub = translated_sid->RelativeId;\r
134   }\r
135 \r
136   ret = 0;\r
137   if (translated_sid->Use == SidTypeWellKnownGroup && ! well_known_sid(*sid)) {\r
138     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
139     ret = 10;\r
140   }\r
141 \r
142   LsaFreeMemory(translated_domains);\r
143   LsaFreeMemory(translated_sid);\r
144 \r
145   return ret;\r
146 }\r
147 \r
148 int username_sid(const TCHAR *username, SID **sid) {\r
149   return username_sid(username, sid, 0);\r
150 }\r
151 \r
152 int canonicalise_username(const TCHAR *username, TCHAR **canon) {\r
153   LSA_HANDLE policy;\r
154   if (open_lsa_policy(&policy)) return 1;\r
155 \r
156   SID *sid;\r
157   if (username_sid(username, &sid, &policy)) return 2;\r
158   PSID sids = { sid };\r
159 \r
160   LSA_REFERENCED_DOMAIN_LIST *translated_domains;\r
161   LSA_TRANSLATED_NAME *translated_name;\r
162   NTSTATUS status = LsaLookupSids(policy, 1, &sids, &translated_domains, &translated_name);\r
163   if (status != STATUS_SUCCESS) {\r
164     LsaFreeMemory(translated_domains);\r
165     LsaFreeMemory(translated_name);\r
166     print_message(stderr, NSSM_MESSAGE_LSALOOKUPSIDS_FAILED, error_string(LsaNtStatusToWinError(status)));\r
167     return 3;\r
168   }\r
169 \r
170   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_name->DomainIndex];\r
171   LSA_UNICODE_STRING lsa_canon;\r
172   lsa_canon.Length = translated_name->Name.Length + trust->Name.Length + sizeof(wchar_t);\r
173   lsa_canon.MaximumLength = lsa_canon.Length + sizeof(wchar_t);\r
174   lsa_canon.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lsa_canon.MaximumLength);\r
175   if (! lsa_canon.Buffer) {\r
176     LsaFreeMemory(translated_domains);\r
177     LsaFreeMemory(translated_name);\r
178     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("lsa_canon"), _T("username_sid"));\r
179     return 9;\r
180   }\r
181 \r
182   /* Buffer is wchar_t but Length is in bytes. */\r
183   memmove((char *) lsa_canon.Buffer, trust->Name.Buffer, trust->Name.Length);\r
184   memmove((char *) lsa_canon.Buffer + trust->Name.Length, L"\\", sizeof(wchar_t));\r
185   memmove((char *) lsa_canon.Buffer + trust->Name.Length + sizeof(wchar_t), translated_name->Name.Buffer, translated_name->Name.Length);\r
186 \r
187   unsigned long canonlen;\r
188   if (from_utf16(lsa_canon.Buffer, canon, &canonlen)) {\r
189     LsaFreeMemory(translated_domains);\r
190     LsaFreeMemory(translated_name);\r
191     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("username_sid"));\r
192     return 10;\r
193   }\r
194   HeapFree(GetProcessHeap(), 0, lsa_canon.Buffer);\r
195 \r
196   LsaFreeMemory(translated_domains);\r
197   LsaFreeMemory(translated_name);\r
198 \r
199   return 0;\r
200 }\r
201 \r
202 /* Do two usernames map to the same SID? */\r
203 int username_equiv(const TCHAR *a, const TCHAR *b) {\r
204   SID *sid_a, *sid_b;\r
205   if (username_sid(a, &sid_a)) return 0;\r
206 \r
207   if (username_sid(b, &sid_b)) {\r
208     FreeSid(sid_a);\r
209     return 0;\r
210   }\r
211 \r
212   int ret = 0;\r
213   if (EqualSid(sid_a, sid_b)) ret = 1;\r
214 \r
215   FreeSid(sid_a);\r
216   FreeSid(sid_b);\r
217 \r
218   return ret;\r
219 }\r
220 \r
221 /* Does the username represent the LocalSystem account? */\r
222 int is_localsystem(const TCHAR *username) {\r
223   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 1;\r
224   if (! imports.IsWellKnownSid) return 0;\r
225 \r
226   SID *sid;\r
227   if (username_sid(username, &sid)) return 0;\r
228 \r
229   int ret = 0;\r
230   if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) ret = 1;\r
231 \r
232   FreeSid(sid);\r
233 \r
234   return ret;\r
235 }\r
236 \r
237 /* Does the username represent a virtual account for the service? */\r
238 int is_virtual_account(const TCHAR *service_name, const TCHAR *username) {\r
239   if (! imports.IsWellKnownSid) return 0;\r
240   if (! service_name) return 0;\r
241   if (! username) return 0;\r
242 \r
243   size_t len = _tcslen(NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN) + _tcslen(service_name) + 2;\r
244   TCHAR *canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));\r
245   if (! canon) {\r
246     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("is_virtual_account"));\r
247     return 0;\r
248   }\r
249   _sntprintf_s(canon, len, _TRUNCATE, _T("%s\\%s"), NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN, service_name);\r
250   int ret = str_equiv(canon, username);\r
251   HeapFree(GetProcessHeap(), 0, canon);\r
252   return ret;\r
253 }\r
254 \r
255 /*\r
256   Get well-known alias for LocalSystem and friends.\r
257   Returns a pointer to a static string.  DO NOT try to free it.\r
258 */\r
259 const TCHAR *well_known_sid(SID *sid) {\r
260   if (! imports.IsWellKnownSid) return 0;\r
261   if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return NSSM_LOCALSYSTEM_ACCOUNT;\r
262   if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return NSSM_LOCALSERVICE_ACCOUNT;\r
263   if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return NSSM_NETWORKSERVICE_ACCOUNT;\r
264   return 0;\r
265 }\r
266 \r
267 const TCHAR *well_known_username(const TCHAR *username) {\r
268   if (! username) return NSSM_LOCALSYSTEM_ACCOUNT;\r
269   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return NSSM_LOCALSYSTEM_ACCOUNT;\r
270   SID *sid;\r
271   if (username_sid(username, &sid)) return 0;\r
272 \r
273   const TCHAR *well_known = well_known_sid(sid);\r
274   FreeSid(sid);\r
275 \r
276   return well_known;\r
277 }\r
278 \r
279 int grant_logon_as_service(const TCHAR *username) {\r
280   if (! username) return 0;\r
281 \r
282   /* Open Policy object. */\r
283   LSA_OBJECT_ATTRIBUTES attributes;\r
284   ZeroMemory(&attributes, sizeof(attributes));\r
285 \r
286   LSA_HANDLE policy;\r
287   NTSTATUS status;\r
288 \r
289   if (open_lsa_policy(&policy)) return 1;\r
290 \r
291   /* Look up SID for the account. */\r
292   SID *sid;\r
293   if (username_sid(username, &sid, &policy)) {\r
294     LsaClose(policy);\r
295     return 2;\r
296   }\r
297 \r
298   /*\r
299     Shouldn't happen because it should have been checked before callling this function.\r
300   */\r
301   if (well_known_sid(sid)) {\r
302     LsaClose(policy);\r
303     return 3;\r
304   }\r
305 \r
306   /* Check if the SID has the "Log on as a service" right. */\r
307   LSA_UNICODE_STRING lsa_right;\r
308   lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;\r
309   lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);\r
310   lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);\r
311 \r
312   LSA_UNICODE_STRING *rights;\r
313   unsigned long count = ~0;\r
314   status = LsaEnumerateAccountRights(policy, sid, &rights, &count);\r
315   if (status != STATUS_SUCCESS) {\r
316     /*\r
317       If the account has no rights set LsaEnumerateAccountRights() will return\r
318       STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.\r
319     */\r
320     unsigned long error = LsaNtStatusToWinError(status);\r
321     if (error != ERROR_FILE_NOT_FOUND) {\r
322       FreeSid(sid);\r
323       LsaClose(policy);\r
324       print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));\r
325       return 4;\r
326     }\r
327   }\r
328 \r
329   for (unsigned long i = 0; i < count; i++) {\r
330     if (rights[i].Length != lsa_right.Length) continue;\r
331     if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;\r
332     /* The SID has the right. */\r
333     FreeSid(sid);\r
334     LsaFreeMemory(rights);\r
335     LsaClose(policy);\r
336     return 0;\r
337   }\r
338   LsaFreeMemory(rights);\r
339 \r
340   /* Add the right. */\r
341   status = LsaAddAccountRights(policy, sid, &lsa_right, 1);\r
342   FreeSid(sid);\r
343   LsaClose(policy);\r
344   if (status != STATUS_SUCCESS) {\r
345     print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));\r
346     return 5;\r
347   }\r
348 \r
349   print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);\r
350   return 0;\r
351 }\r