Use close_handle().
[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 /* Build the virtual account name. */\r
238 TCHAR *virtual_account(const TCHAR *service_name) {\r
239   size_t len = _tcslen(NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN) + _tcslen(service_name) + 2;\r
240   TCHAR *name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));\r
241   if (! name) {\r
242     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("name"), _T("virtual_account"));\r
243     return 0;\r
244   }\r
245 \r
246   _sntprintf_s(name, len, _TRUNCATE, _T("%s\\%s"), NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN, service_name);\r
247   return name;\r
248 }\r
249 \r
250 /* Does the username represent a virtual account for the service? */\r
251 int is_virtual_account(const TCHAR *service_name, const TCHAR *username) {\r
252   if (! imports.IsWellKnownSid) return 0;\r
253   if (! service_name) return 0;\r
254   if (! username) return 0;\r
255 \r
256   TCHAR *canon = virtual_account(service_name);\r
257   int ret = str_equiv(canon, username);\r
258   HeapFree(GetProcessHeap(), 0, canon);\r
259   return ret;\r
260 }\r
261 \r
262 /*\r
263   Get well-known alias for LocalSystem and friends.\r
264   Returns a pointer to a static string.  DO NOT try to free it.\r
265 */\r
266 const TCHAR *well_known_sid(SID *sid) {\r
267   if (! imports.IsWellKnownSid) return 0;\r
268   if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return NSSM_LOCALSYSTEM_ACCOUNT;\r
269   if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return NSSM_LOCALSERVICE_ACCOUNT;\r
270   if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return NSSM_NETWORKSERVICE_ACCOUNT;\r
271   return 0;\r
272 }\r
273 \r
274 const TCHAR *well_known_username(const TCHAR *username) {\r
275   if (! username) return NSSM_LOCALSYSTEM_ACCOUNT;\r
276   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return NSSM_LOCALSYSTEM_ACCOUNT;\r
277   SID *sid;\r
278   if (username_sid(username, &sid)) return 0;\r
279 \r
280   const TCHAR *well_known = well_known_sid(sid);\r
281   FreeSid(sid);\r
282 \r
283   return well_known;\r
284 }\r
285 \r
286 int grant_logon_as_service(const TCHAR *username) {\r
287   if (! username) return 0;\r
288 \r
289   /* Open Policy object. */\r
290   LSA_OBJECT_ATTRIBUTES attributes;\r
291   ZeroMemory(&attributes, sizeof(attributes));\r
292 \r
293   LSA_HANDLE policy;\r
294   NTSTATUS status;\r
295 \r
296   if (open_lsa_policy(&policy)) return 1;\r
297 \r
298   /* Look up SID for the account. */\r
299   SID *sid;\r
300   if (username_sid(username, &sid, &policy)) {\r
301     LsaClose(policy);\r
302     return 2;\r
303   }\r
304 \r
305   /*\r
306     Shouldn't happen because it should have been checked before callling this function.\r
307   */\r
308   if (well_known_sid(sid)) {\r
309     LsaClose(policy);\r
310     return 3;\r
311   }\r
312 \r
313   /* Check if the SID has the "Log on as a service" right. */\r
314   LSA_UNICODE_STRING lsa_right;\r
315   lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;\r
316   lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);\r
317   lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);\r
318 \r
319   LSA_UNICODE_STRING *rights;\r
320   unsigned long count = ~0;\r
321   status = LsaEnumerateAccountRights(policy, sid, &rights, &count);\r
322   if (status != STATUS_SUCCESS) {\r
323     /*\r
324       If the account has no rights set LsaEnumerateAccountRights() will return\r
325       STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.\r
326     */\r
327     unsigned long error = LsaNtStatusToWinError(status);\r
328     if (error != ERROR_FILE_NOT_FOUND) {\r
329       FreeSid(sid);\r
330       LsaClose(policy);\r
331       print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));\r
332       return 4;\r
333     }\r
334   }\r
335 \r
336   for (unsigned long i = 0; i < count; i++) {\r
337     if (rights[i].Length != lsa_right.Length) continue;\r
338     if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;\r
339     /* The SID has the right. */\r
340     FreeSid(sid);\r
341     LsaFreeMemory(rights);\r
342     LsaClose(policy);\r
343     return 0;\r
344   }\r
345   LsaFreeMemory(rights);\r
346 \r
347   /* Add the right. */\r
348   status = LsaAddAccountRights(policy, sid, &lsa_right, 1);\r
349   FreeSid(sid);\r
350   LsaClose(policy);\r
351   if (status != STATUS_SUCCESS) {\r
352     print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));\r
353     return 5;\r
354   }\r
355 \r
356   print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);\r
357   return 0;\r
358 }\r