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