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