1/*
2 * Portions Copyright (C) 2004, 2007, 2009  Internet Systems Consortium, Inc. ("ISC")
3 * Portions Copyright (C) 2001, 2002  Internet Software Consortium.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 * PERFORMANCE OF THIS SOFTWARE.
16 */
17
18/* $Id: AccountInfo.cpp,v 1.10 2009/09/29 23:48:04 tbox Exp $ */
19
20#ifndef UNICODE
21#define UNICODE
22#endif /* UNICODE */
23
24#include "stdafx.h"
25
26#include <windows.h>
27#include <lm.h>
28#include <ntsecapi.h>
29
30#include <isc/ntgroups.h>
31#include <isc/result.h>
32#include "AccountInfo.h"
33
34#define MAX_NAME_LENGTH 256
35
36NTSTATUS
37OpenPolicy(
38    LPWSTR ServerName,		/* machine to open policy on (Unicode) */
39    DWORD DesiredAccess,	/* desired access to policy */
40    PLSA_HANDLE PolicyHandle	/* resultant policy handle */
41    );
42
43BOOL
44GetAccountSid(
45    LPTSTR SystemName,		/* where to lookup account */
46    LPTSTR AccountName,		/* account of interest */
47    PSID *Sid			/* resultant buffer containing SID */
48    );
49
50NTSTATUS
51SetPrivilegeOnAccount(
52    LSA_HANDLE PolicyHandle,	/* open policy handle */
53    PSID AccountSid,		/* SID to grant privilege to */
54    LPWSTR PrivilegeName,	/* privilege to grant (Unicode) */
55    BOOL bEnable		/* enable or disable */
56    );
57
58NTSTATUS
59GetPrivilegesOnAccount(
60    LSA_HANDLE PolicyHandle,	/* open policy handle */
61    PSID AccountSid,		/* SID to grant privilege to */
62    wchar_t **PrivList,		/* Ptr to List of Privileges found */
63    unsigned int *PrivCount	/* total number of Privileges in list */
64    );
65
66NTSTATUS
67AddPrivilegeToAcccount(
68    LPTSTR AccountName,		/* Name of the account */
69    LPWSTR PrivilegeName	/* Privilege to Add */
70    );
71
72void
73InitLsaString(
74    PLSA_UNICODE_STRING LsaString,	/* destination */
75    LPWSTR String			/* source (Unicode) */
76    );
77
78void
79DisplayNtStatus(
80    LPSTR szAPI,		/* pointer to function name (ANSI) */
81    NTSTATUS Status		/* NTSTATUS error value */
82    );
83
84void
85DisplayWinError(
86    LPSTR szAPI,		/* pointer to function name (ANSI) */
87    DWORD WinError		/* DWORD WinError */
88    );
89
90#ifndef STATUS_SUCCESS
91#define STATUS_SUCCESS  ((NTSTATUS)0x00000000L)
92#endif
93
94/*
95 * Note that this code only retrieves the list of privileges of the
96 * requested account or group. However, all accounts belong to the
97 * Everyone group even though that group is not returned by the
98 * calls to get the groups to which that account belongs.
99 * The Everyone group has two privileges associated with it:
100 * SeChangeNotifyPrivilege and SeNetworkLogonRight
101 * It is not advisable to disable or remove these privileges
102 * from the group nor can the account be removed from the Everyone
103 * group
104 * The None group has no privileges associated with it and is the group
105 * to which an account belongs if it is associated with no group.
106 */
107
108int
109GetAccountPrivileges(char *name, wchar_t **PrivList, unsigned int *PrivCount,
110		     char **Accounts, unsigned int *totalAccounts,
111		     int maxAccounts)
112{
113	LSA_HANDLE PolicyHandle;
114	TCHAR AccountName[256];		/* static account name buffer */
115	PSID pSid;
116	unsigned int i;
117	NTSTATUS Status;
118	isc_result_t istatus;
119	int iRetVal = RTN_ERROR;	/* assume error from main */
120
121	/*
122	 * Open the policy on the target machine.
123	 */
124	if ((Status = OpenPolicy(NULL,
125				 POLICY_LOOKUP_NAMES,
126				 &PolicyHandle)) != STATUS_SUCCESS)
127		return (RTN_ERROR);
128
129	/*
130	 * Let's see if the account exists. Return if not
131	 */
132	wsprintf(AccountName, TEXT("%hS"), name);
133	if (!GetAccountSid(NULL, AccountName, &pSid))
134		return (RTN_NOACCOUNT);
135	/*
136	 * Find out what groups the account belongs to
137	 */
138	istatus = isc_ntsecurity_getaccountgroups(name, Accounts, maxAccounts,
139						  totalAccounts);
140	if (istatus == ISC_R_NOMEMORY)
141		return (RTN_NOMEMORY);
142	else if (istatus != ISC_R_SUCCESS)
143		return (RTN_ERROR);
144
145	Accounts[*totalAccounts] = name; /* Add the account to the list */
146	(*totalAccounts)++;
147
148	/*
149	 * Loop through each Account to get the list of privileges
150	 */
151	for (i = 0; i < *totalAccounts; i++) {
152		wsprintf(AccountName, TEXT("%hS"), Accounts[i]);
153		 /* Obtain the SID of the user/group. */
154		if (!GetAccountSid(NULL, AccountName, &pSid))
155			continue;	/* Try the next one */
156		/* Get the Privileges allocated to this SID */
157		if ((Status = GetPrivilegesOnAccount(PolicyHandle, pSid,
158			PrivList, PrivCount)) == STATUS_SUCCESS)
159		{
160			iRetVal=RTN_OK;
161			if (pSid != NULL)
162				HeapFree(GetProcessHeap(), 0, pSid);
163		} else {
164			if (pSid != NULL)
165				HeapFree(GetProcessHeap(), 0, pSid);
166			continue;	/* Try the next one */
167		}
168	}
169	/*
170	 * Close the policy handle.
171	 */
172	LsaClose(PolicyHandle);
173
174	(*totalAccounts)--;	/* Correct for the number of groups */
175	return iRetVal;
176}
177
178BOOL
179CreateServiceAccount(char *name, char *password) {
180	NTSTATUS retstat;
181	USER_INFO_1 ui;
182	DWORD dwLevel = 1;
183	DWORD dwError = 0;
184	NET_API_STATUS nStatus;
185
186	size_t namelen = strlen(name);
187	size_t passwdlen = strlen(password);
188	wchar_t AccountName[MAX_NAME_LENGTH];
189	wchar_t AccountPassword[MAX_NAME_LENGTH];
190
191	mbstowcs(AccountName, name, namelen + 1);
192	mbstowcs(AccountPassword, password, passwdlen + 1);
193
194	/*
195	 * Set up the USER_INFO_1 structure.
196	 * USER_PRIV_USER: name is required here when creating an account
197	 * rather than an administrator or a guest.
198	 */
199
200	ui.usri1_name = (LPWSTR) &AccountName;
201	ui.usri1_password = (LPWSTR) &AccountPassword;
202	ui.usri1_priv = USER_PRIV_USER;
203	ui.usri1_home_dir = NULL;
204	ui.usri1_comment = L"ISC BIND Service Account";
205	ui.usri1_flags = UF_PASSWD_CANT_CHANGE | UF_DONT_EXPIRE_PASSWD |
206			 UF_SCRIPT;
207	ui.usri1_script_path = NULL;
208	/*
209	 * Call the NetUserAdd function, specifying level 1.
210	 */
211	nStatus = NetUserAdd(NULL, dwLevel, (LPBYTE)&ui, &dwError);
212
213	if (nStatus != NERR_Success)
214		return (FALSE);
215
216	retstat = AddPrivilegeToAcccount(name, SE_SERVICE_LOGON_PRIV);
217	return (TRUE);
218}
219
220NTSTATUS
221AddPrivilegeToAcccount(LPTSTR name, LPWSTR PrivilegeName) {
222	LSA_HANDLE PolicyHandle;
223	TCHAR AccountName[256];		/* static account name buffer */
224	PSID pSid;
225	NTSTATUS Status;
226	unsigned long err;
227
228	/*
229	 * Open the policy on the target machine.
230	 */
231	if ((Status = OpenPolicy(NULL, POLICY_ALL_ACCESS, &PolicyHandle))
232		!= STATUS_SUCCESS)
233		return (RTN_ERROR);
234
235	/*
236	 * Let's see if the account exists. Return if not
237	 */
238	wsprintf(AccountName, TEXT("%hS"), name);
239	if (!GetAccountSid(NULL, AccountName, &pSid))
240		return (RTN_NOACCOUNT);
241
242	err = LsaNtStatusToWinError(SetPrivilegeOnAccount(PolicyHandle,
243		pSid, PrivilegeName, TRUE));
244
245	LsaClose(PolicyHandle);
246	if (err == ERROR_SUCCESS)
247		return (RTN_OK);
248	else
249		return (err);
250}
251
252void
253InitLsaString(PLSA_UNICODE_STRING LsaString, LPWSTR String){
254	size_t StringLength;
255
256	if (String == NULL) {
257		LsaString->Buffer = NULL;
258		LsaString->Length = 0;
259		LsaString->MaximumLength = 0;
260		return;
261	}
262
263	StringLength = wcslen(String);
264	LsaString->Buffer = String;
265	LsaString->Length = (USHORT) StringLength * sizeof(WCHAR);
266	LsaString->MaximumLength = (USHORT)(StringLength+1) * sizeof(WCHAR);
267}
268
269NTSTATUS
270OpenPolicy(LPWSTR ServerName, DWORD DesiredAccess, PLSA_HANDLE PolicyHandle){
271	LSA_OBJECT_ATTRIBUTES ObjectAttributes;
272	LSA_UNICODE_STRING ServerString;
273	PLSA_UNICODE_STRING Server = NULL;
274
275	/*
276	 * Always initialize the object attributes to all zeroes.
277	 */
278	ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));
279
280	if (ServerName != NULL) {
281		/*
282		 * Make a LSA_UNICODE_STRING out of the LPWSTR passed in
283		 */
284		InitLsaString(&ServerString, ServerName);
285		Server = &ServerString;
286	}
287
288	/*
289	 * Attempt to open the policy.
290	 */
291	return (LsaOpenPolicy(Server, &ObjectAttributes, DesiredAccess,
292		PolicyHandle));
293}
294
295BOOL
296GetAccountSid(LPTSTR SystemName, LPTSTR AccountName, PSID *Sid) {
297	LPTSTR ReferencedDomain = NULL;
298	DWORD cbSid = 128;    /* initial allocation attempt */
299	DWORD cbReferencedDomain = 16; /* initial allocation size */
300	SID_NAME_USE peUse;
301	BOOL bSuccess = FALSE; /* assume this function will fail */
302
303	__try {
304		/*
305		 * initial memory allocations
306		 */
307		if ((*Sid = HeapAlloc(GetProcessHeap(), 0, cbSid)) == NULL)
308			__leave;
309
310		if ((ReferencedDomain = (LPTSTR) HeapAlloc(GetProcessHeap(), 0,
311				       cbReferencedDomain)) == NULL) __leave;
312
313		/*
314		 * Obtain the SID of the specified account on the specified system.
315		 */
316		while (!LookupAccountName(SystemName, AccountName, *Sid, &cbSid,
317					  ReferencedDomain, &cbReferencedDomain,
318					  &peUse))
319		{
320			if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
321				/* reallocate memory */
322				if ((*Sid = HeapReAlloc(GetProcessHeap(), 0,
323					*Sid, cbSid)) == NULL) __leave;
324
325				if ((ReferencedDomain= (LPTSTR) HeapReAlloc(
326					GetProcessHeap(), 0, ReferencedDomain,
327					cbReferencedDomain)) == NULL)
328				__leave;
329			}
330			else
331				__leave;
332		}
333		bSuccess = TRUE;
334	} /* finally */
335	__finally {
336
337		/* Cleanup and indicate failure, if appropriate. */
338
339		HeapFree(GetProcessHeap(), 0, ReferencedDomain);
340
341		if (!bSuccess) {
342			if (*Sid != NULL) {
343				HeapFree(GetProcessHeap(), 0, *Sid);
344				*Sid = NULL;
345			}
346		}
347
348	}
349
350	return (bSuccess);
351}
352
353NTSTATUS
354SetPrivilegeOnAccount(LSA_HANDLE PolicyHandle, PSID AccountSid,
355		      LPWSTR PrivilegeName, BOOL bEnable)
356{
357	LSA_UNICODE_STRING PrivilegeString;
358
359	/* Create a LSA_UNICODE_STRING for the privilege name. */
360	InitLsaString(&PrivilegeString, PrivilegeName);
361
362	/* grant or revoke the privilege, accordingly */
363	if (bEnable)
364		return (LsaAddAccountRights(PolicyHandle, AccountSid,
365			&PrivilegeString, 1));
366	else
367		return (LsaRemoveAccountRights(PolicyHandle, AccountSid,
368			FALSE, &PrivilegeString, 1));
369}
370
371NTSTATUS
372GetPrivilegesOnAccount(LSA_HANDLE PolicyHandle, PSID AccountSid,
373		       wchar_t **PrivList, unsigned int *PrivCount)
374{
375	NTSTATUS Status;
376	LSA_UNICODE_STRING *UserRights;
377	ULONG CountOfRights;
378	unsigned int retlen = 0;
379	DWORD i, j;
380	int found;
381
382	Status = LsaEnumerateAccountRights(PolicyHandle, AccountSid,
383		&UserRights, &CountOfRights);
384	/* Only continue if there is something */
385	if (UserRights == NULL || Status != STATUS_SUCCESS)
386		return (Status);
387
388	for (i = 0; i < CountOfRights; i++) {
389		found = -1;
390		retlen = UserRights[i].Length/sizeof(wchar_t);
391		for (j = 0; j < *PrivCount; j++) {
392			found = wcsncmp(PrivList[j], UserRights[i].Buffer,
393					retlen);
394			if (found == 0)
395				break;
396		}
397		if (found != 0) {
398			PrivList[*PrivCount] =
399			    (wchar_t *)malloc(UserRights[i].MaximumLength);
400			if (PrivList[*PrivCount] == NULL)
401				return (RTN_NOMEMORY);
402
403			wcsncpy(PrivList[*PrivCount], UserRights[i].Buffer,
404				retlen);
405			PrivList[*PrivCount][retlen] = L'\0';
406			(*PrivCount)++;
407		}
408
409	}
410
411	return (Status);
412}
413
414void
415DisplayNtStatus(LPSTR szAPI, NTSTATUS Status) {
416	/* Convert the NTSTATUS to Winerror. Then call DisplayWinError(). */
417	DisplayWinError(szAPI, LsaNtStatusToWinError(Status));
418}
419
420void
421DisplayWinError(LPSTR szAPI, DWORD WinError) {
422	LPSTR MessageBuffer;
423	DWORD dwBufferLength;
424
425	if (dwBufferLength=FormatMessageA(
426		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
427		NULL, WinError, GetUserDefaultLangID(),
428		(LPSTR) &MessageBuffer, 0, NULL)){
429		DWORD dwBytesWritten; /* unused */
430
431		/* Output message string on stderr. */
432		WriteFile(GetStdHandle(STD_ERROR_HANDLE), MessageBuffer,
433			  dwBufferLength, &dwBytesWritten, NULL);
434
435		/* Free the buffer allocated by the system. */
436		LocalFree(MessageBuffer);
437	}
438}
439