1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Diagnostics; 7 using System.Collections; 8 using System.Collections.Generic; 9 using System.Globalization; 10 using System.Runtime.InteropServices; 11 using System.Net; 12 using System.Security.Principal; 13 using System.Security.Permissions; 14 using System.Collections.Specialized; 15 using System.DirectoryServices; 16 using System.Text; 17 using MACLPrinc = System.Security.Principal; 18 using System.Security.AccessControl; 19 using System.DirectoryServices.ActiveDirectory; 20 21 namespace System.DirectoryServices.AccountManagement 22 { 23 internal partial class ADStoreCtx : StoreCtx 24 { 25 protected DirectoryEntry ctxBase; 26 private const int mappingIndex = 0; 27 28 private object _ctxBaseLock = new object(); // when mutating ctxBase 29 30 private bool _ownCtxBase; // if true, we "own" ctxBase and must Dispose of it when we're done 31 32 private bool _disposed = false; 33 34 protected internal NetCred Credentials { get { return this.credentials; } } 35 protected NetCred credentials = null; 36 37 protected internal AuthenticationTypes AuthTypes { get { return this.authTypes; } } 38 protected AuthenticationTypes authTypes; 39 40 protected ContextOptions contextOptions; 41 InitializeNewDirectoryOptions(DirectoryEntry newDeChild)42 protected internal virtual void InitializeNewDirectoryOptions(DirectoryEntry newDeChild) 43 { 44 } 45 46 // 47 // Static constructor: used for initializing static tables 48 // ADStoreCtx()49 static ADStoreCtx() 50 { 51 // 52 // Load the filterPropertiesTable 53 // 54 LoadFilterMappingTable(mappingIndex, s_filterPropertiesTableRaw); 55 LoadPropertyMappingTable(mappingIndex, s_propertyMappingTableRaw); 56 } 57 58 protected virtual int MappingTableIndex 59 { 60 get 61 { 62 return mappingIndex; 63 } 64 } 65 LoadFilterMappingTable(int mappingIndex, object[,] rawFilterPropertiesTable)66 protected static void LoadFilterMappingTable(int mappingIndex, object[,] rawFilterPropertiesTable) 67 { 68 if (null == s_filterPropertiesTable) 69 s_filterPropertiesTable = new Hashtable(); 70 71 Hashtable mappingTable = new Hashtable(); 72 73 for (int i = 0; i < rawFilterPropertiesTable.GetLength(0); i++) 74 { 75 Type qbeType = rawFilterPropertiesTable[i, 0] as Type; 76 string adPropertyName = rawFilterPropertiesTable[i, 1] as string; 77 FilterConverterDelegate f = rawFilterPropertiesTable[i, 2] as FilterConverterDelegate; 78 79 Debug.Assert(qbeType != null); 80 Debug.Assert(f != null); 81 82 // There should only be one entry per QBE type 83 Debug.Assert(mappingTable[qbeType] == null); 84 85 FilterPropertyTableEntry entry = new FilterPropertyTableEntry(); 86 entry.suggestedADPropertyName = adPropertyName; 87 entry.converter = f; 88 89 mappingTable[qbeType] = entry; 90 } 91 92 s_filterPropertiesTable.Add(mappingIndex, mappingTable); 93 } 94 LoadPropertyMappingTable(int mappingIndex, object[,] rawPropertyMappingTable)95 protected static void LoadPropertyMappingTable(int mappingIndex, object[,] rawPropertyMappingTable) 96 { 97 // 98 // Load the propertyMappingTableByProperty and propertyMappingTableByLDAP tables 99 // 100 if (null == s_propertyMappingTableByProperty) 101 s_propertyMappingTableByProperty = new Hashtable(); 102 103 if (null == s_propertyMappingTableByLDAP) 104 s_propertyMappingTableByLDAP = new Hashtable(); 105 106 if (null == s_propertyMappingTableByPropertyFull) 107 s_propertyMappingTableByPropertyFull = new Hashtable(); 108 109 if (null == TypeToLdapPropListMap) 110 TypeToLdapPropListMap = new Dictionary<int, Dictionary<Type, StringCollection>>(); 111 112 Hashtable mappingTableByProperty = new Hashtable(); 113 Hashtable mappingTableByLDAP = new Hashtable(); 114 Hashtable mappingTableByPropertyFull = new Hashtable(); 115 116 Dictionary<string, string[]> propertyNameToLdapAttr = new Dictionary<string, string[]>(); 117 118 Dictionary<Type, StringCollection> TypeToLdapDict = new Dictionary<Type, StringCollection>(); 119 120 for (int i = 0; i < s_propertyMappingTableRaw.GetLength(0); i++) 121 { 122 string propertyName = rawPropertyMappingTable[i, 0] as string; 123 string ldapAttribute = rawPropertyMappingTable[i, 1] as string; 124 FromLdapConverterDelegate fromLdap = rawPropertyMappingTable[i, 2] as FromLdapConverterDelegate; 125 ToLdapConverterDelegate toLdap = rawPropertyMappingTable[i, 3] as ToLdapConverterDelegate; 126 127 Debug.Assert(propertyName != null); 128 Debug.Assert((ldapAttribute != null && fromLdap != null) || (fromLdap == null)); 129 //Debug.Assert(toLdap != null); 130 131 // Build the table entry. The same entry will be used in both tables. 132 // Once constructed, the table entries are treated as read-only, so there's 133 // no danger in sharing the entries between tables. 134 PropertyMappingTableEntry propertyEntry = new PropertyMappingTableEntry(); 135 propertyEntry.propertyName = propertyName; 136 propertyEntry.suggestedADPropertyName = ldapAttribute; 137 propertyEntry.ldapToPapiConverter = fromLdap; 138 propertyEntry.papiToLdapConverter = toLdap; 139 140 // Build a mapping table from PAPI propertyname to ldapAttribute that we can use below 141 // to build a list of ldap attributes for each object type. 142 if (null != ldapAttribute) 143 { 144 if (propertyNameToLdapAttr.ContainsKey(propertyName)) 145 { 146 string[] props = new string[propertyNameToLdapAttr[propertyName].Length + 1]; 147 propertyNameToLdapAttr[propertyName].CopyTo(props, 0); 148 props[propertyNameToLdapAttr[propertyName].Length] = ldapAttribute; 149 propertyNameToLdapAttr[propertyName] = props; 150 } 151 else 152 propertyNameToLdapAttr.Add(propertyName, new string[] { ldapAttribute }); 153 } 154 155 // propertyMappingTableByProperty 156 // If toLdap is null, there's no PAPI->LDAP mapping for this property 157 // (it's probably read-only, e.g., "lastLogon"). 158 if (toLdap != null) 159 { 160 if (mappingTableByProperty[propertyName] == null) 161 mappingTableByProperty[propertyName] = new ArrayList(); 162 163 ((ArrayList)mappingTableByProperty[propertyName]).Add(propertyEntry); 164 } 165 166 if (mappingTableByPropertyFull[propertyName] == null) 167 mappingTableByPropertyFull[propertyName] = new ArrayList(); 168 169 ((ArrayList)mappingTableByPropertyFull[propertyName]).Add(propertyEntry); 170 171 // mappingTableByLDAP 172 // If fromLdap is null, there's no direct LDAP->PAPI mapping for this property. 173 // It's probably a property that requires custom handling, such as IdentityClaim. 174 if (fromLdap != null) 175 { 176 string ldapAttributeLower = ldapAttribute.ToLower(CultureInfo.InvariantCulture); 177 178 if (mappingTableByLDAP[ldapAttributeLower] == null) 179 mappingTableByLDAP[ldapAttributeLower] = new ArrayList(); 180 181 ((ArrayList)mappingTableByLDAP[ldapAttributeLower]).Add(propertyEntry); 182 } 183 } 184 185 s_propertyMappingTableByProperty.Add(mappingIndex, mappingTableByProperty); 186 s_propertyMappingTableByLDAP.Add(mappingIndex, mappingTableByLDAP); 187 s_propertyMappingTableByPropertyFull.Add(mappingIndex, mappingTableByPropertyFull); 188 189 // Build a table of Type mapped to a collection of all ldap attributes for that type. 190 // This table will be used to load the objects when searching. 191 192 StringCollection principalPropList = new StringCollection(); 193 StringCollection authPrincipalPropList = new StringCollection(); 194 StringCollection userPrincipalPropList = new StringCollection(); 195 StringCollection computerPrincipalPropList = new StringCollection(); 196 StringCollection groupPrincipalPropList = new StringCollection(); 197 198 foreach (string prop in principalProperties) 199 { 200 string[] attr; 201 if (propertyNameToLdapAttr.TryGetValue(prop, out attr)) 202 { 203 foreach (string plist in attr) 204 { 205 principalPropList.Add(plist); 206 authPrincipalPropList.Add(plist); 207 userPrincipalPropList.Add(plist); 208 computerPrincipalPropList.Add(plist); 209 groupPrincipalPropList.Add(plist); 210 } 211 } 212 } 213 214 foreach (string prop in authenticablePrincipalProperties) 215 { 216 string[] attr; 217 if (propertyNameToLdapAttr.TryGetValue(prop, out attr)) 218 { 219 foreach (string plist in attr) 220 { 221 authPrincipalPropList.Add(plist); 222 userPrincipalPropList.Add(plist); 223 computerPrincipalPropList.Add(plist); 224 } 225 } 226 } 227 228 foreach (string prop in groupProperties) 229 { 230 string[] attr; 231 if (propertyNameToLdapAttr.TryGetValue(prop, out attr)) 232 { 233 foreach (string plist in attr) 234 { 235 groupPrincipalPropList.Add(plist); 236 } 237 } 238 } 239 240 foreach (string prop in userProperties) 241 { 242 string[] attr; 243 if (propertyNameToLdapAttr.TryGetValue(prop, out attr)) 244 { 245 foreach (string plist in attr) 246 { 247 userPrincipalPropList.Add(plist); 248 } 249 } 250 } 251 252 foreach (string prop in computerProperties) 253 { 254 string[] attr; 255 if (propertyNameToLdapAttr.TryGetValue(prop, out attr)) 256 { 257 foreach (string plist in attr) 258 { 259 computerPrincipalPropList.Add(plist); 260 } 261 } 262 } 263 264 principalPropList.Add("objectClass"); 265 authPrincipalPropList.Add("objectClass"); 266 userPrincipalPropList.Add("objectClass"); 267 computerPrincipalPropList.Add("objectClass"); 268 groupPrincipalPropList.Add("objectClass"); 269 270 TypeToLdapDict.Add(typeof(Principal), principalPropList); 271 TypeToLdapDict.Add(typeof(GroupPrincipal), groupPrincipalPropList); 272 TypeToLdapDict.Add(typeof(AuthenticablePrincipal), authPrincipalPropList); 273 TypeToLdapDict.Add(typeof(UserPrincipal), userPrincipalPropList); 274 TypeToLdapDict.Add(typeof(ComputerPrincipal), computerPrincipalPropList); 275 276 TypeToLdapPropListMap.Add(mappingIndex, TypeToLdapDict); 277 } 278 279 // 280 // Constructor 281 // 282 283 // Throws ArgumentException if base is not a container class (as indicated by an empty possibleInferiors 284 // attribute in the corresponding schema class definition) ADStoreCtx(DirectoryEntry ctxBase, bool ownCtxBase, string username, string password, ContextOptions options)285 public ADStoreCtx(DirectoryEntry ctxBase, bool ownCtxBase, string username, string password, ContextOptions options) 286 { 287 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Constructing ADStoreCtx for {0}", ctxBase.Path); 288 289 Debug.Assert(ctxBase != null); 290 291 // This will also detect if the server is down or nonexistent 292 if (!IsContainer(ctxBase)) 293 throw new InvalidOperationException(SR.ADStoreCtxMustBeContainer); 294 295 this.ctxBase = ctxBase; 296 _ownCtxBase = ownCtxBase; 297 298 if (username != null && password != null) 299 this.credentials = new NetCred(username, password); 300 301 this.contextOptions = options; 302 this.authTypes = SDSUtils.MapOptionsToAuthTypes(options); 303 } 304 IsContainer(DirectoryEntry de)305 protected bool IsContainer(DirectoryEntry de) 306 { 307 //NOTE: Invoking de.SchemaEntry creates a new DirectoryEntry object, which is not disposed by de. 308 using (DirectoryEntry schemaDE = de.SchemaEntry) 309 { 310 if (schemaDE.Properties["possibleInferiors"].Count == 0) 311 { 312 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "IsContainer: not a container ({0})", schemaDE.Path); 313 return false; 314 } 315 return true; 316 } 317 } 318 319 // 320 // IDisposable implementation 321 // 322 Dispose()323 public override void Dispose() 324 { 325 try 326 { 327 if (!_disposed) 328 { 329 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Dispose: disposing, ownCtxBase={0}", _ownCtxBase); 330 331 if (_ownCtxBase) 332 ctxBase.Dispose(); 333 334 _disposed = true; 335 } 336 } 337 finally 338 { 339 base.Dispose(); 340 } 341 } 342 343 // 344 // StoreCtx information 345 // 346 347 // Retrieves the Path (ADsPath) of the object used as the base of the StoreCtx 348 internal override string BasePath 349 { 350 get 351 { 352 Debug.Assert(this.ctxBase != null); 353 return this.ctxBase.Path; 354 } 355 } 356 357 // 358 // CRUD 359 // 360 361 // Used to perform the specified operation on the Principal. 362 // 363 // Insert() and Update() must check to make sure no properties not supported by this StoreCtx 364 // have been set, prior to persisting the Principal. Insert(Principal p)365 internal override void Insert(Principal p) 366 { 367 try 368 { 369 Debug.Assert(p.unpersisted == true); 370 Debug.Assert(p.fakePrincipal == false); 371 372 // Insert the principal into the store 373 SDSUtils.InsertPrincipal( 374 p, 375 this, 376 new SDSUtils.GroupMembershipUpdater(UpdateGroupMembership), 377 this.credentials, 378 this.authTypes, 379 true 380 ); 381 382 // Load in all the initial values from the store 383 //((DirectoryEntry)p.UnderlyingObject).RefreshCache(); 384 LoadDirectoryEntryAttributes((DirectoryEntry)p.UnderlyingObject); 385 386 // If they set p.Enabled == true, enable the principal 387 EnablePrincipalIfNecessary(p); 388 389 // If they set CannotChangePassword then we need to set it here after the object is already created. 390 SetPasswordSecurityifNeccessary(p); 391 392 // Load in the StoreKey 393 Debug.Assert(p.Key == null); // since it was previously unpersisted 394 395 Debug.Assert(p.UnderlyingObject != null); // since we just persisted it 396 Debug.Assert(p.UnderlyingObject is DirectoryEntry); 397 398 ADStoreKey key = new ADStoreKey(((DirectoryEntry)p.UnderlyingObject).Guid); 399 p.Key = key; 400 401 // Reset the change tracking 402 p.ResetAllChangeStatus(); 403 404 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Insert: new GUID is ", ((DirectoryEntry)p.UnderlyingObject).Guid); 405 } 406 catch (PrincipalExistsException) 407 { 408 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Insert, object already exists"); 409 throw; 410 } 411 catch (System.SystemException e) 412 { 413 try 414 { 415 GlobalDebug.WriteLineIf(GlobalDebug.Error, "ADStoreCtx", "Insert, Save Failed (attempting to delete) Exception {0} ", e.Message); 416 if (null != p.UnderlyingObject) 417 { 418 SDSUtils.DeleteDirectoryEntry((DirectoryEntry)p.UnderlyingObject); 419 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Insert, object deleted"); 420 } 421 else 422 { 423 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Insert, No object was created nothing to delete"); 424 } 425 } 426 catch (System.Runtime.InteropServices.COMException deleteFail) 427 { 428 // The delete failed. Just continue we will throw the original exception below. 429 GlobalDebug.WriteLineIf(GlobalDebug.Error, "ADStoreCtx", "Insert, Deletion Failed {0} ", deleteFail.Message); 430 } 431 432 if (e is System.Runtime.InteropServices.COMException) 433 throw ExceptionHelper.GetExceptionFromCOMException((System.Runtime.InteropServices.COMException)e); 434 else 435 throw e; 436 } 437 } 438 AccessCheck(Principal p, PrincipalAccessMask targetPermission)439 internal override bool AccessCheck(Principal p, PrincipalAccessMask targetPermission) 440 { 441 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "AccessCheck " + targetPermission.ToString()); 442 443 switch (targetPermission) 444 { 445 case PrincipalAccessMask.ChangePassword: 446 447 return CannotChangePwdFromLdapConverter((DirectoryEntry)p.GetUnderlyingObject()); 448 449 default: 450 451 GlobalDebug.WriteLineIf(GlobalDebug.Error, "ADStoreCtx", "Invalid targetPermission in AccessCheck"); 452 453 break; 454 } 455 456 return false; 457 } 458 459 /// <summary> 460 /// If The enabled property was set on the principal then perform actions 461 /// necessary on the principal to set the enabled status to match 462 /// the set value. 463 /// </summary> 464 /// <param name="p"></param> EnablePrincipalIfNecessary(Principal p)465 private void EnablePrincipalIfNecessary(Principal p) 466 { 467 if (p.GetChangeStatusForProperty(PropertyNames.AuthenticablePrincipalEnabled)) 468 { 469 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "EnablePrincipalIfNecessary: enabling principal"); 470 471 Debug.Assert(p is AuthenticablePrincipal); 472 473 bool enable = (bool)p.GetValueForProperty(PropertyNames.AuthenticablePrincipalEnabled); 474 475 SetAuthPrincipalEnableStatus((AuthenticablePrincipal)p, enable); 476 } 477 } 478 SetPasswordSecurityifNeccessary(Principal p)479 private void SetPasswordSecurityifNeccessary(Principal p) 480 { 481 if (p.GetChangeStatusForProperty(PropertyNames.PwdInfoCannotChangePassword)) 482 { 483 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "EnablePrincipalIfNecessary: enabling principal"); 484 485 Debug.Assert(p is AuthenticablePrincipal); 486 487 SetCannotChangePasswordStatus((AuthenticablePrincipal)p, (bool)p.GetValueForProperty(PropertyNames.PwdInfoCannotChangePassword), true); 488 } 489 } 490 SetCannotChangePasswordStatus(Principal ap, bool userCannotChangePassword, bool commitChanges)491 private static void SetCannotChangePasswordStatus(Principal ap, bool userCannotChangePassword, bool commitChanges) 492 { 493 Debug.Assert(ap is AuthenticablePrincipal); 494 Debug.Assert(ap.GetUnderlyingObject() is DirectoryEntry); 495 496 DirectoryEntry de = (DirectoryEntry)ap.GetUnderlyingObject(); 497 // retrieving ObjectSecurity after 498 // previously modifying the ACL will return null unless we force a cache refresh. We have to do this always, 499 // even before we call ObjectSecurity to see if it would return null, because once ObjectSecurity returns null the 500 // first time, it'll keep returning null even if we refresh the cache. 501 if (!de.Properties.Contains("nTSecurityDescriptor")) 502 de.RefreshCache(new string[] { "nTSecurityDescriptor" }); 503 ActiveDirectorySecurity adsSecurity = de.ObjectSecurity; 504 505 bool denySelfFound; 506 bool denyWorldFound; 507 bool allowSelfFound; 508 bool allowWorldFound; 509 510 // Scan the existing ACL to determine its current state 511 512 ScanACLForChangePasswordRight(adsSecurity, out denySelfFound, out denyWorldFound, out allowSelfFound, out allowWorldFound); 513 514 // Build the ACEs that we'll use 515 ActiveDirectoryAccessRule denySelfACE = new ExtendedRightAccessRule( 516 new MACLPrinc.SecurityIdentifier(SelfSddl), 517 AccessControlType.Deny, 518 s_changePasswordGuid); 519 520 ActiveDirectoryAccessRule denyWorldAce = new ExtendedRightAccessRule( 521 new MACLPrinc.SecurityIdentifier(WorldSddl), 522 AccessControlType.Deny, 523 s_changePasswordGuid); 524 525 ActiveDirectoryAccessRule allowSelfACE = new ExtendedRightAccessRule( 526 new MACLPrinc.SecurityIdentifier(SelfSddl), 527 AccessControlType.Allow, 528 s_changePasswordGuid); 529 530 ActiveDirectoryAccessRule allowWorldAce = new ExtendedRightAccessRule( 531 new MACLPrinc.SecurityIdentifier(WorldSddl), 532 AccessControlType.Allow, 533 s_changePasswordGuid); 534 535 // Based on the current state of the ACL and the userCannotChangePassword status, perform the necessary modifications, 536 // if any 537 if (userCannotChangePassword) 538 { 539 // If we want to make it so the user cannot change their password, we need to remove the ALLOW ACEs 540 // (if they exist) and add the necessary explicit DENY ACEs if they don't already exist. 541 542 if (!denySelfFound) 543 { 544 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: add deny self"); 545 adsSecurity.AddAccessRule(denySelfACE); 546 } 547 548 if (!denyWorldFound) 549 { 550 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: add deny world"); 551 adsSecurity.AddAccessRule(denyWorldAce); 552 } 553 554 if (allowSelfFound) 555 { 556 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: remove allow self"); 557 adsSecurity.RemoveAccessRuleSpecific(allowSelfACE); 558 } 559 560 if (allowWorldFound) 561 { 562 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: remove allow world"); 563 adsSecurity.RemoveAccessRuleSpecific(allowWorldAce); 564 } 565 } 566 else 567 { 568 // If we want to make to give the user back the right to change their password, we need to remove 569 // the explicit DENY ACEs if they exist. We'll also add in explicit ALLOW ACEs. 570 571 if (denySelfFound) 572 { 573 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: remove deny self"); 574 adsSecurity.RemoveAccessRuleSpecific(denySelfACE); 575 } 576 577 if (denyWorldFound) 578 { 579 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: remove deny world"); 580 adsSecurity.RemoveAccessRuleSpecific(denyWorldAce); 581 } 582 583 if (!allowSelfFound) 584 { 585 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: add allow self"); 586 adsSecurity.AddAccessRule(allowSelfACE); 587 } 588 589 if (!allowWorldFound) 590 { 591 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: add allow world"); 592 adsSecurity.AddAccessRule(allowWorldAce); 593 } 594 } 595 596 if (commitChanges) 597 de.CommitChanges(); 598 } 599 /// <summary> 600 /// Read the Account Control From the Directory entry. If the control is read then set or 601 /// clear bit 0x2 corresponding to the enable parameter 602 /// </summary> 603 /// <param name="ap">Principal to modify</param> 604 /// <param name="enable">New state of the enable bit</param> 605 /// 606 SetAuthPrincipalEnableStatus(AuthenticablePrincipal ap, bool enable)607 protected virtual void SetAuthPrincipalEnableStatus(AuthenticablePrincipal ap, bool enable) 608 { 609 try 610 { 611 Debug.Assert(ap.fakePrincipal == false); 612 613 int uacValue; 614 615 DirectoryEntry de = (DirectoryEntry)ap.UnderlyingObject; 616 617 if (de.Properties["userAccountControl"].Count > 0) 618 { 619 Debug.Assert(de.Properties["userAccountControl"].Count == 1); 620 621 uacValue = (int)de.Properties["userAccountControl"][0]; 622 } 623 else 624 { 625 // Since we loaded the properties, we should have it. Perhaps we don't have access 626 // to it. In that case, we don't want to blindly overwrite whatever other bits might be there. 627 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "SetAuthPrincipalEnableStatus: can't read userAccountControl"); 628 629 throw new PrincipalOperationException( 630 SR.ADStoreCtxUnableToReadExistingAccountControlFlagsToEnable); 631 } 632 633 if (enable && ((uacValue & 0x2) != 0)) 634 { 635 // It's currently disabled, and we need to enable it 636 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "SetAuthPrincipalEnableStatus: Enabling (old uac={0})", uacValue); 637 638 Utils.ClearBit(ref uacValue, 0x2); // UF_ACCOUNTDISABLE 639 640 WriteAttribute(ap, "userAccountControl", uacValue); 641 } 642 else if (!enable && ((uacValue & 0x2) == 0)) 643 { 644 // It's current enabled, and we need to disable it 645 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "SetAuthPrincipalEnableStatus: Disabling (old uac={0})", uacValue); 646 647 Utils.SetBit(ref uacValue, 0x2); // UF_ACCOUNTDISABLE 648 649 WriteAttribute(ap, "userAccountControl", uacValue); 650 } 651 } 652 catch (System.Runtime.InteropServices.COMException e) 653 { 654 throw ExceptionHelper.GetExceptionFromCOMException(e); 655 } 656 } 657 /// <summary> 658 /// Apply all changed properties on the principal to the Directory Entry. 659 /// Reset the changed status on all the properties 660 /// </summary> 661 /// <param name="p">Principal to update</param> Update(Principal p)662 internal override void Update(Principal p) 663 { 664 try 665 { 666 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Update"); 667 668 Debug.Assert(p.fakePrincipal == false); 669 Debug.Assert(p.unpersisted == false); 670 Debug.Assert(p.UnderlyingObject != null); 671 Debug.Assert(p.UnderlyingObject is DirectoryEntry); 672 673 // Commit the properties 674 SDSUtils.ApplyChangesToDirectory( 675 p, 676 this, 677 new SDSUtils.GroupMembershipUpdater(UpdateGroupMembership), 678 this.credentials, 679 this.authTypes 680 ); 681 682 // Reset the change tracking 683 p.ResetAllChangeStatus(); 684 } 685 catch (System.Runtime.InteropServices.COMException e) 686 { 687 throw ExceptionHelper.GetExceptionFromCOMException(e); 688 } 689 } 690 691 /// <summary> 692 /// Delete the directory entry that corresponds to the principal 693 /// </summary> 694 /// <param name="p">Principal to delete</param> Delete(Principal p)695 internal override void Delete(Principal p) 696 { 697 try 698 { 699 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Delete"); 700 701 Debug.Assert(p.fakePrincipal == false); 702 703 // Principal.Delete() shouldn't be calling us on an unpersisted Principal. 704 Debug.Assert(p.unpersisted == false); 705 Debug.Assert(p.UnderlyingObject != null); 706 707 Debug.Assert(p.UnderlyingObject is DirectoryEntry); 708 SDSUtils.DeleteDirectoryEntry((DirectoryEntry)p.UnderlyingObject); 709 } 710 catch (System.Runtime.InteropServices.COMException e) 711 { 712 throw ExceptionHelper.GetExceptionFromCOMException(e); 713 } 714 } 715 Move(StoreCtx originalStore, Principal p)716 internal override void Move(StoreCtx originalStore, Principal p) 717 { 718 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Move"); 719 720 Debug.Assert(p != null); 721 Debug.Assert(originalStore != null); 722 Debug.Assert(originalStore is ADStoreCtx); 723 724 string name = null; 725 string rdnPrefix = p.ExtensionHelper.RdnPrefix; 726 string baseObjectRdnPrefix = null; 727 Type principalType = p.GetType(); 728 729 if (null == rdnPrefix) 730 { 731 throw new InvalidOperationException(SR.ExtensionInvalidClassAttributes); 732 } 733 734 if (p.GetChangeStatusForProperty(PropertyNames.PrincipalName)) 735 { 736 name = rdnPrefix + "=" + (string)p.GetValueForProperty(PropertyNames.PrincipalName); 737 738 // If the principal class is derived from Group, Computer or User then we need to see 739 // if the class has an RdnPrefix set that differs from the base class prefix. If so then we need 740 // to modify that attribute when if changed the name during the move. 741 742 if (principalType.IsSubclassOf(typeof(GroupPrincipal)) || 743 principalType.IsSubclassOf(typeof(UserPrincipal)) || 744 principalType.IsSubclassOf(typeof(ComputerPrincipal))) 745 { 746 DirectoryRdnPrefixAttribute[] MyAttribute = 747 (DirectoryRdnPrefixAttribute[])Attribute.GetCustomAttributes(principalType.BaseType, typeof(DirectoryRdnPrefixAttribute), false); 748 749 if (MyAttribute == null) 750 throw new InvalidOperationException(SR.ExtensionInvalidClassAttributes); 751 752 string defaultRdn = null; 753 754 // Search for the rdn prefix. This will use either the prefix that has a context type 755 // that matches the principals context or the first rdnPrefix that has a null context type 756 for (int i = 0; i < MyAttribute.Length; i++) 757 { 758 if ((MyAttribute[i].Context == null && null == defaultRdn) || 759 (p.ContextType == MyAttribute[i].Context)) 760 { 761 defaultRdn = MyAttribute[i].RdnPrefix; 762 } 763 } 764 765 // If the base objects RDN prefix is not the same as the dervied class then we need to set both 766 if (defaultRdn != rdnPrefix) 767 { 768 baseObjectRdnPrefix = defaultRdn; 769 } 770 } 771 } 772 773 SDSUtils.MoveDirectoryEntry((DirectoryEntry)p.GetUnderlyingObject(), 774 ctxBase, 775 name); 776 777 p.LoadValueIntoProperty(PropertyNames.PrincipalName, p.GetValueForProperty(PropertyNames.PrincipalName)); 778 779 if (null != baseObjectRdnPrefix) 780 { 781 ((DirectoryEntry)p.GetUnderlyingObject()).Properties[baseObjectRdnPrefix].Value = (string)p.GetValueForProperty(PropertyNames.PrincipalName); 782 } 783 } 784 785 // 786 // Special operations: the Principal classes delegate their implementation of many of the 787 // special methods to their underlying StoreCtx 788 // 789 790 // methods for manipulating accounts 791 792 /// <summary> 793 /// This method sets the default user account control bits for the new principal 794 /// being created in this account store. 795 /// </summary> 796 /// <param name="p"> Principal to set the user account control bits for </param> InitializeUserAccountControl(AuthenticablePrincipal p)797 internal override void InitializeUserAccountControl(AuthenticablePrincipal p) 798 { 799 Debug.Assert(p != null); 800 Debug.Assert(p.fakePrincipal == false); 801 Debug.Assert(p.unpersisted == true); // should only ever be called for new principals 802 803 // set the userAccountControl bits on the underlying directory entry 804 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject; 805 Debug.Assert(de != null); 806 Type principalType = p.GetType(); 807 808 if ((principalType == typeof(ComputerPrincipal)) || (principalType.IsSubclassOf(typeof(ComputerPrincipal)))) 809 { 810 de.Properties["userAccountControl"].Value = SDSUtils.AD_DefaultUAC_Machine; 811 } 812 else if ((principalType == typeof(UserPrincipal)) || (principalType.IsSubclassOf(typeof(UserPrincipal)))) 813 { 814 de.Properties["userAccountControl"].Value = SDSUtils.AD_DefaultUAC; 815 } 816 } 817 818 /// <summary> 819 /// Determine if principal account is locked. 820 /// First read User-Account-control-computed from the DE. On Uplevel platforms this computed attribute will exist and we can 821 /// just check bit 0x0010. On DL platforms this attribute does not exist so we must read lockoutTime and return locked if 822 /// this is greater than 0 823 /// </summary> 824 /// <param name="p">Principal to check status</param> 825 /// <returns>true is account is locked, false if not</returns> IsLockedOut(AuthenticablePrincipal p)826 internal override bool IsLockedOut(AuthenticablePrincipal p) 827 { 828 try 829 { 830 Debug.Assert(p.fakePrincipal == false); 831 832 Debug.Assert(p.unpersisted == false); 833 834 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject; 835 Debug.Assert(de != null); 836 837 de.RefreshCache(new string[] { "msDS-User-Account-Control-Computed", "lockoutTime" }); 838 839 if (de.Properties["msDS-User-Account-Control-Computed"].Count > 0) 840 { 841 // Uplevel platform --- the DC will compute it for us 842 Debug.Assert(de.Properties["msDS-User-Account-Control-Computed"].Count == 1); 843 int uacComputed = (int)de.Properties["msDS-User-Account-Control-Computed"][0]; 844 845 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsLockedOut: using computed uac={0}", uacComputed); 846 847 return ((uacComputed & 0x0010) != 0); // UF_LOCKOUT 848 } 849 else 850 { 851 // Downlevel platform --- we have to compute it 852 bool isLockedOut = false; 853 854 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsLockedOut: downlevel"); 855 856 if (de.Properties["lockoutTime"].Count > 0) 857 { 858 ulong lockoutTime = (ulong)ADUtils.LargeIntToInt64((UnsafeNativeMethods.IADsLargeInteger)de.Properties["lockoutTime"][0]); 859 860 if (lockoutTime != 0) 861 { 862 ulong lockoutDuration = this.LockoutDuration; 863 864 GlobalDebug.WriteLineIf(GlobalDebug.Info, 865 "ADStoreCtx", 866 "IsLockedOut: lockoutTime={0}, lockoutDuration={1}", 867 lockoutTime, 868 lockoutDuration); 869 870 if ((lockoutDuration + lockoutTime) > ((ulong)ADUtils.DateTimeToADFileTime(DateTime.UtcNow))) 871 isLockedOut = true; 872 } 873 } 874 875 return isLockedOut; 876 } 877 } 878 catch (System.Runtime.InteropServices.COMException e) 879 { 880 throw ExceptionHelper.GetExceptionFromCOMException(e); 881 } 882 } 883 884 /// <summary> 885 /// Unlock account by setting LockoutTime to 0 886 /// </summary> 887 /// <param name="p">Principal to unlock</param> UnlockAccount(AuthenticablePrincipal p)888 internal override void UnlockAccount(AuthenticablePrincipal p) 889 { 890 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "UnlockAccount"); 891 892 Debug.Assert(p.fakePrincipal == false); 893 894 WriteAttribute(p, "lockoutTime", 0); 895 } 896 897 // methods for manipulating passwords 898 /// <summary> 899 /// Set the password on the principal. This function requires administrator privileges 900 /// </summary> 901 /// <param name="p">Principal to modify</param> 902 /// <param name="newPassword">New password</param> SetPassword(AuthenticablePrincipal p, string newPassword)903 internal override void SetPassword(AuthenticablePrincipal p, string newPassword) 904 { 905 Debug.Assert(p.fakePrincipal == false); 906 907 Debug.Assert(p != null); 908 Debug.Assert(newPassword != null); // but it could be an empty string 909 910 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject; 911 Debug.Assert(de != null); 912 913 SDSUtils.SetPassword(de, newPassword); 914 } 915 916 /// <summary> 917 /// Change the password on the principal 918 /// </summary> 919 /// <param name="p">Principal to modify</param> 920 /// <param name="oldPassword">Current password</param> 921 /// <param name="newPassword">New password</param> ChangePassword(AuthenticablePrincipal p, string oldPassword, string newPassword)922 internal override void ChangePassword(AuthenticablePrincipal p, string oldPassword, string newPassword) 923 { 924 Debug.Assert(p.fakePrincipal == false); 925 926 // Shouldn't be being called if this is the case 927 Debug.Assert(p.unpersisted == false); 928 929 Debug.Assert(p != null); 930 Debug.Assert(newPassword != null); // but it could be an empty string 931 Debug.Assert(oldPassword != null); // but it could be an empty string 932 933 if ((p.GetType() == typeof(ComputerPrincipal)) || (p.GetType().IsSubclassOf(typeof(ComputerPrincipal)))) 934 { 935 GlobalDebug.WriteLineIf(GlobalDebug.Error, "ADStoreCtx", "ChangePassword: computer acct, can't change password."); 936 throw new NotSupportedException(SR.ADStoreCtxNoComputerPasswordChange); 937 } 938 939 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject; 940 Debug.Assert(de != null); 941 942 SDSUtils.ChangePassword(de, oldPassword, newPassword); 943 } 944 /// <summary> 945 /// Expire password by setting pwdLastSet to 0 946 /// </summary> 947 /// <param name="p"></param> ExpirePassword(AuthenticablePrincipal p)948 internal override void ExpirePassword(AuthenticablePrincipal p) 949 { 950 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "ExpirePassword"); 951 952 Debug.Assert(p.fakePrincipal == false); 953 954 WriteAttribute(p, "pwdLastSet", 0); 955 } 956 957 /// <summary> 958 /// Unexpire password by setting pwdLastSet to -1 959 /// </summary> 960 /// <param name="p"></param> UnexpirePassword(AuthenticablePrincipal p)961 internal override void UnexpirePassword(AuthenticablePrincipal p) 962 { 963 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "UnexpirePassword"); 964 965 Debug.Assert(p.fakePrincipal == false); 966 967 WriteAttribute(p, "pwdLastSet", -1); 968 } 969 970 /// <summary> 971 /// Set value for attribute on the passed principal. This is only valid for integer attribute types 972 /// </summary> 973 /// <param name="p"></param> 974 /// <param name="attribute"></param> 975 /// <param name="value"></param> WriteAttribute(Principal p, string attribute, int value)976 protected void WriteAttribute(Principal p, string attribute, int value) 977 { 978 ; 979 980 Debug.Assert(p != null); 981 982 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject; 983 984 SDSUtils.WriteAttribute(de.Path, attribute, value, this.credentials, this.authTypes); 985 } 986 WriteAttribute(Principal p, string attribute, T value)987 protected void WriteAttribute<T>(Principal p, string attribute, T value) 988 { 989 Debug.Assert(p != null); 990 991 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject; 992 993 SDSUtils.WriteAttribute<T>(de.Path, attribute, value, this.credentials, this.authTypes); 994 } 995 996 // the various FindBy* methods FindByLockoutTime( DateTime dt, MatchType matchType, Type principalType)997 internal override ResultSet FindByLockoutTime( 998 DateTime dt, MatchType matchType, Type principalType) 999 { 1000 return FindByDate(principalType, new string[] { "lockoutTime" }, matchType, dt); 1001 } 1002 FindByLogonTime( DateTime dt, MatchType matchType, Type principalType)1003 internal override ResultSet FindByLogonTime( 1004 DateTime dt, MatchType matchType, Type principalType) 1005 { 1006 return FindByDate(principalType, new string[] { "lastLogon", "lastLogonTimestamp" }, matchType, dt); 1007 } 1008 FindByPasswordSetTime( DateTime dt, MatchType matchType, Type principalType)1009 internal override ResultSet FindByPasswordSetTime( 1010 DateTime dt, MatchType matchType, Type principalType) 1011 { 1012 return FindByDate(principalType, new string[] { "pwdLastSet" }, matchType, dt); 1013 } 1014 FindByBadPasswordAttempt( DateTime dt, MatchType matchType, Type principalType)1015 internal override ResultSet FindByBadPasswordAttempt( 1016 DateTime dt, MatchType matchType, Type principalType) 1017 { 1018 return FindByDate(principalType, new string[] { "badPasswordTime" }, matchType, dt); 1019 } 1020 FindByExpirationTime( DateTime dt, MatchType matchType, Type principalType)1021 internal override ResultSet FindByExpirationTime( 1022 DateTime dt, MatchType matchType, Type principalType) 1023 { 1024 return FindByDate(principalType, new string[] { "accountExpires" }, matchType, dt); 1025 } 1026 FindByDate(Type subtype, string[] ldapAttributes, MatchType matchType, DateTime value)1027 private ResultSet FindByDate(Type subtype, string[] ldapAttributes, MatchType matchType, DateTime value) 1028 { 1029 Debug.Assert(ldapAttributes != null); 1030 Debug.Assert(ldapAttributes.Length > 0); 1031 Debug.Assert(subtype == typeof(Principal) || subtype.IsSubclassOf(typeof(Principal))); 1032 DirectorySearcher ds = new DirectorySearcher(this.ctxBase); 1033 1034 try 1035 { 1036 // Pick some reasonable default values 1037 ds.PageSize = 256; 1038 ds.ServerTimeLimit = new TimeSpan(0, 0, 30); // 30 seconds 1039 1040 // We don't need any attributes returned, since we're just going to get a DirectoryEntry 1041 // for the result. Per RFC 2251, OID 1.1 == no attributes. 1042 BuildPropertySet(subtype, ds.PropertiesToLoad); 1043 1044 // Build the LDAP filter 1045 string ldapValue = ADUtils.DateTimeToADString(value); 1046 StringBuilder ldapFilter = new StringBuilder(); 1047 1048 ldapFilter.Append(GetObjectClassPortion(subtype)); 1049 ldapFilter.Append("(|"); 1050 1051 foreach (string ldapAttribute in ldapAttributes) 1052 { 1053 ldapFilter.Append("("); 1054 1055 switch (matchType) 1056 { 1057 case MatchType.Equals: 1058 ldapFilter.Append(ldapAttribute); 1059 ldapFilter.Append("="); 1060 ldapFilter.Append(ldapValue); 1061 break; 1062 1063 case MatchType.NotEquals: 1064 ldapFilter.Append("!("); 1065 ldapFilter.Append(ldapAttribute); 1066 ldapFilter.Append("="); 1067 ldapFilter.Append(ldapValue); 1068 ldapFilter.Append(")"); 1069 break; 1070 1071 case MatchType.GreaterThanOrEquals: 1072 ldapFilter.Append(ldapAttribute); 1073 ldapFilter.Append(">="); 1074 ldapFilter.Append(ldapValue); 1075 break; 1076 1077 case MatchType.LessThanOrEquals: 1078 ldapFilter.Append(ldapAttribute); 1079 ldapFilter.Append("<="); 1080 ldapFilter.Append(ldapValue); 1081 break; 1082 1083 case MatchType.GreaterThan: 1084 ldapFilter.Append("&"); 1085 1086 // Greater-than-or-equals (or less-than-or-equals)) 1087 ldapFilter.Append("("); 1088 ldapFilter.Append(ldapAttribute); 1089 ldapFilter.Append(matchType == MatchType.GreaterThan ? ">=" : "<="); 1090 ldapFilter.Append(ldapValue); 1091 ldapFilter.Append(")"); 1092 1093 // And not-equal 1094 ldapFilter.Append("(!("); 1095 ldapFilter.Append(ldapAttribute); 1096 ldapFilter.Append("="); 1097 ldapFilter.Append(ldapValue); 1098 ldapFilter.Append("))"); 1099 1100 // And exists (need to include because of tristate LDAP logic) 1101 ldapFilter.Append("("); 1102 ldapFilter.Append(ldapAttribute); 1103 ldapFilter.Append("=*)"); 1104 break; 1105 1106 case MatchType.LessThan: 1107 goto case MatchType.GreaterThan; 1108 1109 default: 1110 Debug.Fail("ADStoreCtx.FindByDate: fell off end looking for " + matchType.ToString()); 1111 break; 1112 } 1113 1114 ldapFilter.Append(")"); 1115 } 1116 1117 ldapFilter.Append("))"); 1118 1119 ds.Filter = ldapFilter.ToString(); 1120 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "FindByDate: using LDAP filter {0}", ds.Filter); 1121 1122 // Perform the search 1123 SearchResultCollection src = ds.FindAll(); 1124 Debug.Assert(src != null); 1125 1126 // Create a ResultSet for the search results 1127 ADEntriesSet resultSet = new ADEntriesSet(src, this); 1128 1129 return resultSet; 1130 } 1131 catch (System.Runtime.InteropServices.COMException e) 1132 { 1133 throw ExceptionHelper.GetExceptionFromCOMException(e); 1134 } 1135 finally 1136 { 1137 ds.Dispose(); 1138 } 1139 } 1140 1141 // Get groups of which p is a direct member 1142 // search 1143 // 1. search for group with same group id as principals primary group ID. 1144 // Then 1145 // 2. use enumeration to expand the users group membership 1146 // ASQ will not work because we cannot correctly generate referrals if one of the users 1147 // groups if from another domain in the forest. GetGroupsMemberOf(Principal p)1148 internal override ResultSet GetGroupsMemberOf(Principal p) 1149 { 1150 // Enforced by the methods that call us 1151 Debug.Assert(p.unpersisted == false); 1152 1153 DirectoryEntry gcPrincipalDe = null; 1154 DirectorySearcher memberOfSearcher = null; 1155 ADDNConstraintLinkedAttrSet.ResultValidator resultValidator = null; 1156 1157 try 1158 { 1159 if (p.fakePrincipal) 1160 { 1161 // If p is a fake principal, this will find the representation of p in the store 1162 // (namely, a FPO), and return the groups of which that FPO is a member 1163 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf: fake principal"); 1164 return GetGroupsMemberOf(p, this); 1165 } 1166 1167 Debug.Assert(p.UnderlyingObject != null); 1168 1169 string primaryGroupDN = null; 1170 ResultSet resultSet = null; 1171 bool useASQ = false; 1172 List<DirectoryEntry> roots = new List<DirectoryEntry>(1); 1173 DirectorySearcher[] searchers = null; 1174 IEnumerable[] enumerators = null; 1175 1176 DirectoryEntry principalDE = (DirectoryEntry)p.GetUnderlyingObject(); 1177 1178 if ((p.ContextType == ContextType.ApplicationDirectory) || (p.Context.ServerInformation.OsVersion == DomainControllerMode.Win2k)) 1179 { 1180 useASQ = false; 1181 } 1182 else 1183 { 1184 useASQ = true; 1185 } 1186 1187 if (p.ContextType != ContextType.ApplicationDirectory) 1188 { 1189 // A users group membership that applies to a particular domain includes the domain's universal, local and global groups plus the 1190 // universal groups from every other domain in the forest that the user is a member of. To get this list we must contact both a GlobalCatalog to get the forest 1191 // universal list and a DC in the users domain to get the domain local groups which are not replicated to the GC. 1192 // If we happen to get a GC in the same domain as the user 1193 // then we don't also need a DC because the domain local group memberships will show up as well. The enumerator code that expands these lists must detect 1194 // duplicates because the list of global groups will show up on both the GC and DC. 1195 Debug.Assert(p.ContextType == ContextType.Domain); 1196 1197 Forest forest = Forest.GetForest(new DirectoryContext(DirectoryContextType.Forest, this.DnsForestName, this.credentials != null ? this.credentials.UserName : null, this.credentials != null ? this.credentials.Password : null)); 1198 1199 DirectoryContext dc = new DirectoryContext(DirectoryContextType.Domain, this.DnsDomainName, this.credentials != null ? this.credentials.UserName : null, this.credentials != null ? this.credentials.Password : null); 1200 DomainController dd = DomainController.FindOne(dc); 1201 1202 GlobalCatalog gc = null; 1203 1204 try 1205 { 1206 gc = forest.FindGlobalCatalog(); 1207 1208 var gg = forest.FindAllGlobalCatalogs(dd.SiteName); 1209 foreach (GlobalCatalog g in gg) 1210 { 1211 if (0 == string.Compare(this.DnsDomainName, g.Domain.Name, StringComparison.OrdinalIgnoreCase)) 1212 { 1213 gc = g; 1214 break; 1215 } 1216 } 1217 1218 roots.Add(new DirectoryEntry("GC://" + gc.Name + "/" + p.DistinguishedName, this.credentials != null ? this.credentials.UserName : null, this.credentials != null ? this.credentials.Password : null, this.AuthTypes)); 1219 1220 if (0 != string.Compare(this.DnsDomainName, gc.Domain.Name, StringComparison.OrdinalIgnoreCase)) 1221 { 1222 //useASQ = false; 1223 roots.Add(principalDE); 1224 1225 //Since the GC does not belong to the same domain (as the principal object passed) 1226 //We should make sure that we ignore domain local groups that we obtained from the cross-domain GC. 1227 resultValidator = delegate (dSPropertyCollection resultPropCollection) 1228 { 1229 if (resultPropCollection["groupType"].Count > 0 && resultPropCollection["objectSid"].Count > 0) 1230 { 1231 int? groupTypeValue = (int?)resultPropCollection["groupType"][0]; 1232 if (groupTypeValue.HasValue && ((groupTypeValue.Value & ADGroupScope.Local) == ADGroupScope.Local)) 1233 { 1234 byte[] sidByteArray = (byte[])resultPropCollection["objectSid"][0]; 1235 SecurityIdentifier resultSid = new SecurityIdentifier(sidByteArray, 0); 1236 return ADUtils.AreSidsInSameDomain(p.Sid, resultSid); 1237 } 1238 } 1239 return true; //Return true for all other case, including the case where we don't have permissions 1240 //to read groupType/objectSid attribute then we declare the result as a match. 1241 }; 1242 } 1243 } 1244 catch (System.DirectoryServices.ActiveDirectory.ActiveDirectoryOperationException e) 1245 { 1246 // if we can't get a GC then just fail. 1247 throw new PrincipalOperationException(e.Message, e); 1248 } 1249 catch (System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException e) 1250 { 1251 // if we can't get a GC then just fail. 1252 throw new PrincipalOperationException(e.Message, e); 1253 } 1254 finally 1255 { 1256 if (gc != null) 1257 { 1258 gc.Dispose(); 1259 } 1260 if (forest != null) 1261 { 1262 forest.Dispose(); 1263 } 1264 } 1265 } 1266 1267 if (false == useASQ) 1268 { 1269 // If this is ADAM then we only need to use the original object. 1270 // IF AD then we will use whatever enumerators we discovered above. 1271 if (p.ContextType != ContextType.ApplicationDirectory) 1272 { 1273 int index = 0; 1274 enumerators = new IEnumerable[roots.Count]; 1275 1276 foreach (DirectoryEntry de in roots) 1277 { 1278 //If, de is not equal to principalDE then it must have been created by this function (above code) 1279 //In that case de is NOT owned by any other modules outside. Hence, configure RangeRetriever to dispose the DirEntry on its dispose. 1280 enumerators[index] = new RangeRetriever(de, "memberOf", (de != principalDE)); 1281 index++; 1282 } 1283 } 1284 else 1285 { 1286 enumerators = new IEnumerable[1]; 1287 //Since principalDE is not owned by us, 1288 //configuring RangeRetriever _NOT_ to dispose the DirEntry on its dispose. 1289 enumerators[0] = new RangeRetriever(principalDE, "memberOf", false); 1290 } 1291 } 1292 else 1293 { 1294 int index = 0; 1295 searchers = new DirectorySearcher[roots.Count]; 1296 1297 foreach (DirectoryEntry de in roots) 1298 { 1299 searchers[index] = SDSUtils.ConstructSearcher(de); 1300 searchers[index].SearchScope = SearchScope.Base; 1301 searchers[index].AttributeScopeQuery = "memberOf"; 1302 searchers[index].Filter = "(objectClass=*)"; 1303 searchers[index].CacheResults = false; 1304 1305 BuildPropertySet(typeof(GroupPrincipal), searchers[index].PropertiesToLoad); 1306 index++; 1307 } 1308 } 1309 1310 string principalDN = (string)principalDE.Properties["distinguishedName"].Value; 1311 1312 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf: principalDN={0}", principalDN); 1313 1314 principalDE.RefreshCache(new string[] { "memberOf", "primaryGroupID" }); 1315 1316 if ((principalDE.Properties["primaryGroupID"].Count > 0) && 1317 (principalDE.Properties["objectSid"].Count > 0)) 1318 { 1319 Debug.Assert(principalDE.Properties["primaryGroupID"].Count == 1); 1320 Debug.Assert(principalDE.Properties["objectSid"].Count == 1); 1321 1322 int primaryGroupID = (int)principalDE.Properties["primaryGroupID"].Value; 1323 byte[] principalSid = (byte[])principalDE.Properties["objectSid"].Value; 1324 1325 primaryGroupDN = GetGroupDnFromGroupID(principalSid, primaryGroupID); 1326 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf: primary group DN={0}", primaryGroupDN); 1327 } 1328 1329 // We must use enumeration to expand the users group membership 1330 // ASQ will not work because we cannot correctly generate referrals if one of the users 1331 // groups if from another domain in the forest. 1332 1333 if (useASQ) 1334 { 1335 if (resultValidator != null) 1336 { 1337 resultSet = new ADDNConstraintLinkedAttrSet( 1338 ADDNConstraintLinkedAttrSet.ConstraintType.ResultValidatorDelegateMatch, 1339 resultValidator, principalDN, searchers, primaryGroupDN, null, false, this); 1340 } 1341 else 1342 { 1343 resultSet = new ADDNLinkedAttrSet(principalDN, searchers, primaryGroupDN, null, false, this); 1344 } 1345 } 1346 else 1347 { 1348 if (resultValidator != null) 1349 { 1350 resultSet = new ADDNConstraintLinkedAttrSet( 1351 ADDNConstraintLinkedAttrSet.ConstraintType.ResultValidatorDelegateMatch, 1352 resultValidator, principalDN, enumerators, primaryGroupDN, null, false, this); 1353 } 1354 else 1355 { 1356 resultSet = new ADDNLinkedAttrSet(principalDN, enumerators, primaryGroupDN, null, false, this); 1357 } 1358 } 1359 return resultSet; 1360 } 1361 catch (System.Runtime.InteropServices.COMException e) 1362 { 1363 throw ExceptionHelper.GetExceptionFromCOMException(e); 1364 } 1365 finally 1366 { 1367 if (null != gcPrincipalDe) 1368 { 1369 gcPrincipalDe.Dispose(); 1370 } 1371 1372 if (null != memberOfSearcher) 1373 { 1374 memberOfSearcher.Dispose(); 1375 } 1376 } 1377 } 1378 1379 // Get groups from this ctx which contain a principal corresponding to foreignPrincipal 1380 // (which is a principal from foreignContext) GetGroupsMemberOf(Principal foreignPrincipal, StoreCtx foreignContext)1381 internal override ResultSet GetGroupsMemberOf(Principal foreignPrincipal, StoreCtx foreignContext) 1382 { 1383 // Get the Principal's SID, so we can look it up by SID in our store 1384 SecurityIdentifier Sid = foreignPrincipal.Sid; 1385 1386 if (Sid == null) 1387 throw new InvalidOperationException(SR.StoreCtxNeedValueSecurityIdentityClaimToQuery); 1388 1389 // Search our store for a object with a matching SID. This could be a user/group/computer object, 1390 // or a foreignSecurityPrincipal. Doesn't really matter --- either way, the store object will have a objectSid 1391 // and a memberOf attribute. 1392 // SID search 1393 // 1394 // 1395 // If we can read the defaultNamingContext and retrive the well known path for the foreignSecurityPrincipal container start there. 1396 // If we can only read the defaultNamingContext then start there 1397 // Else just start at the base DN from the original context 1398 // 1399 // If the object was not found and we started at teh fsp container then search the entire DC. 1400 // Else just exit. we have nothing else to search 1401 1402 // An object exists in the domain that contains links to all the groups it is a member of. 1403 bool rootPrincipalExists = true; 1404 1405 if ((foreignContext is ADStoreCtx) && !(foreignContext is ADAMStoreCtx)) 1406 { 1407 // We only need to check forest status for AD stores. Forest concept does not apply to ADAM. 1408 ADStoreCtx foreignADStore = (ADStoreCtx)foreignContext; 1409 1410 // If same forest but different domain then we have a child or alternate tree domain. We don't have a starting user 1411 // object and must do a search on all groups to find membership. 1412 if (0 == string.Compare(foreignADStore.DnsForestName, this.DnsForestName, StringComparison.OrdinalIgnoreCase)) 1413 { 1414 if (0 == string.Compare(foreignADStore.DnsDomainName, this.DnsDomainName, StringComparison.OrdinalIgnoreCase)) 1415 { 1416 rootPrincipalExists = true; 1417 } 1418 else 1419 { 1420 rootPrincipalExists = false; 1421 } 1422 } 1423 } 1424 1425 DirectoryEntry dncContainer = null; 1426 string fspWkDn = null; 1427 DirectoryEntry fspContainer = null; 1428 ResultSet resultSet = null; 1429 DirectorySearcher ds = null; 1430 1431 try 1432 { 1433 if (rootPrincipalExists) 1434 { 1435 if (this.DefaultNamingContext != null) 1436 { 1437 dncContainer = new DirectoryEntry(@"LDAP://" + this.UserSuppliedServerName + @"/" + this.DefaultNamingContext, Credentials != null ? this.Credentials.UserName : null, Credentials != null ? this.Credentials.Password : null, this.AuthTypes); 1438 1439 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): Read DNC of {0}", this.DefaultNamingContext); 1440 1441 fspWkDn = ADUtils.RetriveWkDn(dncContainer, this.DefaultNamingContext, this.UserSuppliedServerName, Constants.GUID_FOREIGNSECURITYPRINCIPALS_CONTAINER_BYTE); 1442 1443 if (null != fspWkDn) 1444 { 1445 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): Read fsp DN {0}", fspWkDn); 1446 fspContainer = new DirectoryEntry(fspWkDn, Credentials != null ? this.credentials.UserName : null, Credentials != null ? this.credentials.Password : null, this.authTypes); 1447 } 1448 } 1449 1450 ds = new DirectorySearcher((fspContainer != null) ? fspContainer : ((dncContainer != null ? dncContainer : this.ctxBase))); 1451 1452 // Pick some reasonable default values 1453 ds.PageSize = 256; 1454 ds.ServerTimeLimit = new TimeSpan(0, 0, 30); // 30 seconds 1455 1456 // Build the LDAP filter 1457 // Converr the object to a SDDL format 1458 string stringSid = Utils.SecurityIdentifierToLdapHexFilterString(Sid); 1459 if (stringSid == null) 1460 throw new InvalidOperationException(SR.StoreCtxNeedValueSecurityIdentityClaimToQuery); 1461 1462 ds.Filter = "(objectSid=" + stringSid + ")"; 1463 1464 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): using LDAP filter {0}", ds.Filter); 1465 1466 // We only need a few attributes 1467 ds.PropertiesToLoad.Add("memberOf"); 1468 ds.PropertiesToLoad.Add("distinguishedName"); 1469 ds.PropertiesToLoad.Add("primaryGroupID"); 1470 ds.PropertiesToLoad.Add("objectSid"); 1471 1472 // If no corresponding principal exists in this store, then by definition the principal isn't 1473 // a member of any groups in this store. 1474 SearchResult sr = ds.FindOne(); 1475 1476 if (sr == null) 1477 { 1478 // no match so we better do a root level search in case we are targetting a domain where 1479 // the user is not an FSP. 1480 1481 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): No match"); 1482 1483 // We already did a root level search so just exit. 1484 1485 if (null == fspWkDn) 1486 return new EmptySet(); 1487 1488 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): performing DNC level search"); 1489 1490 ds.SearchRoot = dncContainer; 1491 sr = ds.FindOne(); 1492 1493 if (sr == null) 1494 return new EmptySet(); 1495 } 1496 1497 // Now that we found the corresponding principal, the rest is very similiar to the plain GetGroupsMemberOf() 1498 // case, exception we're working with search results (SearchResult/ResultPropertyValueCollection) rather 1499 // than DirectoryEntry/PropertyValueCollection. 1500 string principalDN = (string)sr.Properties["distinguishedName"][0]; 1501 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): match, DN={0}", principalDN); 1502 1503 //Here a new DirectoryEntry object is created by sr.GetDirectoryEntry() and passed 1504 //to RangeRetriever object. Hence, configuring RangeRetriever to dispose the DirEntry on its dispose. 1505 IEnumerable memberOf = new RangeRetriever(sr.GetDirectoryEntry(), "memberOf", true); 1506 1507 string primaryGroupDN = null; 1508 1509 if ((sr.Properties["primaryGroupID"].Count > 0) && 1510 (sr.Properties["objectSid"].Count > 0)) 1511 { 1512 Debug.Assert(sr.Properties["primaryGroupID"].Count == 1); 1513 Debug.Assert(sr.Properties["objectSid"].Count == 1); 1514 1515 int primaryGroupID = (int)sr.Properties["primaryGroupID"][0]; 1516 byte[] principalSid = (byte[])sr.Properties["objectSid"][0]; 1517 1518 primaryGroupDN = GetGroupDnFromGroupID(principalSid, primaryGroupID); 1519 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): primary group DN={0}", primaryGroupDN); 1520 } 1521 1522 resultSet = new ADDNConstraintLinkedAttrSet(ADDNConstraintLinkedAttrSet.ConstraintType.ContainerStringMatch, this.ctxBase.Properties["distinguishedName"].Value, principalDN, new IEnumerable[] { memberOf }, primaryGroupDN, null, false, this); 1523 } 1524 else 1525 { 1526 // We don't need to retrive the Primary group ID here because we have already established that this user is not from this domain 1527 // and the users primary group must be from the same domain as the user. 1528 Debug.Assert(foreignPrincipal.ContextType != ContextType.ApplicationDirectory); 1529 1530 DirectorySearcher[] memberSearcher = { SDSUtils.ConstructSearcher(this.ctxBase) }; 1531 memberSearcher[0].Filter = "(&(objectClass=Group)(member=" + foreignPrincipal.DistinguishedName + "))"; 1532 memberSearcher[0].CacheResults = false; 1533 1534 resultSet = new ADDNLinkedAttrSet(foreignPrincipal.DistinguishedName, memberSearcher, null, null, false, this); 1535 } 1536 1537 return resultSet; 1538 } 1539 catch (System.Runtime.InteropServices.COMException e) 1540 { 1541 throw ExceptionHelper.GetExceptionFromCOMException(e); 1542 } 1543 finally 1544 { 1545 if (null != fspContainer) 1546 fspContainer.Dispose(); 1547 if (null != ds) 1548 ds.Dispose(); 1549 if (null != dncContainer) 1550 dncContainer.Dispose(); 1551 } 1552 } 1553 GetGroupDnFromGroupID(byte[] userSid, int primaryGroupId)1554 private string GetGroupDnFromGroupID(byte[] userSid, int primaryGroupId) 1555 { 1556 IntPtr pGroupSid = IntPtr.Zero; 1557 byte[] groupSid = null; 1558 1559 // This function is based on the technique in KB article 297951. 1560 1561 try 1562 { 1563 string sddlSid = Utils.ConvertSidToSDDL(userSid); 1564 if (sddlSid != null) 1565 { 1566 // Next, we modify the SDDL SID to replace with final subauthority 1567 // with the primary group's RID (the primaryGroupID) 1568 int index = sddlSid.LastIndexOf('-'); 1569 1570 if (index != -1) 1571 { 1572 sddlSid = sddlSid.Substring(0, index) + "-" + ((uint)primaryGroupId).ToString(CultureInfo.InvariantCulture); 1573 1574 // Now, we convert the SDDL back into a SID 1575 if (UnsafeNativeMethods.ConvertStringSidToSid(sddlSid, ref pGroupSid)) 1576 { 1577 // Now we convert the native SID to a byte[] SID 1578 groupSid = Utils.ConvertNativeSidToByteArray(pGroupSid); 1579 } 1580 } 1581 } 1582 } 1583 finally 1584 { 1585 if (pGroupSid != IntPtr.Zero) 1586 UnsafeNativeMethods.LocalFree(pGroupSid); 1587 } 1588 1589 if (groupSid != null) 1590 { 1591 return "<SID=" + Utils.ByteArrayToString(groupSid) + ">"; 1592 } 1593 1594 return null; 1595 } 1596 1597 // Get groups of which p is a member, using AuthZ S4U APIs for recursive membership GetGroupsMemberOfAZ(Principal p)1598 internal override ResultSet GetGroupsMemberOfAZ(Principal p) 1599 { 1600 // Enforced by the methods that call us 1601 Debug.Assert(p.unpersisted == false); 1602 Debug.Assert(p is UserPrincipal); 1603 1604 // Get the user SID that AuthZ will use. 1605 SecurityIdentifier SidObj = p.Sid; 1606 1607 if (SidObj == null) 1608 { 1609 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "GetGroupsMemberOfAZ: no SID IC"); 1610 throw new InvalidOperationException(SR.StoreCtxNeedValueSecurityIdentityClaimToQuery); 1611 } 1612 1613 byte[] sid = new byte[SidObj.BinaryLength]; 1614 SidObj.GetBinaryForm(sid, 0); 1615 1616 if (sid == null) 1617 { 1618 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "GetGroupsMemberOfAZ: bad SID IC"); 1619 throw new ArgumentException(SR.StoreCtxSecurityIdentityClaimBadFormat); 1620 } 1621 1622 try 1623 { 1624 if (true == ADUtils.VerifyOutboundTrust(this.DnsDomainName, (this.credentials == null ? null : this.credentials.UserName), (this.credentials == null ? null : this.credentials.Password))) 1625 { 1626 return new AuthZSet(sid, this.credentials, this.contextOptions, this.FlatDomainName, this, this.ctxBase); 1627 } 1628 else 1629 { 1630 DirectoryEntry principalDE = (DirectoryEntry)p.UnderlyingObject; 1631 string principalDN = (string)principalDE.Properties["distinguishedName"].Value; 1632 1633 return (new TokenGroupSet(principalDN, this, true)); 1634 } 1635 } 1636 catch (System.Runtime.InteropServices.COMException e) 1637 { 1638 throw ExceptionHelper.GetExceptionFromCOMException(e); 1639 } 1640 } 1641 1642 // Get members of group g 1643 // Need 2 searchers 1644 // 1. Users with this group as their primary group ID 1645 // 2. ASQ search against the member attribute on the group object for all contained objects. GetGroupMembership(GroupPrincipal g, bool recursive)1646 internal override BookmarkableResultSet GetGroupMembership(GroupPrincipal g, bool recursive) 1647 { 1648 // Enforced by the methods that call us 1649 Debug.Assert(g.unpersisted == false); 1650 1651 // Fake groups are a member of other groups, but they themselves have no members 1652 // (they don't even exist in the store) 1653 if (g.fakePrincipal) 1654 { 1655 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupMembership: fake principal"); 1656 return new EmptySet(); 1657 } 1658 1659 Debug.Assert(g.UnderlyingObject != null); 1660 1661 try 1662 { 1663 DirectoryEntry groupDE = (DirectoryEntry)g.UnderlyingObject; 1664 1665 // Create a DirectorySearcher for users who are members of this group via their primaryGroupId attribute 1666 DirectorySearcher ds = null; 1667 1668 if (groupDE.Properties["objectSid"].Count > 0) 1669 { 1670 Debug.Assert(groupDE.Properties["objectSid"].Count == 1); 1671 byte[] groupSid = (byte[])groupDE.Properties["objectSid"][0]; 1672 1673 ds = GetDirectorySearcherFromGroupID(groupSid); 1674 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupMembership: using LDAP filter={0}", ds.Filter); 1675 } 1676 1677 string groupDN = (string)groupDE.Properties["distinguishedName"].Value; 1678 BookmarkableResultSet resultSet = null; 1679 1680 // We must use enumeration to expand groups if their scope is Universal or Local 1681 // or if the domain controller is w2k 1682 // or if the context type is ApplicationDirectory (in AD LDS, Global groups can contain members from other partition) 1683 // Universal and Local groups can contain members from other domains in the forest. When this occurs 1684 // the referral is not generated correctly and we get an error. 1685 // 1686 if (g.Context.ContextType == ContextType.ApplicationDirectory || 1687 g.Context.ServerInformation.OsVersion == DomainControllerMode.Win2k || 1688 g.GroupScope != GroupScope.Global) 1689 { 1690 //Here the directory entry passed to RangeRetriever constructor belongs to 1691 //the GroupPrincipal object supplied to this function, which is not owned by us. 1692 //Hence, configuring RangeRetriever _NOT_ to dispose the DirEntry on its dispose. 1693 IEnumerable members = new RangeRetriever(groupDE, "member", false); 1694 1695 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupMembership: groupDN={0}", groupDN); 1696 resultSet = new ADDNLinkedAttrSet(groupDN, new IEnumerable[] { members }, null, ds, recursive, this); 1697 } 1698 else 1699 { 1700 DirectorySearcher[] dsMembers = new DirectorySearcher[1]; 1701 dsMembers[0] = SDSUtils.ConstructSearcher((DirectoryEntry)g.UnderlyingObject); 1702 dsMembers[0].AttributeScopeQuery = "member"; 1703 dsMembers[0].SearchScope = SearchScope.Base; 1704 dsMembers[0].Filter = "(objectClass=*)"; 1705 dsMembers[0].CacheResults = false; 1706 1707 BuildPropertySet(typeof(UserPrincipal), dsMembers[0].PropertiesToLoad); 1708 BuildPropertySet(typeof(GroupPrincipal), dsMembers[0].PropertiesToLoad); 1709 resultSet = new ADDNLinkedAttrSet(groupDN, dsMembers, null, ds, recursive, this); 1710 } 1711 1712 return resultSet; 1713 } 1714 catch (System.Runtime.InteropServices.COMException e) 1715 { 1716 throw ExceptionHelper.GetExceptionFromCOMException(e); 1717 } 1718 } 1719 GetDirectorySearcherFromGroupID(byte[] groupSid)1720 private DirectorySearcher GetDirectorySearcherFromGroupID(byte[] groupSid) 1721 { 1722 Debug.Assert(groupSid != null); 1723 1724 // Get the group's RID from the group's SID 1725 int groupRid = Utils.GetLastRidFromSid(groupSid); 1726 1727 // Build a DirectorySearcher for users whose primaryGroupId == the group's RID 1728 DirectorySearcher ds = new DirectorySearcher(this.ctxBase); 1729 ds.Filter = GetObjectClassPortion(typeof(Principal)) + "(primaryGroupId=" + groupRid.ToString(CultureInfo.InvariantCulture) + "))"; 1730 1731 // Pick some reasonable default values 1732 ds.PageSize = 256; 1733 ds.ServerTimeLimit = new TimeSpan(0, 0, 30); // 30 seconds 1734 1735 BuildPropertySet(typeof(Principal), ds.PropertiesToLoad); 1736 1737 return ds; 1738 } 1739 1740 // Is p a member of g in the store? 1741 internal override bool SupportsNativeMembershipTest { get { return true; } } 1742 1743 /// First check direct group membership by using DE.IsMember 1744 /// If this fails then we may have a ForeignSecurityPrincipal so search for Foreign Security Principals 1745 /// With the p's SID and then call IsMember with the ADS Path returned from the search. IsMemberOfInStore(GroupPrincipal g, Principal p)1746 internal override bool IsMemberOfInStore(GroupPrincipal g, Principal p) 1747 { 1748 Debug.Assert(g.unpersisted == false); 1749 Debug.Assert(p.unpersisted == false); 1750 1751 // Consistent with GetGroupMembership, a group that is a fake principal has no members 1752 if (g.fakePrincipal) 1753 { 1754 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: fake group"); 1755 return false; 1756 } 1757 1758 // AD Groups can only have AD principals as members 1759 if (p.ContextType != ContextType.Domain && p.ContextType != ContextType.ApplicationDirectory) 1760 { 1761 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: member is not a domain principal"); 1762 return false; 1763 } 1764 1765 Debug.Assert(g.UnderlyingObject != null && g.UnderlyingObject is DirectoryEntry); 1766 UnsafeNativeMethods.IADsGroup adsGroup = (UnsafeNativeMethods.IADsGroup)((DirectoryEntry)g.UnderlyingObject).NativeObject; 1767 IEnumerable cachedMembersEnum = null; //This variables stores a reference to the direct members enumerator of the group. 1768 1769 // Only real principals can be directly a member of the group, since only real principals 1770 // actually exist in the store. 1771 if (!p.fakePrincipal) 1772 { 1773 Debug.Assert(p.UnderlyingObject != null && p.UnderlyingObject is DirectoryEntry); 1774 1775 //// Test for direct membership 1776 //// 1777 1778 DirectoryEntry principalDE = (DirectoryEntry)p.UnderlyingObject; 1779 DirectoryEntry groupDE = (DirectoryEntry)g.UnderlyingObject; 1780 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: real principal, DN={0}", principalDE.Path); 1781 string principalDN = (string)principalDE.Properties["distinguishedName"].Value; 1782 1783 // we want to find if a group is "small", meaning that it has less than MaxValRange values (usually 1500) 1784 // the property list for the searcher of a group has "member" attribute. if there are more results than MaxValRange, there will also be a "member;range=..." attribute 1785 if (g.IsSmallGroup()) 1786 { 1787 // small groups has special search object that holds the member attribute so we use it for our search (no need to use the DirectoryEntry) 1788 Debug.Assert(g.SmallGroupMemberSearchResult != null); 1789 cachedMembersEnum = g.SmallGroupMemberSearchResult.Properties["member"]; 1790 if ((g.SmallGroupMemberSearchResult != null) && g.SmallGroupMemberSearchResult.Properties["member"].Contains(principalDN)) 1791 { 1792 return true; 1793 } 1794 } 1795 else 1796 { 1797 // this is a large group. use range retrieval instead of simple attribute check. 1798 RangeRetriever rangeRetriever = new RangeRetriever(groupDE, "member", false); 1799 rangeRetriever.CacheValues = true; 1800 foreach (string memberDN in rangeRetriever) 1801 { 1802 if (principalDN.Equals(memberDN, StringComparison.OrdinalIgnoreCase)) 1803 { 1804 return true; 1805 } 1806 } 1807 rangeRetriever.Reset(); //Reset range-retriever enum, so that it can be traversed again. 1808 cachedMembersEnum = rangeRetriever; 1809 } 1810 } 1811 1812 // 1813 // Might be an FPO (either a real principal from another forest, or a fake principal 1814 // that AD represents as an FPO). Search for the FPO, and test its DN for membership. 1815 // 1816 1817 // Get the Principal's SID, so we can look it up by SID in our store 1818 SecurityIdentifier Sid = p.Sid; 1819 1820 if (Sid == null) 1821 { 1822 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "IsMemberOfInStore: no SID IC or null UrnValue"); 1823 throw new ArgumentException(SR.StoreCtxNeedValueSecurityIdentityClaimToQuery); 1824 } 1825 DirectoryEntry defaultNCDirEntry = null; 1826 DirectorySearcher ds = null; 1827 1828 try 1829 { 1830 string path = String.Format( 1831 CultureInfo.InvariantCulture, 1832 "LDAP://{0}/{1}", 1833 String.IsNullOrEmpty(this.UserSuppliedServerName) ? this.DnsHostName : this.UserSuppliedServerName, 1834 this.ContextBasePartitionDN 1835 ); 1836 1837 defaultNCDirEntry = SDSUtils.BuildDirectoryEntry(path, this.credentials, this.authTypes); 1838 1839 ds = new DirectorySearcher(defaultNCDirEntry); 1840 1841 // Pick some reasonable default values 1842 ds.ServerTimeLimit = new TimeSpan(0, 0, 30); // 30 seconds 1843 1844 // Build the LDAP filter, Convert the sid to SDDL format 1845 string stringSid = Utils.SecurityIdentifierToLdapHexFilterString(Sid); 1846 if (stringSid == null) 1847 { 1848 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "IsMemberOfInStore: bad SID IC"); 1849 throw new ArgumentException(SR.StoreCtxNeedValueSecurityIdentityClaimToQuery); 1850 } 1851 1852 ds.Filter = "(&(objectClass=foreignSecurityPrincipal)(objectSid=" + stringSid + "))"; 1853 GlobalDebug.WriteLineIf(GlobalDebug.Info, 1854 "ADStoreCtx", 1855 "IsMemberOfInStore: FPO principal, using LDAP filter {0}", 1856 ds.Filter); 1857 1858 ds.PropertiesToLoad.Add("distinguishedName"); 1859 1860 SearchResult sr = ds.FindOne(); 1861 1862 // No FPO ---> not a member 1863 if (sr == null) 1864 { 1865 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: no FPO found"); 1866 return false; 1867 } 1868 string fpoDN = (string)sr.Properties["distinguishedName"][0]; 1869 foreach (string memberDN in cachedMembersEnum) 1870 { 1871 if (string.Equals(fpoDN, memberDN, StringComparison.OrdinalIgnoreCase)) 1872 { 1873 return true; 1874 } 1875 } 1876 return false; 1877 } 1878 catch (System.Runtime.InteropServices.COMException e) 1879 { 1880 throw ExceptionHelper.GetExceptionFromCOMException(e); 1881 } 1882 finally 1883 { 1884 if (ds != null) 1885 { 1886 ds.Dispose(); 1887 } 1888 if (defaultNCDirEntry != null) 1889 { 1890 defaultNCDirEntry.Dispose(); 1891 } 1892 } 1893 } 1894 1895 // The only reason a Clear() operation can not be performed on the group is if there 1896 // are one or more members on the store who are a member of the group by virtue of their 1897 // primaryGroupId, rather than by the group's "member" attribute. CanGroupBeCleared(GroupPrincipal g, out string explanationForFailure)1898 internal override bool CanGroupBeCleared(GroupPrincipal g, out string explanationForFailure) 1899 { 1900 explanationForFailure = null; 1901 1902 // If the group is unpersisted, there are certainly no principals in the store who 1903 // are a member of it by vitue of primaryGroupId. If the group is a fake group, it has no 1904 // members. Either way, they can clear it. 1905 if (g.unpersisted || g.fakePrincipal) 1906 { 1907 GlobalDebug.WriteLineIf(GlobalDebug.Info, 1908 "ADStoreCtx", 1909 "CanGroupBeCleared: unpersisted={0}, fake={1}", 1910 g.unpersisted, 1911 g.fakePrincipal); 1912 return true; 1913 } 1914 1915 Debug.Assert(g.UnderlyingObject != null); 1916 DirectoryEntry groupDE = (DirectoryEntry)g.UnderlyingObject; 1917 1918 // Create a DirectorySearcher for users who are members of this group via their primaryGroupId attribute 1919 DirectorySearcher ds = null; 1920 1921 try 1922 { 1923 if (groupDE.Properties["objectSid"].Count > 0) 1924 { 1925 Debug.Assert(groupDE.Properties["objectSid"].Count == 1); 1926 byte[] groupSid = (byte[])groupDE.Properties["objectSid"][0]; 1927 1928 ds = GetDirectorySearcherFromGroupID(groupSid); 1929 1930 // We only need to know if there's at least one such user 1931 ds.SizeLimit = 1; 1932 1933 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: using LDAP filter {0}", ds.Filter); 1934 1935 SearchResult sr = ds.FindOne(); 1936 1937 if (sr != null) 1938 { 1939 // there is such a member, we can't clear the group 1940 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: found member, can't clear"); 1941 1942 explanationForFailure = SR.ADStoreCtxCantClearGroup; 1943 return false; 1944 } 1945 else 1946 { 1947 // no such members, we can clear the group 1948 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: no member, can clear"); 1949 1950 return true; 1951 } 1952 } 1953 else 1954 { 1955 // We don't have sufficient information. Assume we can clear it. 1956 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "IsMemberOfInStore: can't search, assume can clear"); 1957 return true; 1958 } 1959 } 1960 catch (System.Runtime.InteropServices.COMException e) 1961 { 1962 throw ExceptionHelper.GetExceptionFromCOMException(e); 1963 } 1964 finally 1965 { 1966 if (ds != null) 1967 ds.Dispose(); 1968 } 1969 } 1970 1971 // The only reason we wouldn't be able to remove this member is if it's a member by virtue of its 1972 // primaryGroupId rather than the group's "member" attribute CanGroupMemberBeRemoved(GroupPrincipal g, Principal member, out string explanationForFailure)1973 internal override bool CanGroupMemberBeRemoved(GroupPrincipal g, Principal member, out string explanationForFailure) 1974 { 1975 explanationForFailure = null; 1976 1977 // If the member is unpersisted, it has no primaryGroupId attribute that could point to the group. 1978 // If the member is a fake princiapl, it also has no primaryGroupId attribute that could point to the group. 1979 // So either way, we have no objections to it being removed from the group. 1980 if (member.unpersisted || member.fakePrincipal) 1981 { 1982 GlobalDebug.WriteLineIf(GlobalDebug.Info, 1983 "ADStoreCtx", 1984 "CanGroupMemberBeRemoved: member unpersisted={0}, fake={1}", 1985 member.unpersisted, 1986 member.fakePrincipal); 1987 1988 return true; 1989 } 1990 1991 // If the group is unpersisted, then clearly there is no way the principal is 1992 // a member of it by vitue of primaryGroupId. If the group is a fake group, it has no 1993 // members and so we don't care about it. 1994 if (g.unpersisted || g.fakePrincipal) 1995 { 1996 GlobalDebug.WriteLineIf(GlobalDebug.Info, 1997 "ADStoreCtx", 1998 "CanGroupMemberBeRemoved: group unpersisted={0}, fake={1}", 1999 g.unpersisted, 2000 g.fakePrincipal); 2001 return true; 2002 } 2003 2004 // ADAM groups can have AD or ADAM members, AD groups can only have AD members 2005 //, but we could be called before our caller 2006 // has verified the principal being passed in as the member. Just ignore it if the member isn't an AD principal, 2007 // it'll be caught later in PrincipalCollection.Remove(). 2008 if ((g.ContextType == ContextType.Domain && member.ContextType != ContextType.Domain) || 2009 (member.ContextType != ContextType.Domain && member.ContextType != ContextType.ApplicationDirectory)) 2010 { 2011 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CanGroupMemberBeRemoved: member is not a domain or application directory principal"); 2012 return true; 2013 } 2014 2015 try 2016 { 2017 Debug.Assert(g.UnderlyingObject != null); 2018 Debug.Assert(member.UnderlyingObject != null); 2019 DirectoryEntry groupDE = (DirectoryEntry)g.UnderlyingObject; 2020 DirectoryEntry memberDE = (DirectoryEntry)member.UnderlyingObject; 2021 2022 if ((groupDE.Properties["objectSid"].Count > 0) && 2023 (memberDE.Properties["primaryGroupID"].Count > 0)) 2024 { 2025 Debug.Assert(groupDE.Properties["objectSid"].Count == 1); 2026 Debug.Assert(memberDE.Properties["primaryGroupID"].Count == 1); 2027 2028 byte[] groupSid = (byte[])groupDE.Properties["objectSid"][0]; 2029 int primaryGroupID = (int)memberDE.Properties["primaryGroupID"][0]; 2030 2031 int groupRid = Utils.GetLastRidFromSid(groupSid); 2032 2033 if (groupRid == primaryGroupID) 2034 { 2035 // It is a primary group member, we can't remove it. 2036 GlobalDebug.WriteLineIf(GlobalDebug.Info, 2037 "ADStoreCtx", 2038 "CanGroupMemberBeRemoved: primary group member (rid={0}), can't remove", 2039 groupRid); 2040 2041 explanationForFailure = SR.ADStoreCtxCantRemoveMemberFromGroup; 2042 return false; 2043 } 2044 else 2045 { 2046 // It's not a primary group member, we can remove it. 2047 GlobalDebug.WriteLineIf(GlobalDebug.Info, 2048 "ADStoreCtx", 2049 "CanGroupMemberBeRemoved: not primary group member (group rid={0}, primary group={1}), can remove", 2050 groupRid, 2051 primaryGroupID); 2052 2053 return true; 2054 } 2055 } 2056 else 2057 { 2058 // We don't have sufficient information. Assume we can remove it. 2059 // If we can't, we'll get an exception when we try to save the changes. 2060 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "CanGroupMemberBeRemoved: can't test, assume can remove"); 2061 return true; 2062 } 2063 } 2064 catch (System.Runtime.InteropServices.COMException e) 2065 { 2066 throw ExceptionHelper.GetExceptionFromCOMException(e); 2067 } 2068 } 2069 2070 // 2071 // Cross-store support 2072 // 2073 2074 // Given a native store object that represents a "foreign" principal (e.g., a FPO object in this store that 2075 // represents a pointer to another store), maps that representation to the other store's StoreCtx and returns 2076 // a Principal from that other StoreCtx. The implementation of this method is highly dependent on the 2077 // details of the particular store, and must have knowledge not only of this StoreCtx, but also of how to 2078 // interact with other StoreCtxs to fulfill the request. 2079 // 2080 // This method is typically used by ResultSet implementations, when they're iterating over a collection 2081 // (e.g., of group membership) and encounter an entry that represents a foreign principal. ResolveCrossStoreRefToPrincipal(object o)2082 internal override Principal ResolveCrossStoreRefToPrincipal(object o) 2083 { 2084 Debug.Assert(o is DirectoryEntry); 2085 Debug.Assert(ADUtils.IsOfObjectClass((DirectoryEntry)o, "foreignSecurityPrincipal")); 2086 2087 try 2088 { 2089 // Get the SID of the foreign principal 2090 DirectoryEntry fpoDE = (DirectoryEntry)o; 2091 2092 if (fpoDE.Properties["objectSid"].Count == 0) 2093 { 2094 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "ResolveCrossStoreRefToPrincipal: no objectSid found"); 2095 throw new PrincipalOperationException(SR.ADStoreCtxCantRetrieveObjectSidForCrossStore); 2096 } 2097 2098 Debug.Assert(fpoDE.Properties["objectSid"].Count == 1); 2099 2100 byte[] sid = (byte[])fpoDE.Properties["objectSid"].Value; 2101 2102 // What type of SID is it? 2103 SidType sidType = Utils.ClassifySID(sid); 2104 2105 if (sidType == SidType.FakeObject) 2106 { 2107 // It's a FPO for something like NT AUTHORITY\NETWORK SERVICE. 2108 // There's no real store object corresponding to this FPO, so 2109 // fake a Principal. 2110 GlobalDebug.WriteLineIf(GlobalDebug.Info, 2111 "ADStoreCtx", 2112 "ResolveCrossStoreRefToPrincipal: fake principal, SID={0}", 2113 Utils.ByteArrayToString(sid)); 2114 2115 return ConstructFakePrincipalFromSID(sid); 2116 } 2117 2118 StoreCtx foreignStoreCtx; 2119 if (sidType == SidType.RealObjectFakeDomain) 2120 { 2121 // This is a BUILTIN object. It's a real object on the store we're connected to, but LookupSid 2122 // will tell us it's a member of the BUILTIN domain. Resolve it as a principal on our store. 2123 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "ResolveCrossStoreRefToPrincipal: builtin principal"); 2124 2125 foreignStoreCtx = this; 2126 } 2127 else 2128 { 2129 // Ask the OS to resolve the SID to its target. 2130 UnsafeNativeMethods.IAdsObjectOptions objOptions = (UnsafeNativeMethods.IAdsObjectOptions)this.ctxBase.NativeObject; 2131 string serverName = (string)objOptions.GetOption(0 /* == ADS_OPTION_SERVERNAME */); 2132 2133 int accountUsage = 0; 2134 2135 string name; 2136 string domainName; 2137 2138 int err = Utils.LookupSid(serverName, this.credentials, sid, out name, out domainName, out accountUsage); 2139 2140 if (err != 0) 2141 { 2142 GlobalDebug.WriteLineIf(GlobalDebug.Warn, 2143 "ADStoreCtx", 2144 "ResolveCrossStoreRefToPrincipal: LookupSid failed, err={0}, server={1}", 2145 err, 2146 serverName); 2147 2148 throw new PrincipalOperationException( 2149 String.Format(CultureInfo.CurrentCulture, 2150 SR.ADStoreCtxCantResolveSidForCrossStore, 2151 err)); 2152 } 2153 2154 GlobalDebug.WriteLineIf(GlobalDebug.Info, 2155 "ADStoreCtx", 2156 "ResolveCrossStoreRefToPrincipal: LookupSid found {0} in {1}", 2157 name, 2158 domainName); 2159 2160 // Since this is AD, the remote principal must be an AD principal. 2161 // Build a PrincipalContext for the store which owns the principal 2162 2163 // We are now connecting to AD so change to negotiate with signing and sealing 2164 2165 ContextOptions remoteOptions = DefaultContextOptions.ADDefaultContextOption; 2166 2167 #if USE_CTX_CACHE 2168 PrincipalContext remoteCtx = SDSCache.Domain.GetContext(domainName, this.credentials, remoteOptions); 2169 #else 2170 PrincipalContext remoteCtx = new PrincipalContext( 2171 ContextType.Domain, 2172 domainName, 2173 null, 2174 (this.credentials != null ? credentials.UserName : null), 2175 (this.credentials != null ? credentials.Password : null), 2176 remoteOptions); 2177 2178 #endif 2179 foreignStoreCtx = remoteCtx.QueryCtx; 2180 } 2181 2182 Principal p = foreignStoreCtx.FindPrincipalByIdentRef( 2183 typeof(Principal), 2184 UrnScheme.SidScheme, 2185 (new SecurityIdentifier(sid, 0)).ToString(), 2186 DateTime.UtcNow); 2187 2188 if (p != null) 2189 return p; 2190 else 2191 { 2192 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "ResolveCrossStoreRefToPrincipal: no matching principal"); 2193 throw new PrincipalOperationException(SR.ADStoreCtxFailedFindCrossStoreTarget); 2194 } 2195 } 2196 catch (System.Runtime.InteropServices.COMException e) 2197 { 2198 throw ExceptionHelper.GetExceptionFromCOMException(e); 2199 } 2200 } 2201 2202 // Returns true if AccountInfo is supported for the specified principal, false otherwise. 2203 // Used when an application tries to access the AccountInfo property of a newly-inserted 2204 // (not yet persisted) AuthenticablePrincipal, to determine whether it should be allowed. SupportsAccounts(AuthenticablePrincipal p)2205 internal override bool SupportsAccounts(AuthenticablePrincipal p) 2206 { 2207 // Fake principals do not have store objects, so they certainly don't have stored account info. 2208 if (p.fakePrincipal) 2209 return false; 2210 2211 // Computer is a subclass of user in AD, and both therefore support account info. 2212 return true; 2213 } 2214 2215 // Returns the set of credential types supported by this store for the specified principal. 2216 // Used when an application tries to access the PasswordInfo property of a newly-inserted 2217 // (not yet persisted) AuthenticablePrincipal, to determine whether it should be allowed. 2218 // Also used to implement AuthenticablePrincipal.SupportedCredentialTypes. SupportedCredTypes(AuthenticablePrincipal p)2219 internal override CredentialTypes SupportedCredTypes(AuthenticablePrincipal p) 2220 { 2221 // Fake principals do not have store objects, so they certainly don't have stored creds. 2222 if (p.fakePrincipal) 2223 return (CredentialTypes)0; 2224 2225 return CredentialTypes.Password | CredentialTypes.Certificate; 2226 } 2227 2228 // 2229 // When called, this function does a GetInfoEx() to preload the DirectoryEntry's 2230 // property cache with all the attributes we'll be using. This avoids DirectoryEntry 2231 // doing an implicit GetInfo() and pulling down every attribute. 2232 // 2233 // This function is currently loading every attribute from the directory instead of using the known list. LoadDirectoryEntryAttributes(DirectoryEntry de)2234 internal void LoadDirectoryEntryAttributes(DirectoryEntry de) 2235 { 2236 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadDirectoryEntryAttributes, path={0}", de.Path); 2237 2238 string[] ldapAttributesUsed = new string[] 2239 { 2240 "accountExpires", 2241 "badPasswordTime", 2242 "badPwdCount", 2243 "displayName", 2244 "distinguishedName", 2245 "description", 2246 "employeeID", 2247 "givenName", 2248 "groupType", 2249 "homeDirectory", 2250 "homeDrive", 2251 "lastLogon", 2252 "lastLogonTimestamp", 2253 "lockoutTime", 2254 "logonHours", 2255 "mail", 2256 "member", 2257 "memberOf", 2258 "middleName", 2259 "msDS-User-Account-Control-Computed", 2260 "ntSecurityDescriptor", 2261 "objectClass", 2262 "objectGuid", 2263 "objectSid", 2264 "primaryGroupID", 2265 "pwdLastSet", 2266 "samAccountName", 2267 "scriptPath", 2268 "servicePrincipalName", 2269 "sn", 2270 "telephoneNumber", 2271 "userAccountControl", 2272 "userCertificate", 2273 "userPrincipalName", 2274 "userWorkstations" 2275 }; 2276 try 2277 { 2278 // de.RefreshCache(ldapAttributesUsed); 2279 de.RefreshCache(); 2280 } 2281 catch (System.Runtime.InteropServices.COMException e) 2282 { 2283 throw ExceptionHelper.GetExceptionFromCOMException(e); 2284 } 2285 } 2286 2287 // 2288 // Construct a fake Principal to represent a well-known SID like 2289 // "\Everyone" or "NT AUTHORITY\NETWORK SERVICE" 2290 // ConstructFakePrincipalFromSID(byte[] sid)2291 internal override Principal ConstructFakePrincipalFromSID(byte[] sid) 2292 { 2293 UnsafeNativeMethods.IAdsObjectOptions objOptions = (UnsafeNativeMethods.IAdsObjectOptions)this.ctxBase.NativeObject; 2294 string serverName = (string)objOptions.GetOption(0 /* == ADS_OPTION_SERVERNAME */); 2295 2296 GlobalDebug.WriteLineIf(GlobalDebug.Info, 2297 "ADStoreCtx", 2298 "ConstructFakePrincipalFromSID: sid={0}, server={1}, authority={2}", 2299 Utils.ByteArrayToString(sid), 2300 serverName, 2301 this.DnsDomainName); 2302 2303 Principal p = Utils.ConstructFakePrincipalFromSID( 2304 sid, 2305 this.OwningContext, 2306 serverName, 2307 this.credentials, 2308 this.DnsDomainName); 2309 2310 // Assign it a StoreKey 2311 ADStoreKey key = new ADStoreKey(this.DnsDomainName, sid); 2312 p.Key = key; 2313 2314 return p; 2315 } 2316 2317 // 2318 // Private data 2319 // 2320 2321 /// 2322 /// <summary> 2323 /// Returns the DN of the Partition to which the user supplied 2324 /// context base (this.ctxBase) belongs. 2325 /// </summary> 2326 /// 2327 internal string ContextBasePartitionDN 2328 { 2329 get 2330 { 2331 if (this.contextBasePartitionDN == null) 2332 { 2333 lock (this.domainInfoLock) 2334 { 2335 if (contextBasePartitionDN == null) 2336 LoadDomainInfo(); 2337 } 2338 } 2339 2340 return this.contextBasePartitionDN; 2341 } 2342 } 2343 2344 internal string DefaultNamingContext 2345 { 2346 get 2347 { 2348 if (this.defaultNamingContext == null) 2349 { 2350 lock (this.domainInfoLock) 2351 { 2352 if (defaultNamingContext == null) 2353 LoadDomainInfo(); 2354 } 2355 } 2356 2357 return this.defaultNamingContext; 2358 } 2359 } 2360 2361 private string FlatDomainName 2362 { 2363 get 2364 { 2365 if (this.domainFlatName == null) 2366 { 2367 lock (this.domainInfoLock) 2368 { 2369 if (domainFlatName == null) 2370 LoadDomainInfo(); 2371 } 2372 } 2373 2374 return this.domainFlatName; 2375 } 2376 } 2377 2378 internal string DnsDomainName 2379 { 2380 get 2381 { 2382 if (this.domainDnsName == null) 2383 { 2384 lock (this.domainInfoLock) 2385 { 2386 if (domainDnsName == null) 2387 LoadDomainInfo(); 2388 } 2389 } 2390 2391 return this.domainDnsName; 2392 } 2393 } 2394 2395 internal string DnsHostName 2396 { 2397 get 2398 { 2399 if (this.dnsHostName == null) 2400 { 2401 lock (this.domainInfoLock) 2402 { 2403 if (dnsHostName == null) 2404 LoadDomainInfo(); 2405 } 2406 } 2407 2408 return this.dnsHostName; 2409 } 2410 } 2411 2412 internal string DnsForestName 2413 { 2414 get 2415 { 2416 if (this.forestDnsName == null) 2417 { 2418 lock (this.domainInfoLock) 2419 { 2420 if (forestDnsName == null) 2421 LoadDomainInfo(); 2422 } 2423 } 2424 2425 return this.forestDnsName; 2426 } 2427 } 2428 2429 internal string UserSuppliedServerName 2430 { 2431 get 2432 { 2433 if (this.userSuppliedServerName == null) 2434 { 2435 lock (this.domainInfoLock) 2436 { 2437 if (this.userSuppliedServerName == null) 2438 LoadDomainInfo(); 2439 } 2440 } 2441 2442 return this.userSuppliedServerName; 2443 } 2444 } 2445 2446 private ulong LockoutDuration 2447 { 2448 get 2449 { 2450 // We test domainDnsName for null because lockoutDuration could legitimately be 0, 2451 // but lockoutDuration is valid iff domainDnsName is non-null 2452 if (this.domainDnsName == null) 2453 { 2454 lock (this.domainInfoLock) 2455 { 2456 if (domainDnsName == null) 2457 LoadDomainInfo(); 2458 } 2459 } 2460 2461 return this.lockoutDuration; 2462 } 2463 } 2464 2465 protected object domainInfoLock = new object(); 2466 protected string domainFlatName = null; 2467 protected string domainDnsName = null; 2468 protected string forestDnsName = null; 2469 protected string userSuppliedServerName = null; 2470 protected string defaultNamingContext = null; 2471 protected string contextBasePartitionDN = null; //contains the DN of the Partition to which the user supplied context base (this.ctxBase) belongs. 2472 protected string dnsHostName = null; 2473 protected ulong lockoutDuration = 0; 2474 2475 protected enum StoreCapabilityMap 2476 { 2477 ASQSearch = 1, 2478 } 2479 2480 protected StoreCapabilityMap storeCapability = 0; 2481 2482 // Must be called inside of lock(domainInfoLock) LoadDomainInfo()2483 virtual protected void LoadDomainInfo() 2484 { 2485 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadComputerInfo"); 2486 2487 Debug.Assert(this.ctxBase != null); 2488 2489 // 2490 // DNS Domain Name 2491 // 2492 2493 // We need to connect to the server's rootDse to get the naming context 2494 // From that, we can build the DNS Domain Name 2495 this.dnsHostName = ADUtils.GetServerName(this.ctxBase); 2496 2497 string dnsDomainName = ""; 2498 2499 using (DirectoryEntry rootDse = new DirectoryEntry("LDAP://" + this.dnsHostName + "/rootDse", "", "", AuthenticationTypes.Anonymous)) 2500 { 2501 this.defaultNamingContext = (string)rootDse.Properties["defaultNamingContext"][0]; 2502 this.contextBasePartitionDN = this.defaultNamingContext; 2503 2504 // Split the naming context's DN into its RDNs 2505 string[] ncComponents = defaultNamingContext.Split(new char[] { ',' }); 2506 2507 StringBuilder sb = new StringBuilder(); 2508 2509 // Reassemble the RDNs (minus the tag) into the DNS domain name 2510 foreach (string component in ncComponents) 2511 { 2512 // If it's not a "DC=" component, skip it 2513 if ((component.Length > 3) && 2514 (String.Compare(component.Substring(0, 3), "DC=", StringComparison.OrdinalIgnoreCase) == 0)) 2515 { 2516 sb.Append(component.Substring(3)); 2517 sb.Append("."); 2518 } 2519 } 2520 2521 dnsDomainName = sb.ToString(); 2522 2523 // The loop added an extra period at the end. Remove it. 2524 if (dnsDomainName.Length > 0) 2525 dnsDomainName = dnsDomainName.Substring(0, dnsDomainName.Length - 1); 2526 2527 this.domainDnsName = dnsDomainName; 2528 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadComputerInfo: using DNS domain name {0}", dnsDomainName); 2529 } 2530 2531 // 2532 // NetBios (flat) Domain Name, and DNS Forest Name 2533 // 2534 // Given the DNS domain name, we use DsGetDcName to get the flat name. 2535 // The same DsGetDcName call also retrieves the DNS forest name as a side effect. 2536 // 2537 2538 // DS_IS_DNS_NAME | DS_RETURN_FLAT_NAME | DS_DIRECTORY_SERVICE_REQUIRED | DS_BACKGROUND_ONLY 2539 int flags = unchecked((int)(0x00020000 | 0x80000000 | 0x00000010 | 0x00000100)); 2540 UnsafeNativeMethods.DomainControllerInfo info = Utils.GetDcName(null, dnsDomainName, null, flags); 2541 2542 this.domainFlatName = info.DomainName; 2543 this.forestDnsName = info.DnsForestName; 2544 2545 GlobalDebug.WriteLineIf(GlobalDebug.Info, 2546 "ADStoreCtx", 2547 "LoadComputerInfo: using domainFlatName={0}, forestDnsName={1}", 2548 this.domainFlatName, 2549 this.forestDnsName); 2550 2551 // 2552 // Lockout duration 2553 // 2554 // Query the domain NC head to determine the lockout duration. Note that this is stored 2555 // on the server as a negative filetime. 2556 // 2557 DirectoryEntry domainNC = SDSUtils.BuildDirectoryEntry( 2558 "LDAP://" + this.dnsHostName + "/" + this.defaultNamingContext, 2559 this.credentials, 2560 this.authTypes); 2561 2562 // So we don't load every property 2563 domainNC.RefreshCache(new string[] { "lockoutDuration" }); 2564 2565 if (domainNC.Properties["lockoutDuration"].Count > 0) 2566 { 2567 Debug.Assert(domainNC.Properties["lockoutDuration"].Count == 1); 2568 long negativeLockoutDuration = ADUtils.LargeIntToInt64((UnsafeNativeMethods.IADsLargeInteger)domainNC.Properties["lockoutDuration"][0]); 2569 Debug.Assert(negativeLockoutDuration <= 0); 2570 ulong lockoutDuration = (ulong)(-negativeLockoutDuration); 2571 this.lockoutDuration = lockoutDuration; 2572 2573 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadComputerInfo: using lockout duration {0}", lockoutDuration); 2574 } 2575 2576 // 2577 // User supplied name 2578 // 2579 UnsafeNativeMethods.Pathname pathCracker = new UnsafeNativeMethods.Pathname(); 2580 UnsafeNativeMethods.IADsPathname pathName = (UnsafeNativeMethods.IADsPathname)pathCracker; 2581 2582 pathName.Set(this.ctxBase.Path, 1 /* ADS_SETTYPE_FULL */); 2583 2584 try 2585 { 2586 this.userSuppliedServerName = pathName.Retrieve(9 /*ADS_FORMAT_SERVER */); 2587 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadComputerInfo: using user-supplied name {0}", this.userSuppliedServerName); 2588 } 2589 catch (COMException e) 2590 { 2591 if (((uint)e.ErrorCode) == ((uint)0x80005000)) // E_ADS_BAD_PATHNAME 2592 { 2593 // Serverless path 2594 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadComputerInfo: using empty string as user-supplied name"); 2595 this.userSuppliedServerName = ""; 2596 } 2597 else 2598 { 2599 GlobalDebug.WriteLineIf(GlobalDebug.Error, 2600 "ADStoreCtx", 2601 "LoadComputerInfo: caught COMException {0} {1} looking for user-supplied name", 2602 e.ErrorCode, 2603 e.Message); 2604 2605 throw; 2606 } 2607 } 2608 } 2609 IsValidProperty(Principal p, string propertyName)2610 internal override bool IsValidProperty(Principal p, string propertyName) 2611 { 2612 return ((Hashtable)s_propertyMappingTableByProperty[this.MappingTableIndex]).Contains(propertyName); 2613 } 2614 } 2615 } 2616 2617 //#endif // PAPI_AD 2618 2619