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