Handle well-known account names.
[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   LSA_UNICODE_STRING lsa_username;
30 #ifdef UNICODE
31   lsa_username.Buffer = (wchar_t *) username;
32   lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);
33   lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);
34 #else
35   size_t buflen;
36   mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);
37   lsa_username.MaximumLength = (unsigned short) buflen * sizeof(wchar_t);
38   lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);
39   lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);
40   if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);
41   else {
42     if (policy == &handle) LsaClose(handle);
43     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("username_sid()"));
44     return 2;
45   }
46 #endif
47
48   LSA_REFERENCED_DOMAIN_LIST *translated_domains;
49   LSA_TRANSLATED_SID *translated_sid;
50   NTSTATUS status = LsaLookupNames(*policy, 1, &lsa_username, &translated_domains, &translated_sid);
51 #ifndef UNICODE
52   HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);
53 #endif
54   if (policy == &handle) LsaClose(handle);
55   if (status) {
56     LsaFreeMemory(translated_domains);
57     LsaFreeMemory(translated_sid);
58     print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));
59     return 3;
60   }
61
62   if (translated_sid->Use != SidTypeUser && translated_sid->Use != SidTypeWellKnownGroup) {
63     LsaFreeMemory(translated_domains);
64     LsaFreeMemory(translated_sid);
65     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
66     return 4;
67   }
68
69   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
70   if (! trust || ! IsValidSid(trust->Sid)) {
71     LsaFreeMemory(translated_domains);
72     LsaFreeMemory(translated_sid);
73     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
74     return 5;
75   }
76
77   /* GetSidSubAuthority*() return pointers! */
78   unsigned char *n = GetSidSubAuthorityCount(trust->Sid);
79
80   /* Convert translated SID to SID. */
81   *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));
82   if (! *sid) {
83     LsaFreeMemory(translated_domains);
84     LsaFreeMemory(translated_sid);
85     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));
86     return 6;
87   }
88
89   unsigned long error;
90   if (! InitializeSid(*sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {
91     error = GetLastError();
92     HeapFree(GetProcessHeap(), 0, *sid);
93     LsaFreeMemory(translated_domains);
94     LsaFreeMemory(translated_sid);
95     print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));
96     return 7;
97   }
98
99   for (unsigned char i = 0; i <= *n; i++) {
100     unsigned long *sub = GetSidSubAuthority(*sid, i);
101     if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);
102     else *sub = translated_sid->RelativeId;
103   }
104
105   int ret = 0;
106   if (translated_sid->Use == SidTypeWellKnownGroup && requires_password(*sid)) {
107     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
108     ret = 8;
109   }
110
111   LsaFreeMemory(translated_domains);
112   LsaFreeMemory(translated_sid);
113
114   return ret;
115 }
116
117 int username_sid(const TCHAR *username, SID **sid) {
118   return username_sid(username, sid, 0);
119 }
120
121 /* Do two usernames map to the same SID? */
122 int username_equiv(const TCHAR *a, const TCHAR *b) {
123   SID *sid_a, *sid_b;
124   if (username_sid(a, &sid_a)) return 0;
125
126   if (username_sid(b, &sid_b)) {
127     FreeSid(sid_a);
128     return 0;
129   }
130
131   int ret = 0;
132   if (EqualSid(sid_a, sid_b)) ret = 1;
133
134   FreeSid(sid_a);
135   FreeSid(sid_b);
136
137   return ret;
138 }
139
140 /* Does the username represent the LocalSystem account? */
141 int is_localsystem(const TCHAR *username) {
142   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 1;
143   if (! imports.IsWellKnownSid) return 0;
144
145   SID *sid;
146   if (username_sid(username, &sid)) return 0;
147
148   int ret = 0;
149   if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) ret = 1;
150
151   FreeSid(sid);
152
153   return ret;
154 }
155
156 /*
157   Find the canonical name for a well-known account name.
158   MUST ONLY BE USED for well-known account names.
159   Must call LocalFree() on result.
160 */
161 TCHAR *canonical_username(const TCHAR *username) {
162   SID *user_sid;
163   TCHAR *canon;
164   size_t len;
165
166   if (is_localsystem(username)) {
167     len = (_tcslen(NSSM_LOCALSYSTEM_ACCOUNT) + 1) * sizeof(TCHAR);
168     canon = (TCHAR *) LocalAlloc(LPTR, len);
169     if (! canon) {
170       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, NSSM_LOCALSYSTEM_ACCOUNT, _T("canonical_username"));
171       return 0;
172     }
173     memmove(canon, NSSM_LOCALSYSTEM_ACCOUNT, len);
174     _tprintf(_T("it's localsystem = %s!\n"), canon);
175     return canon;
176   }
177
178   if (! imports.CreateWellKnownSid) return 0;
179
180   if (username_sid(username, &user_sid)) return 0;
181
182   /*
183     LsaLookupSids will return the canonical username but the result won't
184     include the NT Authority part.  Thus we must look that up as well.
185   */
186   unsigned long ntsidsize = SECURITY_MAX_SID_SIZE;
187   SID *ntauth_sid = (SID *) HeapAlloc(GetProcessHeap(), 0, ntsidsize);
188   if (! ntauth_sid) {
189     HeapFree(GetProcessHeap(), 0, user_sid);
190     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("NT Authority"), _T("canonical_username"));
191     return 0;
192   }
193
194   if (! imports.CreateWellKnownSid(WinNtAuthoritySid, NULL, ntauth_sid, &ntsidsize)) {
195     HeapFree(GetProcessHeap(), 0, ntauth_sid);
196     print_message(stderr, NSSM_MESSAGE_CREATEWELLKNOWNSID_FAILED, _T("WinNtAuthoritySid"));
197     return 0;
198   }
199
200   LSA_HANDLE policy;
201   if (open_lsa_policy(&policy)) return 0;
202
203   LSA_REFERENCED_DOMAIN_LIST *translated_domains;
204   LSA_TRANSLATED_NAME *translated_names;
205
206   unsigned long n = 2;
207   PSID *sids = (PSID *) HeapAlloc(GetProcessHeap(), 0, n * sizeof(PSID));
208   sids[0] = user_sid;
209   sids[1] = ntauth_sid;
210
211   NTSTATUS status = LsaLookupSids(policy, n, (PSID *) sids, &translated_domains, &translated_names);
212   HeapFree(GetProcessHeap(), 0, user_sid);
213   HeapFree(GetProcessHeap(), 0, ntauth_sid);
214   HeapFree(GetProcessHeap(), 0, sids);
215   LsaClose(policy);
216   if (status) {
217     print_message(stderr, NSSM_MESSAGE_LSALOOKUPSIDS_FAILED);
218     return 0;
219   }
220
221   /* Look up the account name. */
222   LSA_TRANSLATED_NAME *translated_name = &(translated_names[0]);
223   if (translated_name->Use != SidTypeWellKnownGroup) {
224     print_message(stderr, NSSM_GUI_INVALID_USERNAME);
225     LsaFreeMemory(translated_domains);
226     LsaFreeMemory(translated_names);
227     return 0;
228   }
229
230   LSA_UNICODE_STRING *lsa_group = &translated_name->Name;
231
232   /* Look up NT Authority. */
233   translated_name = &(translated_names[1]);
234   if (translated_name->Use != SidTypeDomain) {
235     print_message(stderr, NSSM_GUI_INVALID_USERNAME);
236     LsaFreeMemory(translated_domains);
237     LsaFreeMemory(translated_names);
238     return 0;
239   }
240
241   /* In theory these pointers should all be valid if we got this far... */
242   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_name->DomainIndex];
243   LSA_UNICODE_STRING *lsa_domain = &trust->Name;
244
245   TCHAR *domain, *group;
246   unsigned long lsa_domain_len = lsa_domain->Length;
247   unsigned long lsa_group_len = lsa_group->Length;
248   len = lsa_domain_len + lsa_group_len + 2;
249
250 #ifdef UNICODE
251   domain = lsa_domain->Buffer;
252   group = lsa_group->Buffer;
253 #else
254   size_t buflen;
255
256   wcstombs_s(&buflen, NULL, 0, lsa_domain->Buffer, _TRUNCATE);
257   domain = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
258   if (! domain) {
259     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("domain"), _T("canonical_username"));
260     LsaFreeMemory(translated_domains);
261     LsaFreeMemory(translated_names);
262     return 0;
263   }
264   wcstombs_s(&buflen, (char *) domain, buflen, lsa_domain->Buffer, _TRUNCATE);
265
266   wcstombs_s(&buflen, NULL, 0, lsa_group->Buffer, _TRUNCATE);
267   group = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
268   if (! group) {
269     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("group"), _T("canonical_username"));
270     LsaFreeMemory(translated_domains);
271     LsaFreeMemory(translated_names);
272     return 0;
273   }
274   wcstombs_s(&buflen, (char *) group, buflen, lsa_group->Buffer, _TRUNCATE);
275 #endif
276
277   canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
278   if (! canon) {
279     LsaFreeMemory(translated_domains);
280     LsaFreeMemory(translated_names);
281     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("canonical_username"));
282     return 0;
283   }
284
285   _sntprintf_s(canon, len, _TRUNCATE, _T("%s\\%s"), domain, group);
286
287 #ifndef UNICODE
288   HeapFree(GetProcessHeap(), 0, domain);
289   HeapFree(GetProcessHeap(), 0, group);
290 #endif
291
292   LsaFreeMemory(translated_domains);
293   LsaFreeMemory(translated_names);
294
295   return canon;
296 }
297
298 /* Does the SID type require a password? */
299 int requires_password(SID *sid) {
300   if (! imports.IsWellKnownSid) return -1;
301   if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return 0;
302   if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return 0;
303   if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return 0;;
304   return 1;
305 }
306
307 /* Does the username require a password? */
308 int requires_password(const TCHAR *username) {
309   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;
310
311   SID *sid;
312   int r = username_sid(username, &sid);
313   if (username_sid(username, &sid)) return 0;
314
315   int ret = 0;
316   ret = requires_password(sid);
317
318   FreeSid(sid);
319
320   return ret;
321 }
322
323 int grant_logon_as_service(const TCHAR *username) {
324   if (! username) return 0;
325   if (! requires_password(username)) return 0;
326
327   /* Open Policy object. */
328   LSA_OBJECT_ATTRIBUTES attributes;
329   ZeroMemory(&attributes, sizeof(attributes));
330
331   LSA_HANDLE policy;
332   NTSTATUS status;
333
334   if (open_lsa_policy(&policy)) return 1;
335
336   /* Look up SID for the account. */
337   SID *sid;
338   if (username_sid(username, &sid, &policy)) {
339     LsaClose(policy);
340     return 2;
341   }
342
343   /* Check if the SID has the "Log on as a service" right. */
344   LSA_UNICODE_STRING lsa_right;
345   lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;
346   lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);
347   lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);
348
349   LSA_UNICODE_STRING *rights;
350   unsigned long count = ~0;
351   status = LsaEnumerateAccountRights(policy, sid, &rights, &count);
352   if (status) {
353     /*
354       If the account has no rights set LsaEnumerateAccountRights() will return
355       STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.
356     */
357     unsigned long error = LsaNtStatusToWinError(status);
358     if (error != ERROR_FILE_NOT_FOUND) {
359       FreeSid(sid);
360       LsaClose(policy);
361       print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));
362       return 4;
363     }
364   }
365
366   for (unsigned long i = 0; i < count; i++) {
367     if (rights[i].Length != lsa_right.Length) continue;
368     if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;
369     /* The SID has the right. */
370     FreeSid(sid);
371     LsaFreeMemory(rights);
372     LsaClose(policy);
373     return 0;
374   }
375   LsaFreeMemory(rights);
376
377   /* Add the right. */
378   status = LsaAddAccountRights(policy, sid, &lsa_right, 1);
379   FreeSid(sid);
380   LsaClose(policy);
381   if (status) {
382     print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));
383     return 5;
384   }
385
386   print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);
387   return 0;
388 }