Use UTF-8 functions when working with explicit encodings.
[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     LsaFreeMemory(translated_domains);\r
93     LsaFreeMemory(translated_sid);\r
94     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
95     return 6;\r
96   }\r
97 \r
98   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];\r
99   if (! trust || ! IsValidSid(trust->Sid)) {\r
100     LsaFreeMemory(translated_domains);\r
101     LsaFreeMemory(translated_sid);\r
102     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
103     return 7;\r
104   }\r
105 \r
106   /* GetSidSubAuthority*() return pointers! */\r
107   unsigned char *n = GetSidSubAuthorityCount(trust->Sid);\r
108 \r
109   /* Convert translated SID to SID. */\r
110   *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));\r
111   if (! *sid) {\r
112     LsaFreeMemory(translated_domains);\r
113     LsaFreeMemory(translated_sid);\r
114     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("username_sid"));\r
115     return 8;\r
116   }\r
117 \r
118   unsigned long error;\r
119   if (! InitializeSid(*sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {\r
120     error = GetLastError();\r
121     HeapFree(GetProcessHeap(), 0, *sid);\r
122     LsaFreeMemory(translated_domains);\r
123     LsaFreeMemory(translated_sid);\r
124     print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));\r
125     return 9;\r
126   }\r
127 \r
128   for (unsigned char i = 0; i <= *n; i++) {\r
129     unsigned long *sub = GetSidSubAuthority(*sid, i);\r
130     if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);\r
131     else *sub = translated_sid->RelativeId;\r
132   }\r
133 \r
134   ret = 0;\r
135   if (translated_sid->Use == SidTypeWellKnownGroup && ! well_known_sid(*sid)) {\r
136     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
137     ret = 10;\r
138   }\r
139 \r
140   LsaFreeMemory(translated_domains);\r
141   LsaFreeMemory(translated_sid);\r
142 \r
143   return ret;\r
144 }\r
145 \r
146 int username_sid(const TCHAR *username, SID **sid) {\r
147   return username_sid(username, sid, 0);\r
148 }\r
149 \r
150 int canonicalise_username(const TCHAR *username, TCHAR **canon) {\r
151   LSA_HANDLE policy;\r
152   if (open_lsa_policy(&policy)) return 1;\r
153 \r
154   SID *sid;\r
155   if (username_sid(username, &sid, &policy)) return 2;\r
156   PSID sids = { sid };\r
157 \r
158   LSA_REFERENCED_DOMAIN_LIST *translated_domains;\r
159   LSA_TRANSLATED_NAME *translated_name;\r
160   NTSTATUS status = LsaLookupSids(policy, 1, &sids, &translated_domains, &translated_name);\r
161   if (status != STATUS_SUCCESS) {\r
162     LsaFreeMemory(translated_domains);\r
163     LsaFreeMemory(translated_name);\r
164     print_message(stderr, NSSM_MESSAGE_LSALOOKUPSIDS_FAILED, error_string(LsaNtStatusToWinError(status)));\r
165     return 3;\r
166   }\r
167 \r
168   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_name->DomainIndex];\r
169   LSA_UNICODE_STRING lsa_canon;\r
170   lsa_canon.Length = translated_name->Name.Length + trust->Name.Length + sizeof(wchar_t);\r
171   lsa_canon.MaximumLength = lsa_canon.Length + sizeof(wchar_t);\r
172   lsa_canon.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lsa_canon.MaximumLength);\r
173   if (! lsa_canon.Buffer) {\r
174     LsaFreeMemory(translated_domains);\r
175     LsaFreeMemory(translated_name);\r
176     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("lsa_canon"), _T("username_sid"));\r
177     return 9;\r
178   }\r
179 \r
180   /* Buffer is wchar_t but Length is in bytes. */\r
181   memmove((char *) lsa_canon.Buffer, trust->Name.Buffer, trust->Name.Length);\r
182   memmove((char *) lsa_canon.Buffer + trust->Name.Length, L"\\", sizeof(wchar_t));\r
183   memmove((char *) lsa_canon.Buffer + trust->Name.Length + sizeof(wchar_t), translated_name->Name.Buffer, translated_name->Name.Length);\r
184 \r
185   unsigned long canonlen;\r
186   if (from_utf16(lsa_canon.Buffer, canon, &canonlen)) {\r
187     LsaFreeMemory(translated_domains);\r
188     LsaFreeMemory(translated_name);\r
189     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("username_sid"));\r
190     return 10;\r
191   }\r
192   HeapFree(GetProcessHeap(), 0, lsa_canon.Buffer);\r
193 \r
194   LsaFreeMemory(translated_domains);\r
195   LsaFreeMemory(translated_name);\r
196 \r
197   return 0;\r
198 }\r
199 \r
200 /* Do two usernames map to the same SID? */\r
201 int username_equiv(const TCHAR *a, const TCHAR *b) {\r
202   SID *sid_a, *sid_b;\r
203   if (username_sid(a, &sid_a)) return 0;\r
204 \r
205   if (username_sid(b, &sid_b)) {\r
206     FreeSid(sid_a);\r
207     return 0;\r
208   }\r
209 \r
210   int ret = 0;\r
211   if (EqualSid(sid_a, sid_b)) ret = 1;\r
212 \r
213   FreeSid(sid_a);\r
214   FreeSid(sid_b);\r
215 \r
216   return ret;\r
217 }\r
218 \r
219 /* Does the username represent the LocalSystem account? */\r
220 int is_localsystem(const TCHAR *username) {\r
221   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 1;\r
222   if (! imports.IsWellKnownSid) return 0;\r
223 \r
224   SID *sid;\r
225   if (username_sid(username, &sid)) return 0;\r
226 \r
227   int ret = 0;\r
228   if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) ret = 1;\r
229 \r
230   FreeSid(sid);\r
231 \r
232   return ret;\r
233 }\r
234 \r
235 /*\r
236   Get well-known alias for LocalSystem and friends.\r
237   Returns a pointer to a static string.  DO NOT try to free it.\r
238 */\r
239 const TCHAR *well_known_sid(SID *sid) {\r
240   if (! imports.IsWellKnownSid) return 0;\r
241   if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return NSSM_LOCALSYSTEM_ACCOUNT;\r
242   if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return NSSM_LOCALSERVICE_ACCOUNT;\r
243   if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return NSSM_NETWORKSERVICE_ACCOUNT;\r
244   return 0;\r
245 }\r
246 \r
247 const TCHAR *well_known_username(const TCHAR *username) {\r
248   if (! username) return NSSM_LOCALSYSTEM_ACCOUNT;\r
249   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return NSSM_LOCALSYSTEM_ACCOUNT;\r
250   SID *sid;\r
251   if (username_sid(username, &sid)) return 0;\r
252 \r
253   const TCHAR *well_known = well_known_sid(sid);\r
254   FreeSid(sid);\r
255 \r
256   return well_known;\r
257 }\r
258 \r
259 int grant_logon_as_service(const TCHAR *username) {\r
260   if (! username) return 0;\r
261 \r
262   /* Open Policy object. */\r
263   LSA_OBJECT_ATTRIBUTES attributes;\r
264   ZeroMemory(&attributes, sizeof(attributes));\r
265 \r
266   LSA_HANDLE policy;\r
267   NTSTATUS status;\r
268 \r
269   if (open_lsa_policy(&policy)) return 1;\r
270 \r
271   /* Look up SID for the account. */\r
272   SID *sid;\r
273   if (username_sid(username, &sid, &policy)) {\r
274     LsaClose(policy);\r
275     return 2;\r
276   }\r
277 \r
278   /*\r
279     Shouldn't happen because it should have been checked before callling this function.\r
280   */\r
281   if (well_known_sid(sid)) {\r
282     LsaClose(policy);\r
283     return 3;\r
284   }\r
285 \r
286   /* Check if the SID has the "Log on as a service" right. */\r
287   LSA_UNICODE_STRING lsa_right;\r
288   lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;\r
289   lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);\r
290   lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);\r
291 \r
292   LSA_UNICODE_STRING *rights;\r
293   unsigned long count = ~0;\r
294   status = LsaEnumerateAccountRights(policy, sid, &rights, &count);\r
295   if (status != STATUS_SUCCESS) {\r
296     /*\r
297       If the account has no rights set LsaEnumerateAccountRights() will return\r
298       STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.\r
299     */\r
300     unsigned long error = LsaNtStatusToWinError(status);\r
301     if (error != ERROR_FILE_NOT_FOUND) {\r
302       FreeSid(sid);\r
303       LsaClose(policy);\r
304       print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));\r
305       return 4;\r
306     }\r
307   }\r
308 \r
309   for (unsigned long i = 0; i < count; i++) {\r
310     if (rights[i].Length != lsa_right.Length) continue;\r
311     if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;\r
312     /* The SID has the right. */\r
313     FreeSid(sid);\r
314     LsaFreeMemory(rights);\r
315     LsaClose(policy);\r
316     return 0;\r
317   }\r
318   LsaFreeMemory(rights);\r
319 \r
320   /* Add the right. */\r
321   status = LsaAddAccountRights(policy, sid, &lsa_right, 1);\r
322   FreeSid(sid);\r
323   LsaClose(policy);\r
324   if (status != STATUS_SUCCESS) {\r
325     print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));\r
326     return 5;\r
327   }\r
328 \r
329   print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);\r
330   return 0;\r
331 }\r