diff options
Diffstat (limited to 'src/libs/dutil/WixToolset.DUtil/userutil.cpp')
-rw-r--r-- | src/libs/dutil/WixToolset.DUtil/userutil.cpp | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/src/libs/dutil/WixToolset.DUtil/userutil.cpp b/src/libs/dutil/WixToolset.DUtil/userutil.cpp new file mode 100644 index 00000000..ca6d5480 --- /dev/null +++ b/src/libs/dutil/WixToolset.DUtil/userutil.cpp | |||
@@ -0,0 +1,300 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | #include "precomp.h" | ||
4 | |||
5 | |||
6 | // UserExit macros | ||
7 | #define UserExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) | ||
8 | #define UserExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) | ||
9 | #define UserExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) | ||
10 | #define UserExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) | ||
11 | #define UserExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) | ||
12 | #define UserExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) | ||
13 | #define UserExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_USERUTIL, p, x, e, s, __VA_ARGS__) | ||
14 | #define UserExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_USERUTIL, p, x, s, __VA_ARGS__) | ||
15 | #define UserExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_USERUTIL, p, x, e, s, __VA_ARGS__) | ||
16 | #define UserExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_USERUTIL, p, x, s, __VA_ARGS__) | ||
17 | #define UserExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_USERUTIL, e, x, s, __VA_ARGS__) | ||
18 | #define UserExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_USERUTIL, g, x, s, __VA_ARGS__) | ||
19 | |||
20 | static BOOL CheckIsMemberHelper( | ||
21 | __in_z LPCWSTR pwzGroupUserDomain, | ||
22 | __in_ecount(cguiGroupData) const GROUP_USERS_INFO_0 *pguiGroupData, | ||
23 | __in DWORD cguiGroupData | ||
24 | ); | ||
25 | |||
26 | /******************************************************************* | ||
27 | UserBuildDomainUserName - builds a DOMAIN\USERNAME string | ||
28 | |||
29 | ********************************************************************/ | ||
30 | extern "C" HRESULT DAPI UserBuildDomainUserName( | ||
31 | __out_ecount_z(cchDest) LPWSTR wzDest, | ||
32 | __in int cchDest, | ||
33 | __in_z LPCWSTR pwzName, | ||
34 | __in_z LPCWSTR pwzDomain | ||
35 | ) | ||
36 | { | ||
37 | HRESULT hr = S_OK; | ||
38 | DWORD cchLeft = cchDest; | ||
39 | WCHAR* pwz = wzDest; | ||
40 | DWORD cchWz = cchDest; | ||
41 | DWORD cch; | ||
42 | |||
43 | cch = lstrlenW(pwzDomain); | ||
44 | if (cch >= cchLeft) | ||
45 | { | ||
46 | hr = ERROR_MORE_DATA; | ||
47 | UserExitOnFailure(hr, "Buffer size is not big enough to hold domain name: %ls", pwzDomain); | ||
48 | } | ||
49 | else if (cch > 0) | ||
50 | { | ||
51 | // handle the domain case | ||
52 | |||
53 | hr = ::StringCchCopyNW(pwz, cchWz, pwzDomain, cchLeft - 1); // last parameter does not include '\0' | ||
54 | UserExitOnFailure(hr, "Failed to copy Domain onto string."); | ||
55 | |||
56 | cchLeft -= cch; | ||
57 | pwz += cch; | ||
58 | cchWz -= cch; | ||
59 | |||
60 | if (1 >= cchLeft) | ||
61 | { | ||
62 | hr = ERROR_MORE_DATA; | ||
63 | UserExitOnFailure(hr, "Insufficient buffer size while building domain user name"); | ||
64 | } | ||
65 | |||
66 | hr = ::StringCchCopyNW(pwz, cchWz, L"\\", cchLeft - 1); // last parameter does not include '\0' | ||
67 | UserExitOnFailure(hr, "Failed to copy backslash onto string."); | ||
68 | |||
69 | --cchLeft; | ||
70 | ++pwz; | ||
71 | --cchWz; | ||
72 | } | ||
73 | |||
74 | cch = lstrlenW(pwzName); | ||
75 | if (cch >= cchLeft) | ||
76 | { | ||
77 | hr = ERROR_MORE_DATA; | ||
78 | UserExitOnFailure(hr, "Buffer size is not big enough to hold user name: %ls", pwzName); | ||
79 | } | ||
80 | |||
81 | hr = ::StringCchCopyNW(pwz, cchWz, pwzName, cchLeft - 1); // last parameter does not include '\0' | ||
82 | UserExitOnFailure(hr, "Failed to copy User name onto string."); | ||
83 | |||
84 | LExit: | ||
85 | return hr; | ||
86 | } | ||
87 | |||
88 | |||
89 | /******************************************************************* | ||
90 | Checks whether a user is a member of a group - outputs the result via lpfMember | ||
91 | ********************************************************************/ | ||
92 | extern "C" HRESULT DAPI UserCheckIsMember( | ||
93 | __in_z LPCWSTR pwzName, | ||
94 | __in_z LPCWSTR pwzDomain, | ||
95 | __in_z LPCWSTR pwzGroupName, | ||
96 | __in_z LPCWSTR pwzGroupDomain, | ||
97 | __out LPBOOL lpfMember | ||
98 | ) | ||
99 | { | ||
100 | HRESULT hr = S_OK; | ||
101 | UINT er = ERROR_SUCCESS; | ||
102 | |||
103 | DWORD dwRead = 0; | ||
104 | DWORD dwTotal = 0; | ||
105 | LPCWSTR wz = NULL; | ||
106 | GROUP_USERS_INFO_0 *pguiGroupData = NULL; | ||
107 | WCHAR wzGroupUserDomain[MAX_DARWIN_COLUMN + 1]; // GROUPDOMAIN\GROUPNAME | ||
108 | WCHAR wzUserDomain[MAX_DARWIN_COLUMN + 1]; // USERDOMAIN\USERNAME | ||
109 | BSTR bstrUser = NULL; | ||
110 | BSTR bstrGroup = NULL; | ||
111 | |||
112 | IADsGroup *pGroup = NULL; | ||
113 | VARIANT_BOOL vtBoolResult = VARIANT_FALSE; | ||
114 | |||
115 | hr = UserBuildDomainUserName(wzGroupUserDomain, countof(wzGroupUserDomain), pwzGroupName, pwzGroupDomain); | ||
116 | UserExitOnFailure(hr, "Failed to build group name from group domain %ls, group name %ls", pwzGroupDomain, pwzGroupName); | ||
117 | |||
118 | hr = UserBuildDomainUserName(wzUserDomain, countof(wzUserDomain), pwzName, pwzDomain); | ||
119 | UserExitOnFailure(hr, "Failed to build group name from group domain %ls, group name %ls", pwzGroupDomain, pwzGroupName); | ||
120 | |||
121 | if (pwzDomain && *pwzDomain) | ||
122 | { | ||
123 | wz = pwzDomain; | ||
124 | } | ||
125 | |||
126 | er = ::NetUserGetGroups(wz, pwzName, 0, (LPBYTE *)&pguiGroupData, MAX_PREFERRED_LENGTH, &dwRead, &dwTotal); | ||
127 | // Ignore these errors, and just go to the fallback checks | ||
128 | if (ERROR_BAD_NETPATH == er || ERROR_INVALID_NAME == er || NERR_UserNotFound == er) | ||
129 | { | ||
130 | Trace(REPORT_VERBOSE, "failed to get groups for user %ls from domain %ls with error code 0x%x - continuing", pwzName, (wz != NULL) ? wz : L"", HRESULT_FROM_WIN32(er)); | ||
131 | er = ERROR_SUCCESS; | ||
132 | } | ||
133 | UserExitOnWin32Error(er, hr, "Failed to get list of global groups for user while checking group membership information for user: %ls", pwzName); | ||
134 | |||
135 | if (dwRead != dwTotal) | ||
136 | { | ||
137 | hr = HRESULT_FROM_WIN32(ERROR_MORE_DATA); | ||
138 | UserExitOnRootFailure(hr, "Failed to get entire list of groups (global) for user while checking group membership information for user: %ls", pwzName); | ||
139 | } | ||
140 | |||
141 | if (CheckIsMemberHelper(wzGroupUserDomain, pguiGroupData, dwRead)) | ||
142 | { | ||
143 | *lpfMember = TRUE; | ||
144 | ExitFunction1(hr = S_OK); | ||
145 | } | ||
146 | |||
147 | if (NULL != pguiGroupData) | ||
148 | { | ||
149 | ::NetApiBufferFree(pguiGroupData); | ||
150 | pguiGroupData = NULL; | ||
151 | } | ||
152 | |||
153 | // If we fail with the global groups, try again with the local groups | ||
154 | er = ::NetUserGetLocalGroups(NULL, wzUserDomain, 0, LG_INCLUDE_INDIRECT, (LPBYTE *)&pguiGroupData, MAX_PREFERRED_LENGTH, &dwRead, &dwTotal); | ||
155 | // Ignore these errors, and just go to the fallback checks | ||
156 | if (NERR_UserNotFound == er || NERR_DCNotFound == er || RPC_S_SERVER_UNAVAILABLE == er) | ||
157 | { | ||
158 | Trace(REPORT_VERBOSE, "failed to get local groups for user %ls from domain %ls with error code 0x%x - continuing", pwzName, (wz != NULL) ? wz : L"", HRESULT_FROM_WIN32(er)); | ||
159 | er = ERROR_SUCCESS; | ||
160 | } | ||
161 | UserExitOnWin32Error(er, hr, "Failed to get list of groups for user while checking group membership information for user: %ls", pwzName); | ||
162 | |||
163 | if (dwRead != dwTotal) | ||
164 | { | ||
165 | hr = HRESULT_FROM_WIN32(ERROR_MORE_DATA); | ||
166 | UserExitOnRootFailure(hr, "Failed to get entire list of groups (local) for user while checking group membership information for user: %ls", pwzName); | ||
167 | } | ||
168 | |||
169 | if (CheckIsMemberHelper(wzGroupUserDomain, pguiGroupData, dwRead)) | ||
170 | { | ||
171 | *lpfMember = TRUE; | ||
172 | ExitFunction1(hr = S_OK); | ||
173 | } | ||
174 | |||
175 | // If the above methods failed, let's try active directory | ||
176 | hr = UserCreateADsPath(pwzDomain, pwzName, &bstrUser); | ||
177 | UserExitOnFailure(hr, "failed to create user ADsPath in order to check group membership for group: %ls domain: %ls", pwzName, pwzDomain); | ||
178 | |||
179 | hr = UserCreateADsPath(pwzGroupDomain, pwzGroupName, &bstrGroup); | ||
180 | UserExitOnFailure(hr, "failed to create group ADsPath in order to check group membership for group: %ls domain: %ls", pwzGroupName, pwzGroupDomain); | ||
181 | |||
182 | if (lstrlenW(pwzGroupDomain) > 0) | ||
183 | { | ||
184 | hr = ::ADsGetObject(bstrGroup, IID_IADsGroup, reinterpret_cast<void**>(&pGroup)); | ||
185 | UserExitOnFailure(hr, "Failed to get group '%ls' from active directory.", reinterpret_cast<WCHAR*>(bstrGroup) ); | ||
186 | |||
187 | hr = pGroup->IsMember(bstrUser, &vtBoolResult); | ||
188 | UserExitOnFailure(hr, "Failed to check if user %ls is a member of group '%ls' using active directory.", reinterpret_cast<WCHAR*>(bstrUser), reinterpret_cast<WCHAR*>(bstrGroup) ); | ||
189 | } | ||
190 | |||
191 | if (vtBoolResult) | ||
192 | { | ||
193 | *lpfMember = TRUE; | ||
194 | ExitFunction1(hr = S_OK); | ||
195 | } | ||
196 | |||
197 | hr = ::ADsGetObject(bstrGroup, IID_IADsGroup, reinterpret_cast<void**>(&pGroup)); | ||
198 | UserExitOnFailure(hr, "Failed to get group '%ls' from active directory.", reinterpret_cast<WCHAR*>(bstrGroup) ); | ||
199 | |||
200 | hr = pGroup->IsMember(bstrUser, &vtBoolResult); | ||
201 | UserExitOnFailure(hr, "Failed to check if user %ls is a member of group '%ls' using active directory.", reinterpret_cast<WCHAR*>(bstrUser), reinterpret_cast<WCHAR*>(bstrGroup) ); | ||
202 | |||
203 | if (vtBoolResult) | ||
204 | { | ||
205 | *lpfMember = TRUE; | ||
206 | ExitFunction1(hr = S_OK); | ||
207 | } | ||
208 | |||
209 | LExit: | ||
210 | ReleaseObject(pGroup); | ||
211 | ReleaseBSTR(bstrUser); | ||
212 | ReleaseBSTR(bstrGroup); | ||
213 | |||
214 | if (NULL != pguiGroupData) | ||
215 | { | ||
216 | ::NetApiBufferFree(pguiGroupData); | ||
217 | } | ||
218 | |||
219 | return hr; | ||
220 | } | ||
221 | |||
222 | |||
223 | /******************************************************************* | ||
224 | Takes a domain and name, and allocates a BSTR which represents | ||
225 | DOMAIN\NAME's active directory path. The BSTR this function returns | ||
226 | should be released manually using the ReleaseBSTR() macro. | ||
227 | ********************************************************************/ | ||
228 | extern "C" HRESULT DAPI UserCreateADsPath( | ||
229 | __in_z LPCWSTR wzObjectDomain, | ||
230 | __in_z LPCWSTR wzObjectName, | ||
231 | __out BSTR *pbstrAdsPath | ||
232 | ) | ||
233 | { | ||
234 | Assert(wzObjectDomain && wzObjectName && *wzObjectName); | ||
235 | |||
236 | HRESULT hr = S_OK; | ||
237 | LPWSTR pwzAdsPath = NULL; | ||
238 | |||
239 | hr = StrAllocString(&pwzAdsPath, L"WinNT://", 0); | ||
240 | UserExitOnFailure(hr, "failed to allocate AdsPath string"); | ||
241 | |||
242 | if (*wzObjectDomain) | ||
243 | { | ||
244 | hr = StrAllocFormatted(&pwzAdsPath, L"%s/%s", wzObjectDomain, wzObjectName); | ||
245 | UserExitOnFailure(hr, "failed to allocate AdsPath string"); | ||
246 | } | ||
247 | else if (NULL != wcsstr(wzObjectName, L"\\") || NULL != wcsstr(wzObjectName, L"/")) | ||
248 | { | ||
249 | hr = StrAllocConcat(&pwzAdsPath, wzObjectName, 0); | ||
250 | UserExitOnFailure(hr, "failed to concat objectname: %ls", wzObjectName); | ||
251 | } | ||
252 | else | ||
253 | { | ||
254 | hr = StrAllocConcat(&pwzAdsPath, L"Localhost/", 0); | ||
255 | UserExitOnFailure(hr, "failed to concat LocalHost/"); | ||
256 | |||
257 | hr = StrAllocConcat(&pwzAdsPath, wzObjectName, 0); | ||
258 | UserExitOnFailure(hr, "failed to concat object name: %ls", wzObjectName); | ||
259 | } | ||
260 | |||
261 | *pbstrAdsPath = ::SysAllocString(pwzAdsPath); | ||
262 | if (NULL == *pbstrAdsPath) | ||
263 | { | ||
264 | hr = E_OUTOFMEMORY; | ||
265 | } | ||
266 | |||
267 | LExit: | ||
268 | ReleaseStr(pwzAdsPath); | ||
269 | |||
270 | return hr; | ||
271 | } | ||
272 | |||
273 | |||
274 | /******************************************************************* | ||
275 | Helper function to check if pwzGroupUserDomain (in form of "domain\username" is | ||
276 | a member of a given LOCALGROUP_USERS_INFO_0 structure. Useful to pass in the | ||
277 | output from both NetUserGetGroups() and NetUserGetLocalGroups() | ||
278 | ********************************************************************/ | ||
279 | static BOOL CheckIsMemberHelper( | ||
280 | __in_z LPCWSTR pwzGroupUserDomain, | ||
281 | __in_ecount(cguiGroupData) const GROUP_USERS_INFO_0 *pguiGroupData, | ||
282 | __in DWORD cguiGroupData | ||
283 | ) | ||
284 | { | ||
285 | if (NULL == pguiGroupData) | ||
286 | { | ||
287 | return FALSE; | ||
288 | } | ||
289 | |||
290 | for (DWORD dwCounter = 0; dwCounter < cguiGroupData; ++dwCounter) | ||
291 | { | ||
292 | // If the user is a member of the group, set the output flag to true | ||
293 | if (0 == lstrcmpiW(pwzGroupUserDomain, pguiGroupData[dwCounter].grui0_name)) | ||
294 | { | ||
295 | return TRUE; | ||
296 | } | ||
297 | } | ||
298 | |||
299 | return FALSE; | ||
300 | } | ||