Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

383 lines
8.8 KiB

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright (c) 2000, Microsoft Corp. All rights reserved.
  4. //
  5. // FILE
  6. //
  7. // ntsamuser.cpp
  8. //
  9. // SYNOPSIS
  10. //
  11. // Defines the class AccountValidation.
  12. //
  13. ///////////////////////////////////////////////////////////////////////////////
  14. #include <ias.h>
  15. #include <ntsamuser.h>
  16. #include <autohdl.h>
  17. #include <iaslsa.h>
  18. #include <iasntds.h>
  19. #include <iastlutl.h>
  20. #include <lmaccess.h>
  21. #include <samutil.h>
  22. #include <sdoias.h>
  23. //////////
  24. // Attributes that should be retrieved for each user.
  25. //////////
  26. const PCWSTR PER_USER_ATTRS[] =
  27. {
  28. L"userAccountControl",
  29. L"accountExpires",
  30. L"logonHours",
  31. L"tokenGroups",
  32. L"objectSid",
  33. NULL
  34. };
  35. //////////
  36. //
  37. // Process an LDAP response.
  38. //
  39. // Based on the empirical evidence, it seems that userAccountControl and
  40. // accountExpires are always present while logonHours is optional. However,
  41. // none of these attributes are marked as mandatory in the LDAP schema. Since
  42. // we have already done a rudimentary access check at bind, we will allow any
  43. // of these attributes to be absent.
  44. //
  45. //////////
  46. inline
  47. DWORD
  48. ValidateLdapResponse(
  49. IASTL::IASRequest& request,
  50. LDAPMessage* msg
  51. )
  52. {
  53. // Retrieve the connection for this message.
  54. LDAP* ld = ldap_conn_from_msg(NULL, msg);
  55. PWCHAR *str;
  56. PLDAP_BERVAL *data1, *data2;
  57. // There is exactly one entry.
  58. LDAPMessage* e = ldap_first_entry(ld, msg);
  59. //////////
  60. // Check the UserAccountControl flags.
  61. //////////
  62. ULONG userAccountControl;
  63. str = ldap_get_valuesW(ld, e, L"userAccountControl");
  64. if (str)
  65. {
  66. userAccountControl = (ULONG)_wtoi64(*str);
  67. ldap_value_freeW(str);
  68. if (userAccountControl & UF_ACCOUNTDISABLE)
  69. {
  70. return ERROR_ACCOUNT_DISABLED;
  71. }
  72. if (userAccountControl & UF_LOCKOUT)
  73. {
  74. return ERROR_ACCOUNT_LOCKED_OUT;
  75. }
  76. }
  77. //////////
  78. // Retrieve AccountExpires.
  79. //////////
  80. LARGE_INTEGER accountExpires;
  81. str = ldap_get_valuesW(ld, e, L"accountExpires");
  82. if (str)
  83. {
  84. accountExpires.QuadPart = _wtoi64(*str);
  85. ldap_value_freeW(str);
  86. }
  87. else
  88. {
  89. accountExpires.QuadPart = 0;
  90. }
  91. //////////
  92. // Retrieve LogonHours.
  93. //////////
  94. IAS_LOGON_HOURS logonHours;
  95. data1 = ldap_get_values_lenW(ld, e, L"logonHours");
  96. if (data1 != NULL)
  97. {
  98. logonHours.UnitsPerWeek = 8 * (USHORT)data1[0]->bv_len;
  99. logonHours.LogonHours = (PUCHAR)data1[0]->bv_val;
  100. }
  101. else
  102. {
  103. logonHours.UnitsPerWeek = 0;
  104. logonHours.LogonHours = NULL;
  105. }
  106. //////////
  107. // Check the account restrictions.
  108. //////////
  109. DWORD status;
  110. status = IASCheckAccountRestrictions(
  111. &accountExpires,
  112. &logonHours
  113. );
  114. ldap_value_free_len(data1);
  115. if (status != NO_ERROR) { return status; }
  116. //////////
  117. // Retrieve tokenGroups and objectSid.
  118. //////////
  119. data1 = ldap_get_values_lenW(ld, e, L"tokenGroups");
  120. data2 = ldap_get_values_lenW(ld, e, L"objectSid");
  121. PTOKEN_GROUPS allGroups;
  122. ULONG length;
  123. if (data1 && data2)
  124. {
  125. // Allocate memory for the TOKEN_GROUPS struct.
  126. ULONG numGroups = ldap_count_values_len(data1);
  127. PTOKEN_GROUPS tokenGroups =
  128. (PTOKEN_GROUPS)_alloca(
  129. FIELD_OFFSET(TOKEN_GROUPS, Groups) +
  130. sizeof(SID_AND_ATTRIBUTES) * numGroups
  131. );
  132. // Store the number of groups.
  133. tokenGroups->GroupCount = numGroups;
  134. // Store the group SIDs.
  135. for (ULONG i = 0; i < numGroups; ++i)
  136. {
  137. tokenGroups->Groups[i].Sid = (PSID)data1[i]->bv_val;
  138. tokenGroups->Groups[i].Attributes = SE_GROUP_ENABLED;
  139. }
  140. // Expand the group membership locally.
  141. status = IASGetAliasMembership(
  142. (PSID)data2[0]->bv_val,
  143. tokenGroups,
  144. CoTaskMemAlloc,
  145. &allGroups,
  146. &length
  147. );
  148. }
  149. else
  150. {
  151. status = ERROR_ACCESS_DENIED;
  152. }
  153. ldap_value_free_len(data1);
  154. ldap_value_free_len(data2);
  155. if (status != NO_ERROR) { return status; }
  156. //////////
  157. // Initialize and store the attribute.
  158. //////////
  159. IASTL::IASAttribute attr(true);
  160. attr->dwId = IAS_ATTRIBUTE_TOKEN_GROUPS;
  161. attr->Value.itType = IASTYPE_OCTET_STRING;
  162. attr->Value.OctetString.dwLength = length;
  163. attr->Value.OctetString.lpValue = (PBYTE)allGroups;
  164. attr.store(request);
  165. return NO_ERROR;
  166. }
  167. HRESULT AccountValidation::Initialize()
  168. {
  169. return IASNtdsInitialize();
  170. }
  171. HRESULT AccountValidation::Shutdown() throw ()
  172. {
  173. IASNtdsUninitialize();
  174. return S_OK;
  175. }
  176. IASREQUESTSTATUS AccountValidation::onSyncRequest(IRequest* pRequest) throw ()
  177. {
  178. try
  179. {
  180. IASTL::IASRequest request(pRequest);
  181. //////////
  182. // Only process requests that don't have Token-Groups already.
  183. //////////
  184. IASTL::IASAttribute tokenGroups;
  185. if (!tokenGroups.load(
  186. request,
  187. IAS_ATTRIBUTE_TOKEN_GROUPS,
  188. IASTYPE_OCTET_STRING
  189. ))
  190. {
  191. //////////
  192. // Extract the NT4-Account-Name attribute.
  193. //////////
  194. IASTL::IASAttribute identity;
  195. if (identity.load(
  196. request,
  197. IAS_ATTRIBUTE_NT4_ACCOUNT_NAME,
  198. IASTYPE_STRING
  199. ))
  200. {
  201. //////////
  202. // Convert the User-Name to SAM format.
  203. //////////
  204. PCWSTR domain, username;
  205. EXTRACT_SAM_IDENTITY(identity->Value.String, domain, username);
  206. IASTracePrintf(
  207. "Validating Windows account %S\\%S.",
  208. domain,
  209. username
  210. );
  211. //////////
  212. // Validate the account.
  213. //////////
  214. if (!tryNativeMode(request, domain, username))
  215. {
  216. doDownlevel(request, domain, username);
  217. }
  218. }
  219. }
  220. }
  221. catch (const _com_error& ce)
  222. {
  223. IASTraceExcept();
  224. IASProcessFailure(pRequest, ce.Error());
  225. }
  226. return IAS_REQUEST_STATUS_CONTINUE;
  227. }
  228. void AccountValidation::doDownlevel(
  229. IASTL::IASRequest& request,
  230. PCWSTR domainName,
  231. PCWSTR username
  232. )
  233. {
  234. IASTraceString("Using downlevel APIs to validate account.");
  235. //////////
  236. // Inject the user's groups.
  237. //////////
  238. IASTL::IASAttribute groups(true);
  239. DWORD status;
  240. status = IASGetGroupsForUser(
  241. username,
  242. domainName,
  243. &CoTaskMemAlloc,
  244. (PTOKEN_GROUPS*)&groups->Value.OctetString.lpValue,
  245. &groups->Value.OctetString.dwLength
  246. );
  247. if (status == NO_ERROR)
  248. {
  249. // Insert the groups.
  250. groups->dwId = IAS_ATTRIBUTE_TOKEN_GROUPS;
  251. groups->Value.itType = IASTYPE_OCTET_STRING;
  252. groups.store(request);
  253. IASTraceString("Successfully validated account.");
  254. }
  255. else
  256. {
  257. IASTraceFailure("IASGetGroupsForUser", status);
  258. status = IASMapWin32Error(status, IAS_SERVER_UNAVAILABLE);
  259. IASProcessFailure(request, status);
  260. }
  261. }
  262. bool AccountValidation::tryNativeMode(
  263. IASTL::IASRequest& request,
  264. PCWSTR domainName,
  265. PCWSTR username
  266. )
  267. {
  268. //////////
  269. // Only handle domain users.
  270. //////////
  271. if (IASGetRole() != IAS_ROLE_DC && IASIsDomainLocal(domainName))
  272. {
  273. return false;
  274. }
  275. //////////
  276. // Query the DS.
  277. //////////
  278. DWORD error;
  279. auto_handle< PLDAPMessage,
  280. ULONG (LDAPAPI*)(PLDAPMessage),
  281. &ldap_msgfree
  282. > res;
  283. error = IASNtdsQueryUserAttributes(
  284. domainName,
  285. username,
  286. LDAP_SCOPE_BASE,
  287. const_cast<PWCHAR*>(PER_USER_ATTRS),
  288. &res
  289. );
  290. switch (error)
  291. {
  292. case NO_ERROR:
  293. {
  294. // We got something back, so validate the response.
  295. error = ValidateLdapResponse(request, res);
  296. if (error == NO_ERROR)
  297. {
  298. IASTraceString("Successfully validated account.");
  299. return true;
  300. }
  301. IASTraceFailure("ValidateLdapResponse", error);
  302. break;
  303. }
  304. case ERROR_DS_NOT_INSTALLED:
  305. case ERROR_INVALID_DOMAIN_ROLE:
  306. {
  307. // No DS, so we can't handle.
  308. return false;
  309. }
  310. default:
  311. {
  312. // We have a DS for this user, but we can't talk to it.
  313. break;
  314. }
  315. }
  316. IASProcessFailure(
  317. request,
  318. IASMapWin32Error(error, IAS_DOMAIN_UNAVAILABLE)
  319. );
  320. return true;
  321. }