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